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 <mark@harmstone.com>
Signed-off-by: David Sterba <dsterba@suse.com>
This commit is contained in:
Mark Harmstone 2023-04-30 21:32:25 +01:00 committed by David Sterba
parent 0927d3056b
commit 6cf11f3e38

View file

@ -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);