[BUG]
When the data space is exhausted, even the inode has NOCOW attribute,
btrfs 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
Current btrfs will fail at the fpunch part.
[CAUSE]
Because btrfs_truncate_block() always reserve 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() to follow btrfs_buffered_write() to try to
reserve data space first, and falls 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 use btrfs_start_nocow_check() to do the same check in
btrfs_buffered_write() to fix the problem.
Reported-by: Martin Doucha <martin.doucha@xxxxxxxx>
Signed-off-by: Qu Wenruo <wqu@xxxxxxxx>
Reviewed-by: Filipe Manana <fdmanana@xxxxxxxx>
---
fs/btrfs/ctree.h | 3 +++
fs/btrfs/file.c | 14 +++++++-------
fs/btrfs/inode.c | 42 ++++++++++++++++++++++++++++++++++++------
3 files changed, 46 insertions(+), 13 deletions(-)
diff --git a/fs/btrfs/ctree.h b/fs/btrfs/ctree.h
index d8301bf240e0..61ca99423b51 100644
--- a/fs/btrfs/ctree.h
+++ b/fs/btrfs/ctree.h
@@ -3035,6 +3035,9 @@ int btrfs_dirty_pages(struct inode *inode, struct page **pages,
size_t num_pages, loff_t pos, size_t write_bytes,
struct extent_state **cached);
int btrfs_fdatawrite_range(struct inode *inode, loff_t start, loff_t end);
+int btrfs_start_nocow_check(struct btrfs_inode *inode, loff_t pos,
+ size_t *write_bytes);
+void btrfs_end_nocow_check(struct btrfs_inode *inode);
/* tree-defrag.c */
int btrfs_defrag_leaves(struct btrfs_trans_handle *trans,
diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c
index 7c904e41c5b6..2a9b1a2860e9 100644
--- a/fs/btrfs/file.c
+++ b/fs/btrfs/file.c
@@ -1603,8 +1603,8 @@ static noinline int __check_can_nocow(struct btrfs_inode *inode, loff_t pos,
* @write_bytes: The length of the range to check, also contains the nocow
* writable length if we can do nocow write
*/
-static int start_nocow_check(struct btrfs_inode *inode, loff_t pos,
- size_t *write_bytes)
+int btrfs_start_nocow_check(struct btrfs_inode *inode, loff_t pos,
+ size_t *write_bytes)
{
return __check_can_nocow(inode, pos, write_bytes, false);
}
@@ -1615,7 +1615,7 @@ static int try_nocow_check(struct btrfs_inode *inode, loff_t pos,
return __check_can_nocow(inode, pos, write_bytes, true);
}
-static void end_nocow_check(struct btrfs_inode *inode)
+void btrfs_end_nocow_check(struct btrfs_inode *inode)
{
btrfs_assert_drew_write_locked(&inode->root->snapshot_lock);
btrfs_drew_write_unlock(&inode->root->snapshot_lock);
@@ -1686,8 +1686,8 @@ static noinline ssize_t btrfs_buffered_write(struct kiocb *iocb,
if (ret < 0) {
if ((BTRFS_I(inode)->flags & (BTRFS_INODE_NODATACOW |
BTRFS_INODE_PREALLOC)) &&
- start_nocow_check(BTRFS_I(inode), pos,
- &write_bytes) > 0) {
+ btrfs_start_nocow_check(BTRFS_I(inode), pos,
+ &write_bytes) > 0) {
/*
* For nodata cow case, no need to reserve
* data space.
@@ -1820,7 +1820,7 @@ static noinline ssize_t btrfs_buffered_write(struct kiocb *iocb,
release_bytes = 0;
if (only_release_metadata)
- end_nocow_check(BTRFS_I(inode));
+ btrfs_end_nocow_check(BTRFS_I(inode));
if (only_release_metadata && copied > 0) {
lockstart = round_down(pos,
@@ -1847,7 +1847,7 @@ static noinline ssize_t btrfs_buffered_write(struct kiocb *iocb,
if (release_bytes) {
if (only_release_metadata) {
- end_nocow_check(BTRFS_I(inode));
+ btrfs_end_nocow_check(BTRFS_I(inode));
btrfs_delalloc_release_metadata(BTRFS_I(inode),
release_bytes, true);
} else {
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index 48e16eae7278..ef636b193227 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -4519,11 +4519,13 @@ int btrfs_truncate_block(struct inode *inode, loff_t from, loff_t len,
struct extent_state *cached_state = NULL;
struct extent_changeset *data_reserved = NULL;
char *kaddr;
+ bool only_release_metadata = false;
u32 blocksize = fs_info->sectorsize;
pgoff_t index = from >> PAGE_SHIFT;
unsigned offset = from & (blocksize - 1);
struct page *page;
gfp_t mask = btrfs_alloc_write_mask(mapping);
+ size_t write_bytes = blocksize;
int ret = 0;
u64 block_start;
u64 block_end;
@@ -4535,10 +4537,26 @@ int btrfs_truncate_block(struct inode *inode, loff_t from, loff_t len,
block_start = round_down(from, blocksize);
block_end = block_start + blocksize - 1;
- ret = btrfs_delalloc_reserve_space(inode, &data_reserved,
- block_start, blocksize);
- if (ret)
+ ret = btrfs_check_data_free_space(inode, &data_reserved, block_start,
+ blocksize);
+ if (ret < 0) {
+ if ((BTRFS_I(inode)->flags & (BTRFS_INODE_NODATACOW |
+ BTRFS_INODE_PREALLOC)) &&
+ btrfs_start_nocow_check(BTRFS_I(inode), block_start,
+ &write_bytes) > 0) {
+ /* For nocow case, no need to reserve data space. */
+ only_release_metadata = true;
+ } else {
+ goto out;
+ }
+ }
+ ret = btrfs_delalloc_reserve_metadata(BTRFS_I(inode), blocksize);
+ if (ret < 0) {
+ if (!only_release_metadata)
+ btrfs_free_reserved_data_space(inode, data_reserved,
+ block_start, blocksize);
goto out;
+ }
again:
page = find_or_create_page(mapping, index, mask);
@@ -4608,14 +4626,26 @@ int btrfs_truncate_block(struct inode *inode, loff_t from, loff_t len,
set_page_dirty(page);
unlock_extent_cached(io_tree, block_start, block_end, &cached_state);
+ if (only_release_metadata)
+ set_extent_bit(&BTRFS_I(inode)->io_tree, block_start,
+ block_end, EXTENT_NORESERVE, NULL, NULL,
+ GFP_NOFS);
+
out_unlock:
- if (ret)
- btrfs_delalloc_release_space(inode, data_reserved, block_start,
- blocksize, true);
+ if (ret) {
+ if (!only_release_metadata)
+ btrfs_delalloc_release_space(inode, data_reserved,
+ block_start, blocksize, true);
+ else
+ btrfs_delalloc_release_metadata(BTRFS_I(inode),
+ blocksize, true);
+ }
btrfs_delalloc_release_extents(BTRFS_I(inode), blocksize);
unlock_page(page);
put_page(page);
out:
+ if (only_release_metadata)
+ btrfs_end_nocow_check(BTRFS_I(inode));
extent_changeset_free(data_reserved);
return ret;
}
--
2.27.0