btrfs: always use the second newest slot for rescue=usebackuproot

[RESCUE FAILURE]
There is a bug report that "rescue=usebackuproot" failed to properly
mount the fs, but if using btrfs-mod-sb to use the second newest slot,
then the fs can even pass the full "btrfs check" and be mounted RW.

[CAUSE]
During init_tree_roots(), "rescue=usebackuproot" only makes a difference
if the tree and chunk roots failed to be load.

The loading only requires the root node/leaf to be properly loaded:

- tree root
- extent tree
- block group tree
- device tree
- data reloc tree
- quota tree (optional)
- uuid tree
- raid stripe tree (for RST feature)

And it doesn't really check any child tree blocks, thus for a lot of
cases, "rescue=usebackuproot" won't make a difference.

[ENHANCEMENT]
Instead of trying every slot and settling down on the first passing
backup slot, always use the second newest slot.

The newest slot should always match the root info in the super block,
and if it worked the end user should not even need to bother to use
backup root.

Older backup slots normally have a very low chance to work.

Link: https://github.com/kdave/btrfs-progs/issues/1162#issuecomment-5475477108
Reviewed-by: Boris Burkov <boris@bur.io>
Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
This commit is contained in:
Qu Wenruo 2026-09-06 16:33:37 +09:30 committed by David Sterba
parent d00b85308a
commit c73eb6f727

View File

@ -2711,73 +2711,55 @@ static int __cold init_tree_roots(struct btrfs_fs_info *fs_info)
int backup_index = find_newest_super_backup(fs_info);
struct btrfs_super_block *sb = fs_info->super_copy;
struct btrfs_root *tree_root = fs_info->tree_root;
bool handle_error = false;
bool use_backup = btrfs_test_opt(fs_info, USEBACKUPROOT);
int ret = 0;
int i;
for (i = 0; i < BTRFS_NUM_BACKUP_ROOTS; i++) {
if (handle_error) {
if (!IS_ERR(tree_root->node))
free_extent_buffer(tree_root->node);
tree_root->node = NULL;
if (!btrfs_test_opt(fs_info, USEBACKUPROOT))
break;
free_root_pointers(fs_info, 0);
/*
* Don't use the log in recovery mode, it won't be
* valid
*/
btrfs_set_super_log_root(sb, 0);
btrfs_warn(fs_info, "try to load backup roots slot %d", i);
ret = read_backup_root(fs_info, i);
backup_index = ret;
if (ret < 0)
return ret;
if (use_backup) {
ret = read_backup_root(fs_info, 1);
if (ret < 0) {
btrfs_err(fs_info,
"failed to load backup roots at slot %d", 1);
return ret;
}
ret = load_important_roots(fs_info);
if (ret) {
handle_error = true;
continue;
}
/*
* No need to hold btrfs_root::objectid_mutex since the fs
* hasn't been fully initialised and we are the only user
* Don't use the log in recovery mode, it won't be
* valid
*/
ret = btrfs_init_root_free_objectid(tree_root);
if (ret < 0) {
handle_error = true;
continue;
}
ASSERT(tree_root->free_objectid <= BTRFS_LAST_FREE_OBJECTID);
ret = btrfs_read_roots(fs_info);
if (ret < 0) {
handle_error = true;
continue;
}
/* All successful */
fs_info->generation = btrfs_header_generation(tree_root->node);
btrfs_set_last_trans_committed(fs_info, fs_info->generation);
fs_info->last_reloc_trans = 0;
/* Always begin writing backup roots after the one being used */
if (backup_index < 0) {
fs_info->backup_root_index = 0;
} else {
fs_info->backup_root_index = backup_index + 1;
fs_info->backup_root_index %= BTRFS_NUM_BACKUP_ROOTS;
}
break;
btrfs_set_super_log_root(sb, 0);
backup_index = ret;
btrfs_warn(fs_info, "loaded backup roots at slot %d", 1);
}
ret = load_important_roots(fs_info);
if (ret)
return ret;
/*
* No need to hold btrfs_root::objectid_mutex since the fs
* hasn't been fully initialised and we are the only user
*/
ret = btrfs_init_root_free_objectid(tree_root);
if (ret < 0)
return ret;
ASSERT(tree_root->free_objectid <= BTRFS_LAST_FREE_OBJECTID);
ret = btrfs_read_roots(fs_info);
if (ret < 0)
return ret;
/* All successful */
fs_info->generation = btrfs_header_generation(tree_root->node);
btrfs_set_last_trans_committed(fs_info, fs_info->generation);
fs_info->last_reloc_trans = 0;
/* Always begin writing backup roots after the one being used */
if (backup_index < 0) {
fs_info->backup_root_index = 0;
} else {
fs_info->backup_root_index = backup_index + 1;
fs_info->backup_root_index %= BTRFS_NUM_BACKUP_ROOTS;
}
return ret;
}