Skip to content

Commit 4fc7b57

Browse files
fdmananakdave
authored andcommitted
btrfs: fix processing of delayed data refs during backref walking
When processing delayed data references during backref walking and we are using a share context (we are being called through fiemap), whenever we find a delayed data reference for an inode different from the one we are interested in, then we immediately exit and consider the data extent as shared. This is wrong, because: 1) This might be a DROP reference that will cancel out a reference in the extent tree; 2) Even if it's an ADD reference, it may be followed by a DROP reference that cancels it out. In either case we should not exit immediately. Fix this by never exiting when we find a delayed data reference for another inode - instead add the reference and if it does not cancel out other delayed reference, we will exit early when we call extent_is_shared() after processing all delayed references. If we find a drop reference, then signal the code that processes references from the extent tree (add_inline_refs() and add_keyed_refs()) to not exit immediately if it finds there a reference for another inode, since we have delayed drop references that may cancel it out. In this later case we exit once we don't have references in the rb trees that cancel out each other and have two references for different inodes. Example reproducer for case 1): $ cat test-1.sh #!/bin/bash DEV=/dev/sdj MNT=/mnt/sdj mkfs.btrfs -f $DEV mount $DEV $MNT xfs_io -f -c "pwrite 0 64K" $MNT/foo cp --reflink=always $MNT/foo $MNT/bar echo echo "fiemap after cloning:" xfs_io -c "fiemap -v" $MNT/foo rm -f $MNT/bar echo echo "fiemap after removing file bar:" xfs_io -c "fiemap -v" $MNT/foo umount $MNT Running it before this patch, the extent is still listed as shared, it has the flag 0x2000 (FIEMAP_EXTENT_SHARED) set: $ ./test-1.sh fiemap after cloning: /mnt/sdj/foo: EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS 0: [0..127]: 26624..26751 128 0x2001 fiemap after removing file bar: /mnt/sdj/foo: EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS 0: [0..127]: 26624..26751 128 0x2001 Example reproducer for case 2): $ cat test-2.sh #!/bin/bash DEV=/dev/sdj MNT=/mnt/sdj mkfs.btrfs -f $DEV mount $DEV $MNT xfs_io -f -c "pwrite 0 64K" $MNT/foo cp --reflink=always $MNT/foo $MNT/bar # Flush delayed references to the extent tree and commit current # transaction. sync echo echo "fiemap after cloning:" xfs_io -c "fiemap -v" $MNT/foo rm -f $MNT/bar echo echo "fiemap after removing file bar:" xfs_io -c "fiemap -v" $MNT/foo umount $MNT Running it before this patch, the extent is still listed as shared, it has the flag 0x2000 (FIEMAP_EXTENT_SHARED) set: $ ./test-2.sh fiemap after cloning: /mnt/sdj/foo: EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS 0: [0..127]: 26624..26751 128 0x2001 fiemap after removing file bar: /mnt/sdj/foo: EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS 0: [0..127]: 26624..26751 128 0x2001 After this patch, after deleting bar in both tests, the extent is not reported with the 0x2000 flag anymore, it gets only the flag 0x1 (which is FIEMAP_EXTENT_LAST): $ ./test-1.sh fiemap after cloning: /mnt/sdj/foo: EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS 0: [0..127]: 26624..26751 128 0x2001 fiemap after removing file bar: /mnt/sdj/foo: EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS 0: [0..127]: 26624..26751 128 0x1 $ ./test-2.sh fiemap after cloning: /mnt/sdj/foo: EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS 0: [0..127]: 26624..26751 128 0x2001 fiemap after removing file bar: /mnt/sdj/foo: EXT: FILE-OFFSET BLOCK-RANGE TOTAL FLAGS 0: [0..127]: 26624..26751 128 0x1 These tests will later be converted to a test case for fstests. Fixes: dc046b1 ("Btrfs: make fiemap not blow when you have lots of snapshots") Signed-off-by: Filipe Manana <[email protected]> Signed-off-by: David Sterba <[email protected]>
1 parent 295a53c commit 4fc7b57

File tree

1 file changed

+24
-9
lines changed

1 file changed

+24
-9
lines changed

fs/btrfs/backref.c

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ struct share_check {
138138
u64 root_objectid;
139139
u64 inum;
140140
int share_count;
141+
bool have_delayed_delete_refs;
141142
};
142143

143144
static inline int extent_is_shared(struct share_check *sc)
@@ -884,13 +885,22 @@ static int add_delayed_refs(const struct btrfs_fs_info *fs_info,
884885
key.offset = ref->offset;
885886

886887
/*
887-
* Found a inum that doesn't match our known inum, we
888-
* know it's shared.
888+
* If we have a share check context and a reference for
889+
* another inode, we can't exit immediately. This is
890+
* because even if this is a BTRFS_ADD_DELAYED_REF
891+
* reference we may find next a BTRFS_DROP_DELAYED_REF
892+
* which cancels out this ADD reference.
893+
*
894+
* If this is a DROP reference and there was no previous
895+
* ADD reference, then we need to signal that when we
896+
* process references from the extent tree (through
897+
* add_inline_refs() and add_keyed_refs()), we should
898+
* not exit early if we find a reference for another
899+
* inode, because one of the delayed DROP references
900+
* may cancel that reference in the extent tree.
889901
*/
890-
if (sc && sc->inum && ref->objectid != sc->inum) {
891-
ret = BACKREF_FOUND_SHARED;
892-
goto out;
893-
}
902+
if (sc && count < 0)
903+
sc->have_delayed_delete_refs = true;
894904

895905
ret = add_indirect_ref(fs_info, preftrees, ref->root,
896906
&key, 0, node->bytenr, count, sc,
@@ -920,7 +930,7 @@ static int add_delayed_refs(const struct btrfs_fs_info *fs_info,
920930
}
921931
if (!ret)
922932
ret = extent_is_shared(sc);
923-
out:
933+
924934
spin_unlock(&head->lock);
925935
return ret;
926936
}
@@ -1023,7 +1033,8 @@ static int add_inline_refs(const struct btrfs_fs_info *fs_info,
10231033
key.type = BTRFS_EXTENT_DATA_KEY;
10241034
key.offset = btrfs_extent_data_ref_offset(leaf, dref);
10251035

1026-
if (sc && sc->inum && key.objectid != sc->inum) {
1036+
if (sc && sc->inum && key.objectid != sc->inum &&
1037+
!sc->have_delayed_delete_refs) {
10271038
ret = BACKREF_FOUND_SHARED;
10281039
break;
10291040
}
@@ -1033,6 +1044,7 @@ static int add_inline_refs(const struct btrfs_fs_info *fs_info,
10331044
ret = add_indirect_ref(fs_info, preftrees, root,
10341045
&key, 0, bytenr, count,
10351046
sc, GFP_NOFS);
1047+
10361048
break;
10371049
}
10381050
default:
@@ -1122,7 +1134,8 @@ static int add_keyed_refs(struct btrfs_root *extent_root,
11221134
key.type = BTRFS_EXTENT_DATA_KEY;
11231135
key.offset = btrfs_extent_data_ref_offset(leaf, dref);
11241136

1125-
if (sc && sc->inum && key.objectid != sc->inum) {
1137+
if (sc && sc->inum && key.objectid != sc->inum &&
1138+
!sc->have_delayed_delete_refs) {
11261139
ret = BACKREF_FOUND_SHARED;
11271140
break;
11281141
}
@@ -1661,6 +1674,7 @@ int btrfs_is_data_extent_shared(struct btrfs_root *root, u64 inum, u64 bytenr,
16611674
.root_objectid = root->root_key.objectid,
16621675
.inum = inum,
16631676
.share_count = 0,
1677+
.have_delayed_delete_refs = false,
16641678
};
16651679
int level;
16661680

@@ -1726,6 +1740,7 @@ int btrfs_is_data_extent_shared(struct btrfs_root *root, u64 inum, u64 bytenr,
17261740
break;
17271741
}
17281742
shared.share_count = 0;
1743+
shared.have_delayed_delete_refs = false;
17291744
cond_resched();
17301745
}
17311746

0 commit comments

Comments
 (0)