* [RFC] new ioctl BTRFS_IOC_GET_CHUNK_INFO @ 2020-03-15 15:24 Goffredo Baroncelli 2020-03-15 15:24 ` [PATCH] New " Goffredo Baroncelli 0 siblings, 1 reply; 3+ messages in thread From: Goffredo Baroncelli @ 2020-03-15 15:24 UTC (permalink / raw) To: linux-btrfs This is a repost of an old patch (~2017). At the time it din't received any feedback. I repost it hoping that it still be interesting. This patch is the kernel related one; another one related to the btrfs-progs is send separately. This patch set creates a new ioctl BTRFS_IOC_GET_CHUNK_INFO. The aim is to replace the BTRFS_IOC_TREE_SEARCH ioctl used by "btrfs fi usage" to obtain information about the chunks/block groups. The problems in using the BTRFS_IOC_TREE_SEARCH is that it access the very low data structure of BTRFS. This means: 1) this would be complicated a possible change of the disk format 2) it requires the root privileges The BTRFS_IOC_GET_CHUNK_INFO ioctl can be called even from a not root user: I think that the data exposed are not sensibile data. These patches allow to use "btrfs fi usage" without root privileges. before: ------------------------------------------- $ btrfs fi us / WARNING: cannot read detailed chunk info, per-device usage will not be shown, run as root Overall: Device size: 100.00GiB Device allocated: 26.03GiB Device unallocated: 73.97GiB Device missing: 0.00B Used: 17.12GiB Free (estimated): 80.42GiB (min: 80.42GiB) Data ratio: 1.00 Metadata ratio: 1.00 Global reserve: 53.12MiB (used: 0.00B) Data,single: Size:23.00GiB, Used:16.54GiB (71.93%) Metadata,single: Size:3.00GiB, Used:588.94MiB (19.17%) System,single: Size:32.00MiB, Used:16.00KiB (0.05%) after: ----------------------------------------------- $ ./btrfs fi us / Overall: Device size: 100.00GiB Device allocated: 26.03GiB Device unallocated: 73.97GiB Device missing: 0.00B Used: 17.12GiB Free (estimated): 80.42GiB (min: 80.42GiB) Data ratio: 1.00 Metadata ratio: 1.00 Global reserve: 53.12MiB (used: 0.00B) Data,single: Size:23.00GiB, Used:16.54GiB (71.93%) /dev/sdd3 23.00GiB Metadata,single: Size:3.00GiB, Used:588.94MiB (19.17%) /dev/sdd3 3.00GiB System,single: Size:32.00MiB, Used:16.00KiB (0.05%) /dev/sdd3 32.00MiB Unallocated: /dev/sdd3 73.97GiB Comments are welcome BR G.Baroncelli -- gpg @keyserver.linux.it: Goffredo Baroncelli <kreijackATinwind.it> Key fingerprint BBF5 1610 0B64 DAC6 5F7D 17B2 0EDA 9B37 8B82 E0B5 ^ permalink raw reply [flat|nested] 3+ messages in thread
* [PATCH] New ioctl BTRFS_IOC_GET_CHUNK_INFO. 2020-03-15 15:24 [RFC] new ioctl BTRFS_IOC_GET_CHUNK_INFO Goffredo Baroncelli @ 2020-03-15 15:24 ` Goffredo Baroncelli 2020-03-19 14:42 ` Josef Bacik 0 siblings, 1 reply; 3+ messages in thread From: Goffredo Baroncelli @ 2020-03-15 15:24 UTC (permalink / raw) To: linux-btrfs; +Cc: Goffredo Baroncelli From: Goffredo Baroncelli <kreijack@inwind.it> Add a new ioctl to get info about chunk without requiring the root privileges. This allow to a non root user to know how the space of the filesystem is allocated. Signed-off-by: Goffredo Baroncelli <kreijack@inwind.it> --- fs/btrfs/ioctl.c | 215 +++++++++++++++++++++++++++++++++++++ include/uapi/linux/btrfs.h | 38 +++++++ 2 files changed, 253 insertions(+) diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index 173758d86feb..fbe8c86f0de6 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -2282,6 +2282,219 @@ static noinline int btrfs_ioctl_tree_search_v2(struct file *file, return ret; } +/* + * Return: + * 0 -> copied all data, possible further data + * 1 -> copied all data, no further data + * -EAGAIN -> not enough space, restart it + * -EFAULT -> the user passed an invalid address/size pair + */ +static noinline int copy_chunk_info(struct btrfs_path *path, + char __user *ubuf, + size_t buf_size, + u64 *used_buf, + int *num_found, + u64 *offset) +{ + struct extent_buffer *leaf; + unsigned long item_off; + unsigned long item_len; + int nritems; + int i; + int slot; + int ret = 0; + struct btrfs_key key; + + leaf = path->nodes[0]; + slot = path->slots[0]; + nritems = btrfs_header_nritems(leaf); + + for (i = slot; i < nritems; i++) { + u64 destsize; + struct btrfs_chunk_info ci; + struct btrfs_chunk chunk; + int j, chunk_size; + + item_off = btrfs_item_ptr_offset(leaf, i); + item_len = btrfs_item_size_nr(leaf, i); + + btrfs_item_key_to_cpu(leaf, &key, i); + /* + * we are not interested in other items type + */ + if (key.type != BTRFS_CHUNK_ITEM_KEY) { + ret = 1; + goto out; + } + + /* + * In any case, the next search must start from here + */ + *offset = key.offset; + read_extent_buffer(leaf, &chunk, item_off, sizeof(chunk)); + + /* + * chunk.num_stripes-1 is correct, because btrfs_chunk includes + * already a stripe + */ + destsize = sizeof(struct btrfs_chunk_info) + + (chunk.num_stripes - 1) * sizeof(struct btrfs_stripe); + + BUG_ON(destsize > item_len); + + if (buf_size < destsize + *used_buf) { + if (*num_found) + /* try onother time */ + ret = -EAGAIN; + else + /* in any case the buffer is too small */ + ret = -EOVERFLOW; + goto out; + } + + /* copy chunk */ + chunk_size = offsetof(struct btrfs_chunk_info, stripes); + memset(&ci, 0, chunk_size); + ci.length = btrfs_stack_chunk_length(&chunk); + ci.stripe_len = btrfs_stack_chunk_stripe_len(&chunk); + ci.type = btrfs_stack_chunk_type(&chunk); + ci.num_stripes = btrfs_stack_chunk_num_stripes(&chunk); + ci.sub_stripes = btrfs_stack_chunk_sub_stripes(&chunk); + ci.offset = key.offset; + + if (copy_to_user(ubuf + *used_buf, &ci, chunk_size)) { + ret = -EFAULT; + goto out; + } + *used_buf += chunk_size; + + /* copy stripes */ + for (j = 0 ; j < chunk.num_stripes ; j++) { + struct btrfs_stripe chunk_stripe; + struct btrfs_chunk_info_stripe csi; + + /* + * j-1 is correct, because btrfs_chunk includes already + * a stripe + */ + read_extent_buffer(leaf, &chunk_stripe, + item_off + sizeof(struct btrfs_chunk) + + sizeof(struct btrfs_stripe) * + (j - 1), sizeof(chunk_stripe)); + + memset(&csi, 0, sizeof(csi)); + + csi.devid = btrfs_stack_stripe_devid(&chunk_stripe); + csi.offset = btrfs_stack_stripe_offset(&chunk_stripe); + memcpy(csi.dev_uuid, chunk_stripe.dev_uuid, + sizeof(chunk_stripe.dev_uuid)); + if (copy_to_user(ubuf + *used_buf, &csi, sizeof(csi))) { + ret = -EFAULT; + goto out; + } + *used_buf += sizeof(csi); + } + + ++(*num_found); + } + + ret = 0; + if (*offset < (u64)-1) + ++(*offset); +out: + return ret; +} + +static noinline int search_chunk_info(struct inode *inode, u64 *offset, + int *items_count, + char __user *ubuf, u64 buf_size) +{ + struct btrfs_fs_info *info = btrfs_sb(inode->i_sb); + struct btrfs_root *root; + struct btrfs_key key; + struct btrfs_path *path; + int ret; + u64 used_buf = 0; + + path = btrfs_alloc_path(); + if (!path) + return -ENOMEM; + + /* search for BTRFS_CHUNK_TREE_OBJECTID tree */ + key.objectid = BTRFS_CHUNK_TREE_OBJECTID; + key.type = BTRFS_ROOT_ITEM_KEY; + key.offset = (u64)-1; + root = btrfs_read_fs_root_no_name(info, &key); + if (IS_ERR(root)) { + btrfs_err(info, "could not find root\n"); + btrfs_free_path(path); + return -ENOENT; + } + + + while (used_buf < buf_size) { + key.objectid = 0x0100; + key.type = BTRFS_CHUNK_ITEM_KEY; + key.offset = *offset; + + ret = btrfs_search_forward(root, &key, path, 0); + if (ret != 0) { + if (ret > 0) + ret = 0; + goto ret; + } + + ret = copy_chunk_info(path, ubuf, buf_size, + &used_buf, items_count, offset); + + btrfs_release_path(path); + if (ret) + break; + + } + +ret: + btrfs_free_path(path); + return ret; +} + +static noinline int btrfs_ioctl_get_chunk_info(struct file *file, + void __user *argp) +{ + struct btrfs_ioctl_chunk_info arg; + struct inode *inode; + int ret; + size_t buf_size; + u64 data_offset; + const size_t buf_limit = SZ_16M; + + + data_offset = sizeof(struct btrfs_ioctl_chunk_info); + inode = file_inode(file); + + if (copy_from_user(&arg, argp, sizeof(arg))) + return -EFAULT; + + buf_size = arg.buf_size; + arg.items_count = 0; + + if (buf_size < sizeof(struct btrfs_ioctl_chunk_info) + + sizeof(struct btrfs_chunk_info)) + return -EOVERFLOW; + + /* limit result size to 16MB */ + if (buf_size > buf_limit) + buf_size = buf_limit; + + ret = search_chunk_info(inode, &arg.offset, &arg.items_count, + argp + data_offset, buf_size - data_offset); + + if (copy_to_user(argp, &arg, data_offset)) + return -EFAULT; + + return ret; +} + /* * Search INODE_REFs to identify path name of 'dirid' directory * in a 'tree_id' tree. and sets path name to 'name'. @@ -5581,6 +5794,8 @@ long btrfs_ioctl(struct file *file, unsigned int return btrfs_ioctl_get_subvol_rootref(file, argp); case BTRFS_IOC_INO_LOOKUP_USER: return btrfs_ioctl_ino_lookup_user(file, argp); + case BTRFS_IOC_GET_CHUNK_INFO: + return btrfs_ioctl_get_chunk_info(file, argp); } return -ENOTTY; diff --git a/include/uapi/linux/btrfs.h b/include/uapi/linux/btrfs.h index 7a8bc8b920f5..a66a4532d3e4 100644 --- a/include/uapi/linux/btrfs.h +++ b/include/uapi/linux/btrfs.h @@ -713,6 +713,42 @@ struct btrfs_ioctl_received_subvol_args { __u64 reserved[16]; /* in */ }; +struct btrfs_chunk_info_stripe { + __u64 devid; + __u64 offset; + __u8 dev_uuid[BTRFS_UUID_SIZE]; +}; + +struct btrfs_chunk_info { + /* logical start of this chunk */ + __u64 offset; + /* size of this chunk in bytes */ + __u64 length; + + __u64 stripe_len; + __u64 type; + + /* 2^16 stripes is quite a lot, a second limit is the size of a single + * item in the btree + */ + __u16 num_stripes; + + /* sub stripes only matter for raid10 */ + __u16 sub_stripes; + + struct btrfs_chunk_info_stripe stripes[1]; + /* additional stripes go here */ +}; + + +struct btrfs_ioctl_chunk_info { + u64 offset; /* offset to start the search */ + u32 buf_size; /* size of the buffer, including + * btrfs_ioctl_chunk_info + */ + u32 items_count; /* number of items returned */ +}; + /* * Caller doesn't want file data in the send stream, even if the * search of clone sources doesn't find an extent. UPDATE_EXTENT @@ -949,5 +985,7 @@ enum btrfs_err_code { struct btrfs_ioctl_get_subvol_rootref_args) #define BTRFS_IOC_INO_LOOKUP_USER _IOWR(BTRFS_IOCTL_MAGIC, 62, \ struct btrfs_ioctl_ino_lookup_user_args) +#define BTRFS_IOC_GET_CHUNK_INFO _IOR(BTRFS_IOCTL_MAGIC, 64, \ + struct btrfs_ioctl_chunk_info) #endif /* _UAPI_LINUX_BTRFS_H */ -- 2.25.1 ^ permalink raw reply related [flat|nested] 3+ messages in thread
* Re: [PATCH] New ioctl BTRFS_IOC_GET_CHUNK_INFO. 2020-03-15 15:24 ` [PATCH] New " Goffredo Baroncelli @ 2020-03-19 14:42 ` Josef Bacik 0 siblings, 0 replies; 3+ messages in thread From: Josef Bacik @ 2020-03-19 14:42 UTC (permalink / raw) To: Goffredo Baroncelli, linux-btrfs; +Cc: Goffredo Baroncelli On 3/15/20 11:24 AM, Goffredo Baroncelli wrote: > From: Goffredo Baroncelli <kreijack@inwind.it> > > Add a new ioctl to get info about chunk without requiring the root privileges. > This allow to a non root user to know how the space of the filesystem is > allocated. > > Signed-off-by: Goffredo Baroncelli <kreijack@inwind.it> > --- > fs/btrfs/ioctl.c | 215 +++++++++++++++++++++++++++++++++++++ > include/uapi/linux/btrfs.h | 38 +++++++ > 2 files changed, 253 insertions(+) > > diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c > index 173758d86feb..fbe8c86f0de6 100644 > --- a/fs/btrfs/ioctl.c > +++ b/fs/btrfs/ioctl.c > @@ -2282,6 +2282,219 @@ static noinline int btrfs_ioctl_tree_search_v2(struct file *file, > return ret; > } > > +/* > + * Return: > + * 0 -> copied all data, possible further data > + * 1 -> copied all data, no further data > + * -EAGAIN -> not enough space, restart it > + * -EFAULT -> the user passed an invalid address/size pair > + */ > +static noinline int copy_chunk_info(struct btrfs_path *path, > + char __user *ubuf, > + size_t buf_size, > + u64 *used_buf, > + int *num_found, > + u64 *offset) > +{ > + struct extent_buffer *leaf; > + unsigned long item_off; > + unsigned long item_len; > + int nritems; > + int i; > + int slot; > + int ret = 0; > + struct btrfs_key key; > + > + leaf = path->nodes[0]; > + slot = path->slots[0]; > + nritems = btrfs_header_nritems(leaf); > + > + for (i = slot; i < nritems; i++) { > + u64 destsize; > + struct btrfs_chunk_info ci; > + struct btrfs_chunk chunk; > + int j, chunk_size; > + > + item_off = btrfs_item_ptr_offset(leaf, i); > + item_len = btrfs_item_size_nr(leaf, i); > + > + btrfs_item_key_to_cpu(leaf, &key, i); > + /* > + * we are not interested in other items type > + */ > + if (key.type != BTRFS_CHUNK_ITEM_KEY) { > + ret = 1; > + goto out; out doesn't clean anything up, just return 1; > + } > + > + /* > + * In any case, the next search must start from here > + */ > + *offset = key.offset; > + read_extent_buffer(leaf, &chunk, item_off, sizeof(chunk)); > + > + /* > + * chunk.num_stripes-1 is correct, because btrfs_chunk includes > + * already a stripe > + */ > + destsize = sizeof(struct btrfs_chunk_info) + > + (chunk.num_stripes - 1) * sizeof(struct btrfs_stripe); > + > + BUG_ON(destsize > item_len); We should be EIO'ing at search slot time if this happens so I think this is redundant, however if we're going to catch corruption lets return an error, so do something like if (destsize > item_len) { ASSERT(0); return -EINVAL; } The assert will catch developers if they make a mistake, and if there's fs corruption it'll just return an error. > + > + if (buf_size < destsize + *used_buf) { > + if (*num_found) > + /* try onother time */ > + ret = -EAGAIN; > + else > + /* in any case the buffer is too small */ > + ret = -EOVERFLOW; > + goto out; Again, just return, we're not cleaning anything. > + } > + > + /* copy chunk */ > + chunk_size = offsetof(struct btrfs_chunk_info, stripes); > + memset(&ci, 0, chunk_size); > + ci.length = btrfs_stack_chunk_length(&chunk); > + ci.stripe_len = btrfs_stack_chunk_stripe_len(&chunk); > + ci.type = btrfs_stack_chunk_type(&chunk); > + ci.num_stripes = btrfs_stack_chunk_num_stripes(&chunk); > + ci.sub_stripes = btrfs_stack_chunk_sub_stripes(&chunk); > + ci.offset = key.offset; > + > + if (copy_to_user(ubuf + *used_buf, &ci, chunk_size)) { > + ret = -EFAULT; > + goto out; Same here. > + } > + *used_buf += chunk_size; > + > + /* copy stripes */ > + for (j = 0 ; j < chunk.num_stripes ; j++) { > + struct btrfs_stripe chunk_stripe; > + struct btrfs_chunk_info_stripe csi; > + > + /* > + * j-1 is correct, because btrfs_chunk includes already > + * a stripe > + */ > + read_extent_buffer(leaf, &chunk_stripe, > + item_off + sizeof(struct btrfs_chunk) + > + sizeof(struct btrfs_stripe) * > + (j - 1), sizeof(chunk_stripe)); > + > + memset(&csi, 0, sizeof(csi)); > + > + csi.devid = btrfs_stack_stripe_devid(&chunk_stripe); > + csi.offset = btrfs_stack_stripe_offset(&chunk_stripe); > + memcpy(csi.dev_uuid, chunk_stripe.dev_uuid, > + sizeof(chunk_stripe.dev_uuid)); > + if (copy_to_user(ubuf + *used_buf, &csi, sizeof(csi))) { > + ret = -EFAULT; > + goto out; > + } > + *used_buf += sizeof(csi); > + } > + > + ++(*num_found); > + } > + > + ret = 0; > + if (*offset < (u64)-1) > + ++(*offset); > +out: Drop out. > + return ret; > +} > + > +static noinline int search_chunk_info(struct inode *inode, u64 *offset, > + int *items_count, > + char __user *ubuf, u64 buf_size) > +{ > + struct btrfs_fs_info *info = btrfs_sb(inode->i_sb); > + struct btrfs_root *root; > + struct btrfs_key key; > + struct btrfs_path *path; > + int ret; > + u64 used_buf = 0; > + > + path = btrfs_alloc_path(); > + if (!path) > + return -ENOMEM; > + > + /* search for BTRFS_CHUNK_TREE_OBJECTID tree */ > + key.objectid = BTRFS_CHUNK_TREE_OBJECTID; > + key.type = BTRFS_ROOT_ITEM_KEY; > + key.offset = (u64)-1; > + root = btrfs_read_fs_root_no_name(info, &key); > + if (IS_ERR(root)) { > + btrfs_err(info, "could not find root\n"); > + btrfs_free_path(path); > + return -ENOENT; > + } This will need to be rebased onto misc-next, as we now hold refs on roots we're looking up. I don't mind the overall concept, just needs to be cleaned up. Thanks, Josef ^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2020-03-19 14:42 UTC | newest] Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2020-03-15 15:24 [RFC] new ioctl BTRFS_IOC_GET_CHUNK_INFO Goffredo Baroncelli 2020-03-15 15:24 ` [PATCH] New " Goffredo Baroncelli 2020-03-19 14:42 ` Josef Bacik
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).