Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/mason/linux-btrfs
Pull more btrfs updates from Chris Mason: "This has a few fixes since our last pull and a new ioctl for doing btree searches from userland. It's very similar to the existing ioctl, but lets us return larger items back down to the app" * 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/mason/linux-btrfs: btrfs: fix error handling in create_pending_snapshot btrfs: fix use of uninit "ret" in end_extent_writepage() btrfs: free ulist in qgroup_shared_accounting() error path Btrfs: fix qgroups sanity test crash or hang btrfs: prevent RCU warning when dereferencing radix tree slot Btrfs: fix unfinished readahead thread for raid5/6 degraded mounting btrfs: new ioctl TREE_SEARCH_V2 btrfs: tree_search, search_ioctl: direct copy to userspace btrfs: new function read_extent_buffer_to_user btrfs: tree_search, copy_to_sk: return needed size on EOVERFLOW btrfs: tree_search, copy_to_sk: return EOVERFLOW for too small buffer btrfs: tree_search, search_ioctl: accept varying buffer btrfs: tree_search: eliminate redundant nr_items check
This commit is contained in:
149
fs/btrfs/ioctl.c
149
fs/btrfs/ioctl.c
@@ -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)
|
||||
item_len = 0;
|
||||
if (sizeof(sh) + item_len > *buf_size) {
|
||||
if (*num_found) {
|
||||
ret = 1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (sizeof(sh) + item_len + *sk_offset >
|
||||
BTRFS_SEARCH_ARGS_BUFSIZE) {
|
||||
/*
|
||||
* 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 > *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:
|
||||
|
Reference in New Issue
Block a user