btrfs-progs: tune: add new option to convert back to extent tree

With previous btrfstune support to convert to block-group-tree, it has
implemented most of the infrastructure for bi-directional conversion.

This patch will implement the remaining conversion support to go back to
extent tree.

The modification includes:

- New convert_to_extent_tree() function in btrfstune.c
  It's almost the same as convert_to_bg_tree(), but with small changes:
  * No need to set extra features like NO_HOLES/FST.
  * Need to delete the block group tree when everything finished.

- Update btrfs_delete_and_free_root() to handle non-global roots
  Currently the function can only accepts global roots (extent/csum/free
  space trees)

  If we pass a non-global root into the function, we will screw up
  global_roots_tree and crash.

  Since we're going to use btrfs_delete_and_free_root() to free block
  group tree which is not a global tree, this is needed.

- New handling for half converted fs in get_last_converted_bg()
  There are two cases need to be handled:

  * The bg tree is already empty
    We need to grab the first bg in extent tree.
    Or at conversion function we will fail at grabbing the first bg.

  * The bg tree is not empty
    Then we need to grab the last bg in extent tree.

- Extra root switching in involved functions. This involves:

  * read_converting_block_groups()
  * insert_block_group_item()
  * update_block_group_item()

  We just need to update our target root according to the current
  compat_ro and super flags.

Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
This commit is contained in:
Qu Wenruo 2023-04-19 00:41:14 +02:00 committed by David Sterba
parent 5a0398bac5
commit 68a04bc710
6 changed files with 251 additions and 18 deletions

View File

@ -29,6 +29,12 @@ OPTIONS
Enable block group tree feature (greatly reduce mount time),
enabled by mkfs feature *block-group-tree*.
--disable-block-group-tree
(since kernel 6.1)
Disable block group tree feature and convert the filesystem to extent
tree.
-f
Allow dangerous changes, e.g. clear the seeding flag or change fsid.
Make sure that you are aware of the dangers.

View File

@ -2282,6 +2282,14 @@ int btrfs_set_buffer_uptodate(struct extent_buffer *eb)
return set_extent_buffer_uptodate(eb);
}
static bool is_global_root(struct btrfs_root *root)
{
if (root->root_key.objectid == BTRFS_EXTENT_TREE_OBJECTID ||
root->root_key.objectid == BTRFS_CSUM_TREE_OBJECTID ||
root->root_key.objectid == BTRFS_FREE_SPACE_TREE_OBJECTID)
return true;
return false;
}
int btrfs_delete_and_free_root(struct btrfs_trans_handle *trans,
struct btrfs_root *root)
{
@ -2300,7 +2308,8 @@ int btrfs_delete_and_free_root(struct btrfs_trans_handle *trans,
ret = btrfs_free_tree_block(trans, root, root->node, 0, 1);
if (ret)
return ret;
rb_erase(&root->rb_node, &fs_info->global_roots_tree);
if (is_global_root(root))
rb_erase(&root->rb_node, &fs_info->global_roots_tree);
free_extent_buffer(root->node);
free_extent_buffer(root->commit_root);
kfree(root);

View File

@ -1551,8 +1551,14 @@ static int update_block_group_item(struct btrfs_trans_handle *trans,
*/
if (btrfs_super_flags(fs_info->super_copy) &
BTRFS_SUPER_FLAG_CHANGING_BG_TREE &&
cache->start >= fs_info->last_converted_bg_bytenr)
root = fs_info->block_group_root;
cache->start >= fs_info->last_converted_bg_bytenr) {
if (btrfs_fs_compat_ro(fs_info, BLOCK_GROUP_TREE))
/* Converting back to extent tree. */
root = btrfs_extent_root(fs_info, 0);
else
/* Convert to new bg tree.*/
root = fs_info->block_group_root;
}
key.objectid = cache->start;
key.type = BTRFS_BLOCK_GROUP_ITEM_KEY;
@ -2741,6 +2747,56 @@ static int get_last_converted_bg(struct btrfs_fs_info *fs_info)
struct btrfs_key key = {0};
int ret;
/* Converting back to extent tree case. */
if (btrfs_fs_compat_ro(fs_info, BLOCK_GROUP_TREE)) {
struct btrfs_root *extent_root = btrfs_extent_root(fs_info, 0);
/* Load the first bg in bg tree. */
ret = btrfs_search_slot(NULL, bg_root, &key, &path, 0, 0);
if (ret < 0)
return ret;
ASSERT(ret > 0);
/* We should always be at the slot 0 of the first leaf. */
ASSERT(path.slots[0] == 0);
key.type = BTRFS_BLOCK_GROUP_ITEM_KEY;
/* Empty bg tree, all converted, then grab the first bg. */
if (btrfs_header_nritems(path.nodes[0]) == 0) {
btrfs_release_path(&path);
ret = find_first_block_group(extent_root, &path, &key);
/* We should have at least one bg item in extent tree. */
ASSERT(ret == 0);
btrfs_item_key_to_cpu(path.nodes[0], &key, path.slots[0]);
fs_info->last_converted_bg_bytenr = key.objectid;
goto out;
}
btrfs_release_path(&path);
/* Grab the last bg in extent tree as the last converted one. */
key.objectid = (u64)-1;
key.type = BTRFS_BLOCK_GROUP_ITEM_KEY;
key.offset = (u64)-1;
ret = btrfs_search_slot(NULL, extent_root, &key, &path, 0, 0);
if (ret < 0)
goto out;
ASSERT(ret > 0);
ret = btrfs_previous_item(extent_root, &path, 0, BTRFS_BLOCK_GROUP_ITEM_KEY);
if (ret < 0)
goto out;
/* No converted bg item in extent tree.*/
if (ret > 0) {
ret = 0;
fs_info->last_converted_bg_bytenr = (u64)-1;
goto out;
}
btrfs_item_key_to_cpu(path.nodes[0], &key, path.slots[0]);
fs_info->last_converted_bg_bytenr = key.objectid;
goto out;
}
/* Load the first bg in bg tree, that would be our last converted bg. */
ret = btrfs_search_slot(NULL, bg_root, &key, &path, 0, 0);
if (ret < 0)
@ -2758,7 +2814,6 @@ static int get_last_converted_bg(struct btrfs_fs_info *fs_info)
btrfs_item_key_to_cpu(path.nodes[0], &key, path.slots[0]);
ASSERT(key.type == BTRFS_BLOCK_GROUP_ITEM_KEY);
fs_info->last_converted_bg_bytenr = key.objectid;
out:
btrfs_release_path(&path);
return ret;
@ -2881,12 +2936,19 @@ out:
static int read_converting_block_groups(struct btrfs_fs_info *fs_info)
{
struct btrfs_root *old_root = btrfs_extent_root(fs_info, 0);
struct btrfs_root *new_root = btrfs_block_group_root(fs_info);
struct btrfs_root *old_root;
struct btrfs_root *new_root;
int ret;
/* Currently we only support converting to bg tree feature. */
ASSERT(!btrfs_fs_compat_ro(fs_info, BLOCK_GROUP_TREE));
if (btrfs_fs_compat_ro(fs_info, BLOCK_GROUP_TREE)) {
/* Converting back to extent tree. */
old_root = fs_info->block_group_root;
new_root = btrfs_extent_root(fs_info, 0);
} else {
/* Converting to block group tree. */
old_root = btrfs_extent_root(fs_info, 0);
new_root = fs_info->block_group_root;
}
ret = get_last_converted_bg(fs_info);
if (ret < 0) {
@ -2994,8 +3056,15 @@ static int insert_block_group_item(struct btrfs_trans_handle *trans,
*/
if (btrfs_super_flags(fs_info->super_copy) &
BTRFS_SUPER_FLAG_CHANGING_BG_TREE &&
block_group->start >= fs_info->last_converted_bg_bytenr)
root = fs_info->block_group_root;
block_group->start >= fs_info->last_converted_bg_bytenr) {
if (btrfs_super_compat_ro_flags(fs_info->super_copy) &
BTRFS_FEATURE_COMPAT_RO_BLOCK_GROUP_TREE)
/* Converting to extent tree, return extent root. */
root = btrfs_extent_root(fs_info, block_group->start);
else
/* Converting to bg tree, return bg root. */
root = fs_info->block_group_root;
}
return btrfs_insert_item(trans, root, &key, &bgi, sizeof(bgi));
}
@ -4041,11 +4110,6 @@ int btrfs_convert_one_bg(struct btrfs_trans_handle *trans, u64 bytenr)
ASSERT(old_root);
ASSERT(btrfs_super_flags(fs_info->super_copy) &
BTRFS_SUPER_FLAG_CHANGING_BG_TREE);
/*
* Only support converting to bg tree yet, thus the feature should not
* be set.
*/
ASSERT(!btrfs_fs_compat_ro(fs_info, BLOCK_GROUP_TREE));
bg = btrfs_lookup_block_group(fs_info, bytenr);
if (!bg) {

View File

@ -18,6 +18,7 @@
#include <errno.h>
#include <stdio.h>
#include "kernel-shared/ctree.h"
#include "kernel-shared/disk-io.h"
#include "kernel-shared/transaction.h"
#include "common/messages.h"
#include "common/extent-cache.h"
@ -147,4 +148,124 @@ error:
return ret;
}
int convert_to_extent_tree(struct btrfs_fs_info *fs_info)
{
struct btrfs_super_block *sb = fs_info->super_copy;
struct btrfs_trans_handle *trans;
struct cache_extent *ce;
u32 converted_bgs = 0;
int ret;
trans = btrfs_start_transaction(fs_info->tree_root, 2);
if (IS_ERR(trans)) {
ret = PTR_ERR(trans);
error_msg(ERROR_MSG_START_TRANS, "%m");
return ret;
}
/* We're resuming from previous run. */
if (btrfs_super_flags(sb) & BTRFS_SUPER_FLAG_CHANGING_BG_TREE)
goto iterate_bgs;
btrfs_set_super_flags(sb, btrfs_super_flags(sb) | BTRFS_SUPER_FLAG_CHANGING_BG_TREE);
fs_info->last_converted_bg_bytenr = (u64)-1;
/* Now commit the transaction to make the above changes to reach disks. */
ret = btrfs_commit_transaction(trans, fs_info->tree_root);
if (ret < 0) {
errno = -ret;
error_msg(ERROR_MSG_COMMIT_TRANS, "new extent tree root: %m");
goto error;
}
trans = btrfs_start_transaction(fs_info->tree_root, 2);
if (IS_ERR(trans)) {
ret = PTR_ERR(trans);
errno = -ret;
error_msg(ERROR_MSG_START_TRANS, "%m");
return ret;
}
iterate_bgs:
if (fs_info->last_converted_bg_bytenr == (u64)-1) {
ce = last_cache_extent(&fs_info->mapping_tree.cache_tree);
} else {
ce = search_cache_extent(&fs_info->mapping_tree.cache_tree,
fs_info->last_converted_bg_bytenr);
if (!ce) {
error("failed to find block group for bytenr %llu",
fs_info->last_converted_bg_bytenr);
ret = -ENOENT;
goto error;
}
ce = prev_cache_extent(ce);
if (!ce) {
error("no more block groups before bytenr %llu",
fs_info->last_converted_bg_bytenr);
ret = -ENOENT;
goto error;
}
}
/* Now convert each block group. */
while (ce) {
struct cache_extent *prev = prev_cache_extent(ce);
u64 bytenr = ce->start;
ret = btrfs_convert_one_bg(trans, bytenr);
if (ret < 0)
goto error;
converted_bgs++;
ce = prev;
if (converted_bgs % BLOCK_GROUP_BATCH == 0) {
ret = btrfs_commit_transaction(trans, fs_info->tree_root);
if (ret < 0) {
errno = -ret;
error_msg(ERROR_MSG_COMMIT_TRANS, "%m");
return ret;
}
trans = btrfs_start_transaction(fs_info->tree_root, 2);
if (IS_ERR(trans)) {
ret = PTR_ERR(trans);
errno = -ret;
error_msg(ERROR_MSG_COMMIT_TRANS, "%m");
return ret;
}
}
}
/*
* Remove block group tree, at this stage, the block group tree root
* should be empty.
*/
ASSERT(btrfs_header_nritems(fs_info->block_group_root->node) == 0);
ret = btrfs_delete_and_free_root(trans, fs_info->block_group_root);
if (ret < 0) {
errno = -ret;
error("failed to remove bg tree: %m");
goto error;
}
fs_info->block_group_root = NULL;
/*
* All bgs converted and bg tree removed, remove the CHANGING_BG_TREE
* flag and remove the compat ro flag.
*/
fs_info->last_converted_bg_bytenr = 0;
btrfs_set_super_flags(sb,
btrfs_super_flags(sb) & ~BTRFS_SUPER_FLAG_CHANGING_BG_TREE);
btrfs_set_super_compat_ro_flags(sb,
btrfs_super_compat_ro_flags(sb) &
~BTRFS_FEATURE_COMPAT_RO_BLOCK_GROUP_TREE);
ret = btrfs_commit_transaction(trans, fs_info->tree_root);
if (ret < 0) {
errno = -ret;
error_msg(ERROR_MSG_COMMIT_TRANS, "%m");
return ret;
}
pr_verbose(LOG_DEFAULT,
"Converted filesystem with block-group-tree to extent tree feature");
return 0;
error:
btrfs_abort_transaction(trans, ret);
return ret;
}

View File

@ -71,6 +71,7 @@ static const char * const tune_usage[] = {
OPTLINE("-n", "enable no-holes feature (mkfs: no-holes, more efficient sparse file representation)"),
OPTLINE("-S <0|1>", "set/unset seeding status of a device"),
OPTLINE("--enable-block-group-tree", "enable block group tree (mkfs: block-group-tree, for less mount time)"),
OPTLINE("--disable-block-group-tree", "disable block group tree (mkfs: ^block-group-tree)n"),
"",
"UUID changes:",
OPTLINE("-u", "rewrite fsid, use a random one"),
@ -103,6 +104,7 @@ int BOX_MAIN(btrfstune)(int argc, char *argv[])
u64 seeding_value = 0;
int random_fsid = 0;
int change_metadata_uuid = 0;
bool to_extent_tree = false;
bool to_bg_tree = false;
int csum_type = -1;
char *new_fsid_str = NULL;
@ -114,11 +116,14 @@ int BOX_MAIN(btrfstune)(int argc, char *argv[])
while(1) {
enum { GETOPT_VAL_CSUM = GETOPT_VAL_FIRST,
GETOPT_VAL_ENABLE_BLOCK_GROUP_TREE };
GETOPT_VAL_ENABLE_BLOCK_GROUP_TREE,
GETOPT_VAL_DISABLE_BLOCK_GROUP_TREE };
static const struct option long_options[] = {
{ "help", no_argument, NULL, GETOPT_VAL_HELP},
{ "enable-block-group-tree", no_argument, NULL,
GETOPT_VAL_ENABLE_BLOCK_GROUP_TREE},
{ "disable-block-group-tree", no_argument, NULL,
GETOPT_VAL_DISABLE_BLOCK_GROUP_TREE},
#if EXPERIMENTAL
{ "csum", required_argument, NULL, GETOPT_VAL_CSUM },
#endif
@ -165,6 +170,9 @@ int BOX_MAIN(btrfstune)(int argc, char *argv[])
case GETOPT_VAL_ENABLE_BLOCK_GROUP_TREE:
to_bg_tree = true;
break;
case GETOPT_VAL_DISABLE_BLOCK_GROUP_TREE:
to_extent_tree = true;
break;
#if EXPERIMENTAL
case GETOPT_VAL_CSUM:
btrfs_warn_experimental(
@ -189,7 +197,8 @@ int BOX_MAIN(btrfstune)(int argc, char *argv[])
return 1;
}
if (!super_flags && !seeding_flag && !(random_fsid || new_fsid_str) &&
!change_metadata_uuid && csum_type == -1 && !to_bg_tree) {
!change_metadata_uuid && csum_type == -1 && !to_bg_tree &&
!to_extent_tree) {
error("at least one option should be specified");
usage(&tune_cmd, 1);
return 1;
@ -235,7 +244,12 @@ int BOX_MAIN(btrfstune)(int argc, char *argv[])
return 1;
}
if (to_bg_tree) {
if (to_bg_tree) {
if (to_extent_tree) {
error("option --enable-block-group-tree conflicts with --disable-block-group-tree");
ret = 1;
goto out;
}
if (btrfs_fs_compat_ro(root->fs_info, BLOCK_GROUP_TREE)) {
error("the filesystem already has block group tree feature");
ret = 1;
@ -253,6 +267,24 @@ int BOX_MAIN(btrfstune)(int argc, char *argv[])
}
goto out;
}
if (to_extent_tree) {
if (to_bg_tree) {
error("option --enable-block-group-tree conflicts with --disable-block-group-tree");
ret = 1;
goto out;
}
if (!btrfs_fs_compat_ro(root->fs_info, BLOCK_GROUP_TREE)) {
error("filesystem doesn't have block-group-tree feature");
ret = 1;
goto out;
}
ret = convert_to_extent_tree(root->fs_info);
if (ret < 0) {
error("failed to convert the filesystem from block group tree feature");
goto out;
}
goto out;
}
if (seeding_flag) {
if (btrfs_fs_incompat(root->fs_info, METADATA_UUID)) {
error("SEED flag cannot be changed on a metadata-uuid changed fs");

View File

@ -28,6 +28,7 @@ int change_uuid(struct btrfs_fs_info *fs_info, const char *new_fsid_str);
int set_metadata_uuid(struct btrfs_root *root, const char *uuid_string);
int convert_to_bg_tree(struct btrfs_fs_info *fs_info);
int convert_to_extent_tree(struct btrfs_fs_info *fs_info);
int rewrite_checksums(struct btrfs_fs_info *fs_info, int csum_type);