vfs: Add open by file handle support
Aneesh Kumar K.V [Sat, 29 Jan 2011 13:13:26 +0000 (18:13 +0530)]
[AV: duplicate of open() guts removed; file_open_root() used instead]

Signed-off-by: Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>

fs/compat.c
fs/exportfs/expfs.c
fs/fhandle.c
fs/internal.h
include/linux/syscalls.h
kernel/sys_ni.c

index a071775..c6d31a3 100644 (file)
@@ -2284,3 +2284,16 @@ asmlinkage long compat_sys_timerfd_gettime(int ufd,
 }
 
 #endif /* CONFIG_TIMERFD */
+
+#ifdef CONFIG_FHANDLE
+/*
+ * Exactly like fs/open.c:sys_open_by_handle_at(), except that it
+ * doesn't set the O_LARGEFILE flag.
+ */
+asmlinkage long
+compat_sys_open_by_handle_at(int mountdirfd,
+                            struct file_handle __user *handle, int flags)
+{
+       return do_handle_open(mountdirfd, handle, flags);
+}
+#endif
index cfe5573..b05acb7 100644 (file)
@@ -374,6 +374,8 @@ struct dentry *exportfs_decode_fh(struct vfsmount *mnt, struct fid *fid,
        /*
         * Try to get any dentry for the given file handle from the filesystem.
         */
+       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);
index 9f79e74..bf93ad2 100644 (file)
@@ -5,6 +5,8 @@
 #include <linux/mount.h>
 #include <linux/namei.h>
 #include <linux/exportfs.h>
+#include <linux/fs_struct.h>
+#include <linux/fsnotify.h>
 #include <asm/uaccess.h>
 #include "internal.h"
 
@@ -105,3 +107,159 @@ SYSCALL_DEFINE5(name_to_handle_at, int, dfd, const char __user *, name,
        }
        return err;
 }
+
+static struct vfsmount *get_vfsmount_from_fd(int fd)
+{
+       struct path path;
+
+       if (fd == AT_FDCWD) {
+               struct fs_struct *fs = current->fs;
+               spin_lock(&fs->lock);
+               path = fs->pwd;
+               mntget(path.mnt);
+               spin_unlock(&fs->lock);
+       } else {
+               int fput_needed;
+               struct file *file = fget_light(fd, &fput_needed);
+               if (!file)
+                       return ERR_PTR(-EBADF);
+               path = file->f_path;
+               mntget(path.mnt);
+               fput_light(file, fput_needed);
+       }
+       return path.mnt;
+}
+
+static int vfs_dentry_acceptable(void *context, struct dentry *dentry)
+{
+       return 1;
+}
+
+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;
+}
+
+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;
+
+       /*
+        * With handle we don't look at the execute bit on the
+        * the directory. Ideally we would like CAP_DAC_SEARCH.
+        * But we don't have that
+        */
+       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;
+       }
+       /* copy the full handle */
+       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;
+}
+
+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;
+}
+
+/**
+ * sys_open_by_handle_at: Open the file handle
+ * @mountdirfd: directory file descriptor
+ * @handle: file handle to be opened
+ * @flag: open flags.
+ *
+ * @mountdirfd indicate the directory file descriptor
+ * of the mount point. file handle is decoded relative
+ * to the vfsmount pointed by the @mountdirfd. @flags
+ * value is same as the open(2) flags.
+ */
+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;
+}
index 52abc52..f3d15de 100644 (file)
@@ -117,6 +117,9 @@ extern struct file *do_filp_open(int dfd, const char *pathname,
 extern struct file *do_file_open_root(struct dentry *, struct vfsmount *,
                const char *, const struct open_flags *, int lookup_flags);
 
+extern long do_handle_open(int mountdirfd,
+                          struct file_handle __user *ufh, int open_flag);
+
 /*
  * inode.c
  */
index 9701126..2d9b79c 100644 (file)
@@ -836,4 +836,7 @@ asmlinkage long sys_old_mmap(struct mmap_arg_struct __user *arg);
 asmlinkage long sys_name_to_handle_at(int dfd, const char __user *name,
                                      struct file_handle __user *handle,
                                      int __user *mnt_id, int flag);
+asmlinkage long sys_open_by_handle_at(int mountdirfd,
+                                     struct file_handle __user *handle,
+                                     int flags);
 #endif
index 4e01343..25cc41c 100644 (file)
@@ -189,3 +189,5 @@ cond_syscall(sys_fanotify_mark);
 
 /* open by handle */
 cond_syscall(sys_name_to_handle_at);
+cond_syscall(sys_open_by_handle_at);
+cond_syscall(compat_sys_open_by_handle_at);