diff options
author | Filipe Manana <fdmanana@suse.com> | 2015-06-20 13:20:09 -0400 |
---|---|---|
committer | Filipe Manana <fdmanana@suse.com> | 2015-07-11 17:33:14 -0400 |
commit | c1aa45759e90b4204ab8bce027a925fc7c87d00a (patch) | |
tree | 42bea0bd8b1dd00ae3d12e673be706e35cbca381 | |
parent | 9689457b5b0a2b69874c421a489d3fb50ca76b7b (diff) |
Btrfs: fix shrinking truncate when the no_holes feature is enabled
If the no_holes feature is enabled, we attempt to shrink a file to a size
that ends up in the middle of a hole and we don't have any file extent
items in the fs/subvol tree that go beyond the new file size (or any
ordered extents that will insert such file extent items), we end up not
updating the inode's disk_i_size, we only update the inode's i_size.
This means that after unmounting and mounting the filesystem, or after
the inode is evicted and reloaded, its i_size ends up being incorrect
(an inode's i_size is set to the disk_i_size field when an inode is
loaded). This happens when btrfs_truncate_inode_items() doesn't find
any file extent items to drop - in this case it never makes a call to
btrfs_ordered_update_i_size() in order to update the inode's disk_i_size.
Example reproducer:
$ mkfs.btrfs -O no-holes -f /dev/sdd
$ mount /dev/sdd /mnt
# Create our test file with some data and durably persist it.
$ xfs_io -f -c "pwrite -S 0xaa 0 128K" /mnt/foo
$ sync
# Append some data to the file, increasing its size, and leave a hole
# between the old size and the start offset if the following write. So
# our file gets a hole in the range [128Kb, 256Kb[.
$ xfs_io -c "truncate 160K" /mnt/foo
# We expect to see our file with a size of 160Kb, with the first 128Kb
# of data all having the value 0xaa and the remaining 32Kb of data all
# having the value 0x00.
$ od -t x1 /mnt/foo
0000000 aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa
*
0400000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*
0500000
# Now cleanly unmount and mount again the filesystem.
$ umount /mnt
$ mount /dev/sdd /mnt
# We expect to get the same result as before, a file with a size of
# 160Kb, with the first 128Kb of data all having the value 0xaa and the
# remaining 32Kb of data all having the value 0x00.
$ od -t x1 /mnt/foo
0000000 aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa
*
0400000
In the example above the file size/data do not match what they were before
the remount.
Fix this by always calling btrfs_ordered_update_i_size() with a size
matching the size the file was truncated to if btrfs_truncate_inode_items()
is not called for a log tree and no file extent items were dropped. This
ensures the same behaviour as when the no_holes feature is not enabled.
A test case for fstests follows soon.
Signed-off-by: Filipe Manana <fdmanana@suse.com>
-rw-r--r-- | fs/btrfs/inode.c | 5 |
1 files changed, 2 insertions, 3 deletions
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index b33c0cf02668..e33dff356460 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c | |||
@@ -4209,7 +4209,7 @@ int btrfs_truncate_inode_items(struct btrfs_trans_handle *trans, | |||
4209 | u64 extent_num_bytes = 0; | 4209 | u64 extent_num_bytes = 0; |
4210 | u64 extent_offset = 0; | 4210 | u64 extent_offset = 0; |
4211 | u64 item_end = 0; | 4211 | u64 item_end = 0; |
4212 | u64 last_size = (u64)-1; | 4212 | u64 last_size = new_size; |
4213 | u32 found_type = (u8)-1; | 4213 | u32 found_type = (u8)-1; |
4214 | int found_extent; | 4214 | int found_extent; |
4215 | int del_item; | 4215 | int del_item; |
@@ -4493,8 +4493,7 @@ out: | |||
4493 | btrfs_abort_transaction(trans, root, ret); | 4493 | btrfs_abort_transaction(trans, root, ret); |
4494 | } | 4494 | } |
4495 | error: | 4495 | error: |
4496 | if (last_size != (u64)-1 && | 4496 | if (root->root_key.objectid != BTRFS_TREE_LOG_OBJECTID) |
4497 | root->root_key.objectid != BTRFS_TREE_LOG_OBJECTID) | ||
4498 | btrfs_ordered_update_i_size(inode, last_size, NULL); | 4497 | btrfs_ordered_update_i_size(inode, last_size, NULL); |
4499 | 4498 | ||
4500 | btrfs_free_path(path); | 4499 | btrfs_free_path(path); |