Skip to content

Commit 76b42ab

Browse files
committed
Btrfs: fix data loss after truncate when using the no-holes feature
If we have a file with an implicit hole (NO_HOLES feature enabled) that has an extent following the hole, delayed writes against regions of the file behind the hole happened before but were not yet flushed and then we truncate the file to a smaller size that lies inside the hole, we end up persisting a wrong disk_i_size value for our inode that leads to data loss after umounting and mounting again the filesystem or after the inode is evicted and loaded again. This happens because at inode.c:btrfs_truncate_inode_items() we end up setting last_size to the offset of the extent that we deleted and that followed the hole. We then pass that value to btrfs_ordered_update_i_size() which updates the inode's disk_i_size to a value smaller then the offset of the buffered (delayed) writes. Example reproducer: $ mkfs.btrfs -f /dev/sdb $ mount /dev/sdb /mnt $ xfs_io -f -c "pwrite -S 0x01 0K 32K" /mnt/foo $ xfs_io -d -c "pwrite -S 0x02 -b 32K 64K 32K" /mnt/foo $ xfs_io -c "truncate 60K" /mnt/foo --> inode's disk_i_size updated to 0 $ md5sum /mnt/foo 3c5ca3c3ab42f4b04d7e7eb0b0d4d806 /mnt/foo $ umount /dev/sdb $ mount /dev/sdb /mnt $ md5sum /mnt/foo d41d8cd98f00b204e9800998ecf8427e /mnt/foo --> Empty file, all data lost! Cc: <[email protected]> # 3.14+ Fixes: 16e7549 ("Btrfs: incompatible format change to remove hole extents") Signed-off-by: Filipe Manana <[email protected]> Reviewed-by: Liu Bo <[email protected]>
1 parent 82bfb2e commit 76b42ab

File tree

1 file changed

+6
-13
lines changed

1 file changed

+6
-13
lines changed

fs/btrfs/inode.c

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4418,19 +4418,8 @@ int btrfs_truncate_inode_items(struct btrfs_trans_handle *trans,
44184418
if (found_type > min_type) {
44194419
del_item = 1;
44204420
} else {
4421-
if (item_end < new_size) {
4422-
/*
4423-
* With NO_HOLES mode, for the following mapping
4424-
*
4425-
* [0-4k][hole][8k-12k]
4426-
*
4427-
* if truncating isize down to 6k, it ends up
4428-
* isize being 8k.
4429-
*/
4430-
if (btrfs_fs_incompat(root->fs_info, NO_HOLES))
4431-
last_size = new_size;
4421+
if (item_end < new_size)
44324422
break;
4433-
}
44344423
if (found_key.offset >= new_size)
44354424
del_item = 1;
44364425
else
@@ -4613,8 +4602,12 @@ int btrfs_truncate_inode_items(struct btrfs_trans_handle *trans,
46134602
btrfs_abort_transaction(trans, ret);
46144603
}
46154604
error:
4616-
if (root->root_key.objectid != BTRFS_TREE_LOG_OBJECTID)
4605+
if (root->root_key.objectid != BTRFS_TREE_LOG_OBJECTID) {
4606+
ASSERT(last_size >= new_size);
4607+
if (!err && last_size > new_size)
4608+
last_size = new_size;
46174609
btrfs_ordered_update_i_size(inode, last_size, NULL);
4610+
}
46184611

46194612
btrfs_free_path(path);
46204613

0 commit comments

Comments
 (0)