btrfs: make clear_cache mount option to rebuild FST without disabling it
authorQu Wenruo <wqu@suse.com>
Fri, 28 Apr 2023 06:13:05 +0000 (14:13 +0800)
committerDavid Sterba <dsterba@suse.com>
Wed, 10 May 2023 12:51:27 +0000 (14:51 +0200)
Previously clear_cache mount option would simply disable free-space-tree
feature temporarily then re-enable it to rebuild the whole free space
tree.

But this is problematic for block-group-tree feature, as we have an
artificial dependency on free-space-tree feature.

If we go the existing method, after clearing the free-space-tree
feature, we would flip the filesystem to read-only mode, as we detect a
super block write with block-group-tree but no free-space-tree feature.

This patch would change the behavior by properly rebuilding the free
space tree without disabling this feature, thus allowing clear_cache
mount option to work with block group tree.

Now we can mount a filesystem with block-group-tree feature and
clear_mount option:

  $ mkfs.btrfs  -O block-group-tree /dev/test/scratch1  -f
  $ sudo mount /dev/test/scratch1 /mnt/btrfs -o clear_cache
  $ sudo dmesg -t | head -n 5
  BTRFS info (device dm-1): force clearing of disk cache
  BTRFS info (device dm-1): using free space tree
  BTRFS info (device dm-1): auto enabling async discard
  BTRFS info (device dm-1): rebuilding free space tree
  BTRFS info (device dm-1): checking UUID tree

CC: stable@vger.kernel.org # 6.1+
Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
fs/btrfs/disk-io.c
fs/btrfs/free-space-tree.c
fs/btrfs/free-space-tree.h
fs/btrfs/super.c

index 59ea049fe7ee0de22a15c6c9795fdf077b8369eb..fbf9006c62346c40c570fa119aa1b43c2e79595a 100644 (file)
@@ -3121,23 +3121,34 @@ int btrfs_start_pre_rw_mount(struct btrfs_fs_info *fs_info)
 {
        int ret;
        const bool cache_opt = btrfs_test_opt(fs_info, SPACE_CACHE);
-       bool clear_free_space_tree = false;
+       bool rebuild_free_space_tree = false;
 
        if (btrfs_test_opt(fs_info, CLEAR_CACHE) &&
            btrfs_fs_compat_ro(fs_info, FREE_SPACE_TREE)) {
-               clear_free_space_tree = true;
+               rebuild_free_space_tree = true;
        } else if (btrfs_fs_compat_ro(fs_info, FREE_SPACE_TREE) &&
                   !btrfs_fs_compat_ro(fs_info, FREE_SPACE_TREE_VALID)) {
                btrfs_warn(fs_info, "free space tree is invalid");
-               clear_free_space_tree = true;
+               rebuild_free_space_tree = true;
        }
 
-       if (clear_free_space_tree) {
-               btrfs_info(fs_info, "clearing free space tree");
-               ret = btrfs_clear_free_space_tree(fs_info);
+       if (rebuild_free_space_tree) {
+               btrfs_info(fs_info, "rebuilding free space tree");
+               ret = btrfs_rebuild_free_space_tree(fs_info);
                if (ret) {
                        btrfs_warn(fs_info,
-                                  "failed to clear free space tree: %d", ret);
+                                  "failed to rebuild free space tree: %d", ret);
+                       goto out;
+               }
+       }
+
+       if (btrfs_fs_compat_ro(fs_info, FREE_SPACE_TREE) &&
+           !btrfs_test_opt(fs_info, FREE_SPACE_TREE)) {
+               btrfs_info(fs_info, "disabling free space tree");
+               ret = btrfs_delete_free_space_tree(fs_info);
+               if (ret) {
+                       btrfs_warn(fs_info,
+                                  "failed to disable free space tree: %d", ret);
                        goto out;
                }
        }
index 4d155a48ec59d333bc97eac224398e5d1a7ac388..b21da1446f2aac2733508bf8e8ee65c3bd9fbfab 100644 (file)
@@ -1252,7 +1252,7 @@ out:
        return ret;
 }
 
-int btrfs_clear_free_space_tree(struct btrfs_fs_info *fs_info)
+int btrfs_delete_free_space_tree(struct btrfs_fs_info *fs_info)
 {
        struct btrfs_trans_handle *trans;
        struct btrfs_root *tree_root = fs_info->tree_root;
@@ -1298,6 +1298,54 @@ abort:
        return ret;
 }
 
+int btrfs_rebuild_free_space_tree(struct btrfs_fs_info *fs_info)
+{
+       struct btrfs_trans_handle *trans;
+       struct btrfs_key key = {
+               .objectid = BTRFS_FREE_SPACE_TREE_OBJECTID,
+               .type = BTRFS_ROOT_ITEM_KEY,
+               .offset = 0,
+       };
+       struct btrfs_root *free_space_root = btrfs_global_root(fs_info, &key);
+       struct rb_node *node;
+       int ret;
+
+       trans = btrfs_start_transaction(free_space_root, 1);
+       if (IS_ERR(trans))
+               return PTR_ERR(trans);
+
+       set_bit(BTRFS_FS_CREATING_FREE_SPACE_TREE, &fs_info->flags);
+       set_bit(BTRFS_FS_FREE_SPACE_TREE_UNTRUSTED, &fs_info->flags);
+
+       ret = clear_free_space_tree(trans, free_space_root);
+       if (ret)
+               goto abort;
+
+       node = rb_first_cached(&fs_info->block_group_cache_tree);
+       while (node) {
+               struct btrfs_block_group *block_group;
+
+               block_group = rb_entry(node, struct btrfs_block_group,
+                                      cache_node);
+               ret = populate_free_space_tree(trans, block_group);
+               if (ret)
+                       goto abort;
+               node = rb_next(node);
+       }
+
+       btrfs_set_fs_compat_ro(fs_info, FREE_SPACE_TREE);
+       btrfs_set_fs_compat_ro(fs_info, FREE_SPACE_TREE_VALID);
+       clear_bit(BTRFS_FS_CREATING_FREE_SPACE_TREE, &fs_info->flags);
+
+       ret = btrfs_commit_transaction(trans);
+       clear_bit(BTRFS_FS_FREE_SPACE_TREE_UNTRUSTED, &fs_info->flags);
+       return ret;
+abort:
+       btrfs_abort_transaction(trans, ret);
+       btrfs_end_transaction(trans);
+       return ret;
+}
+
 static int __add_block_group_free_space(struct btrfs_trans_handle *trans,
                                        struct btrfs_block_group *block_group,
                                        struct btrfs_path *path)
index dc2463e4cfe3c9a78a73d83f7aa05b2953531ccf..6d5551d0ced810cc02ac85390be192f8fab826dd 100644 (file)
@@ -18,7 +18,8 @@ struct btrfs_caching_control;
 
 void set_free_space_tree_thresholds(struct btrfs_block_group *block_group);
 int btrfs_create_free_space_tree(struct btrfs_fs_info *fs_info);
-int btrfs_clear_free_space_tree(struct btrfs_fs_info *fs_info);
+int btrfs_delete_free_space_tree(struct btrfs_fs_info *fs_info);
+int btrfs_rebuild_free_space_tree(struct btrfs_fs_info *fs_info);
 int load_free_space_tree(struct btrfs_caching_control *caching_ctl);
 int add_block_group_free_space(struct btrfs_trans_handle *trans,
                               struct btrfs_block_group *block_group);
index 0f2f915e42b06ac4c97d8305cebfc13cdcfb34fe..ec18e22106023bcb6b7b141f50972dc387debfd3 100644 (file)
@@ -828,8 +828,7 @@ out:
                ret = -EINVAL;
        }
        if (btrfs_fs_compat_ro(info, BLOCK_GROUP_TREE) &&
-           (btrfs_test_opt(info, CLEAR_CACHE) ||
-            !btrfs_test_opt(info, FREE_SPACE_TREE))) {
+            !btrfs_test_opt(info, FREE_SPACE_TREE)) {
                btrfs_err(info, "cannot disable free space tree with block-group-tree feature");
                ret = -EINVAL;
        }