summaryrefslogtreecommitdiffstats
path: root/fs
diff options
context:
space:
mode:
authorWang Xiaoguang <wangxg.fnst@cn.fujitsu.com>2016-09-07 20:17:38 +0800
committerDavid Sterba <dsterba@suse.com>2017-01-19 18:00:58 +0100
commit47b5d64691350e116093c9b47b55ca6b9433bc50 (patch)
tree1882c38d550baab0c3b788f1c7e1df19db8cd7a8 /fs
parent0bf70aebf12d8fa0d06967b72ca4b257eb6adf06 (diff)
downloadlinux-47b5d64691350e116093c9b47b55ca6b9433bc50.tar.gz
linux-47b5d64691350e116093c9b47b55ca6b9433bc50.tar.xz
btrfs: fix false enospc error when truncating heavily reflinked file
Below test script can reveal this bug: dd if=/dev/zero of=fs.img bs=$((1024*1024)) count=100 dev=$(losetup --show -f fs.img) mkdir -p /mnt/mntpoint mkfs.btrfs -f $dev mount $dev /mnt/mntpoint cd /mnt/mntpoint echo "workdir is: /mnt/mntpoint" blocksize=$((128 * 1024)) dd if=/dev/zero of=testfile bs=$blocksize count=1 sync count=$((17*1024*1024*1024/blocksize)) echo "file size is:" $((count*blocksize)) for ((i = 1; i <= $count; i++)); do dst_offset=$((blocksize * i)) xfs_io -f -c "reflink testfile 0 $dst_offset $blocksize"\ testfile > /dev/null done sync truncate --size 0 testfile The last truncate operation will fail for ENOSPC reason, but indeed it should not fail. In btrfs_truncate(), we use a temporary block_rsv to do truncate operation. With every btrfs_truncate_inode_items() call, we migrate space to this block_rsv, but forget to cleanup previous reservation, which will make this block_rsv's reserved bytes keep growing, and this reserved space will only be released in the end of btrfs_truncate(), this metadata leak will impact other's metadata reservation. In this case, it's "btrfs_start_transaction(root, 2);" fails for enospc error, which make this truncate operation fail. Call btrfs_block_rsv_release() to fix this bug. Signed-off-by: Wang Xiaoguang <wangxg.fnst@cn.fujitsu.com> Reviewed-by: David Sterba <dsterba@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
Diffstat (limited to 'fs')
-rw-r--r--fs/btrfs/inode.c1
1 files changed, 1 insertions, 0 deletions
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index 128e52489f81..415a6ad5ba22 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -9215,6 +9215,7 @@ static int btrfs_truncate(struct inode *inode)
break;
}
+ btrfs_block_rsv_release(fs_info, rsv, -1);
ret = btrfs_block_rsv_migrate(&fs_info->trans_block_rsv,
rsv, min_size, 0);
BUG_ON(ret); /* shouldn't happen */