Skip to contentSkip to conversation

btrfs-progs: Rework of "subvolume list/show" and relax the root privileges of them

PATCH In review Misono Tomohiro 2 participants 26 messages Jun 18, 2018 – Aug 21, 2018

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

v2 cover btrfs-progs: Rework of "subvolume list/show" and relax the root privileges of them

Changelog

v1 -> v2:
generally update whole patch set, especially:
- rebased to progs 4.17
- Improve error handling
- Update man/help/commit message
- Add/Update several options of sub list:
-f ... follow mounted subvolumes
-a ... remove meaningless filter
-A ... print path in absolute path
--nosort ... output results incrementally
Please see below examples
=====
github: https://github.com/t-msn/btrfs-progs/tree/rework-sub-list

Hello,

This series requires some new ioctls which are now in kernel 4.18-rc1.

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 (in both old and new kernel) and some options are newly added. For "subvolume show", root's output is the same as before but there are some difference from non-privileged user's output.

Please see below examples.

* Behavior summary of new "sub list"
- default (no option)
- lists subvolumes below the specified path (inc. path itself)
- If new ioctls exists
- the path can be non-subvolume directory
- non-privileged user can call it
(subvolumes to which the user cannot access will be skipped)

- -f
- follow mounted subvolume below the specified path and list them too (only if it is the same filesystem)

- -a
- updated to remove filter. i.e. the output is the same as current progs without option (require root privileges)

- -A
- print path in absolute path

-- nosort
- output results incrementally without loading information to memory

[Example]

  $ mkfs.btrfs -f $DEV
  $ mkfs.btrfs -f $DEV2
  $ mount $DEV $MNT
  $ btrfs subvolume create $MNT/AAA
  $ btrfs subvolume create $MNT/BBB
  $ btrfs subvolume create $MNT/CCC
  $ btrfs subvolume create $MNT/DDD
  $ mkdir $MNT/AAA/bbb
  $ mkdir $MNT/AAA/ccc
  $ mkdir $MNT/AAA/other
  $ umount $MNT
  $ mount -o subvol=AAA $DEV $MNT
  $ mount -o subvol=BBB $DEV $MNT/bbb
  $ mount -o subvol=CCC $DEV $MNT/ccc
  $ mount -o $DEV2 $MNT/other
  $ btrfs subvolume list $MNT # print subvolumes below the path
  ID 256 gen 10 top level 5 path .
  $ btrfs subvolume list -A $MNT # print path in absolute path
  ID 256 gen 10 top level 5 path /mnt
  $ btrfs subvolume list -f $MNT # follow mounted subvolumes too
  ID 256 gen 10 top level 5 path .
  ID 258 gen 7 top level 5 path bbb
  ID 259 gen 8 top level 5 path ccc
  $ btrfs subvolume list -a $MNT
  # print all subvolumes in the fs. same output as progs<=4.17 without option
  ID 256 gen 10 top level 5 path AAA
  ID 258 gen 7 top level 5 path BBB
  ID 259 gen 8 top level 5 path CCC
  ID 260 gen 9 top level 5 path DDD

More details are in each commit log.

* Behavior summary of new "sub show"
- No change for root's output
- If new ioctls exists, non-privileged user can call it
- In that case, path to be shown is absolute path (for root, it is relative to top-level subvolume) Also, snapshots to be shown are to which the user can access from current mount point.
(for root, all snapshots in the fs)

* Patch structure
The first several patches update libbtrfsutil and the latter patches update sub list/show command.

1st patch is independent and updates man doc of btrfs-subvolume

2nd-6th 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 for non-privileged user.

- Code path of root and non-privileged user is different. While root uses TREE_SEARCH ioctl as before, non-privileged user uses newly added ioctls. However, There is only one exception and when subvolume iterator is created from non-subvolume directory, code path of both is the same (and thus both use new ioctls).

7th patch update the "sub list" to use libbtrfsutil (no behavior change yet)
This is a copy of non-merged following patch originally written by Omar Sandoval:
btrfs-progs: use libbtrfsutil for subvolume list [1]
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.)

8th-15th patch update the behavior of "sub list"

16th-17th patch update the behavior of "sub show"

18th-20th patch are 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:
- Remove obsolete field (i.e. top-level) from output

Any comments are welcome.
Thanks,
Tomohiro Misono

[1] https://www.spinics.net/lists/linux-btrfs/msg74917.html

Misono Tomohiro (20):
btrfs-progs: doc: Update man btrfs subvolume
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: Relax the privileges of subvolume iterator
btrfs-progs: sub list: Use libbtrfsuitl for subvolume list
btrfs-progs: sub list: factor out main part of btrfs_list_subvols
btrfs-progs: sub list: Change the default behavior of "subvolume list"
and allow non-privileged user to call it
btrfs-progs: sub list: Add -A option to output path in absolute path
btrfs-progs: sub list: Add -f option to follow mounted subvolumes
below the path
btrfs-progs: sub list: Add --nosort option to output incrementally
without sort
btrfs-progs: sub list: Update -a option and remove meaningless filter
btrfs-progs: sub list: Update help message of -o option
btrfs-progs: sub list: Update help message of -d option
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
btrfs-progs: test: Add cli-test/010 to check "subvolume list -f"
option

 Documentation/btrfs-subvolume.asciidoc            |  108 +-
 cmds-subvolume.c                                  | 1372 ++++++++++++++++++++-
 ioctl.h                                           |   99 ++
 libbtrfsutil/btrfs.h                              |   97 ++
 libbtrfsutil/btrfsutil.h                          |   25 +-
 libbtrfsutil/errors.c                             |   10 +
 libbtrfsutil/subvolume.c                          |  494 +++++++-
 tests/cli-tests/009-subvolume-list/test.sh        |  134 ++
 tests/cli-tests/010-subvolume-list-follow/test.sh |   86 ++
 tests/common                                      |   10 +
 utils.c                                           |    3 +
 11 files changed, 2315 insertions(+), 123 deletions(-)
 create mode 100755 tests/cli-tests/009-subvolume-list/test.sh
 create mode 100755 tests/cli-tests/010-subvolume-list-follow/test.sh
Signature · 1 line
2.14.4
#1 lore raw

PATCH v2 1/20 btrfs-progs: doc: Update man btrfs subvolume

Some information is obsolete and updated as follows:
- Add missing explanations of some options
- Remove outdated explanation of "subvolrootid" mount option
- Reorder/group options of "sub list" to corresponds help message
- Add explanation about different meaning of parent in "parent ID/UUID"
- Fix indent/misspelling
- Add missing comma

---
 Documentation/btrfs-subvolume.asciidoc | 77 ++++++++++++++++++++++++----------
 1 file changed, 55 insertions(+), 22 deletions(-)
Documentation/btrfs-subvolume.asciidoc +55 -22
index a8c4af4b..f3eb4e26 100644
@@ -77,13 +77,16 @@ safely stored on the device.
 `Options`
 +
 -c|--commit-after::::
-wait for transaction commit at the end of the operation
+wait for transaction commit at the end of the operation.
 +
 -C|--commit-each::::
-wait for transaction commit after deleting each subvolume
+wait for transaction commit after deleting each subvolume.
++
+-v|--verbose::::
+verbose output of operations.
 
 *find-new* <subvolume> <last_gen>::
-List the recently modified files in a subvolume, after <last_gen> ID.
+List the recently modified files in a subvolume, after <last_gen> generation.
 
 *get-default* <path>::
 Get the default subvolume of the filesystem <path>.
@@ -93,40 +96,54 @@ The output format is similar to *subvolume list* command.
 *list* [options] [-G [\+|-]<value>] [-C [+|-]<value>] [--sort=rootid,gen,ogen,path] <path>::
 List the subvolumes present in the filesystem <path>.
 +
-For every subvolume the following information is shown by default. +
-ID <ID> top level <ID> path <path> +
-where path is the relative path of the subvolume to the top level subvolume.
+For every subvolume the following information is shown by default:
++
+ID <ID> gen <generation> top level <ID> path <path>
++
+where ID is subvolume's id, gen is an internal counter which is updated
+every transaction, top level is the same as parent subvolume's id, and
+path is the relative path of the subvolume to the top level subvolume.
 The subvolume's ID may be used by the subvolume set-default command,
 or at mount time via the subvolid= option.
-If `-p` is given, then parent <ID> is added to the output between ID
-and top level. The parent's ID may be used at mount time via the
-`subvolrootid=` option.
 +
 `Options`
 +
--p::::
-print parent ID.
+Path filtering;;
+-o::::
+print only subvolumes below specified <path>.
 -a::::
 print all the subvolumes in the filesystem and distinguish between
 absolute and relative path with respect to the given <path>.
+
+Field selection;;
+-p::::
+print the parent ID
+('parent' here means the subvolume which contains this subvolume).
 -c::::
 print the ogeneration of the subvolume, aliases: ogen or origin generation.
 -g::::
-print the generation of the subvolume.
--o::::
-print only subvolumes below specified <path>.
+print the generation of the subvolume (default).
 -u::::
 print the UUID of the subvolume.
 -q::::
-print the parent uuid of subvolumes (and snapshots).
+print the parent UUID of the subvolume
+('parent' here means subvolume of which this subvolume is a snapshot).
 -R::::
-print the UUID of the sent subvolume, where the subvolume is the result of a receive operation
--t::::
-print the result as a table.
+print the UUID of the sent subvolume, where the subvolume is the result of a receive operation.
+
+Type filtering;;
 -s::::
 only snapshot subvolumes in the filesystem will be listed.
 -r::::
 only readonly subvolumes in the filesystem will be listed.
+-d::::
+list deleted subvolumes that are not yet cleaned.
+
+Other;;
+-t::::
+print the result as a table.
+
+Sorting;;
 -G [+|-]<value>::::
 list subvolumes in the filesystem that its generation is
 >=, \<= or = value. \'\+' means >= value, \'-' means \<= value, If there is
@@ -144,9 +161,9 @@ for --sort you can combine some items together by \',', just like
 
 *set-default* [<subvolume>|<id> <path>]::
 Set the default subvolume for the (mounted) filesystem.
-
++
 Set the default subvolume for the (mounted) filesystem at <path>. This will hide
-the top-level subvolume (ie. the one mounted with 'subvol=/' or 'subvolid=5').
+the top-level subvolume (i.e. the one mounted with 'subvol=/' or 'subvolid=5').
 Takes action on next mount.
 +
 There are two ways how to specify the subvolume, by <id> or by the <subvolume>
@@ -154,10 +171,22 @@ path.
 The id can be obtained from *btrfs subvolume list*, *btrfs subvolume show* or
 *btrfs inspect-internal rootid*.
 
-*show* <path>::
+*show* [options] <path>|<mnt>::
 Show information of a given subvolume in the <path>.
++
+`Options`
++
+-r|--rootid::::
+rootid of the subvolume.
+-u|--uuid:::
+UUID of the subvolume.
+
++
+If no option is specified, subvolume information of <path> is shown,
+otherwise the subvolume information of rootid or UUID in the filesystem
+is shown.
 
-*snapshot* [-r] <source> <dest>|[<dest>/]<name>::
+*snapshot* [-r|-i <qgroupid>] <source> <dest>|[<dest>/]<name>::
 Create a snapshot of the subvolume <source> with the
 name <name> in the <dest> directory.
 +
@@ -168,6 +197,10 @@ If <source> is not a subvolume, btrfs returns an error.
 +
 -r::::
 Make the new snapshot read only.
++
+-i <qgroupid>::::
+Add the newly created subvolume to a qgroup. This option can be given multiple
+times.
 
 *sync* <path> [subvolid...]::
 Wait until given subvolume(s) are completely removed from the filesystem after
Signature · 1 line
2.14.4
#2 lore raw

PATCH v2 2/20 btrfs-progs: ioctl/libbtrfsutil: Add 3 definitions of new unprivileged ioctl

In reply to Misono Tomohiro

Copy and add 3 definitions of new unprivileged ioctl (BTRFS_IOC_GET_SUBVOL_INFO, BTRFS_IOC_GET_SUBVOL_ROOTREF and BTRFS_IOC_INO_LOOKUP_USER) from kernel. They will be used to implement the user version of "btrfs subvolume list/show" etc.

---
 ioctl.h              | 99 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 libbtrfsutil/btrfs.h | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 196 insertions(+)
ioctl.h +99 -0
index 709e996f..cce55dbd 100644
@@ -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 treeid;
+	/* out, name of the subvolume of 'treeid' */
+	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,83 @@ BUILD_ASSERT(sizeof(struct btrfs_ioctl_send_args_64) == 72);
 
 #define BTRFS_IOC_SEND_64_COMPAT_DEFINED 1
 
+/*
+ * Information about a fs tree root.
+ *
+ * All items are filled by the ioctl
+ */
+struct btrfs_ioctl_get_subvol_info_args {
+	/* Id of this subvolume */
+	__u64 treeid;
+
+	/* 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 a deleted subvolume.
+	 */
+	__u64 parent_id;
+
+	/*
+	 * Inode number of the directory which contains this subvolume.
+	 * Zero for top-level subvolume or a 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 a non-snapshot subvolume.
+	 */
+	__u8 parent_uuid[BTRFS_UUID_SIZE];
+
+	/*
+	 * UUID of the subvolume from which this subvolume was received.
+	 * All zero for non-received subvolume.
+	 */
+	__u8 received_uuid[BTRFS_UUID_SIZE];
+
+	/* Transaction id indicating when change/create/send/receive happened */
+	__u64 ctransid;
+	__u64 otransid;
+	__u64 stransid;
+	__u64 rtransid;
+	/* Time corresponding 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;
+
+	/* Must be zero */
+	__u64 reserved[8];
+};
+
+#define BTRFS_MAX_ROOTREF_BUFFER_NUM 255
+struct btrfs_ioctl_get_subvol_rootref_args {
+		/* in/out, minimum id of rootref's treeid to be searched */
+		__u64 min_treeid;
+
+		/* out */
+		struct {
+			__u64 treeid;
+			__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 +921,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
libbtrfsutil/btrfs.h +97 -0

114 lines not on this page · raw message

Signature · 1 line
2.14.4
#3 lore raw

PATCH v2 3/20 btrfs-progs: libbtrfsutil: Factor out btrfs_util_subvolume_info_fd()

In reply to Misono Tomohiro

Factor out main logic of btrfs_util_subvolume_info_fd(). This is a preparation work to relax the root privilege of this function.

No functional change happens.

---
 libbtrfsutil/subvolume.c | 45 ++++++++++++++++++++++++++-------------------
 1 file changed, 26 insertions(+), 19 deletions(-)
libbtrfsutil/subvolume.c +26 -19
index 867b3e10..0d7ef5bf 100644
@@ -295,8 +295,8 @@ PUBLIC enum btrfs_util_error btrfs_util_subvolume_info(const char *path,
 	return err;
 }
 
-PUBLIC enum btrfs_util_error btrfs_util_subvolume_info_fd(int fd, uint64_t id,
-							  struct btrfs_util_subvolume_info *subvol)
+static enum btrfs_util_error get_subvolume_info_root(int fd, uint64_t id,
+						     struct btrfs_util_subvolume_info *subvol)
 {
 	struct btrfs_ioctl_search_args search = {
 		.key = {
@@ -310,27 +310,10 @@ PUBLIC enum btrfs_util_error btrfs_util_subvolume_info_fd(int fd, uint64_t id,
 			.nr_items = 0,
 		},
 	};
-	enum btrfs_util_error err;
 	size_t items_pos = 0, buf_off = 0;
 	bool need_root_item = true, need_root_backref = true;
 	int ret;
 
-	if (id == 0) {
-		err = btrfs_util_is_subvolume_fd(fd);
-		if (err)
-			return err;
-
-		err = btrfs_util_subvolume_id_fd(fd, &id);
-		if (err)
-			return err;
-	}
-
-	if ((id < BTRFS_FIRST_FREE_OBJECTID && id != BTRFS_FS_TREE_OBJECTID) ||
-	    id > BTRFS_LAST_FREE_OBJECTID) {
-		errno = ENOENT;
-		return BTRFS_UTIL_ERROR_SUBVOLUME_NOT_FOUND;
-	}
-
 	search.key.min_objectid = search.key.max_objectid = id;
 
 	if (subvol) {
@@ -400,6 +383,30 @@ PUBLIC enum btrfs_util_error btrfs_util_subvolume_info_fd(int fd, uint64_t id,
 	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 (id == 0) {
+		err = btrfs_util_is_subvolume_fd(fd);
+		if (err)
+			return err;
+
+		err = btrfs_util_subvolume_id_fd(fd, &id);
+		if (err)
+			return err;
+	}
+
+	if ((id < BTRFS_FIRST_FREE_OBJECTID && id != BTRFS_FS_TREE_OBJECTID) ||
+	    id > BTRFS_LAST_FREE_OBJECTID) {
+		errno = ENOENT;
+		return BTRFS_UTIL_ERROR_SUBVOLUME_NOT_FOUND;
+	}
+
+	return get_subvolume_info_root(fd, id, subvol);
+}
+
 PUBLIC enum btrfs_util_error btrfs_util_get_subvolume_read_only_fd(int fd,
 								   bool *read_only_ret)
 {
Signature · 1 line
2.14.4
#4 lore raw

PATCH v2 4/20 btrfs-porgs: libbtrfsutil: Relax the privileges of util_subvolume_info()

In reply to Misono Tomohiro

This commit relaxes the privileges of util_subvolume_info() if kernel supports new ioctl (BTRFS_IOC_GET_SUBVOL_INFO) and @id is zero (i.e. when getting the information of the given path/fd). For older kernel (< 4.18), the behavior is the same.

---
 libbtrfsutil/btrfsutil.h |  7 +++++-
 libbtrfsutil/errors.c    |  4 ++++
 libbtrfsutil/subvolume.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 68 insertions(+), 1 deletion(-)
libbtrfsutil/btrfsutil.h +6 -1
index 6d655f49..3f21d163 100644
@@ -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 kernel < 4.18.
+ * From kernel >= 4.18 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.
  */
libbtrfsutil/errors.c +4 -0
index 634edc65..f196fa71 100644
@@ -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)
libbtrfsutil/subvolume.c +58 -0

75 lines not on this page · raw message

Signature · 1 line
2.14.4
#5 lore raw

PATCH v2 5/20 btrfs-progs: libbtrfsuitl: Factor out btrfs_util_subvolume_iterator_next()

In reply to Misono Tomohiro

Factor out the main logic of btrfs_util_subvolume_iterator_next(). This is a preparation work to relax the root privilege of this function.

No functional change happens.

---
 libbtrfsutil/subvolume.c | 13 ++++++++++---
 1 file changed, 10 insertions(+), 3 deletions(-)
libbtrfsutil/subvolume.c +10 -3
index e94c7079..73471d4f 100644
@@ -1255,9 +1255,9 @@ 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,
-								char **path_ret,
-								uint64_t *id_ret)
+static enum btrfs_util_error subvolume_iterator_next_root(struct btrfs_util_subvolume_iterator *iter,
+							  char **path_ret,
+							  uint64_t *id_ret)
 {
 	struct search_stack_entry *top;
 	const struct btrfs_ioctl_search_header *header;
@@ -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)
Signature · 1 line
2.14.4
#6 lore raw

PATCH v2 6/20 btrfs-progs: libbtrfsutil: Relax the privileges of subvolume iterator

In reply to Misono Tomohiro

This commit relax the privileges of subvolume iterator when the kernel supports new ioctls (BTRFS_IOC_GET_SUBVOL_ROOTREF and BTRFS_IOC_INO_LOOKUP_USER).

For older kernel (< 4.18), the behavior is the same.

Non-privileged user can use subvolume iterator only if new ioctls are supported and btrfs_util_create_subvolume_iterator() is called with @top zero (i.e. if the iterator is created from given path/fd). In this case:
- an iterator can be created from non-subvolume directory
- subvolume will be skipped 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

Note that this commit also allows root user to specify non-subvolume direcotry when @top is zero. Only in that case, root's code path is the same as user.

---
 libbtrfsutil/btrfsutil.h |  18 ++-
 libbtrfsutil/errors.c    |   6 +
 libbtrfsutil/subvolume.c | 380 +++++++++++++++++++++++++++++++++++++++++++----
 3 files changed, 377 insertions(+), 27 deletions(-)
libbtrfsutil/btrfsutil.h +16 -2
index 3f21d163..49454a63 100644
@@ -65,6 +65,10 @@ 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,
+	BTRFS_UTIL_ERROR_DUP_FAILED,
+	BTRFS_UTIL_ERROR_CHDIR_FAILED,
 };
 
 /**
@@ -510,6 +514,14 @@ struct btrfs_util_subvolume_iterator;
  * @flags: Bitmask of BTRFS_UTIL_SUBVOLUME_ITERATOR_* flags.
  * @ret: Returned iterator.
  *
+ * Using subvolume iterator requires appropriate privilege (CAP_SYS_ADMIN) for
+ * kernel < 4.18. From kenrel >= 4.18 which supports
+ * BTRFS_IOC_GET_SUBVOL_ROOTREF and BTRFS_IOC_INO_LOOKUP_USER, non-previleged
+ * user can use it too (in that case @top must be zero). Also from kernel
+ * >=4.18, if @top is zero, the specified path can be non-subvolume directory
+ * and subvolumes which cannot be accessed will be skipped (either due to
+ * permission error or path is hidden by other mount).
+ *
  * The returned iterator must be freed with
  * btrfs_util_destroy_subvolume_iterator().
  *
@@ -558,7 +570,8 @@ 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 kernel < 4.18.
+ * See the comment of btrfs_util_create_subvolume_iterator()
  *
  * 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 +590,8 @@ 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 kernel < 4.18.
+ * See the comment of btrfs_util_create_subvolume_iterator()
  *
  * Return: See btrfs_util_subvolume_iterator_next().
  */
libbtrfsutil/errors.c +6 -0
index f196fa71..c77407bf 100644
@@ -49,6 +49,12 @@ 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_SUBVOL_ROOTREF",
+	[BTRFS_UTIL_ERROR_INO_LOOKUP_USER_FAILED] =
+	"Could not resolve subvolume path by BTRFS_IOC_INO_LOOKUP_USER",
+	[BTRFS_UTIL_ERROR_DUP_FAILED] = "Could not dup",
+	[BTRFS_UTIL_ERROR_CHDIR_FAILED] = "Could not chdir",
 };
 
 PUBLIC const char *btrfs_util_strerror(enum btrfs_util_error err)
libbtrfsutil/subvolume.c +355 -25

447 lines not on this page · raw message

Signature · 1 line
2.14.4
#7 lore raw

PATCH v2 7/20 btrfs-progs: sub list: Use libbtrfsuitl for subvolume list

In reply to Misono Tomohiro

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 <[email protected]>

---
 cmds-subvolume.c | 963 +++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 936 insertions(+), 27 deletions(-)
cmds-subvolume.c +936 -27
index 45363a5a..c54a8003 100644
@@ -404,6 +404,915 @@ 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.generation)
+		ret = 1;
+	else if (entry1->info.generation < entry2->info.generation)
+		ret = -1;
+	else
+		ret = 0;
+
+	return is_descending ? -ret : ret;
+}
+
+static int comp_entry_with_ogen_v2(const struct listed_subvol *entry1,
+				const struct listed_subvol *entry2,
+				int is_descending)
+{
+	int ret;
+
+	if (entry1->info.otransid > entry2->info.otransid)
+		ret = 1;
+	else if (entry1->info.otransid < entry2->info.otransid)
+		ret = -1;
+	else
+		ret = 0;
+
+	return is_descending ? -ret : ret;
+}
+
+static int comp_entry_with_path_v2(const struct listed_subvol *entry1,
+				const struct listed_subvol *entry2,
+				int is_descending)
+{
+	int ret;
+
+	if (strcmp(entry1->path, entry2->path) > 0)
+		ret = 1;
+	else if (strcmp(entry1->path, entry2->path) < 0)
+		ret = -1;
+	else
+		ret = 0;
+
+	return is_descending ? -ret : ret;
+}
+
+static btrfs_list_comp_func_v2 all_comp_funcs[] = {
+	[BTRFS_LIST_COMP_ROOTID]	= comp_entry_with_rootid_v2,
+	[BTRFS_LIST_COMP_OGEN]		= comp_entry_with_ogen_v2,
+	[BTRFS_LIST_COMP_GEN]		= comp_entry_with_gen_v2,
+	[BTRFS_LIST_COMP_PATH]		= comp_entry_with_path_v2,
+};
+
+static char *all_sort_items[] = {
+	[BTRFS_LIST_COMP_ROOTID]	= "rootid",
+	[BTRFS_LIST_COMP_OGEN]		= "ogen",
+	[BTRFS_LIST_COMP_GEN]		= "gen",
+	[BTRFS_LIST_COMP_PATH]		= "path",
+	[BTRFS_LIST_COMP_MAX]		= NULL,
+};
+
+static int btrfs_list_get_sort_item(char *sort_name)
+{
+	int i;
+
+	for (i = 0; i < BTRFS_LIST_COMP_MAX; i++) {
+		if (strcmp(sort_name, all_sort_items[i]) == 0)
+			return i;
+	}
+	return -1;
+}
+
+static struct btrfs_list_comparer_set_v2 *btrfs_list_alloc_comparer_set_v2(void)
+{
+	struct btrfs_list_comparer_set_v2 *set;
+	int size;
+
+	size = sizeof(struct btrfs_list_comparer_set_v2) +
+		BTRFS_LIST_NCOMPS_INCREASE *
+		sizeof(struct btrfs_list_comparer_v2);
+	set = calloc(1, size);
+	if (!set) {
+		fprintf(stderr, "memory allocation failed\n");
+		exit(1);
+	}
+
+	set->total = BTRFS_LIST_NCOMPS_INCREASE;
+
+	return set;
+}
+
+static int btrfs_list_setup_comparer_v2(struct btrfs_list_comparer_set_v2 **comp_set,
+				     enum btrfs_list_comp_enum comparer,
+				     int is_descending)
+{
+	struct btrfs_list_comparer_set_v2 *set = *comp_set;
+	int size;
+
+	ASSERT(set != NULL);
+	ASSERT(comparer < BTRFS_LIST_COMP_MAX);
+	ASSERT(set->ncomps <= set->total);
+
+	if (set->ncomps == set->total) {
+		void *tmp;
+
+		size = set->total + BTRFS_LIST_NCOMPS_INCREASE;
+		size = sizeof(*set) +
+			size * sizeof(struct btrfs_list_comparer_v2);
+		tmp = set;
+		set = realloc(set, size);
+		if (!set) {
+			fprintf(stderr, "memory allocation failed\n");
+			free(tmp);
+			exit(1);
+		}
+
+		memset(&set->comps[set->total], 0,
+		       BTRFS_LIST_NCOMPS_INCREASE *
+		       sizeof(struct btrfs_list_comparer_v2));
+		set->total += BTRFS_LIST_NCOMPS_INCREASE;
+		*comp_set = set;
+	}
+
+	ASSERT(set->comps[set->ncomps].comp_func == NULL);
+
+	set->comps[set->ncomps].comp_func = all_comp_funcs[comparer];
+	set->comps[set->ncomps].is_descending = is_descending;
+	set->ncomps++;
+	return 0;
+}
+
+static int subvol_comp(const void *entry1, const void *entry2, void *arg)
+{
+	const struct btrfs_list_comparer_set_v2 * const set = arg;
+	int rootid_compared = 0;
+	int ret;
+	int i;
+
+	for (i = 0; set && i < set->ncomps; i++) {
+		if (!set->comps[i].comp_func)
+			break;
+
+		ret = set->comps[i].comp_func(entry1, entry2,
+					      set->comps[i].is_descending);
+		if (ret)
+			return ret;
+
+		if (set->comps[i].comp_func == comp_entry_with_rootid_v2)
+			rootid_compared = 1;
+	}
+
+	if (!rootid_compared)
+		return comp_entry_with_rootid_v2(entry1, entry2, 0);
+
+	return 0;
+}
+
+static void sort_subvols(struct btrfs_list_comparer_set_v2 *comp_set,
+			 struct subvol_list *subvols)
+{
+	qsort_r(subvols->subvols, subvols->num, sizeof(subvols->subvols[0]),
+		subvol_comp, comp_set);
+}
+
+static int filter_by_rootid(struct listed_subvol *subvol, uint64_t data)
+{
+	return subvol->info.id == data;
+}
+
+static int filter_snapshot(struct listed_subvol *subvol, uint64_t data)
+{
+	return !uuid_is_null(subvol->info.parent_uuid);
+}
+
+static int filter_flags(struct listed_subvol *subvol, uint64_t data)
+{
+	return subvol->info.flags & data;
+}
+
+static int filter_gen_more(struct listed_subvol *subvol, uint64_t data)
+{
+	return subvol->info.generation >= data;
+}
+
+static int filter_gen_less(struct listed_subvol *subvol, uint64_t data)
+{
+	return subvol->info.generation <= data;
+}
+
+static int filter_gen_equal(struct listed_subvol *subvol, uint64_t data)
+{
+	return subvol->info.generation == data;
+}
+
+static int filter_cgen_more(struct listed_subvol *subvol, uint64_t data)
+{
+	return subvol->info.otransid >= data;
+}
+
+static int filter_cgen_less(struct listed_subvol *subvol, uint64_t data)
+{
+	return subvol->info.otransid <= data;
+}
+
+static int filter_cgen_equal(struct listed_subvol *subvol, uint64_t data)
+{
+	return subvol->info.otransid == data;
+}
+
+static int filter_topid_equal(struct listed_subvol *subvol, uint64_t data)
+{
+	/* See the comment in print_subvolume_column() about top level. */
+	return subvol->info.parent_id == data;
+}
+
+static int filter_full_path(struct listed_subvol *subvol, uint64_t data)
+{
+	/*
+	 * This implements the same behavior as before the conversion to
+	 * libbtrfsutil, which is mostly nonsensical.
+	 */
+	if (subvol->info.parent_id != data) {
+		char *tmp;
+		int ret;
+
+		ret = asprintf(&tmp, "<FS_TREE>/%s", subvol->path);
+		if (ret == -1) {
+			error("out of memory");
+			exit(1);
+		}
+
+		free(subvol->path);
+		subvol->path = tmp;
+	}
+	return 1;
+}
+
+static int filter_by_parent(struct listed_subvol *subvol, uint64_t data)
+{
+	return !uuid_compare(subvol->info.parent_uuid,
+			     (u8 *)(unsigned long)data);
+}
+
+static btrfs_list_filter_func_v2 all_filter_funcs[] = {
+	[BTRFS_LIST_FILTER_ROOTID]		= filter_by_rootid,
+	[BTRFS_LIST_FILTER_SNAPSHOT_ONLY]	= filter_snapshot,
+	[BTRFS_LIST_FILTER_FLAGS]		= filter_flags,
+	[BTRFS_LIST_FILTER_GEN_MORE]		= filter_gen_more,
+	[BTRFS_LIST_FILTER_GEN_LESS]		= filter_gen_less,
+	[BTRFS_LIST_FILTER_GEN_EQUAL]           = filter_gen_equal,
+	[BTRFS_LIST_FILTER_CGEN_MORE]		= filter_cgen_more,
+	[BTRFS_LIST_FILTER_CGEN_LESS]		= filter_cgen_less,
+	[BTRFS_LIST_FILTER_CGEN_EQUAL]          = filter_cgen_equal,
+	[BTRFS_LIST_FILTER_TOPID_EQUAL]		= filter_topid_equal,
+	[BTRFS_LIST_FILTER_FULL_PATH]		= filter_full_path,
+	[BTRFS_LIST_FILTER_BY_PARENT]		= filter_by_parent,
+};
+
+static struct btrfs_list_filter_set_v2 *btrfs_list_alloc_filter_set_v2(void)
+{
+	struct btrfs_list_filter_set_v2 *set;
+	int size;
+
+	size = sizeof(struct btrfs_list_filter_set_v2) +
+		BTRFS_LIST_NFILTERS_INCREASE *
+		sizeof(struct btrfs_list_filter_v2);
+	set = calloc(1, size);
+	if (!set) {
+		fprintf(stderr, "memory allocation failed\n");
+		exit(1);
+	}
+
+	set->total = BTRFS_LIST_NFILTERS_INCREASE;
+
+	return set;
+}
+
+/*
+ * Setup list filters. Exit if there's not enough memory, as we can't continue
+ * without the structures set up properly.
+ */
+static void btrfs_list_setup_filter_v2(struct btrfs_list_filter_set_v2 **filter_set,
+				    enum btrfs_list_filter_enum filter,
+				    u64 data)
+{
+	struct btrfs_list_filter_set_v2 *set = *filter_set;
+	int size;
+
+	ASSERT(set != NULL);
+	ASSERT(filter < BTRFS_LIST_FILTER_MAX);
+	ASSERT(set->nfilters <= set->total);
+
+	if (set->nfilters == set->total) {
+		void *tmp;
+
+		size = set->total + BTRFS_LIST_NFILTERS_INCREASE;
+		size = sizeof(*set) +
+			size * sizeof(struct btrfs_list_filter_v2);
+		tmp = set;
+		set = realloc(set, size);
+		if (!set) {
+			fprintf(stderr, "memory allocation failed\n");
+			free(tmp);
+			exit(1);
+		}
+
+		memset(&set->filters[set->total], 0,
+		       BTRFS_LIST_NFILTERS_INCREASE *
+		       sizeof(struct btrfs_list_filter_v2));
+		set->total += BTRFS_LIST_NFILTERS_INCREASE;
+		*filter_set = set;
+	}
+
+	ASSERT(set->filters[set->nfilters].filter_func == NULL);
+
+	if (filter == BTRFS_LIST_FILTER_DELETED) {
+		set->only_deleted = 1;
+	} else {
+		set->filters[set->nfilters].filter_func =
+					all_filter_funcs[filter];
+		set->filters[set->nfilters].data = data;
+		set->nfilters++;
+	}
+}
+
+static int filters_match(struct listed_subvol *subvol,
+			 struct btrfs_list_filter_set_v2 *set)
+{
+	int i, ret;
+
+	if (!set)
+		return 1;
+
+	for (i = 0; i < set->nfilters; i++) {
+		if (!set->filters[i].filter_func)
+			break;
+		ret = set->filters[i].filter_func(subvol, set->filters[i].data);
+		if (!ret)
+			return 0;
+	}
+	return 1;
+}
+
+static void print_subvolume_column(struct listed_subvol *subvol,
+				   enum btrfs_list_column_enum column)
+{
+	char tstr[256];
+	char uuidparse[BTRFS_UUID_UNPARSED_SIZE];
+
+	ASSERT(0 <= column && column < BTRFS_LIST_ALL);
+
+	switch (column) {
+	case BTRFS_LIST_OBJECTID:
+		printf("%" PRIu64, subvol->info.id);
+		break;
+	case BTRFS_LIST_GENERATION:
+		printf("%" PRIu64, subvol->info.generation);
+		break;
+	case BTRFS_LIST_OGENERATION:
+		printf("%" PRIu64, subvol->info.otransid);
+		break;
+	case BTRFS_LIST_PARENT:
+	/*
+	 * Top level used to mean something else, but since 4f5ebb3ef553
+	 * ("Btrfs-progs: fix to make list specified directory's subvolumes
+	 * work") it was always set to the parent ID. See
+	 * https://www.spinics.net/lists/linux-btrfs/msg69820.html.
+	 */
+	case BTRFS_LIST_TOP_LEVEL:
+		printf("%" PRIu64, subvol->info.parent_id);
+		break;
+	case BTRFS_LIST_OTIME:
+		if (subvol->info.otime.tv_sec) {
+			struct tm tm;
+
+			localtime_r(&subvol->info.otime.tv_sec, &tm);
+			strftime(tstr, sizeof(tstr), "%Y-%m-%d %X", &tm);
+		} else
+			strcpy(tstr, "-");
+		printf("%s", tstr);
+		break;
+	case BTRFS_LIST_UUID:
+		if (uuid_is_null(subvol->info.uuid))
+			strcpy(uuidparse, "-");
+		else
+			uuid_unparse(subvol->info.uuid, uuidparse);
+		printf("%-36s", uuidparse);
+		break;
+	case BTRFS_LIST_PUUID:
+		if (uuid_is_null(subvol->info.parent_uuid))
+			strcpy(uuidparse, "-");
+		else
+			uuid_unparse(subvol->info.parent_uuid, uuidparse);
+		printf("%-36s", uuidparse);
+		break;
+	case BTRFS_LIST_RUUID:
+		if (uuid_is_null(subvol->info.received_uuid))
+			strcpy(uuidparse, "-");
+		else
+			uuid_unparse(subvol->info.received_uuid, uuidparse);
+		printf("%-36s", uuidparse);
+		break;
+	case BTRFS_LIST_PATH:
+		printf("%s", subvol->path);
+		break;
+	default:
+		break;
+	}
+}
+
+static void print_one_subvol_info_raw(struct listed_subvol *subvol,
+				      const char *raw_prefix)
+{
+	int i;
+
+	for (i = 0; i < BTRFS_LIST_ALL; i++) {
+		if (!btrfs_list_columns[i].need_print)
+			continue;
+
+		if (raw_prefix)
+			printf("%s", raw_prefix);
+
+		print_subvolume_column(subvol, i);
+	}
+	printf("\n");
+}
+
+static void print_one_subvol_info_table(struct listed_subvol *subvol)
+{
+	int i;
+
+	for (i = 0; i < BTRFS_LIST_ALL; i++) {
+		if (!btrfs_list_columns[i].need_print)
+			continue;
+
+		print_subvolume_column(subvol, i);
+
+		if (i != BTRFS_LIST_PATH)
+			printf("\t");
+
+		if (i == BTRFS_LIST_TOP_LEVEL)
+			printf("\t");
+	}
+	printf("\n");
+}
+
+static void print_one_subvol_info_default(struct listed_subvol *subvol)
+{
+	int i;
+
+	for (i = 0; i < BTRFS_LIST_ALL; i++) {
+		if (!btrfs_list_columns[i].need_print)
+			continue;
+
+		printf("%s ", btrfs_list_columns[i].name);
+		print_subvolume_column(subvol, i);
+
+		if (i != BTRFS_LIST_PATH)
+			printf(" ");
+	}
+	printf("\n");
+}
+
+static void print_all_subvol_info_tab_head(void)
+{
+	int i;
+	int len;
+	char barrier[20];
+
+	for (i = 0; i < BTRFS_LIST_ALL; i++) {
+		if (btrfs_list_columns[i].need_print)
+			printf("%s\t", btrfs_list_columns[i].name);
+
+		if (i == BTRFS_LIST_ALL-1)
+			printf("\n");
+	}
+
+	for (i = 0; i < BTRFS_LIST_ALL; i++) {
+		memset(barrier, 0, sizeof(barrier));
+
+		if (btrfs_list_columns[i].need_print) {
+			len = strlen(btrfs_list_columns[i].name);
+			while (len--)
+				strcat(barrier, "-");
+
+			printf("%s\t", barrier);
+		}
+		if (i == BTRFS_LIST_ALL-1)
+			printf("\n");
+	}
+}
+
+static void print_all_subvol_info(struct subvol_list *subvols,
+				  enum btrfs_list_layout layout,
+				  const char *raw_prefix)
+{
+	size_t i;
+
+	if (layout == BTRFS_LIST_LAYOUT_TABLE)
+		print_all_subvol_info_tab_head();
+
+	for (i = 0; i < subvols->num; i++) {
+		struct listed_subvol *subvol = &subvols->subvols[i];
+
+		switch (layout) {
+		case BTRFS_LIST_LAYOUT_DEFAULT:
+			print_one_subvol_info_default(subvol);
+			break;
+		case BTRFS_LIST_LAYOUT_TABLE:
+			print_one_subvol_info_table(subvol);
+			break;
+		case BTRFS_LIST_LAYOUT_RAW:
+			print_one_subvol_info_raw(subvol, raw_prefix);
+			break;
+		}
+	}
+}
+
+static void free_subvol_list(struct subvol_list *subvols)
+{
+	size_t i;
+
+	if (subvols) {
+		for (i = 0; i < subvols->num; i++)
+			free(subvols->subvols[i].path);
+		free(subvols);
+	}
+}
+
+static struct subvol_list *btrfs_list_deleted_subvols(int fd,
+						      struct btrfs_list_filter_set_v2 *filter_set)
+{
+	struct subvol_list *subvols = NULL;
+	uint64_t *ids = NULL;
+	size_t i, n;
+	enum btrfs_util_error err;
+	int ret = -1;
+
+	err = btrfs_util_deleted_subvolumes_fd(fd, &ids, &n);
+	if (err) {
+		error_btrfs_util(err);
+		return NULL;
+	}
+
+	subvols = malloc(sizeof(*subvols) + n * sizeof(subvols->subvols[0]));
+	if (!subvols) {
+		error("out of memory");
+		goto out;
+	}
+
+	subvols->num = 0;
+	for (i = 0; i < n; i++) {
+		struct listed_subvol *subvol = &subvols->subvols[subvols->num];
+
+		err = btrfs_util_subvolume_info_fd(fd, ids[i], &subvol->info);
+		if (err) {
+			error_btrfs_util(err);
+			goto out;
+		}
+
+		subvol->path = strdup("DELETED");
+		if (!subvol->path)
+			goto out;
+
+		if (!filters_match(subvol, filter_set)) {
+			free(subvol->path);
+			continue;
+		}
+
+		subvols->num++;
+	}
+
+	ret = 0;
+out:
+	if (ret) {
+		free_subvol_list(subvols);
+		subvols = NULL;
+		free(ids);
+	}
+	return subvols;
+}
+
+static struct subvol_list *btrfs_list_subvols(int fd,
+					      struct btrfs_list_filter_set_v2 *filter_set)
+{
+	struct subvol_list *subvols;
+	size_t capacity = 0;
+	struct btrfs_util_subvolume_iterator *iter;
+	enum btrfs_util_error err;
+	int ret = -1;
+
+	subvols = malloc(sizeof(*subvols));
+	if (!subvols) {
+		error("out of memory");
+		return NULL;
+	}
+	subvols->num = 0;
+
+	err = btrfs_util_create_subvolume_iterator_fd(fd,
+						      BTRFS_FS_TREE_OBJECTID, 0,
+						      &iter);
+	if (err) {
+		iter = NULL;
+		error_btrfs_util(err);
+		goto out;
+	}
+
+	for (;;) {
+		struct listed_subvol subvol;
+
+		err = btrfs_util_subvolume_iterator_next_info(iter,
+							      &subvol.path,
+							      &subvol.info);
+		if (err == BTRFS_UTIL_ERROR_STOP_ITERATION) {
+			break;
+		} else if (err) {
+			error_btrfs_util(err);
+			goto out;
+		}
+
+		if (!filters_match(&subvol, filter_set)) {
+			free(subvol.path);
+			continue;
+		}
+
+		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;
+		}
+
+		subvols->subvols[subvols->num] = subvol;
+		subvols->num++;
+	}
+
+	ret = 0;
+out:
+	if (iter)
+		btrfs_util_destroy_subvolume_iterator(iter);
+	if (ret) {
+		free_subvol_list(subvols);
+		subvols = NULL;
+	}
+	return subvols;
+}
+
+static int btrfs_list_subvols_print_v2(int fd,
+				    struct btrfs_list_filter_set_v2 *filter_set,
+				    struct btrfs_list_comparer_set_v2 *comp_set,
+				    enum btrfs_list_layout layout,
+				    int full_path, const char *raw_prefix)
+{
+	struct subvol_list *subvols;
+
+	/*
+	 * full_path hasn't done anything since 4f5ebb3ef553 ("Btrfs-progs: fix
+	 * to make list specified directory's subvolumes work"). See
+	 * https://www.spinics.net/lists/linux-btrfs/msg69820.html
+	 */
+
+	if (filter_set->only_deleted)
+		subvols = btrfs_list_deleted_subvols(fd, filter_set);
+	else
+		subvols = btrfs_list_subvols(fd, filter_set);
+	if (!subvols)
+		return -1;
+
+	sort_subvols(comp_set, subvols);
+
+	print_all_subvol_info(subvols, layout, raw_prefix);
+
+	free_subvol_list(subvols);
+
+	return 0;
+}
+
+static int btrfs_list_parse_sort_string_v2(char *opt_arg,
+					struct btrfs_list_comparer_set_v2 **comps)
+{
+	int order;
+	int flag;
+	char *p;
+	char **ptr_argv;
+	int what_to_sort;
+
+	while ((p = strtok(opt_arg, ",")) != NULL) {
+		flag = 0;
+		ptr_argv = all_sort_items;
+
+		while (*ptr_argv) {
+			if (strcmp(*ptr_argv, p) == 0) {
+				flag = 1;
+				break;
+			} else {
+				p++;
+				if (strcmp(*ptr_argv, p) == 0) {
+					flag = 1;
+					p--;
+					break;
+				}
+				p--;
+			}
+			ptr_argv++;
+		}
+
+		if (flag == 0)
+			return -1;
+
+		else {
+			if (*p == '+') {
+				order = 0;
+				p++;
+			} else if (*p == '-') {
+				order = 1;
+				p++;
+			} else
+				order = 0;
+
+			what_to_sort = btrfs_list_get_sort_item(p);
+			btrfs_list_setup_comparer_v2(comps,
+						what_to_sort, order);
+		}
+		opt_arg = NULL;
+	}
+
+	return 0;
+}
+
+static int btrfs_list_parse_filter_string_v2(char *opt_arg,
+					  struct btrfs_list_filter_set_v2 **filters,
+					  enum btrfs_list_filter_enum type)
+{
+
+	u64 arg;
+
+	switch (*(opt_arg++)) {
+	case '+':
+		arg = arg_strtou64(opt_arg);
+		type += 2;
+
+		btrfs_list_setup_filter_v2(filters, type, arg);
+		break;
+	case '-':
+		arg = arg_strtou64(opt_arg);
+		type += 1;
+
+		btrfs_list_setup_filter_v2(filters, type, arg);
+		break;
+	default:
+		opt_arg--;
+		arg = arg_strtou64(opt_arg);
+
+		btrfs_list_setup_filter_v2(filters, type, arg);
+		break;
+	}
+
+	return 0;
+}
+
 /*
  * Naming of options:
  * - uppercase for filters and sort options
@@ -451,8 +1360,8 @@ static const char * const cmd_subvol_list_usage[] = {
 
 static int cmd_subvol_list(int argc, char **argv)
 {
-	struct btrfs_list_filter_set *filter_set;
-	struct btrfs_list_comparer_set *comparer_set;
+	struct btrfs_list_filter_set_v2 *filter_set;
+	struct btrfs_list_comparer_set_v2 *comparer_set;
 	u64 flags = 0;
 	int fd = -1;
 	u64 top_id;
@@ -463,8 +1372,8 @@ static int cmd_subvol_list(int argc, char **argv)
 	DIR *dirstream = NULL;
 	enum btrfs_list_layout layout = BTRFS_LIST_LAYOUT_DEFAULT;
 
-	filter_set = btrfs_list_alloc_filter_set();
-	comparer_set = btrfs_list_alloc_comparer_set();
+	filter_set = btrfs_list_alloc_filter_set_v2();
+	comparer_set = btrfs_list_alloc_comparer_set_v2();
 
 	while(1) {
 		int c;
@@ -480,21 +1389,21 @@ static int cmd_subvol_list(int argc, char **argv)
 
 		switch(c) {
 		case 'p':
-			btrfs_list_setup_print_column(BTRFS_LIST_PARENT);
+			btrfs_list_setup_print_column_v2(BTRFS_LIST_PARENT);
 			break;
 		case 'a':
 			is_list_all = 1;
 			break;
 		case 'c':
-			btrfs_list_setup_print_column(BTRFS_LIST_OGENERATION);
+			btrfs_list_setup_print_column_v2(BTRFS_LIST_OGENERATION);
 			break;
 		case 'd':
-			btrfs_list_setup_filter(&filter_set,
+			btrfs_list_setup_filter_v2(&filter_set,
 						BTRFS_LIST_FILTER_DELETED,
 						0);
 			break;
 		case 'g':
-			btrfs_list_setup_print_column(BTRFS_LIST_GENERATION);
+			btrfs_list_setup_print_column_v2(BTRFS_LIST_GENERATION);
 			break;
 		case 'o':
 			is_only_in_path = 1;
@@ -503,27 +1412,27 @@ static int cmd_subvol_list(int argc, char **argv)
 			layout = BTRFS_LIST_LAYOUT_TABLE;
 			break;
 		case 's':
-			btrfs_list_setup_filter(&filter_set,
+			btrfs_list_setup_filter_v2(&filter_set,
 						BTRFS_LIST_FILTER_SNAPSHOT_ONLY,
 						0);
-			btrfs_list_setup_print_column(BTRFS_LIST_OGENERATION);
-			btrfs_list_setup_print_column(BTRFS_LIST_OTIME);
+			btrfs_list_setup_print_column_v2(BTRFS_LIST_OGENERATION);
+			btrfs_list_setup_print_column_v2(BTRFS_LIST_OTIME);
 			break;
 		case 'u':
-			btrfs_list_setup_print_column(BTRFS_LIST_UUID);
+			btrfs_list_setup_print_column_v2(BTRFS_LIST_UUID);
 			break;
 		case 'q':
-			btrfs_list_setup_print_column(BTRFS_LIST_PUUID);
+			btrfs_list_setup_print_column_v2(BTRFS_LIST_PUUID);
 			break;
 		case 'R':
-			btrfs_list_setup_print_column(BTRFS_LIST_RUUID);
+			btrfs_list_setup_print_column_v2(BTRFS_LIST_RUUID);
 			break;
 		case 'r':
 			flags |= BTRFS_ROOT_SUBVOL_RDONLY;
 			break;
 		case 'G':
-			btrfs_list_setup_print_column(BTRFS_LIST_GENERATION);
-			ret = btrfs_list_parse_filter_string(optarg,
+			btrfs_list_setup_print_column_v2(BTRFS_LIST_GENERATION);
+			ret = btrfs_list_parse_filter_string_v2(optarg,
 							&filter_set,
 							BTRFS_LIST_FILTER_GEN);
 			if (ret) {
@@ -533,8 +1442,8 @@ static int cmd_subvol_list(int argc, char **argv)
 			break;
 
 		case 'C':
-			btrfs_list_setup_print_column(BTRFS_LIST_OGENERATION);
-			ret = btrfs_list_parse_filter_string(optarg,
+			btrfs_list_setup_print_column_v2(BTRFS_LIST_OGENERATION);
+			ret = btrfs_list_parse_filter_string_v2(optarg,
 							&filter_set,
 							BTRFS_LIST_FILTER_CGEN);
 			if (ret) {
@@ -543,7 +1452,7 @@ static int cmd_subvol_list(int argc, char **argv)
 			}
 			break;
 		case 'S':
-			ret = btrfs_list_parse_sort_string(optarg,
+			ret = btrfs_list_parse_sort_string_v2(optarg,
 							   &comparer_set);
 			if (ret) {
 				uerr = 1;
@@ -571,7 +1480,7 @@ static int cmd_subvol_list(int argc, char **argv)
 	}
 
 	if (flags)
-		btrfs_list_setup_filter(&filter_set, BTRFS_LIST_FILTER_FLAGS,
+		btrfs_list_setup_filter_v2(&filter_set, BTRFS_LIST_FILTER_FLAGS,
 					flags);
 
 	ret = btrfs_list_get_path_rootid(fd, &top_id);
@@ -579,21 +1488,21 @@ static int cmd_subvol_list(int argc, char **argv)
 		goto out;
 
 	if (is_list_all)
-		btrfs_list_setup_filter(&filter_set,
+		btrfs_list_setup_filter_v2(&filter_set,
 					BTRFS_LIST_FILTER_FULL_PATH,
 					top_id);
 	else if (is_only_in_path)
-		btrfs_list_setup_filter(&filter_set,
+		btrfs_list_setup_filter_v2(&filter_set,
 					BTRFS_LIST_FILTER_TOPID_EQUAL,
 					top_id);
 
 	/* by default we shall print the following columns*/
-	btrfs_list_setup_print_column(BTRFS_LIST_OBJECTID);
-	btrfs_list_setup_print_column(BTRFS_LIST_GENERATION);
-	btrfs_list_setup_print_column(BTRFS_LIST_TOP_LEVEL);
-	btrfs_list_setup_print_column(BTRFS_LIST_PATH);
+	btrfs_list_setup_print_column_v2(BTRFS_LIST_OBJECTID);
+	btrfs_list_setup_print_column_v2(BTRFS_LIST_GENERATION);
+	btrfs_list_setup_print_column_v2(BTRFS_LIST_TOP_LEVEL);
+	btrfs_list_setup_print_column_v2(BTRFS_LIST_PATH);
 
-	ret = btrfs_list_subvols_print(fd, filter_set, comparer_set,
+	ret = btrfs_list_subvols_print_v2(fd, filter_set, comparer_set,
 			layout, !is_list_all && !is_only_in_path, NULL);
 
 out:
Signature · 1 line
2.14.4
#8 lore raw

PATCH v2 8/20 btrfs-progs: sub list: factor out main part of btrfs_list_subvols

In reply to Misono Tomohiro

No functional changes.
This is a preparation work for reworking "subvolume list".

---
 cmds-subvolume.c | 50 ++++++++++++++++++++++++++++++--------------------
 1 file changed, 30 insertions(+), 20 deletions(-)
cmds-subvolume.c +30 -20
index c54a8003..279a6e51 100644
@@ -1125,22 +1125,15 @@ out:
 	return subvols;
 }
 
-static struct subvol_list *btrfs_list_subvols(int fd,
-					      struct btrfs_list_filter_set_v2 *filter_set)
+static void get_subvols_info(struct subvol_list **subvols,
+			     struct btrfs_list_filter_set_v2 *filter_set,
+			     int fd,
+			     size_t *capacity)
 {
-	struct subvol_list *subvols;
-	size_t capacity = 0;
 	struct btrfs_util_subvolume_iterator *iter;
 	enum btrfs_util_error err;
 	int ret = -1;
 
-	subvols = malloc(sizeof(*subvols));
-	if (!subvols) {
-		error("out of memory");
-		return NULL;
-	}
-	subvols->num = 0;
-
 	err = btrfs_util_create_subvolume_iterator_fd(fd,
 						      BTRFS_FS_TREE_OBJECTID, 0,
 						      &iter);
@@ -1168,11 +1161,11 @@ static struct subvol_list *btrfs_list_subvols(int fd,
 			continue;
 		}
 
-		if (subvols->num >= capacity) {
+		if ((*subvols)->num >= *capacity) {
 			struct subvol_list *new_subvols;
-			size_t new_capacity = max_t(size_t, 1, capacity * 2);
+			size_t new_capacity = max_t(size_t, 1, *capacity * 2);
 
-			new_subvols = realloc(subvols,
+			new_subvols = realloc(*subvols,
 					      sizeof(*new_subvols) +
 					      new_capacity *
 					      sizeof(new_subvols->subvols[0]));
@@ -1181,12 +1174,12 @@ static struct subvol_list *btrfs_list_subvols(int fd,
 				goto out;
 			}
 
-			subvols = new_subvols;
-			capacity = new_capacity;
+			*subvols = new_subvols;
+			*capacity = new_capacity;
 		}
 
-		subvols->subvols[subvols->num] = subvol;
-		subvols->num++;
+		(*subvols)->subvols[(*subvols)->num] = subvol;
+		(*subvols)->num++;
 	}
 
 	ret = 0;
@@ -1194,9 +1187,26 @@ out:
 	if (iter)
 		btrfs_util_destroy_subvolume_iterator(iter);
 	if (ret) {
-		free_subvol_list(subvols);
-		subvols = NULL;
+		free_subvol_list(*subvols);
+		*subvols = NULL;
+	}
+}
+
+static struct subvol_list *btrfs_list_subvols(int fd,
+					      struct btrfs_list_filter_set_v2 *filter_set)
+{
+	struct subvol_list *subvols;
+	size_t capacity = 0;
+
+	subvols = malloc(sizeof(*subvols));
+	if (!subvols) {
+		error("out of memory");
+		return NULL;
 	}
+	subvols->num = 0;
+
+	get_subvols_info(&subvols, filter_set, fd, &capacity);
+
 	return subvols;
 }
 
Signature · 1 line
2.14.4
#9 lore raw

PATCH v2 9/20 btrfs-progs: sub list: Change the default behavior of "subvolume list" and allow non-privileged user to call it

In reply to Misono Tomohiro

Change the default behavior of "subvolume list" and allow non-privileged user to call it as well.

Quoted 1 line

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_SUBVOL_ROOTREF and BTRFS_IOC_INO_LOOKUP_USER, which are avilable from 4.18),
- the specified path can be non-subvolume directory.
- non-privileged user can also call it (subvolumes which the user does not have access right will be skiped).

Note that root user can list all the subvolume in the fs with -a option.

[Example]

 $ mkfs.btrfs -f $DEV
 $ mount $DEV /mnt
 $ btrfs subvolume create /mnt/AAA
 $ btrfs subvolume create /mnt/AAA/BBB
 $ mkdir /mnt/AAA/BBB/dir
 $ btrfs subvolume create /mnt/AAA/BBB/dir/CCC
 $ btrfs subvolume create /mnt/ZZZ
 $ umount /mnt
 $ mount -o subvol=AAA $DEV /mnt
 $ btrfs subvolume list /mnt
 ID 256 gen 11 top level 5 path .
 ID 257 gen 8 top level 256 path BBB
 ID 258 gen 8 top level 257 path BBB/dir/CCC
 $ btrfs subvolume list /mnt/BBB
 ID 257 gen 8 top level 256 path .
 ID 258 gen 8 top level 257 path dir/CCC
 $ btrfs subvolume list /mnt/BBB/dir
 ID 258 gen 8 top level 257 path CCC

** output of progs <= 4.17

 $ mount -o subvol=AAA $DEV /mnt
 $ btrfs subvolume list /mnt
 ID 256 gen 11 top level 5 path AAA
 ID 257 gen 8 top level 256 path BBB
 ID 258 gen 8 top level 257 path BBB/dir/CCC
 ID 259 gen 11 top level 256 path ZZZ
---
 Documentation/btrfs-subvolume.asciidoc |   8 +-
 cmds-subvolume.c                       | 144 +++++++++++++++++++++++++--------
 2 files changed, 119 insertions(+), 33 deletions(-)
Documentation/btrfs-subvolume.asciidoc +7 -1
index f3eb4e26..99fff977 100644
@@ -95,6 +95,12 @@ The output format is similar to *subvolume list* command.
 
 *list* [options] [-G [\+|-]<value>] [-C [+|-]<value>] [--sort=rootid,gen,ogen,path] <path>::
 List the subvolumes present in the filesystem <path>.
+By default, this only lists the subvolumes under <path>,
+including <path> itself (except top-level subvolume).
++
+This command had required root privileges. From kernel 4.18,
+non privileged user can call this too. Also from kernel 4.18,
+It is possible to specify non-subvolume directory as <path>.
 +
 For every subvolume the following information is shown by default:
 +
@@ -102,7 +108,7 @@ ID <ID> gen <generation> top level <ID> path <path>
 +
 where ID is subvolume's id, gen is an internal counter which is updated
 every transaction, top level is the same as parent subvolume's id, and
-path is the relative path of the subvolume to the top level subvolume.
+path is the relative path of the subvolume to the specified path.
 The subvolume's ID may be used by the subvolume set-default command,
 or at mount time via the subvolid= option.
 +
cmds-subvolume.c +112 -32

231 lines not on this page · raw message

Signature · 1 line
2.14.4
#10 lore raw

PATCH v2 10/20 btrfs-progs: sub list: Add -A option to output path in absolute path

In reply to Misono Tomohiro

By default, the printed path is relative to the specified path. Let's add option to print absolute path with -A option.

[Example]

 $ mkfs.btrfs -f $DEV
 $ mount $DEV /mnt
 $ btrfs subvolume create /mnt/AAA
 $ btrfs subvolume list /mnt
 ID 256 gen 6 top level 5 path AAA
 $ btrfs subvolume list -A /mnt
 ID 256 gen 6 top level 5 path /mnt/AAA
---
 Documentation/btrfs-subvolume.asciidoc |  2 +
 cmds-subvolume.c                       | 73 +++++++++++++++++++++++++++++-----
 2 files changed, 66 insertions(+), 9 deletions(-)
Documentation/btrfs-subvolume.asciidoc +2 -0
index 99fff977..fec4b769 100644
@@ -148,6 +148,8 @@ list deleted subvolumes that are not yet cleaned.
 Other;;
 -t::::
 print the result as a table.
+-A::::
+print path in absolute path.
 
 Sorting;;
 -G [+|-]<value>::::
cmds-subvolume.c +64 -9

150 lines not on this page · raw message

Signature · 1 line
2.14.4
#11 lore raw

PATCH v2 11/20 btrfs-progs: sub list: Add -f option to follow mounted subvolumes below the path

In reply to Misono Tomohiro

Add -f option to follow mounted subvolumes below the specified path, only if it is the same filesystem.

[Example]

 $ mkfs.btrfs -f $DEV
 $ mkfs.btrfs -f $DEV2
 $ mount $DEV /mnt
 $ btrfs subvolume create /mnt/AAA
 $ btrfs subvolume create /mnt/BBB
 $ btrfs subvolume create /mnt/CCC
 $ mkdir /mnt/AAA/bbb
 $ mkdir /mnt/AAA/ccc
 $ mkdir /mnt/AAA/other
 $ umount /mnt
 $ mount -o subvol=AAA $DEV /mnt
 $ mount -o subvol=BBB $DEV /mnt/bbb
 $ mount -o subvol=CCC $DEV /mnt/ccc
 $ mount -o $DEV2 /mnt/other
 $ btrfs subvolume list /mnt
 ID 256 gen 9 top level 5 path .
 $ btrfs subvolume list -f /mnt
 ID 256 gen 9 top level 5 path .
 ID 258 gen 7 top level 5 path bbb
 ID 259 gen 8 top level 5 path ccc

Note that this option lists top-level subvolume if it is mounted in the way.

---
 Documentation/btrfs-subvolume.asciidoc |   4 ++
 cmds-subvolume.c                       | 116 +++++++++++++++++++++++++++++++--
 2 files changed, 113 insertions(+), 7 deletions(-)
Documentation/btrfs-subvolume.asciidoc +4 -0
index fec4b769..b2461398 100644
@@ -120,6 +120,10 @@ print only subvolumes below specified <path>.
 -a::::
 print all the subvolumes in the filesystem and distinguish between
 absolute and relative path with respect to the given <path>.
+-f::::
+follow mounted subvolumes below <path> recursively and list them too
+(only if it is the same filesystem). If top-level subvolume is mounted
+in the way, it is also listed.
 
 Field selection;;
 -p::::
cmds-subvolume.c +109 -7

201 lines not on this page · raw message

Signature · 1 line
2.14.4
#12 lore raw

PATCH v2 12/20 btrfs-progs: sub list: Add --nosort option to output incrementally without sort

In reply to Misono Tomohiro

Currently, "subvolume list" loads all the subvolume information into memory first in order to sort them. This may cause a performance problem if there are a lot of subvolumes.

This commit adds --nosort option to output subvolume information incrementally without sort to avoid consuming memory.

---
 Documentation/btrfs-subvolume.asciidoc |   5 ++
 cmds-subvolume.c                       | 140 ++++++++++++++++++++++-----------
 2 files changed, 100 insertions(+), 45 deletions(-)
Documentation/btrfs-subvolume.asciidoc +5 -0
index b2461398..cd91385a 100644
@@ -170,6 +170,11 @@ you can add \'\+' or \'-' in front of each items, \'+' means ascending,
 +
 for --sort you can combine some items together by \',', just like
 --sort=+ogen,-gen,path,rootid.
++
+--nosort::::
+Output the results incrementally without sort. This avoids loading all
+subvolume information to memory and can be useful when there is a lot
+of subvolumes.
 
 *set-default* [<subvolume>|<id> <path>]::
 Set the default subvolume for the (mounted) filesystem.
cmds-subvolume.c +95 -45

292 lines not on this page · raw message

Signature · 1 line
2.14.4
#13 lore raw

PATCH v2 13/20 btrfs-progs: sub list: Update -a option and remove meaningless filter

In reply to Misono Tomohiro

Currently, -a option add filter and change subvolume path as follows:
- If a subvolume is a child of the specified path, nothing changes
- otherwise, adds <FS_TREE> to head

This is rather meaningless, so let's remove this filter.

As a result, the behavior of -a option becomes the same as default behavior of sub list in progs <= 4.17

[Example]

 $ mkfs.btrfs -f $DEV
 $ mount $DEV /mnt
 $ btrfs subvolume create /mnt/AAA
 $ btrfs subvolume create /mnt/AAA/BBB
 $ btrfs subvolume create /mnt/ZZZ
 $ btrfs subvolume list -a /mnt
 ID 256 gen 9 top level 5 path AAA
 ID 257 gen 9 top level 256 path AAA/BBB
 ID 258 gen 10 top level 5 path ZZZ

** output of progs <= 4.17

 $ btrfs subvolume list -a /mnt
 ID 256 gen 9 top level 5 path AAA
 ID 257 gen 9 top level 256 path <FS_TREE>/AAA/BBB
 ID 258 gen 10 top level 5 path ZZZ
---
 Documentation/btrfs-subvolume.asciidoc |  6 ++++--
 cmds-subvolume.c                       | 35 ++++------------------------------
 2 files changed, 8 insertions(+), 33 deletions(-)
Documentation/btrfs-subvolume.asciidoc +4 -2
index cd91385a..20fae1e1 100644
@@ -118,8 +118,10 @@ Path filtering;;
 -o::::
 print only subvolumes below specified <path>.
 -a::::
-print all the subvolumes in the filesystem and distinguish between
-absolute and relative path with respect to the given <path>.
+print all the subvolumes in the filesystem, including subvolumes
+which cannot be accessed from current mount point.
+path to be shown is relative to the top-level subvolume
+(require root privileges).
 -f::::
 follow mounted subvolumes below <path> recursively and list them too
 (only if it is the same filesystem). If top-level subvolume is mounted
cmds-subvolume.c +4 -31
index 802f5c5e..dab266aa 100644
@@ -759,28 +759,6 @@ static int filter_topid_equal(struct listed_subvol *subvol, uint64_t data)
 	return subvol->info.parent_id == data;
 }
 
-static int filter_full_path(struct listed_subvol *subvol, uint64_t data)
-{
-	/*
-	 * This implements the same behavior as before the conversion to
-	 * libbtrfsutil, which is mostly nonsensical.
-	 */
-	if (subvol->info.parent_id != data) {
-		char *tmp;
-		int ret;
-
-		ret = asprintf(&tmp, "<FS_TREE>/%s", subvol->path);
-		if (ret == -1) {
-			error("out of memory");
-			exit(1);
-		}
-
-		free(subvol->path);
-		subvol->path = tmp;
-	}
-	return 1;
-}
-
 static int filter_by_parent(struct listed_subvol *subvol, uint64_t data)
 {
 	return !uuid_compare(subvol->info.parent_uuid,
@@ -798,7 +776,6 @@ static btrfs_list_filter_func_v2 all_filter_funcs[] = {
 	[BTRFS_LIST_FILTER_CGEN_LESS]		= filter_cgen_less,
 	[BTRFS_LIST_FILTER_CGEN_EQUAL]          = filter_cgen_equal,
 	[BTRFS_LIST_FILTER_TOPID_EQUAL]		= filter_topid_equal,
-	[BTRFS_LIST_FILTER_FULL_PATH]		= filter_full_path,
 	[BTRFS_LIST_FILTER_BY_PARENT]		= filter_by_parent,
 };
 
@@ -1574,9 +1551,9 @@ static const char * const cmd_subvol_list_usage[] = {
 	"",
 	"Path filtering:",
 	"-o           print only subvolumes below specified path",
-	"-a           print all the subvolumes in the filesystem and",
-	"             distinguish absolute and relative path with respect",
-	"             to the given <path> (require root privileges)",
+	"-a           print all the subvolumes in the filesystem.",
+	"             path to be shown is relative to the top-level",
+	"             subvolume (require root privileges)",
 	"-f           follow mounted subvolumes below the specified path",
 	"             and list them too (only if it is the same filesystem)",
 	"",
@@ -1783,11 +1760,7 @@ static int cmd_subvol_list(int argc, char **argv)
 	if (ret)
 		goto out;
 
-	if (is_list_all)
-		btrfs_list_setup_filter_v2(&filter_set,
-					BTRFS_LIST_FILTER_FULL_PATH,
-					top_id);
-	else if (is_only_in_path)
+	if (is_only_in_path)
 		btrfs_list_setup_filter_v2(&filter_set,
 					BTRFS_LIST_FILTER_TOPID_EQUAL,
 					top_id);
Signature · 1 line
2.14.4
#14 lore raw

PATCH v2 14/20 btrfs-progs: sub list: Update help message of -o option

In reply to Misono Tomohiro

Currently "sub list -o" lists only child subvolumes of the specified path. So, update help message and variable name more appropriately.

---
 Documentation/btrfs-subvolume.asciidoc |  2 +-
 cmds-subvolume.c                       | 10 +++++-----
 2 files changed, 6 insertions(+), 6 deletions(-)
Documentation/btrfs-subvolume.asciidoc +1 -1
index 20fae1e1..0381c92c 100644
@@ -116,7 +116,7 @@ or at mount time via the subvolid= option.
 +
 Path filtering;;
 -o::::
-print only subvolumes below specified <path>.
+print only subvolumes which the subvolume of <path> contains.
 -a::::
 print all the subvolumes in the filesystem, including subvolumes
 which cannot be accessed from current mount point.
cmds-subvolume.c +5 -5
index dab266aa..552c6dea 100644
@@ -1550,7 +1550,7 @@ static const char * const cmd_subvol_list_usage[] = {
 	"It is possible to specify non-subvolume directory as <path>.",
 	"",
 	"Path filtering:",
-	"-o           print only subvolumes below specified path",
+	"-o           print only subvolumes which the subvolume of <path> contains",
 	"-a           print all the subvolumes in the filesystem.",
 	"             path to be shown is relative to the top-level",
 	"             subvolume (require root privileges)",
@@ -1605,7 +1605,7 @@ static int cmd_subvol_list(int argc, char **argv)
 	int follow_mount = 0;
 	int sort = 0;
 	int no_sort = 0;
-	int is_only_in_path = 0;
+	int is_only_child = 0;
 	int absolute_path = 0;
 	DIR *dirstream = NULL;
 	enum btrfs_list_layout layout = BTRFS_LIST_LAYOUT_DEFAULT;
@@ -1651,7 +1651,7 @@ static int cmd_subvol_list(int argc, char **argv)
 			btrfs_list_setup_print_column_v2(BTRFS_LIST_GENERATION);
 			break;
 		case 'o':
-			is_only_in_path = 1;
+			is_only_child = 1;
 			break;
 		case 't':
 			layout = BTRFS_LIST_LAYOUT_TABLE;
@@ -1732,7 +1732,7 @@ static int cmd_subvol_list(int argc, char **argv)
 		goto out;
 	}
 
-	if (follow_mount && (is_list_all || is_only_in_path)) {
+	if (follow_mount && (is_list_all || is_only_child)) {
 		ret = -1;
 		error("cannot use -f with -a or -o option");
 		goto out;
@@ -1760,7 +1760,7 @@ static int cmd_subvol_list(int argc, char **argv)
 	if (ret)
 		goto out;
 
-	if (is_only_in_path)
+	if (is_only_child)
 		btrfs_list_setup_filter_v2(&filter_set,
 					BTRFS_LIST_FILTER_TOPID_EQUAL,
 					top_id);
Signature · 1 line
2.14.4
#15 lore raw

PATCH v2 15/20 btrfs-progs: sub list: Update help message of -d option

In reply to Misono Tomohiro

Explicitly states that -d requires root privileges. Also, update some option handling with regard to -d option.

---
 Documentation/btrfs-subvolume.asciidoc | 3 ++-
 cmds-subvolume.c                       | 8 ++++++++
 2 files changed, 10 insertions(+), 1 deletion(-)
Documentation/btrfs-subvolume.asciidoc +2 -1
index 0381c92c..2db1d479 100644
@@ -149,7 +149,8 @@ only snapshot subvolumes in the filesystem will be listed.
 -r::::
 only readonly subvolumes in the filesystem will be listed.
 -d::::
-list deleted subvolumes that are not yet cleaned.
+list deleted subvolumes that are not yet cleaned
+(require root privileges).
 
 Other;;
 -t::::
cmds-subvolume.c +8 -0
index 552c6dea..ef39789a 100644
@@ -1569,6 +1569,7 @@ static const char * const cmd_subvol_list_usage[] = {
 	"-s           list only snapshots",
 	"-r           list readonly subvolumes (including snapshots)",
 	"-d           list deleted subvolumes that are not yet cleaned",
+	"             (require root privileges)",
 	"",
 	"Other:",
 	"-t           print the result as a table",
@@ -1744,6 +1745,13 @@ static int cmd_subvol_list(int argc, char **argv)
 		goto out;
 	}
 
+	if (filter_set->only_deleted &&
+			(is_list_all || absolute_path || follow_mount)) {
+		ret = -1;
+		error("cannot use -d with -a/f/A option");
+		goto out;
+	}
+
 	subvol = argv[optind];
 	fd = btrfs_open_dir(subvol, &dirstream, 1);
 	if (fd < 0) {
Signature · 1 line
2.14.4
#16 lore raw

PATCH v2 16/20 btrfs-progs: utils: Fallback to open without O_NOATIME flag in find_mount_root():

In reply to Misono Tomohiro

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".

---
 utils.c | 3 +++
 1 file changed, 3 insertions(+)
utils.c +3 -0
index 21de09d3..1a4366e8 100644
@@ -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);
Signature · 1 line
2.14.4
#17 lore raw

PATCH v2 17/20 btrfs-progs: sub show: Allow non-privileged user to call "subvolume show"

In reply to Misono Tomohiro

Allow non-privileged user to call subvolume show if new ioctls (BTRFS_IOC_GET_SUBVOL_INFO/BTRFS_IOC_GET_SUBVOL_ROOTREF, BTRFS_IOC_INO_LOOKUP_USER, from kernel 4.18) are available. Non-privileged user still cannot use -r or -u option.

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 absolute path
list snapshots under the mountpoint
(only to which the user has appropriate access right)

[Example]

 $ sudo mkfs.btrfs -f $DEV
 $ sudo mount $DEV /mnt
 $ sudo btrfs subvolume create /mnt/AAA
 $ sudo btrfs subvolume snapshot /mnt/AAA /mnt/snap1
 $ sudo btrfs subvolume snapshot /mnt/AAA /mnt/AAA/snap2
 $ sudo umount /mnt
 $ sudo mount -o subvol=AAA $DEV /mnt
 # root
 $ sudo btrfs subvolume show /mnt
 AAA
      Name:        AAA
      UUID:        15e80697-2ffb-0b4b-8e1e-e0873a7cf944
      ...
      Snapshot(s):
                   AAA/snap2
                   snap1
 # non-privileged user
 $ btrfs subvolume show /mnt
 /mnt
      Name:        AAA
      UUID:        15e80697-2ffb-0b4b-8e1e-e0873a7cf944
      ...
      Snapshot(s):
                   /mnt/snap2
---
 Documentation/btrfs-subvolume.asciidoc |  11 +++-
 cmds-subvolume.c                       | 107 +++++++++++++++++++++++++++++----
 2 files changed, 105 insertions(+), 13 deletions(-)
Documentation/btrfs-subvolume.asciidoc +9 -2
index 2db1d479..fc57e6bc 100644
@@ -194,12 +194,19 @@ The id can be obtained from *btrfs subvolume list*, *btrfs subvolume show* or
 *show* [options] <path>|<mnt>::
 Show information of a given subvolume in the <path>.
 +
+This command had required root privileges. From kernel 4.18,
+non-privileged user can call this unless -r/-u option is not used.
+Note that for root, output path is relative to the top-level subvolume
+while absolute path is shown for non-privileged user.
+Also for root, snapshots filed lists all the snapshots in the fs while
+only snapshots under mount point are shown for non-privileged user.
++
 `Options`
 +
 -r|--rootid::::
-rootid of the subvolume.
+rootid of the subvolume (require root privileges).
 -u|--uuid:::
-UUID of the subvolume.
+UUID of the subvolume (require root privileges).
 
 +
 If no option is specified, subvolume information of <path> is shown,
cmds-subvolume.c +96 -11

170 lines not on this page · raw message

Signature · 1 line
2.14.4
#18 lore raw

PATCH v2 18/20 btrfs-progs: test: Add helper function to check if test user exists

In reply to Misono Tomohiro

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.

---
 tests/common | 10 ++++++++++
 1 file changed, 10 insertions(+)
tests/common +10 -0
index 7e4e09df..7a37a4cd 100644
@@ -314,6 +314,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
Signature · 1 line
2.14.4
#19 lore raw

PATCH v2 19/20 btrfs-porgs: test: Add cli-test/009 to check subvolume list for both root and normal user

In reply to Misono Tomohiro
---
 tests/cli-tests/009-subvolume-list/test.sh | 134 +++++++++++++++++++++++++++++
 1 file changed, 134 insertions(+)
 create mode 100755 tests/cli-tests/009-subvolume-list/test.sh
tests/cli-tests/009-subvolume-list/test.sh new file +134 -0
new file mode 100755
index 00000000..bf6c31d6
@@ -0,0 +1,134 @@
+#!/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
+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
+test_list 0 "$TEST_MNT" "256 258 259 260 261 265 266"
+test_list 0 "$TEST_MNT/sub1" "256 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_MNT/sub3"
+test_list 0 "$TEST_MNT/sub4" "265 266"
+run_mustfail "should raise permission error" \
+	sudo -u progs-test "$TOP/btrfs" subvolume list "$TEST_MNT/sub4/dir"
+run_mustfail "should fail for file" \
+	sudo -u progs-test "$TOP/btrfs" subvolume list "$TEST_MNT/file"
+
+test_list 0 "." "256 258 259 260 261 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
Signature · 1 line
2.14.4
#20 lore raw

PATCH v2 20/20 btrfs-progs: test: Add cli-test/010 to check "subvolume list -f" option

In reply to Misono Tomohiro
---
 tests/cli-tests/010-subvolume-list-follow/test.sh | 86 +++++++++++++++++++++++
 1 file changed, 86 insertions(+)
 create mode 100755 tests/cli-tests/010-subvolume-list-follow/test.sh
tests/cli-tests/010-subvolume-list-follow/test.sh new file +86 -0
new file mode 100755
index 00000000..8fb746c6
@@ -0,0 +1,86 @@
+#!/bin/bash
+# test for "subvolume list -f"
+
+source "$TEST_TOP/common"
+
+check_prereq mkfs.btrfs
+check_prereq btrfs
+
+setup_root_helper
+setup_loopdevs 2
+prepare_loopdevs
+dev1=${loopdevs[1]}
+dev2=${loopdevs[2]}
+
+# test if the ids returned by "sub list" match expected ids
+# $1  ... option for subvolume list
+# $2  ... PATH to be specified by sub list command
+# $3~ ... expected return ids
+test_list()
+{
+	result=$(run_check_stdout $SUDO "$TOP/btrfs" subvolume list $1 "$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 "$dev1"
+run_check $SUDO_HELPER "$TOP/mkfs.btrfs" -f "$dev2"
+
+run_check $SUDO_HELPER mount "$dev1" "$TEST_MNT"
+cd "$TEST_MNT"
+
+# Create some subvolumes and directories
+# <FS_TREE> (id 5)
+#   |- AAA (id 256)
+#   |   |- top
+#   |   |- bbb
+#   |   -- ccc
+#   |
+#   |- BBB (id 258)
+#   -- CCC (id 259)
+run_check $SUDO_HELPER "$TOP/btrfs" subvolume create AAA
+run_check $SUDO_HELPER "$TOP/btrfs" subvolume create BBB
+run_check $SUDO_HELPER "$TOP/btrfs" subvolume create CCC
+run_check $SUDO_HELPER mkdir AAA/top
+run_check $SUDO_HELPER mkdir AAA/bbb
+run_check $SUDO_HELPER mkdir AAA/ccc
+
+test_list "" "." "256 258 259"
+test_list "-f" "." "256 258 259"
+cd ..
+run_check $SUDO_HELPER umount "$TEST_MNT"
+
+# Mount as follows:
+#
+# "TEST_MNT" (AAA)
+#   |- top (FS_TREE)
+#   |   |- AAA
+#   |   |- BBB
+#   |   -- CCC
+#   |
+#   |- bbb (BBB)
+#   -- ccc (CCC)
+run_check $SUDO_HELPER mount -o subvol=AAA "$dev1" "$TEST_MNT"
+run_check $SUDO_HELPER mount "$dev1" "$TEST_MNT/top"
+run_check $SUDO_HELPER mount -o subvol=BBB "$dev1" "$TEST_MNT/bbb"
+run_check $SUDO_HELPER mount -o subvol=CCC "$dev1" "$TEST_MNT/ccc"
+
+cd "$TEST_MNT"
+test_list "" "." "256"
+# With -f option, subvolume AAA/BBB/CCC will be counted twice.
+# Also, it will list FS_TREE (5) if it is mounted below the specified path.
+test_list "-f" "." "5 256 256 258 258 259 259"
+
+cd ..
+run_check $SUDO_HELPER umount -R "$TEST_MNT"
+cleanup_loopdevs
Signature · 1 line
2.14.4
#21 lore raw
In reply to Misono Tomohiro

Gentle ping, as this is related to the new ioctls merged in 4.18-rc1.

Quoted 202 lines · Misono Tomohiro

Changelog

v1 -> v2:
generally update whole patch set, especially:
- rebased to progs 4.17
- Improve error handling
- Update man/help/commit message
- Add/Update several options of sub list:
-f ... follow mounted subvolumes
-a ... remove meaningless filter
-A ... print path in absolute path
--nosort ... output results incrementally
Please see below examples
=====
github: https://github.com/t-msn/btrfs-progs/tree/rework-sub-list

Hello,

This series requires some new ioctls which are now in kernel 4.18-rc1.

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 (in both old and new kernel) and some options are newly added. For "subvolume show", root's output is the same as before but there are some difference from non-privileged user's output.

Please see below examples.

* Behavior summary of new "sub list"
- default (no option)
- lists subvolumes below the specified path (inc. path itself)
- If new ioctls exists
- the path can be non-subvolume directory
- non-privileged user can call it
(subvolumes to which the user cannot access will be skipped)

- -f
- follow mounted subvolume below the specified path and list them too (only if it is the same filesystem)

- -a
- updated to remove filter. i.e. the output is the same as current progs without option (require root privileges)

- -A
- print path in absolute path

-- nosort
- output results incrementally without loading information to memory

[Example]

  $ mkfs.btrfs -f $DEV
  $ mkfs.btrfs -f $DEV2
  $ mount $DEV $MNT
  $ btrfs subvolume create $MNT/AAA
  $ btrfs subvolume create $MNT/BBB
  $ btrfs subvolume create $MNT/CCC
  $ btrfs subvolume create $MNT/DDD
  $ mkdir $MNT/AAA/bbb
  $ mkdir $MNT/AAA/ccc
  $ mkdir $MNT/AAA/other
  $ umount $MNT
  $ mount -o subvol=AAA $DEV $MNT
  $ mount -o subvol=BBB $DEV $MNT/bbb
  $ mount -o subvol=CCC $DEV $MNT/ccc
  $ mount -o $DEV2 $MNT/other
  $ btrfs subvolume list $MNT # print subvolumes below the path
  ID 256 gen 10 top level 5 path .
  $ btrfs subvolume list -A $MNT # print path in absolute path
  ID 256 gen 10 top level 5 path /mnt
  $ btrfs subvolume list -f $MNT # follow mounted subvolumes too
  ID 256 gen 10 top level 5 path .
  ID 258 gen 7 top level 5 path bbb
  ID 259 gen 8 top level 5 path ccc
  $ btrfs subvolume list -a $MNT
  # print all subvolumes in the fs. same output as progs<=4.17 without option
  ID 256 gen 10 top level 5 path AAA
  ID 258 gen 7 top level 5 path BBB
  ID 259 gen 8 top level 5 path CCC
  ID 260 gen 9 top level 5 path DDD

More details are in each commit log.

* Behavior summary of new "sub show"
- No change for root's output
- If new ioctls exists, non-privileged user can call it
- In that case, path to be shown is absolute path (for root, it is relative to top-level subvolume) Also, snapshots to be shown are to which the user can access from current mount point.
(for root, all snapshots in the fs)

* Patch structure
The first several patches update libbtrfsutil and the latter patches update sub list/show command.

1st patch is independent and updates man doc of btrfs-subvolume

2nd-6th 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 for non-privileged user.

- Code path of root and non-privileged user is different. While root uses TREE_SEARCH ioctl as before, non-privileged user uses newly added ioctls. However, There is only one exception and when subvolume iterator is created from non-subvolume directory, code path of both is the same (and thus both use new ioctls).

7th patch update the "sub list" to use libbtrfsutil (no behavior change yet)
This is a copy of non-merged following patch originally written by Omar Sandoval:
btrfs-progs: use libbtrfsutil for subvolume list [1]
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.)

8th-15th patch update the behavior of "sub list"

16th-17th patch update the behavior of "sub show"

18th-20th patch are 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:
- Remove obsolete field (i.e. top-level) from output

Any comments are welcome.
Thanks,
Tomohiro Misono

[1] https://www.spinics.net/lists/linux-btrfs/msg74917.html

Misono Tomohiro (20):
btrfs-progs: doc: Update man btrfs subvolume
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: Relax the privileges of subvolume iterator
btrfs-progs: sub list: Use libbtrfsuitl for subvolume list
btrfs-progs: sub list: factor out main part of btrfs_list_subvols
btrfs-progs: sub list: Change the default behavior of "subvolume list"
and allow non-privileged user to call it
btrfs-progs: sub list: Add -A option to output path in absolute path
btrfs-progs: sub list: Add -f option to follow mounted subvolumes
below the path
btrfs-progs: sub list: Add --nosort option to output incrementally
without sort
btrfs-progs: sub list: Update -a option and remove meaningless filter
btrfs-progs: sub list: Update help message of -o option
btrfs-progs: sub list: Update help message of -d option
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
btrfs-progs: test: Add cli-test/010 to check "subvolume list -f"
option

 Documentation/btrfs-subvolume.asciidoc            |  108 +-
 cmds-subvolume.c                                  | 1372 ++++++++++++++++++++-
 ioctl.h                                           |   99 ++
 libbtrfsutil/btrfs.h                              |   97 ++
 libbtrfsutil/btrfsutil.h                          |   25 +-
 libbtrfsutil/errors.c                             |   10 +
 libbtrfsutil/subvolume.c                          |  494 +++++++-
 tests/cli-tests/009-subvolume-list/test.sh        |  134 ++
 tests/cli-tests/010-subvolume-list-follow/test.sh |   86 ++
 tests/common                                      |   10 +
 utils.c                                           |    3 +
 11 files changed, 2315 insertions(+), 123 deletions(-)
 create mode 100755 tests/cli-tests/009-subvolume-list/test.sh
 create mode 100755 tests/cli-tests/010-subvolume-list-follow/test.sh
#22 lore raw
Quoted 1 line · Misono Tomohiro

Gentle ping, as this is related to the new ioctls merged in 4.18-rc1.

Due to me spending more time than expected on kernel, this patchset will be merged partially or the 4.18 will be deleayed.

#23 lore raw
Quoted 5 lines · David Sterba
Quoted 1 line · Misono Tomohiro

Gentle ping, as this is related to the new ioctls merged in 4.18-rc1.

Due to me spending more time than expected on kernel, this patchset will be merged partially or the 4.18 will be deleayed.

github version is rebased to 4.17.1:
https://github.com/t-msn/btrfs-progs/tree/rework-sub-list

I will take a vacation from tomorrow and my response may be delayed.

Regards,
Misono

#24 lore raw
In reply to Misono Tomohiro

Hi,

so I've merged the first bath to devel. There was quite some whitespace damage and style issues, but I fixed that as I went through the pathes.

I've left out all patches that introduce new options, this is the hard part and single letter options cannot be added just randomly. That's what led to the current state.

The semantics of current options is preserved, with the slight exception to the option -a.

Quoted 3 lines

* Future todo:
If this approach is ok, I'd like to update the output of "sub list" more like:
- Remove obsolete field (i.e. top-level) from output

Yeah the top-level field can be removed from the user output. Current idea of the fields printed by default is: id, generation, type, path. The type is a representation of eg. read-write status, subvolume/snapshot, and maybe received, or if it is default.

The remaining fields are on-demand by other options and the future proof solution is to provide the single option to specify which columns to print.

The inline format, ie. the current one, is not very comprehensive so the table layout should be the default for user.

The path format needs to be selected by a more concise way than just -A and possibly more added options. Eg. --path

* absolute (shorthand abs)
* realative to the current path (pwd)
* toplevel relative (toplevel)

In order to make it possible to distinguish in the output without prior knowledge of which one was selected, I propose this formatting:

* absolute path always starts with / and is really absolute to the /
* relative path to the current directory starts with ./
* toplevel does not start with ./ nor /

There are patches touching the libbtrfsutil code, so I'd like to hear from Omar what he thinks.

What's now in devel should not change and provide base for further patches, so please use it if you're going to implement something from the above.

#25 lore raw
Quoted 4 lines · David Sterba

Hi,

so I've merged the first bath to devel. There was quite some whitespace damage and style issues, but I fixed that as I went through the pathes.

Thanks for taking this.

Quoted 4 lines

I've left out all patches that introduce new options, this is the hard part and single letter options cannot be added just randomly. That's what led to the current state.

Ok, but could you consider to add -f (11th path, or --follow instead of short option) which is useful when btrfs is used for root fs (or please remove cli-tests/010 for the moment from devel branch).

Quoted 3 lines

The semantics of current options is preserved, with the slight exception to the option -a.

Devel branch also excludes 13th patch and behavior of -a is not changed, is it intended? Without the patch, there is no way to get the same result of current "btrfs subvol list" when mount point is not FS_TREE.

Quoted 27 lines
Quoted 4 lines

* Future todo:
If this approach is ok, I'd like to update the output of "sub list" more like:
- Remove obsolete field (i.e. top-level) from output

Yeah the top-level field can be removed from the user output. Current idea of the fields printed by default is: id, generation, type, path. The type is a representation of eg. read-write status, subvolume/snapshot, and maybe received, or if it is default.

The remaining fields are on-demand by other options and the future proof solution is to provide the single option to specify which columns to print.

The inline format, ie. the current one, is not very comprehensive so the table layout should be the default for user.

The path format needs to be selected by a more concise way than just -A and possibly more added options. Eg. --path

* absolute (shorthand abs)
* realative to the current path (pwd)
* toplevel relative (toplevel)

In order to make it possible to distinguish in the output without prior knowledge of which one was selected, I propose this formatting:

Quoted 6 lines

* absolute path always starts with / and is really absolute to the /
* relative path to the current directory starts with ./
* toplevel does not start with ./ nor /

There are patches touching the libbtrfsutil code, so I'd like to hear from Omar what he thinks.

Also, I want to get acknowledgement about 7th patch (as I wrote in cover letter, I'm ok with original version too).

Quoted 4 lines

What's now in devel should not change and provide base for further patches, so please use it if you're going to implement something from the above

Understood. I will consider them if current patches in devel has been settled.

Regards,
Misono