diff mbox series

[2/3] btrfs-progs: check/original: detect and repair ram_bytes mismatch

Message ID 71ec5089099c57cddb6e004b8f79a476dd2874b4.1714640642.git.wqu@suse.com (mailing list archive)
State New
Headers show
Series btrfs-progs: check: detect and repair ram_bytes mismatch for non-compressed data extents | expand

Commit Message

Qu Wenruo May 2, 2024, 9:07 a.m. UTC
For non-compressed non-hole file extent items, the ram_bytes should
match disk_num_bytes.

But due to kernel bugs, we have several cases where ram_bytes is not
correctly updated.

Thankfully this is really a very minor mismatch and can never cause data
corruption since the kernel does not utilize ram_bytes for
non-compressed extents at all.

So here we just detect and repair it for original mode.

Signed-off-by: Qu Wenruo <wqu@suse.com>
---
 check/main.c          | 126 ++++++++++++++++++++++++++++++++++++++++--
 check/mode-original.h |   8 +++
 2 files changed, 130 insertions(+), 4 deletions(-)
diff mbox series

Patch

diff --git a/check/main.c b/check/main.c
index 93af325f2525..6c5f61fe1bb1 100644
--- a/check/main.c
+++ b/check/main.c
@@ -493,6 +493,33 @@  static int device_record_compare(const struct rb_node *node1, const struct rb_no
 		return 0;
 }
 
+static int add_mismatch_ram_bytes_record(struct inode_record *inode_rec,
+					 struct btrfs_key *key)
+{
+	struct mismatch_ram_bytes_record *record;
+
+	record = malloc(sizeof(*record));
+	if (!record) {
+		error_msg(ERROR_MSG_MEMORY, "mismatch ram bytes record");
+		return -ENOMEM;
+	}
+	memcpy(&record->key, key, sizeof(*key));
+	list_add_tail(&record->list, &inode_rec->mismatch_ram_bytes);
+	return 0;
+}
+
+static void free_mismatch_ram_bytes_records(struct inode_record *inode_rec)
+{
+	if (!list_empty(&inode_rec->mismatch_ram_bytes)) {
+		struct mismatch_ram_bytes_record *ram;
+
+		ram = list_entry(inode_rec->mismatch_ram_bytes.next,
+				 struct mismatch_ram_bytes_record, list);
+		list_del(&ram->list);
+		free(ram);
+	}
+}
+
 static struct inode_record *clone_inode_rec(struct inode_record *orig_rec)
 {
 	struct inode_record *rec;
@@ -501,6 +528,7 @@  static struct inode_record *clone_inode_rec(struct inode_record *orig_rec)
 	struct inode_backref *tmp;
 	struct mismatch_dir_hash_record *hash_record;
 	struct mismatch_dir_hash_record *new_record;
+	struct mismatch_ram_bytes_record *ram_record;
 	struct unaligned_extent_rec_t *src;
 	struct unaligned_extent_rec_t *dst;
 	struct rb_node *rb;
@@ -514,6 +542,7 @@  static struct inode_record *clone_inode_rec(struct inode_record *orig_rec)
 	rec->refs = 1;
 	INIT_LIST_HEAD(&rec->backrefs);
 	INIT_LIST_HEAD(&rec->mismatch_dir_hash);
+	INIT_LIST_HEAD(&rec->mismatch_ram_bytes);
 	INIT_LIST_HEAD(&rec->unaligned_extent_recs);
 	rec->holes = RB_ROOT;
 
@@ -537,6 +566,11 @@  static struct inode_record *clone_inode_rec(struct inode_record *orig_rec)
 		memcpy(&new_record, hash_record, size);
 		list_add_tail(&new_record->list, &rec->mismatch_dir_hash);
 	}
+	list_for_each_entry(ram_record, &orig_rec->mismatch_ram_bytes, list) {
+		ret = add_mismatch_ram_bytes_record(rec, &ram_record->key);
+		if (ret < 0)
+			goto cleanup;
+	}
 	list_for_each_entry(src, &orig_rec->unaligned_extent_recs, list) {
 		size = sizeof(*src);
 		dst = malloc(size);
@@ -578,6 +612,7 @@  cleanup:
 			free(hash_record);
 		}
 	}
+	free_mismatch_ram_bytes_records(rec);
 	if (!list_empty(&rec->unaligned_extent_recs))
 		list_for_each_entry_safe(src, dst, &rec->unaligned_extent_recs,
 				list) {
@@ -619,6 +654,8 @@  static void print_inode_error(struct btrfs_root *root, struct inode_record *rec)
 		fprintf(stderr, ", odd file extent");
 	if (errors & I_ERR_BAD_FILE_EXTENT)
 		fprintf(stderr, ", bad file extent");
+	if (errors & I_ERR_RAM_BYTES_MISMATCH)
+		fprintf(stderr, ", bad ram bytes for non-compressed extents");
 	if (errors & I_ERR_FILE_EXTENT_OVERLAP)
 		fprintf(stderr, ", file extent overlap");
 	if (errors & I_ERR_FILE_EXTENT_TOO_LARGE)
@@ -637,8 +674,6 @@  static void print_inode_error(struct btrfs_root *root, struct inode_record *rec)
 		fprintf(stderr, ", link count wrong");
 	if (errors & I_ERR_ODD_INODE_FLAGS)
 		fprintf(stderr, ", odd inode flags");
-	if (errors & I_ERR_INLINE_RAM_BYTES_WRONG)
-		fprintf(stderr, ", invalid inline ram bytes");
 	if (errors & I_ERR_INVALID_IMODE)
 		fprintf(stderr, ", invalid inode mode bit 0%o",
 			rec->imode & ~07777);
@@ -699,6 +734,17 @@  static void print_inode_error(struct btrfs_root *root, struct inode_record *rec)
 				hash_record->key.offset);
 		}
 	}
+	if (errors & I_ERR_RAM_BYTES_MISMATCH) {
+		struct mismatch_ram_bytes_record *ram_record;
+
+		fprintf(stderr,
+		"Non-compressed file extents with invalid ram_bytes (minor errors):\n");
+		list_for_each_entry(ram_record, &rec->mismatch_ram_bytes, list) {
+			fprintf(stderr, "\tino=%llu offset=%llu\n",
+				ram_record->key.objectid,
+				ram_record->key.offset);
+		}
+	}
 }
 
 static void print_ref_error(int errors)
@@ -760,6 +806,7 @@  static struct inode_record *get_inode_rec(struct cache_tree *inode_cache,
 		rec->refs = 1;
 		INIT_LIST_HEAD(&rec->backrefs);
 		INIT_LIST_HEAD(&rec->mismatch_dir_hash);
+		INIT_LIST_HEAD(&rec->mismatch_ram_bytes);
 		INIT_LIST_HEAD(&rec->unaligned_extent_recs);
 		rec->holes = RB_ROOT;
 
@@ -811,6 +858,14 @@  static void free_inode_rec(struct inode_record *rec)
 		list_del(&backref->list);
 		free(backref);
 	}
+	while (!list_empty(&rec->mismatch_ram_bytes)) {
+		struct mismatch_ram_bytes_record *ram;
+
+		ram = list_entry(rec->mismatch_ram_bytes.next,
+				 struct mismatch_ram_bytes_record, list);
+		list_del(&ram->list);
+		free(ram);
+	}
 	list_for_each_entry_safe(hash, next, &rec->mismatch_dir_hash, list)
 		free(hash);
 	free_unaligned_extent_recs(&rec->unaligned_extent_recs);
@@ -821,7 +876,8 @@  static void free_inode_rec(struct inode_record *rec)
 static bool can_free_inode_rec(struct inode_record *rec)
 {
 	if (!rec->errors && rec->checked && rec->found_inode_item &&
-	    rec->nlink == rec->found_link && list_empty(&rec->backrefs))
+	    rec->nlink == rec->found_link && list_empty(&rec->backrefs) &&
+	    list_empty(&rec->mismatch_ram_bytes))
 		return true;
 	return false;
 }
@@ -1742,6 +1798,14 @@  static int process_file_extent(struct btrfs_root *root,
 			rec->errors |= I_ERR_BAD_FILE_EXTENT;
 		if (compression && rec->nodatasum)
 			rec->errors |= I_ERR_BAD_FILE_EXTENT;
+		if (disk_bytenr && !compression &&
+		    btrfs_file_extent_ram_bytes(eb, fi) !=
+		    btrfs_file_extent_disk_num_bytes(eb, fi)) {
+			rec->errors |= I_ERR_RAM_BYTES_MISMATCH;
+			ret = add_mismatch_ram_bytes_record(rec, key);
+			if (ret < 0)
+				return ret;
+		}
 		if (disk_bytenr > 0)
 			rec->found_size += num_bytes;
 	} else {
@@ -3044,6 +3108,57 @@  static int repair_inode_gen_original(struct btrfs_trans_handle *trans,
 	return 0;
 }
 
+static int repair_ram_bytes(struct btrfs_trans_handle *trans,
+			    struct btrfs_root *root,
+			    struct btrfs_path *path,
+			    struct inode_record *rec)
+{
+	struct mismatch_ram_bytes_record *record;
+	struct mismatch_ram_bytes_record *tmp;
+	int ret = 0;
+
+	btrfs_release_path(path);
+	list_for_each_entry_safe(record, tmp, &rec->mismatch_ram_bytes, list) {
+		struct btrfs_file_extent_item *fi;
+		struct extent_buffer *leaf;
+		int type;
+		int slot;
+		int search_ret;
+
+		search_ret = btrfs_search_slot(trans, root, &record->key, path, 0, 1);
+		if (search_ret > 0)
+			search_ret = -ENOENT;
+		if (search_ret < 0) {
+			ret = search_ret;
+			btrfs_release_path(path);
+			continue;
+		}
+		leaf = path->nodes[0];
+		slot = path->slots[0];
+		fi = btrfs_item_ptr(leaf, slot, struct btrfs_file_extent_item);
+		type = btrfs_file_extent_type(leaf, fi);
+		if (type != BTRFS_FILE_EXTENT_REG &&
+		    type != BTRFS_FILE_EXTENT_PREALLOC) {
+			ret = -EUCLEAN;
+			btrfs_release_path(path);
+			continue;
+		}
+		if (btrfs_file_extent_disk_bytenr(path->nodes[0], fi) == 0 ||
+		    btrfs_file_extent_compression(path->nodes[0], fi)) {
+			ret = -EUCLEAN;
+			btrfs_release_path(path);
+			continue;
+		}
+		btrfs_set_file_extent_ram_bytes(leaf, fi,
+				btrfs_file_extent_disk_num_bytes(leaf, fi));
+		btrfs_mark_buffer_dirty(leaf);
+		btrfs_release_path(path);
+	}
+	if (!ret)
+		rec->errors &= ~I_ERR_RAM_BYTES_MISMATCH;
+	return ret;
+}
+
 static int try_repair_inode(struct btrfs_root *root, struct inode_record *rec)
 {
 	struct btrfs_trans_handle *trans;
@@ -3066,7 +3181,8 @@  static int try_repair_inode(struct btrfs_root *root, struct inode_record *rec)
 			     I_ERR_MISMATCH_DIR_HASH |
 			     I_ERR_UNALIGNED_EXTENT_REC |
 			     I_ERR_INVALID_IMODE |
-			     I_ERR_INVALID_GEN)))
+			     I_ERR_INVALID_GEN |
+			     I_ERR_RAM_BYTES_MISMATCH)))
 		return rec->errors;
 
 	/*
@@ -3106,6 +3222,8 @@  static int try_repair_inode(struct btrfs_root *root, struct inode_record *rec)
 		ret = repair_unaligned_extent_recs(trans, root, &path, rec);
 	if (!ret && rec->errors & I_ERR_INVALID_GEN)
 		ret = repair_inode_gen_original(trans, root, &path, rec);
+	if (!ret && rec->errors & I_ERR_RAM_BYTES_MISMATCH)
+		ret = repair_ram_bytes(trans, root, &path, rec);
 	btrfs_release_path(&path);
 	ret = btrfs_commit_transaction(trans, root);
 	if (ret < 0) {
diff --git a/check/mode-original.h b/check/mode-original.h
index ac8de57cc5d4..fbc6c2e5bd0f 100644
--- a/check/mode-original.h
+++ b/check/mode-original.h
@@ -189,6 +189,8 @@  struct unaligned_extent_rec_t {
 #define I_ERR_INVALID_GEN		(1U << 20)
 #define I_ERR_INVALID_NLINK		(1U << 21)
 #define I_ERR_INVALID_XATTR		(1U << 22)
+/* Ram_bytes mismatch for non-compressed data extents. */
+#define I_ERR_RAM_BYTES_MISMATCH	(1U << 23)
 
 struct inode_record {
 	struct list_head backrefs;
@@ -216,6 +218,7 @@  struct inode_record {
 	u64 extent_end;
 	struct rb_root holes;
 	struct list_head mismatch_dir_hash;
+	struct list_head mismatch_ram_bytes;
 
 	u32 refs;
 };
@@ -232,6 +235,11 @@  struct mismatch_dir_hash_record {
 	/* namebuf follows here */
 };
 
+struct mismatch_ram_bytes_record {
+	struct list_head list;
+	struct btrfs_key key;
+};
+
 struct root_backref {
 	struct list_head list;
 	unsigned int found_dir_item:1;