open_by_handle_atシステムコール
Rev.1を表示中。最新版はこちら。
force_o_largefile()で32ビットシステムならO_LARGEFILEとして、do_handle_open()でhandleのファイルIDを取得します。
SYSCALL_DEFINE3(open_by_handle_at, int, mountdirfd,
struct file_handle __user *, handle,
int, flags)
{
long ret;
if (force_o_largefile())
flags |= O_LARGEFILE;
ret = do_handle_open(mountdirfd, handle, flags);
return ret;
}
handle_to_path()でファイルハンドルからpathを設定し、get_unused_fd_flags()カレントプロセスの未使用のファイルIDを取得後、handle_to_path()で取得したpath.dentryでFILE構造体を取得して、fd_install()でファイルIDに設定します。そのIDを返しことで、ユーザプロセスはこのファイルIDで通常ファイルのread/writeシステムコールで読み書きします。
long do_handle_open(int mountdirfd,
struct file_handle __user *ufh, int open_flag)
{
long retval = 0;
struct path path;
struct file *file;
int fd;
retval = handle_to_path(mountdirfd, ufh, &path);
if (retval)
return retval;
fd = get_unused_fd_flags(open_flag);
if (fd < 0) {
path_put(&path);
return fd;
}
file = file_open_root(path.dentry, path.mnt, "", open_flag);
if (IS_ERR(file)) {
put_unused_fd(fd);
retval = PTR_ERR(file);
} else {
retval = fd;
fsnotify_open(file);
fd_install(fd, file);
}
path_put(&path);
return retval;
}
static int handle_to_path(int mountdirfd, struct file_handle __user *ufh,
struct path *path)
{
int retval = 0;
struct file_handle f_handle;
struct file_handle *handle = NULL;
if (!capable(CAP_DAC_READ_SEARCH)) {
retval = -EPERM;
goto out_err;
}
if (copy_from_user(&f_handle, ufh, sizeof(struct file_handle))) {
retval = -EFAULT;
goto out_err;
}
if ((f_handle.handle_bytes > MAX_HANDLE_SZ) ||
(f_handle.handle_bytes == 0)) {
retval = -EINVAL;
goto out_err;
}
handle = kmalloc(sizeof(struct file_handle) + f_handle.handle_bytes,
GFP_KERNEL);
if (!handle) {
retval = -ENOMEM;
goto out_err;
}
if (copy_from_user(handle, ufh,
sizeof(struct file_handle) +
f_handle.handle_bytes)) {
retval = -EFAULT;
goto out_handle;
}
retval = do_handle_to_path(mountdirfd, handle, path);
out_handle:
kfree(handle);
out_err:
return retval;
}
get_vfsmount_from_fd()でマウント元のファイルIDのmountdirfdからvfsmount構造体を取得します。この時mountdirfd=AT_FDCWDなら、current->fs->pwdから取得し、exportfs_decode_fh()でhandle->f_handleのinode番号で、dentryを取得します。
static int do_handle_to_path(int mountdirfd, struct file_handle *handle,
struct path *path)
{
int retval = 0;
int handle_dwords;
path->mnt = get_vfsmount_from_fd(mountdirfd);
if (IS_ERR(path->mnt)) {
retval = PTR_ERR(path->mnt);
goto out_err;
}
/* change the handle size to multiple of sizeof(u32) */
handle_dwords = handle->handle_bytes >> 2;
path->dentry = exportfs_decode_fh(path->mnt,
(struct fid *)handle->f_handle,
handle_dwords, handle->handle_type,
vfs_dentry_acceptable, NULL);
if (IS_ERR(path->dentry)) {
retval = PTR_ERR(path->dentry);
goto out_mnt;
}
return 0;
out_mnt:
mntput(path->mnt);
out_err:
return retval;
}
スーパブロックのexport_operationsコールバックのfh_to_dentry()でdentryを取得します。取得したdentryがディレクトリの時、親ディレクトリ下に無ければ、reconnect_path()で親に設定します。事の時、ファイルシステムのrootまで、順次、親ディレクトリの親ディレクトリも同様にチェックします。取得したdentryが通常ファイルなら、このdentryがアクセス可能かチェックし、OKならそのdentryを返します。取得できないなら親ディレクトリにこのdentryを登録しなければなりません。親ディレクトリを取得し、dentryがDCACHE_DISCONNECTEDされてないかチェックします。なお、この親ディレクトリも取得したdentryがディレクトリの時と同様reconnect_path()をコールします。通常ファイルがアクセスできない場合、親ディレクトリその物が無くなっている事もあるからです。
ここまでOKなら、exportfs_get_name()で親ディレクトリにこのdentryに、nop->fh_to_dentry()のdentryが登録されているかチェックし必要なら登録します。
struct dentry *exportfs_decode_fh(struct vfsmount *mnt, struct fid *fid,
int fh_len, int fileid_type,
int (*acceptable)(void *, struct dentry *), void *context)
{
const struct export_operations *nop = mnt->mnt_sb->s_export_op;
struct dentry *result, *alias;
char nbuf[NAME_MAX+1];
int err;
if (!nop || !nop->fh_to_dentry)
return ERR_PTR(-ESTALE);
result = nop->fh_to_dentry(mnt->mnt_sb, fid, fh_len, fileid_type);
if (!result)
result = ERR_PTR(-ESTALE);
if (IS_ERR(result))
return result;
if (S_ISDIR(result->d_inode->i_mode)) {
if (result->d_flags & DCACHE_DISCONNECTED) {
err = reconnect_path(mnt, result, nbuf);
if (err)
goto err_result;
}
if (!acceptable(context, result)) {
err = -EACCES;
goto err_result;
}
return result;
} else {
struct dentry *target_dir, *nresult;
alias = find_acceptable_alias(result, acceptable, context);
if (alias)
return alias;
err = -ESTALE;
if (!nop->fh_to_parent)
goto err_result;
target_dir = nop->fh_to_parent(mnt->mnt_sb, fid,
fh_len, fileid_type);
if (!target_dir)
goto err_result;
err = PTR_ERR(target_dir);
if (IS_ERR(target_dir))
goto err_result;
err = reconnect_path(mnt, target_dir, nbuf);
if (err) {
dput(target_dir);
goto err_result;
}
err = exportfs_get_name(mnt, target_dir, nbuf, result);
if (!err) {
mutex_lock(&target_dir->d_inode->i_mutex);
nresult = lookup_one_len(nbuf, target_dir,
strlen(nbuf));
mutex_unlock(&target_dir->d_inode->i_mutex);
if (!IS_ERR(nresult)) {
if (nresult->d_inode) {
dput(result);
result = nresult;
} else
dput(nresult);
}
}
dput(target_dir);
alias = find_acceptable_alias(result, acceptable, context);
if (!alias) {
err = -EACCES;
goto err_result;
}
return alias;
}
err_result:
dput(result);
return ERR_PTR(err);
}
ext2のexport_operations の.fh_to_dentry コールバック関数です。引数のext2_nfs_get_inodeは、ファイルシステムに依存したスーパブロックからinodeを取得する関数ポインターです。
static struct dentry *ext2_fh_to_dentry(struct super_block *sb, struct fid *fid,
int fh_len, int fh_type)
{
return generic_fh_to_dentry(sb, fid, fh_len, fh_type,
ext2_nfs_get_inode);
}
get_inode()でスーパブロックからfhandleのinoに一致するinodeを検索し、inodeを取得します。get_inode()はファイルシステムに依存し、ext2ではext2_nfs_get_inode()がコールされ、inoに一致するinode取得します。inodeが取得できたら、d_obtain_alias()でそのinodeのdentryを取得します。d_obtain_alias()で新規にdentryを割り当てる場合、inode->i_dentryに割り当てたdentryがリストされますが、別名としてdentry.d_aliasがリストされ、nfs等で割り当てられたdentryと通常のdentryを管理しています。
struct dentry *generic_fh_to_dentry(struct super_block *sb, struct fid *fid,
int fh_len, int fh_type, struct inode *(*get_inode)
(struct super_block *sb, u64 ino, u32 gen))
{
struct inode *inode = NULL;
if (fh_len < 2)
return NULL;
switch (fh_type) {
case FILEID_INO32_GEN:
case FILEID_INO32_GEN_PARENT:
inode = get_inode(sb, fid->i32.ino, fid->i32.gen);
break;
}
return d_obtain_alias(inode);
}
static struct inode *ext2_nfs_get_inode(struct super_block *sb,
u64 ino, u32 generation)
{
struct inode *inode;
if (ino < EXT2_FIRST_INO(sb) && ino != EXT2_ROOT_INO)
return ERR_PTR(-ESTALE);
if (ino > le32_to_cpu(EXT2_SB(sb)->s_es->s_inodes_count))
return ERR_PTR(-ESTALE);
inode = ext2_iget(sb, ino);
if (IS_ERR(inode))
return ERR_CAST(inode);
if (generation && inode->i_generation != generation) {
iput(inode);
return ERR_PTR(-ESTALE);
}
return inode;
}
iget_locked()でスーパブロックからinoに一致するinodeを返します。もし一致するinodeがなければ新規にinodeを割り当てます。新規にinodeを割り当てたらinode->i_state & I_NEWとなり、この時、元のinode情報/コールバック関数が設定されます。le16_to_cpu()エンディアン処理で、実デバイスデータはリトルエンディアンです。
struct inode *ext2_iget (struct super_block *sb, unsigned long ino)
{
struct ext2_inode_info *ei;
struct buffer_head * bh;
struct ext2_inode *raw_inode;
struct inode *inode;
long ret = -EIO;
int n;
inode = iget_locked(sb, ino);
if (!inode)
return ERR_PTR(-ENOMEM);
if (!(inode->i_state & I_NEW))
return inode;
ei = EXT2_I(inode);
ei->i_block_alloc_info = NULL;
raw_inode = ext2_get_inode(inode->i_sb, ino, &bh);
if (IS_ERR(raw_inode)) {
ret = PTR_ERR(raw_inode);
goto bad_inode;
}
inode->i_mode = le16_to_cpu(raw_inode->i_mode);
inode->i_uid = (uid_t)le16_to_cpu(raw_inode->i_uid_low);
inode->i_gid = (gid_t)le16_to_cpu(raw_inode->i_gid_low);
if (!(test_opt (inode->i_sb, NO_UID32))) {
inode->i_uid |= le16_to_cpu(raw_inode->i_uid_high) << 16;
inode->i_gid |= le16_to_cpu(raw_inode->i_gid_high) << 16;
}
set_nlink(inode, le16_to_cpu(raw_inode->i_links_count));
inode->i_size = le32_to_cpu(raw_inode->i_size);
inode->i_atime.tv_sec = (signed)le32_to_cpu(raw_inode->i_atime);
inode->i_ctime.tv_sec = (signed)le32_to_cpu(raw_inode->i_ctime);
inode->i_mtime.tv_sec = (signed)le32_to_cpu(raw_inode->i_mtime);
inode->i_atime.tv_nsec = inode->i_mtime.tv_nsec = inode->i_ctime.tv_nsec = 0;
ei->i_dtime = le32_to_cpu(raw_inode->i_dtime);
if (inode->i_nlink == 0 && (inode->i_mode == 0 || ei->i_dtime)) {
brelse (bh);
ret = -ESTALE;
goto bad_inode;
}
inode->i_blocks = le32_to_cpu(raw_inode->i_blocks);
ei->i_flags = le32_to_cpu(raw_inode->i_flags);
ei->i_faddr = le32_to_cpu(raw_inode->i_faddr);
ei->i_frag_no = raw_inode->i_frag;
ei->i_frag_size = raw_inode->i_fsize;
ei->i_file_acl = le32_to_cpu(raw_inode->i_file_acl);
ei->i_dir_acl = 0;
if (S_ISREG(inode->i_mode))
inode->i_size |= ((__u64)le32_to_cpu(raw_inode->i_size_high)) << 32;
else
ei->i_dir_acl = le32_to_cpu(raw_inode->i_dir_acl);
ei->i_dtime = 0;
inode->i_generation = le32_to_cpu(raw_inode->i_generation);
ei->i_state = 0;
ei->i_block_group = (ino - 1) / EXT2_INODES_PER_GROUP(inode->i_sb);
ei->i_dir_start_lookup = 0;
for (n = 0; n < EXT2_N_BLOCKS; n++)
ei->i_data[n] = raw_inode->i_block[n];
if (S_ISREG(inode->i_mode)) {
inode->i_op = &ext2_file_inode_operations;
if (ext2_use_xip(inode->i_sb)) {
inode->i_mapping->a_ops = &ext2_aops_xip;
inode->i_fop = &ext2_xip_file_operations;
} else if (test_opt(inode->i_sb, NOBH)) {
inode->i_mapping->a_ops = &ext2_nobh_aops;
inode->i_fop = &ext2_file_operations;
} else {
inode->i_mapping->a_ops = &ext2_aops;
inode->i_fop = &ext2_file_operations;
}
} else if (S_ISDIR(inode->i_mode)) {
inode->i_op = &ext2_dir_inode_operations;
inode->i_fop = &ext2_dir_operations;
if (test_opt(inode->i_sb, NOBH))
inode->i_mapping->a_ops = &ext2_nobh_aops;
else
inode->i_mapping->a_ops = &ext2_aops;
} else if (S_ISLNK(inode->i_mode)) {
if (ext2_inode_is_fast_symlink(inode)) {
inode->i_op = &ext2_fast_symlink_inode_operations;
nd_terminate_link(ei->i_data, inode->i_size,
sizeof(ei->i_data) - 1);
} else {
inode->i_op = &ext2_symlink_inode_operations;
if (test_opt(inode->i_sb, NOBH))
inode->i_mapping->a_ops = &ext2_nobh_aops;
else
inode->i_mapping->a_ops = &ext2_aops;
}
} else {
inode->i_op = &ext2_special_inode_operations;
if (raw_inode->i_block[0])
init_special_inode(inode, inode->i_mode,
old_decode_dev(le32_to_cpu(raw_inode->i_block[0])));
else
init_special_inode(inode, inode->i_mode,
new_decode_dev(le32_to_cpu(raw_inode->i_block[1])));
}
brelse (bh);
ext2_set_inode_flags(inode);
unlock_new_inode(inode);
return inode;
bad_inode:
iget_failed(inode);
return ERR_PTR(ret);
}





