From 6cf11f3e3815da5029b53b71fd6cec375c440c9f Mon Sep 17 00:00:00 2001 From: Mark Harmstone Date: Sun, 30 Apr 2023 21:32:25 +0100 Subject: [PATCH] btrfs-progs: check: check order of inline extent refs The kernel seems to order inline extent items in a particular way: forward by sub-type, then reverse by hash. Having these out of order can cause a volume to go readonly when deleting an inode. See https://github.com/maharmstone/ntfs2btrfs/issues/51 With additional comments from the pull request: - lookup_inline_extent_backref() is skipping the remaining backref item if data/metadata item is smaller (either through the data hash, or metadata parent/ref_root) than the target range - the fix could be still missing in lowmem mode - image could be created according this comment https://github.com/maharmstone/ntfs2btrfs/issues/51#issuecomment-1500781204 - due to late merge, the squota newly added key BTRFS_EXTENT_OWNER_REF_KEY was not part of the patch and the value of 'hash' needs to be verified Pull-request: #622 Signed-off-by: Mark Harmstone Signed-off-by: David Sterba --- check/main.c | 42 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/check/main.c b/check/main.c index 62824907..6cf80166 100644 --- a/check/main.c +++ b/check/main.c @@ -5534,13 +5534,14 @@ static int process_extent_item(struct btrfs_root *root, unsigned long end; unsigned long ptr; int ret; - int type; + int type, last_type; u32 item_size = btrfs_item_size(eb, slot); u64 refs = 0; u64 offset; u64 num_bytes; u64 gen; int metadata = 0; + u64 last_hash, hash; btrfs_item_key_to_cpu(eb, &key, slot); @@ -5623,13 +5624,19 @@ static int process_extent_item(struct btrfs_root *root, key.type == BTRFS_EXTENT_ITEM_KEY) ptr += sizeof(struct btrfs_tree_block_info); + last_hash = U64_MAX; + last_type = 0; + end = (unsigned long)ei + item_size; while (ptr < end) { iref = (struct btrfs_extent_inline_ref *)ptr; type = btrfs_extent_inline_ref_type(eb, iref); offset = btrfs_extent_inline_ref_offset(eb, iref); + switch (type) { case BTRFS_TREE_BLOCK_REF_KEY: + hash = offset; + ret = add_tree_backref(extent_cache, key.objectid, 0, offset, 0); if (ret < 0) { @@ -5639,6 +5646,8 @@ static int process_extent_item(struct btrfs_root *root, } break; case BTRFS_SHARED_BLOCK_REF_KEY: + hash = offset; + ret = add_tree_backref(extent_cache, key.objectid, offset, 0, 0); if (ret < 0) { @@ -5649,6 +5658,12 @@ static int process_extent_item(struct btrfs_root *root, break; case BTRFS_EXTENT_DATA_REF_KEY: dref = (struct btrfs_extent_data_ref *)(&iref->offset); + + hash = hash_extent_data_ref( + btrfs_extent_data_ref_root(eb, dref), + btrfs_extent_data_ref_objectid(eb, dref), + btrfs_extent_data_ref_offset(eb, dref)); + add_data_backref(extent_cache, key.objectid, 0, btrfs_extent_data_ref_root(eb, dref), btrfs_extent_data_ref_objectid(eb, @@ -5658,6 +5673,8 @@ static int process_extent_item(struct btrfs_root *root, gen, 0, num_bytes); break; case BTRFS_SHARED_DATA_REF_KEY: + hash = offset; + sref = (struct btrfs_shared_data_ref *)(iref + 1); add_data_backref(extent_cache, key.objectid, offset, 0, 0, 0, @@ -5665,6 +5682,7 @@ static int process_extent_item(struct btrfs_root *root, gen, 0, num_bytes); break; case BTRFS_EXTENT_OWNER_REF_KEY: + hash = offset; break; default: fprintf(stderr, @@ -5672,6 +5690,28 @@ static int process_extent_item(struct btrfs_root *root, key.objectid, key.type, num_bytes); goto out; } + + if (type != last_type) { + last_hash = U64_MAX; + + if (type < last_type) { + fprintf(stderr, + "inline extent refs out of order: key [%llu,%u,%llu]\n", + key.objectid, key.type, num_bytes); + goto out; + } + + last_type = type; + } + + if (hash > last_hash) { + fprintf(stderr, + "inline extent refs out of order: key [%llu,%u,%llu]\n", + key.objectid, key.type, num_bytes); + goto out; + } + + last_hash = hash; ptr += btrfs_extent_inline_ref_size(type); } WARN_ON(ptr > end);