btrfs-progs: rescue: introduce clear-uuid-tree

[BUG]
There is a bug report that a corrupted key type (expected
UUID_KEY_SUBVOL, has EXTENT_ITEM) causing newer kernel to reject a
mount.

Although the root cause is not determined yet, with roll out of v5.11
kernel to various distros, such problem should be prevented by
tree-checker, no matter if it's hardware problem or not.

And older kernel with "-o uuid_rescan" mount option won't help, as
uuid_rescan will only delete items with
UUID_KEY_SUBVOL/UUID_KEY_RECEIVED_SUBVOL key types, not deleting such
corrupted key.

[FIX]
To fix such problem we have to rely on offline tool, thus there we
introduce a new rescue tool, clear-uuid-tree, to empty and then remove
uuid tree.

Kernel will re-generate the correct uuid tree at next mount.

Reported-by: S. <sb56637@gmail.com>
Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
This commit is contained in:
Qu Wenruo 2021-11-11 16:14:33 +08:00 committed by David Sterba
parent c0ad9bde42
commit 77db6bd402
3 changed files with 120 additions and 0 deletions

View file

@ -50,6 +50,14 @@ The mismatch may also exhibit as a kernel warning:
WARNING: CPU: 3 PID: 439 at fs/btrfs/ctree.h:1559 btrfs_update_device+0x1c5/0x1d0 [btrfs]
----
*clear-uuid-tree* <device>::
Clear uuid tree, so that kernel can re-generate it at next read-write mount.
+
Since kernel v4.16 there and more sanity check performed, and sometimes
non-critical trees like uuid tree can cause problems and reject the mount.
In such case, clearing uuid tree may make the filesystem to be mountable again
without much risk as it's built from other trees.
*super-recover* [options] <device>::
Recover bad superblocks from good copies.
+

View file

@ -50,6 +50,15 @@ fix-device-size <device>
WARNING: CPU: 3 PID: 439 at fs/btrfs/ctree.h:1559 btrfs_update_device+0x1c5/0x1d0 [btrfs]
clear-uuid-tree <device>
Clear uuid tree, so that kernel can re-generate it at next read-write
mount.
Since kernel v4.16 there are more sanity check performed, and sometimes
non-critical trees like uuid tree can cause problems and reject the mount.
In such case, clearing uuid tree may make the filesystem to be mountable again
without much risk as it's built from other trees.
super-recover [options] <device>
Recover bad superblocks from good copies.

View file

@ -296,6 +296,108 @@ static int cmd_rescue_create_control_device(const struct cmd_struct *cmd,
}
static DEFINE_SIMPLE_COMMAND(rescue_create_control_device, "create-control-device");
static int clear_uuid_tree(struct btrfs_fs_info *fs_info)
{
struct btrfs_root *uuid_root = fs_info->uuid_root;
struct btrfs_trans_handle *trans;
struct btrfs_path path = {};
struct btrfs_key key = {};
int ret;
if (!uuid_root)
return 0;
fs_info->uuid_root = NULL;
trans = btrfs_start_transaction(fs_info->tree_root, 0);
if (IS_ERR(trans))
return PTR_ERR(trans);
while (1) {
int nr;
ret = btrfs_search_slot(trans, uuid_root, &key, &path, -1, 1);
if (ret < 0)
goto out;
ASSERT(ret > 0);
ASSERT(path.slots[0] == 0);
nr = btrfs_header_nritems(path.nodes[0]);
if (nr == 0) {
btrfs_release_path(&path);
break;
}
ret = btrfs_del_items(trans, uuid_root, &path, 0, nr);
btrfs_release_path(&path);
if (ret < 0)
goto out;
}
ret = btrfs_del_root(trans, fs_info->tree_root, &uuid_root->root_key);
if (ret < 0)
goto out;
list_del(&uuid_root->dirty_list);
ret = clean_tree_block(uuid_root->node);
if (ret < 0)
goto out;
ret = btrfs_free_tree_block(trans, uuid_root, uuid_root->node, 0, 1);
if (ret < 0)
goto out;
free_extent_buffer(uuid_root->node);
free_extent_buffer(uuid_root->commit_root);
kfree(uuid_root);
out:
if (ret < 0)
btrfs_abort_transaction(trans, ret);
else
ret = btrfs_commit_transaction(trans, fs_info->tree_root);
return ret;
}
static const char * const cmd_rescue_clear_uuid_tree_usage[] = {
"btrfs rescue clear-uuid-tree",
"Delete uuid tree so that kernel can rebuild it at mount time",
NULL,
};
static int cmd_rescue_clear_uuid_tree(const struct cmd_struct *cmd,
int argc, char **argv)
{
struct btrfs_fs_info *fs_info;
struct open_ctree_flags ocf = {};
char *devname;
int ret;
clean_args_no_options(cmd, argc, argv);
if (check_argc_exact(argc, 2))
return -EINVAL;
devname = argv[optind];
ret = check_mounted(devname);
if (ret < 0) {
errno = -ret;
error("could not check mount status: %m");
goto out;
} else if (ret) {
error("%s is currently mounted", devname);
ret = -EBUSY;
goto out;
}
ocf.filename = devname;
ocf.flags = OPEN_CTREE_WRITES | OPEN_CTREE_PARTIAL;
fs_info = open_ctree_fs_info(&ocf);
if (!fs_info) {
error("could not open btrfs");
ret = -EIO;
goto out;
}
ret = clear_uuid_tree(fs_info);
close_ctree(fs_info->tree_root);
out:
return !!ret;
}
static DEFINE_SIMPLE_COMMAND(rescue_clear_uuid_tree, "clear-uuid-tree");
static const char rescue_cmd_group_info[] =
"toolbox for specific rescue operations";
@ -306,6 +408,7 @@ static const struct cmd_group rescue_cmd_group = {
&cmd_struct_rescue_zero_log,
&cmd_struct_rescue_fix_device_size,
&cmd_struct_rescue_create_control_device,
&cmd_struct_rescue_clear_uuid_tree,
NULL
}
};