]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
btrfs: mark dirty extent range for out of bound prealloc extents
authoraustinchang <austinchang@synology.com>
Wed, 29 Oct 2025 09:35:27 +0000 (09:35 +0000)
committerDavid Sterba <dsterba@suse.com>
Thu, 30 Oct 2025 18:18:18 +0000 (19:18 +0100)
In btrfs_fallocate(), when the allocated range overlaps with a prealloc
extent and the extent starts after i_size, the range doesn't get marked
dirty in file_extent_tree. This results in persisting an incorrect
disk_i_size for the inode when not using the no-holes feature.

This is reproducible since commit 41a2ee75aab0 ("btrfs: introduce
per-inode file extent tree"), then became hidden since commit 3d7db6e8bd22
("btrfs: don't allocate file extent tree for non regular files") and then
visible again after commit 8679d2687c35 ("btrfs: initialize
inode::file_extent_tree after i_mode has been set"), which fixes the
previous commit.

The following reproducer triggers the problem:

$ cat test.sh

MNT=/mnt/test
DEV=/dev/vdb

mkdir -p $MNT

mkfs.btrfs -f -O ^no-holes $DEV
mount $DEV $MNT

touch $MNT/file1
fallocate -n -o 1M -l 2M $MNT/file1

umount $MNT
mount $DEV $MNT

len=$((1 * 1024 * 1024))

fallocate -o 1M -l $len $MNT/file1

du --bytes $MNT/file1

umount $MNT
mount $DEV $MNT

du --bytes $MNT/file1

umount $MNT

Running the reproducer gives the following result:

$ ./test.sh
(...)
2097152 /mnt/test/file1
1048576 /mnt/test/file1

The difference is exactly 1048576 as we assigned.

Fix by adding a call to btrfs_inode_set_file_extent_range() in
btrfs_fallocate_update_isize().

Fixes: 41a2ee75aab0 ("btrfs: introduce per-inode file extent tree")
Signed-off-by: austinchang <austinchang@synology.com>
Reviewed-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
fs/btrfs/file.c

index 7efd1f8a19121f4f42f79b25158107e1423b9f1d..fa82def46e3952748352e4a7ee2e9e003a72050b 100644 (file)
@@ -2854,12 +2854,22 @@ static int btrfs_fallocate_update_isize(struct inode *inode,
 {
        struct btrfs_trans_handle *trans;
        struct btrfs_root *root = BTRFS_I(inode)->root;
+       u64 range_start;
+       u64 range_end;
        int ret;
        int ret2;
 
        if (mode & FALLOC_FL_KEEP_SIZE || end <= i_size_read(inode))
                return 0;
 
+       range_start = round_down(i_size_read(inode), root->fs_info->sectorsize);
+       range_end = round_up(end, root->fs_info->sectorsize);
+
+       ret = btrfs_inode_set_file_extent_range(BTRFS_I(inode), range_start,
+                                               range_end - range_start);
+       if (ret)
+               return ret;
+
        trans = btrfs_start_transaction(root, 1);
        if (IS_ERR(trans))
                return PTR_ERR(trans);