btrfs-progs: qgroup: add path to show output

The 'btrfs qgroup show' command currently only prints qgroup IDs,
forcing the user to resolve which subvolume each corresponds to.

Adds subvolume path resolution to 'qgroup show' so that when
the -P option is used, the last column contains the pathname of
the root of the subvolume it describes.  In the case of nested
qgroups, it will show the number of member qgroups or the paths
of the members if the -v option is used.

Path can also be used as a sort parameter.

Sample output:

qgroupid         rfer         excl       path
--------         ----         ----       ----
0/5          16.00KiB     16.00KiB   <FS_ROOT>
0/256        16.00KiB     16.00KiB   <FS_ROOT>/subv1
0/257        16.00KiB     16.00KiB   <missing>
0/258        16.00KiB     16.00KiB   <FS_ROOT>/subv3
0/259        16.00KiB     16.00KiB   <FS_ROOT>/snap1

Pull-request: #139
Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Jeff Mahoney <jeffm@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
This commit is contained in:
Jeff Mahoney 2022-10-25 10:56:56 +02:00 committed by David Sterba
parent f80a6b40b9
commit 8d1c854094
2 changed files with 148 additions and 19 deletions

View file

@ -68,6 +68,9 @@ struct btrfs_qgroup {
struct rb_node all_parent_node; struct rb_node all_parent_node;
u64 qgroupid; u64 qgroupid;
/* NULL for qgroups with level > 0 */
const char *path;
/* /*
* info_item * info_item
*/ */
@ -118,11 +121,13 @@ enum btrfs_qgroup_column_enum {
BTRFS_QGROUP_MAX_EXCL, BTRFS_QGROUP_MAX_EXCL,
BTRFS_QGROUP_PARENT, BTRFS_QGROUP_PARENT,
BTRFS_QGROUP_CHILD, BTRFS_QGROUP_CHILD,
BTRFS_QGROUP_PATH,
BTRFS_QGROUP_ALL, BTRFS_QGROUP_ALL,
}; };
enum btrfs_qgroup_comp_enum { enum btrfs_qgroup_comp_enum {
BTRFS_QGROUP_COMP_QGROUPID, BTRFS_QGROUP_COMP_QGROUPID,
BTRFS_QGROUP_COMP_PATH,
BTRFS_QGROUP_COMP_RFER, BTRFS_QGROUP_COMP_RFER,
BTRFS_QGROUP_COMP_EXCL, BTRFS_QGROUP_COMP_EXCL,
BTRFS_QGROUP_COMP_MAX_RFER, BTRFS_QGROUP_COMP_MAX_RFER,
@ -194,6 +199,13 @@ static struct {
.unit_mode = 0, .unit_mode = 0,
.max_len = 5, .max_len = 5,
}, },
{
.name = "path",
.column_name = "Path",
.need_print = 0,
.unit_mode = 0,
.max_len = 10,
},
{ {
.name = NULL, .name = NULL,
.column_name = NULL, .column_name = NULL,
@ -276,8 +288,50 @@ static void print_qgroup_column_add_blank(enum btrfs_qgroup_column_enum column,
printf(" "); printf(" ");
} }
void print_path_column(struct btrfs_qgroup *qgroup, bool verbose)
{
struct btrfs_qgroup_list *list = NULL;
fputs(" ", stdout);
if (btrfs_qgroup_level(qgroup->qgroupid) > 0) {
int count = 0;
list_for_each_entry(list, &qgroup->qgroups,
next_qgroup) {
if (verbose) {
struct btrfs_qgroup *member = list->qgroup;
u64 qgroupid = member->qgroupid;
u64 level = btrfs_qgroup_level(qgroupid);
u64 sid = btrfs_qgroup_subvid(qgroupid);
if (count)
fputs(" ", stdout);
if (level == 0) {
const char *path = member->path;
if (!path)
path = "<missing>";
fputs(path, stdout);
} else
printf("%llu/%llu", level, sid);
}
count++;
}
if (!count)
fputs("<empty>", stdout);
else if (!verbose)
printf("<%u member qgroup%c>", count,
count != 1 ? 's' : '\0');
} else if (qgroup->path)
printf("<FS_ROOT>%s%s", *qgroup->path ? "/" : "",
qgroup->path);
else
fputs("<missing>", stdout);
}
static void print_qgroup_column(struct btrfs_qgroup *qgroup, static void print_qgroup_column(struct btrfs_qgroup *qgroup,
enum btrfs_qgroup_column_enum column) enum btrfs_qgroup_column_enum column,
bool verbose)
{ {
int len; int len;
int unit_mode = btrfs_qgroup_columns[column].unit_mode; int unit_mode = btrfs_qgroup_columns[column].unit_mode;
@ -321,19 +375,22 @@ static void print_qgroup_column(struct btrfs_qgroup *qgroup,
len = print_child_column(qgroup); len = print_child_column(qgroup);
print_qgroup_column_add_blank(BTRFS_QGROUP_CHILD, len); print_qgroup_column_add_blank(BTRFS_QGROUP_CHILD, len);
break; break;
case BTRFS_QGROUP_PATH:
print_path_column(qgroup, verbose);
break;
default: default:
break; break;
} }
} }
static void print_single_qgroup_table(struct btrfs_qgroup *qgroup) static void print_single_qgroup_table(struct btrfs_qgroup *qgroup, bool verbose)
{ {
int i; int i;
for (i = 0; i < BTRFS_QGROUP_ALL; i++) { for (i = 0; i < BTRFS_QGROUP_ALL; i++) {
if (!btrfs_qgroup_columns[i].need_print) if (!btrfs_qgroup_columns[i].need_print)
continue; continue;
print_qgroup_column(qgroup, i); print_qgroup_column(qgroup, i, verbose);
if (i != BTRFS_QGROUP_ALL - 1) if (i != BTRFS_QGROUP_ALL - 1)
printf(" "); printf(" ");
@ -406,6 +463,46 @@ static int comp_entry_with_qgroupid(struct btrfs_qgroup *entry1,
return is_descending ? -ret : ret; return is_descending ? -ret : ret;
} }
/* Sorts first-level qgroups by path and nested qgroups by qgroupid */
static int comp_entry_with_path(struct btrfs_qgroup *entry1,
struct btrfs_qgroup *entry2,
int is_descending)
{
int ret = 0;
const char *p1 = entry1->path;
const char *p2 = entry2->path;
u64 level1 = btrfs_qgroup_level(entry1->qgroupid);
u64 level2 = btrfs_qgroup_level(entry2->qgroupid);
if (level1 != level2) {
if (entry1->qgroupid > entry2->qgroupid)
ret = 1;
else if (entry1->qgroupid < entry2->qgroupid)
ret = -1;
}
if (ret)
goto out;
while (*p1 && *p2) {
if (*p1 != *p2)
break;
p1++;
p2++;
}
if (*p1 == '/')
ret = 1;
else if (*p2 == '/')
ret = -1;
else if (*p1 > *p2)
ret = 1;
else if (*p1 < *p2)
ret = -1;
out:
return (is_descending ? -ret : ret);
}
static int comp_entry_with_rfer(struct btrfs_qgroup *entry1, static int comp_entry_with_rfer(struct btrfs_qgroup *entry1,
struct btrfs_qgroup *entry2, struct btrfs_qgroup *entry2,
int is_descending) int is_descending)
@ -472,6 +569,7 @@ static int comp_entry_with_max_excl(struct btrfs_qgroup *entry1,
static btrfs_qgroup_comp_func all_comp_funcs[] = { static btrfs_qgroup_comp_func all_comp_funcs[] = {
[BTRFS_QGROUP_COMP_QGROUPID] = comp_entry_with_qgroupid, [BTRFS_QGROUP_COMP_QGROUPID] = comp_entry_with_qgroupid,
[BTRFS_QGROUP_COMP_PATH] = comp_entry_with_path,
[BTRFS_QGROUP_COMP_RFER] = comp_entry_with_rfer, [BTRFS_QGROUP_COMP_RFER] = comp_entry_with_rfer,
[BTRFS_QGROUP_COMP_EXCL] = comp_entry_with_excl, [BTRFS_QGROUP_COMP_EXCL] = comp_entry_with_excl,
[BTRFS_QGROUP_COMP_MAX_RFER] = comp_entry_with_max_rfer, [BTRFS_QGROUP_COMP_MAX_RFER] = comp_entry_with_max_rfer,
@ -480,6 +578,7 @@ static btrfs_qgroup_comp_func all_comp_funcs[] = {
static char *all_sort_items[] = { static char *all_sort_items[] = {
[BTRFS_QGROUP_COMP_QGROUPID] = "qgroupid", [BTRFS_QGROUP_COMP_QGROUPID] = "qgroupid",
[BTRFS_QGROUP_COMP_PATH] = "path",
[BTRFS_QGROUP_COMP_RFER] = "rfer", [BTRFS_QGROUP_COMP_RFER] = "rfer",
[BTRFS_QGROUP_COMP_EXCL] = "excl", [BTRFS_QGROUP_COMP_EXCL] = "excl",
[BTRFS_QGROUP_COMP_MAX_RFER] = "max_rfer", [BTRFS_QGROUP_COMP_MAX_RFER] = "max_rfer",
@ -655,7 +754,7 @@ static struct btrfs_qgroup *qgroup_tree_search(struct qgroup_lookup *root_tree,
* Return the pointer to the btrfs_qgroup if found or if inserted successfully. * Return the pointer to the btrfs_qgroup if found or if inserted successfully.
* Return ERR_PTR if any error occurred. * Return ERR_PTR if any error occurred.
*/ */
static struct btrfs_qgroup *get_or_add_qgroup( static struct btrfs_qgroup *get_or_add_qgroup(int fd,
struct qgroup_lookup *qgroup_lookup, u64 qgroupid) struct qgroup_lookup *qgroup_lookup, u64 qgroupid)
{ {
struct btrfs_qgroup *bq; struct btrfs_qgroup *bq;
@ -675,6 +774,23 @@ static struct btrfs_qgroup *get_or_add_qgroup(
INIT_LIST_HEAD(&bq->qgroups); INIT_LIST_HEAD(&bq->qgroups);
INIT_LIST_HEAD(&bq->members); INIT_LIST_HEAD(&bq->members);
if (btrfs_qgroup_level(qgroupid) == 0) {
enum btrfs_util_error uret;
char *path;
uret = btrfs_util_subvolume_path_fd(fd, qgroupid, &path);
if (uret == BTRFS_UTIL_OK)
bq->path = path;
/* Ignore stale qgroup items */
else if (uret != BTRFS_UTIL_ERROR_SUBVOLUME_NOT_FOUND) {
error("%s", btrfs_util_strerror(uret));
if (uret == BTRFS_UTIL_ERROR_NO_MEMORY)
return ERR_PTR(-ENOMEM);
else
return ERR_PTR(-EIO);
}
}
ret = qgroup_tree_insert(qgroup_lookup, bq); ret = qgroup_tree_insert(qgroup_lookup, bq);
if (ret) { if (ret) {
errno = -ret; errno = -ret;
@ -686,12 +802,12 @@ static struct btrfs_qgroup *get_or_add_qgroup(
return bq; return bq;
} }
static int update_qgroup_info(struct qgroup_lookup *qgroup_lookup, u64 qgroupid, static int update_qgroup_info(int fd, struct qgroup_lookup *qgroup_lookup, u64 qgroupid,
struct btrfs_qgroup_info_item *info) struct btrfs_qgroup_info_item *info)
{ {
struct btrfs_qgroup *bq; struct btrfs_qgroup *bq;
bq = get_or_add_qgroup(qgroup_lookup, qgroupid); bq = get_or_add_qgroup(fd, qgroup_lookup, qgroupid);
if (IS_ERR_OR_NULL(bq)) if (IS_ERR_OR_NULL(bq))
return PTR_ERR(bq); return PTR_ERR(bq);
@ -706,13 +822,13 @@ static int update_qgroup_info(struct qgroup_lookup *qgroup_lookup, u64 qgroupid,
return 0; return 0;
} }
static int update_qgroup_limit(struct qgroup_lookup *qgroup_lookup, static int update_qgroup_limit(int fd, struct qgroup_lookup *qgroup_lookup,
u64 qgroupid, u64 qgroupid,
struct btrfs_qgroup_limit_item *limit) struct btrfs_qgroup_limit_item *limit)
{ {
struct btrfs_qgroup *bq; struct btrfs_qgroup *bq;
bq = get_or_add_qgroup(qgroup_lookup, qgroupid); bq = get_or_add_qgroup(fd, qgroup_lookup, qgroupid);
if (IS_ERR_OR_NULL(bq)) if (IS_ERR_OR_NULL(bq))
return PTR_ERR(bq); return PTR_ERR(bq);
@ -784,6 +900,8 @@ static void __free_btrfs_qgroup(struct btrfs_qgroup *bq)
list_del(&list->next_member); list_del(&list->next_member);
free(list); free(list);
} }
if (bq->path)
free((void *)bq->path);
free(bq); free(bq);
} }
@ -1194,7 +1312,7 @@ static int __qgroups_search(int fd, struct btrfs_ioctl_search_args *args,
info = (struct btrfs_qgroup_info_item *) info = (struct btrfs_qgroup_info_item *)
(args->buf + off); (args->buf + off);
ret = update_qgroup_info(qgroup_lookup, ret = update_qgroup_info(fd, qgroup_lookup,
qgroupid, info); qgroupid, info);
break; break;
case BTRFS_QGROUP_LIMIT_KEY: case BTRFS_QGROUP_LIMIT_KEY:
@ -1202,7 +1320,7 @@ static int __qgroups_search(int fd, struct btrfs_ioctl_search_args *args,
limit = (struct btrfs_qgroup_limit_item *) limit = (struct btrfs_qgroup_limit_item *)
(args->buf + off); (args->buf + off);
ret = update_qgroup_limit(qgroup_lookup, ret = update_qgroup_limit(fd, qgroup_lookup,
qgroupid, limit); qgroupid, limit);
break; break;
case BTRFS_QGROUP_RELATION_KEY: case BTRFS_QGROUP_RELATION_KEY:
@ -1308,7 +1426,7 @@ int btrfs_qgroup_query(int fd, u64 qgroupid, struct btrfs_qgroup_stats *stats)
return ret; return ret;
} }
static void print_all_qgroups(struct qgroup_lookup *qgroup_lookup) static void print_all_qgroups(struct qgroup_lookup *qgroup_lookup, bool verbose)
{ {
struct rb_node *n; struct rb_node *n;
@ -1319,14 +1437,15 @@ static void print_all_qgroups(struct qgroup_lookup *qgroup_lookup)
n = rb_first(&qgroup_lookup->root); n = rb_first(&qgroup_lookup->root);
while (n) { while (n) {
entry = rb_entry(n, struct btrfs_qgroup, sort_node); entry = rb_entry(n, struct btrfs_qgroup, sort_node);
print_single_qgroup_table(entry); print_single_qgroup_table(entry, verbose);
n = rb_next(n); n = rb_next(n);
} }
} }
static int show_qgroups(int fd, static int show_qgroups(int fd,
struct btrfs_qgroup_filter_set *filter_set, struct btrfs_qgroup_filter_set *filter_set,
struct btrfs_qgroup_comparer_set *comp_set) struct btrfs_qgroup_comparer_set *comp_set,
bool verbose)
{ {
struct qgroup_lookup qgroup_lookup; struct qgroup_lookup qgroup_lookup;
@ -1338,7 +1457,7 @@ static int show_qgroups(int fd,
return ret; return ret;
__filter_and_sort_qgroups(&qgroup_lookup, &sort_tree, __filter_and_sort_qgroups(&qgroup_lookup, &sort_tree,
filter_set, comp_set); filter_set, comp_set);
print_all_qgroups(&sort_tree); print_all_qgroups(&sort_tree, verbose);
__free_all_qgroups(&qgroup_lookup); __free_all_qgroups(&qgroup_lookup);
return ret; return ret;
@ -1731,8 +1850,11 @@ static const char * const cmd_qgroup_show_usage[] = {
" (including ancestral qgroups)", " (including ancestral qgroups)",
"-f list all qgroups which impact the given path", "-f list all qgroups which impact the given path",
" (excluding ancestral qgroups)", " (excluding ancestral qgroups)",
"-P print first-level qgroups subvolume path",
" - nested qgroups will be reported as a count",
"-v verbose, prints pathn for all nested qgroups",
HELPINFO_UNITS_LONG, HELPINFO_UNITS_LONG,
"--sort=qgroupid,rfer,excl,max_rfer,max_excl", "--sort=qgroupid,rfer,excl,max_rfer,max_excl,path",
" list qgroups sorted by specified items", " list qgroups sorted by specified items",
" you can use '+' or '-' in front of each item.", " you can use '+' or '-' in front of each item.",
" (+:ascending, -:descending, ascending default)", " (+:ascending, -:descending, ascending default)",
@ -1751,11 +1873,11 @@ static int cmd_qgroup_show(const struct cmd_struct *cmd, int argc, char **argv)
unsigned unit_mode; unsigned unit_mode;
bool sync = false; bool sync = false;
enum btrfs_util_error err; enum btrfs_util_error err;
struct btrfs_qgroup_comparer_set *comparer_set; struct btrfs_qgroup_comparer_set *comparer_set;
struct btrfs_qgroup_filter_set *filter_set; struct btrfs_qgroup_filter_set *filter_set;
filter_set = qgroup_alloc_filter_set(); filter_set = qgroup_alloc_filter_set();
comparer_set = qgroup_alloc_comparer_set(); comparer_set = qgroup_alloc_comparer_set();
bool verbose = false;
unit_mode = get_unit_mode_from_arg(&argc, argv, 0); unit_mode = get_unit_mode_from_arg(&argc, argv, 0);
@ -1769,10 +1891,11 @@ static int cmd_qgroup_show(const struct cmd_struct *cmd, int argc, char **argv)
static const struct option long_options[] = { static const struct option long_options[] = {
{"sort", required_argument, NULL, GETOPT_VAL_SORT}, {"sort", required_argument, NULL, GETOPT_VAL_SORT},
{"sync", no_argument, NULL, GETOPT_VAL_SYNC}, {"sync", no_argument, NULL, GETOPT_VAL_SYNC},
{"verbose", no_argument, NULL, 'v'},
{ NULL, 0, NULL, 0 } { NULL, 0, NULL, 0 }
}; };
c = getopt_long(argc, argv, "pcreFf", long_options, NULL); c = getopt_long(argc, argv, "pPcreFfv", long_options, NULL);
if (c < 0) if (c < 0)
break; break;
switch (c) { switch (c) {
@ -1794,6 +1917,12 @@ static int cmd_qgroup_show(const struct cmd_struct *cmd, int argc, char **argv)
case 'f': case 'f':
filter_flag |= 0x2; filter_flag |= 0x2;
break; break;
case 'P':
qgroup_setup_print_column(BTRFS_QGROUP_PATH);
break;
case 'v':
verbose = true;
break;
case GETOPT_VAL_SORT: case GETOPT_VAL_SORT:
ret = qgroup_parse_sort_string(optarg, &comparer_set); ret = qgroup_parse_sort_string(optarg, &comparer_set);
if (ret < 0) { if (ret < 0) {
@ -1849,7 +1978,7 @@ static int cmd_qgroup_show(const struct cmd_struct *cmd, int argc, char **argv)
BTRFS_QGROUP_FILTER_PARENT, BTRFS_QGROUP_FILTER_PARENT,
qgroupid); qgroupid);
} }
ret = show_qgroups(fd, filter_set, comparer_set); ret = show_qgroups(fd, filter_set, comparer_set, verbose);
close_file_or_dir(fd, dirstream); close_file_or_dir(fd, dirstream);
free(filter_set); free(filter_set);
free(comparer_set); free(comparer_set);

View file

@ -35,7 +35,7 @@
#include <linux/types.h> #include <linux/types.h>
#include <linux/const.h> #include <linux/const.h>
#include <stdint.h> #include <stdint.h>
#include <stdbool.h>
#include <features.h> #include <features.h>
/* /*