btrfs-progs: fix a false failure for inode cache cleanup

[BUG]
There is one report about `btrfs rescue clear-ino-cache` failed with
tree block level mismatch:

 # btrfs rescue clear-ino-cache /dev/mapper/rootext
 Successfully cleaned up ino cache for root id: 5
 Successfully cleaned up ino cache for root id: 257
 Successfully cleaned up ino cache for root id: 258
 corrupt node: root=7 block=647369064448 slot=0, invalid level for leaf, have 1 expect 0
 node 647369064448 level 1 items 252 free space 241 generation 6065173 owner CSUM_TREE
 node 647369064448 flags 0x1(WRITTEN) backref revision 1
 fs uuid e6614f01-6f56-4776-8b0a-c260089c35e7
 chunk uuid f665f535-4cfd-49e0-8be9-7f94bf59b75d
     key (EXTENT_CSUM EXTENT_CSUM 3714473984) block 677126111232 gen 6065002
     [...]
     key (EXTENT_CSUM EXTENT_CSUM 6192357376) block 646396493824 gen 6065032
 ERROR: failed to clear ino cache: Input/output error

[CAUSE]
During `btrfs rescue clear-ino-cache`, btrfs-progs will iterate through
all the subvolumes, and clear the inode cache inode from each subvolume.

The problem is in how we iterate the subvolumes.

We hold a path of tree root, and go modifiy the fs for each found
subvolume, then call btrfs_next_item().

This is not safe, because the path to tree root is not longer reliable
if we modified the fs.

So the btrfs_next_item() call will fail because the fs is modified
halfway, resulting the above problem.

[FIX]
Instead of holding a path to a subvolume root item, and modify the fs
halfway, here introduce a helper, find_next_root(), to locate the root
item whose objectid >= our target rootid, and return the found item key.

The path to root tree is only hold then released inside
find_next_root().

By this, we won't hold any unrelated path while modifying the
filesystem.

And since we're here, also adding back the missing new line when all ino
cache is cleared.

Pull-request: #890
Reported-by: Archange <archange@archlinux.org>
Link: https://lore.kernel.org/linux-btrfs/4803f696-2dc5-4987-a353-fce1272e93e7@archlinux.org/
Signed-off-by: Qu Wenruo <wqu@suse.com>
This commit is contained in:
Qu Wenruo 2024-09-12 07:56:05 +09:30 committed by David Sterba
parent 18ecbfd3dd
commit 85225ea00a
2 changed files with 71 additions and 54 deletions

View File

@ -449,7 +449,7 @@ static int cmd_rescue_clear_ino_cache(const struct cmd_struct *cmd,
errno = -ret;
error("failed to clear ino cache: %m");
} else {
pr_verbose(LOG_DEFAULT, "Successfully cleared ino cache");
pr_verbose(LOG_DEFAULT, "Successfully cleared ino cache\n");
}
close_ctree(fs_info->tree_root);
out:

View File

@ -555,69 +555,86 @@ out:
return ret;
}
int clear_ino_cache_items(struct btrfs_fs_info *fs_info)
/*
* Find a root item whose key.objectid >= @rootid, and save the found
* key into @found_key.
*
* Return 0 if a root item is found.
* Return >0 if no more root item is found.
* Return <0 for error.
*/
static int find_next_root(struct btrfs_fs_info *fs_info, u64 rootid,
struct btrfs_key *found_key)
{
int ret;
struct btrfs_key key = {
.objectid = rootid,
.type = BTRFS_ROOT_ITEM_KEY,
.offset = 0,
};
struct btrfs_path path = { 0 };
struct btrfs_key key;
key.objectid = BTRFS_FS_TREE_OBJECTID;
key.type = BTRFS_ROOT_ITEM_KEY;
key.offset = 0;
int ret;
ret = btrfs_search_slot(NULL, fs_info->tree_root, &key, &path, 0, 0);
if (ret < 0)
return ret;
while(1) {
struct btrfs_key found_key;
btrfs_item_key_to_cpu(path.nodes[0], &found_key, path.slots[0]);
if (found_key.type == BTRFS_ROOT_ITEM_KEY &&
is_fstree(found_key.objectid)) {
struct btrfs_root *root;
found_key.offset = (u64)-1;
root = btrfs_read_fs_root(fs_info, &found_key);
if (IS_ERR(root))
goto next;
ret = truncate_free_ino_items(root);
if (ret)
goto out;
printf("Successfully cleaned up ino cache for root id: %lld\n",
root->objectid);
} else {
/* If we get a negative tree this means it's the last one */
if ((s64)found_key.objectid < 0 &&
found_key.type == BTRFS_ROOT_ITEM_KEY)
goto out;
}
/*
* Only fs roots contain an ino cache information - either
* FS_TREE_OBJECTID or subvol id >= BTRFS_FIRST_FREE_OBJECTID
*/
next:
if (key.objectid == BTRFS_FS_TREE_OBJECTID) {
key.objectid = BTRFS_FIRST_FREE_OBJECTID;
btrfs_release_path(&path);
ret = btrfs_search_slot(NULL, fs_info->tree_root, &key,
&path, 0, 0);
if (ret < 0)
return ret;
} else {
ret = btrfs_next_item(fs_info->tree_root, &path);
if (ret < 0) {
goto out;
} else if (ret > 0) {
ret = 0;
goto out;
}
while (true) {
btrfs_item_key_to_cpu(path.nodes[0], &key, path.slots[0]);
if (key.type == BTRFS_ROOT_ITEM_KEY && key.objectid >= rootid) {
memcpy(found_key, &key, sizeof(key));
ret = 0;
goto out;
}
ret = btrfs_next_item(fs_info->tree_root, &path);
if (ret)
goto out;
}
out:
btrfs_release_path(&path);
return ret;
}
int clear_ino_cache_items(struct btrfs_fs_info *fs_info)
{
u64 cur_subvol = BTRFS_FS_TREE_OBJECTID;
int ret;
while (1) {
struct btrfs_key key = { 0 };
struct btrfs_root *root;
ret = find_next_root(fs_info, cur_subvol, &key);
if (ret < 0) {
errno = -ret;
error("failed to find the next root item for rootid %llu: %m",
cur_subvol);
break;
}
if (ret > 0 || !is_fstree(key.objectid)) {
ret = 0;
break;
}
root = btrfs_read_fs_root(fs_info, &key);
if (IS_ERR(root)) {
ret = PTR_ERR(root);
errno = -ret;
error("failed to read root %llu: %m", key.objectid);
break;
}
ret = truncate_free_ino_items(root);
if (ret < 0) {
errno = -ret;
error("failed to clean up ino cache for root %llu: %m",
key.objectid);
break;
}
printf("Successfully cleaned up ino cache for root id: %lld\n",
root->objectid);
if (cur_subvol == BTRFS_FS_TREE_OBJECTID)
cur_subvol = BTRFS_FIRST_FREE_OBJECTID;
else
cur_subvol = root->objectid + 1;
}
return ret;
}