Skip to content

Commit 2eb8f9f

Browse files
boryasadam900710
authored andcommitted
btrfs-progs: btrfstune: add ability to remove squotas
When simple quotas is enabled, every new data extent gets a special inline OWNER_REF item that identifies the owning subvolume. This makes simple quotas backwards incompatible with kernels older than v6.7. Even if you disable quotas on the filesystem, the OWNER_REF items are sprinkled throughout the extent tree and older kernels are unable to parse them. However, it is relatively easy to simply walk the extent tree and remove these inline ref items. This gives squota adopters the option to *fully* disable squotas on their system and un-set the incompat bit. Add this capability to btrfstune, which requires only a little tricky btrfs item data shifting. This functionality was tested with a new unit test, as well as a similar but more thorough integration test in fstests Reviewed-by: Anand Jain <[email protected]> Reviewed-by: Qu Wenruo <[email protected]> Signed-off-by: Boris Burkov <[email protected]>
1 parent 813e157 commit 2eb8f9f

File tree

5 files changed

+212
-1
lines changed

5 files changed

+212
-1
lines changed

Documentation/btrfstune.rst

+3
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ OPTIONS
4949
alternative to qgroups with a smaller performance impact but no notion
5050
of shared vs. exclusive usage.
5151

52+
--remove-simple-quota
53+
Remove simple quota accounting related structures.
54+
5255
-f
5356
Allow dangerous changes, e.g. clear the seeding flag or change fsid.
5457
Make sure that you are aware of the dangers.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#!/bin/bash
2+
# Verify btrfstune for enabling and removing simple quotas
3+
4+
source "$TEST_TOP/common" || exit
5+
source "$TEST_TOP/common.convert" || exit
6+
7+
check_experimental_build
8+
setup_root_helper
9+
prepare_test_dev
10+
11+
# Create the fs without simple quota
12+
run_check_mkfs_test_dev
13+
run_check_mount_test_dev
14+
populate_fs
15+
run_check_umount_test_dev
16+
# Enable simple quotas
17+
run_check $SUDO_HELPER "$TOP/btrfstune" --enable-simple-quota "$TEST_DEV"
18+
run_check_mount_test_dev
19+
run_check $SUDO_HELPER dd if=/dev/zero of="$TEST_MNT"/file2 bs=1M count=1
20+
run_check_umount_test_dev
21+
run_check $SUDO_HELPER "$TOP/btrfs" check "$TEST_DEV"
22+
23+
# Populate new fs with simple quotas enabled
24+
run_check_mkfs_test_dev -O squota
25+
run_check_mount_test_dev
26+
populate_fs
27+
run_check_umount_test_dev
28+
# Remove simple quotas
29+
run_check $SUDO_HELPER "$TOP/btrfstune" --remove-simple-quota "$TEST_DEV"
30+
run_check_mount_test_dev
31+
run_check $SUDO_HELPER dd if=/dev/zero of="$TEST_MNT"/file3 bs=1M count=1
32+
run_check_umount_test_dev
33+
run_check $SUDO_HELPER "$TOP/btrfs" check "$TEST_DEV"

tune/main.c

+15-1
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ static const char * const tune_usage[] = {
104104
OPTLINE("-n", "enable no-holes feature (mkfs: no-holes, more efficient sparse file representation)"),
105105
OPTLINE("-S <0|1>", "set/unset seeding status of a device"),
106106
OPTLINE("--enable-simple-quota", "enable simple quotas on the file system. (mkfs: squota)"),
107+
OPTLINE("--remove-simple-quota", "remove simple quotas from the file system."),
107108
OPTLINE("--convert-to-block-group-tree", "convert filesystem to track block groups in "
108109
"the separate block-group-tree instead of extent tree (sets the incompat bit)"),
109110
OPTLINE("--convert-from-block-group-tree",
@@ -198,6 +199,7 @@ int BOX_MAIN(btrfstune)(int argc, char *argv[])
198199
int ret;
199200
u64 super_flags = 0;
200201
int quota = 0;
202+
int remove_simple_quota = 0;
201203
int fd = -1;
202204
int oflags = O_RDWR;
203205

@@ -209,7 +211,7 @@ int BOX_MAIN(btrfstune)(int argc, char *argv[])
209211
GETOPT_VAL_DISABLE_BLOCK_GROUP_TREE,
210212
GETOPT_VAL_ENABLE_FREE_SPACE_TREE,
211213
GETOPT_VAL_ENABLE_SIMPLE_QUOTA,
212-
214+
GETOPT_VAL_REMOVE_SIMPLE_QUOTA,
213215
};
214216
static const struct option long_options[] = {
215217
{ "help", no_argument, NULL, GETOPT_VAL_HELP},
@@ -221,6 +223,8 @@ int BOX_MAIN(btrfstune)(int argc, char *argv[])
221223
GETOPT_VAL_ENABLE_FREE_SPACE_TREE},
222224
{ "enable-simple-quota", no_argument, NULL,
223225
GETOPT_VAL_ENABLE_SIMPLE_QUOTA },
226+
{ "remove-simple-quota", no_argument, NULL,
227+
GETOPT_VAL_REMOVE_SIMPLE_QUOTA},
224228
#if EXPERIMENTAL
225229
{ "csum", required_argument, NULL, GETOPT_VAL_CSUM },
226230
#endif
@@ -288,6 +292,10 @@ int BOX_MAIN(btrfstune)(int argc, char *argv[])
288292
quota = 1;
289293
btrfstune_cmd_groups[QGROUP] = true;
290294
break;
295+
case GETOPT_VAL_REMOVE_SIMPLE_QUOTA:
296+
remove_simple_quota = 1;
297+
btrfstune_cmd_groups[QGROUP] = true;
298+
break;
291299
#if EXPERIMENTAL
292300
case GETOPT_VAL_CSUM:
293301
btrfs_warn_experimental(
@@ -535,6 +543,12 @@ int BOX_MAIN(btrfstune)(int argc, char *argv[])
535543
goto out;
536544
}
537545

546+
if (remove_simple_quota) {
547+
ret = remove_squota(root->fs_info);
548+
if (ret)
549+
goto out;
550+
}
551+
538552
out:
539553
if (ret < 0) {
540554
fs_info->readonly = 1;

tune/quota.c

+160
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,166 @@
66
#include "common/messages.h"
77
#include "tune/tune.h"
88

9+
static int remove_quota_tree(struct btrfs_fs_info *fs_info)
10+
{
11+
int ret;
12+
struct btrfs_root *quota_root = fs_info->quota_root;
13+
struct btrfs_root *tree_root = fs_info->tree_root;
14+
struct btrfs_super_block *sb = fs_info->super_copy;
15+
int super_flags = btrfs_super_incompat_flags(sb);
16+
struct btrfs_trans_handle *trans;
17+
18+
trans = btrfs_start_transaction(quota_root, 0);
19+
ret = btrfs_clear_tree(trans, quota_root);
20+
if (ret) {
21+
btrfs_abort_transaction(trans, ret);
22+
return ret;
23+
}
24+
25+
ret = btrfs_delete_and_free_root(trans, quota_root);
26+
if (ret) {
27+
btrfs_abort_transaction(trans, ret);
28+
return ret;
29+
}
30+
fs_info->quota_root = NULL;
31+
super_flags &= ~BTRFS_FEATURE_INCOMPAT_SIMPLE_QUOTA;
32+
btrfs_set_super_incompat_flags(sb, super_flags);
33+
btrfs_commit_transaction(trans, tree_root);
34+
return 0;
35+
}
36+
37+
/*
38+
* Given a pointer (ptr) into DATAi (i = slot), and an amount to shift,
39+
* move all the data to the left (slots >= slot) of that ptr to the right by
40+
* the shift amount. This overwrites the shift bytes after ptr, effectively
41+
* removing them from the item data. We must update affected item sizes (only
42+
* at slot) and offsets (slots >= slot).
43+
*
44+
* Leaf view, using '-' to show shift scale:
45+
* Before:
46+
* [ITEM0,...,ITEMi,...,ITEMn,-------,DATAn,...,[---DATAi---],...,DATA0]
47+
* After:
48+
* [ITEM0,...,ITEMi,...,ITEMn,--------,DATAn,...,[--DATAi---],...,DATA0]
49+
*
50+
* Zooming in on DATAi
51+
* (ptr points at the start of the Ys, and shift is length of the Ys)
52+
* Before:
53+
* ...[DATAi+1][XXXXXXXXXXXXYYYYYYYYYYYYYYYYXXXXXXX][DATAi-1]...
54+
* After:
55+
* ...................[DATAi+1][XXXXXXXXXXXXXXXXXXX][DATAi-1]...
56+
* Note that DATAi-1 and smaller are not affected.
57+
*/
58+
static void shift_leaf_data(struct btrfs_trans_handle *trans,
59+
struct extent_buffer *leaf, int slot,
60+
unsigned long ptr, u32 shift)
61+
{
62+
u32 nr = btrfs_header_nritems(leaf);
63+
u32 leaf_data_off = btrfs_item_ptr_offset(leaf, nr - 1);
64+
u32 len = ptr - leaf_data_off;
65+
u32 new_size = btrfs_item_size(leaf, slot) - shift;
66+
for (int i = slot; i < nr; i++) {
67+
u32 old_item_offset = btrfs_item_offset(leaf, i);
68+
btrfs_set_item_offset(leaf, i, old_item_offset + shift);
69+
}
70+
memmove_extent_buffer(leaf, leaf_data_off + shift, leaf_data_off, len);
71+
btrfs_set_item_size(leaf, slot, new_size);
72+
btrfs_set_header_generation(leaf, trans->transid);
73+
btrfs_mark_buffer_dirty(leaf);
74+
}
75+
76+
/*
77+
* Iterate over the extent tree and for each EXTENT_DATA item that has an inline
78+
* ref of type OWNER_REF, shift that leaf to eliminate the owner ref.
79+
*
80+
* Note: we use a search_slot per leaf rather than find_next_leaf to get the
81+
* needed CoW-ing and rebalancing for each leaf and its path up to the root.
82+
*/
83+
static int remove_owner_refs(struct btrfs_fs_info *fs_info)
84+
{
85+
struct btrfs_trans_handle *trans;
86+
struct btrfs_root *extent_root;
87+
struct btrfs_key key;
88+
struct extent_buffer *leaf;
89+
struct btrfs_path path = { 0 };
90+
int slot;
91+
int ret;
92+
93+
extent_root = btrfs_extent_root(fs_info, 0);
94+
95+
trans = btrfs_start_transaction(extent_root, 0);
96+
97+
key.objectid = 0;
98+
key.type = BTRFS_EXTENT_ITEM_KEY;
99+
key.offset = 0;
100+
101+
search_slot:
102+
ret = btrfs_search_slot(trans, extent_root, &key, &path, 1, 1);
103+
if (ret < 0)
104+
return ret;
105+
leaf = path.nodes[0];
106+
slot = path.slots[0];
107+
108+
while (1) {
109+
struct btrfs_key found_key;
110+
struct btrfs_extent_item *ei;
111+
struct btrfs_extent_inline_ref *iref;
112+
u8 type;
113+
unsigned long ptr;
114+
unsigned long item_end;
115+
116+
if (slot >= btrfs_header_nritems(leaf)) {
117+
ret = btrfs_next_leaf(extent_root, &path);
118+
if (ret < 0) {
119+
break;
120+
} else if (ret) {
121+
ret = 0;
122+
break;
123+
}
124+
leaf = path.nodes[0];
125+
slot = path.slots[0];
126+
btrfs_item_key_to_cpu(leaf, &key, slot);
127+
btrfs_release_path(&path);
128+
goto search_slot;
129+
}
130+
131+
btrfs_item_key_to_cpu(leaf, &found_key, slot);
132+
if (found_key.type != BTRFS_EXTENT_ITEM_KEY)
133+
goto next;
134+
ei = btrfs_item_ptr(leaf, slot, struct btrfs_extent_item);
135+
ptr = (unsigned long)(ei + 1);
136+
item_end = (unsigned long)ei + btrfs_item_size(leaf, slot);
137+
/* No inline extent references; accessing type is invalid. */
138+
if (ptr > item_end)
139+
goto next;
140+
iref = (struct btrfs_extent_inline_ref *)ptr;
141+
type = btrfs_extent_inline_ref_type(leaf, iref);
142+
if (type == BTRFS_EXTENT_OWNER_REF_KEY)
143+
shift_leaf_data(trans, leaf, slot, ptr, sizeof(*iref));
144+
next:
145+
slot++;
146+
}
147+
btrfs_release_path(&path);
148+
149+
ret = btrfs_commit_transaction(trans, extent_root);
150+
if (ret < 0) {
151+
errno = -ret;
152+
error_msg(ERROR_MSG_COMMIT_TRANS, "%m");
153+
return ret;
154+
}
155+
return 0;
156+
}
157+
158+
int remove_squota(struct btrfs_fs_info *fs_info)
159+
{
160+
int ret;
161+
162+
ret = remove_owner_refs(fs_info);
163+
if (ret)
164+
return ret;
165+
166+
return remove_quota_tree(fs_info);
167+
}
168+
9169
static int create_qgroup(struct btrfs_fs_info *fs_info,
10170
struct btrfs_trans_handle *trans,
11171
u64 qgroupid)

tune/tune.h

+1
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,6 @@ int convert_to_extent_tree(struct btrfs_fs_info *fs_info);
3333
int btrfs_change_csum_type(struct btrfs_fs_info *fs_info, u16 new_csum_type);
3434

3535
int enable_quota(struct btrfs_fs_info *fs_info, bool simple);
36+
int remove_squota(struct btrfs_fs_info *fs_info);
3637

3738
#endif

0 commit comments

Comments
 (0)