@@ -130,7 +130,7 @@ list [options] [-G [\+|-]<value>] [-C [+|-]<value>] [--sort=rootid,gen,ogen,path
For every subvolume the following information is shown by default:
- ID *ID* gen *generation* top level *parent_ID* path *path*
+ ID *ID* gen *generation* top level *parent_ID* [size (referenced) *rfer* size (exclusive) *excl*] path *path*
where *ID* is subvolume's (root)id, *generation* is an internal counter which is
updated every transaction, *parent_ID* is the same as the parent subvolume's id,
@@ -138,6 +138,11 @@ list [options] [-G [\+|-]<value>] [-C [+|-]<value>] [--sort=rootid,gen,ogen,path
The subvolume's ID may be used by the subvolume set-default command,
or at mount time via the *subvolid=* option.
+ The sizes of a subvolume will only be outputted if qgroup is enabled,
+ as that's the only way to get accurate size of a subvolume.
+ The referenced size is the total bytes of every extent referred by that subvolume.
+ While the exclusive size is total bytes that is exclusive to that subvolume.
+
``Options``
Path filtering:
@@ -163,6 +168,11 @@ list [options] [-G [\+|-]<value>] [-C [+|-]<value>] [--sort=rootid,gen,ogen,path
-q
print the parent UUID of the subvolume
(*parent* here means subvolume of which this subvolume is a snapshot).
+
+ -Q
+ always print the qgroup sizes of the subvolume
+ If qgroup is not enabled, a warning would be outputted and all
+ qgroup sizes would be zero.
-R
print the UUID of the sent subvolume, where the subvolume is the result of a receive operation.
@@ -66,6 +66,7 @@ static const char * const cmd_subvolume_list_usage[] = {
OPTLINE("-g", "print the generation of the subvolume"),
OPTLINE("-u", "print the uuid of subvolumes (and snapshots)"),
OPTLINE("-q", "print the parent uuid of the snapshots"),
+ OPTLINE("-Q", "always print the qgroup size of each subvolume (instead of auto detect)"),
OPTLINE("-R", "print the uuid of the received snapshots"),
"",
"Type filtering:",
@@ -131,6 +132,10 @@ struct root_attr {
/* creation time of this root in sec*/
time_t otime;
+ /* Qgroup accounting. */
+ u64 rfer;
+ u64 excl;
+
u8 uuid[BTRFS_UUID_SIZE];
u8 puuid[BTRFS_UUID_SIZE];
u8 ruuid[BTRFS_UUID_SIZE];
@@ -195,6 +200,8 @@ enum btrfs_list_column_enum {
BTRFS_LIST_PUUID,
BTRFS_LIST_RUUID,
BTRFS_LIST_UUID,
+ BTRFS_LIST_QGROUP_RFER,
+ BTRFS_LIST_QGROUP_EXCL,
BTRFS_LIST_PATH,
BTRFS_LIST_ALL,
};
@@ -286,6 +293,16 @@ static struct {
.column_name = "UUID",
.need_print = 0,
},
+ {
+ .name = "rfer",
+ .column_name = "RFER",
+ .need_print = 0,
+ },
+ {
+ .name = "excl",
+ .column_name = "EXCL",
+ .need_print = 0,
+ },
{
.name = "path",
.column_name = "Path",
@@ -567,6 +584,10 @@ static void update_root_attr(struct root_attr *found,
found->ogen = new->root_offset;
if (new->otime)
found->otime = new->otime;
+ if (new->rfer)
+ found->rfer = new->rfer;
+ if (new->excl)
+ found->excl = new->excl;
if (new->uuid[0])
memcpy(&found->uuid, new->uuid, BTRFS_UUID_SIZE);
if (new->puuid[0])
@@ -574,6 +595,7 @@ static void update_root_attr(struct root_attr *found,
if (new->ruuid[0])
memcpy(&found->ruuid, new->ruuid, BTRFS_UUID_SIZE);
+
}
static int update_root(struct rb_root *root_lookup,
@@ -789,6 +811,88 @@ static int lookup_ino_path(int fd, struct root_info *ri)
return 0;
}
+static int fill_qgroup_attrs(int fd, struct rb_root *root_lookup)
+{
+ struct btrfs_ioctl_search_args args = {
+ .key = {
+ .tree_id = BTRFS_QUOTA_TREE_OBJECTID,
+ .max_type = BTRFS_QGROUP_INFO_KEY,
+ .min_type = BTRFS_QGROUP_INFO_KEY,
+ .max_objectid = (u64)-1,
+ .max_offset = (u64)-1,
+ .max_transid = (u64)-1,
+ .nr_items = 4096,
+ },
+ };
+ struct btrfs_ioctl_search_key *sk = &args.key;
+ int ret;
+
+ while (1) {
+ unsigned long off = 0;
+
+ ret = ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args);
+ if (ret < 0) {
+ if (errno == ENOENT)
+ ret = -ENOTTY;
+ else
+ ret = -errno;
+ goto out;
+ }
+ /* the ioctl returns the number of item it found in nr_items */
+ if (sk->nr_items == 0)
+ goto out;
+
+ for (int i = 0; i < sk->nr_items; i++) {
+ struct root_attr attr = { 0 };
+ struct btrfs_ioctl_search_header *sh;
+ struct btrfs_qgroup_info_item *info;
+ struct btrfs_key key;
+
+ sh = (struct btrfs_ioctl_search_header *)(args.buf + off);
+ off += sizeof(*sh);
+
+ key.objectid = btrfs_search_header_objectid(sh);
+ key.type = btrfs_search_header_type(sh);
+ key.offset = btrfs_search_header_offset(sh);
+
+ /* Skip unrealted items and higher level qgroups. */
+ if (key.type != BTRFS_QGROUP_INFO_KEY ||
+ btrfs_qgroup_level(key.offset))
+ goto next;
+
+ info = (struct btrfs_qgroup_info_item *)(args.buf + off);
+
+ attr.root_id = key.offset;
+ attr.rfer = btrfs_stack_qgroup_info_rfer(info);
+ attr.excl = btrfs_stack_qgroup_info_excl(info);
+ ret = add_root(root_lookup, &attr);
+ if (ret < 0)
+ break;
+next:
+ off += btrfs_search_header_len(sh);
+
+ /*
+ * record the mins in sk so we can make sure the
+ * next search doesn't repeat this root
+ */
+ sk->min_type = key.type;
+ sk->min_offset = key.offset;
+ sk->min_objectid = key.objectid;
+ }
+ sk->nr_items = 4096;
+ /*
+ * this iteration is done, step forward one qgroup for the next
+ * ioctl
+ */
+ if (sk->min_offset < (u64)-1)
+ sk->min_offset++;
+ else
+ break;
+ }
+out:
+ return ret;
+}
+
static int list_subvol_search(int fd, struct rb_root *root_lookup)
{
int ret;
@@ -1157,6 +1261,12 @@ static void print_subvolume_column(struct root_info *subv,
uuid_unparse(attrs->ruuid, uuidparse);
pr_verbose(LOG_DEFAULT, "%-36s", uuidparse);
break;
+ case BTRFS_LIST_QGROUP_RFER:
+ pr_verbose(LOG_DEFAULT, "%llu", attrs->rfer);
+ break;
+ case BTRFS_LIST_QGROUP_EXCL:
+ pr_verbose(LOG_DEFAULT, "%llu", attrs->excl);
+ break;
case BTRFS_LIST_PATH:
BUG_ON(!attrs->full_path);
pr_verbose(LOG_DEFAULT, "%s", attrs->full_path);
@@ -1280,6 +1390,12 @@ static void print_subvol_json_key(struct format_ctx *fctx,
case BTRFS_LIST_UUID:
fmt_print(fctx, column_name, attrs->uuid);
break;
+ case BTRFS_LIST_QGROUP_RFER:
+ fmt_print(fctx, column_name, attrs->rfer);
+ break;
+ case BTRFS_LIST_QGROUP_EXCL:
+ fmt_print(fctx, column_name, attrs->excl);
+ break;
case BTRFS_LIST_PUUID:
fmt_print(fctx, column_name, attrs->puuid);
break;
@@ -1363,6 +1479,7 @@ static int btrfs_list_subvols(int fd, struct rb_root *root_lookup)
{
int ret;
struct rb_node *n;
+ bool qgroup_enabled = false;
ret = list_subvol_search(fd, root_lookup);
if (ret) {
@@ -1370,6 +1487,17 @@ static int btrfs_list_subvols(int fd, struct rb_root *root_lookup)
return ret;
}
+ ret = fill_qgroup_attrs(fd, root_lookup);
+ /*
+ * Qgroup is not enabled but the user is trying to output qgroup info
+ * explicitly, give a warning at least.
+ */
+ if (ret < 0 && (btrfs_list_columns[BTRFS_LIST_QGROUP_RFER].need_print ||
+ btrfs_list_columns[BTRFS_LIST_QGROUP_EXCL].need_print)) {
+ errno = -ret;
+ warning("qgroup output is not available: %m");
+ }
+
/*
* now we have an rbtree full of root_info objects, but we need to fill
* in their path names within the subvol that is referencing each one.
@@ -1379,11 +1507,17 @@ static int btrfs_list_subvols(int fd, struct rb_root *root_lookup)
struct root_info *entry;
entry = to_root_info(n);
+ if (entry->attrs.rfer && entry->attrs.excl)
+ qgroup_enabled = true;
ret = lookup_ino_path(fd, entry);
if (ret && ret != -ENOENT)
return ret;
n = rb_next(n);
}
+ if (qgroup_enabled) {
+ btrfs_list_setup_print_column(BTRFS_LIST_QGROUP_RFER);
+ btrfs_list_setup_print_column(BTRFS_LIST_QGROUP_EXCL);
+ }
return 0;
}
@@ -1568,7 +1702,7 @@ static int cmd_subvolume_list(const struct cmd_struct *cmd, int argc, char **arg
};
c = getopt_long(argc, argv,
- "acdgopqsurRG:C:t", long_options, NULL);
+ "acdgopqQsurRG:C:t", long_options, NULL);
if (c < 0)
break;
@@ -1609,6 +1743,10 @@ static int cmd_subvolume_list(const struct cmd_struct *cmd, int argc, char **arg
case 'q':
btrfs_list_setup_print_column(BTRFS_LIST_PUUID);
break;
+ case 'Q':
+ btrfs_list_setup_print_column(BTRFS_LIST_QGROUP_RFER);
+ btrfs_list_setup_print_column(BTRFS_LIST_QGROUP_EXCL);
+ break;
case 'R':
btrfs_list_setup_print_column(BTRFS_LIST_RUUID);
break;
Inspired by ZFS `zpool list`, which would output the sizes of each subvolume, we can do better for out "btrfs subvolume list" UI/UX. This patch would introduce the auto-detection and auto-output using qgroup sizes for subvolumes. The output would look like this: # ./btrfs subv list -t /mnt/btrfs/ ID gen top level rfer excl path -- --- --------- ---- ---- ---- 256 11 5 1064960 1064960 subvol1 257 11 5 4210688 4210688 subvol2 Unfortunately there would be some pitfalls: - No output for subvolume 5 (fs tree) As we do not output subvolume 5 (fs tree) at all, thus if the end user wants the size of fs tree, they would still be upset. - If qgroup is not enabled, the sizes would be omitted This may lead to different outputs for different use cases and can lead to some confusion. - Over simplified column name As a developer get too used to qgroup, I don't have any better names for the sizes right now. - Table output can easily be screwed up by the larger sizes This requires some update to the table output to know the possible longest values to adjust. Which is definitely worthy some other patchsets to address. Signed-off-by: Qu Wenruo <wqu@suse.com> --- Documentation/btrfs-subvolume.rst | 12 ++- cmds/subvolume-list.c | 140 +++++++++++++++++++++++++++++- 2 files changed, 150 insertions(+), 2 deletions(-)