diff options
author | Filipe Manana <fdmanana@suse.com> | 2020-04-04 21:20:22 +0100 |
---|---|---|
committer | David Sterba <dsterba@suse.com> | 2020-04-08 19:10:34 +0200 |
commit | 4fdb688c7071f8d5acca0f1f340ea276e9a61dce (patch) | |
tree | 71d551436072247eb11b9923b311eeb29e372706 /fs/btrfs/reflink.c | |
parent | 4d4225fc228e46948486d8b8207955f0c031b92e (diff) | |
download | lwn-4fdb688c7071f8d5acca0f1f340ea276e9a61dce.tar.gz lwn-4fdb688c7071f8d5acca0f1f340ea276e9a61dce.zip |
btrfs: fix lost i_size update after cloning inline extent
When not using the NO_HOLES feature we were not marking the destination's
file range as written after cloning an inline extent into it. This can
lead to a data loss if the current destination file size is smaller than
the source file's size.
Example:
$ mkfs.btrfs -f -O ^no-holes /dev/sdc
$ mount /mnt/sdc /mnt
$ echo "hello world" > /mnt/foo
$ cp --reflink=always /mnt/foo /mnt/bar
$ rm -f /mnt/foo
$ umount /mnt
$ mount /mnt/sdc /mnt
$ cat /mnt/bar
$
$ stat -c %s /mnt/bar
0
# -> the file is empty, since we deleted foo, the data lost is forever
Fix that by calling btrfs_inode_set_file_extent_range() after cloning an
inline extent.
A test case for fstests will follow soon.
Link: https://lore.kernel.org/linux-btrfs/20200404193846.GA432065@latitude/
Reported-by: Johannes Hirte <johannes.hirte@datenkhaos.de>
Fixes: 9ddc959e802bf ("btrfs: use the file extent tree infrastructure")
Tested-by: Johannes Hirte <johannes.hirte@datenkhaos.de>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
Diffstat (limited to 'fs/btrfs/reflink.c')
-rw-r--r-- | fs/btrfs/reflink.c | 1 |
1 files changed, 1 insertions, 0 deletions
diff --git a/fs/btrfs/reflink.c b/fs/btrfs/reflink.c index d1973141d3bb..040009d1cc31 100644 --- a/fs/btrfs/reflink.c +++ b/fs/btrfs/reflink.c @@ -264,6 +264,7 @@ copy_inline_extent: size); inode_add_bytes(dst, datal); set_bit(BTRFS_INODE_NEEDS_FULL_SYNC, &BTRFS_I(dst)->runtime_flags); + ret = btrfs_inode_set_file_extent_range(BTRFS_I(dst), 0, aligned_end); out: if (!ret && !trans) { /* |