diff mbox series

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

Message ID 6a7acf399863f0ad8627a082a92ef8fc332c2642.1726094645.git.wqu@suse.com (mailing list archive)
State New
Headers show
Series btrfs-progs: fix a false failure for inode cache cleanup | expand

Commit Message

Qu Wenruo Sept. 11, 2024, 10:45 p.m. UTC
[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.

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>
---
 cmds/rescue.c        |   2 +-
 common/clear-cache.c | 122 ++++++++++++++++++++++++-------------------
 2 files changed, 70 insertions(+), 54 deletions(-)
diff mbox series

Patch

diff --git a/cmds/rescue.c b/cmds/rescue.c
index 5bbd47e5c2e3..c60bf11675b9 100644
--- a/cmds/rescue.c
+++ b/cmds/rescue.c
@@ -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:
diff --git a/common/clear-cache.c b/common/clear-cache.c
index 960c3466ce44..f0bc40753e6d 100644
--- a/common/clear-cache.c
+++ b/common/clear-cache.c
@@ -555,69 +555,85 @@  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: %m");
+			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;
+}
+