linux-btrfs.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [RFC PATCH 0/7] btrfs-progs: Allow normal user to call "subvolume list/show"
@ 2018-03-06  8:32 Misono, Tomohiro
  2018-03-06  8:33 ` [RFC PATCH 1/7] btrfs-progs: Add 2 definitions of new unprivileged ioctl Misono, Tomohiro
                   ` (6 more replies)
  0 siblings, 7 replies; 8+ messages in thread
From: Misono, Tomohiro @ 2018-03-06  8:32 UTC (permalink / raw)
  To: linux-btrfs

This RFC implements user version of "subvolume list/show" using two new ioctls.
The ioctl patch to the kernel can be found in the ML titled 
  "[PATCH 0/2] btrfs: Add two new unprivileged ioctls to allow normal users to call "sub list/show" etc."

1st-4th are some prepartion works.
5th patch is the main part.
6th-7th adds the test for "subvolume list"

The main behavior differences between root and normal users are:
 - "subvolume list" only lists subvolumes which exist under the specified path.
   (The specified path itself is not needed to be a subvolume.)
 - snapshot filed of "subvolume show" just lists the snapshots under the specified path.

This is a part of RFC I sent last December[1] whose aim is to improve normal users' usability.
The remaining works of RFC are: 
  - Allow "sub delete" for empty subvolume
  - Allow "qgroup show" to check quota limit

[1] https://www.mail-archive.com/linux-btrfs@vger.kernel.org/msg70991.html

Tomohiro Misono (7):
  btrfs-progs: Add 2 definitions of new unprivileged ioctl
  btrfs-progs: sub list: Add helper function which checks the permission
    for tree search ioctl
  btrfs-progs: sub list: Pass specified path down to
    btrfs_list_subvols()
  btrfs-progs: fallback to open without O_NOATIME flag in
    find_mount_root()
  btrfs-progs: sub list: Allow normal user to call "subvolume list/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

 btrfs-list.c                               | 289 +++++++++++++++++++++++++++--
 btrfs-list.h                               |   8 +-
 cmds-subvolume.c                           |  12 +-
 ioctl.h                                    |  19 ++
 tests/cli-tests/009-subvolume-list/test.sh | 136 ++++++++++++++
 tests/common                               |  10 +
 utils.c                                    |  13 +-
 7 files changed, 464 insertions(+), 23 deletions(-)
 create mode 100755 tests/cli-tests/009-subvolume-list/test.sh

-- 
2.14.3


^ permalink raw reply	[flat|nested] 8+ messages in thread

* [RFC PATCH 1/7] btrfs-progs: Add 2 definitions of new unprivileged ioctl
  2018-03-06  8:32 [RFC PATCH 0/7] btrfs-progs: Allow normal user to call "subvolume list/show" Misono, Tomohiro
@ 2018-03-06  8:33 ` Misono, Tomohiro
  2018-03-06  8:34 ` [RFC PATCH 2/7] btrfs-progs: sub list: Add helper function which checks the permission for tree search ioctl Misono, Tomohiro
                   ` (5 subsequent siblings)
  6 siblings, 0 replies; 8+ messages in thread
From: Misono, Tomohiro @ 2018-03-06  8:33 UTC (permalink / raw)
  To: linux-btrfs

Add 2 definitions of new unprivileged ioctl (BTRFS_IOC_GET_SUBVOL_INFO
and BTRFS_IOC_INO_LOOKUP_USER). They will be used to implement
user version of "btrfs subvolume list" etc.

Signed-off-by: Tomohiro Misono <misono.tomohiro@jp.fujitsu.com>
---
 ioctl.h | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

diff --git a/ioctl.h b/ioctl.h
index 709e996f..c5181dd9 100644
--- a/ioctl.h
+++ b/ioctl.h
@@ -320,6 +320,21 @@ struct btrfs_ioctl_ino_lookup_args {
 };
 BUILD_ASSERT(sizeof(struct btrfs_ioctl_ino_lookup_args) == 4096);
 
+#define BTRFS_INO_LOOKUP_USER_PATH_MAX (4072-BTRFS_VOL_NAME_MAX)
+struct btrfs_ioctl_ino_lookup_user_args {
+	/* in */
+	__u64 treeid;
+	/* in, 'dirid' is the same as objectid in btrfs_ioctl_ino_lookup_args */
+	__u64 dirid;
+	/* in, subvolume id */
+	__u64 subid;
+	/* out, name of the subvolume whose objectid is 'subid' */
+	char name[BTRFS_VOL_NAME_MAX];
+	/* out, 'path' is the same as 'name' in btrfs_ioctl_ino_lookup_args */
+	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;
@@ -828,6 +843,10 @@ 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 _IOWR(BTRFS_IOCTL_MAGIC, 60, \
+					struct btrfs_ioctl_search_args)
+#define BTRFS_IOC_INO_LOOKUP_USER _IOWR(BTRFS_IOCTL_MAGIC, 61, \
+					struct btrfs_ioctl_ino_lookup_args)
 #ifdef __cplusplus
 }
 #endif
-- 
2.14.3


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [RFC PATCH 2/7] btrfs-progs: sub list: Add helper function which checks the permission for tree search ioctl
  2018-03-06  8:32 [RFC PATCH 0/7] btrfs-progs: Allow normal user to call "subvolume list/show" Misono, Tomohiro
  2018-03-06  8:33 ` [RFC PATCH 1/7] btrfs-progs: Add 2 definitions of new unprivileged ioctl Misono, Tomohiro
@ 2018-03-06  8:34 ` Misono, Tomohiro
  2018-03-06  8:34 ` [PATCH 3/7] btrfs-progs: sub list: Pass specified path down to btrfs_list_subvols() Misono, Tomohiro
                   ` (4 subsequent siblings)
  6 siblings, 0 replies; 8+ messages in thread
From: Misono, Tomohiro @ 2018-03-06  8:34 UTC (permalink / raw)
  To: linux-btrfs

This is a preparetion work to allow normal user to call "subvolume list".

Signed-off-by: Tomohiro Misono <misono.tomohiro@jp.fujitsu.com>
---
 btrfs-list.c | 30 ++++++++++++++++++++++++++++++
 btrfs-list.h |  1 +
 2 files changed, 31 insertions(+)

diff --git a/btrfs-list.c b/btrfs-list.c
index e01c5899..aea917c5 100644
--- a/btrfs-list.c
+++ b/btrfs-list.c
@@ -958,6 +958,36 @@ out:
 	return 0;
 }
 
+/*
+ * Check the permission for tree search ioctl by searching a key
+ * which alwasys exists
+ */
+int check_perm_for_tree_search(int fd)
+{
+	struct btrfs_ioctl_search_args args;
+	struct btrfs_ioctl_search_key *sk = &args.key;
+	int ret;
+
+	memset(&args, 0, sizeof(args));
+	sk->tree_id = BTRFS_ROOT_TREE_OBJECTID;
+	sk->min_objectid = BTRFS_EXTENT_TREE_OBJECTID;
+	sk->max_objectid = BTRFS_EXTENT_TREE_OBJECTID;
+	sk->min_type = BTRFS_ROOT_ITEM_KEY;
+	sk->max_type = BTRFS_ROOT_ITEM_KEY;
+	sk->min_offset = 0;
+	sk->max_offset = (u64)-1;
+	sk->min_transid = 0;
+	sk->max_transid = (u64)-1;
+	sk->nr_items = 1;
+	ret = ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args);
+
+	if (ret == -EPERM)
+		return 0;
+	if (ret)
+		return ret;
+	return 1;
+}
+
 static int list_subvol_search(int fd, struct root_lookup *root_lookup)
 {
 	int ret;
diff --git a/btrfs-list.h b/btrfs-list.h
index 6e5fc778..6225311d 100644
--- a/btrfs-list.h
+++ b/btrfs-list.h
@@ -176,5 +176,6 @@ char *btrfs_list_path_for_root(int fd, u64 root);
 int btrfs_list_get_path_rootid(int fd, u64 *treeid);
 int btrfs_get_subvol(int fd, struct root_info *the_ri);
 int btrfs_get_toplevel_subvol(int fd, struct root_info *the_ri);
+int check_perm_for_tree_search(int fd);
 
 #endif
-- 
2.14.3


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH 3/7] btrfs-progs: sub list: Pass specified path down to btrfs_list_subvols()
  2018-03-06  8:32 [RFC PATCH 0/7] btrfs-progs: Allow normal user to call "subvolume list/show" Misono, Tomohiro
  2018-03-06  8:33 ` [RFC PATCH 1/7] btrfs-progs: Add 2 definitions of new unprivileged ioctl Misono, Tomohiro
  2018-03-06  8:34 ` [RFC PATCH 2/7] btrfs-progs: sub list: Add helper function which checks the permission for tree search ioctl Misono, Tomohiro
@ 2018-03-06  8:34 ` Misono, Tomohiro
  2018-03-06  8:35 ` [RFC PATCH 4/7] btrfs-progs: fallback to open without O_NOATIME flag in, find_mount_root() Misono, Tomohiro
                   ` (3 subsequent siblings)
  6 siblings, 0 replies; 8+ messages in thread
From: Misono, Tomohiro @ 2018-03-06  8:34 UTC (permalink / raw)
  To: linux-btrfs

This is a preparetion work to allow normal user to call "subvolume list".

Signed-off-by: Tomohiro Misono <misono.tomohiro@jp.fujitsu.com>
---
 btrfs-list.c     | 16 +++++++++-------
 btrfs-list.h     |  7 ++++---
 cmds-subvolume.c |  6 +++---
 utils.c          | 10 +++++-----
 4 files changed, 21 insertions(+), 18 deletions(-)

diff --git a/btrfs-list.c b/btrfs-list.c
index aea917c5..1b49f4a1 100644
--- a/btrfs-list.c
+++ b/btrfs-list.c
@@ -1519,7 +1519,8 @@ next:
 	}
 }
 
-static int btrfs_list_subvols(int fd, struct root_lookup *root_lookup)
+static int btrfs_list_subvols(int fd, struct root_lookup *root_lookup,
+			const char *path)
 {
 	int ret;
 
@@ -1540,7 +1541,7 @@ static int btrfs_list_subvols(int fd, struct root_lookup *root_lookup)
 int btrfs_list_subvols_print(int fd, struct btrfs_list_filter_set *filter_set,
 		       struct btrfs_list_comparer_set *comp_set,
 		       enum btrfs_list_layout layout, int full_path,
-		       const char *raw_prefix)
+		       const char *raw_prefix, const char *path)
 {
 	struct root_lookup root_lookup;
 	struct root_lookup root_sort;
@@ -1552,7 +1553,7 @@ int btrfs_list_subvols_print(int fd, struct btrfs_list_filter_set *filter_set,
 	if (ret)
 		return ret;
 
-	ret = btrfs_list_subvols(fd, &root_lookup);
+	ret = btrfs_list_subvols(fd, &root_lookup, path);
 	if (ret)
 		return ret;
 	filter_and_sort_subvol(&root_lookup, &root_sort, filter_set,
@@ -1571,7 +1572,8 @@ static char *strdup_or_null(const char *s)
 	return strdup(s);
 }
 
-int btrfs_get_toplevel_subvol(int fd, struct root_info *the_ri)
+int btrfs_get_toplevel_subvol(int fd, struct root_info *the_ri,
+			const char *path)
 {
 	int ret;
 	struct root_lookup rl;
@@ -1583,7 +1585,7 @@ int btrfs_get_toplevel_subvol(int fd, struct root_info *the_ri)
 	if (ret)
 		return ret;
 
-	ret = btrfs_list_subvols(fd, &rl);
+	ret = btrfs_list_subvols(fd, &rl, path);
 	if (ret)
 		return ret;
 
@@ -1602,7 +1604,7 @@ int btrfs_get_toplevel_subvol(int fd, struct root_info *the_ri)
 	return ret;
 }
 
-int btrfs_get_subvol(int fd, struct root_info *the_ri)
+int btrfs_get_subvol(int fd, struct root_info *the_ri, const char *path)
 {
 	int ret, rr;
 	struct root_lookup rl;
@@ -1614,7 +1616,7 @@ int btrfs_get_subvol(int fd, struct root_info *the_ri)
 	if (ret)
 		return ret;
 
-	ret = btrfs_list_subvols(fd, &rl);
+	ret = btrfs_list_subvols(fd, &rl, path);
 	if (ret)
 		return ret;
 
diff --git a/btrfs-list.h b/btrfs-list.h
index 6225311d..eb21bebe 100644
--- a/btrfs-list.h
+++ b/btrfs-list.h
@@ -169,13 +169,14 @@ struct btrfs_list_comparer_set *btrfs_list_alloc_comparer_set(void);
 int btrfs_list_subvols_print(int fd, struct btrfs_list_filter_set *filter_set,
 		       struct btrfs_list_comparer_set *comp_set,
 		       enum btrfs_list_layout layot, int full_path,
-		       const char *raw_prefix);
+		       const char *raw_prefix, const char *path);
 int btrfs_list_find_updated_files(int fd, u64 root_id, u64 oldest_gen);
 int btrfs_list_get_default_subvolume(int fd, u64 *default_id);
 char *btrfs_list_path_for_root(int fd, u64 root);
 int btrfs_list_get_path_rootid(int fd, u64 *treeid);
-int btrfs_get_subvol(int fd, struct root_info *the_ri);
-int btrfs_get_toplevel_subvol(int fd, struct root_info *the_ri);
+int btrfs_get_subvol(int fd, struct root_info *the_ri, const char *path);
+int btrfs_get_toplevel_subvol(int fd, struct root_info *the_ri,
+			const char *path);
 int check_perm_for_tree_search(int fd);
 
 #endif
diff --git a/cmds-subvolume.c b/cmds-subvolume.c
index 8a473f7a..faa10c5a 100644
--- a/cmds-subvolume.c
+++ b/cmds-subvolume.c
@@ -620,7 +620,7 @@ static int cmd_subvol_list(int argc, char **argv)
 	btrfs_list_setup_print_column(BTRFS_LIST_PATH);
 
 	ret = btrfs_list_subvols_print(fd, filter_set, comparer_set,
-			layout, !is_list_all && !is_only_in_path, NULL);
+			layout, !is_list_all && !is_only_in_path, NULL, subvol);
 
 out:
 	close_file_or_dir(fd, dirstream);
@@ -844,7 +844,7 @@ static int cmd_subvol_get_default(int argc, char **argv)
 	btrfs_list_setup_print_column(BTRFS_LIST_PATH);
 
 	ret = btrfs_list_subvols_print(fd, filter_set, NULL,
-		BTRFS_LIST_LAYOUT_DEFAULT, 1, NULL);
+		BTRFS_LIST_LAYOUT_DEFAULT, 1, NULL, subvol);
 
 	if (filter_set)
 		free(filter_set);
@@ -1110,7 +1110,7 @@ static int cmd_subvol_show(int argc, char **argv)
 		goto out;
 	}
 	btrfs_list_subvols_print(fd, filter_set, NULL, BTRFS_LIST_LAYOUT_RAW,
-			1, raw_prefix);
+			1, raw_prefix, fullpath);
 
 out:
 	/* clean up */
diff --git a/utils.c b/utils.c
index e9cb3a82..81c54faa 100644
--- a/utils.c
+++ b/utils.c
@@ -2542,9 +2542,9 @@ int get_subvol_info(const char *fullpath, struct root_info *get_ri)
 	get_ri->root_id = sv_id;
 
 	if (sv_id == BTRFS_FS_TREE_OBJECTID)
-		ret = btrfs_get_toplevel_subvol(mntfd, get_ri);
+		ret = btrfs_get_toplevel_subvol(mntfd, get_ri, mnt);
 	else
-		ret = btrfs_get_subvol(mntfd, get_ri);
+		ret = btrfs_get_subvol(mntfd, get_ri, mnt);
 	if (ret)
 		error("can't find '%s': %d", svpath, ret);
 
@@ -2570,9 +2570,9 @@ int get_subvol_info_by_rootid(const char *mnt, struct root_info *get_ri, u64 r_i
 	get_ri->root_id = r_id;
 
 	if (r_id == BTRFS_FS_TREE_OBJECTID)
-		ret = btrfs_get_toplevel_subvol(fd, get_ri);
+		ret = btrfs_get_toplevel_subvol(fd, get_ri, mnt);
 	else
-		ret = btrfs_get_subvol(fd, get_ri);
+		ret = btrfs_get_subvol(fd, get_ri, mnt);
 
 	if (ret)
 		error("can't find rootid '%llu' on '%s': %d", r_id, mnt, ret);
@@ -2595,7 +2595,7 @@ int get_subvol_info_by_uuid(const char *mnt, struct root_info *get_ri, u8 *uuid_
 	memset(get_ri, 0, sizeof(*get_ri));
 	uuid_copy(get_ri->uuid, uuid_arg);
 
-	ret = btrfs_get_subvol(fd, get_ri);
+	ret = btrfs_get_subvol(fd, get_ri, mnt);
 	if (ret) {
 		char uuid_parsed[BTRFS_UUID_UNPARSED_SIZE];
 		uuid_unparse(uuid_arg, uuid_parsed);
-- 
2.14.3


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [RFC PATCH 4/7] btrfs-progs: fallback to open without O_NOATIME flag in, find_mount_root()
  2018-03-06  8:32 [RFC PATCH 0/7] btrfs-progs: Allow normal user to call "subvolume list/show" Misono, Tomohiro
                   ` (2 preceding siblings ...)
  2018-03-06  8:34 ` [PATCH 3/7] btrfs-progs: sub list: Pass specified path down to btrfs_list_subvols() Misono, Tomohiro
@ 2018-03-06  8:35 ` Misono, Tomohiro
  2018-03-06  8:36 ` [RFC PATCH 5/7] btrfs-progs: sub list: Allow normal user to call "subvolume list/show" Misono, Tomohiro
                   ` (2 subsequent siblings)
  6 siblings, 0 replies; 8+ messages in thread
From: Misono, Tomohiro @ 2018-03-06  8:35 UTC (permalink / raw)
  To: linux-btrfs

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 normal user can also call find_mount_root().

This is a preparation work to allow normal user to call "subvolume show".

Signed-off-by: Tomohiro Misono <misono.tomohiro@jp.fujitsu.com>
---
 utils.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/utils.c b/utils.c
index 81c54faa..acea70a5 100644
--- a/utils.c
+++ b/utils.c
@@ -2045,6 +2045,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


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [RFC PATCH 5/7] btrfs-progs: sub list: Allow normal user to call "subvolume list/show"
  2018-03-06  8:32 [RFC PATCH 0/7] btrfs-progs: Allow normal user to call "subvolume list/show" Misono, Tomohiro
                   ` (3 preceding siblings ...)
  2018-03-06  8:35 ` [RFC PATCH 4/7] btrfs-progs: fallback to open without O_NOATIME flag in, find_mount_root() Misono, Tomohiro
@ 2018-03-06  8:36 ` Misono, Tomohiro
  2018-03-06  8:36 ` [RFC PATCH 6/7] btrfs-progs: test: Add helper function to check if test user exists Misono, Tomohiro
  2018-03-06  8:37 ` [RFC PATCH 7/7] btrfs-porgs: test: Add cli-test/009 to check subvolume list for both root and normal user Misono, Tomohiro
  6 siblings, 0 replies; 8+ messages in thread
From: Misono, Tomohiro @ 2018-03-06  8:36 UTC (permalink / raw)
  To: linux-btrfs

Allow normal user to call "subvolume list/show" by using 2 new
unprivileged ioctls (BTRFS_IOC_GET_SUBVOL_INFO 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. The specified path itself is not
needed to be a subvolume.

Also, for normal user, snapshot filed of "subvolume show" just lists
the snapshots under the specified subvolume.

Signed-off-by: Tomohiro Misono <misono.tomohiro@jp.fujitsu.com>
---
 btrfs-list.c     | 243 +++++++++++++++++++++++++++++++++++++++++++++++++++++--
 cmds-subvolume.c |   6 ++
 2 files changed, 244 insertions(+), 5 deletions(-)

diff --git a/btrfs-list.c b/btrfs-list.c
index 1b49f4a1..3974a50f 100644
--- a/btrfs-list.c
+++ b/btrfs-list.c
@@ -33,6 +33,7 @@
 #include <uuid/uuid.h>
 #include "btrfs-list.h"
 #include "rbtree-utils.h"
+#include <sys/queue.h>
 
 #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,47 @@ 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.treeid = ri->ref_tree;
+	args.dirid = ri->dir_id;
+	args.subid = 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: %s",
+			(unsigned long long)ri->ref_tree, strerror(errno));
+			return ret;
+		} else {
+			return -EACCES;
+		}
+	}
+
+	ri->path = malloc(strlen(args.path) + 1);
+	strcpy(ri->path, args.path);
+
+	ri->name = malloc(strlen(args.name) + 1);
+	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
  *
@@ -988,7 +1033,12 @@ int check_perm_for_tree_search(int fd)
 	return 1;
 }
 
-static int list_subvol_search(int fd, struct root_lookup *root_lookup)
+/*
+ * If is_root is true, BTRFS_IOC_TREE_SEARCH is used.
+ * Otherwise BTRFS_IOC_GET_SUBVOL_INFO is used.
+ */
+static int list_subvol_search(int fd, struct root_lookup *root_lookup,
+				bool is_root)
 {
 	int ret;
 	struct btrfs_ioctl_search_args args;
@@ -1004,6 +1054,12 @@ static int list_subvol_search(int fd, struct root_lookup *root_lookup)
 	u64 ogen;
 	u64 flags;
 	int i;
+	int ioctl_num;
+
+	if (is_root)
+		ioctl_num = BTRFS_IOC_TREE_SEARCH;
+	else
+		ioctl_num = BTRFS_IOC_GET_SUBVOL_INFO;
 
 	root_lookup->root.rb_node = NULL;
 	memset(&args, 0, sizeof(args));
@@ -1019,7 +1075,7 @@ static int list_subvol_search(int fd, struct root_lookup *root_lookup)
 
 	while(1) {
 		sk->nr_items = 4096;
-		ret = ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args);
+		ret = ioctl(fd, ioctl_num, &args);
 		if (ret < 0)
 			return ret;
 		if (sk->nr_items == 0)
@@ -1300,6 +1356,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_fill_paths_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) {
@@ -1340,6 +1410,157 @@ static int list_subvol_fill_paths(int fd, struct root_lookup *root_lookup)
 	return 0;
 }
 
+
+static int list_subvol_fill_paths_user(int top_fd,
+					struct root_lookup *root_lookup,
+					const char *path)
+{
+	struct rb_node *n;
+	int ret = 0;
+	int fd;
+	struct root_info *ri, *parent;
+	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;
+
+	ret = btrfs_list_get_path_rootid(top_fd, &top_id);
+	if (ret)
+		return ret;
+
+	fullpath = realpath(path, NULL);
+	if (!fullpath)
+		return -ENOMEM;
+
+	/* Just fill the entry of top_id which is need */
+	ri = root_tree_search(root_lookup, top_id);
+	if (test_issubvolume(fullpath) && top_id != BTRFS_FS_TREE_OBJECTID) {
+		char *addr;
+
+		ri->top_id = ri->ref_tree;
+		addr = strrchr(fullpath, '/');
+		ri->name = malloc(strlen(addr+1));
+		ri->path = malloc(strlen(addr+1));
+		ri->full_path = malloc(strlen(addr+1));
+		if (!ri->name || !ri->path || !ri->full_path) {
+			free(fullpath);
+			free(ri->name);
+			free(ri->path);
+			free(ri->full_path);
+			return -ENOMEM;
+		}
+		strcpy(ri->name, addr+1);
+		strcpy(ri->path, addr+1);
+		strcpy(ri->full_path, addr+1);
+	}
+
+	/* 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) {
+		free(fullpath);
+		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. If path can be opened, iterate over rb_tree:
+	 * 3-1. Searth the rb_tree whose ref_tree is entry's id
+	 *    (this means searched subvolume exists under e->id's subvolume)
+	 * 3-2. Call ino_lookup ioctl
+	 * 3-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);
+
+		parent = root_tree_search(root_lookup, e->id);
+		if (e->id == top_id) {
+			fd = top_fd;
+		} else {
+			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;
+			}
+		}
+
+		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 */
+	if (!test_issubvolume(fullpath)) {
+		ri = root_tree_search(root_lookup, top_id);
+		rb_erase(&ri->rb_node, &root_lookup->root);
+		free_root_info(&ri->rb_node);
+	}
+
+	free(fullpath);
+
+	return 0;
+
+err:
+	free(fullpath);
+	if (fd != -1 && fd != top_fd)
+		close(fd);
+
+	/* free remaining 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)
 {
@@ -1523,8 +1744,10 @@ static int btrfs_list_subvols(int fd, struct root_lookup *root_lookup,
 			const char *path)
 {
 	int ret;
+	bool is_root;
 
-	ret = list_subvol_search(fd, root_lookup);
+	is_root = check_perm_for_tree_search(fd);
+	ret = list_subvol_search(fd, root_lookup, is_root);
 	if (ret) {
 		error("can't perform the search: %m");
 		return ret;
@@ -1534,7 +1757,11 @@ static int btrfs_list_subvols(int fd, struct root_lookup *root_lookup,
 	 * 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 (is_root)
+		ret = list_subvol_fill_paths(fd, root_lookup);
+	else
+		ret = list_subvol_fill_paths_user(fd, root_lookup, path);
+
 	return ret;
 }
 
@@ -1623,6 +1850,12 @@ int btrfs_get_subvol(int fd, struct root_info *the_ri, const char *path)
 	rbn = rb_first(&rl.root);
 	while(rbn) {
 		ri = rb_entry(rbn, struct root_info, rb_node);
+		if (ri->root_id == BTRFS_FS_TREE_OBJECTID || !ri->path) {
+			ret = -ENOENT;
+			rbn = rb_next(rbn);
+			continue;
+		}
+
 		rr = resolve_root(&rl, ri, root_id);
 		if (rr == -ENOENT) {
 			ret = -ENOENT;
@@ -1834,7 +2067,7 @@ char *btrfs_list_path_for_root(int fd, u64 root)
 	if (ret)
 		return ERR_PTR(ret);
 
-	ret = list_subvol_search(fd, &root_lookup);
+	ret = list_subvol_search(fd, &root_lookup, true);
 	if (ret < 0)
 		return ERR_PTR(ret);
 
diff --git a/cmds-subvolume.c b/cmds-subvolume.c
index faa10c5a..61076a9e 100644
--- a/cmds-subvolume.c
+++ b/cmds-subvolume.c
@@ -596,6 +596,12 @@ static int cmd_subvol_list(int argc, char **argv)
 		goto out;
 	}
 
+	if (!check_perm_for_tree_search(fd) && 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);
-- 
2.14.3


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [RFC PATCH 6/7] btrfs-progs: test: Add helper function to check if test user exists
  2018-03-06  8:32 [RFC PATCH 0/7] btrfs-progs: Allow normal user to call "subvolume list/show" Misono, Tomohiro
                   ` (4 preceding siblings ...)
  2018-03-06  8:36 ` [RFC PATCH 5/7] btrfs-progs: sub list: Allow normal user to call "subvolume list/show" Misono, Tomohiro
@ 2018-03-06  8:36 ` Misono, Tomohiro
  2018-03-06  8:37 ` [RFC PATCH 7/7] btrfs-porgs: test: Add cli-test/009 to check subvolume list for both root and normal user Misono, Tomohiro
  6 siblings, 0 replies; 8+ messages in thread
From: Misono, Tomohiro @ 2018-03-06  8:36 UTC (permalink / raw)
  To: linux-btrfs

Test user 'progs-test' will be used to test the behavior of normal user.

In order to pass this check, add the user by "useradd -M progs-test".
Note that progs-test should not have root privileges.

Signed-off-by: Tomohiro Misono <misono.tomohiro@jp.fujitsu.com>
---
 tests/common | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/tests/common b/tests/common
index fae30f1d..e8f7c061 100644
--- a/tests/common
+++ b/tests/common
@@ -307,6 +307,16 @@ check_global_prereq()
 	fi
 }
 
+check_testuser()
+{
+	id -u progs-test > /dev/null 2>&1
+	if [ $? -ne 0 ]; then
+		_not_run "Need to add user \"progs-test\""
+	fi
+	# Note that progs-test should not have root privileges
+	# otherwise test may not run as expected
+}
+
 check_image()
 {
 	local image
-- 
2.14.3


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [RFC PATCH 7/7] btrfs-porgs: test: Add cli-test/009 to check subvolume list for both root and normal user
  2018-03-06  8:32 [RFC PATCH 0/7] btrfs-progs: Allow normal user to call "subvolume list/show" Misono, Tomohiro
                   ` (5 preceding siblings ...)
  2018-03-06  8:36 ` [RFC PATCH 6/7] btrfs-progs: test: Add helper function to check if test user exists Misono, Tomohiro
@ 2018-03-06  8:37 ` Misono, Tomohiro
  6 siblings, 0 replies; 8+ messages in thread
From: Misono, Tomohiro @ 2018-03-06  8:37 UTC (permalink / raw)
  To: linux-btrfs


Signed-off-by: Tomohiro Misono <misono.tomohiro@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 00000000..e946837b
--- /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:
+#
+# ID 257 gen 9 top level 5 path sub1
+# ID 258 gen 8 top level 257 path sub1/subsub1
+# ID 259 gen 11 top level 5 path sub2
+# ID 260 gen 10 top level 259 path sub2/dir/subsub2
+# ID 261 gen 11 top level 259 path sub2/dir/dirdir/subsubX
+# ID 262 gen 15 top level 5 path sub3
+# ID 263 gen 13 top level 262 path sub3/subsub3
+# ID 264 gen 14 top level 262 path sub3/dir/subsubY
+# ID 265 gen 17 top level 5 path sub4
+# ID 266 gen 16 top level 265 path sub4/subsub4
+# ID 267 gen 17 top level 265 path sub4/dir/subsubZ
+
+# check for root for both absolute/relative path
+# always returns all subvolumes
+all=$(seq 257 267)
+test_list 1 "$TEST_MNT" "${all[@]}"
+test_list 1 "$TEST_MNT/sub1" "${all[@]}"
+test_list 1 "$TEST_MNT/sub1/dir" "${all[@]}"
+test_list 1 "$TEST_MNT/sub2" "${all[@]}"
+test_list 1 "$TEST_MNT/sub2/dir" "${all[@]}"
+test_list 1 "$TEST_MNT/sub3" "${all[@]}"
+test_list 1 "$TEST_MNT/sub4" "${all[@]}"
+run_mustfail "should fail for file" \
+	$SUDO_HELPER "$TOP/btrfs" subvolume list "$TEST/file"
+
+test_list 1 "." "${all[@]}"
+test_list 1 "sub1" "${all[@]}"
+test_list 1 "sub1/dir" "${all[@]}"
+test_list 1 "sub2" "${all[@]}"
+test_list 1 "sub2/dir" "${all[@]}"
+test_list 1 "sub3" "${all[@]}"
+test_list 1 "sub4" "${all[@]}"
+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" "257 258 259 260 261 262 265 266"
+test_list 0 "$TEST_MNT/sub1" "257 258"
+test_list 0 "$TEST_MNT/sub1/dir" ""
+test_list 0 "$TEST_MNT/sub2" "259 260 261"
+test_list 0 "$TEST_MNT/sub2/dir" "260 261"
+run_mustfail "should raise permission error" \
+	sudo -u progs-test "$TOP/btrfs" subvolume list "$TEST/sub3"
+test_list 0 "$TEST_MNT/sub4" "265 266"
+run_mustfail "should raise permission error" \
+	sudo -u progs-test "$TOP/btrfs" subvolume list "$TEST/sub4/dir"
+run_mustfail "should fail for file" \
+	sudo -u progs-test "$TOP/btrfs" subvolume list "$TEST/file"
+
+test_list 0 "." "257 258 259 260 261 262 265 266"
+test_list 0 "sub1/dir" ""
+test_list 0 "sub2" "259 260 261"
+test_list 0 "sub2/dir" "260 261"
+run_mustfail "should raise permission error" \
+	sudo -u progs-test "$TOP/btrfs" subvolume list "sub3"
+test_list 0 "sub4" "265 266"
+run_mustfail "should raise permission error" \
+	sudo -u progs-test "$TOP/btrfs" subvolume list "sub4/dir"
+run_mustfail "should fail for file" \
+	sudo -u progs-test "$TOP/btrfs" subvolume list "file"
+
+cd ..
+run_check_umount_test_dev
-- 
2.14.3


^ permalink raw reply related	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2018-03-06  8:37 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2018-03-06  8:32 [RFC PATCH 0/7] btrfs-progs: Allow normal user to call "subvolume list/show" Misono, Tomohiro
2018-03-06  8:33 ` [RFC PATCH 1/7] btrfs-progs: Add 2 definitions of new unprivileged ioctl Misono, Tomohiro
2018-03-06  8:34 ` [RFC PATCH 2/7] btrfs-progs: sub list: Add helper function which checks the permission for tree search ioctl Misono, Tomohiro
2018-03-06  8:34 ` [PATCH 3/7] btrfs-progs: sub list: Pass specified path down to btrfs_list_subvols() Misono, Tomohiro
2018-03-06  8:35 ` [RFC PATCH 4/7] btrfs-progs: fallback to open without O_NOATIME flag in, find_mount_root() Misono, Tomohiro
2018-03-06  8:36 ` [RFC PATCH 5/7] btrfs-progs: sub list: Allow normal user to call "subvolume list/show" Misono, Tomohiro
2018-03-06  8:36 ` [RFC PATCH 6/7] btrfs-progs: test: Add helper function to check if test user exists Misono, Tomohiro
2018-03-06  8:37 ` [RFC PATCH 7/7] btrfs-porgs: test: Add cli-test/009 to check subvolume list for both root and normal user Misono, Tomohiro

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).