Skip to content

Commit 73abcba

Browse files
osandovmaharmstone
authored andcommitted
btrfs-progs: subvol list: add sane -O and -A options
Now that we've documented the current nonsensical behavior, add a couple of options that actually make sense: -O lists all subvolumes below a path (which is what people think -o does), and -A lists all subvolumes with no path munging (which is what people think the default or -a do). -O can even be used by unprivileged users. -O and -A also renames the "top level" in the default output to what it actually is now: the "parent". Signed-off-by: Omar Sandoval <[email protected]>
1 parent a374292 commit 73abcba

File tree

3 files changed

+117
-15
lines changed

3 files changed

+117
-15
lines changed

Documentation/btrfs-subvolume.rst

+17
Original file line numberDiff line numberDiff line change
@@ -145,13 +145,30 @@ list [options] [-G [\+|-]<value>] [-C [+|-]<value>] [--sort=rootid,gen,ogen,path
145145
updated every transaction, *parent_ID* is the same as the parent subvolume's id,
146146
and *path* is the path of the subvolume. The exact meaning of *path*
147147
depends on the **Path filtering** option used.
148+
149+
If -O or -A is given, "top level" is replaced by "parent".
150+
148151
The subvolume's ID may be used by the subvolume set-default command,
149152
or at mount time via the *subvolid=* option.
150153

151154
``Options``
152155

153156
Path filtering:
154157

158+
-O
159+
Print <path> and all subvolumes below it, recursively. <path>
160+
must be a subvolume. Paths are printed relative to <path>.
161+
162+
This may be used by unprivileged users, in which case this only
163+
lists subvolumes that the user has access to.
164+
-A
165+
Print all subvolumes in the filesystem. Paths are printed
166+
relative to the root of the filesystem.
167+
168+
You likely always want either -O or -A. The -o and -a options and the
169+
default if no path filtering options are given have very confusing,
170+
accidental behavior that is only kept for backwards compatibility.
171+
155172
-o
156173
Print only the immediate children subvolumes of the subvolume
157174
containing <path>. Paths are printed relative to the root of

cmds/subvolume-list.c

+62-15
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,13 @@ static const char * const cmd_subvolume_list_usage[] = {
5959
"List subvolumes and snapshots in the filesystem.",
6060
"",
6161
"Path filtering:",
62+
OPTLINE("-O", "print all subvolumes below <path> relative to <path>"),
63+
OPTLINE("-A", "print all subvolumes in the filesystem relative to the "
64+
"root of the filesystem"),
65+
"",
66+
"You likely always want either -O or -A. The -o and -a options and the",
67+
"default are confusing and only kept for backwards compatibility.",
68+
"",
6269
OPTLINE("-o", "print only the immediate children subvolumes of the "
6370
"subvolume containing <path>"),
6471
OPTLINE("-a", "print all subvolumes in the filesystem other than the "
@@ -866,9 +873,11 @@ static struct subvol_list *btrfs_list_deleted_subvols(int fd,
866873
return subvols;
867874
}
868875

869-
static struct subvol_list *btrfs_list_subvols(int fd,
876+
static struct subvol_list *btrfs_list_subvols(int fd, bool include_top,
877+
bool below,
870878
struct btrfs_list_filter_set *filter_set)
871879
{
880+
u64 top_id = below ? 0 : BTRFS_FS_TREE_OBJECTID;
872881
struct subvol_list *subvols;
873882
size_t capacity = 4;
874883
struct btrfs_util_subvolume_iterator *iter;
@@ -883,15 +892,28 @@ static struct subvol_list *btrfs_list_subvols(int fd,
883892
}
884893
subvols->num = 0;
885894

886-
err = btrfs_util_create_subvolume_iterator_fd(fd,
887-
BTRFS_FS_TREE_OBJECTID, 0,
888-
&iter);
895+
err = btrfs_util_create_subvolume_iterator_fd(fd, top_id, 0, &iter);
889896
if (err) {
890897
iter = NULL;
891898
error_btrfs_util(err);
892899
goto out;
893900
}
894901

902+
if (include_top) {
903+
err = btrfs_util_subvolume_info_fd(fd, top_id,
904+
&subvols->subvols[0].info);
905+
if (err) {
906+
error_btrfs_util(err);
907+
goto out;
908+
}
909+
subvols->subvols[0].path = strdup("");
910+
if (!subvols->subvols[0].path) {
911+
error("out of memory");
912+
goto out;
913+
}
914+
subvols->num++;
915+
}
916+
895917
for (;;) {
896918
struct root_info subvol;
897919

@@ -945,14 +967,15 @@ static struct subvol_list *btrfs_list_subvols(int fd,
945967

946968
static int btrfs_list_subvols_print(int fd, struct btrfs_list_filter_set *filter_set,
947969
struct btrfs_list_comparer_set *comp_set,
948-
enum btrfs_list_layout layout)
970+
enum btrfs_list_layout layout, bool include_top,
971+
bool below)
949972
{
950973
struct subvol_list *subvols;
951974

952975
if (filter_set->only_deleted)
953976
subvols = btrfs_list_deleted_subvols(fd, filter_set);
954977
else
955-
subvols = btrfs_list_subvols(fd, filter_set);
978+
subvols = btrfs_list_subvols(fd, include_top, below, filter_set);
956979
if (!subvols)
957980
return -1;
958981

@@ -1097,8 +1120,10 @@ static int cmd_subvolume_list(const struct cmd_struct *cmd, int argc, char **arg
10971120
char *top_path = NULL;
10981121
int ret = -1, uerr = 0;
10991122
char *subvol;
1123+
bool is_list_below = false;
11001124
bool is_list_all = false;
1101-
bool is_only_in_path = false;
1125+
bool is_old_a_option = false;
1126+
bool is_old_o_option = false;
11021127
enum btrfs_list_layout layout = BTRFS_LIST_LAYOUT_DEFAULT;
11031128

11041129
filter_set = btrfs_list_alloc_filter_set();
@@ -1113,7 +1138,7 @@ static int cmd_subvolume_list(const struct cmd_struct *cmd, int argc, char **arg
11131138
};
11141139

11151140
c = getopt_long(argc, argv,
1116-
"acdgopqsurRG:C:t", long_options, NULL);
1141+
"acdgopqsurRG:C:tOA", long_options, NULL);
11171142
if (c < 0)
11181143
break;
11191144

@@ -1122,7 +1147,7 @@ static int cmd_subvolume_list(const struct cmd_struct *cmd, int argc, char **arg
11221147
btrfs_list_setup_print_column(BTRFS_LIST_PARENT);
11231148
break;
11241149
case 'a':
1125-
is_list_all = true;
1150+
is_old_a_option = true;
11261151
break;
11271152
case 'c':
11281153
btrfs_list_setup_print_column(BTRFS_LIST_OGENERATION);
@@ -1134,7 +1159,7 @@ static int cmd_subvolume_list(const struct cmd_struct *cmd, int argc, char **arg
11341159
btrfs_list_setup_print_column(BTRFS_LIST_GENERATION);
11351160
break;
11361161
case 'o':
1137-
is_only_in_path = true;
1162+
is_old_o_option = true;
11381163
break;
11391164
case 't':
11401165
layout = BTRFS_LIST_LAYOUT_TABLE;
@@ -1187,6 +1212,12 @@ static int cmd_subvolume_list(const struct cmd_struct *cmd, int argc, char **arg
11871212
goto out;
11881213
}
11891214
break;
1215+
case 'O':
1216+
is_list_below = true;
1217+
break;
1218+
case 'A':
1219+
is_list_all = true;
1220+
break;
11901221

11911222
default:
11921223
uerr = 1;
@@ -1197,6 +1228,19 @@ static int cmd_subvolume_list(const struct cmd_struct *cmd, int argc, char **arg
11971228
if (check_argc_exact(argc - optind, 1))
11981229
goto out;
11991230

1231+
/*
1232+
* The path filtering options and -d don't make sense together. For -O
1233+
* and -A, we're strict about not combining them with each other or with
1234+
* -o, -a, or -d. The -o, -a, and -d options are older and have never
1235+
* been restricted, so although they produce dubious results when
1236+
* combined, we allow it for backwards compatibility.
1237+
*/
1238+
if (is_list_below + is_list_all +
1239+
(is_old_a_option || is_old_o_option || filter_set->only_deleted) > 1) {
1240+
error("-O, -A, -o, -a, and -d are mutually exclusive");
1241+
goto out;
1242+
}
1243+
12001244
subvol = argv[optind];
12011245
fd = btrfs_open_dir(subvol);
12021246
if (fd < 0) {
@@ -1216,15 +1260,15 @@ static int cmd_subvolume_list(const struct cmd_struct *cmd, int argc, char **arg
12161260
goto out;
12171261
}
12181262

1219-
if (is_list_all)
1263+
if (is_old_a_option)
12201264
btrfs_list_setup_filter(&filter_set,
12211265
BTRFS_LIST_FILTER_FULL_PATH,
12221266
top_id);
1223-
else if (is_only_in_path)
1267+
else if (is_old_o_option)
12241268
btrfs_list_setup_filter(&filter_set,
12251269
BTRFS_LIST_FILTER_TOPID_EQUAL,
12261270
top_id);
1227-
else if (!filter_set->only_deleted) {
1271+
else if (!is_list_below && !is_list_all && !filter_set->only_deleted) {
12281272
enum btrfs_util_error err;
12291273

12301274
err = btrfs_util_subvolume_get_path_fd(fd, top_id, &top_path);
@@ -1241,13 +1285,16 @@ static int cmd_subvolume_list(const struct cmd_struct *cmd, int argc, char **arg
12411285
/* by default we shall print the following columns*/
12421286
btrfs_list_setup_print_column(BTRFS_LIST_OBJECTID);
12431287
btrfs_list_setup_print_column(BTRFS_LIST_GENERATION);
1244-
btrfs_list_setup_print_column(BTRFS_LIST_TOP_LEVEL);
1288+
btrfs_list_setup_print_column(is_list_below || is_list_all ?
1289+
BTRFS_LIST_PARENT : BTRFS_LIST_TOP_LEVEL);
12451290
btrfs_list_setup_print_column(BTRFS_LIST_PATH);
12461291

12471292
if (bconf.output_format == CMD_FORMAT_JSON)
12481293
layout = BTRFS_LIST_LAYOUT_JSON;
12491294

1250-
ret = btrfs_list_subvols_print(fd, filter_set, comparer_set, layout);
1295+
ret = btrfs_list_subvols_print(fd, filter_set, comparer_set, layout,
1296+
is_list_below || is_list_all,
1297+
is_list_below);
12511298

12521299
out:
12531300
free(top_path);

tests/cli-tests/026-subvolume-list-path-filtering/test.sh

+38
Original file line numberDiff line numberDiff line change
@@ -114,5 +114,43 @@ EOF
114114
expect_subvol_list_paths -o a/b/c/d << EOF
115115
EOF
116116

117+
### -A ###
118+
119+
# Paths are always relative to the root of the filesystem.
120+
for path in . a/b a/b/c; do
121+
expect_subvol_list_paths -A "$path" << EOF
122+
123+
a
124+
a/b/c
125+
a/b/c/d
126+
a/e
127+
EOF
128+
done
129+
130+
### -O ###
131+
132+
# Paths are relative to the given path.
133+
expect_subvol_list_paths -O . << EOF
134+
135+
a
136+
a/b/c
137+
a/b/c/d
138+
a/e
139+
EOF
140+
141+
expect_subvol_list_paths -O a << EOF
142+
143+
b/c
144+
b/c/d
145+
e
146+
EOF
147+
148+
expect_subvol_list_paths -O a/e << EOF
149+
150+
EOF
151+
152+
run_mustfail "btrfs subvol list -O allowed non-subvolume" \
153+
$SUDO_HELPER "$TOP/btrfs" subvolume list -O a/b
154+
117155
cd ..
118156
run_check_umount_test_dev

0 commit comments

Comments
 (0)