Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/mason/linux...
[firefly-linux-kernel-4.4.55.git] / fs / btrfs / ioctl.c
index 82c18ba12e3f62e4ff3c71daf2269d353c4f18ce..0d321c23069a0aa9ea1aeac570bdf837cf208d24 100644 (file)
@@ -1957,7 +1957,8 @@ static noinline int copy_to_sk(struct btrfs_root *root,
                               struct btrfs_path *path,
                               struct btrfs_key *key,
                               struct btrfs_ioctl_search_key *sk,
-                              char *buf,
+                              size_t *buf_size,
+                              char __user *ubuf,
                               unsigned long *sk_offset,
                               int *num_found)
 {
@@ -1989,13 +1990,25 @@ static noinline int copy_to_sk(struct btrfs_root *root,
                if (!key_in_sk(key, sk))
                        continue;
 
-               if (sizeof(sh) + item_len > BTRFS_SEARCH_ARGS_BUFSIZE)
+               if (sizeof(sh) + item_len > *buf_size) {
+                       if (*num_found) {
+                               ret = 1;
+                               goto out;
+                       }
+
+                       /*
+                        * return one empty item back for v1, which does not
+                        * handle -EOVERFLOW
+                        */
+
+                       *buf_size = sizeof(sh) + item_len;
                        item_len = 0;
+                       ret = -EOVERFLOW;
+               }
 
-               if (sizeof(sh) + item_len + *sk_offset >
-                   BTRFS_SEARCH_ARGS_BUFSIZE) {
+               if (sizeof(sh) + item_len + *sk_offset > *buf_size) {
                        ret = 1;
-                       goto overflow;
+                       goto out;
                }
 
                sh.objectid = key->objectid;
@@ -2005,20 +2018,33 @@ static noinline int copy_to_sk(struct btrfs_root *root,
                sh.transid = found_transid;
 
                /* copy search result header */
-               memcpy(buf + *sk_offset, &sh, sizeof(sh));
+               if (copy_to_user(ubuf + *sk_offset, &sh, sizeof(sh))) {
+                       ret = -EFAULT;
+                       goto out;
+               }
+
                *sk_offset += sizeof(sh);
 
                if (item_len) {
-                       char *p = buf + *sk_offset;
+                       char __user *up = ubuf + *sk_offset;
                        /* copy the item */
-                       read_extent_buffer(leaf, p,
-                                          item_off, item_len);
+                       if (read_extent_buffer_to_user(leaf, up,
+                                                      item_off, item_len)) {
+                               ret = -EFAULT;
+                               goto out;
+                       }
+
                        *sk_offset += item_len;
                }
                (*num_found)++;
 
-               if (*num_found >= sk->nr_items)
-                       break;
+               if (ret) /* -EOVERFLOW from above */
+                       goto out;
+
+               if (*num_found >= sk->nr_items) {
+                       ret = 1;
+                       goto out;
+               }
        }
 advance_key:
        ret = 0;
@@ -2033,22 +2059,37 @@ advance_key:
                key->objectid++;
        } else
                ret = 1;
-overflow:
+out:
+       /*
+        *  0: all items from this leaf copied, continue with next
+        *  1: * more items can be copied, but unused buffer is too small
+        *     * all items were found
+        *     Either way, it will stops the loop which iterates to the next
+        *     leaf
+        *  -EOVERFLOW: item was to large for buffer
+        *  -EFAULT: could not copy extent buffer back to userspace
+        */
        return ret;
 }
 
 static noinline int search_ioctl(struct inode *inode,
-                                struct btrfs_ioctl_search_args *args)
+                                struct btrfs_ioctl_search_key *sk,
+                                size_t *buf_size,
+                                char __user *ubuf)
 {
        struct btrfs_root *root;
        struct btrfs_key key;
        struct btrfs_path *path;
-       struct btrfs_ioctl_search_key *sk = &args->key;
        struct btrfs_fs_info *info = BTRFS_I(inode)->root->fs_info;
        int ret;
        int num_found = 0;
        unsigned long sk_offset = 0;
 
+       if (*buf_size < sizeof(struct btrfs_ioctl_search_header)) {
+               *buf_size = sizeof(struct btrfs_ioctl_search_header);
+               return -EOVERFLOW;
+       }
+
        path = btrfs_alloc_path();
        if (!path)
                return -ENOMEM;
@@ -2082,14 +2123,15 @@ static noinline int search_ioctl(struct inode *inode,
                                ret = 0;
                        goto err;
                }
-               ret = copy_to_sk(root, path, &key, sk, args->buf,
+               ret = copy_to_sk(root, path, &key, sk, buf_size, ubuf,
                                 &sk_offset, &num_found);
                btrfs_release_path(path);
-               if (ret || num_found >= sk->nr_items)
+               if (ret)
                        break;
 
        }
-       ret = 0;
+       if (ret > 0)
+               ret = 0;
 err:
        sk->nr_items = num_found;
        btrfs_free_path(path);
@@ -2099,22 +2141,73 @@ err:
 static noinline int btrfs_ioctl_tree_search(struct file *file,
                                           void __user *argp)
 {
-        struct btrfs_ioctl_search_args *args;
-        struct inode *inode;
-        int ret;
+       struct btrfs_ioctl_search_args __user *uargs;
+       struct btrfs_ioctl_search_key sk;
+       struct inode *inode;
+       int ret;
+       size_t buf_size;
 
        if (!capable(CAP_SYS_ADMIN))
                return -EPERM;
 
-       args = memdup_user(argp, sizeof(*args));
-       if (IS_ERR(args))
-               return PTR_ERR(args);
+       uargs = (struct btrfs_ioctl_search_args __user *)argp;
+
+       if (copy_from_user(&sk, &uargs->key, sizeof(sk)))
+               return -EFAULT;
+
+       buf_size = sizeof(uargs->buf);
 
        inode = file_inode(file);
-       ret = search_ioctl(inode, args);
-       if (ret == 0 && copy_to_user(argp, args, sizeof(*args)))
+       ret = search_ioctl(inode, &sk, &buf_size, uargs->buf);
+
+       /*
+        * In the origin implementation an overflow is handled by returning a
+        * search header with a len of zero, so reset ret.
+        */
+       if (ret == -EOVERFLOW)
+               ret = 0;
+
+       if (ret == 0 && copy_to_user(&uargs->key, &sk, sizeof(sk)))
                ret = -EFAULT;
-       kfree(args);
+       return ret;
+}
+
+static noinline int btrfs_ioctl_tree_search_v2(struct file *file,
+                                              void __user *argp)
+{
+       struct btrfs_ioctl_search_args_v2 __user *uarg;
+       struct btrfs_ioctl_search_args_v2 args;
+       struct inode *inode;
+       int ret;
+       size_t buf_size;
+       const size_t buf_limit = 16 * 1024 * 1024;
+
+       if (!capable(CAP_SYS_ADMIN))
+               return -EPERM;
+
+       /* copy search header and buffer size */
+       uarg = (struct btrfs_ioctl_search_args_v2 __user *)argp;
+       if (copy_from_user(&args, uarg, sizeof(args)))
+               return -EFAULT;
+
+       buf_size = args.buf_size;
+
+       if (buf_size < sizeof(struct btrfs_ioctl_search_header))
+               return -EOVERFLOW;
+
+       /* limit result size to 16MB */
+       if (buf_size > buf_limit)
+               buf_size = buf_limit;
+
+       inode = file_inode(file);
+       ret = search_ioctl(inode, &args.key, &buf_size,
+                          (char *)(&uarg->buf[0]));
+       if (ret == 0 && copy_to_user(&uarg->key, &args.key, sizeof(args.key)))
+               ret = -EFAULT;
+       else if (ret == -EOVERFLOW &&
+               copy_to_user(&uarg->buf_size, &buf_size, sizeof(buf_size)))
+               ret = -EFAULT;
+
        return ret;
 }
 
@@ -5198,6 +5291,8 @@ long btrfs_ioctl(struct file *file, unsigned int
                return btrfs_ioctl_trans_end(file);
        case BTRFS_IOC_TREE_SEARCH:
                return btrfs_ioctl_tree_search(file, argp);
+       case BTRFS_IOC_TREE_SEARCH_V2:
+               return btrfs_ioctl_tree_search_v2(file, argp);
        case BTRFS_IOC_INO_LOOKUP:
                return btrfs_ioctl_ino_lookup(file, argp);
        case BTRFS_IOC_INO_PATHS: