btrfs: add and use helper to assert an inode range is clean
authorFilipe Manana <fdmanana@suse.com>
Tue, 15 Mar 2022 15:22:41 +0000 (15:22 +0000)
committerDavid Sterba <dsterba@suse.com>
Mon, 16 May 2022 15:03:09 +0000 (17:03 +0200)
We have four different scenarios where we don't expect to find ordered
extents after locking a file range:

1) During plain fallocate;
2) During hole punching;
3) During zero range;
4) During reflinks (both cloning and deduplication).

This is because in all these cases we follow the pattern:

1) Lock the inode's VFS lock in exclusive mode;

2) Lock the inode's i_mmap_lock in exclusive node, to serialize with
   mmap writes;

3) Flush delalloc in a file range and wait for all ordered extents
   to complete - both done through btrfs_wait_ordered_range();

4) Lock the file range in the inode's io_tree.

So add a helper that asserts that we don't have ordered extents for a
given range. Make the four scenarios listed above use this helper after
locking the respective file range.

Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
fs/btrfs/ctree.h
fs/btrfs/file.c
fs/btrfs/inode.c
fs/btrfs/reflink.c

index 9addfd5..e43e785 100644 (file)
@@ -3375,6 +3375,7 @@ void btrfs_inode_unlock(struct inode *inode, unsigned int ilock_flags);
 void btrfs_update_inode_bytes(struct btrfs_inode *inode,
                              const u64 add_bytes,
                              const u64 del_bytes);
+void btrfs_assert_inode_range_clean(struct btrfs_inode *inode, u64 start, u64 end);
 
 /* ioctl.c */
 long btrfs_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
index fd90e25..bd32931 100644 (file)
@@ -2608,6 +2608,8 @@ static void btrfs_punch_hole_lock_range(struct inode *inode,
                unlock_extent_cached(&BTRFS_I(inode)->io_tree, lockstart,
                                     lockend, cached_state);
        }
+
+       btrfs_assert_inode_range_clean(BTRFS_I(inode), lockstart, lockend);
 }
 
 static int btrfs_insert_replace_extent(struct btrfs_trans_handle *trans,
@@ -3479,6 +3481,8 @@ static long btrfs_fallocate(struct file *file, int mode,
        lock_extent_bits(&BTRFS_I(inode)->io_tree, alloc_start, locked_end,
                         &cached_state);
 
+       btrfs_assert_inode_range_clean(BTRFS_I(inode), alloc_start, locked_end);
+
        /* First, check if we exceed the qgroup limit */
        INIT_LIST_HEAD(&reserve_list);
        while (cur_offset < alloc_end) {
index 31ccb82..c4b68be 100644 (file)
@@ -11261,6 +11261,41 @@ void btrfs_update_inode_bytes(struct btrfs_inode *inode,
        spin_unlock(&inode->lock);
 }
 
+/**
+ * Verify that there are no ordered extents for a given file range.
+ *
+ * @inode:   The target inode.
+ * @start:   Start offset of the file range, should be sector size aligned.
+ * @end:     End offset (inclusive) of the file range, its value +1 should be
+ *           sector size aligned.
+ *
+ * This should typically be used for cases where we locked an inode's VFS lock in
+ * exclusive mode, we have also locked the inode's i_mmap_lock in exclusive mode,
+ * we have flushed all delalloc in the range, we have waited for all ordered
+ * extents in the range to complete and finally we have locked the file range in
+ * the inode's io_tree.
+ */
+void btrfs_assert_inode_range_clean(struct btrfs_inode *inode, u64 start, u64 end)
+{
+       struct btrfs_root *root = inode->root;
+       struct btrfs_ordered_extent *ordered;
+
+       if (!IS_ENABLED(CONFIG_BTRFS_ASSERT))
+               return;
+
+       ordered = btrfs_lookup_first_ordered_range(inode, start, end + 1 - start);
+       if (ordered) {
+               btrfs_err(root->fs_info,
+"found unexpected ordered extent in file range [%llu, %llu] for inode %llu root %llu (ordered range [%llu, %llu])",
+                         start, end, btrfs_ino(inode), root->root_key.objectid,
+                         ordered->file_offset,
+                         ordered->file_offset + ordered->num_bytes - 1);
+               btrfs_put_ordered_extent(ordered);
+       }
+
+       ASSERT(ordered == NULL);
+}
+
 static const struct inode_operations btrfs_dir_inode_operations = {
        .getattr        = btrfs_getattr,
        .lookup         = btrfs_lookup,
index c257bf0..c39f8b3 100644 (file)
@@ -614,14 +614,23 @@ static void btrfs_double_extent_unlock(struct inode *inode1, u64 loff1,
 static void btrfs_double_extent_lock(struct inode *inode1, u64 loff1,
                                     struct inode *inode2, u64 loff2, u64 len)
 {
+       u64 range1_end = loff1 + len - 1;
+       u64 range2_end = loff2 + len - 1;
+
        if (inode1 < inode2) {
                swap(inode1, inode2);
                swap(loff1, loff2);
+               swap(range1_end, range2_end);
        } else if (inode1 == inode2 && loff2 < loff1) {
                swap(loff1, loff2);
+               swap(range1_end, range2_end);
        }
-       lock_extent(&BTRFS_I(inode1)->io_tree, loff1, loff1 + len - 1);
-       lock_extent(&BTRFS_I(inode2)->io_tree, loff2, loff2 + len - 1);
+
+       lock_extent(&BTRFS_I(inode1)->io_tree, loff1, range1_end);
+       lock_extent(&BTRFS_I(inode2)->io_tree, loff2, range2_end);
+
+       btrfs_assert_inode_range_clean(BTRFS_I(inode1), loff1, range1_end);
+       btrfs_assert_inode_range_clean(BTRFS_I(inode2), loff2, range2_end);
 }
 
 static void btrfs_double_mmap_lock(struct inode *inode1, struct inode *inode2)