From patchwork Thu Mar 15 08:15:23 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Misono Tomohiro X-Patchwork-Id: 10284031 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id 3807360386 for ; Thu, 15 Mar 2018 08:15:42 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 257B2288F1 for ; Thu, 15 Mar 2018 08:15:42 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 19F55288F4; Thu, 15 Mar 2018 08:15:42 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-6.9 required=2.0 tests=BAYES_00,RCVD_IN_DNSWL_HI autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 2DB9A288F3 for ; Thu, 15 Mar 2018 08:15:41 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751095AbeCOIPj (ORCPT ); Thu, 15 Mar 2018 04:15:39 -0400 Received: from mgwym02.jp.fujitsu.com ([211.128.242.41]:30568 "EHLO mgwym02.jp.fujitsu.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1750722AbeCOIPf (ORCPT ); Thu, 15 Mar 2018 04:15:35 -0400 Received: from yt-mxoi1.gw.nic.fujitsu.com (unknown [192.168.229.67]) by mgwym02.jp.fujitsu.com with smtp id 3f9b_548f_5f9b1377_6c09_4215_bb76_6fd84cae1c35; Thu, 15 Mar 2018 17:15:27 +0900 Received: from g01jpfmpwyt02.exch.g01.fujitsu.local (g01jpfmpwyt02.exch.g01.fujitsu.local [10.128.193.56]) by yt-mxoi1.gw.nic.fujitsu.com (Postfix) with ESMTP id 26E8EAC0173 for ; Thu, 15 Mar 2018 17:15:27 +0900 (JST) Received: from g01jpexchyt35.g01.fujitsu.local (unknown [10.128.193.4]) by g01jpfmpwyt02.exch.g01.fujitsu.local (Postfix) with ESMTP id 2E01D584371 for ; Thu, 15 Mar 2018 17:15:26 +0900 (JST) X-SecurityPolicyCheck: OK by SHieldMailChecker v2.5.2 X-SHieldMailCheckerPolicyVersion: FJ-ISEC-20170217-enc X-SHieldMailCheckerMailID: 43d153938bc74b93b76594fe3d7ebad5 Subject: [RFC PATCH v2 6/8] btrfs-progs: sub list: Allow normal user to call "subvolume list/show" From: "Misono, Tomohiro" To: linux-btrfs References: <226b6805-c5aa-c40d-4ea6-f81dc1b7de20@jp.fujitsu.com> Message-ID: Date: Thu, 15 Mar 2018 17:15:23 +0900 User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:52.0) Gecko/20100101 Thunderbird/52.6.0 MIME-Version: 1.0 In-Reply-To: <226b6805-c5aa-c40d-4ea6-f81dc1b7de20@jp.fujitsu.com> Content-Language: en-US X-SecurityPolicyCheck-GC: OK by FENCE-Mail X-TM-AS-MML: disable Sender: linux-btrfs-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-btrfs@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP Allow normal user to call "subvolume list/show" by using 3 new unprivileged ioctls (BTRFS_IOC_GET_SUBVOL_INFO, BTRFS_IOC_GET_SUBVOL_ROOTREF and BTRFS_IOC_INO_LOOKUP_USER). Note that for root, "subvolume list" returns all the subvolume in the filesystem by default, but for normal user, it returns subvolumes which exist under the specified path (including the path itself). The specified path itself is not needed to be a subvolume. If the subvolume cannot be opened but the parent directory can be, the information other than name or id would be zeroed out. Also, for normal user, snapshot filed of "subvolume show" just lists the snapshots under the specified subvolume. Signed-off-by: Tomohiro Misono --- btrfs-list.c | 326 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- cmds-subvolume.c | 13 +++ 2 files changed, 332 insertions(+), 7 deletions(-) diff --git a/btrfs-list.c b/btrfs-list.c index 2e82dc5c..ef97d2db 100644 --- a/btrfs-list.c +++ b/btrfs-list.c @@ -33,6 +33,7 @@ #include #include "btrfs-list.h" #include "rbtree-utils.h" +#include #define BTRFS_LIST_NFILTERS_INCREASE (2 * BTRFS_LIST_FILTER_MAX) #define BTRFS_LIST_NCOMPS_INCREASE (2 * BTRFS_LIST_COMP_MAX) @@ -549,6 +550,9 @@ static int resolve_root(struct root_lookup *rl, struct root_info *ri, int len = 0; struct root_info *found; + if (ri->full_path != NULL) + return 0; + /* * we go backwards from the root_info object and add pathnames * from parent directories as we go. @@ -672,6 +676,50 @@ static int lookup_ino_path(int fd, struct root_info *ri) return 0; } +/* user version of lookup_ino_path which also cheks the access right */ +static int lookup_ino_path_user(int fd, struct root_info *ri) +{ + struct btrfs_ioctl_ino_lookup_user_args args; + int ret = 0; + + if (ri->path) + return 0; + if (!ri->ref_tree) + return -ENOENT; + + memset(&args, 0, sizeof(args)); + args.dirid = ri->dir_id; + args.subvolid = ri->root_id; + + ret = ioctl(fd, BTRFS_IOC_INO_LOOKUP_USER, &args); + if (ret < 0) { + if (errno == ENOENT) { + ri->ref_tree = 0; + return -ENOENT; + } + if (errno != EACCES) { + error("failed to lookup path for root %llu: %m", + (unsigned long long)ri->ref_tree); + return ret; + } else { + return -EACCES; + } + } + + ri->path = malloc(strlen(args.path) + strlen(args.name) + 1); + if (!ri->path) + return -ENOMEM; + strcpy(ri->path, args.path); + + ri->name = malloc(strlen(args.name) + 1); + if (!ri->name) + return -ENOMEM; + strcpy(ri->name, args.name); + + strcat(ri->path, ri->name); + return ret; +} + /* finding the generation for a given path is a two step process. * First we use the inode lookup routine to find out the root id * @@ -1303,6 +1351,20 @@ static void filter_and_sort_subvol(struct root_lookup *all_subvols, while (n) { entry = rb_entry(n, struct root_info, rb_node); + /* + * If list_subvol_user() is used, there may be + * entries which have been skipped for search. + * Just remove these entries here. + */ + if (!entry->path) { + struct rb_node *prev = rb_prev(n); + + rb_erase(n, &all_subvols->root); + free_root_info(n); + n = prev; + continue; + } + ret = resolve_root(all_subvols, entry, top_id); if (ret == -ENOENT) { if (entry->root_id != BTRFS_FS_TREE_OBJECTID) { @@ -1343,6 +1405,243 @@ static int list_subvol_fill_paths(int fd, struct root_lookup *root_lookup) return 0; } +static int fill_subvol_info(struct root_lookup *root_lookup, int fd) +{ + struct btrfs_ioctl_get_subvol_info_args subvol_info; + u64 root_offset; + int ret; + + ret = ioctl(fd, BTRFS_IOC_GET_SUBVOL_INFO, &subvol_info); + if (ret < 0) + return -errno; + + if (!uuid_is_null(subvol_info.parent_uuid)) + root_offset = subvol_info.otransid; + else + root_offset = 0; + + add_root(root_lookup, subvol_info.id, 0, + root_offset, subvol_info.flags, subvol_info.dirid, + NULL, 0, + subvol_info.otransid, subvol_info.generation, + subvol_info.otime.sec, subvol_info.uuid, + subvol_info.parent_uuid, subvol_info.received_uuid); + + return 0; +} + +static int fill_subvol_info_top(struct root_lookup *root_lookup, int fd) +{ + struct btrfs_ioctl_get_subvol_info_args subvol_info; + struct root_info *ri; + u64 root_offset; + int ret; + + ret = ioctl(fd, BTRFS_IOC_GET_SUBVOL_INFO, &subvol_info); + if (ret < 0) + return -errno; + + if (!uuid_is_null(subvol_info.parent_uuid)) + root_offset = subvol_info.otransid; + else + root_offset = 0; + + add_root(root_lookup, subvol_info.id, subvol_info.parent_id, + root_offset, subvol_info.flags, subvol_info.dirid, + subvol_info.name, strlen(subvol_info.name), + subvol_info.otransid, subvol_info.generation, + subvol_info.otime.sec, subvol_info.uuid, + subvol_info.parent_uuid, subvol_info.received_uuid); + + /* + * fill the path since we won't lookup directory/subvolume + * above this subvolume and cannot use root_lookup() + */ + ri = root_tree_search(root_lookup, subvol_info.id); + ri->top_id = subvol_info.parent_id; + if (subvol_info.id == BTRFS_FS_TREE_OBJECTID) { + ri->path = strdup("/"); + ri->name = strdup(""); + ri->full_path = strdup("/"); + if (!ri->path || !ri->name || !ri->full_path) + return -ENOMEM; + } else { + ri->path = malloc(strlen(ri->name + 1)); + ri->full_path = malloc(strlen(ri->name + 1)); + if (!ri->path || !ri->full_path) + return -ENOMEM; + + strcpy(ri->path, ri->name); + strcpy(ri->full_path, ri->name); + } + + return 0; +} + +static int fill_rootref(struct root_lookup *root_lookup, int fd, int parent_id) +{ + struct btrfs_ioctl_get_subvol_rootref_args rootrefs; + bool continue_search = true; + int i, ret; + + memset(&rootrefs, 0, sizeof(rootrefs)); + while (continue_search) { + ret = ioctl(fd, BTRFS_IOC_GET_SUBVOL_ROOTREF, &rootrefs); + if (ret < 0) { + if (errno != EOVERFLOW) + return -errno; + continue_search = true; + } else { + continue_search = false; + } + + for (i = 0; i < rootrefs.num_items; i++) { + add_root(root_lookup, rootrefs.rootref[i].subvolid, + parent_id, 0, 0, rootrefs.rootref[i].dirid, + NULL, 0, 0, 0, 0, NULL, NULL, NULL); + } + } + + return 0; +} + +static int list_subvol_user(int top_fd, struct root_lookup *root_lookup, + const char *path) +{ + struct root_info *ri, *parent; + struct rb_node *n; + char *fullpath; + u64 top_id; + /* fifo queue entry which holds subvolume's id */ + struct queue_entry { + u64 id; + + STAILQ_ENTRY(queue_entry) entries; + } *e, *etemp; + int fd; + int ret = 0; + + root_lookup->root.rb_node = NULL; + + ret = btrfs_list_get_path_rootid(top_fd, &top_id); + if (ret) + return ret; + + /* Add top_id to the queue */ + STAILQ_HEAD(slistead, queue_entry) head = STAILQ_HEAD_INITIALIZER(head); + STAILQ_INIT(&head); + e = malloc(sizeof(struct queue_entry)); + if (!e) + return -ENOMEM; + e->id = top_id; + STAILQ_INSERT_TAIL(&head, e, entries); + + /* + * Iterate until queue is empty: + * 1. Pop the first entry + * 2. Open the entry's path + * 3 Get the subvolume information by fill_subvol_info/fill_rootref + * 4. Iterate over rb_tree: + * 4-1. Searth the rb_tree whose ref_tree is e->id + * (this means the subvolume exists under e->id's subvolume) + * 4-2. Call ino_lookup_user ioctl + * 4-3. If the call succeeds, add the subvolume id to the queue + */ + while (!STAILQ_EMPTY(&head)) { + e = STAILQ_FIRST(&head); + STAILQ_REMOVE_HEAD(&head, entries); + + if (e->id == top_id) { + fd = top_fd; + fill_subvol_info_top(root_lookup, fd); + fill_rootref(root_lookup, fd, e->id); + } else { + parent = root_tree_search(root_lookup, e->id); + resolve_root(root_lookup, parent, top_id); + fd = openat(top_fd, parent->full_path, O_RDONLY); + if (fd == -1) { + if (errno == EACCES) { + /* skip this subvolume */ + continue; + } else { + error("error at open %s: %m", + parent->full_path); + goto err; + } + } + + fill_subvol_info(root_lookup, fd); + fill_rootref(root_lookup, fd, e->id); + } + + n = rb_first(&root_lookup->root); + while (n) { + ri = rb_entry(n, struct root_info, rb_node); + if (ri->ref_tree == 0) { + /* BTRFS_FS_TREE_OBJECTID or deleted */ + n = rb_next(n); + continue; + } + + if (ri->ref_tree == e->id) { + ret = lookup_ino_path_user(fd, ri); + if (ret < 0 && ret != -ENOENT && ret != -EACCES) + goto err; + + /* add ths subvol id to queue */ + if (!ret) { + etemp = malloc(sizeof(struct queue_entry)); + + if (!etemp) { + ret = -ENOMEM; + goto err; + } + etemp->id = ri->root_id; + STAILQ_INSERT_TAIL(&head, etemp, + entries); + } + } + n = rb_next(n); + } + + if (fd != top_fd) + close(fd); + free(e); + } + + /* If the specified path itself is not a subvolume, remove the entry */ + fullpath = realpath(path, NULL); + if (!fullpath) { + ret = -ENOMEM; + goto err; + } + ret = test_issubvolume(fullpath); + free(fullpath); + if (ret < 0) + goto err; + + if (!ret) { + ri = root_tree_search(root_lookup, top_id); + rb_erase(&ri->rb_node, &root_lookup->root); + free_root_info(&ri->rb_node); + } + + return 0; + +err: + if (fd != -1 && fd != top_fd) + close(fd); + + /* free remaining queue entries */ + while (!STAILQ_EMPTY(&head)) { + e = STAILQ_FIRST(&head); + STAILQ_REMOVE_HEAD(&head, entries); + free(e); + } + + return ret; +} + static void print_subvolume_column(struct root_info *subv, enum btrfs_list_column_enum column) { @@ -1527,17 +1826,28 @@ static int btrfs_list_subvols(int fd, struct root_lookup *root_lookup, { int ret; - ret = list_subvol_search(fd, root_lookup); - if (ret) { - error("can't perform the search: %m"); - return ret; + ret = check_perm_for_tree_search(fd); + if (ret < 0) { + error("can't check the permission for tree search: %s", + strerror(-ret)); + return -1; } /* * now we have an rbtree full of root_info objects, but we need to fill * in their path names within the subvol that is referencing each one. */ - ret = list_subvol_fill_paths(fd, root_lookup); + if (ret) { + ret = list_subvol_search(fd, root_lookup); + if (ret) { + error("can't perform the search: %s", strerror(-ret)); + return ret; + } + ret = list_subvol_fill_paths(fd, root_lookup); + } else { + ret = list_subvol_user(fd, root_lookup, path); + } + return ret; } @@ -1631,12 +1941,14 @@ int btrfs_get_subvol(int fd, struct root_info *the_ri, const char *path) return ret; } + ret = -ENOENT; rbn = rb_first(&rl.root); while(rbn) { ri = rb_entry(rbn, struct root_info, rb_node); rr = resolve_root(&rl, ri, root_id); - if (rr == -ENOENT) { - ret = -ENOENT; + if (rr == -ENOENT || + ri->root_id == BTRFS_FS_TREE_OBJECTID || + uuid_is_null(ri->uuid)) { rbn = rb_next(rbn); continue; } diff --git a/cmds-subvolume.c b/cmds-subvolume.c index faa10c5a..7a7c6f3b 100644 --- a/cmds-subvolume.c +++ b/cmds-subvolume.c @@ -596,6 +596,19 @@ static int cmd_subvol_list(int argc, char **argv) goto out; } + ret = check_perm_for_tree_search(fd); + if (ret < 0) { + ret = -1; + error("can't check the permission for tree search: %s", + strerror(-ret)); + goto out; + } + if (!ret && is_list_all) { + ret = -1; + error("only root can use -a option"); + goto out; + } + if (flags) btrfs_list_setup_filter(&filter_set, BTRFS_LIST_FILTER_FLAGS, flags);