Patch "btrfs: fix processing of delayed data refs during backref walking" has been added to the 5.15-stable tree

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This is a note to let you know that I've just added the patch titled

    btrfs: fix processing of delayed data refs during backref walking

to the 5.15-stable tree which can be found at:
    http://www.kernel.org/git/?p=linux/kernel/git/stable/stable-queue.git;a=summary

The filename of the patch is:
     btrfs-fix-processing-of-delayed-data-refs-during-bac.patch
and it can be found in the queue-5.15 subdirectory.

If you, or anyone else, feels it should not be added to the stable tree,
please let <stable@xxxxxxxxxxxxxxx> know about it.



commit 019fc9c7d081504818aad53b437b4f6189919ae3
Author: Filipe Manana <fdmanana@xxxxxxxx>
Date:   Tue Oct 11 13:16:51 2022 +0100

    btrfs: fix processing of delayed data refs during backref walking
    
    [ Upstream commit 4fc7b57228243d09c0d878873bf24fa64a90fa01 ]
    
    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: dc046b10c8b7d4 ("Btrfs: make fiemap not blow when you have lots of snapshots")
    Signed-off-by: Filipe Manana <fdmanana@xxxxxxxx>
    Signed-off-by: David Sterba <dsterba@xxxxxxxx>
    Signed-off-by: Sasha Levin <sashal@xxxxxxxxxx>

diff --git a/fs/btrfs/backref.c b/fs/btrfs/backref.c
index 8b090c40daf7..f33bad9f5e29 100644
--- a/fs/btrfs/backref.c
+++ b/fs/btrfs/backref.c
@@ -138,6 +138,7 @@ struct share_check {
 	u64 root_objectid;
 	u64 inum;
 	int share_count;
+	bool have_delayed_delete_refs;
 };
 
 static inline int extent_is_shared(struct share_check *sc)
@@ -882,13 +883,22 @@ static int add_delayed_refs(const struct btrfs_fs_info *fs_info,
 			key.offset = ref->offset;
 
 			/*
-			 * Found a inum that doesn't match our known inum, we
-			 * know it's shared.
+			 * If we have a share check context and a reference for
+			 * another inode, we can't exit immediately. This is
+			 * because even if this is a BTRFS_ADD_DELAYED_REF
+			 * reference we may find next a BTRFS_DROP_DELAYED_REF
+			 * which cancels out this ADD reference.
+			 *
+			 * If this is a DROP reference and there was no previous
+			 * ADD reference, then we need to signal that when we
+			 * process references from the extent tree (through
+			 * add_inline_refs() and add_keyed_refs()), we should
+			 * not exit early if we find a reference for another
+			 * inode, because one of the delayed DROP references
+			 * may cancel that reference in the extent tree.
 			 */
-			if (sc && sc->inum && ref->objectid != sc->inum) {
-				ret = BACKREF_FOUND_SHARED;
-				goto out;
-			}
+			if (sc && count < 0)
+				sc->have_delayed_delete_refs = true;
 
 			ret = add_indirect_ref(fs_info, preftrees, ref->root,
 					       &key, 0, node->bytenr, count, sc,
@@ -918,7 +928,7 @@ static int add_delayed_refs(const struct btrfs_fs_info *fs_info,
 	}
 	if (!ret)
 		ret = extent_is_shared(sc);
-out:
+
 	spin_unlock(&head->lock);
 	return ret;
 }
@@ -1021,7 +1031,8 @@ static int add_inline_refs(const struct btrfs_fs_info *fs_info,
 			key.type = BTRFS_EXTENT_DATA_KEY;
 			key.offset = btrfs_extent_data_ref_offset(leaf, dref);
 
-			if (sc && sc->inum && key.objectid != sc->inum) {
+			if (sc && sc->inum && key.objectid != sc->inum &&
+			    !sc->have_delayed_delete_refs) {
 				ret = BACKREF_FOUND_SHARED;
 				break;
 			}
@@ -1031,6 +1042,7 @@ static int add_inline_refs(const struct btrfs_fs_info *fs_info,
 			ret = add_indirect_ref(fs_info, preftrees, root,
 					       &key, 0, bytenr, count,
 					       sc, GFP_NOFS);
+
 			break;
 		}
 		default:
@@ -1120,7 +1132,8 @@ static int add_keyed_refs(struct btrfs_fs_info *fs_info,
 			key.type = BTRFS_EXTENT_DATA_KEY;
 			key.offset = btrfs_extent_data_ref_offset(leaf, dref);
 
-			if (sc && sc->inum && key.objectid != sc->inum) {
+			if (sc && sc->inum && key.objectid != sc->inum &&
+			    !sc->have_delayed_delete_refs) {
 				ret = BACKREF_FOUND_SHARED;
 				break;
 			}
@@ -1547,6 +1560,7 @@ int btrfs_check_shared(struct btrfs_root *root, u64 inum, u64 bytenr,
 		.root_objectid = root->root_key.objectid,
 		.inum = inum,
 		.share_count = 0,
+		.have_delayed_delete_refs = false,
 	};
 
 	ulist_init(roots);
@@ -1581,6 +1595,7 @@ int btrfs_check_shared(struct btrfs_root *root, u64 inum, u64 bytenr,
 			break;
 		bytenr = node->val;
 		shared.share_count = 0;
+		shared.have_delayed_delete_refs = false;
 		cond_resched();
 	}
 



[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Index of Archives]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux