Skip to content
This repository was archived by the owner on Nov 8, 2023. It is now read-only.

Commit e7db9e5

Browse files
boryaskdave
authored andcommitted
btrfs: fix encoded write i_size corruption with no-holes
We have observed a btrfs filesystem corruption on workloads using no-holes and encoded writes via send stream v2. The symptom is that a file appears to be truncated to the end of its last aligned extent, even though the final unaligned extent and even the file extent and otherwise correctly updated inode item have been written. So if we were writing out a 1MiB+X file via 8 128K extents and one extent of length X, i_size would be set to 1MiB, but the ninth extent, nbyte, etc. would all appear correct otherwise. The source of the race is a narrow (one line of code) window in which a no-holes fs has read in an updated i_size, but has not yet set a shared disk_i_size variable to write. Therefore, if two ordered extents run in parallel (par for the course for receive workloads), the following sequence can play out: (following "threads" a bit loosely, since there are callbacks involved for endio but extra threads aren't needed to cause the issue) ENC-WR1 (second to last) ENC-WR2 (last) ------- ------- btrfs_do_encoded_write set i_size = 1M submit bio B1 ending at 1M endio B1 btrfs_inode_safe_disk_i_size_write local i_size = 1M falls off a cliff for some reason btrfs_do_encoded_write set i_size = 1M+X submit bio B2 ending at 1M+X endio B2 btrfs_inode_safe_disk_i_size_write local i_size = 1M+X disk_i_size = 1M+X disk_i_size = 1M btrfs_delayed_update_inode btrfs_delayed_update_inode And the delayed inode ends up filled with nbytes=1M+X and isize=1M, and writes respect i_size and present a corrupted file missing its last extents. Fix this by holding the inode lock in the no-holes case so that a thread can't sneak in a write to disk_i_size that gets overwritten with an out of date i_size. Fixes: 41a2ee7 ("btrfs: introduce per-inode file extent tree") CC: [email protected] # 5.10+ Reviewed-by: Josef Bacik <[email protected]> Signed-off-by: Boris Burkov <[email protected]> Reviewed-by: David Sterba <[email protected]> Signed-off-by: David Sterba <[email protected]>
1 parent 631003e commit e7db9e5

File tree

1 file changed

+3
-2
lines changed

1 file changed

+3
-2
lines changed

fs/btrfs/file-item.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,20 +52,21 @@ void btrfs_inode_safe_disk_i_size_write(struct btrfs_inode *inode, u64 new_i_siz
5252
u64 start, end, i_size;
5353
int ret;
5454

55+
spin_lock(&inode->lock);
5556
i_size = new_i_size ?: i_size_read(&inode->vfs_inode);
5657
if (btrfs_fs_incompat(fs_info, NO_HOLES)) {
5758
inode->disk_i_size = i_size;
58-
return;
59+
goto out_unlock;
5960
}
6061

61-
spin_lock(&inode->lock);
6262
ret = find_contiguous_extent_bit(&inode->file_extent_tree, 0, &start,
6363
&end, EXTENT_DIRTY);
6464
if (!ret && start == 0)
6565
i_size = min(i_size, end + 1);
6666
else
6767
i_size = 0;
6868
inode->disk_i_size = i_size;
69+
out_unlock:
6970
spin_unlock(&inode->lock);
7071
}
7172

0 commit comments

Comments
 (0)