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); }