[PATCH v7 1/3] btrfs: Add unprivileged ioctl which returns subvolume information
Add new unprivileged ioctl BTRFS_IOC_GET_SUBVOL_INFO which returns the information of subvolume containing this inode. (i.e. returns the information in ROOT_ITEM and ROOT_BACKREF.) Reviewed-by: Gu Jinxiang <g...@cn.fujitsu.com> Tested-by: Gu Jinxiang <g...@cn.fujitsu.com> Signed-off-by: Tomohiro Misono <misono.tomoh...@jp.fujitsu.com> --- v5 -> v6 - Use brfs_read_fs_root_no_name() to get subvolume root and simplify code v4 -> v5 - Update error handling of btrfs_next_leaf() to cover all cases - Return error if ROOT_BACKREF is not found (except top-level) fs/btrfs/ioctl.c | 125 + include/uapi/linux/btrfs.h | 51 ++ 2 files changed, 176 insertions(+) diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index 8f7c1b9a2db7..a395c6d51dd6 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -2387,6 +2387,129 @@ static noinline int btrfs_ioctl_ino_lookup(struct file *file, return ret; } +/* Get the subvolume information in BTRFS_ROOT_ITEM and BTRFS_ROOT_BACKREF */ +static noinline int btrfs_ioctl_get_subvol_info(struct file *file, + void __user *argp) +{ + struct btrfs_ioctl_get_subvol_info_args *subvol_info; + struct btrfs_fs_info *fs_info; + struct btrfs_root *root; + struct btrfs_path *path; + struct btrfs_key key; + + struct btrfs_root_item *root_item; + struct btrfs_root_ref *rref; + struct extent_buffer *l; + int slot; + + unsigned long item_off; + unsigned long item_len; + + struct inode *inode; + int ret = 0; + + path = btrfs_alloc_path(); + if (!path) + return -ENOMEM; + + subvol_info = kzalloc(sizeof(*subvol_info), GFP_KERNEL); + if (!subvol_info) { + btrfs_free_path(path); + return -ENOMEM; + } + + inode = file_inode(file); + fs_info = BTRFS_I(inode)->root->fs_info; + + /* Get root_item of inode's subvolume */ + key.objectid = BTRFS_I(inode)->root->root_key.objectid; + key.type = BTRFS_ROOT_ITEM_KEY; + key.offset = (u64)-1; + root = btrfs_read_fs_root_no_name(fs_info, ); + if (IS_ERR(root)) { + ret = PTR_ERR(root); + goto out; + } + root_item = >root_item; + + subvol_info->id = key.objectid; + + subvol_info->generation = btrfs_root_generation(root_item); + subvol_info->flags = btrfs_root_flags(root_item); + + memcpy(subvol_info->uuid, root_item->uuid, BTRFS_UUID_SIZE); + memcpy(subvol_info->parent_uuid, root_item->parent_uuid, + BTRFS_UUID_SIZE); + memcpy(subvol_info->received_uuid, root_item->received_uuid, + BTRFS_UUID_SIZE); + + subvol_info->ctransid = btrfs_root_ctransid(root_item); + subvol_info->ctime.sec = btrfs_stack_timespec_sec(_item->ctime); + subvol_info->ctime.nsec = btrfs_stack_timespec_nsec(_item->ctime); + + subvol_info->otransid = btrfs_root_otransid(root_item); + subvol_info->otime.sec = btrfs_stack_timespec_sec(_item->otime); + subvol_info->otime.nsec = btrfs_stack_timespec_nsec(_item->otime); + + subvol_info->stransid = btrfs_root_stransid(root_item); + subvol_info->stime.sec = btrfs_stack_timespec_sec(_item->stime); + subvol_info->stime.nsec = btrfs_stack_timespec_nsec(_item->stime); + + subvol_info->rtransid = btrfs_root_rtransid(root_item); + subvol_info->rtime.sec = btrfs_stack_timespec_sec(_item->rtime); + subvol_info->rtime.nsec = btrfs_stack_timespec_nsec(_item->rtime); + + if (key.objectid != BTRFS_FS_TREE_OBJECTID) { + /* Search root tree for ROOT_BACKREF of this subvolume */ + root = fs_info->tree_root; + + key.type = BTRFS_ROOT_BACKREF_KEY; + key.offset = 0; + ret = btrfs_search_slot(NULL, root, , path, 0, 0); + if (ret < 0) { + goto out; + } else if (path->slots[0] >= + btrfs_header_nritems(path->nodes[0])) { + ret = btrfs_next_leaf(root, path); + if (ret < 0) { + goto out; + } else if (ret > 0) { + ret = -EUCLEAN; + goto out; + } + } + + l = path->nodes[0]; + slot = path->slots[0]; + btrfs_item_key_to_cpu(l, , slot); + if (key.objectid == subvol_info->id && + key.type == BTRFS_ROOT_BACKREF_KEY) { +
[PATCH v7 0/3] btrfs: Add three new unprivileged ioctls to allow normal users to call "sub list/show" etc.
changelog: v6 -> v7 - Rebase to current misc-next again - Fix 1st patch where otime is wrongly set instead of ctime - Add reviewed-by/tested-by tag v5 -> v6 - Update 1st patch by using btrfs_fs_root_no_name() - Return -EUCLEAN when btrfs_next_leaf/next_item() should not fail - Add Reviewed-by tag v4 -> v5 - Update error handling of 1st/2nd patch. See each log for details - Fix misspelling v3 -> v4 - call btrfs_next_leaf() after btrfs_search_slot() when the slot position exceeds the number of items - rebased to current misc-next v2 -> v3 - fix kbuild test bot warning v1 -> v2 - completely reimplement 1st/2nd ioctl to have user friendly api - various cleanup, remove unnecessary goto === This adds three new unprivileged ioctls: 1st patch: ioctl which returns subvolume information of ROOT_ITEM and ROOT_BACKREF 2nd patch: ioctl which returns subvolume information of ROOT_REF (without subvolume name) 3rd patch: user version of ino_lookup ioctl which also performs permission check. They will be used to implement user version of "subvolume list/show" etc. in user tools. See each commit log for more detals. The implementation of btrfs-progs can be found in the ML titled as follows: [PATCH 0/11] btrfs-progs: Rework of "subvolume list/show" and relax the root privileges of them Tomohiro Misono (3): btrfs: Add unprivileged ioctl which returns subvolume information btrfs: Add unprivileged ioctl which returns subvolume's ROOT_REF btrfs: Add unprivileged version of ino_lookup ioctl fs/btrfs/ioctl.c | 431 + include/uapi/linux/btrfs.h | 84 + 2 files changed, 515 insertions(+) -- 2.14.3 -- To unsubscribe from this list: send the line "unsubscribe linux-btrfs" in the body of a message to majord...@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
[PATCH v7 2/3] btrfs: Add unprivileged ioctl which returns subvolume's ROOT_REF
Add unprivileged ioctl BTRFS_IOC_GET_SUBVOL_ROOTREF which returns ROOT_REF information of the subvolume containing this inode except the subvolume name (this is because to prevent potential name leak). The subvolume name will be gained by user version of ino_lookup ioctl (BTRFS_IOC_INO_LOOKUP_USER) which also performs permission check. The min id of root ref's subvolume to be searched is specified by @min_id in struct btrfs_ioctl_get_subvol_rootref_args. After the search ends, @min_id is set to the last searched root ref's subvolid + 1. Also, if there are more root refs than BTRFS_MAX_ROOTREF_BUFFER_NUM, -EOVERFLOW is returned. Therefore the caller can just call this ioctl again without changing the argument to continue search. Reviewed-by: Qu Wenruo <w...@suse.com> Reviewed-by: Gu Jinxiang <g...@cn.fujitsu.com> Tested-by: Gu Jinxiang <g...@cn.fujitsu.com> Signed-off-by: Tomohiro Misono <misono.tomoh...@jp.fujitsu.com> --- v4 -> v5 - Update error handling of btrfs_next_leaf() to cover all cases - Use btrfs_next_item() to reduce the call of btrfs_search_slot() fs/btrfs/ioctl.c | 102 + include/uapi/linux/btrfs.h | 16 +++ 2 files changed, 118 insertions(+) diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index a395c6d51dd6..66237e83f54d 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -2510,6 +2510,106 @@ static noinline int btrfs_ioctl_get_subvol_info(struct file *file, return ret; } +/* + * Return ROOT_REF information of the subvolume containing this inode + * except the subvolume name. + */ +static noinline int btrfs_ioctl_get_subvol_rootref(struct file *file, + void __user *argp) +{ + struct btrfs_ioctl_get_subvol_rootref_args *rootrefs; + struct btrfs_root_ref *rref; + struct btrfs_root *root; + struct btrfs_path *path; + struct btrfs_key key; + + struct extent_buffer *l; + int slot; + + struct inode *inode; + int ret; + u64 objectid; + u8 found; + + path = btrfs_alloc_path(); + if (!path) + return -ENOMEM; + + rootrefs = memdup_user(argp, sizeof(*rootrefs)); + if (!rootrefs) { + btrfs_free_path(path); + return -ENOMEM; + } + + inode = file_inode(file); + root = BTRFS_I(inode)->root->fs_info->tree_root; + objectid = BTRFS_I(inode)->root->root_key.objectid; + + key.objectid = objectid; + key.type = BTRFS_ROOT_REF_KEY; + key.offset = rootrefs->min_id; + found = 0; + + ret = btrfs_search_slot(NULL, root, , path, 0, 0); + if (ret < 0) { + goto out; + } else if (path->slots[0] >= + btrfs_header_nritems(path->nodes[0])) { + ret = btrfs_next_leaf(root, path); + if (ret < 0) { + goto out; + } else if (ret > 0) { + ret = -EUCLEAN; + goto out; + } + } + while (1) { + l = path->nodes[0]; + slot = path->slots[0]; + + btrfs_item_key_to_cpu(l, , slot); + if (key.objectid != objectid || + key.type != BTRFS_ROOT_REF_KEY) { + ret = 0; + goto out; + } + + if (found == BTRFS_MAX_ROOTREF_BUFFER_NUM) { + ret = -EOVERFLOW; + goto out; + } + + rref = btrfs_item_ptr(l, slot, struct btrfs_root_ref); + rootrefs->rootref[found].subvolid = key.offset; + rootrefs->rootref[found].dirid = + btrfs_root_ref_dirid(l, rref); + found++; + + ret = btrfs_next_item(root, path); + if (ret < 0) { + goto out; + } else if (ret > 0) { + ret = -EUCLEAN; + goto out; + } + } + +out: + if (!ret || ret == -EOVERFLOW) { + rootrefs->num_items = found; + /* update min_id for next search */ + if (found) + rootrefs->min_id = + rootrefs->rootref[found - 1].subvolid + 1; + if (copy_to_user(argp, rootrefs, sizeof(*rootrefs))) + ret = -EFAULT; + } + + btrfs_free_path(path); + kfree(rootrefs); + return ret; +} + static noinline int btrfs_ioctl_snap_destroy(struct file *file, void __user *arg) { @@ -5670,6 +5770,8 @@ long btrfs_ioctl(struct file *file, unsigned int return btrfs_ioctl_fssetxattr(file, argp); case BTRFS_IOC_GET
[PATCH v7 3/3] btrfs: Add unprivileged version of ino_lookup ioctl
Add unprivileged version of ino_lookup ioctl BTRFS_IOC_INO_LOOKUP_USER to allow normal users to call "btrfs subvolume list/show" etc. in combination with BTRFS_IOC_GET_SUBVOL_INFO/BTRFS_IOC_GET_SUBVOL_ROOTREF. This can be used like BTRFS_IOC_INO_LOOKUP but the argument is different. This is because it always searches the fs/file tree correspoinding to the fd with which this ioctl is called and also returns the name of bottom subvolume. The main differences from original ino_lookup ioctl are: 1. Read + Exec permission will be checked using inode_permission() during path construction. -EACCES will be returned in case of failure. 2. Path construction will be stopped at the inode number which corresponds to the fd with which this ioctl is called. If constructed path does not exist under fd's inode, -EACCES will be returned. 3. The name of bottom subvolume is also searched and filled. Note that the maximum length of path is shorter 256 (BTRFS_VOL_NAME_MAX+1) bytes than ino_lookup ioctl because of space of subvolume's name. Reviewed-by: Gu Jinxiang <g...@cn.fujitsu.com> Reviewed-by: Qu Wenruo <w...@suse.com> Tested-by: Gu Jinxiang <g...@cn.fujitsu.com> Signed-off-by: Tomohiro Misono <misono.tomoh...@jp.fujitsu.com> --- fs/btrfs/ioctl.c | 204 + include/uapi/linux/btrfs.h | 17 2 files changed, 221 insertions(+) diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index 66237e83f54d..7e0ea42813da 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -2345,6 +2345,166 @@ static noinline int btrfs_search_path_in_tree(struct btrfs_fs_info *info, return ret; } +static noinline int btrfs_search_path_in_tree_user(struct inode *inode, + struct btrfs_ioctl_ino_lookup_user_args *args) +{ + struct btrfs_fs_info *fs_info = BTRFS_I(inode)->root->fs_info; + struct super_block *sb = inode->i_sb; + struct btrfs_key upper_limit = BTRFS_I(inode)->location; + u64 treeid = BTRFS_I(inode)->root->root_key.objectid; + u64 dirid = args->dirid; + + unsigned long item_off; + unsigned long item_len; + struct btrfs_inode_ref *iref; + struct btrfs_root_ref *rref; + struct btrfs_root *root; + struct btrfs_path *path; + struct btrfs_key key, key2; + struct extent_buffer *l; + struct inode *temp_inode; + char *ptr; + int slot; + int len; + int total_len = 0; + int ret; + + path = btrfs_alloc_path(); + if (!path) + return -ENOMEM; + + /* +* If the bottom subvolume does not exist directly under upper_limit, +* construct the path in bottomup way. +*/ + if (dirid != upper_limit.objectid) { + ptr = >path[BTRFS_INO_LOOKUP_USER_PATH_MAX - 1]; + + key.objectid = treeid; + key.type = BTRFS_ROOT_ITEM_KEY; + key.offset = (u64)-1; + root = btrfs_read_fs_root_no_name(fs_info, ); + if (IS_ERR(root)) { + ret = PTR_ERR(root); + goto out; + } + + key.objectid = dirid; + key.type = BTRFS_INODE_REF_KEY; + key.offset = (u64)-1; + while (1) { + ret = btrfs_search_slot(NULL, root, , path, 0, 0); + if (ret < 0) { + goto out; + } else if (ret > 0) { + ret = btrfs_previous_item(root, path, dirid, + BTRFS_INODE_REF_KEY); + if (ret < 0) { + goto out; + } else if (ret > 0) { + ret = -ENOENT; + goto out; + } + } + + l = path->nodes[0]; + slot = path->slots[0]; + btrfs_item_key_to_cpu(l, , slot); + + iref = btrfs_item_ptr(l, slot, struct btrfs_inode_ref); + len = btrfs_inode_ref_name_len(l, iref); + ptr -= len + 1; + total_len += len + 1; + if (ptr < args->path) { + ret = -ENAMETOOLONG; + goto out; + } + + *(ptr + len) = '/'; + read_extent_buffer(l, ptr, + (unsigned long)(iref + 1), len); + + /* Check the read+exec permission of this directory */ + ret = btrfs_previous_item(root, path, dirid, +
[PATCH v6 3/3] btrfs: Add unprivileged version of ino_lookup ioctl
Add unprivileged version of ino_lookup ioctl BTRFS_IOC_INO_LOOKUP_USER to allow normal users to call "btrfs subvolume list/show" etc. in combination with BTRFS_IOC_GET_SUBVOL_INFO/BTRFS_IOC_GET_SUBVOL_ROOTREF. This can be used like BTRFS_IOC_INO_LOOKUP but the argument is different. This is because it always searches the fs/file tree correspoinding to the fd with which this ioctl is called and also returns the name of bottom subvolume. The main differences from original ino_lookup ioctl are: 1. Read + Exec permission will be checked using inode_permission() during path construction. -EACCES will be returned in case of failure. 2. Path construction will be stopped at the inode number which corresponds to the fd with which this ioctl is called. If constructed path does not exist under fd's inode, -EACCES will be returned. 3. The name of bottom subvolume is also searched and filled. Note that the maximum length of path is shorter 256 (BTRFS_VOL_NAME_MAX+1) bytes than ino_lookup ioctl because of space of subvolume's name. Reviewed-by: Gu Jinxiang <g...@cn.fujitsu.com> Reviewed-by: Qu Wenruo <w...@suse.com> Signed-off-by: Tomohiro Misono <misono.tomoh...@jp.fujitsu.com> --- fs/btrfs/ioctl.c | 204 + include/uapi/linux/btrfs.h | 17 2 files changed, 221 insertions(+) diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index 463ddedd90da..5e8dfa816ba9 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -2200,6 +2200,166 @@ static noinline int btrfs_search_path_in_tree(struct btrfs_fs_info *info, return ret; } +static noinline int btrfs_search_path_in_tree_user(struct inode *inode, + struct btrfs_ioctl_ino_lookup_user_args *args) +{ + struct btrfs_fs_info *fs_info = BTRFS_I(inode)->root->fs_info; + struct super_block *sb = inode->i_sb; + struct btrfs_key upper_limit = BTRFS_I(inode)->location; + u64 treeid = BTRFS_I(inode)->root->root_key.objectid; + u64 dirid = args->dirid; + + unsigned long item_off; + unsigned long item_len; + struct btrfs_inode_ref *iref; + struct btrfs_root_ref *rref; + struct btrfs_root *root; + struct btrfs_path *path; + struct btrfs_key key, key2; + struct extent_buffer *l; + struct inode *temp_inode; + char *ptr; + int slot; + int len; + int total_len = 0; + int ret; + + path = btrfs_alloc_path(); + if (!path) + return -ENOMEM; + + /* +* If the bottom subvolume does not exist directly under upper_limit, +* construct the path in bottomup way. +*/ + if (dirid != upper_limit.objectid) { + ptr = >path[BTRFS_INO_LOOKUP_USER_PATH_MAX - 1]; + + key.objectid = treeid; + key.type = BTRFS_ROOT_ITEM_KEY; + key.offset = (u64)-1; + root = btrfs_read_fs_root_no_name(fs_info, ); + if (IS_ERR(root)) { + ret = -ENOENT; + goto out; + } + + key.objectid = dirid; + key.type = BTRFS_INODE_REF_KEY; + key.offset = (u64)-1; + while (1) { + ret = btrfs_search_slot(NULL, root, , path, 0, 0); + if (ret < 0) { + goto out; + } else if (ret > 0) { + ret = btrfs_previous_item(root, path, dirid, + BTRFS_INODE_REF_KEY); + if (ret < 0) { + goto out; + } else if (ret > 0) { + ret = -ENOENT; + goto out; + } + } + + l = path->nodes[0]; + slot = path->slots[0]; + btrfs_item_key_to_cpu(l, , slot); + + iref = btrfs_item_ptr(l, slot, struct btrfs_inode_ref); + len = btrfs_inode_ref_name_len(l, iref); + ptr -= len + 1; + total_len += len + 1; + if (ptr < args->path) { + ret = -ENAMETOOLONG; + goto out; + } + + *(ptr + len) = '/'; + read_extent_buffer(l, ptr, + (unsigned long)(iref + 1), len); + + /* Check the read+exec permission of this directory */ + ret = btrfs_previous_item(root, path, dirid, + BTRFS_INODE_ITEM_KEY); +
[PATCH v6 1/3] btrfs: Add unprivileged ioctl which returns subvolume information
Add new unprivileged ioctl BTRFS_IOC_GET_SUBVOL_INFO which returns the information of subvolume containing this inode. (i.e. returns the information in ROOT_ITEM and ROOT_BACKREF.) Signed-off-by: Tomohiro Misono <misono.tomoh...@jp.fujitsu.com> --- v5 -> v6 - Use brfs_read_fs_root_no_name() to get subvolume root and simplify code v4 -> v5 - Update error handling of btrfs_next_leaf() to cover all cases - Return error if ROOT_BACKREF is not found (except top-level) fs/btrfs/ioctl.c | 125 + include/uapi/linux/btrfs.h | 51 ++ 2 files changed, 176 insertions(+) diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index 48e2ddff32bd..31af6e91c614 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -2242,6 +2242,129 @@ static noinline int btrfs_ioctl_ino_lookup(struct file *file, return ret; } +/* Get the subvolume information in BTRFS_ROOT_ITEM and BTRFS_ROOT_BACKREF */ +static noinline int btrfs_ioctl_get_subvol_info(struct file *file, + void __user *argp) +{ + struct btrfs_ioctl_get_subvol_info_args *subvol_info; + struct btrfs_fs_info *fs_info; + struct btrfs_root *root; + struct btrfs_path *path; + struct btrfs_key key; + + struct btrfs_root_item *root_item; + struct btrfs_root_ref *rref; + struct extent_buffer *l; + int slot; + + unsigned long item_off; + unsigned long item_len; + + struct inode *inode; + int ret = 0; + + path = btrfs_alloc_path(); + if (!path) + return -ENOMEM; + + subvol_info = kzalloc(sizeof(*subvol_info), GFP_KERNEL); + if (!subvol_info) { + btrfs_free_path(path); + return -ENOMEM; + } + + inode = file_inode(file); + fs_info = BTRFS_I(inode)->root->fs_info; + + /* Get root_item of inode's subvolume */ + key.objectid = BTRFS_I(inode)->root->root_key.objectid; + key.type = BTRFS_ROOT_ITEM_KEY; + key.offset = (u64)-1; + root = btrfs_read_fs_root_no_name(fs_info, ); + if (IS_ERR(root)) { + ret = PTR_ERR(root); + goto out; + } + root_item = >root_item; + + subvol_info->id = key.objectid; + + subvol_info->generation = btrfs_root_generation(root_item); + subvol_info->flags = btrfs_root_flags(root_item); + + memcpy(subvol_info->uuid, root_item->uuid, BTRFS_UUID_SIZE); + memcpy(subvol_info->parent_uuid, root_item->parent_uuid, + BTRFS_UUID_SIZE); + memcpy(subvol_info->received_uuid, root_item->received_uuid, + BTRFS_UUID_SIZE); + + subvol_info->ctransid = btrfs_root_ctransid(root_item); + subvol_info->ctime.sec = btrfs_stack_timespec_sec(_item->otime); + subvol_info->ctime.nsec = btrfs_stack_timespec_nsec(_item->ctime); + + subvol_info->otransid = btrfs_root_otransid(root_item); + subvol_info->otime.sec = btrfs_stack_timespec_sec(_item->otime); + subvol_info->otime.nsec = btrfs_stack_timespec_nsec(_item->otime); + + subvol_info->stransid = btrfs_root_stransid(root_item); + subvol_info->stime.sec = btrfs_stack_timespec_sec(_item->stime); + subvol_info->stime.nsec = btrfs_stack_timespec_nsec(_item->stime); + + subvol_info->rtransid = btrfs_root_rtransid(root_item); + subvol_info->rtime.sec = btrfs_stack_timespec_sec(_item->rtime); + subvol_info->rtime.nsec = btrfs_stack_timespec_nsec(_item->rtime); + + if (key.objectid != BTRFS_FS_TREE_OBJECTID) { + /* Search root tree for ROOT_BACKREF of this subvolume */ + root = fs_info->tree_root; + + key.type = BTRFS_ROOT_BACKREF_KEY; + key.offset = 0; + ret = btrfs_search_slot(NULL, root, , path, 0, 0); + if (ret < 0) { + goto out; + } else if (path->slots[0] >= + btrfs_header_nritems(path->nodes[0])) { + ret = btrfs_next_leaf(root, path); + if (ret < 0) { + goto out; + } else if (ret > 0) { + ret = -EUCLEAN; + goto out; + } + } + + l = path->nodes[0]; + slot = path->slots[0]; + btrfs_item_key_to_cpu(l, , slot); + if (key.objectid == subvol_info->id && + key.type == BTRFS_ROOT_BACKREF_KEY) { + subvol_info->parent_id = key.offset; + + rref = btr
[PATCH v6 0/3] btrfs: Add three new unprivileged ioctls to allow normal users to call "sub list/show" etc.
changelog: v5 -> v6 - Update 1st patch by using btrfs_fs_root_no_name() - Return -EUCLEAN when btrfs_next_leaf/next_item() should not fail - Add Reviewed-by tag v4 -> v5 - Update error handling of 1st/2nd patch. See each log for details - Fix misspelling v3 -> v4 - call btrfs_next_leaf() after btrfs_search_slot() when the slot position exceeds the number of items - rebased to current misc-next v2 -> v3 - fix kbuild test bot warning v1 -> v2 - completely reimplement 1st/2nd ioctl to have user friendly api - various cleanup, remove unnecessary goto === This adds three new unprivileged ioctls: 1st patch: ioctl which returns subvolume information of ROOT_ITEM and ROOT_BACKREF 2nd patch: ioctl which returns subvolume information of ROOT_REF (without subvolume name) 3rd patch: user version of ino_lookup ioctl which also performs permission check. They will be used to implement user version of "subvolume list/show" etc. in user tools. See each commit log for more detals. The implementation of btrfs-progs can be found in the ML titled as follows: [PATCH 0/11] btrfs-progs: Rework of "subvolume list/show" and relax the root privileges of them Tomohiro Misono (3): btrfs: Add unprivileged ioctl which returns subvolume information btrfs: Add unprivileged ioctl which returns subvolume's ROOT_REF btrfs: Add unprivileged version of ino_lookup ioctl fs/btrfs/ioctl.c | 431 + include/uapi/linux/btrfs.h | 84 + 2 files changed, 515 insertions(+) -- 2.14.3 -- To unsubscribe from this list: send the line "unsubscribe linux-btrfs" in the body of a message to majord...@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
[PATCH v6 2/3] btrfs: Add unprivileged ioctl which returns subvolume's ROOT_REF
Add unprivileged ioctl BTRFS_IOC_GET_SUBVOL_ROOTREF which returns ROOT_REF information of the subvolume containing this inode except the subvolume name (this is because to prevent potential name leak). The subvolume name will be gained by user version of ino_lookup ioctl (BTRFS_IOC_INO_LOOKUP_USER) which also performs permission check. The min id of root ref's subvolume to be searched is specified by @min_id in struct btrfs_ioctl_get_subvol_rootref_args. After the search ends, @min_id is set to the last searched root ref's subvolid + 1. Also, if there are more root refs than BTRFS_MAX_ROOTREF_BUFFER_NUM, -EOVERFLOW is returned. Therefore the caller can just call this ioctl again without changing the argument to continue search. Reviewed-by: Qu Wenruo <w...@suse.com> Signed-off-by: Tomohiro Misono <misono.tomoh...@jp.fujitsu.com> --- v4 -> v5 - Update error handling of btrfs_next_leaf() to cover all cases - Use btrfs_next_item() to reduce the call of btrfs_search_slot() fs/btrfs/ioctl.c | 102 + include/uapi/linux/btrfs.h | 16 +++ 2 files changed, 118 insertions(+) diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index 31af6e91c614..463ddedd90da 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -2365,6 +2365,106 @@ static noinline int btrfs_ioctl_get_subvol_info(struct file *file, return ret; } +/* + * Return ROOT_REF information of the subvolume containing this inode + * except the subvolume name. + */ +static noinline int btrfs_ioctl_get_subvol_rootref(struct file *file, + void __user *argp) +{ + struct btrfs_ioctl_get_subvol_rootref_args *rootrefs; + struct btrfs_root_ref *rref; + struct btrfs_root *root; + struct btrfs_path *path; + struct btrfs_key key; + + struct extent_buffer *l; + int slot; + + struct inode *inode; + int ret; + u64 objectid; + u8 found; + + path = btrfs_alloc_path(); + if (!path) + return -ENOMEM; + + rootrefs = memdup_user(argp, sizeof(*rootrefs)); + if (!rootrefs) { + btrfs_free_path(path); + return -ENOMEM; + } + + inode = file_inode(file); + root = BTRFS_I(inode)->root->fs_info->tree_root; + objectid = BTRFS_I(inode)->root->root_key.objectid; + + key.objectid = objectid; + key.type = BTRFS_ROOT_REF_KEY; + key.offset = rootrefs->min_id; + found = 0; + + ret = btrfs_search_slot(NULL, root, , path, 0, 0); + if (ret < 0) { + goto out; + } else if (path->slots[0] >= + btrfs_header_nritems(path->nodes[0])) { + ret = btrfs_next_leaf(root, path); + if (ret < 0) { + goto out; + } else if (ret > 0) { + ret = -EUCLEAN; + goto out; + } + } + while (1) { + l = path->nodes[0]; + slot = path->slots[0]; + + btrfs_item_key_to_cpu(l, , slot); + if (key.objectid != objectid || + key.type != BTRFS_ROOT_REF_KEY) { + ret = 0; + goto out; + } + + if (found == BTRFS_MAX_ROOTREF_BUFFER_NUM) { + ret = -EOVERFLOW; + goto out; + } + + rref = btrfs_item_ptr(l, slot, struct btrfs_root_ref); + rootrefs->rootref[found].subvolid = key.offset; + rootrefs->rootref[found].dirid = + btrfs_root_ref_dirid(l, rref); + found++; + + ret = btrfs_next_item(root, path); + if (ret < 0) { + goto out; + } else if (ret > 0) { + ret = -EUCLEAN; + goto out; + } + } + +out: + if (!ret || ret == -EOVERFLOW) { + rootrefs->num_items = found; + /* update min_id for next search */ + if (found) + rootrefs->min_id = + rootrefs->rootref[found - 1].subvolid + 1; + if (copy_to_user(argp, rootrefs, sizeof(*rootrefs))) + ret = -EFAULT; + } + + btrfs_free_path(path); + kfree(rootrefs); + return ret; +} + static noinline int btrfs_ioctl_snap_destroy(struct file *file, void __user *arg) { @@ -5499,6 +5599,8 @@ long btrfs_ioctl(struct file *file, unsigned int return btrfs_ioctl_set_features(file, argp); case BTRFS_IOC_GET_SUBVOL_INFO: return btrfs_ioctl_get_subvol_info(file, argp); + case BTRFS_IOC_GET_SU
[PATCH v2 2/2] btrfs: sysfs: Add entry which shows rmdir(2) can work for subvolume
Deletion of a subvolume by rmdir(2) has become allowed by the 'commit cd2decf640b1 ("btrfs: Allow rmdir(2) to delete an empty subvolume")'. It is a kind of new feature and this commits add a sysfs entry /sys/fs/btrfs/static_features/rmdir_subvol to indicate the availability of feature so that a user program (e.g. xfstests) can detect it. Note that new sysfs directory "static_features" is created since a entry in /sys/fs/btrfs/features depends on feature bits of superblock (in other words, they may be different between each fs) and is not suitable to hold the features which only depend on kernel version. New attribute_group "btrfs_static_feature_attr_group" is created for this purpose. Signed-off-by: Tomohiro Misono <misono.tomoh...@jp.fujitsu.com> --- fs/btrfs/sysfs.c | 29 + 1 file changed, 29 insertions(+) diff --git a/fs/btrfs/sysfs.c b/fs/btrfs/sysfs.c index 217d401fe8ae..35b3ac567899 100644 --- a/fs/btrfs/sysfs.c +++ b/fs/btrfs/sysfs.c @@ -210,12 +210,32 @@ static struct attribute *btrfs_supported_feature_attrs[] = { NULL }; +/* Features which depend on feature bits and may differ between each fs */ static const struct attribute_group btrfs_feature_attr_group = { .name = "features", .is_visible = btrfs_feature_visible, .attrs = btrfs_supported_feature_attrs, }; +static ssize_t rmdir_subvol_show(struct kobject *kobj, +struct kobj_attribute *ka, char *buf) +{ + /* No meaning for the value */ + return snprintf(buf, PAGE_SIZE, "0\n"); +} +BTRFS_ATTR(static_feature, rmdir_subvol, rmdir_subvol_show); + +static struct attribute *btrfs_supported_static_feature_attrs[] = { + BTRFS_ATTR_PTR(static_feature, rmdir_subvol), + NULL +}; + +/* Features which only depend on kernel version */ +static const struct attribute_group btrfs_static_feature_attr_group = { + .name = "static_features", + .attrs = btrfs_supported_static_feature_attrs, +}; + static ssize_t btrfs_show_u64(u64 *value_ptr, spinlock_t *lock, char *buf) { u64 val; @@ -901,8 +921,15 @@ int __init btrfs_init_sysfs(void) ret = sysfs_create_group(_kset->kobj, _feature_attr_group); if (ret) goto out2; + ret = sysfs_create_group(_kset->kobj, +_static_feature_attr_group); + if (ret) + goto out3; return 0; + +out3: + sysfs_remove_group(_kset->kobj, _feature_attr_group); out2: debugfs_remove_recursive(btrfs_debugfs_root_dentry); out1: @@ -914,6 +941,8 @@ int __init btrfs_init_sysfs(void) void __cold btrfs_exit_sysfs(void) { sysfs_remove_group(_kset->kobj, _feature_attr_group); + sysfs_remove_group(_kset->kobj, + _static_feature_attr_group); kset_unregister(btrfs_kset); debugfs_remove_recursive(btrfs_debugfs_root_dentry); } -- 2.14.3 -- To unsubscribe from this list: send the line "unsubscribe linux-btrfs" in the body of a message to majord...@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
[PATCH v2 0/2] btrfs: sysfs: Add entry which shows rmdir(2) can work for subvolume
[based on current misc-next] - changelog v1 -> v2 - Explicitly set start of enum btrfs_feature_set as 0 in 1st patch - Create new sysfs group which shows static feature (i.e. features which only depends on kernel version). See 2nd path. - This adds new sysfs entry /sys/fs/btrfs/static_features/rmdir_subvol to indicate that the kernel can delete a subvolume by rmdir(2), which is allowed by: https://www.spinics.net/lists/linux-btrfs/msg76938.html The aim of adding a entry is to enable a user program (e.g. xfstest) to detect a feature. The first patch is a cleanup and the second one is a main part. Tomohiro Misono (2): btrfs: sysfs: Use enum/define value intead of magic number btrfs: sysfs: Add entry which shows rmdir(2) can work for subvolume fs/btrfs/sysfs.c | 40 +++- fs/btrfs/sysfs.h | 4 ++-- 2 files changed, 37 insertions(+), 7 deletions(-) -- 2.14.3 -- To unsubscribe from this list: send the line "unsubscribe linux-btrfs" in the body of a message to majord...@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
[PATCH v2 1/2] btrfs: sysfs: Use enum/define value intead of magic number
Signed-off-by: Tomohiro Misono <misono.tomoh...@jp.fujitsu.com> --- fs/btrfs/sysfs.c | 11 ++- fs/btrfs/sysfs.h | 4 ++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/fs/btrfs/sysfs.c b/fs/btrfs/sysfs.c index fa6c8c88b250..217d401fe8ae 100644 --- a/fs/btrfs/sysfs.c +++ b/fs/btrfs/sysfs.c @@ -514,10 +514,11 @@ static inline struct btrfs_fs_info *to_fs_info(struct kobject *kobj) } #define NUM_FEATURE_BITS 64 -static char btrfs_unknown_feature_names[3][NUM_FEATURE_BITS][13]; -static struct btrfs_feature_attr btrfs_feature_attrs[3][NUM_FEATURE_BITS]; +#define BTRFS_FEATURE_NAME_MAX 13 +static char btrfs_unknown_feature_names[FEAT_MAX][NUM_FEATURE_BITS][BTRFS_FEATURE_NAME_MAX]; +static struct btrfs_feature_attr btrfs_feature_attrs[FEAT_MAX][NUM_FEATURE_BITS]; -static const u64 supported_feature_masks[3] = { +static const u64 supported_feature_masks[FEAT_MAX] = { [FEAT_COMPAT]= BTRFS_FEATURE_COMPAT_SUPP, [FEAT_COMPAT_RO] = BTRFS_FEATURE_COMPAT_RO_SUPP, [FEAT_INCOMPAT] = BTRFS_FEATURE_INCOMPAT_SUPP, @@ -609,7 +610,7 @@ void btrfs_sysfs_remove_mounted(struct btrfs_fs_info *fs_info) btrfs_sysfs_rm_device_link(fs_info->fs_devices, NULL); } -const char * const btrfs_feature_set_names[3] = { +const char * const btrfs_feature_set_names[FEAT_MAX] = { [FEAT_COMPAT]= "compat", [FEAT_COMPAT_RO] = "compat_ro", [FEAT_INCOMPAT] = "incompat", @@ -673,7 +674,7 @@ static void init_feature_attrs(void) if (fa->kobj_attr.attr.name) continue; - snprintf(name, 13, "%s:%u", + snprintf(name, BTRFS_FEATURE_NAME_MAX, "%s:%u", btrfs_feature_set_names[set], i); fa->kobj_attr.attr.name = name; diff --git a/fs/btrfs/sysfs.h b/fs/btrfs/sysfs.h index b567560d9aa9..c6ee600aff89 100644 --- a/fs/btrfs/sysfs.h +++ b/fs/btrfs/sysfs.h @@ -9,7 +9,7 @@ extern u64 btrfs_debugfs_test; enum btrfs_feature_set { - FEAT_COMPAT, + FEAT_COMPAT = 0, FEAT_COMPAT_RO, FEAT_INCOMPAT, FEAT_MAX @@ -77,7 +77,7 @@ attr_to_btrfs_feature_attr(struct attribute *attr) } char *btrfs_printable_features(enum btrfs_feature_set set, u64 flags); -extern const char * const btrfs_feature_set_names[3]; +extern const char * const btrfs_feature_set_names[FEAT_MAX]; extern struct kobj_type space_info_ktype; extern struct kobj_type btrfs_raid_ktype; int btrfs_sysfs_add_device_link(struct btrfs_fs_devices *fs_devices, -- 2.14.3 -- To unsubscribe from this list: send the line "unsubscribe linux-btrfs" in the body of a message to majord...@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
[PATCH v5 1/3] btrfs: Add unprivileged ioctl which returns subvolume information
Add new unprivileged ioctl BTRFS_IOC_GET_SUBVOL_INFO which returns the information of subvolume containing this inode. (i.e. returns the information in ROOT_ITEM and ROOT_BACKREF.) Signed-off-by: Tomohiro Misono <misono.tomoh...@jp.fujitsu.com> --- v4 -> v5 - Update error handling of btrfs_next_leaf() to cover all cases - Return error if ROOT_BACKREF is not found (except top-level) fs/btrfs/ioctl.c | 146 + include/uapi/linux/btrfs.h | 51 2 files changed, 197 insertions(+) diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index 48e2ddff32bd..c1c9ae9a937d 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -2242,6 +2242,150 @@ static noinline int btrfs_ioctl_ino_lookup(struct file *file, return ret; } +/* Get the subvolume information in BTRFS_ROOT_ITEM and BTRFS_ROOT_BACKREF */ +static noinline int btrfs_ioctl_get_subvol_info(struct file *file, + void __user *argp) +{ + struct btrfs_ioctl_get_subvol_info_args *subvol_info; + struct btrfs_root *root; + struct btrfs_path *path; + struct btrfs_key key; + + struct btrfs_root_item root_item; + struct btrfs_root_ref *rref; + struct extent_buffer *l; + int slot; + + unsigned long item_off; + unsigned long item_len; + + struct inode *inode; + int ret; + + path = btrfs_alloc_path(); + if (!path) + return -ENOMEM; + + subvol_info = kzalloc(sizeof(*subvol_info), GFP_KERNEL); + if (!subvol_info) { + btrfs_free_path(path); + return -ENOMEM; + } + + inode = file_inode(file); + root = BTRFS_I(inode)->root->fs_info->tree_root; + + key.objectid = BTRFS_I(inode)->root->root_key.objectid; + key.type = BTRFS_ROOT_ITEM_KEY; + key.offset = 0; + + ret = btrfs_search_slot(NULL, root, , path, 0, 0); + if (ret < 0) { + goto out; + } else if (ret > 0) { + u64 objectid = key.objectid; + + if (path->slots[0] >= btrfs_header_nritems(path->nodes[0])) { + ret = btrfs_next_leaf(root, path); + if (ret < 0) { + goto out; + } else if (ret > 0) { + ret = -ENOENT; + goto out; + } + } + + /* If the subvolume is a snapshot, offset is not zero */ + btrfs_item_key_to_cpu(path->nodes[0], , path->slots[0]); + if (key.objectid != objectid || + key.type != BTRFS_ROOT_ITEM_KEY) { + ret = -ENOENT; + goto out; + } + } + + l = path->nodes[0]; + slot = path->slots[0]; + item_off = btrfs_item_ptr_offset(l, slot); + item_len = btrfs_item_size_nr(l, slot); + read_extent_buffer(l, _item, item_off, item_len); + + subvol_info->id = key.objectid; + + subvol_info->generation = btrfs_root_generation(_item); + subvol_info->flags = btrfs_root_flags(_item); + + memcpy(subvol_info->uuid, root_item.uuid, BTRFS_UUID_SIZE); + memcpy(subvol_info->parent_uuid, root_item.parent_uuid, + BTRFS_UUID_SIZE); + memcpy(subvol_info->received_uuid, root_item.received_uuid, + BTRFS_UUID_SIZE); + + subvol_info->ctransid = btrfs_root_ctransid(_item); + subvol_info->ctime.sec = btrfs_stack_timespec_sec(_item.ctime); + subvol_info->ctime.nsec = btrfs_stack_timespec_nsec(_item.ctime); + + subvol_info->otransid = btrfs_root_otransid(_item); + subvol_info->otime.sec = btrfs_stack_timespec_sec(_item.otime); + subvol_info->otime.nsec = btrfs_stack_timespec_nsec(_item.otime); + + subvol_info->stransid = btrfs_root_stransid(_item); + subvol_info->stime.sec = btrfs_stack_timespec_sec(_item.stime); + subvol_info->stime.nsec = btrfs_stack_timespec_nsec(_item.stime); + + subvol_info->rtransid = btrfs_root_rtransid(_item); + subvol_info->rtime.sec = btrfs_stack_timespec_sec(_item.rtime); + subvol_info->rtime.nsec = btrfs_stack_timespec_nsec(_item.rtime); + + btrfs_release_path(path); + if (key.objectid != BTRFS_FS_TREE_OBJECTID) { + key.type = BTRFS_ROOT_BACKREF_KEY; + key.offset = 0; + ret = btrfs_search_slot(NULL, root, , path, 0, 0); + if (ret < 0) { + goto out; + } else if (path->slots[0] >= + btrfs_header_nritems(path->nodes[0])) { + ret = btrfs_next_leaf(r
[PATCH v5 0/3] btrfs: Add three new unprivileged ioctls to allow normal users to call "sub list/show" etc.
[based on current misc-next] changelog: v4 -> v5 - Update error handling of 1st/2nd patch. See each log for details - Fix misspelling v3 -> v4 - call btrfs_next_leaf() after btrfs_search_slot() when the slot position exceeds the number of items - rebased to current misc-next v2 -> v3 - fix kbuild test bot warning v1 -> v2 - completely reimplement 1st/2nd ioctl to have user friendly api - various cleanup, remove unnecessary goto === This adds three new unprivileged ioctls: 1st patch: ioctl which returns subvolume information of ROOT_ITEM and ROOT_BACKREF 2nd patch: ioctl which returns subvolume information of ROOT_REF (without subvolume name) 3rd patch: user version of ino_lookup ioctl which also performs permission check. They will be used to implement user version of "subvolume list/show" etc. in user tools. See each commit log for more detals. The implementation of btrfs-progs can be found in the ML titled as follows: [PATCH 0/11] btrfs-progs: Rework of "subvolume list/show" and relax the root privileges of them Tomohiro Misono (3): btrfs: Add unprivileged ioctl which returns subvolume information btrfs: Add unprivileged ioctl which returns subvolume's ROOT_REF btrfs: Add unprivileged version of ino_lookup ioctl fs/btrfs/ioctl.c | 452 + include/uapi/linux/btrfs.h | 84 + 2 files changed, 536 insertions(+) -- 2.14.3 -- To unsubscribe from this list: send the line "unsubscribe linux-btrfs" in the body of a message to majord...@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
[PATCH v5 2/3] btrfs: Add unprivileged ioctl which returns subvolume's ROOT_REF
Add unprivileged ioctl BTRFS_IOC_GET_SUBVOL_ROOTREF which returns ROOT_REF information of the subvolume containing this inode except the subvolume name (this is because to prevent potential name leak). The subvolume name will be gained by user version of ino_lookup ioctl (BTRFS_IOC_INO_LOOKUP_USER) which also performs permission check. The min id of root ref's subvolume to be searched is specified by @min_id in struct btrfs_ioctl_get_subvol_rootref_args. After the search ends, @min_id is set to the last searched root ref's subvolid + 1. Also, if there are more root refs than BTRFS_MAX_ROOTREF_BUFFER_NUM, -EOVERFLOW is returned. Therefore the caller can just call this ioctl again without changing the argument to continue search. Signed-off-by: Tomohiro Misono <misono.tomoh...@jp.fujitsu.com> --- v4 -> v5 - Update error handling of btrfs_next_leaf() to cover all cases - Use btrfs_next_item() to reduce the call of btrfs_search_slot() fs/btrfs/ioctl.c | 102 + include/uapi/linux/btrfs.h | 16 +++ 2 files changed, 118 insertions(+) diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index c1c9ae9a937d..db5de77540e1 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -2386,6 +2386,106 @@ static noinline int btrfs_ioctl_get_subvol_info(struct file *file, return ret; } +/* + * Return ROOT_REF information of the subvolume containing this inode + * except the subvolume name. + */ +static noinline int btrfs_ioctl_get_subvol_rootref(struct file *file, + void __user *argp) +{ + struct btrfs_ioctl_get_subvol_rootref_args *rootrefs; + struct btrfs_root_ref *rref; + struct btrfs_root *root; + struct btrfs_path *path; + struct btrfs_key key; + + struct extent_buffer *l; + int slot; + + struct inode *inode; + int ret; + u64 objectid; + u8 found; + + path = btrfs_alloc_path(); + if (!path) + return -ENOMEM; + + rootrefs = memdup_user(argp, sizeof(*rootrefs)); + if (!rootrefs) { + btrfs_free_path(path); + return -ENOMEM; + } + + inode = file_inode(file); + root = BTRFS_I(inode)->root->fs_info->tree_root; + objectid = BTRFS_I(inode)->root->root_key.objectid; + + key.objectid = objectid; + key.type = BTRFS_ROOT_REF_KEY; + key.offset = rootrefs->min_id; + found = 0; + + ret = btrfs_search_slot(NULL, root, , path, 0, 0); + if (ret < 0) { + goto out; + } else if (path->slots[0] >= + btrfs_header_nritems(path->nodes[0])) { + ret = btrfs_next_leaf(root, path); + if (ret < 0) { + goto out; + } else if (ret > 0) { + ret = -ENOENT; + goto out; + } + } + while (1) { + l = path->nodes[0]; + slot = path->slots[0]; + + btrfs_item_key_to_cpu(l, , slot); + if (key.objectid != objectid || + key.type != BTRFS_ROOT_REF_KEY) { + ret = 0; + goto out; + } + + if (found == BTRFS_MAX_ROOTREF_BUFFER_NUM) { + ret = -EOVERFLOW; + goto out; + } + + rref = btrfs_item_ptr(l, slot, struct btrfs_root_ref); + rootrefs->rootref[found].subvolid = key.offset; + rootrefs->rootref[found].dirid = + btrfs_root_ref_dirid(l, rref); + found++; + + ret = btrfs_next_item(root, path); + if (ret < 0) { + goto out; + } else if (ret > 0) { + ret = -ENOENT; + goto out; + } + } + +out: + if (!ret || ret == -EOVERFLOW) { + rootrefs->num_items = found; + /* update min_id for next search */ + if (found) + rootrefs->min_id = + rootrefs->rootref[found - 1].subvolid + 1; + if (copy_to_user(argp, rootrefs, sizeof(*rootrefs))) + ret = -EFAULT; + } + + btrfs_free_path(path); + kfree(rootrefs); + return ret; +} + static noinline int btrfs_ioctl_snap_destroy(struct file *file, void __user *arg) { @@ -5520,6 +5620,8 @@ long btrfs_ioctl(struct file *file, unsigned int return btrfs_ioctl_set_features(file, argp); case BTRFS_IOC_GET_SUBVOL_INFO: return btrfs_ioctl_get_subvol_info(file, argp); + case BTRFS_IOC_GET_SUBVOL_ROOTREF: + return btrfs_ioctl_get_su
[PATCH v5 3/3] btrfs: Add unprivileged version of ino_lookup ioctl
Add unprivileged version of ino_lookup ioctl BTRFS_IOC_INO_LOOKUP_USER to allow normal users to call "btrfs subvololume list/show" etc. in combination with BTRFS_IOC_GET_SUBVOL_INFO/BTRFS_IOC_GET_SUBVOL_ROOTREF. This can be used like BTRFS_IOC_INO_LOOKUP but the argument is different. This is because it always searches the fs/file tree correspoinding to the fd with which this ioctl is called and also returns the name of bottom subvolume. The main differences from original ino_lookup ioctl are: 1. Read + Exec permission will be checked using inode_permission() during path construction. -EACCES will be returned in case of failure. 2. Path construction will be stopped at the inode number which corresponds to the fd with which this ioctl is called. If constructed path does not exist under fd's inode, -EACCES will be returned. 3. The name of bottom subvolume is also searched and filled. Note that the maximum length of path is shorter 256 (BTRFS_VOL_NAME_MAX+1) bytes than ino_lookup ioctl because of space of subvolume's name. Signed-off-by: Tomohiro Misono <misono.tomoh...@jp.fujitsu.com> --- fs/btrfs/ioctl.c | 204 + include/uapi/linux/btrfs.h | 17 2 files changed, 221 insertions(+) diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index db5de77540e1..5120e934f602 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -2200,6 +2200,166 @@ static noinline int btrfs_search_path_in_tree(struct btrfs_fs_info *info, return ret; } +static noinline int btrfs_search_path_in_tree_user(struct inode *inode, + struct btrfs_ioctl_ino_lookup_user_args *args) +{ + struct btrfs_fs_info *fs_info = BTRFS_I(inode)->root->fs_info; + struct super_block *sb = inode->i_sb; + struct btrfs_key upper_limit = BTRFS_I(inode)->location; + u64 treeid = BTRFS_I(inode)->root->root_key.objectid; + u64 dirid = args->dirid; + + unsigned long item_off; + unsigned long item_len; + struct btrfs_inode_ref *iref; + struct btrfs_root_ref *rref; + struct btrfs_root *root; + struct btrfs_path *path; + struct btrfs_key key, key2; + struct extent_buffer *l; + struct inode *temp_inode; + char *ptr; + int slot; + int len; + int total_len = 0; + int ret; + + path = btrfs_alloc_path(); + if (!path) + return -ENOMEM; + + /* +* If the bottom subvolume does not exist directly under upper_limit, +* construct the path in bottomup way. +*/ + if (dirid != upper_limit.objectid) { + ptr = >path[BTRFS_INO_LOOKUP_USER_PATH_MAX - 1]; + + key.objectid = treeid; + key.type = BTRFS_ROOT_ITEM_KEY; + key.offset = (u64)-1; + root = btrfs_read_fs_root_no_name(fs_info, ); + if (IS_ERR(root)) { + ret = -ENOENT; + goto out; + } + + key.objectid = dirid; + key.type = BTRFS_INODE_REF_KEY; + key.offset = (u64)-1; + while (1) { + ret = btrfs_search_slot(NULL, root, , path, 0, 0); + if (ret < 0) { + goto out; + } else if (ret > 0) { + ret = btrfs_previous_item(root, path, dirid, + BTRFS_INODE_REF_KEY); + if (ret < 0) { + goto out; + } else if (ret > 0) { + ret = -ENOENT; + goto out; + } + } + + l = path->nodes[0]; + slot = path->slots[0]; + btrfs_item_key_to_cpu(l, , slot); + + iref = btrfs_item_ptr(l, slot, struct btrfs_inode_ref); + len = btrfs_inode_ref_name_len(l, iref); + ptr -= len + 1; + total_len += len + 1; + if (ptr < args->path) { + ret = -ENAMETOOLONG; + goto out; + } + + *(ptr + len) = '/'; + read_extent_buffer(l, ptr, + (unsigned long)(iref + 1), len); + + /* Check the read+exec permission of this directory */ + ret = btrfs_previous_item(root, path, dirid, + BTRFS_INODE_ITEM_KEY); + if (ret < 0) { + goto out; +
[PATCH 00/11] btrfs-progs: Rework of "subvolume list/show" and relax the root privileges of them
Hello, This series is an updated version of [RFC PATCH v3 0/7] btrfs-progs: Allow normal user to call "subvolume list/show" [1] and requires new ioctls which can be found in ML as [PATCH v4 0/3] btrfs: Add three new unprivileged ioctls to allow normal users to call "sub list/show" etc. Or, code can be found at: kernel ... https://github.com/t-msn/linux/tree/add-user-subvol-ioctl-misc progs ... https://github.com/t-msn/btrfs-progs/tree/rework-sub-list Since libbtrfsutil has been merged, I completely rewrote the logic using libbtrfsutil and reset the version number. The aim of this series is to relax the root privileges of "sub list/show" while keeping as much output consistency between root and non-privileged user. For "subvolume list", default output has been changed from current btrfs-progs (see below). * Behavior summary of new "sub list/show" - "subvolume list" - The default behavior is changed to output only the subvolumes which exist below the specified path (incl. the specified path itself. the subvolumes mounted below the specified path is not considered yet). - If kernel supports new ioctls, the path to a non-subvolume directory can be specified. - If kernel supports new ioctls, non-privileged user can also call it. The subvolumes which cannot be accessed will be skipped. Note that root user can get all the subvolume information in the fs by using -a option just as before. - "subvolume show" - No change for root. - If kernel supports new ioctls, non-privileged user can also call it. In that case, the path to be shown is a relative from mount point and snapshots field lists snapshots which exist under mountpoint. * Patch structure 1st-5th update the libbtrfsutil using new ioctls: - Relax the privileges of following functions if kernel supports new ioctls and @top/@id is zero (i.e. the given path/fd is used instead of arbitrary subvolume id). - util_subvolume_info() - subvolume iterator related ones (util_subvolume_iterator_next() etc.) - For subvolume iterator, if kernel supports new ioctls and @top is zero, non-subvolume directory can be specified as a start point. Also, subvolume which cannot be accessed (either because of permission error or not found (may happen if other volume is mounted in the path)) will be skipped. 6th patch update the "sub list" to use libbtrfsutil (no behavior change) This is a copy of non-merged following patch originally written by Omar Sandoval: btrfs-progs: use libbtrfsutil for subvolume list [2] expect this commit keeps libbtrfs implementation which above commit tries to remove. (I suspect that the part of the reason that the original patch has not been merged is it removes libbtrfs and this commits modify this. but I'm completely fine with the original patch instead of this.) 7th-9th patch update the behavior of "sub list/show" 10th-11th patch is a cli-test for "sub list" of new behavior. * Future todo: If this approach is ok, I'd like to update the output of "sub list" more like: - Consider subvolume mounted below the specified path and list them as well - Remove obsolete field (i.e. top-level) from output Any comments are welcome. Thanks, Tomohiro Misono [1] https://www.spinics.net/lists/linux-btrfs/msg76008.html [2] https://www.spinics.net/lists/linux-btrfs/msg74917.html Tomohiro Misono (11): btrfs-progs: ioctl/libbtrfsutil: Add 3 definitions of new unprivileged ioctl btrfs-progs: libbtrfsutil: Factor out btrfs_util_subvolume_info_fd() btrfs-porgs: libbtrfsutil: Relax the privileges of util_subvolume_info() btrfs-progs: libbtrfsuitl: Factor out btrfs_util_subvolume_iterator_next() btrfs-progs: libbtrfsutil: Update the behavior of subvolume iterator and relax the privileges btrfs-progs: sub list: Use libbtrfsuitl for subvolume list btrfs-progs: sub list: Change the default behavior of "subvolume list" and allow non-privileged user to call it btrfs-progs: utils: Fallback to open without O_NOATIME flag in find_mount_root(): btrfs-progs: sub show: Allow non-privileged user to call "subvolume show" btrfs-progs: test: Add helper function to check if test user exists btrfs-porgs: test: Add cli-test/009 to check subvolume list for both root and normal user Documentation/btrfs-subvolume.asciidoc |2 + cmds-subvolume.c | 1123 +++- ioctl.h| 86 +++ libbtrfsutil/btrfs.h | 84 +++ libbtrfsutil/btrfsutil.h | 26 +- libbtrfsutil/errors.c |8 + libbtrfsutil/subvolume.c | 429 +-- tests/cli-tests/009-subvolume-list/test.sh | 136 tests/com
[PATCH 11/11] btrfs-porgs: test: Add cli-test/009 to check subvolume list for both root and normal user
Signed-off-by: Tomohiro Misono <misono.tomoh...@jp.fujitsu.com> --- tests/cli-tests/009-subvolume-list/test.sh | 136 + 1 file changed, 136 insertions(+) create mode 100755 tests/cli-tests/009-subvolume-list/test.sh diff --git a/tests/cli-tests/009-subvolume-list/test.sh b/tests/cli-tests/009-subvolume-list/test.sh new file mode 100755 index ..5f7b7919 --- /dev/null +++ b/tests/cli-tests/009-subvolume-list/test.sh @@ -0,0 +1,136 @@ +#!/bin/bash +# test for "subvolume list" both for root and normal user + +source "$TEST_TOP/common" + +check_testuser +check_prereq mkfs.btrfs +check_prereq btrfs + +setup_root_helper +prepare_test_dev + + +# test if the ids returned by "sub list" match expected ids +# $1 ... indicate run as root or test user +# $2 ... PATH to be specified by sub list command +# $3~ ... expected return ids +test_list() +{ + local SUDO + if [ $1 -eq 1 ]; then + SUDO=$SUDO_HELPER + else + SUDO="sudo -u progs-test" + fi + + result=$(run_check_stdout $SUDO "$TOP/btrfs" subvolume list "$2" | \ + awk '{print $2}' | xargs | sort -n) + + shift + shift + expected=($(echo "$@" | tr " " "\n" | sort -n)) + expected=$(IFS=" "; echo "${expected[*]}") + + if [ "$result" != "$expected" ]; then + echo "result : $result" + echo "expected: $expected" + _fail "ids returned by sub list does not match expected ids" + fi +} + +run_check $SUDO_HELPER "$TOP/mkfs.btrfs" -f "$TEST_DEV" +run_check_mount_test_dev +cd "$TEST_MNT" + +# create subvolumes and directories and make some non-readable +# by user 'progs-test' +run_check $SUDO_HELPER "$TOP/btrfs" subvolume create sub1 +run_check $SUDO_HELPER "$TOP/btrfs" subvolume create sub1/subsub1 +run_check $SUDO_HELPER mkdir sub1/dir + +run_check $SUDO_HELPER "$TOP/btrfs" subvolume create sub2 +run_check $SUDO_HELPER mkdir -p sub2/dir/dirdir +run_check $SUDO_HELPER "$TOP/btrfs" subvolume create sub2/dir/subsub2 +run_check $SUDO_HELPER "$TOP/btrfs" subvolume create sub2/dir/dirdir/subsubX + +run_check $SUDO_HELPER "$TOP/btrfs" subvolume create sub3 +run_check $SUDO_HELPER "$TOP/btrfs" subvolume create sub3/subsub3 +run_check $SUDO_HELPER mkdir sub3/dir +run_check $SUDO_HELPER "$TOP/btrfs" subvolume create sub3/dir/subsubY +run_check $SUDO_HELPER chmod o-r sub3 + +run_check $SUDO_HELPER "$TOP/btrfs" subvolume create sub4 +run_check $SUDO_HELPER "$TOP/btrfs" subvolume create sub4/subsub4 +run_check $SUDO_HELPER mkdir sub4/dir +run_check $SUDO_HELPER "$TOP/btrfs" subvolume create sub4/dir/subsubZ +run_check $SUDO_HELPER setfacl -m u:progs-test:- sub4/dir + +run_check $SUDO_HELPER touch "file" + +# expected result for root at mount point: +# +# ID 256 gen 8 top level 5 path sub1 +# ID 258 gen 7 top level 256 path sub1/subsub1 +# ID 259 gen 10 top level 5 path sub2 +# ID 260 gen 9 top level 259 path sub2/dir/subsub2 +# ID 261 gen 10 top level 259 path sub2/dir/dirdir/subsubX +# ID 262 gen 14 top level 5 path sub3 +# ID 263 gen 12 top level 262 path sub3/subsub3 +# ID 264 gen 13 top level 262 path sub3/dir/subsubY +# ID 265 gen 17 top level 5 path sub4 +# ID 266 gen 15 top level 265 path sub4/subsub4 +# ID 267 gen 16 top level 265 path sub4/dir/subsubZ + +# check for root for both absolute/relative path +# always returns all subvolumes +all=(256 258 259 260 261 262 263 264 265 266 267) +test_list 1 "$TEST_MNT" "${all[@]}" +test_list 1 "$TEST_MNT/sub1" "256 258" +test_list 1 "$TEST_MNT/sub1/dir" "" +test_list 1 "$TEST_MNT/sub2" "259 260 261" +test_list 1 "$TEST_MNT/sub2/dir" "260 261" +test_list 1 "$TEST_MNT/sub3" "262 263 264" +test_list 1 "$TEST_MNT/sub4" "265 266 267" +run_mustfail "should fail for file" \ + $SUDO_HELPER "$TOP/btrfs" subvolume list "$TEST_MNT/file" + +test_list 1 "." "${all[@]}" +test_list 1 "sub1" "256 258" +test_list 1 "sub1/dir" "" +test_list 1 "sub2" "259 260 261" +test_list 1 "sub2/dir" "260 261" +test_list 1 "sub3" "262 263 264" +test_list 1 "sub4" "265 266 267" +run_mustfail "should fail for file" \ + $SUDO_HELPER "$TOP/btrfs" subvolume list "file" + +# check for normal user for both absolute/relative path +# only returns subvolumes under specified path +test_list 0 "$TEST_MNT" "256 258 259 260 26
[PATCH 03/11] btrfs-porgs: libbtrfsutil: Relax the privileges of util_subvolume_info()
By using new ioctl (BTRFS_IOC_GET_SUBVOL_INFO), this commit allows non-privileged user to call util_subvolume_info() as long as @id is zero (user can only get the information of the subvolume which he can open). Signed-off-by: Tomohiro Misono <misono.tomoh...@jp.fujitsu.com> --- libbtrfsutil/btrfsutil.h | 7 +- libbtrfsutil/errors.c| 4 libbtrfsutil/subvolume.c | 58 3 files changed, 68 insertions(+), 1 deletion(-) diff --git a/libbtrfsutil/btrfsutil.h b/libbtrfsutil/btrfsutil.h index 6d655f49..5fe798c5 100644 --- a/libbtrfsutil/btrfsutil.h +++ b/libbtrfsutil/btrfsutil.h @@ -63,6 +63,8 @@ enum btrfs_util_error { BTRFS_UTIL_ERROR_SYNC_FAILED, BTRFS_UTIL_ERROR_START_SYNC_FAILED, BTRFS_UTIL_ERROR_WAIT_SYNC_FAILED, + BTRFS_UTIL_ERROR_INVALID_ARGUMENT_FOR_USER, + BTRFS_UTIL_ERROR_GET_SUBVOL_INFO_FAILED, }; /** @@ -266,7 +268,10 @@ struct btrfs_util_subvolume_info { * to check whether the subvolume exists; %BTRFS_UTIL_ERROR_SUBVOLUME_NOT_FOUND * will be returned if it does not. * - * This requires appropriate privilege (CAP_SYS_ADMIN). + * This requires appropriate privilege (CAP_SYS_ADMIN) for older kernel. + * For newer kernel which supports BTRFS_IOC_GET_SUGBVOL_INFO, + * non-privileged user with appropriate permission for @path can use this too + * (in that case @id must be zero). * * Return: %BTRFS_UTIL_OK on success, non-zero error code on failure. */ diff --git a/libbtrfsutil/errors.c b/libbtrfsutil/errors.c index 634edc65..f196fa71 100644 --- a/libbtrfsutil/errors.c +++ b/libbtrfsutil/errors.c @@ -45,6 +45,10 @@ static const char * const error_messages[] = { [BTRFS_UTIL_ERROR_SYNC_FAILED] = "Could not sync filesystem", [BTRFS_UTIL_ERROR_START_SYNC_FAILED] = "Could not start filesystem sync", [BTRFS_UTIL_ERROR_WAIT_SYNC_FAILED] = "Could not wait for filesystem sync", + [BTRFS_UTIL_ERROR_INVALID_ARGUMENT_FOR_USER] = + "Non-root user cannot specify subvolume id", + [BTRFS_UTIL_ERROR_GET_SUBVOL_INFO_FAILED] = + "Could not get subvolume information by BTRFS_IOC_GET_SUBVOL_INFO", }; PUBLIC const char *btrfs_util_strerror(enum btrfs_util_error err) diff --git a/libbtrfsutil/subvolume.c b/libbtrfsutil/subvolume.c index 0d7ef5bf..3ce6e0a6 100644 --- a/libbtrfsutil/subvolume.c +++ b/libbtrfsutil/subvolume.c @@ -31,6 +31,14 @@ #include "btrfsutil_internal.h" +static bool is_root(void) +{ + uid_t uid; + + uid = geteuid(); + return (uid == 0); +} + /* * This intentionally duplicates btrfs_util_is_subvolume_fd() instead of opening * a file descriptor and calling it, because fstat() and fstatfs() don't accept @@ -383,11 +391,61 @@ static enum btrfs_util_error get_subvolume_info_root(int fd, uint64_t id, return BTRFS_UTIL_OK; } +static enum btrfs_util_error get_subvolume_info_user(int fd, +struct btrfs_util_subvolume_info *subvol) +{ + struct btrfs_ioctl_get_subvol_info_args info; + int ret; + + ret = ioctl(fd, BTRFS_IOC_GET_SUBVOL_INFO, ); + if (ret < 0) + return BTRFS_UTIL_ERROR_GET_SUBVOL_INFO_FAILED; + + subvol->id = info.id; + subvol->parent_id = info.parent_id; + subvol->dir_id = info.dirid; + subvol->flags = info.flags; + subvol->generation = info.generation; + + memcpy(subvol->uuid, info.uuid, sizeof(subvol->uuid)); + memcpy(subvol->parent_uuid, info.parent_uuid, + sizeof(subvol->parent_uuid)); + memcpy(subvol->received_uuid, info.received_uuid, + sizeof(subvol->received_uuid)); + + subvol->ctransid = info.ctransid; + subvol->otransid = info.otransid; + subvol->stransid = info.stransid; + subvol->rtransid = info.rtransid; + + subvol->ctime.tv_sec = info.ctime.sec; + subvol->ctime.tv_nsec = info.ctime.nsec; + subvol->otime.tv_sec = info.otime.sec; + subvol->otime.tv_nsec = info.otime.nsec; + subvol->stime.tv_sec = info.stime.sec; + subvol->stime.tv_nsec = info.stime.nsec; + subvol->rtime.tv_sec = info.rtime.sec; + subvol->rtime.tv_nsec = info.rtime.nsec; + + return BTRFS_UTIL_OK; +} + PUBLIC enum btrfs_util_error btrfs_util_subvolume_info_fd(int fd, uint64_t id, struct btrfs_util_subvolume_info *subvol) { enum btrfs_util_error err; + if (!is_root()) { + if (id != 0) + return BTRFS_UTIL_ERROR_INVALID_ARGUMENT_FOR_USER; + + err = btrfs_util_is_subvolume_fd(fd); + if (err) + return err; + + return get_subvolume_info_user
[PATCH 04/11] btrfs-progs: libbtrfsuitl: Factor out btrfs_util_subvolume_iterator_next()
Factor out the main logic of btrfs_util_subvolume_iterator_next(). This is a prepareation work to update the behavior of this function and relax the required root privilege. No functional change happens. Signed-off-by: Tomohiro Misono <misono.tomoh...@jp.fujitsu.com> --- libbtrfsutil/subvolume.c | 9 - 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/libbtrfsutil/subvolume.c b/libbtrfsutil/subvolume.c index 3ce6e0a6..08bbeca2 100644 --- a/libbtrfsutil/subvolume.c +++ b/libbtrfsutil/subvolume.c @@ -1255,7 +1255,7 @@ static enum btrfs_util_error build_subvol_path(struct btrfs_util_subvolume_itera return BTRFS_UTIL_OK; } -PUBLIC enum btrfs_util_error btrfs_util_subvolume_iterator_next(struct btrfs_util_subvolume_iterator *iter, +static enum btrfs_util_error subvolume_iterator_next_root(struct btrfs_util_subvolume_iterator *iter, char **path_ret, uint64_t *id_ret) { @@ -1331,6 +1331,13 @@ out: return BTRFS_UTIL_OK; } +PUBLIC enum btrfs_util_error btrfs_util_subvolume_iterator_next(struct btrfs_util_subvolume_iterator *iter, + char **path_ret, + uint64_t *id_ret) +{ + return subvolume_iterator_next_root(iter, path_ret, id_ret); +} + PUBLIC enum btrfs_util_error btrfs_util_subvolume_iterator_next_info(struct btrfs_util_subvolume_iterator *iter, char **path_ret, struct btrfs_util_subvolume_info *subvol) -- 2.14.3 -- To unsubscribe from this list: send the line "unsubscribe linux-btrfs" in the body of a message to majord...@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
[PATCH 05/11] btrfs-progs: libbtrfsutil: Update the behavior of subvolume iterator and relax the privileges
By using new ioctls (BTRFS_IOC_GET_ROOTREF_INFO/BTRFS_IOC_INO_LOOKUP_USER), this commit update the subvolume iterator when it is created by btrfs_util_create_subvolume_iterator() with @top zero (i.e. if the iterator is created from givin path/fd). In that case, - an iterator can be created from non-subvolume directory and will skip a subvolume if - it does not exist nor has different id from the found subvolume by INO_LOOKUP_USER (may happen if a dir in the path is being mounted) - it cannot be opened due to permission error Since above ioctls do not require root privileges, non-privileged user can also use the iterator. If @top is specified, the behavior is the same as before (and thus non-privileged user cannot use). Signed-off-by: Tomohiro Misono <misono.tomoh...@jp.fujitsu.com> --- libbtrfsutil/btrfsutil.h | 19 ++- libbtrfsutil/errors.c| 4 + libbtrfsutil/subvolume.c | 319 +++ 3 files changed, 315 insertions(+), 27 deletions(-) diff --git a/libbtrfsutil/btrfsutil.h b/libbtrfsutil/btrfsutil.h index 5fe798c5..b90dc93e 100644 --- a/libbtrfsutil/btrfsutil.h +++ b/libbtrfsutil/btrfsutil.h @@ -65,6 +65,8 @@ enum btrfs_util_error { BTRFS_UTIL_ERROR_WAIT_SYNC_FAILED, BTRFS_UTIL_ERROR_INVALID_ARGUMENT_FOR_USER, BTRFS_UTIL_ERROR_GET_SUBVOL_INFO_FAILED, + BTRFS_UTIL_ERROR_GET_SUBVOL_ROOTREF_FAILED, + BTRFS_UTIL_ERROR_INO_LOOKUP_USER_FAILED, }; /** @@ -510,6 +512,11 @@ struct btrfs_util_subvolume_iterator; * @flags: Bitmask of BTRFS_UTIL_SUBVOLUME_ITERATOR_* flags. * @ret: Returned iterator. * + * For newer kenrels which supports BTRFS_IOC_GET_SUBVOL_ROOTREF and + * BTRFS_IOC_INO_LOOKUP_USER, @path does not have to refer to a subvolume when + * @top is zero. In that case, subvolumes only below the specified path will + * be returned. + * * The returned iterator must be freed with * btrfs_util_destroy_subvolume_iterator(). * @@ -558,7 +565,11 @@ int btrfs_util_subvolume_iterator_fd(const struct btrfs_util_subvolume_iterator * Must be freed with free(). * @id_ret: Returned subvolume ID. May be %NULL. * - * This requires appropriate privilege (CAP_SYS_ADMIN). + * This requires appropriate privilege (CAP_SYS_ADMIN) for older kernel. + * For newer kenrels which supports BTRFS_IOC_GET_SUBVOL_ROOTREF and + * BTRFS_IOC_INO_LOOKUP_USER, non-privileged user also can use this. + * In that case, subvolumes which cannot be accessed by the user will be + * skipped. * * Return: %BTRFS_UTIL_OK on success, %BTRFS_UTIL_ERROR_STOP_ITERATION if there * are no more subvolumes, non-zero error code on failure. @@ -577,7 +588,11 @@ enum btrfs_util_error btrfs_util_subvolume_iterator_next(struct btrfs_util_subvo * This convenience function basically combines * btrfs_util_subvolume_iterator_next() and btrfs_util_subvolume_info(). * - * This requires appropriate privilege (CAP_SYS_ADMIN). + * This requires appropriate privilege (CAP_SYS_ADMIN) for older kernel. + * For newer kenrels which supports BTRFS_IOC_GET_SUGBVOL_INFO, + * BTRFS_IOC_GET_SUBVOL_ROOTREF and BTRFS_IOC_INO_LOOKUP_USER, + * non-privileged user also can use this. In that case, subvolumes which + * cannot be accessed by the user will be skipped. * * Return: See btrfs_util_subvolume_iterator_next(). */ diff --git a/libbtrfsutil/errors.c b/libbtrfsutil/errors.c index f196fa71..21bbc7b2 100644 --- a/libbtrfsutil/errors.c +++ b/libbtrfsutil/errors.c @@ -49,6 +49,10 @@ static const char * const error_messages[] = { "Non-root user cannot specify subvolume id", [BTRFS_UTIL_ERROR_GET_SUBVOL_INFO_FAILED] = "Could not get subvolume information by BTRFS_IOC_GET_SUBVOL_INFO", + [BTRFS_UTIL_ERROR_GET_SUBVOL_ROOTREF_FAILED] = + "Could not get rootref information by BTRRFS_IOC_GET_ROOTREF_INFO", + [BTRFS_UTIL_ERROR_INO_LOOKUP_USER_FAILED] = + "Could not resolve subvolume path by BTRFS_IOC_INO_LOOKUP_USER", }; PUBLIC const char *btrfs_util_strerror(enum btrfs_util_error err) diff --git a/libbtrfsutil/subvolume.c b/libbtrfsutil/subvolume.c index 08bbeca2..036af546 100644 --- a/libbtrfsutil/subvolume.c +++ b/libbtrfsutil/subvolume.c @@ -39,6 +39,24 @@ static bool is_root(void) return (uid == 0); } +/* + * We need both BTRFS_IOC_GET_SUBVOL_ROOTREF and BTRFS_IOC_INO_LOOKUP_USER + * but only checks BTRFS_IOC_GET_SUBVOL_ROOTREF for brevity. + */ +static bool check_support_rootref_ioctl(int fd) +{ + struct btrfs_ioctl_get_subvol_rootref_args args; + int ret; + + memset(, 0, sizeof(args)); + ret = ioctl(fd, BTRFS_IOC_GET_SUBVOL_ROOTREF, ); + + if (ret < 0 && errno == ENOTTY) + return false; + + return true; +} + /* * This intentionally duplicates btrfs_util_is_subvolume_fd() instead of opening * a file descriptor and calling it, because fstat() and fstatfs() don't
[PATCH 06/11] btrfs-progs: sub list: Use libbtrfsuitl for subvolume list
This is a copy of non-merged following patch originally written by Omar Sandoval: btrfs-progs: use libbtrfsutil for subvolume list expect this commit keeps libbtrfs implementation which above commit tries to remove (therefore this adds suffix _v2 for struct/function). Original Author: Omar Sandoval <osan...@fb.com> Signed-off-by: Tomohiro Misono <misono.tomoh...@jp.fujitsu.com> --- cmds-subvolume.c | 961 +-- 1 file changed, 934 insertions(+), 27 deletions(-) diff --git a/cmds-subvolume.c b/cmds-subvolume.c index 45363a5a..06686943 100644 --- a/cmds-subvolume.c +++ b/cmds-subvolume.c @@ -404,6 +404,913 @@ keep_fd: return ret; } +#define BTRFS_LIST_NFILTERS_INCREASE (2 * BTRFS_LIST_FILTER_MAX) +#define BTRFS_LIST_NCOMPS_INCREASE (2 * BTRFS_LIST_COMP_MAX) + +struct listed_subvol { + struct btrfs_util_subvolume_info info; + char *path; +}; + +struct subvol_list { + size_t num; + struct listed_subvol subvols[]; +}; + +typedef int (*btrfs_list_filter_func_v2)(struct listed_subvol *, uint64_t); +typedef int (*btrfs_list_comp_func_v2)(const struct listed_subvol *, + const struct listed_subvol *, + int); + +struct btrfs_list_filter_v2 { + btrfs_list_filter_func_v2 filter_func; + u64 data; +}; + +struct btrfs_list_comparer_v2 { + btrfs_list_comp_func_v2 comp_func; + int is_descending; +}; + +struct btrfs_list_filter_set_v2 { + int total; + int nfilters; + int only_deleted; + struct btrfs_list_filter_v2 filters[0]; +}; + +struct btrfs_list_comparer_set_v2 { + int total; + int ncomps; + struct btrfs_list_comparer_v2 comps[0]; +}; + +static struct { + char*name; + char*column_name; + int need_print; +} btrfs_list_columns[] = { + { + .name = "ID", + .column_name= "ID", + .need_print = 0, + }, + { + .name = "gen", + .column_name= "Gen", + .need_print = 0, + }, + { + .name = "cgen", + .column_name= "CGen", + .need_print = 0, + }, + { + .name = "parent", + .column_name= "Parent", + .need_print = 0, + }, + { + .name = "top level", + .column_name= "Top Level", + .need_print = 0, + }, + { + .name = "otime", + .column_name= "OTime", + .need_print = 0, + }, + { + .name = "parent_uuid", + .column_name= "Parent UUID", + .need_print = 0, + }, + { + .name = "received_uuid", + .column_name= "Received UUID", + .need_print = 0, + }, + { + .name = "uuid", + .column_name= "UUID", + .need_print = 0, + }, + { + .name = "path", + .column_name= "Path", + .need_print = 0, + }, + { + .name = NULL, + .column_name= NULL, + .need_print = 0, + }, +}; + +static btrfs_list_filter_func_v2 all_filter_funcs[]; +static btrfs_list_comp_func_v2 all_comp_funcs[]; + +static void btrfs_list_setup_print_column_v2(enum btrfs_list_column_enum column) +{ + int i; + + ASSERT(0 <= column && column <= BTRFS_LIST_ALL); + + if (column < BTRFS_LIST_ALL) { + btrfs_list_columns[column].need_print = 1; + return; + } + + for (i = 0; i < BTRFS_LIST_ALL; i++) + btrfs_list_columns[i].need_print = 1; +} + +static int comp_entry_with_rootid_v2(const struct listed_subvol *entry1, + const struct listed_subvol *entry2, + int is_descending) +{ + int ret; + + if (entry1->info.id > entry2->info.id) + ret = 1; + else if (entry1->info.id < entry2->info.id) + ret = -1; + else + ret = 0; + + return is_descending ? -ret : ret; +} + +static int comp_entry_with_gen_v2(const struct listed_subvol *entry1, + const struct listed_subvol *entry2, + int is_descending) +{ + int ret; + + if (entry1->info.generation > entry2->info.g
[PATCH 01/11] btrfs-progs: ioctl/libbtrfsutil: Add 3 definitions of new unprivileged ioctl
Add 3 definitions of new unprivileged ioctl (BTRFS_IOC_GET_SUBVOL_INFO, BTRFS_IOC_GET_SUBVOL_ROOTREF and BTRFS_IOC_INO_LOOKUP_USER). They will be used to implement the user version of "btrfs subvolume list" etc. Signed-off-by: Tomohiro Misono <misono.tomoh...@jp.fujitsu.com> --- ioctl.h | 86 libbtrfsutil/btrfs.h | 84 ++ 2 files changed, 170 insertions(+) diff --git a/ioctl.h b/ioctl.h index 709e996f..c6624352 100644 --- a/ioctl.h +++ b/ioctl.h @@ -320,6 +320,22 @@ struct btrfs_ioctl_ino_lookup_args { }; BUILD_ASSERT(sizeof(struct btrfs_ioctl_ino_lookup_args) == 4096); +#define BTRFS_INO_LOOKUP_USER_PATH_MAX (4080-BTRFS_VOL_NAME_MAX-1) +struct btrfs_ioctl_ino_lookup_user_args { + /* in, inode number containing the subvolume of 'subvolid' */ + __u64 dirid; + /* in */ + __u64 subvolid; + /* out, name of the subvolume of 'subvolid' */ + char name[BTRFS_VOL_NAME_MAX + 1]; + /* +* out, constructed path from the directory with which +* the ioctl is called to dirid +*/ + char path[BTRFS_INO_LOOKUP_USER_PATH_MAX]; +}; +BUILD_ASSERT(sizeof(struct btrfs_ioctl_ino_lookup_user_args) == 4096); + struct btrfs_ioctl_search_key { /* which root are we searching. 0 is the tree of tree roots */ __u64 tree_id; @@ -672,6 +688,70 @@ BUILD_ASSERT(sizeof(struct btrfs_ioctl_send_args_64) == 72); #define BTRFS_IOC_SEND_64_COMPAT_DEFINED 1 +struct btrfs_ioctl_get_subvol_info_args { + /* All filed is out */ + /* Id of this subvolume */ + __u64 id; + /* Name of this subvolume, used to get the real name at mount point */ + char name[BTRFS_VOL_NAME_MAX + 1]; + /* +* Id of the subvolume which contains this subvolume. +* Zero for top-level subvolume or deleted subvolume +*/ + __u64 parent_id; + /* +* Inode number of the directory which contains this subvolume. +* Zero for top-level subvolume or deleted subvolume +*/ + __u64 dirid; + + /* Latest transaction id of this subvolume */ + __u64 generation; + /* Flags of this subvolume */ + __u64 flags; + + /* uuid of this subvolume */ + __u8 uuid[BTRFS_UUID_SIZE]; + /* +* uuid of the subvolume of which this subvolume is a snapshot. +* All zero for non-snapshot subvolume +*/ + __u8 parent_uuid[BTRFS_UUID_SIZE]; + /* +* uuid of the subvolume from which this subvolume is received. +* All zero for non-received subvolume +*/ + __u8 received_uuid[BTRFS_UUID_SIZE]; + + /* Transaction id indicates when change/create/send/receive happens */ + __u64 ctransid; + __u64 otransid; + __u64 stransid; + __u64 rtransid; + /* Time corresponds to c/o/s/rtransid */ + struct btrfs_ioctl_timespec ctime; + struct btrfs_ioctl_timespec otime; + struct btrfs_ioctl_timespec stime; + struct btrfs_ioctl_timespec rtime; + + __u64 reserved[8]; +}; + +#define BTRFS_MAX_ROOTREF_BUFFER_NUM 255 +struct btrfs_ioctl_get_subvol_rootref_args { + /* in/out, min id of rootref's subvolid to be searched */ + __u64 min_id; + /* out */ + struct { + __u64 subvolid; + __u64 dirid; + } rootref[BTRFS_MAX_ROOTREF_BUFFER_NUM]; + /* out, number of found items */ + __u8 num_items; + __u8 align[7]; +}; +BUILD_ASSERT(sizeof(struct btrfs_ioctl_get_subvol_rootref_args) == 4096); + /* Error codes as returned by the kernel */ enum btrfs_err_code { notused, @@ -828,6 +908,12 @@ static inline char *btrfs_err_str(enum btrfs_err_code err_code) struct btrfs_ioctl_feature_flags[3]) #define BTRFS_IOC_RM_DEV_V2_IOW(BTRFS_IOCTL_MAGIC, 58, \ struct btrfs_ioctl_vol_args_v2) +#define BTRFS_IOC_GET_SUBVOL_INFO _IOR(BTRFS_IOCTL_MAGIC, 60, \ + struct btrfs_ioctl_get_subvol_info_args) +#define BTRFS_IOC_GET_SUBVOL_ROOTREF _IOWR(BTRFS_IOCTL_MAGIC, 61, \ + 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) #ifdef __cplusplus } #endif diff --git a/libbtrfsutil/btrfs.h b/libbtrfsutil/btrfs.h index c293f6bf..451e227c 100644 --- a/libbtrfsutil/btrfs.h +++ b/libbtrfsutil/btrfs.h @@ -421,6 +421,21 @@ struct btrfs_ioctl_ino_lookup_args { char name[BTRFS_INO_LOOKUP_PATH_MAX]; }; +#define BTRFS_INO_LOOKUP_USER_PATH_MAX (4080-BTRFS_VOL_NAME_MAX-1) +struct btrfs_ioctl_ino_lookup_user_args { + /* in, inode number containing the subvol
[PATCH 08/11] btrfs-progs: utils: Fallback to open without O_NOATIME flag in find_mount_root():
O_NOATIME flag requires effective UID of process matches file's owner or has CAP_FOWNER capabilities. Fallback to open without O_NOATIME flag so that non-privileged user can also call find_mount_root(). This is a preparation work to allow non-privileged user to call "subvolume show". Signed-off-by: Tomohiro Misono <misono.tomoh...@jp.fujitsu.com> --- utils.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/utils.c b/utils.c index d81d4980..84b81311 100644 --- a/utils.c +++ b/utils.c @@ -2048,6 +2048,9 @@ int find_mount_root(const char *path, char **mount_root) char *longest_match = NULL; fd = open(path, O_RDONLY | O_NOATIME); + if (fd < 0 && errno == EPERM) + fd = open(path, O_RDONLY); + if (fd < 0) return -errno; close(fd); -- 2.14.3 -- To unsubscribe from this list: send the line "unsubscribe linux-btrfs" in the body of a message to majord...@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
[PATCH 07/11] btrfs-progs: sub list: Change the default behavior of "subvolume list" and allow non-privileged user to call it
Change the default behavior of "subvolume list" and allow non-privileged user to call it as well. >From this commit, by default it only lists subvolumes under the specified path (incl. the path itself except top-level subvolume). Also, if kernel supports new ioctls (BTRFS_IOC_GET_SUBVOL_INFO/BTRFS_IOC_GET_ROOTREF/ BTRFS_IOC_INO_LOOKUP_USER), - the specified path can be non-subvolume directory. - non-privileged user can also call it. Note that root user can list all the subvolume in the fs with -a option (the same behavior as before). Signed-off-by: Tomohiro Misono <misono.tomoh...@jp.fujitsu.com> --- Documentation/btrfs-subvolume.asciidoc | 2 + cmds-subvolume.c | 90 +- 2 files changed, 81 insertions(+), 11 deletions(-) diff --git a/Documentation/btrfs-subvolume.asciidoc b/Documentation/btrfs-subvolume.asciidoc index a8c4af4b..e03d4a6e 100644 --- a/Documentation/btrfs-subvolume.asciidoc +++ b/Documentation/btrfs-subvolume.asciidoc @@ -92,6 +92,7 @@ The output format is similar to *subvolume list* command. *list* [options] [-G [\+|-]] [-C [+|-]] [--sort=rootid,gen,ogen,path] :: List the subvolumes present in the filesystem . +By default, this lists the subvolume under the specified path. + For every subvolume the following information is shown by default. + ID top level path + @@ -109,6 +110,7 @@ print parent ID. -a print all the subvolumes in the filesystem and distinguish between absolute and relative path with respect to the given . +This requires root privileges. -c print the ogeneration of the subvolume, aliases: ogen or origin generation. -g diff --git a/cmds-subvolume.c b/cmds-subvolume.c index 06686943..c3952172 100644 --- a/cmds-subvolume.c +++ b/cmds-subvolume.c @@ -1126,6 +1126,7 @@ out: } static struct subvol_list *btrfs_list_subvols(int fd, + int is_list_all, struct btrfs_list_filter_set_v2 *filter_set) { struct subvol_list *subvols; @@ -1133,6 +1134,7 @@ static struct subvol_list *btrfs_list_subvols(int fd, struct btrfs_util_subvolume_iterator *iter; enum btrfs_util_error err; int ret = -1; + int tree_id = 0; subvols = malloc(sizeof(*subvols)); if (!subvols) { @@ -1141,8 +1143,11 @@ static struct subvol_list *btrfs_list_subvols(int fd, } subvols->num = 0; + if (is_list_all) + tree_id = BTRFS_FS_TREE_OBJECTID; + err = btrfs_util_create_subvolume_iterator_fd(fd, - BTRFS_FS_TREE_OBJECTID, 0, + tree_id, 0, ); if (err) { iter = NULL; @@ -1189,6 +1194,60 @@ static struct subvol_list *btrfs_list_subvols(int fd, subvols->num++; } + /* +* Subvolume iterator does not include the information of the +* specified path/fd. So, add it here. +*/ + if (!is_list_all) { + uint64_t id; + struct listed_subvol subvol; + + err = btrfs_util_is_subvolume_fd(fd); + if (err != BTRFS_UTIL_OK) { + if (err == BTRFS_UTIL_ERROR_NOT_SUBVOLUME) + ret = 0; + goto out; + } + err = btrfs_util_subvolume_id_fd(fd, ); + if (err) + goto out; + if (id == BTRFS_FS_TREE_OBJECTID) { + /* Skip top level subvolume */ + ret = 0; + goto out; + } + + err = btrfs_util_subvolume_info_fd(fd, 0, ); + if (err) + goto out; + + subvol.path = strdup("."); + if (!filters_match(, filter_set)) { + free(subvol.path); + } else { + if (subvols->num >= capacity) { + struct subvol_list *new_subvols; + size_t new_capacity = + max_t(size_t, 1, capacity * 2); + + new_subvols = realloc(subvols, + sizeof(*new_subvols) + + new_capacity * + sizeof(new_subvols->subvols[0])); + if (!new_subvols) { + error("out of memory"); + goto out; + } + + subvols = new_subvols; + capacity = new_capacity; + } + +
[PATCH 09/11] btrfs-progs: sub show: Allow non-privileged user to call "subvolume show"
Allow non-privileged user to call subvolume show (-r or -u cannot be used) if new ioctls (BTRFS_IOC_GET_SUBVOL_INFO etc.) are available. The behavior for root user is the same as before. There are some output differences between root and user: root ... subvolume path is from top-level subvolume list all snapshots in the fs (inc. non-accessible ones) user ... subvolume path is from mount point list snapshots under the mountpoint (to which the user has appropriate privileges) Signed-off-by: Tomohiro Misono <misono.tomoh...@jp.fujitsu.com> --- cmds-subvolume.c | 90 1 file changed, 78 insertions(+), 12 deletions(-) diff --git a/cmds-subvolume.c b/cmds-subvolume.c index c3952172..d88d5d76 100644 --- a/cmds-subvolume.c +++ b/cmds-subvolume.c @@ -1883,8 +1883,8 @@ static int cmd_subvol_find_new(int argc, char **argv) static const char * const cmd_subvol_show_usage[] = { "btrfs subvolume show [options] |", "Show more information about the subvolume", - "-r|--rootid rootid of the subvolume", - "-u|--uuid uuid of the subvolume", + "-r|--rootid rootid of the subvolume (require root privileges)", + "-u|--uuid uuid of the subvolume (require root privileges)", "", "If no option is specified, will be shown, otherwise", "the rootid or uuid are resolved relative to the path.", @@ -1897,8 +1897,10 @@ static int cmd_subvol_show(int argc, char **argv) char uuidparse[BTRFS_UUID_UNPARSED_SIZE]; char *fullpath = NULL; int fd = -1; + int fd_mnt = -1; int ret = 1; DIR *dirstream1 = NULL; + DIR *dirstream_mnt = NULL; int by_rootid = 0; int by_uuid = 0; u64 rootid_arg = 0; @@ -1906,6 +1908,8 @@ static int cmd_subvol_show(int argc, char **argv) struct btrfs_util_subvolume_iterator *iter; struct btrfs_util_subvolume_info subvol; char *subvol_path = NULL; + char *subvol_name = NULL; + char *mount_point = NULL; enum btrfs_util_error err; while (1) { @@ -1943,6 +1947,11 @@ static int cmd_subvol_show(int argc, char **argv) usage(cmd_subvol_show_usage); } + if (!is_root() && (by_rootid || by_uuid)) { + error("Only root can use -r or -u options"); + return -1; + } + fullpath = realpath(argv[optind], NULL); if (!fullpath) { error("cannot find real path for '%s': %m", argv[optind]); @@ -1997,19 +2006,65 @@ static int cmd_subvol_show(int argc, char **argv) goto out; } - err = btrfs_util_subvolume_path_fd(fd, subvol.id, _path); - if (err) { - error_btrfs_util(err); - goto out; + if (is_root()) { + /* Construct path from top-level subvolume */ + err = btrfs_util_subvolume_path_fd(fd, subvol.id, + _path); + if (err) { + error_btrfs_util(err); + goto out; + } + subvol_name = strdup(basename(subvol_path)); + } else { + /* Construct path from mount point */ + ret = find_mount_root(fullpath, _point); + if (ret < 0) { + error("cannot get mount point"); + goto out; + } + + fd_mnt = open_file_or_dir(mount_point, _mnt); + if (fd_mnt < 0) { + error("cannot open mount point"); + goto out; + } + + if (strlen(fullpath) == strlen(mount_point)) { + /* Get real name at mount point */ + struct btrfs_ioctl_get_subvol_info_args arg; + + ret = ioctl(fd_mnt, BTRFS_IOC_GET_SUBVOL_INFO, + ); + if (ret < 0) { + error("cannot get subvolume info"); + goto out; + } + subvol_path = strdup("./"); + subvol_name = strdup(arg.name); + } else { + subvol_path = malloc(strlen(fullpath) - + strlen(mount_point)
[PATCH v4 2/3] btrfs: Add unprivileged ioctl which returns subvolume's ROOT_REF
Add unprivileged ioctl BTRFS_IOC_GET_SUBVOL_ROOTREF which returns ROOT_REF information of the subvolume containing this inode except the subvolume name (this is because to prevent potential name leak). The subvolume name will be gained by user version of ino_lookup ioctl (BTRFS_IOC_INO_LOOKUP_USER) which also performs permission check. The min id of root ref's subvolume to be searched is specified by @min_id in struct btrfs_ioctl_get_subvol_rootref_args. After the search ends, @min_id is set to the last searched root ref's subvolid + 1. Also, if there are more root refs than BTRFS_MAX_ROOTREF_BUFFER_NUM, -EOVERFLOW is returned. Therefore the caller can just call this ioctl again without changing the argument to continue search. Signed-off-by: Tomohiro Misono <misono.tomoh...@jp.fujitsu.com> --- fs/btrfs/ioctl.c | 102 + include/uapi/linux/btrfs.h | 16 +++ 2 files changed, 118 insertions(+) diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index 64b23e22852f..7988d328aed5 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -2369,6 +2369,106 @@ static noinline int btrfs_ioctl_get_subvol_info(struct file *file, return ret; } +/* + * Return ROOT_REF information of the subvolume contining this inode + * except the subvolume name. + */ +static noinline int btrfs_ioctl_get_subvol_rootref(struct file *file, + void __user *argp) +{ + struct btrfs_ioctl_get_subvol_rootref_args *rootrefs; + struct btrfs_root_ref *rref; + struct btrfs_root *root; + struct btrfs_path *path; + struct btrfs_key key; + + struct extent_buffer *l; + int slot; + + struct inode *inode; + int i, nritems; + int ret; + u64 objectid; + u8 found; + + path = btrfs_alloc_path(); + if (!path) + return -ENOMEM; + + rootrefs = memdup_user(argp, sizeof(*rootrefs)); + if (!rootrefs) { + btrfs_free_path(path); + return -ENOMEM; + } + + inode = file_inode(file); + root = BTRFS_I(inode)->root->fs_info->tree_root; + objectid = BTRFS_I(inode)->root->root_key.objectid; + + key.objectid = objectid; + key.type = BTRFS_ROOT_REF_KEY; + key.offset = rootrefs->min_id; + found = 0; + while (1) { + ret = btrfs_search_slot(NULL, root, , path, 0, 0); + if (ret < 0) { + goto out; + } else if (path->slots[0] >= + btrfs_header_nritems(path->nodes[0])) { + ret = btrfs_next_leaf(root, path); + if (ret < 0) + return ret; + } + + l = path->nodes[0]; + slot = path->slots[0]; + nritems = btrfs_header_nritems(l); + if (nritems - slot == 0) { + ret = 0; + goto out; + } + + for (i = slot; i < nritems; i++) { + btrfs_item_key_to_cpu(l, , i); + if (key.objectid != objectid || + key.type != BTRFS_ROOT_REF_KEY) { + ret = 0; + goto out; + } + + if (found == BTRFS_MAX_ROOTREF_BUFFER_NUM) { + ret = -EOVERFLOW; + goto out; + } + + rref = btrfs_item_ptr(l, i, struct btrfs_root_ref); + rootrefs->rootref[found].subvolid = key.offset; + rootrefs->rootref[found].dirid = + btrfs_root_ref_dirid(l, rref); + found++; + } + + btrfs_release_path(path); + key.offset++; + } + +out: + if (!ret || ret == -EOVERFLOW) { + rootrefs->num_items = found; + /* update min_id for next search */ + if (found) + rootrefs->min_id = + rootrefs->rootref[found - 1].subvolid + 1; + if (copy_to_user(argp, rootrefs, sizeof(*rootrefs))) + ret = -EFAULT; + } + + btrfs_free_path(path); + kfree(rootrefs); + + return ret; +} + static noinline int btrfs_ioctl_snap_destroy(struct file *file, void __user *arg) { @@ -5503,6 +5603,8 @@ long btrfs_ioctl(struct file *file, unsigned int return btrfs_ioctl_set_features(file, argp); case BTRFS_IOC_GET_SUBVOL_INFO: return btrfs_ioctl_get_subvol_info(file, argp); + case BTRFS_IOC_GET_SUBVOL_ROOTREF: + return btrfs_i
[PATCH v4 1/3] btrfs: Add unprivileged ioctl which returns subvolume information
Add new unprivileged ioctl BTRFS_IOC_GET_SUBVOL_INFO which returns the information of subvolume containing this inode. (i.e. returns the information in ROOT_ITEM and ROOT_BACKREF.) Signed-off-by: Tomohiro Misono <misono.tomoh...@jp.fujitsu.com> --- fs/btrfs/ioctl.c | 129 + include/uapi/linux/btrfs.h | 51 ++ 2 files changed, 180 insertions(+) diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index 48e2ddff32bd..64b23e22852f 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -2242,6 +2242,133 @@ static noinline int btrfs_ioctl_ino_lookup(struct file *file, return ret; } +/* Get the subvolume information in BTRFS_ROOT_ITEM and BTRFS_ROOT_BACKREF */ +static noinline int btrfs_ioctl_get_subvol_info(struct file *file, + void __user *argp) +{ + struct btrfs_ioctl_get_subvol_info_args *subvol_info; + struct btrfs_root *root; + struct btrfs_path *path; + struct btrfs_key key; + + struct btrfs_root_item root_item; + struct btrfs_root_ref *rref; + struct extent_buffer *l; + int slot; + + unsigned long item_off; + unsigned long item_len; + + struct inode *inode; + int ret; + + path = btrfs_alloc_path(); + if (!path) + return -ENOMEM; + + subvol_info = kzalloc(sizeof(*subvol_info), GFP_KERNEL); + if (!subvol_info) { + btrfs_free_path(path); + return -ENOMEM; + } + inode = file_inode(file); + + root = BTRFS_I(inode)->root->fs_info->tree_root; + key.objectid = BTRFS_I(inode)->root->root_key.objectid; + key.type = BTRFS_ROOT_ITEM_KEY; + key.offset = 0; + ret = btrfs_search_slot(NULL, root, , path, 0, 0); + if (ret < 0) { + goto out; + } else if (ret > 0) { + u64 objectid = key.objectid; + + if (path->slots[0] >= btrfs_header_nritems(path->nodes[0])) { + ret = btrfs_next_leaf(root, path); + if (ret < 0) + return ret; + } + + /* If the subvolume is a snapshot, offset is not zero */ + btrfs_item_key_to_cpu(path->nodes[0], , path->slots[0]); + if (key.objectid != objectid || + key.type != BTRFS_ROOT_ITEM_KEY) { + ret = -ENOENT; + goto out; + } + } + + l = path->nodes[0]; + slot = path->slots[0]; + item_off = btrfs_item_ptr_offset(l, slot); + item_len = btrfs_item_size_nr(l, slot); + read_extent_buffer(l, _item, item_off, item_len); + + subvol_info->id = key.objectid; + + subvol_info->generation = btrfs_root_generation(_item); + subvol_info->flags = btrfs_root_flags(_item); + + memcpy(subvol_info->uuid, root_item.uuid, BTRFS_UUID_SIZE); + memcpy(subvol_info->parent_uuid, root_item.parent_uuid, + BTRFS_UUID_SIZE); + memcpy(subvol_info->received_uuid, root_item.received_uuid, + BTRFS_UUID_SIZE); + + subvol_info->ctransid = btrfs_root_ctransid(_item); + subvol_info->ctime.sec = btrfs_stack_timespec_sec(_item.ctime); + subvol_info->ctime.nsec = btrfs_stack_timespec_nsec(_item.ctime); + + subvol_info->otransid = btrfs_root_otransid(_item); + subvol_info->otime.sec = btrfs_stack_timespec_sec(_item.otime); + subvol_info->otime.nsec = btrfs_stack_timespec_nsec(_item.otime); + + subvol_info->stransid = btrfs_root_stransid(_item); + subvol_info->stime.sec = btrfs_stack_timespec_sec(_item.stime); + subvol_info->stime.nsec = btrfs_stack_timespec_nsec(_item.stime); + + subvol_info->rtransid = btrfs_root_rtransid(_item); + subvol_info->rtime.sec = btrfs_stack_timespec_sec(_item.rtime); + subvol_info->rtime.nsec = btrfs_stack_timespec_nsec(_item.rtime); + + btrfs_release_path(path); + key.type = BTRFS_ROOT_BACKREF_KEY; + key.offset = 0; + ret = btrfs_search_slot(NULL, root, , path, 0, 0); + if (ret < 0) { + goto out; + } else if (path->slots[0] >= btrfs_header_nritems(path->nodes[0])) { + ret = btrfs_next_leaf(root, path); + if (ret < 0) + return ret; + } + + l = path->nodes[0]; + slot = path->slots[0]; + btrfs_item_key_to_cpu(l, , slot); + if (key.objectid == subvol_info->id && + key.type == BTRFS_ROOT_BACKREF_KEY){ + subvol_info->parent_id = key.offset; + + rref = btrfs_item_ptr(l, slot, struct btr
[PATCH v4 3/3] btrfs: Add unprivileged version of ino_lookup ioctl
Add unprivileged version of ino_lookup ioctl BTRFS_IOC_INO_LOOKUP_USER to allow normal users to call "btrfs subvololume list/show" etc. in combination with BTRFS_IOC_GET_SUBVOL_INFO/BTRFS_IOC_GET_SUBVOL_ROOTREF. This can be used like BTRFS_IOC_INO_LOOKUP but the argument is different. This is because it always searches the fs/file tree correspoinding to the fd with which this ioctl is called and also returns the name of bottom subvolume. The main differences from original ino_lookup ioctl are: 1. Read + Exec permission will be checked using inode_permission() during path construction. -EACCES will be returned in case of failure. 2. Path construction will be stopped at the inode number which corresponds to the fd with which this ioctl is called. If constructed path does not exist under fd's inode, -EACCES will be returned. 3. The name of bottom subvolume is also searched and filled. Note that the maximum length of path is shorter 256 (BTRFS_VOL_NAME_MAX+1) bytes than ino_lookup ioctl because of space of subvolume's name. Signed-off-by: Tomohiro Misono <misono.tomoh...@jp.fujitsu.com> --- fs/btrfs/ioctl.c | 204 + include/uapi/linux/btrfs.h | 17 2 files changed, 221 insertions(+) diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index 7988d328aed5..e326a85134f4 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -2200,6 +2200,166 @@ static noinline int btrfs_search_path_in_tree(struct btrfs_fs_info *info, return ret; } +static noinline int btrfs_search_path_in_tree_user(struct inode *inode, + struct btrfs_ioctl_ino_lookup_user_args *args) +{ + struct btrfs_fs_info *fs_info = BTRFS_I(inode)->root->fs_info; + struct super_block *sb = inode->i_sb; + struct btrfs_key upper_limit = BTRFS_I(inode)->location; + u64 treeid = BTRFS_I(inode)->root->root_key.objectid; + u64 dirid = args->dirid; + + unsigned long item_off; + unsigned long item_len; + struct btrfs_inode_ref *iref; + struct btrfs_root_ref *rref; + struct btrfs_root *root; + struct btrfs_path *path; + struct btrfs_key key, key2; + struct extent_buffer *l; + struct inode *temp_inode; + char *ptr; + int slot; + int len; + int total_len = 0; + int ret = -1; + + path = btrfs_alloc_path(); + if (!path) + return -ENOMEM; + + /* +* If the bottom subvolume does not exist directly under upper_limit, +* construct the path in bottomup way. +*/ + if (dirid != upper_limit.objectid) { + ptr = >path[BTRFS_INO_LOOKUP_USER_PATH_MAX - 1]; + + key.objectid = treeid; + key.type = BTRFS_ROOT_ITEM_KEY; + key.offset = (u64)-1; + root = btrfs_read_fs_root_no_name(fs_info, ); + if (IS_ERR(root)) { + ret = -ENOENT; + goto out; + } + + key.objectid = dirid; + key.type = BTRFS_INODE_REF_KEY; + key.offset = (u64)-1; + while (1) { + ret = btrfs_search_slot(NULL, root, , path, 0, 0); + if (ret < 0) { + goto out; + } else if (ret > 0) { + ret = btrfs_previous_item(root, path, dirid, + BTRFS_INODE_REF_KEY); + if (ret < 0) { + goto out; + } else if (ret > 0) { + ret = -ENOENT; + goto out; + } + } + + l = path->nodes[0]; + slot = path->slots[0]; + btrfs_item_key_to_cpu(l, , slot); + + iref = btrfs_item_ptr(l, slot, struct btrfs_inode_ref); + len = btrfs_inode_ref_name_len(l, iref); + ptr -= len + 1; + total_len += len + 1; + if (ptr < args->path) { + ret = -ENAMETOOLONG; + goto out; + } + + *(ptr + len) = '/'; + read_extent_buffer(l, ptr, + (unsigned long)(iref + 1), len); + + /* Check the read+exec permission of this directory */ + ret = btrfs_previous_item(root, path, dirid, + BTRFS_INODE_ITEM_KEY); + if (ret < 0) { + goto out; +
[PATCH v4 0/3] btrfs: Add three new unprivileged ioctls to allow normal users to call "sub list/show" etc.
changelog: v3 -> v4 - call btrfs_next_leaf() after btrfs_search_slot() when the slot position exceeds the number of items - rebased to current misc-next v2 -> v3 - fix kbuild test bot warning v1 -> v2 - completely reimplement 1st/2nd ioctl to have user friendly api - various cleanup, remove unnecessary goto === This adds three new unprivileged ioctls: 1st patch: ioctl which returns subvolume information of ROOT_ITEM and ROOT_BACKREF 2nd patch: ioctl which returns subvolume information of ROOT_REF (without subvolume name) 3rd patch: user version of ino_lookup ioctl which also performs permission check. They will be used to implement user version of "subvolume list/show" etc. in user tools. See each commit log for more detals. The implementation of btrfs-progs can be found in the ML titled as follows: [PATCH 0/11] btrfs-progs: Rework of "subvolume list/show" and relax the root privileges of them Tomohiro Misono (3): btrfs: Add unprivileged ioctl which returns subvolume information btrfs: Add unprivileged ioctl which returns subvolume's ROOT_REF btrfs: Add unprivileged version of ino_lookup ioctl fs/btrfs/ioctl.c | 435 + include/uapi/linux/btrfs.h | 84 + 2 files changed, 519 insertions(+) -- 2.14.3 -- To unsubscribe from this list: send the line "unsubscribe linux-btrfs" in the body of a message to majord...@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html