Commit c562ba6
btrfs: fix incorrect i_size after remount caused by KEEP_SIZE prealloc gap
When fallocate() with FALLOC_FL_KEEP_SIZE preallocates an extent past the
current i_size, the file_extent_tree of the inode is updated to cover
that range. However, on the next mount, btrfs_read_locked_inode() only
re-populates file_extent_tree with [0, round_up(i_size, sectorsize)),
losing the marks that belonged to the KEEP_SIZE prealloc extent beyond
i_size.
Later, when a non-KEEP_SIZE fallocate() extends i_size into / past that
old prealloc extent, the reservation loop in btrfs_fallocate() skips
already-prealloc segments and does not call into the path that marks the
file_extent_tree, so a gap remains inside the file_extent_tree across
[old_aligned_i_size, start_of_new_alloc). Then __btrfs_prealloc_file_range()
calls btrfs_inode_safe_disk_i_size_write(), which uses
find_contiguous_extent_bit() starting at offset 0 to derive disk_i_size.
The walk stops at the gap, so disk_i_size ends up smaller than i_size and
gets persisted. After the next mount, the file shows the wrong (smaller)
size.
The following reproducer triggers the problem:
$ cat test.sh
MNT=/mnt/sdi
DEV=/dev/sdi
mkdir -p $MNT
mkfs.btrfs -f -O ^no-holes $DEV
mount $DEV $MNT
touch $MNT/file1
# KEEP_SIZE prealloc beyond i_size (i_size stays 0)
fallocate -n -o 4M -l 4M $MNT/file1
umount $MNT
mount $DEV $MNT
# non-KEEP_SIZE fallocate that overlaps the previous prealloc tail
# and extends past it
fallocate -o 7M -l 2M $MNT/file1
ls -lh $MNT/file1
umount $MNT
mount $DEV $MNT
ls -lh $MNT/file1
umount $MNT
Running the reproducer gives the following result:
$ ./test.sh
(...)
-rw-rw-r-- 1 root root 9.0M May 4 16:35 /mnt/sdi/file1
-rw-rw-r-- 1 root root 7.0M May 4 16:35 /mnt/sdi/file1
The size before the second mount is correct (9M), but after the
remount it drops to 7M, i.e. the start of the gap inside file_extent_tree.
Fix this in __btrfs_prealloc_file_range() by marking the entire range
[round_down(old_i_size, sectorsize), round_up(new_i_size, sectorsize))
in file_extent_tree before updating i_size and calling
btrfs_inode_safe_disk_i_size_write(). This ensures the contiguous bit
search starting from 0 is not truncated by a stale gap left behind by a
previous KEEP_SIZE prealloc that was not restored on inode load.
The fix has no effect when the NO_HOLES feature is enabled because
btrfs_inode_safe_disk_i_size_write() and
btrfs_inode_set_file_extent_range()
both take the fast path that directly tracks disk_i_size without
consulting file_extent_tree.
Fixes: 9ddc959 ("btrfs: use the file extent tree infrastructure")
Reviewed-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: Robbie Ko <robbieko@synology.com>
[ Minor updates to the change log ]
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>1 parent 4066c55 commit c562ba6
1 file changed
Lines changed: 28 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
9299 | 9299 | | |
9300 | 9300 | | |
9301 | 9301 | | |
| 9302 | + | |
| 9303 | + | |
| 9304 | + | |
9302 | 9305 | | |
9303 | 9306 | | |
9304 | 9307 | | |
9305 | 9308 | | |
| 9309 | + | |
| 9310 | + | |
| 9311 | + | |
| 9312 | + | |
| 9313 | + | |
| 9314 | + | |
| 9315 | + | |
| 9316 | + | |
| 9317 | + | |
| 9318 | + | |
| 9319 | + | |
| 9320 | + | |
| 9321 | + | |
| 9322 | + | |
| 9323 | + | |
| 9324 | + | |
| 9325 | + | |
| 9326 | + | |
| 9327 | + | |
| 9328 | + | |
| 9329 | + | |
| 9330 | + | |
| 9331 | + | |
| 9332 | + | |
| 9333 | + | |
9306 | 9334 | | |
9307 | 9335 | | |
9308 | 9336 | | |
| |||
0 commit comments