Skip to content

Commit 6d4572a

Browse files
adam900710kdave
authored andcommitted
btrfs: allow btrfs_truncate_block() to fallback to nocow for data space reservation
[BUG] When the data space is exhausted, even if the inode has NOCOW attribute, we will still refuse to truncate unaligned range due to ENOSPC. The following script can reproduce it pretty easily: #!/bin/bash dev=/dev/test/test mnt=/mnt/btrfs umount $dev &> /dev/null umount $mnt &> /dev/null mkfs.btrfs -f $dev -b 1G mount -o nospace_cache $dev $mnt touch $mnt/foobar chattr +C $mnt/foobar xfs_io -f -c "pwrite -b 4k 0 4k" $mnt/foobar > /dev/null xfs_io -f -c "pwrite -b 4k 0 1G" $mnt/padding &> /dev/null sync xfs_io -c "fpunch 0 2k" $mnt/foobar umount $mnt Currently this will fail at the fpunch part. [CAUSE] Because btrfs_truncate_block() always reserves space without checking the NOCOW attribute. Since the writeback path follows NOCOW bit, we only need to bother the space reservation code in btrfs_truncate_block(). [FIX] Make btrfs_truncate_block() follow btrfs_buffered_write() to try to reserve data space first, and fall back to NOCOW check only when we don't have enough space. Such always-try-reserve is an optimization introduced in btrfs_buffered_write(), to avoid expensive btrfs_check_can_nocow() call. This patch will export check_can_nocow() as btrfs_check_can_nocow(), and use it in btrfs_truncate_block() to fix the problem. Reported-by: Martin Doucha <martin.doucha@suse.com> Reviewed-by: Filipe Manana <fdmanana@suse.com> Reviewed-by: Anand Jain <anand.jain@oracle.com> Signed-off-by: Qu Wenruo <wqu@suse.com> Reviewed-by: David Sterba <dsterba@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
1 parent b547a88 commit 6d4572a

File tree

3 files changed

+45
-13
lines changed

3 files changed

+45
-13
lines changed

fs/btrfs/ctree.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3033,6 +3033,8 @@ int btrfs_dirty_pages(struct inode *inode, struct page **pages,
30333033
size_t num_pages, loff_t pos, size_t write_bytes,
30343034
struct extent_state **cached);
30353035
int btrfs_fdatawrite_range(struct inode *inode, loff_t start, loff_t end);
3036+
int btrfs_check_can_nocow(struct btrfs_inode *inode, loff_t pos,
3037+
size_t *write_bytes, bool nowait);
30363038

30373039
/* tree-defrag.c */
30383040
int btrfs_defrag_leaves(struct btrfs_trans_handle *trans,

fs/btrfs/file.c

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1533,8 +1533,8 @@ lock_and_cleanup_extent_if_need(struct btrfs_inode *inode, struct page **pages,
15331533
return ret;
15341534
}
15351535

1536-
static noinline int check_can_nocow(struct btrfs_inode *inode, loff_t pos,
1537-
size_t *write_bytes, bool nowait)
1536+
int btrfs_check_can_nocow(struct btrfs_inode *inode, loff_t pos,
1537+
size_t *write_bytes, bool nowait)
15381538
{
15391539
struct btrfs_fs_info *fs_info = inode->root->fs_info;
15401540
struct btrfs_root *root = inode->root;
@@ -1649,8 +1649,8 @@ static noinline ssize_t btrfs_buffered_write(struct kiocb *iocb,
16491649
if (ret < 0) {
16501650
if ((BTRFS_I(inode)->flags & (BTRFS_INODE_NODATACOW |
16511651
BTRFS_INODE_PREALLOC)) &&
1652-
check_can_nocow(BTRFS_I(inode), pos,
1653-
&write_bytes, false) > 0) {
1652+
btrfs_check_can_nocow(BTRFS_I(inode), pos,
1653+
&write_bytes, false) > 0) {
16541654
/*
16551655
* For nodata cow case, no need to reserve
16561656
* data space.
@@ -1927,8 +1927,8 @@ static ssize_t btrfs_file_write_iter(struct kiocb *iocb,
19271927
*/
19281928
if (!(BTRFS_I(inode)->flags & (BTRFS_INODE_NODATACOW |
19291929
BTRFS_INODE_PREALLOC)) ||
1930-
check_can_nocow(BTRFS_I(inode), pos, &nocow_bytes,
1931-
true) <= 0) {
1930+
btrfs_check_can_nocow(BTRFS_I(inode), pos, &nocow_bytes,
1931+
true) <= 0) {
19321932
inode_unlock(inode);
19331933
return -EAGAIN;
19341934
}

fs/btrfs/inode.c

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4512,11 +4512,13 @@ int btrfs_truncate_block(struct inode *inode, loff_t from, loff_t len,
45124512
struct extent_state *cached_state = NULL;
45134513
struct extent_changeset *data_reserved = NULL;
45144514
char *kaddr;
4515+
bool only_release_metadata = false;
45154516
u32 blocksize = fs_info->sectorsize;
45164517
pgoff_t index = from >> PAGE_SHIFT;
45174518
unsigned offset = from & (blocksize - 1);
45184519
struct page *page;
45194520
gfp_t mask = btrfs_alloc_write_mask(mapping);
4521+
size_t write_bytes = blocksize;
45204522
int ret = 0;
45214523
u64 block_start;
45224524
u64 block_end;
@@ -4528,11 +4530,27 @@ int btrfs_truncate_block(struct inode *inode, loff_t from, loff_t len,
45284530
block_start = round_down(from, blocksize);
45294531
block_end = block_start + blocksize - 1;
45304532

4531-
ret = btrfs_delalloc_reserve_space(inode, &data_reserved,
4532-
block_start, blocksize);
4533-
if (ret)
4534-
goto out;
45354533

4534+
ret = btrfs_check_data_free_space(inode, &data_reserved, block_start,
4535+
blocksize);
4536+
if (ret < 0) {
4537+
if ((BTRFS_I(inode)->flags & (BTRFS_INODE_NODATACOW |
4538+
BTRFS_INODE_PREALLOC)) &&
4539+
btrfs_check_can_nocow(BTRFS_I(inode), block_start,
4540+
&write_bytes, false) > 0) {
4541+
/* For nocow case, no need to reserve data space */
4542+
only_release_metadata = true;
4543+
} else {
4544+
goto out;
4545+
}
4546+
}
4547+
ret = btrfs_delalloc_reserve_metadata(BTRFS_I(inode), blocksize);
4548+
if (ret < 0) {
4549+
if (!only_release_metadata)
4550+
btrfs_free_reserved_data_space(inode, data_reserved,
4551+
block_start, blocksize);
4552+
goto out;
4553+
}
45364554
again:
45374555
page = find_or_create_page(mapping, index, mask);
45384556
if (!page) {
@@ -4601,14 +4619,26 @@ int btrfs_truncate_block(struct inode *inode, loff_t from, loff_t len,
46014619
set_page_dirty(page);
46024620
unlock_extent_cached(io_tree, block_start, block_end, &cached_state);
46034621

4622+
if (only_release_metadata)
4623+
set_extent_bit(&BTRFS_I(inode)->io_tree, block_start,
4624+
block_end, EXTENT_NORESERVE, NULL, NULL,
4625+
GFP_NOFS);
4626+
46044627
out_unlock:
4605-
if (ret)
4606-
btrfs_delalloc_release_space(inode, data_reserved, block_start,
4607-
blocksize, true);
4628+
if (ret) {
4629+
if (only_release_metadata)
4630+
btrfs_delalloc_release_metadata(BTRFS_I(inode),
4631+
blocksize, true);
4632+
else
4633+
btrfs_delalloc_release_space(inode, data_reserved,
4634+
block_start, blocksize, true);
4635+
}
46084636
btrfs_delalloc_release_extents(BTRFS_I(inode), blocksize);
46094637
unlock_page(page);
46104638
put_page(page);
46114639
out:
4640+
if (only_release_metadata)
4641+
btrfs_drew_write_unlock(&BTRFS_I(inode)->root->snapshot_lock);
46124642
extent_changeset_free(data_reserved);
46134643
return ret;
46144644
}

0 commit comments

Comments
 (0)