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:
commit
16d52ef7c0
9 changed files with 193 additions and 37 deletions
|
@ -2354,7 +2354,7 @@ int end_extent_writepage(struct page *page, int err, u64 start, u64 end)
|
|||
{
|
||||
int uptodate = (err == 0);
|
||||
struct extent_io_tree *tree;
|
||||
int ret;
|
||||
int ret = 0;
|
||||
|
||||
tree = &BTRFS_I(page->mapping->host)->io_tree;
|
||||
|
||||
|
@ -5068,6 +5068,43 @@ void read_extent_buffer(struct extent_buffer *eb, void *dstv,
|
|||
}
|
||||
}
|
||||
|
||||
int read_extent_buffer_to_user(struct extent_buffer *eb, void __user *dstv,
|
||||
unsigned long start,
|
||||
unsigned long len)
|
||||
{
|
||||
size_t cur;
|
||||
size_t offset;
|
||||
struct page *page;
|
||||
char *kaddr;
|
||||
char __user *dst = (char __user *)dstv;
|
||||
size_t start_offset = eb->start & ((u64)PAGE_CACHE_SIZE - 1);
|
||||
unsigned long i = (start_offset + start) >> PAGE_CACHE_SHIFT;
|
||||
int ret = 0;
|
||||
|
||||
WARN_ON(start > eb->len);
|
||||
WARN_ON(start + len > eb->start + eb->len);
|
||||
|
||||
offset = (start_offset + start) & (PAGE_CACHE_SIZE - 1);
|
||||
|
||||
while (len > 0) {
|
||||
page = extent_buffer_page(eb, i);
|
||||
|
||||
cur = min(len, (PAGE_CACHE_SIZE - offset));
|
||||
kaddr = page_address(page);
|
||||
if (copy_to_user(dst, kaddr + offset, cur)) {
|
||||
ret = -EFAULT;
|
||||
break;
|
||||
}
|
||||
|
||||
dst += cur;
|
||||
len -= cur;
|
||||
offset = 0;
|
||||
i++;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int map_private_extent_buffer(struct extent_buffer *eb, unsigned long start,
|
||||
unsigned long min_len, char **map,
|
||||
unsigned long *map_start,
|
||||
|
|
|
@ -304,6 +304,9 @@ int memcmp_extent_buffer(struct extent_buffer *eb, const void *ptrv,
|
|||
void read_extent_buffer(struct extent_buffer *eb, void *dst,
|
||||
unsigned long start,
|
||||
unsigned long len);
|
||||
int read_extent_buffer_to_user(struct extent_buffer *eb, void __user *dst,
|
||||
unsigned long start,
|
||||
unsigned long len);
|
||||
void write_extent_buffer(struct extent_buffer *eb, const void *src,
|
||||
unsigned long start, unsigned long len);
|
||||
void copy_extent_buffer(struct extent_buffer *dst, struct extent_buffer *src,
|
||||
|
|
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 @@ static noinline int copy_to_sk(struct btrfs_root *root,
|
|||
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 @@ static noinline int search_ioctl(struct inode *inode,
|
|||
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:
|
||||
|
|
|
@ -1798,8 +1798,10 @@ static int qgroup_shared_accounting(struct btrfs_trans_handle *trans,
|
|||
return -ENOMEM;
|
||||
|
||||
tmp = ulist_alloc(GFP_NOFS);
|
||||
if (!tmp)
|
||||
if (!tmp) {
|
||||
ulist_free(qgroups);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
btrfs_get_tree_mod_seq(fs_info, &elem);
|
||||
ret = btrfs_find_all_roots(trans, fs_info, oper->bytenr, elem.seq,
|
||||
|
|
|
@ -428,8 +428,13 @@ static struct reada_extent *reada_find_extent(struct btrfs_root *root,
|
|||
continue;
|
||||
}
|
||||
if (!dev->bdev) {
|
||||
/* cannot read ahead on missing device */
|
||||
continue;
|
||||
/*
|
||||
* cannot read ahead on missing device, but for RAID5/6,
|
||||
* REQ_GET_READ_MIRRORS return 1. So don't skip missing
|
||||
* device for such case.
|
||||
*/
|
||||
if (nzones > 1)
|
||||
continue;
|
||||
}
|
||||
if (dev_replace_is_ongoing &&
|
||||
dev == fs_info->dev_replace.tgtdev) {
|
||||
|
|
|
@ -135,7 +135,7 @@ static void btrfs_free_dummy_fs_info(struct btrfs_fs_info *fs_info)
|
|||
radix_tree_for_each_slot(slot, &fs_info->buffer_radix, &iter, 0) {
|
||||
struct extent_buffer *eb;
|
||||
|
||||
eb = radix_tree_deref_slot(slot);
|
||||
eb = radix_tree_deref_slot_protected(slot, &fs_info->buffer_lock);
|
||||
if (!eb)
|
||||
continue;
|
||||
/* Shouldn't happen but that kind of thinking creates CVE's */
|
||||
|
|
|
@ -415,6 +415,8 @@ int btrfs_test_qgroups(void)
|
|||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
btrfs_set_header_level(root->node, 0);
|
||||
btrfs_set_header_nritems(root->node, 0);
|
||||
root->alloc_bytenr += 8192;
|
||||
|
||||
tmp_root = btrfs_alloc_dummy_root();
|
||||
|
|
|
@ -1284,11 +1284,13 @@ static noinline int create_pending_snapshot(struct btrfs_trans_handle *trans,
|
|||
goto fail;
|
||||
}
|
||||
|
||||
pending->error = btrfs_qgroup_inherit(trans, fs_info,
|
||||
root->root_key.objectid,
|
||||
objectid, pending->inherit);
|
||||
if (pending->error)
|
||||
goto no_free_objectid;
|
||||
ret = btrfs_qgroup_inherit(trans, fs_info,
|
||||
root->root_key.objectid,
|
||||
objectid, pending->inherit);
|
||||
if (ret) {
|
||||
btrfs_abort_transaction(trans, root, ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* see comments in should_cow_block() */
|
||||
set_bit(BTRFS_ROOT_FORCE_COW, &root->state);
|
||||
|
|
|
@ -306,6 +306,14 @@ struct btrfs_ioctl_search_args {
|
|||
char buf[BTRFS_SEARCH_ARGS_BUFSIZE];
|
||||
};
|
||||
|
||||
struct btrfs_ioctl_search_args_v2 {
|
||||
struct btrfs_ioctl_search_key key; /* in/out - search parameters */
|
||||
__u64 buf_size; /* in - size of buffer
|
||||
* out - on EOVERFLOW: needed size
|
||||
* to store item */
|
||||
__u64 buf[0]; /* out - found items */
|
||||
};
|
||||
|
||||
struct btrfs_ioctl_clone_range_args {
|
||||
__s64 src_fd;
|
||||
__u64 src_offset, src_length;
|
||||
|
@ -558,6 +566,8 @@ static inline char *btrfs_err_str(enum btrfs_err_code err_code)
|
|||
struct btrfs_ioctl_defrag_range_args)
|
||||
#define BTRFS_IOC_TREE_SEARCH _IOWR(BTRFS_IOCTL_MAGIC, 17, \
|
||||
struct btrfs_ioctl_search_args)
|
||||
#define BTRFS_IOC_TREE_SEARCH_V2 _IOWR(BTRFS_IOCTL_MAGIC, 17, \
|
||||
struct btrfs_ioctl_search_args_v2)
|
||||
#define BTRFS_IOC_INO_LOOKUP _IOWR(BTRFS_IOCTL_MAGIC, 18, \
|
||||
struct btrfs_ioctl_ino_lookup_args)
|
||||
#define BTRFS_IOC_DEFAULT_SUBVOL _IOW(BTRFS_IOCTL_MAGIC, 19, __u64)
|
||||
|
|
Loading…
Reference in a new issue