public inbox for linux-btrfs@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/3] btrfs: fix mmap writes for NOCOW files/extents
@ 2025-07-09  8:53 fdmanana
  2025-07-09  8:53 ` [PATCH 1/3] btrfs: fix -ENOSPC mmap write failure on " fdmanana
                   ` (4 more replies)
  0 siblings, 5 replies; 17+ messages in thread
From: fdmanana @ 2025-07-09  8:53 UTC (permalink / raw)
  To: linux-btrfs

From: Filipe Manana <fdmanana@suse.com>

A bug fix and a couple cleanups. Details in the individual change logs.

Filipe Manana (3):
  btrfs: fix -ENOSPC mmap write failure on NOCOW files/extents
  btrfs: use variable for io_tree when clearing range in btrfs_page_mkwrite()
  btrfs: use btrfs_inode local variable at btrfs_page_mkwrite()

 fs/btrfs/file.c | 85 +++++++++++++++++++++++++++++++++----------------
 1 file changed, 58 insertions(+), 27 deletions(-)

-- 
2.47.2


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH 1/3] btrfs: fix -ENOSPC mmap write failure on NOCOW files/extents
  2025-07-09  8:53 [PATCH 0/3] btrfs: fix mmap writes for NOCOW files/extents fdmanana
@ 2025-07-09  8:53 ` fdmanana
  2025-07-09 10:02   ` Qu Wenruo
  2025-07-09  8:53 ` [PATCH 2/3] btrfs: use variable for io_tree when clearing range in btrfs_page_mkwrite() fdmanana
                   ` (3 subsequent siblings)
  4 siblings, 1 reply; 17+ messages in thread
From: fdmanana @ 2025-07-09  8:53 UTC (permalink / raw)
  To: linux-btrfs

From: Filipe Manana <fdmanana@suse.com>

If we attempt a mmap write into a NOCOW file or a prealloc extent when
there is no more available data space (or unallocated space to allocate a
new data block group) and we can do a NOCOW write (there are no reflinks
for the target extent or snapshots), we always fail due to -ENOSPC, unlike
for the regular buffered write and direct IO paths where we check that we
can do a NOCOW write in case we can't reserve data space.

Simple reproducer:

  $ cat test.sh
  #!/bin/bash

  DEV=/dev/sdi
  MNT=/mnt/sdi

  umount $DEV &> /dev/null
  mkfs.btrfs -f -b $((512 * 1024 * 1024)) $DEV
  mount $DEV $MNT

  touch $MNT/foobar
  # Make it a NOCOW file.
  chattr +C $MNT/foobar

  # Add initial data to file.
  xfs_io -c "pwrite -S 0xab 0 1M" $MNT/foobar

  # Fill all the remaining data space and unallocated space with data.
  dd if=/dev/zero of=$MNT/filler bs=4K &> /dev/null

  # Overwrite the file with a mmap write. Should succeed.
  xfs_io -c "mmap -w 0 1M"        \
         -c "mwrite -S 0xcd 0 1M" \
         -c "munmap"              \
         $MNT/foobar

  # Unmount, mount again and verify the new data was persisted.
  umount $MNT
  mount $DEV $MNT

  od -A d -t x1 $MNT/foobar

  umount $MNT

Running this:

  $ ./test.sh
  (...)
  wrote 1048576/1048576 bytes at offset 0
  1 MiB, 256 ops; 0.0008 sec (1.188 GiB/sec and 311435.5231 ops/sec)
  ./test.sh: line 24: 234865 Bus error               xfs_io -c "mmap -w 0 1M" -c "mwrite -S 0xcd 0 1M" -c "munmap" $MNT/foobar
  0000000 ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab
  *
  1048576

Fix this by not failing in case we can't allocate data space and we can
NOCOW into the target extent - reserving only metadata space in this case.

After this change the test passes:

  $ ./test.sh
  (...)
  wrote 1048576/1048576 bytes at offset 0
  1 MiB, 256 ops; 0.0007 sec (1.262 GiB/sec and 330749.3540 ops/sec)
  0000000 cd cd cd cd cd cd cd cd cd cd cd cd cd cd cd cd
  *
  1048576

A test case for fstests will be added soon.

Signed-off-by: Filipe Manana <fdmanana@suse.com>
---
 fs/btrfs/file.c | 49 +++++++++++++++++++++++++++++++++++++++++--------
 1 file changed, 41 insertions(+), 8 deletions(-)

diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c
index 05b046c6806f..f08c72dbb530 100644
--- a/fs/btrfs/file.c
+++ b/fs/btrfs/file.c
@@ -1841,6 +1841,7 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
 	loff_t size;
 	size_t fsize = folio_size(folio);
 	int ret;
+	bool only_release_metadata = false;
 	u64 reserved_space;
 	u64 page_start;
 	u64 page_end;
@@ -1861,10 +1862,28 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
 	 * end up waiting indefinitely to get a lock on the page currently
 	 * being processed by btrfs_page_mkwrite() function.
 	 */
-	ret = btrfs_delalloc_reserve_space(BTRFS_I(inode), &data_reserved,
-					   page_start, reserved_space);
-	if (ret < 0)
+	ret = btrfs_check_data_free_space(BTRFS_I(inode), &data_reserved,
+					  page_start, reserved_space, false);
+	if (ret < 0) {
+		size_t write_bytes = reserved_space;
+
+		if (btrfs_check_nocow_lock(BTRFS_I(inode), page_start,
+					   &write_bytes, false) <= 0)
+			goto out_noreserve;
+
+		if (write_bytes < reserved_space)
+			goto out_noreserve;
+
+		only_release_metadata = true;
+	}
+	ret = btrfs_delalloc_reserve_metadata(BTRFS_I(inode), reserved_space,
+					      reserved_space, false);
+	if (ret < 0) {
+		if (!only_release_metadata)
+			btrfs_free_reserved_data_space(BTRFS_I(inode), data_reserved,
+						       page_start, reserved_space);
 		goto out_noreserve;
+	}
 
 	ret = file_update_time(vmf->vma->vm_file);
 	if (ret < 0)
@@ -1906,9 +1925,11 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
 		reserved_space = round_up(size - page_start, fs_info->sectorsize);
 		if (reserved_space < fsize) {
 			end = page_start + reserved_space - 1;
-			btrfs_delalloc_release_space(BTRFS_I(inode),
-					data_reserved, end + 1,
-					fsize - reserved_space, true);
+			if (!only_release_metadata)
+				btrfs_delalloc_release_space(BTRFS_I(inode),
+							     data_reserved, end + 1,
+							     fsize - reserved_space,
+							     true);
 		}
 	}
 
@@ -1945,10 +1966,16 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
 
 	btrfs_set_inode_last_sub_trans(BTRFS_I(inode));
 
+	if (only_release_metadata)
+		btrfs_set_extent_bit(io_tree, page_start, end, EXTENT_NORESERVE,
+				     &cached_state);
+
 	btrfs_unlock_extent(io_tree, page_start, page_end, &cached_state);
 	up_read(&BTRFS_I(inode)->i_mmap_lock);
 
 	btrfs_delalloc_release_extents(BTRFS_I(inode), fsize);
+	if (only_release_metadata)
+		btrfs_check_nocow_unlock(BTRFS_I(inode));
 	sb_end_pagefault(inode->i_sb);
 	extent_changeset_free(data_reserved);
 	return VM_FAULT_LOCKED;
@@ -1958,10 +1985,16 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
 	up_read(&BTRFS_I(inode)->i_mmap_lock);
 out:
 	btrfs_delalloc_release_extents(BTRFS_I(inode), fsize);
-	btrfs_delalloc_release_space(BTRFS_I(inode), data_reserved, page_start,
-				     reserved_space, true);
+	if (only_release_metadata)
+		btrfs_delalloc_release_metadata(BTRFS_I(inode), reserved_space, true);
+	else
+		btrfs_delalloc_release_space(BTRFS_I(inode), data_reserved,
+					     page_start, reserved_space, true);
 	extent_changeset_free(data_reserved);
 out_noreserve:
+	if (only_release_metadata)
+		btrfs_check_nocow_unlock(BTRFS_I(inode));
+
 	sb_end_pagefault(inode->i_sb);
 
 	if (ret < 0)
-- 
2.47.2


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH 2/3] btrfs: use variable for io_tree when clearing range in btrfs_page_mkwrite()
  2025-07-09  8:53 [PATCH 0/3] btrfs: fix mmap writes for NOCOW files/extents fdmanana
  2025-07-09  8:53 ` [PATCH 1/3] btrfs: fix -ENOSPC mmap write failure on " fdmanana
@ 2025-07-09  8:53 ` fdmanana
  2025-07-09 10:02   ` Qu Wenruo
  2025-07-09  8:53 ` [PATCH 3/3] btrfs: use btrfs_inode local variable at btrfs_page_mkwrite() fdmanana
                   ` (2 subsequent siblings)
  4 siblings, 1 reply; 17+ messages in thread
From: fdmanana @ 2025-07-09  8:53 UTC (permalink / raw)
  To: linux-btrfs

From: Filipe Manana <fdmanana@suse.com>

We have the inode's io_tree already stored in a local variable, so use it
instead of grabbing it again in the call to btrfs_clear_extent_bit().

Signed-off-by: Filipe Manana <fdmanana@suse.com>
---
 fs/btrfs/file.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c
index f08c72dbb530..e76e92873de5 100644
--- a/fs/btrfs/file.c
+++ b/fs/btrfs/file.c
@@ -1940,7 +1940,7 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
 	 * clear any delalloc bits within this page range since we have to
 	 * reserve data&meta space before lock_page() (see above comments).
 	 */
-	btrfs_clear_extent_bit(&BTRFS_I(inode)->io_tree, page_start, end,
+	btrfs_clear_extent_bit(io_tree, page_start, end,
 			       EXTENT_DELALLOC | EXTENT_DO_ACCOUNTING |
 			       EXTENT_DEFRAG, &cached_state);
 
-- 
2.47.2


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH 3/3] btrfs: use btrfs_inode local variable at btrfs_page_mkwrite()
  2025-07-09  8:53 [PATCH 0/3] btrfs: fix mmap writes for NOCOW files/extents fdmanana
  2025-07-09  8:53 ` [PATCH 1/3] btrfs: fix -ENOSPC mmap write failure on " fdmanana
  2025-07-09  8:53 ` [PATCH 2/3] btrfs: use variable for io_tree when clearing range in btrfs_page_mkwrite() fdmanana
@ 2025-07-09  8:53 ` fdmanana
  2025-07-09 10:04   ` Qu Wenruo
  2025-07-09 11:13 ` [PATCH v2 0/3] btrfs: fix mmap writes for NOCOW files/extents fdmanana
  2025-07-09 11:45 ` [PATCH v3 0/3] btrfs: fix mmap writes for NOCOW files/extents fdmanana
  4 siblings, 1 reply; 17+ messages in thread
From: fdmanana @ 2025-07-09  8:53 UTC (permalink / raw)
  To: linux-btrfs

From: Filipe Manana <fdmanana@suse.com>

Most of the time we want to use the btrfs_inode, so change the local inode
variable to be a btrfs_inode instead of a vfs inode, reducing verbosity
by eleminating a lot of BTRFS_I() calls.

Signed-off-by: Filipe Manana <fdmanana@suse.com>
---
 fs/btrfs/file.c | 58 ++++++++++++++++++++++++-------------------------
 1 file changed, 28 insertions(+), 30 deletions(-)

diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c
index e76e92873de5..765ffa06688b 100644
--- a/fs/btrfs/file.c
+++ b/fs/btrfs/file.c
@@ -1831,9 +1831,9 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
 {
 	struct page *page = vmf->page;
 	struct folio *folio = page_folio(page);
-	struct inode *inode = file_inode(vmf->vma->vm_file);
-	struct btrfs_fs_info *fs_info = inode_to_fs_info(inode);
-	struct extent_io_tree *io_tree = &BTRFS_I(inode)->io_tree;
+	struct btrfs_inode *inode = BTRFS_I(file_inode(vmf->vma->vm_file));
+	struct btrfs_fs_info *fs_info = inode->root->fs_info;
+	struct extent_io_tree *io_tree = &inode->io_tree;
 	struct btrfs_ordered_extent *ordered;
 	struct extent_state *cached_state = NULL;
 	struct extent_changeset *data_reserved = NULL;
@@ -1849,7 +1849,7 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
 
 	reserved_space = fsize;
 
-	sb_start_pagefault(inode->i_sb);
+	sb_start_pagefault(inode->vfs_inode.i_sb);
 	page_start = folio_pos(folio);
 	page_end = page_start + folio_size(folio) - 1;
 	end = page_end;
@@ -1862,13 +1862,12 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
 	 * end up waiting indefinitely to get a lock on the page currently
 	 * being processed by btrfs_page_mkwrite() function.
 	 */
-	ret = btrfs_check_data_free_space(BTRFS_I(inode), &data_reserved,
-					  page_start, reserved_space, false);
+	ret = btrfs_check_data_free_space(inode, &data_reserved, page_start,
+					  reserved_space, false);
 	if (ret < 0) {
 		size_t write_bytes = reserved_space;
 
-		if (btrfs_check_nocow_lock(BTRFS_I(inode), page_start,
-					   &write_bytes, false) <= 0)
+		if (btrfs_check_nocow_lock(inode, page_start, &write_bytes, false) <= 0)
 			goto out_noreserve;
 
 		if (write_bytes < reserved_space)
@@ -1876,11 +1875,11 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
 
 		only_release_metadata = true;
 	}
-	ret = btrfs_delalloc_reserve_metadata(BTRFS_I(inode), reserved_space,
+	ret = btrfs_delalloc_reserve_metadata(inode, reserved_space,
 					      reserved_space, false);
 	if (ret < 0) {
 		if (!only_release_metadata)
-			btrfs_free_reserved_data_space(BTRFS_I(inode), data_reserved,
+			btrfs_free_reserved_data_space(inode, data_reserved,
 						       page_start, reserved_space);
 		goto out_noreserve;
 	}
@@ -1889,11 +1888,11 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
 	if (ret < 0)
 		goto out;
 again:
-	down_read(&BTRFS_I(inode)->i_mmap_lock);
+	down_read(&inode->i_mmap_lock);
 	folio_lock(folio);
-	size = i_size_read(inode);
+	size = i_size_read(&inode->vfs_inode);
 
-	if ((folio->mapping != inode->i_mapping) ||
+	if ((folio->mapping != inode->vfs_inode.i_mapping) ||
 	    (page_start >= size)) {
 		/* Page got truncated out from underneath us. */
 		goto out_unlock;
@@ -1911,11 +1910,11 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
 	 * We can't set the delalloc bits if there are pending ordered
 	 * extents.  Drop our locks and wait for them to finish.
 	 */
-	ordered = btrfs_lookup_ordered_range(BTRFS_I(inode), page_start, fsize);
+	ordered = btrfs_lookup_ordered_range(inode, page_start, fsize);
 	if (ordered) {
 		btrfs_unlock_extent(io_tree, page_start, page_end, &cached_state);
 		folio_unlock(folio);
-		up_read(&BTRFS_I(inode)->i_mmap_lock);
+		up_read(&inode->i_mmap_lock);
 		btrfs_start_ordered_extent(ordered);
 		btrfs_put_ordered_extent(ordered);
 		goto again;
@@ -1926,7 +1925,7 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
 		if (reserved_space < fsize) {
 			end = page_start + reserved_space - 1;
 			if (!only_release_metadata)
-				btrfs_delalloc_release_space(BTRFS_I(inode),
+				btrfs_delalloc_release_space(inode,
 							     data_reserved, end + 1,
 							     fsize - reserved_space,
 							     true);
@@ -1944,8 +1943,7 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
 			       EXTENT_DELALLOC | EXTENT_DO_ACCOUNTING |
 			       EXTENT_DEFRAG, &cached_state);
 
-	ret = btrfs_set_extent_delalloc(BTRFS_I(inode), page_start, end, 0,
-					&cached_state);
+	ret = btrfs_set_extent_delalloc(inode, page_start, end, 0, &cached_state);
 	if (ret < 0) {
 		btrfs_unlock_extent(io_tree, page_start, page_end, &cached_state);
 		goto out_unlock;
@@ -1964,38 +1962,38 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
 	btrfs_folio_set_dirty(fs_info, folio, page_start, end + 1 - page_start);
 	btrfs_folio_set_uptodate(fs_info, folio, page_start, end + 1 - page_start);
 
-	btrfs_set_inode_last_sub_trans(BTRFS_I(inode));
+	btrfs_set_inode_last_sub_trans(inode);
 
 	if (only_release_metadata)
 		btrfs_set_extent_bit(io_tree, page_start, end, EXTENT_NORESERVE,
 				     &cached_state);
 
 	btrfs_unlock_extent(io_tree, page_start, page_end, &cached_state);
-	up_read(&BTRFS_I(inode)->i_mmap_lock);
+	up_read(&inode->i_mmap_lock);
 
-	btrfs_delalloc_release_extents(BTRFS_I(inode), fsize);
+	btrfs_delalloc_release_extents(inode, fsize);
 	if (only_release_metadata)
-		btrfs_check_nocow_unlock(BTRFS_I(inode));
-	sb_end_pagefault(inode->i_sb);
+		btrfs_check_nocow_unlock(inode);
+	sb_end_pagefault(inode->vfs_inode.i_sb);
 	extent_changeset_free(data_reserved);
 	return VM_FAULT_LOCKED;
 
 out_unlock:
 	folio_unlock(folio);
-	up_read(&BTRFS_I(inode)->i_mmap_lock);
+	up_read(&inode->i_mmap_lock);
 out:
-	btrfs_delalloc_release_extents(BTRFS_I(inode), fsize);
+	btrfs_delalloc_release_extents(inode, fsize);
 	if (only_release_metadata)
-		btrfs_delalloc_release_metadata(BTRFS_I(inode), reserved_space, true);
+		btrfs_delalloc_release_metadata(inode, reserved_space, true);
 	else
-		btrfs_delalloc_release_space(BTRFS_I(inode), data_reserved,
-					     page_start, reserved_space, true);
+		btrfs_delalloc_release_space(inode, data_reserved, page_start,
+					     reserved_space, true);
 	extent_changeset_free(data_reserved);
 out_noreserve:
 	if (only_release_metadata)
-		btrfs_check_nocow_unlock(BTRFS_I(inode));
+		btrfs_check_nocow_unlock(inode);
 
-	sb_end_pagefault(inode->i_sb);
+	sb_end_pagefault(inode->vfs_inode.i_sb);
 
 	if (ret < 0)
 		return vmf_error(ret);
-- 
2.47.2


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* Re: [PATCH 1/3] btrfs: fix -ENOSPC mmap write failure on NOCOW files/extents
  2025-07-09  8:53 ` [PATCH 1/3] btrfs: fix -ENOSPC mmap write failure on " fdmanana
@ 2025-07-09 10:02   ` Qu Wenruo
  2025-07-09 10:36     ` Filipe Manana
  0 siblings, 1 reply; 17+ messages in thread
From: Qu Wenruo @ 2025-07-09 10:02 UTC (permalink / raw)
  To: fdmanana, linux-btrfs



在 2025/7/9 18:23, fdmanana@kernel.org 写道:
> From: Filipe Manana <fdmanana@suse.com>
> 
> If we attempt a mmap write into a NOCOW file or a prealloc extent when
> there is no more available data space (or unallocated space to allocate a
> new data block group) and we can do a NOCOW write (there are no reflinks
> for the target extent or snapshots), we always fail due to -ENOSPC, unlike
> for the regular buffered write and direct IO paths where we check that we
> can do a NOCOW write in case we can't reserve data space.
> 
> Simple reproducer:
> 
>    $ cat test.sh
>    #!/bin/bash
> 
>    DEV=/dev/sdi
>    MNT=/mnt/sdi
> 
>    umount $DEV &> /dev/null
>    mkfs.btrfs -f -b $((512 * 1024 * 1024)) $DEV
>    mount $DEV $MNT
> 
>    touch $MNT/foobar
>    # Make it a NOCOW file.
>    chattr +C $MNT/foobar
> 
>    # Add initial data to file.
>    xfs_io -c "pwrite -S 0xab 0 1M" $MNT/foobar
> 
>    # Fill all the remaining data space and unallocated space with data.
>    dd if=/dev/zero of=$MNT/filler bs=4K &> /dev/null
> 
>    # Overwrite the file with a mmap write. Should succeed.
>    xfs_io -c "mmap -w 0 1M"        \
>           -c "mwrite -S 0xcd 0 1M" \
>           -c "munmap"              \
>           $MNT/foobar
> 
>    # Unmount, mount again and verify the new data was persisted.
>    umount $MNT
>    mount $DEV $MNT
> 
>    od -A d -t x1 $MNT/foobar
> 
>    umount $MNT
> 
> Running this:
> 
>    $ ./test.sh
>    (...)
>    wrote 1048576/1048576 bytes at offset 0
>    1 MiB, 256 ops; 0.0008 sec (1.188 GiB/sec and 311435.5231 ops/sec)
>    ./test.sh: line 24: 234865 Bus error               xfs_io -c "mmap -w 0 1M" -c "mwrite -S 0xcd 0 1M" -c "munmap" $MNT/foobar
>    0000000 ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab
>    *
>    1048576
> 
> Fix this by not failing in case we can't allocate data space and we can
> NOCOW into the target extent - reserving only metadata space in this case.
> 
> After this change the test passes:
> 
>    $ ./test.sh
>    (...)
>    wrote 1048576/1048576 bytes at offset 0
>    1 MiB, 256 ops; 0.0007 sec (1.262 GiB/sec and 330749.3540 ops/sec)
>    0000000 cd cd cd cd cd cd cd cd cd cd cd cd cd cd cd cd
>    *
>    1048576
> 
> A test case for fstests will be added soon.
> 
> Signed-off-by: Filipe Manana <fdmanana@suse.com>

Reviewed-by: Qu Wenruo <wqu@suse.com>

With large data folios, I'm afraid we may fail the nocow check more 
frequently than the regular page sized folios.
But that's unavoidable, we have to ensure the whole folio can be written 
back NOCOW.

Thanks,
Qu

> ---
>   fs/btrfs/file.c | 49 +++++++++++++++++++++++++++++++++++++++++--------
>   1 file changed, 41 insertions(+), 8 deletions(-)
> 
> diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c
> index 05b046c6806f..f08c72dbb530 100644
> --- a/fs/btrfs/file.c
> +++ b/fs/btrfs/file.c
> @@ -1841,6 +1841,7 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
>   	loff_t size;
>   	size_t fsize = folio_size(folio);
>   	int ret;
> +	bool only_release_metadata = false;
>   	u64 reserved_space;
>   	u64 page_start;
>   	u64 page_end;
> @@ -1861,10 +1862,28 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
>   	 * end up waiting indefinitely to get a lock on the page currently
>   	 * being processed by btrfs_page_mkwrite() function.
>   	 */
> -	ret = btrfs_delalloc_reserve_space(BTRFS_I(inode), &data_reserved,
> -					   page_start, reserved_space);
> -	if (ret < 0)
> +	ret = btrfs_check_data_free_space(BTRFS_I(inode), &data_reserved,
> +					  page_start, reserved_space, false);
> +	if (ret < 0) {
> +		size_t write_bytes = reserved_space;
> +
> +		if (btrfs_check_nocow_lock(BTRFS_I(inode), page_start,
> +					   &write_bytes, false) <= 0)
> +			goto out_noreserve;
> +
> +		if (write_bytes < reserved_space)
> +			goto out_noreserve;
> +
> +		only_release_metadata = true;
> +	}
> +	ret = btrfs_delalloc_reserve_metadata(BTRFS_I(inode), reserved_space,
> +					      reserved_space, false);
> +	if (ret < 0) {
> +		if (!only_release_metadata)
> +			btrfs_free_reserved_data_space(BTRFS_I(inode), data_reserved,
> +						       page_start, reserved_space);
>   		goto out_noreserve;
> +	}
>   
>   	ret = file_update_time(vmf->vma->vm_file);
>   	if (ret < 0)
> @@ -1906,9 +1925,11 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
>   		reserved_space = round_up(size - page_start, fs_info->sectorsize);
>   		if (reserved_space < fsize) {
>   			end = page_start + reserved_space - 1;
> -			btrfs_delalloc_release_space(BTRFS_I(inode),
> -					data_reserved, end + 1,
> -					fsize - reserved_space, true);
> +			if (!only_release_metadata)
> +				btrfs_delalloc_release_space(BTRFS_I(inode),
> +							     data_reserved, end + 1,
> +							     fsize - reserved_space,
> +							     true);
>   		}
>   	}
>   
> @@ -1945,10 +1966,16 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
>   
>   	btrfs_set_inode_last_sub_trans(BTRFS_I(inode));
>   
> +	if (only_release_metadata)
> +		btrfs_set_extent_bit(io_tree, page_start, end, EXTENT_NORESERVE,
> +				     &cached_state);
> +
>   	btrfs_unlock_extent(io_tree, page_start, page_end, &cached_state);
>   	up_read(&BTRFS_I(inode)->i_mmap_lock);
>   
>   	btrfs_delalloc_release_extents(BTRFS_I(inode), fsize);
> +	if (only_release_metadata)
> +		btrfs_check_nocow_unlock(BTRFS_I(inode));
>   	sb_end_pagefault(inode->i_sb);
>   	extent_changeset_free(data_reserved);
>   	return VM_FAULT_LOCKED;
> @@ -1958,10 +1985,16 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
>   	up_read(&BTRFS_I(inode)->i_mmap_lock);
>   out:
>   	btrfs_delalloc_release_extents(BTRFS_I(inode), fsize);
> -	btrfs_delalloc_release_space(BTRFS_I(inode), data_reserved, page_start,
> -				     reserved_space, true);
> +	if (only_release_metadata)
> +		btrfs_delalloc_release_metadata(BTRFS_I(inode), reserved_space, true);
> +	else
> +		btrfs_delalloc_release_space(BTRFS_I(inode), data_reserved,
> +					     page_start, reserved_space, true);
>   	extent_changeset_free(data_reserved);
>   out_noreserve:
> +	if (only_release_metadata)
> +		btrfs_check_nocow_unlock(BTRFS_I(inode));
> +
>   	sb_end_pagefault(inode->i_sb);
>   
>   	if (ret < 0)


^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH 2/3] btrfs: use variable for io_tree when clearing range in btrfs_page_mkwrite()
  2025-07-09  8:53 ` [PATCH 2/3] btrfs: use variable for io_tree when clearing range in btrfs_page_mkwrite() fdmanana
@ 2025-07-09 10:02   ` Qu Wenruo
  0 siblings, 0 replies; 17+ messages in thread
From: Qu Wenruo @ 2025-07-09 10:02 UTC (permalink / raw)
  To: fdmanana, linux-btrfs



在 2025/7/9 18:23, fdmanana@kernel.org 写道:
> From: Filipe Manana <fdmanana@suse.com>
> 
> We have the inode's io_tree already stored in a local variable, so use it
> instead of grabbing it again in the call to btrfs_clear_extent_bit().
> 
> Signed-off-by: Filipe Manana <fdmanana@suse.com>

Reviewed-by: Qu Wenruo <wqu@suse.com>

Thanks,
Qu

> ---
>   fs/btrfs/file.c | 2 +-
>   1 file changed, 1 insertion(+), 1 deletion(-)
> 
> diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c
> index f08c72dbb530..e76e92873de5 100644
> --- a/fs/btrfs/file.c
> +++ b/fs/btrfs/file.c
> @@ -1940,7 +1940,7 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
>   	 * clear any delalloc bits within this page range since we have to
>   	 * reserve data&meta space before lock_page() (see above comments).
>   	 */
> -	btrfs_clear_extent_bit(&BTRFS_I(inode)->io_tree, page_start, end,
> +	btrfs_clear_extent_bit(io_tree, page_start, end,
>   			       EXTENT_DELALLOC | EXTENT_DO_ACCOUNTING |
>   			       EXTENT_DEFRAG, &cached_state);
>   


^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH 3/3] btrfs: use btrfs_inode local variable at btrfs_page_mkwrite()
  2025-07-09  8:53 ` [PATCH 3/3] btrfs: use btrfs_inode local variable at btrfs_page_mkwrite() fdmanana
@ 2025-07-09 10:04   ` Qu Wenruo
  2025-07-09 10:39     ` Filipe Manana
  0 siblings, 1 reply; 17+ messages in thread
From: Qu Wenruo @ 2025-07-09 10:04 UTC (permalink / raw)
  To: fdmanana, linux-btrfs



在 2025/7/9 18:23, fdmanana@kernel.org 写道:
> From: Filipe Manana <fdmanana@suse.com>
> 
> Most of the time we want to use the btrfs_inode, so change the local inode
> variable to be a btrfs_inode instead of a vfs inode, reducing verbosity
> by eleminating a lot of BTRFS_I() calls.
> 
> Signed-off-by: Filipe Manana <fdmanana@suse.com>

Reviewed-by: Qu Wenruo <wqu@suse.com>
[...]
> -		if (btrfs_check_nocow_lock(BTRFS_I(inode), page_start,
> -					   &write_bytes, false) <= 0)
> +		if (btrfs_check_nocow_lock(inode, page_start, &write_bytes, false) <= 0)

I guess we are no longer limited by 80 chars line limit anymore?

What would be the new limit? 100 from checkpatch or something slightly 
lower like 90?

Thanks,
Qu

>   			goto out_noreserve;
>   
>   		if (write_bytes < reserved_space)
> @@ -1876,11 +1875,11 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
>   
>   		only_release_metadata = true;
>   	}
> -	ret = btrfs_delalloc_reserve_metadata(BTRFS_I(inode), reserved_space,
> +	ret = btrfs_delalloc_reserve_metadata(inode, reserved_space,
>   					      reserved_space, false);
>   	if (ret < 0) {
>   		if (!only_release_metadata)
> -			btrfs_free_reserved_data_space(BTRFS_I(inode), data_reserved,
> +			btrfs_free_reserved_data_space(inode, data_reserved,
>   						       page_start, reserved_space);
>   		goto out_noreserve;
>   	}
> @@ -1889,11 +1888,11 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
>   	if (ret < 0)
>   		goto out;
>   again:
> -	down_read(&BTRFS_I(inode)->i_mmap_lock);
> +	down_read(&inode->i_mmap_lock);
>   	folio_lock(folio);
> -	size = i_size_read(inode);
> +	size = i_size_read(&inode->vfs_inode);
>   
> -	if ((folio->mapping != inode->i_mapping) ||
> +	if ((folio->mapping != inode->vfs_inode.i_mapping) ||
>   	    (page_start >= size)) {
>   		/* Page got truncated out from underneath us. */
>   		goto out_unlock;
> @@ -1911,11 +1910,11 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
>   	 * We can't set the delalloc bits if there are pending ordered
>   	 * extents.  Drop our locks and wait for them to finish.
>   	 */
> -	ordered = btrfs_lookup_ordered_range(BTRFS_I(inode), page_start, fsize);
> +	ordered = btrfs_lookup_ordered_range(inode, page_start, fsize);
>   	if (ordered) {
>   		btrfs_unlock_extent(io_tree, page_start, page_end, &cached_state);
>   		folio_unlock(folio);
> -		up_read(&BTRFS_I(inode)->i_mmap_lock);
> +		up_read(&inode->i_mmap_lock);
>   		btrfs_start_ordered_extent(ordered);
>   		btrfs_put_ordered_extent(ordered);
>   		goto again;
> @@ -1926,7 +1925,7 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
>   		if (reserved_space < fsize) {
>   			end = page_start + reserved_space - 1;
>   			if (!only_release_metadata)
> -				btrfs_delalloc_release_space(BTRFS_I(inode),
> +				btrfs_delalloc_release_space(inode,
>   							     data_reserved, end + 1,
>   							     fsize - reserved_space,
>   							     true);
> @@ -1944,8 +1943,7 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
>   			       EXTENT_DELALLOC | EXTENT_DO_ACCOUNTING |
>   			       EXTENT_DEFRAG, &cached_state);
>   
> -	ret = btrfs_set_extent_delalloc(BTRFS_I(inode), page_start, end, 0,
> -					&cached_state);
> +	ret = btrfs_set_extent_delalloc(inode, page_start, end, 0, &cached_state);
>   	if (ret < 0) {
>   		btrfs_unlock_extent(io_tree, page_start, page_end, &cached_state);
>   		goto out_unlock;
> @@ -1964,38 +1962,38 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
>   	btrfs_folio_set_dirty(fs_info, folio, page_start, end + 1 - page_start);
>   	btrfs_folio_set_uptodate(fs_info, folio, page_start, end + 1 - page_start);
>   
> -	btrfs_set_inode_last_sub_trans(BTRFS_I(inode));
> +	btrfs_set_inode_last_sub_trans(inode);
>   
>   	if (only_release_metadata)
>   		btrfs_set_extent_bit(io_tree, page_start, end, EXTENT_NORESERVE,
>   				     &cached_state);
>   
>   	btrfs_unlock_extent(io_tree, page_start, page_end, &cached_state);
> -	up_read(&BTRFS_I(inode)->i_mmap_lock);
> +	up_read(&inode->i_mmap_lock);
>   
> -	btrfs_delalloc_release_extents(BTRFS_I(inode), fsize);
> +	btrfs_delalloc_release_extents(inode, fsize);
>   	if (only_release_metadata)
> -		btrfs_check_nocow_unlock(BTRFS_I(inode));
> -	sb_end_pagefault(inode->i_sb);
> +		btrfs_check_nocow_unlock(inode);
> +	sb_end_pagefault(inode->vfs_inode.i_sb);
>   	extent_changeset_free(data_reserved);
>   	return VM_FAULT_LOCKED;
>   
>   out_unlock:
>   	folio_unlock(folio);
> -	up_read(&BTRFS_I(inode)->i_mmap_lock);
> +	up_read(&inode->i_mmap_lock);
>   out:
> -	btrfs_delalloc_release_extents(BTRFS_I(inode), fsize);
> +	btrfs_delalloc_release_extents(inode, fsize);
>   	if (only_release_metadata)
> -		btrfs_delalloc_release_metadata(BTRFS_I(inode), reserved_space, true);
> +		btrfs_delalloc_release_metadata(inode, reserved_space, true);
>   	else
> -		btrfs_delalloc_release_space(BTRFS_I(inode), data_reserved,
> -					     page_start, reserved_space, true);
> +		btrfs_delalloc_release_space(inode, data_reserved, page_start,
> +					     reserved_space, true);
>   	extent_changeset_free(data_reserved);
>   out_noreserve:
>   	if (only_release_metadata)
> -		btrfs_check_nocow_unlock(BTRFS_I(inode));
> +		btrfs_check_nocow_unlock(inode);
>   
> -	sb_end_pagefault(inode->i_sb);
> +	sb_end_pagefault(inode->vfs_inode.i_sb);
>   
>   	if (ret < 0)
>   		return vmf_error(ret);


^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH 1/3] btrfs: fix -ENOSPC mmap write failure on NOCOW files/extents
  2025-07-09 10:02   ` Qu Wenruo
@ 2025-07-09 10:36     ` Filipe Manana
  0 siblings, 0 replies; 17+ messages in thread
From: Filipe Manana @ 2025-07-09 10:36 UTC (permalink / raw)
  To: Qu Wenruo; +Cc: linux-btrfs

On Wed, Jul 9, 2025 at 11:02 AM Qu Wenruo <quwenruo.btrfs@gmx.com> wrote:
>
>
>
> 在 2025/7/9 18:23, fdmanana@kernel.org 写道:
> > From: Filipe Manana <fdmanana@suse.com>
> >
> > If we attempt a mmap write into a NOCOW file or a prealloc extent when
> > there is no more available data space (or unallocated space to allocate a
> > new data block group) and we can do a NOCOW write (there are no reflinks
> > for the target extent or snapshots), we always fail due to -ENOSPC, unlike
> > for the regular buffered write and direct IO paths where we check that we
> > can do a NOCOW write in case we can't reserve data space.
> >
> > Simple reproducer:
> >
> >    $ cat test.sh
> >    #!/bin/bash
> >
> >    DEV=/dev/sdi
> >    MNT=/mnt/sdi
> >
> >    umount $DEV &> /dev/null
> >    mkfs.btrfs -f -b $((512 * 1024 * 1024)) $DEV
> >    mount $DEV $MNT
> >
> >    touch $MNT/foobar
> >    # Make it a NOCOW file.
> >    chattr +C $MNT/foobar
> >
> >    # Add initial data to file.
> >    xfs_io -c "pwrite -S 0xab 0 1M" $MNT/foobar
> >
> >    # Fill all the remaining data space and unallocated space with data.
> >    dd if=/dev/zero of=$MNT/filler bs=4K &> /dev/null
> >
> >    # Overwrite the file with a mmap write. Should succeed.
> >    xfs_io -c "mmap -w 0 1M"        \
> >           -c "mwrite -S 0xcd 0 1M" \
> >           -c "munmap"              \
> >           $MNT/foobar
> >
> >    # Unmount, mount again and verify the new data was persisted.
> >    umount $MNT
> >    mount $DEV $MNT
> >
> >    od -A d -t x1 $MNT/foobar
> >
> >    umount $MNT
> >
> > Running this:
> >
> >    $ ./test.sh
> >    (...)
> >    wrote 1048576/1048576 bytes at offset 0
> >    1 MiB, 256 ops; 0.0008 sec (1.188 GiB/sec and 311435.5231 ops/sec)
> >    ./test.sh: line 24: 234865 Bus error               xfs_io -c "mmap -w 0 1M" -c "mwrite -S 0xcd 0 1M" -c "munmap" $MNT/foobar
> >    0000000 ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab
> >    *
> >    1048576
> >
> > Fix this by not failing in case we can't allocate data space and we can
> > NOCOW into the target extent - reserving only metadata space in this case.
> >
> > After this change the test passes:
> >
> >    $ ./test.sh
> >    (...)
> >    wrote 1048576/1048576 bytes at offset 0
> >    1 MiB, 256 ops; 0.0007 sec (1.262 GiB/sec and 330749.3540 ops/sec)
> >    0000000 cd cd cd cd cd cd cd cd cd cd cd cd cd cd cd cd
> >    *
> >    1048576
> >
> > A test case for fstests will be added soon.
> >
> > Signed-off-by: Filipe Manana <fdmanana@suse.com>
>
> Reviewed-by: Qu Wenruo <wqu@suse.com>
>
> With large data folios, I'm afraid we may fail the nocow check more
> frequently than the regular page sized folios.

Why?
For a NOCOW file, assuming there are no reflinks or snapshots, it
doesn't matter how big the folio is or how many extents it spans.

> But that's unavoidable, we have to ensure the whole folio can be written
> back NOCOW.
>
> Thanks,
> Qu
>
> > ---
> >   fs/btrfs/file.c | 49 +++++++++++++++++++++++++++++++++++++++++--------
> >   1 file changed, 41 insertions(+), 8 deletions(-)
> >
> > diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c
> > index 05b046c6806f..f08c72dbb530 100644
> > --- a/fs/btrfs/file.c
> > +++ b/fs/btrfs/file.c
> > @@ -1841,6 +1841,7 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
> >       loff_t size;
> >       size_t fsize = folio_size(folio);
> >       int ret;
> > +     bool only_release_metadata = false;
> >       u64 reserved_space;
> >       u64 page_start;
> >       u64 page_end;
> > @@ -1861,10 +1862,28 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
> >        * end up waiting indefinitely to get a lock on the page currently
> >        * being processed by btrfs_page_mkwrite() function.
> >        */
> > -     ret = btrfs_delalloc_reserve_space(BTRFS_I(inode), &data_reserved,
> > -                                        page_start, reserved_space);
> > -     if (ret < 0)
> > +     ret = btrfs_check_data_free_space(BTRFS_I(inode), &data_reserved,
> > +                                       page_start, reserved_space, false);
> > +     if (ret < 0) {
> > +             size_t write_bytes = reserved_space;
> > +
> > +             if (btrfs_check_nocow_lock(BTRFS_I(inode), page_start,
> > +                                        &write_bytes, false) <= 0)
> > +                     goto out_noreserve;
> > +
> > +             if (write_bytes < reserved_space)
> > +                     goto out_noreserve;
> > +
> > +             only_release_metadata = true;
> > +     }
> > +     ret = btrfs_delalloc_reserve_metadata(BTRFS_I(inode), reserved_space,
> > +                                           reserved_space, false);
> > +     if (ret < 0) {
> > +             if (!only_release_metadata)
> > +                     btrfs_free_reserved_data_space(BTRFS_I(inode), data_reserved,
> > +                                                    page_start, reserved_space);
> >               goto out_noreserve;
> > +     }
> >
> >       ret = file_update_time(vmf->vma->vm_file);
> >       if (ret < 0)
> > @@ -1906,9 +1925,11 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
> >               reserved_space = round_up(size - page_start, fs_info->sectorsize);
> >               if (reserved_space < fsize) {
> >                       end = page_start + reserved_space - 1;
> > -                     btrfs_delalloc_release_space(BTRFS_I(inode),
> > -                                     data_reserved, end + 1,
> > -                                     fsize - reserved_space, true);
> > +                     if (!only_release_metadata)
> > +                             btrfs_delalloc_release_space(BTRFS_I(inode),
> > +                                                          data_reserved, end + 1,
> > +                                                          fsize - reserved_space,
> > +                                                          true);
> >               }
> >       }
> >
> > @@ -1945,10 +1966,16 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
> >
> >       btrfs_set_inode_last_sub_trans(BTRFS_I(inode));
> >
> > +     if (only_release_metadata)
> > +             btrfs_set_extent_bit(io_tree, page_start, end, EXTENT_NORESERVE,
> > +                                  &cached_state);
> > +
> >       btrfs_unlock_extent(io_tree, page_start, page_end, &cached_state);
> >       up_read(&BTRFS_I(inode)->i_mmap_lock);
> >
> >       btrfs_delalloc_release_extents(BTRFS_I(inode), fsize);
> > +     if (only_release_metadata)
> > +             btrfs_check_nocow_unlock(BTRFS_I(inode));
> >       sb_end_pagefault(inode->i_sb);
> >       extent_changeset_free(data_reserved);
> >       return VM_FAULT_LOCKED;
> > @@ -1958,10 +1985,16 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
> >       up_read(&BTRFS_I(inode)->i_mmap_lock);
> >   out:
> >       btrfs_delalloc_release_extents(BTRFS_I(inode), fsize);
> > -     btrfs_delalloc_release_space(BTRFS_I(inode), data_reserved, page_start,
> > -                                  reserved_space, true);
> > +     if (only_release_metadata)
> > +             btrfs_delalloc_release_metadata(BTRFS_I(inode), reserved_space, true);
> > +     else
> > +             btrfs_delalloc_release_space(BTRFS_I(inode), data_reserved,
> > +                                          page_start, reserved_space, true);
> >       extent_changeset_free(data_reserved);
> >   out_noreserve:
> > +     if (only_release_metadata)
> > +             btrfs_check_nocow_unlock(BTRFS_I(inode));
> > +
> >       sb_end_pagefault(inode->i_sb);
> >
> >       if (ret < 0)
>

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH 3/3] btrfs: use btrfs_inode local variable at btrfs_page_mkwrite()
  2025-07-09 10:04   ` Qu Wenruo
@ 2025-07-09 10:39     ` Filipe Manana
  0 siblings, 0 replies; 17+ messages in thread
From: Filipe Manana @ 2025-07-09 10:39 UTC (permalink / raw)
  To: Qu Wenruo; +Cc: linux-btrfs

On Wed, Jul 9, 2025 at 11:05 AM Qu Wenruo <quwenruo.btrfs@gmx.com> wrote:
>
>
>
> 在 2025/7/9 18:23, fdmanana@kernel.org 写道:
> > From: Filipe Manana <fdmanana@suse.com>
> >
> > Most of the time we want to use the btrfs_inode, so change the local inode
> > variable to be a btrfs_inode instead of a vfs inode, reducing verbosity
> > by eleminating a lot of BTRFS_I() calls.
> >
> > Signed-off-by: Filipe Manana <fdmanana@suse.com>
>
> Reviewed-by: Qu Wenruo <wqu@suse.com>
> [...]
> > -             if (btrfs_check_nocow_lock(BTRFS_I(inode), page_start,
> > -                                        &write_bytes, false) <= 0)
> > +             if (btrfs_check_nocow_lock(inode, page_start, &write_bytes, false) <= 0)
>
> I guess we are no longer limited by 80 chars line limit anymore?

No we aren't, and it shouldn't be news to anyone anymore, it's been
well over a year, maybe two.
I've commented in reviews several times to avoid line splits when the
total length doesn't exceed 80 characters by too much.

>
> What would be the new limit? 100 from checkpatch or something slightly
> lower like 90?

Within reasonability, in this case it's 88 characters and it makes the
code more readable.

Thanks.

>
> Thanks,
> Qu
>
> >                       goto out_noreserve;
> >
> >               if (write_bytes < reserved_space)
> > @@ -1876,11 +1875,11 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
> >
> >               only_release_metadata = true;
> >       }
> > -     ret = btrfs_delalloc_reserve_metadata(BTRFS_I(inode), reserved_space,
> > +     ret = btrfs_delalloc_reserve_metadata(inode, reserved_space,
> >                                             reserved_space, false);
> >       if (ret < 0) {
> >               if (!only_release_metadata)
> > -                     btrfs_free_reserved_data_space(BTRFS_I(inode), data_reserved,
> > +                     btrfs_free_reserved_data_space(inode, data_reserved,
> >                                                      page_start, reserved_space);
> >               goto out_noreserve;
> >       }
> > @@ -1889,11 +1888,11 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
> >       if (ret < 0)
> >               goto out;
> >   again:
> > -     down_read(&BTRFS_I(inode)->i_mmap_lock);
> > +     down_read(&inode->i_mmap_lock);
> >       folio_lock(folio);
> > -     size = i_size_read(inode);
> > +     size = i_size_read(&inode->vfs_inode);
> >
> > -     if ((folio->mapping != inode->i_mapping) ||
> > +     if ((folio->mapping != inode->vfs_inode.i_mapping) ||
> >           (page_start >= size)) {
> >               /* Page got truncated out from underneath us. */
> >               goto out_unlock;
> > @@ -1911,11 +1910,11 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
> >        * We can't set the delalloc bits if there are pending ordered
> >        * extents.  Drop our locks and wait for them to finish.
> >        */
> > -     ordered = btrfs_lookup_ordered_range(BTRFS_I(inode), page_start, fsize);
> > +     ordered = btrfs_lookup_ordered_range(inode, page_start, fsize);
> >       if (ordered) {
> >               btrfs_unlock_extent(io_tree, page_start, page_end, &cached_state);
> >               folio_unlock(folio);
> > -             up_read(&BTRFS_I(inode)->i_mmap_lock);
> > +             up_read(&inode->i_mmap_lock);
> >               btrfs_start_ordered_extent(ordered);
> >               btrfs_put_ordered_extent(ordered);
> >               goto again;
> > @@ -1926,7 +1925,7 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
> >               if (reserved_space < fsize) {
> >                       end = page_start + reserved_space - 1;
> >                       if (!only_release_metadata)
> > -                             btrfs_delalloc_release_space(BTRFS_I(inode),
> > +                             btrfs_delalloc_release_space(inode,
> >                                                            data_reserved, end + 1,
> >                                                            fsize - reserved_space,
> >                                                            true);
> > @@ -1944,8 +1943,7 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
> >                              EXTENT_DELALLOC | EXTENT_DO_ACCOUNTING |
> >                              EXTENT_DEFRAG, &cached_state);
> >
> > -     ret = btrfs_set_extent_delalloc(BTRFS_I(inode), page_start, end, 0,
> > -                                     &cached_state);
> > +     ret = btrfs_set_extent_delalloc(inode, page_start, end, 0, &cached_state);
> >       if (ret < 0) {
> >               btrfs_unlock_extent(io_tree, page_start, page_end, &cached_state);
> >               goto out_unlock;
> > @@ -1964,38 +1962,38 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
> >       btrfs_folio_set_dirty(fs_info, folio, page_start, end + 1 - page_start);
> >       btrfs_folio_set_uptodate(fs_info, folio, page_start, end + 1 - page_start);
> >
> > -     btrfs_set_inode_last_sub_trans(BTRFS_I(inode));
> > +     btrfs_set_inode_last_sub_trans(inode);
> >
> >       if (only_release_metadata)
> >               btrfs_set_extent_bit(io_tree, page_start, end, EXTENT_NORESERVE,
> >                                    &cached_state);
> >
> >       btrfs_unlock_extent(io_tree, page_start, page_end, &cached_state);
> > -     up_read(&BTRFS_I(inode)->i_mmap_lock);
> > +     up_read(&inode->i_mmap_lock);
> >
> > -     btrfs_delalloc_release_extents(BTRFS_I(inode), fsize);
> > +     btrfs_delalloc_release_extents(inode, fsize);
> >       if (only_release_metadata)
> > -             btrfs_check_nocow_unlock(BTRFS_I(inode));
> > -     sb_end_pagefault(inode->i_sb);
> > +             btrfs_check_nocow_unlock(inode);
> > +     sb_end_pagefault(inode->vfs_inode.i_sb);
> >       extent_changeset_free(data_reserved);
> >       return VM_FAULT_LOCKED;
> >
> >   out_unlock:
> >       folio_unlock(folio);
> > -     up_read(&BTRFS_I(inode)->i_mmap_lock);
> > +     up_read(&inode->i_mmap_lock);
> >   out:
> > -     btrfs_delalloc_release_extents(BTRFS_I(inode), fsize);
> > +     btrfs_delalloc_release_extents(inode, fsize);
> >       if (only_release_metadata)
> > -             btrfs_delalloc_release_metadata(BTRFS_I(inode), reserved_space, true);
> > +             btrfs_delalloc_release_metadata(inode, reserved_space, true);
> >       else
> > -             btrfs_delalloc_release_space(BTRFS_I(inode), data_reserved,
> > -                                          page_start, reserved_space, true);
> > +             btrfs_delalloc_release_space(inode, data_reserved, page_start,
> > +                                          reserved_space, true);
> >       extent_changeset_free(data_reserved);
> >   out_noreserve:
> >       if (only_release_metadata)
> > -             btrfs_check_nocow_unlock(BTRFS_I(inode));
> > +             btrfs_check_nocow_unlock(inode);
> >
> > -     sb_end_pagefault(inode->i_sb);
> > +     sb_end_pagefault(inode->vfs_inode.i_sb);
> >
> >       if (ret < 0)
> >               return vmf_error(ret);
>

^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH v2 0/3] btrfs: fix mmap writes for NOCOW files/extents
  2025-07-09  8:53 [PATCH 0/3] btrfs: fix mmap writes for NOCOW files/extents fdmanana
                   ` (2 preceding siblings ...)
  2025-07-09  8:53 ` [PATCH 3/3] btrfs: use btrfs_inode local variable at btrfs_page_mkwrite() fdmanana
@ 2025-07-09 11:13 ` fdmanana
  2025-07-09 11:13   ` [PATCH v2 1/3] btrfs: fix -ENOSPC mmap write failure on " fdmanana
                     ` (2 more replies)
  2025-07-09 11:45 ` [PATCH v3 0/3] btrfs: fix mmap writes for NOCOW files/extents fdmanana
  4 siblings, 3 replies; 17+ messages in thread
From: fdmanana @ 2025-07-09 11:13 UTC (permalink / raw)
  To: linux-btrfs

From: Filipe Manana <fdmanana@suse.com>

A bug fix and a couple cleanups. Details in the individual change logs.

V2: Fix a bug in patch 1/3 where if write_bytes < reserved_space we would
    exit without unlocking the cow lock, so set only_release_metadata to
    true before bailing out in that case.
    Added Qu's review tags.

Filipe Manana (3):
  btrfs: fix -ENOSPC mmap write failure on NOCOW files/extents
  btrfs: use variable for io_tree when clearing range in btrfs_page_mkwrite()
  btrfs: use btrfs_inode local variable at btrfs_page_mkwrite()

 fs/btrfs/file.c | 91 ++++++++++++++++++++++++++++++++++---------------
 1 file changed, 64 insertions(+), 27 deletions(-)

-- 
2.47.2


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH v2 1/3] btrfs: fix -ENOSPC mmap write failure on NOCOW files/extents
  2025-07-09 11:13 ` [PATCH v2 0/3] btrfs: fix mmap writes for NOCOW files/extents fdmanana
@ 2025-07-09 11:13   ` fdmanana
  2025-07-09 11:13   ` [PATCH v2 2/3] btrfs: use variable for io_tree when clearing range in btrfs_page_mkwrite() fdmanana
  2025-07-09 11:13   ` [PATCH v2 3/3] btrfs: use btrfs_inode local variable at btrfs_page_mkwrite() fdmanana
  2 siblings, 0 replies; 17+ messages in thread
From: fdmanana @ 2025-07-09 11:13 UTC (permalink / raw)
  To: linux-btrfs

From: Filipe Manana <fdmanana@suse.com>

If we attempt a mmap write into a NOCOW file or a prealloc extent when
there is no more available data space (or unallocated space to allocate a
new data block group) and we can do a NOCOW write (there are no reflinks
for the target extent or snapshots), we always fail due to -ENOSPC, unlike
for the regular buffered write and direct IO paths where we check that we
can do a NOCOW write in case we can't reserve data space.

Simple reproducer:

  $ cat test.sh
  #!/bin/bash

  DEV=/dev/sdi
  MNT=/mnt/sdi

  umount $DEV &> /dev/null
  mkfs.btrfs -f -b $((512 * 1024 * 1024)) $DEV
  mount $DEV $MNT

  touch $MNT/foobar
  # Make it a NOCOW file.
  chattr +C $MNT/foobar

  # Add initial data to file.
  xfs_io -c "pwrite -S 0xab 0 1M" $MNT/foobar

  # Fill all the remaining data space and unallocated space with data.
  dd if=/dev/zero of=$MNT/filler bs=4K &> /dev/null

  # Overwrite the file with a mmap write. Should succeed.
  xfs_io -c "mmap -w 0 1M"        \
         -c "mwrite -S 0xcd 0 1M" \
         -c "munmap"              \
         $MNT/foobar

  # Unmount, mount again and verify the new data was persisted.
  umount $MNT
  mount $DEV $MNT

  od -A d -t x1 $MNT/foobar

  umount $MNT

Running this:

  $ ./test.sh
  (...)
  wrote 1048576/1048576 bytes at offset 0
  1 MiB, 256 ops; 0.0008 sec (1.188 GiB/sec and 311435.5231 ops/sec)
  ./test.sh: line 24: 234865 Bus error               xfs_io -c "mmap -w 0 1M" -c "mwrite -S 0xcd 0 1M" -c "munmap" $MNT/foobar
  0000000 ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab
  *
  1048576

Fix this by not failing in case we can't allocate data space and we can
NOCOW into the target extent - reserving only metadata space in this case.

After this change the test passes:

  $ ./test.sh
  (...)
  wrote 1048576/1048576 bytes at offset 0
  1 MiB, 256 ops; 0.0007 sec (1.262 GiB/sec and 330749.3540 ops/sec)
  0000000 cd cd cd cd cd cd cd cd cd cd cd cd cd cd cd cd
  *
  1048576

A test case for fstests will be added soon.

Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
---
 fs/btrfs/file.c | 55 ++++++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 47 insertions(+), 8 deletions(-)

diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c
index 05b046c6806f..9ea3b4d47f81 100644
--- a/fs/btrfs/file.c
+++ b/fs/btrfs/file.c
@@ -1841,6 +1841,7 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
 	loff_t size;
 	size_t fsize = folio_size(folio);
 	int ret;
+	bool only_release_metadata = false;
 	u64 reserved_space;
 	u64 page_start;
 	u64 page_end;
@@ -1861,10 +1862,34 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
 	 * end up waiting indefinitely to get a lock on the page currently
 	 * being processed by btrfs_page_mkwrite() function.
 	 */
-	ret = btrfs_delalloc_reserve_space(BTRFS_I(inode), &data_reserved,
-					   page_start, reserved_space);
-	if (ret < 0)
+	ret = btrfs_check_data_free_space(BTRFS_I(inode), &data_reserved,
+					  page_start, reserved_space, false);
+	if (ret < 0) {
+		size_t write_bytes = reserved_space;
+
+		if (btrfs_check_nocow_lock(BTRFS_I(inode), page_start,
+					   &write_bytes, false) <= 0)
+			goto out_noreserve;
+
+		only_release_metadata = true;
+
+		/*
+		 * Can't write the whole range, there may be shared extents or
+		 * holes in the range, bail out with @only_release_metadata set
+		 * to true so that we unlock the nocow lock before returning the
+		 * error.
+		 */
+		if (write_bytes < reserved_space)
+			goto out_noreserve;
+	}
+	ret = btrfs_delalloc_reserve_metadata(BTRFS_I(inode), reserved_space,
+					      reserved_space, false);
+	if (ret < 0) {
+		if (!only_release_metadata)
+			btrfs_free_reserved_data_space(BTRFS_I(inode), data_reserved,
+						       page_start, reserved_space);
 		goto out_noreserve;
+	}
 
 	ret = file_update_time(vmf->vma->vm_file);
 	if (ret < 0)
@@ -1906,9 +1931,11 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
 		reserved_space = round_up(size - page_start, fs_info->sectorsize);
 		if (reserved_space < fsize) {
 			end = page_start + reserved_space - 1;
-			btrfs_delalloc_release_space(BTRFS_I(inode),
-					data_reserved, end + 1,
-					fsize - reserved_space, true);
+			if (!only_release_metadata)
+				btrfs_delalloc_release_space(BTRFS_I(inode),
+							     data_reserved, end + 1,
+							     fsize - reserved_space,
+							     true);
 		}
 	}
 
@@ -1945,10 +1972,16 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
 
 	btrfs_set_inode_last_sub_trans(BTRFS_I(inode));
 
+	if (only_release_metadata)
+		btrfs_set_extent_bit(io_tree, page_start, end, EXTENT_NORESERVE,
+				     &cached_state);
+
 	btrfs_unlock_extent(io_tree, page_start, page_end, &cached_state);
 	up_read(&BTRFS_I(inode)->i_mmap_lock);
 
 	btrfs_delalloc_release_extents(BTRFS_I(inode), fsize);
+	if (only_release_metadata)
+		btrfs_check_nocow_unlock(BTRFS_I(inode));
 	sb_end_pagefault(inode->i_sb);
 	extent_changeset_free(data_reserved);
 	return VM_FAULT_LOCKED;
@@ -1958,10 +1991,16 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
 	up_read(&BTRFS_I(inode)->i_mmap_lock);
 out:
 	btrfs_delalloc_release_extents(BTRFS_I(inode), fsize);
-	btrfs_delalloc_release_space(BTRFS_I(inode), data_reserved, page_start,
-				     reserved_space, true);
+	if (only_release_metadata)
+		btrfs_delalloc_release_metadata(BTRFS_I(inode), reserved_space, true);
+	else
+		btrfs_delalloc_release_space(BTRFS_I(inode), data_reserved,
+					     page_start, reserved_space, true);
 	extent_changeset_free(data_reserved);
 out_noreserve:
+	if (only_release_metadata)
+		btrfs_check_nocow_unlock(BTRFS_I(inode));
+
 	sb_end_pagefault(inode->i_sb);
 
 	if (ret < 0)
-- 
2.47.2


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v2 2/3] btrfs: use variable for io_tree when clearing range in btrfs_page_mkwrite()
  2025-07-09 11:13 ` [PATCH v2 0/3] btrfs: fix mmap writes for NOCOW files/extents fdmanana
  2025-07-09 11:13   ` [PATCH v2 1/3] btrfs: fix -ENOSPC mmap write failure on " fdmanana
@ 2025-07-09 11:13   ` fdmanana
  2025-07-09 11:13   ` [PATCH v2 3/3] btrfs: use btrfs_inode local variable at btrfs_page_mkwrite() fdmanana
  2 siblings, 0 replies; 17+ messages in thread
From: fdmanana @ 2025-07-09 11:13 UTC (permalink / raw)
  To: linux-btrfs

From: Filipe Manana <fdmanana@suse.com>

We have the inode's io_tree already stored in a local variable, so use it
instead of grabbing it again in the call to btrfs_clear_extent_bit().

Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
---
 fs/btrfs/file.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c
index 9ea3b4d47f81..80a828f39bbd 100644
--- a/fs/btrfs/file.c
+++ b/fs/btrfs/file.c
@@ -1946,7 +1946,7 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
 	 * clear any delalloc bits within this page range since we have to
 	 * reserve data&meta space before lock_page() (see above comments).
 	 */
-	btrfs_clear_extent_bit(&BTRFS_I(inode)->io_tree, page_start, end,
+	btrfs_clear_extent_bit(io_tree, page_start, end,
 			       EXTENT_DELALLOC | EXTENT_DO_ACCOUNTING |
 			       EXTENT_DEFRAG, &cached_state);
 
-- 
2.47.2


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v2 3/3] btrfs: use btrfs_inode local variable at btrfs_page_mkwrite()
  2025-07-09 11:13 ` [PATCH v2 0/3] btrfs: fix mmap writes for NOCOW files/extents fdmanana
  2025-07-09 11:13   ` [PATCH v2 1/3] btrfs: fix -ENOSPC mmap write failure on " fdmanana
  2025-07-09 11:13   ` [PATCH v2 2/3] btrfs: use variable for io_tree when clearing range in btrfs_page_mkwrite() fdmanana
@ 2025-07-09 11:13   ` fdmanana
  2 siblings, 0 replies; 17+ messages in thread
From: fdmanana @ 2025-07-09 11:13 UTC (permalink / raw)
  To: linux-btrfs

From: Filipe Manana <fdmanana@suse.com>

Most of the time we want to use the btrfs_inode, so change the local inode
variable to be a btrfs_inode instead of a vfs inode, reducing verbosity
by eleminating a lot of BTRFS_I() calls.

Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
---
 fs/btrfs/file.c | 58 ++++++++++++++++++++++++-------------------------
 1 file changed, 28 insertions(+), 30 deletions(-)

diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c
index 80a828f39bbd..d1f5da14f6d1 100644
--- a/fs/btrfs/file.c
+++ b/fs/btrfs/file.c
@@ -1831,9 +1831,9 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
 {
 	struct page *page = vmf->page;
 	struct folio *folio = page_folio(page);
-	struct inode *inode = file_inode(vmf->vma->vm_file);
-	struct btrfs_fs_info *fs_info = inode_to_fs_info(inode);
-	struct extent_io_tree *io_tree = &BTRFS_I(inode)->io_tree;
+	struct btrfs_inode *inode = BTRFS_I(file_inode(vmf->vma->vm_file));
+	struct btrfs_fs_info *fs_info = inode->root->fs_info;
+	struct extent_io_tree *io_tree = &inode->io_tree;
 	struct btrfs_ordered_extent *ordered;
 	struct extent_state *cached_state = NULL;
 	struct extent_changeset *data_reserved = NULL;
@@ -1849,7 +1849,7 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
 
 	reserved_space = fsize;
 
-	sb_start_pagefault(inode->i_sb);
+	sb_start_pagefault(inode->vfs_inode.i_sb);
 	page_start = folio_pos(folio);
 	page_end = page_start + folio_size(folio) - 1;
 	end = page_end;
@@ -1862,13 +1862,12 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
 	 * end up waiting indefinitely to get a lock on the page currently
 	 * being processed by btrfs_page_mkwrite() function.
 	 */
-	ret = btrfs_check_data_free_space(BTRFS_I(inode), &data_reserved,
-					  page_start, reserved_space, false);
+	ret = btrfs_check_data_free_space(inode, &data_reserved, page_start,
+					  reserved_space, false);
 	if (ret < 0) {
 		size_t write_bytes = reserved_space;
 
-		if (btrfs_check_nocow_lock(BTRFS_I(inode), page_start,
-					   &write_bytes, false) <= 0)
+		if (btrfs_check_nocow_lock(inode, page_start, &write_bytes, false) <= 0)
 			goto out_noreserve;
 
 		only_release_metadata = true;
@@ -1882,11 +1881,11 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
 		if (write_bytes < reserved_space)
 			goto out_noreserve;
 	}
-	ret = btrfs_delalloc_reserve_metadata(BTRFS_I(inode), reserved_space,
+	ret = btrfs_delalloc_reserve_metadata(inode, reserved_space,
 					      reserved_space, false);
 	if (ret < 0) {
 		if (!only_release_metadata)
-			btrfs_free_reserved_data_space(BTRFS_I(inode), data_reserved,
+			btrfs_free_reserved_data_space(inode, data_reserved,
 						       page_start, reserved_space);
 		goto out_noreserve;
 	}
@@ -1895,11 +1894,11 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
 	if (ret < 0)
 		goto out;
 again:
-	down_read(&BTRFS_I(inode)->i_mmap_lock);
+	down_read(&inode->i_mmap_lock);
 	folio_lock(folio);
-	size = i_size_read(inode);
+	size = i_size_read(&inode->vfs_inode);
 
-	if ((folio->mapping != inode->i_mapping) ||
+	if ((folio->mapping != inode->vfs_inode.i_mapping) ||
 	    (page_start >= size)) {
 		/* Page got truncated out from underneath us. */
 		goto out_unlock;
@@ -1917,11 +1916,11 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
 	 * We can't set the delalloc bits if there are pending ordered
 	 * extents.  Drop our locks and wait for them to finish.
 	 */
-	ordered = btrfs_lookup_ordered_range(BTRFS_I(inode), page_start, fsize);
+	ordered = btrfs_lookup_ordered_range(inode, page_start, fsize);
 	if (ordered) {
 		btrfs_unlock_extent(io_tree, page_start, page_end, &cached_state);
 		folio_unlock(folio);
-		up_read(&BTRFS_I(inode)->i_mmap_lock);
+		up_read(&inode->i_mmap_lock);
 		btrfs_start_ordered_extent(ordered);
 		btrfs_put_ordered_extent(ordered);
 		goto again;
@@ -1932,7 +1931,7 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
 		if (reserved_space < fsize) {
 			end = page_start + reserved_space - 1;
 			if (!only_release_metadata)
-				btrfs_delalloc_release_space(BTRFS_I(inode),
+				btrfs_delalloc_release_space(inode,
 							     data_reserved, end + 1,
 							     fsize - reserved_space,
 							     true);
@@ -1950,8 +1949,7 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
 			       EXTENT_DELALLOC | EXTENT_DO_ACCOUNTING |
 			       EXTENT_DEFRAG, &cached_state);
 
-	ret = btrfs_set_extent_delalloc(BTRFS_I(inode), page_start, end, 0,
-					&cached_state);
+	ret = btrfs_set_extent_delalloc(inode, page_start, end, 0, &cached_state);
 	if (ret < 0) {
 		btrfs_unlock_extent(io_tree, page_start, page_end, &cached_state);
 		goto out_unlock;
@@ -1970,38 +1968,38 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
 	btrfs_folio_set_dirty(fs_info, folio, page_start, end + 1 - page_start);
 	btrfs_folio_set_uptodate(fs_info, folio, page_start, end + 1 - page_start);
 
-	btrfs_set_inode_last_sub_trans(BTRFS_I(inode));
+	btrfs_set_inode_last_sub_trans(inode);
 
 	if (only_release_metadata)
 		btrfs_set_extent_bit(io_tree, page_start, end, EXTENT_NORESERVE,
 				     &cached_state);
 
 	btrfs_unlock_extent(io_tree, page_start, page_end, &cached_state);
-	up_read(&BTRFS_I(inode)->i_mmap_lock);
+	up_read(&inode->i_mmap_lock);
 
-	btrfs_delalloc_release_extents(BTRFS_I(inode), fsize);
+	btrfs_delalloc_release_extents(inode, fsize);
 	if (only_release_metadata)
-		btrfs_check_nocow_unlock(BTRFS_I(inode));
-	sb_end_pagefault(inode->i_sb);
+		btrfs_check_nocow_unlock(inode);
+	sb_end_pagefault(inode->vfs_inode.i_sb);
 	extent_changeset_free(data_reserved);
 	return VM_FAULT_LOCKED;
 
 out_unlock:
 	folio_unlock(folio);
-	up_read(&BTRFS_I(inode)->i_mmap_lock);
+	up_read(&inode->i_mmap_lock);
 out:
-	btrfs_delalloc_release_extents(BTRFS_I(inode), fsize);
+	btrfs_delalloc_release_extents(inode, fsize);
 	if (only_release_metadata)
-		btrfs_delalloc_release_metadata(BTRFS_I(inode), reserved_space, true);
+		btrfs_delalloc_release_metadata(inode, reserved_space, true);
 	else
-		btrfs_delalloc_release_space(BTRFS_I(inode), data_reserved,
-					     page_start, reserved_space, true);
+		btrfs_delalloc_release_space(inode, data_reserved, page_start,
+					     reserved_space, true);
 	extent_changeset_free(data_reserved);
 out_noreserve:
 	if (only_release_metadata)
-		btrfs_check_nocow_unlock(BTRFS_I(inode));
+		btrfs_check_nocow_unlock(inode);
 
-	sb_end_pagefault(inode->i_sb);
+	sb_end_pagefault(inode->vfs_inode.i_sb);
 
 	if (ret < 0)
 		return vmf_error(ret);
-- 
2.47.2


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v3 0/3] btrfs: fix mmap writes for NOCOW files/extents
  2025-07-09  8:53 [PATCH 0/3] btrfs: fix mmap writes for NOCOW files/extents fdmanana
                   ` (3 preceding siblings ...)
  2025-07-09 11:13 ` [PATCH v2 0/3] btrfs: fix mmap writes for NOCOW files/extents fdmanana
@ 2025-07-09 11:45 ` fdmanana
  2025-07-09 11:45   ` [PATCH v3 1/3] btrfs: fix -ENOSPC mmap write failure on " fdmanana
                     ` (2 more replies)
  4 siblings, 3 replies; 17+ messages in thread
From: fdmanana @ 2025-07-09 11:45 UTC (permalink / raw)
  To: linux-btrfs

From: Filipe Manana <fdmanana@suse.com>

A bug fix and a couple cleanups. Details in the individual change logs.

V2: Fix a bug in patch 1/3 where if write_bytes < reserved_space we would
    exit without unlocking the cow lock, so set only_release_metadata to
    true before bailing out in that case.
    Added Qu's review tags.

V3: Add missing release of metadata reserved space in case we fall
    back to NOCOW mode and we are in a folio that contains i_size
    somewhere in its middle

Filipe Manana (3):
  btrfs: fix -ENOSPC mmap write failure on NOCOW files/extents
  btrfs: use variable for io_tree when clearing range in btrfs_page_mkwrite()
  btrfs: use btrfs_inode local variable at btrfs_page_mkwrite()

 fs/btrfs/file.c | 93 +++++++++++++++++++++++++++++++++++--------------
 1 file changed, 66 insertions(+), 27 deletions(-)

-- 
2.47.2


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH v3 1/3] btrfs: fix -ENOSPC mmap write failure on NOCOW files/extents
  2025-07-09 11:45 ` [PATCH v3 0/3] btrfs: fix mmap writes for NOCOW files/extents fdmanana
@ 2025-07-09 11:45   ` fdmanana
  2025-07-09 11:45   ` [PATCH v3 2/3] btrfs: use variable for io_tree when clearing range in btrfs_page_mkwrite() fdmanana
  2025-07-09 11:45   ` [PATCH v3 3/3] btrfs: use btrfs_inode local variable at btrfs_page_mkwrite() fdmanana
  2 siblings, 0 replies; 17+ messages in thread
From: fdmanana @ 2025-07-09 11:45 UTC (permalink / raw)
  To: linux-btrfs

From: Filipe Manana <fdmanana@suse.com>

If we attempt a mmap write into a NOCOW file or a prealloc extent when
there is no more available data space (or unallocated space to allocate a
new data block group) and we can do a NOCOW write (there are no reflinks
for the target extent or snapshots), we always fail due to -ENOSPC, unlike
for the regular buffered write and direct IO paths where we check that we
can do a NOCOW write in case we can't reserve data space.

Simple reproducer:

  $ cat test.sh
  #!/bin/bash

  DEV=/dev/sdi
  MNT=/mnt/sdi

  umount $DEV &> /dev/null
  mkfs.btrfs -f -b $((512 * 1024 * 1024)) $DEV
  mount $DEV $MNT

  touch $MNT/foobar
  # Make it a NOCOW file.
  chattr +C $MNT/foobar

  # Add initial data to file.
  xfs_io -c "pwrite -S 0xab 0 1M" $MNT/foobar

  # Fill all the remaining data space and unallocated space with data.
  dd if=/dev/zero of=$MNT/filler bs=4K &> /dev/null

  # Overwrite the file with a mmap write. Should succeed.
  xfs_io -c "mmap -w 0 1M"        \
         -c "mwrite -S 0xcd 0 1M" \
         -c "munmap"              \
         $MNT/foobar

  # Unmount, mount again and verify the new data was persisted.
  umount $MNT
  mount $DEV $MNT

  od -A d -t x1 $MNT/foobar

  umount $MNT

Running this:

  $ ./test.sh
  (...)
  wrote 1048576/1048576 bytes at offset 0
  1 MiB, 256 ops; 0.0008 sec (1.188 GiB/sec and 311435.5231 ops/sec)
  ./test.sh: line 24: 234865 Bus error               xfs_io -c "mmap -w 0 1M" -c "mwrite -S 0xcd 0 1M" -c "munmap" $MNT/foobar
  0000000 ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab
  *
  1048576

Fix this by not failing in case we can't allocate data space and we can
NOCOW into the target extent - reserving only metadata space in this case.

After this change the test passes:

  $ ./test.sh
  (...)
  wrote 1048576/1048576 bytes at offset 0
  1 MiB, 256 ops; 0.0007 sec (1.262 GiB/sec and 330749.3540 ops/sec)
  0000000 cd cd cd cd cd cd cd cd cd cd cd cd cd cd cd cd
  *
  1048576

A test case for fstests will be added soon.

Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
---
 fs/btrfs/file.c | 59 ++++++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 51 insertions(+), 8 deletions(-)

diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c
index 05b046c6806f..cb07274d4126 100644
--- a/fs/btrfs/file.c
+++ b/fs/btrfs/file.c
@@ -1841,6 +1841,7 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
 	loff_t size;
 	size_t fsize = folio_size(folio);
 	int ret;
+	bool only_release_metadata = false;
 	u64 reserved_space;
 	u64 page_start;
 	u64 page_end;
@@ -1861,10 +1862,34 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
 	 * end up waiting indefinitely to get a lock on the page currently
 	 * being processed by btrfs_page_mkwrite() function.
 	 */
-	ret = btrfs_delalloc_reserve_space(BTRFS_I(inode), &data_reserved,
-					   page_start, reserved_space);
-	if (ret < 0)
+	ret = btrfs_check_data_free_space(BTRFS_I(inode), &data_reserved,
+					  page_start, reserved_space, false);
+	if (ret < 0) {
+		size_t write_bytes = reserved_space;
+
+		if (btrfs_check_nocow_lock(BTRFS_I(inode), page_start,
+					   &write_bytes, false) <= 0)
+			goto out_noreserve;
+
+		only_release_metadata = true;
+
+		/*
+		 * Can't write the whole range, there may be shared extents or
+		 * holes in the range, bail out with @only_release_metadata set
+		 * to true so that we unlock the nocow lock before returning the
+		 * error.
+		 */
+		if (write_bytes < reserved_space)
+			goto out_noreserve;
+	}
+	ret = btrfs_delalloc_reserve_metadata(BTRFS_I(inode), reserved_space,
+					      reserved_space, false);
+	if (ret < 0) {
+		if (!only_release_metadata)
+			btrfs_free_reserved_data_space(BTRFS_I(inode), data_reserved,
+						       page_start, reserved_space);
 		goto out_noreserve;
+	}
 
 	ret = file_update_time(vmf->vma->vm_file);
 	if (ret < 0)
@@ -1905,10 +1930,16 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
 	if (folio_contains(folio, (size - 1) >> PAGE_SHIFT)) {
 		reserved_space = round_up(size - page_start, fs_info->sectorsize);
 		if (reserved_space < fsize) {
+			const u64 to_free = fsize - reserved_space;
+
 			end = page_start + reserved_space - 1;
-			btrfs_delalloc_release_space(BTRFS_I(inode),
-					data_reserved, end + 1,
-					fsize - reserved_space, true);
+			if (only_release_metadata)
+				btrfs_delalloc_release_metadata(BTRFS_I(inode),
+								to_free, true);
+			else
+				btrfs_delalloc_release_space(BTRFS_I(inode),
+							     data_reserved, end + 1,
+							     to_free, true);
 		}
 	}
 
@@ -1945,10 +1976,16 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
 
 	btrfs_set_inode_last_sub_trans(BTRFS_I(inode));
 
+	if (only_release_metadata)
+		btrfs_set_extent_bit(io_tree, page_start, end, EXTENT_NORESERVE,
+				     &cached_state);
+
 	btrfs_unlock_extent(io_tree, page_start, page_end, &cached_state);
 	up_read(&BTRFS_I(inode)->i_mmap_lock);
 
 	btrfs_delalloc_release_extents(BTRFS_I(inode), fsize);
+	if (only_release_metadata)
+		btrfs_check_nocow_unlock(BTRFS_I(inode));
 	sb_end_pagefault(inode->i_sb);
 	extent_changeset_free(data_reserved);
 	return VM_FAULT_LOCKED;
@@ -1958,10 +1995,16 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
 	up_read(&BTRFS_I(inode)->i_mmap_lock);
 out:
 	btrfs_delalloc_release_extents(BTRFS_I(inode), fsize);
-	btrfs_delalloc_release_space(BTRFS_I(inode), data_reserved, page_start,
-				     reserved_space, true);
+	if (only_release_metadata)
+		btrfs_delalloc_release_metadata(BTRFS_I(inode), reserved_space, true);
+	else
+		btrfs_delalloc_release_space(BTRFS_I(inode), data_reserved,
+					     page_start, reserved_space, true);
 	extent_changeset_free(data_reserved);
 out_noreserve:
+	if (only_release_metadata)
+		btrfs_check_nocow_unlock(BTRFS_I(inode));
+
 	sb_end_pagefault(inode->i_sb);
 
 	if (ret < 0)
-- 
2.47.2


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v3 2/3] btrfs: use variable for io_tree when clearing range in btrfs_page_mkwrite()
  2025-07-09 11:45 ` [PATCH v3 0/3] btrfs: fix mmap writes for NOCOW files/extents fdmanana
  2025-07-09 11:45   ` [PATCH v3 1/3] btrfs: fix -ENOSPC mmap write failure on " fdmanana
@ 2025-07-09 11:45   ` fdmanana
  2025-07-09 11:45   ` [PATCH v3 3/3] btrfs: use btrfs_inode local variable at btrfs_page_mkwrite() fdmanana
  2 siblings, 0 replies; 17+ messages in thread
From: fdmanana @ 2025-07-09 11:45 UTC (permalink / raw)
  To: linux-btrfs

From: Filipe Manana <fdmanana@suse.com>

We have the inode's io_tree already stored in a local variable, so use it
instead of grabbing it again in the call to btrfs_clear_extent_bit().

Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
---
 fs/btrfs/file.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c
index cb07274d4126..e11b1733e3ae 100644
--- a/fs/btrfs/file.c
+++ b/fs/btrfs/file.c
@@ -1950,7 +1950,7 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
 	 * clear any delalloc bits within this page range since we have to
 	 * reserve data&meta space before lock_page() (see above comments).
 	 */
-	btrfs_clear_extent_bit(&BTRFS_I(inode)->io_tree, page_start, end,
+	btrfs_clear_extent_bit(io_tree, page_start, end,
 			       EXTENT_DELALLOC | EXTENT_DO_ACCOUNTING |
 			       EXTENT_DEFRAG, &cached_state);
 
-- 
2.47.2


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [PATCH v3 3/3] btrfs: use btrfs_inode local variable at btrfs_page_mkwrite()
  2025-07-09 11:45 ` [PATCH v3 0/3] btrfs: fix mmap writes for NOCOW files/extents fdmanana
  2025-07-09 11:45   ` [PATCH v3 1/3] btrfs: fix -ENOSPC mmap write failure on " fdmanana
  2025-07-09 11:45   ` [PATCH v3 2/3] btrfs: use variable for io_tree when clearing range in btrfs_page_mkwrite() fdmanana
@ 2025-07-09 11:45   ` fdmanana
  2 siblings, 0 replies; 17+ messages in thread
From: fdmanana @ 2025-07-09 11:45 UTC (permalink / raw)
  To: linux-btrfs

From: Filipe Manana <fdmanana@suse.com>

Most of the time we want to use the btrfs_inode, so change the local inode
variable to be a btrfs_inode instead of a vfs inode, reducing verbosity
by eleminating a lot of BTRFS_I() calls.

Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
---
 fs/btrfs/file.c | 64 +++++++++++++++++++++++--------------------------
 1 file changed, 30 insertions(+), 34 deletions(-)

diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c
index e11b1733e3ae..37bf473f2132 100644
--- a/fs/btrfs/file.c
+++ b/fs/btrfs/file.c
@@ -1831,9 +1831,9 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
 {
 	struct page *page = vmf->page;
 	struct folio *folio = page_folio(page);
-	struct inode *inode = file_inode(vmf->vma->vm_file);
-	struct btrfs_fs_info *fs_info = inode_to_fs_info(inode);
-	struct extent_io_tree *io_tree = &BTRFS_I(inode)->io_tree;
+	struct btrfs_inode *inode = BTRFS_I(file_inode(vmf->vma->vm_file));
+	struct btrfs_fs_info *fs_info = inode->root->fs_info;
+	struct extent_io_tree *io_tree = &inode->io_tree;
 	struct btrfs_ordered_extent *ordered;
 	struct extent_state *cached_state = NULL;
 	struct extent_changeset *data_reserved = NULL;
@@ -1849,7 +1849,7 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
 
 	reserved_space = fsize;
 
-	sb_start_pagefault(inode->i_sb);
+	sb_start_pagefault(inode->vfs_inode.i_sb);
 	page_start = folio_pos(folio);
 	page_end = page_start + folio_size(folio) - 1;
 	end = page_end;
@@ -1862,13 +1862,12 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
 	 * end up waiting indefinitely to get a lock on the page currently
 	 * being processed by btrfs_page_mkwrite() function.
 	 */
-	ret = btrfs_check_data_free_space(BTRFS_I(inode), &data_reserved,
-					  page_start, reserved_space, false);
+	ret = btrfs_check_data_free_space(inode, &data_reserved, page_start,
+					  reserved_space, false);
 	if (ret < 0) {
 		size_t write_bytes = reserved_space;
 
-		if (btrfs_check_nocow_lock(BTRFS_I(inode), page_start,
-					   &write_bytes, false) <= 0)
+		if (btrfs_check_nocow_lock(inode, page_start, &write_bytes, false) <= 0)
 			goto out_noreserve;
 
 		only_release_metadata = true;
@@ -1882,11 +1881,11 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
 		if (write_bytes < reserved_space)
 			goto out_noreserve;
 	}
-	ret = btrfs_delalloc_reserve_metadata(BTRFS_I(inode), reserved_space,
+	ret = btrfs_delalloc_reserve_metadata(inode, reserved_space,
 					      reserved_space, false);
 	if (ret < 0) {
 		if (!only_release_metadata)
-			btrfs_free_reserved_data_space(BTRFS_I(inode), data_reserved,
+			btrfs_free_reserved_data_space(inode, data_reserved,
 						       page_start, reserved_space);
 		goto out_noreserve;
 	}
@@ -1895,11 +1894,11 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
 	if (ret < 0)
 		goto out;
 again:
-	down_read(&BTRFS_I(inode)->i_mmap_lock);
+	down_read(&inode->i_mmap_lock);
 	folio_lock(folio);
-	size = i_size_read(inode);
+	size = i_size_read(&inode->vfs_inode);
 
-	if ((folio->mapping != inode->i_mapping) ||
+	if ((folio->mapping != inode->vfs_inode.i_mapping) ||
 	    (page_start >= size)) {
 		/* Page got truncated out from underneath us. */
 		goto out_unlock;
@@ -1917,11 +1916,11 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
 	 * We can't set the delalloc bits if there are pending ordered
 	 * extents.  Drop our locks and wait for them to finish.
 	 */
-	ordered = btrfs_lookup_ordered_range(BTRFS_I(inode), page_start, fsize);
+	ordered = btrfs_lookup_ordered_range(inode, page_start, fsize);
 	if (ordered) {
 		btrfs_unlock_extent(io_tree, page_start, page_end, &cached_state);
 		folio_unlock(folio);
-		up_read(&BTRFS_I(inode)->i_mmap_lock);
+		up_read(&inode->i_mmap_lock);
 		btrfs_start_ordered_extent(ordered);
 		btrfs_put_ordered_extent(ordered);
 		goto again;
@@ -1934,12 +1933,10 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
 
 			end = page_start + reserved_space - 1;
 			if (only_release_metadata)
-				btrfs_delalloc_release_metadata(BTRFS_I(inode),
-								to_free, true);
+				btrfs_delalloc_release_metadata(inode, to_free, true);
 			else
-				btrfs_delalloc_release_space(BTRFS_I(inode),
-							     data_reserved, end + 1,
-							     to_free, true);
+				btrfs_delalloc_release_space(inode, data_reserved,
+							     end + 1, to_free, true);
 		}
 	}
 
@@ -1954,8 +1951,7 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
 			       EXTENT_DELALLOC | EXTENT_DO_ACCOUNTING |
 			       EXTENT_DEFRAG, &cached_state);
 
-	ret = btrfs_set_extent_delalloc(BTRFS_I(inode), page_start, end, 0,
-					&cached_state);
+	ret = btrfs_set_extent_delalloc(inode, page_start, end, 0, &cached_state);
 	if (ret < 0) {
 		btrfs_unlock_extent(io_tree, page_start, page_end, &cached_state);
 		goto out_unlock;
@@ -1974,38 +1970,38 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
 	btrfs_folio_set_dirty(fs_info, folio, page_start, end + 1 - page_start);
 	btrfs_folio_set_uptodate(fs_info, folio, page_start, end + 1 - page_start);
 
-	btrfs_set_inode_last_sub_trans(BTRFS_I(inode));
+	btrfs_set_inode_last_sub_trans(inode);
 
 	if (only_release_metadata)
 		btrfs_set_extent_bit(io_tree, page_start, end, EXTENT_NORESERVE,
 				     &cached_state);
 
 	btrfs_unlock_extent(io_tree, page_start, page_end, &cached_state);
-	up_read(&BTRFS_I(inode)->i_mmap_lock);
+	up_read(&inode->i_mmap_lock);
 
-	btrfs_delalloc_release_extents(BTRFS_I(inode), fsize);
+	btrfs_delalloc_release_extents(inode, fsize);
 	if (only_release_metadata)
-		btrfs_check_nocow_unlock(BTRFS_I(inode));
-	sb_end_pagefault(inode->i_sb);
+		btrfs_check_nocow_unlock(inode);
+	sb_end_pagefault(inode->vfs_inode.i_sb);
 	extent_changeset_free(data_reserved);
 	return VM_FAULT_LOCKED;
 
 out_unlock:
 	folio_unlock(folio);
-	up_read(&BTRFS_I(inode)->i_mmap_lock);
+	up_read(&inode->i_mmap_lock);
 out:
-	btrfs_delalloc_release_extents(BTRFS_I(inode), fsize);
+	btrfs_delalloc_release_extents(inode, fsize);
 	if (only_release_metadata)
-		btrfs_delalloc_release_metadata(BTRFS_I(inode), reserved_space, true);
+		btrfs_delalloc_release_metadata(inode, reserved_space, true);
 	else
-		btrfs_delalloc_release_space(BTRFS_I(inode), data_reserved,
-					     page_start, reserved_space, true);
+		btrfs_delalloc_release_space(inode, data_reserved, page_start,
+					     reserved_space, true);
 	extent_changeset_free(data_reserved);
 out_noreserve:
 	if (only_release_metadata)
-		btrfs_check_nocow_unlock(BTRFS_I(inode));
+		btrfs_check_nocow_unlock(inode);
 
-	sb_end_pagefault(inode->i_sb);
+	sb_end_pagefault(inode->vfs_inode.i_sb);
 
 	if (ret < 0)
 		return vmf_error(ret);
-- 
2.47.2


^ permalink raw reply related	[flat|nested] 17+ messages in thread

end of thread, other threads:[~2025-07-09 11:45 UTC | newest]

Thread overview: 17+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-07-09  8:53 [PATCH 0/3] btrfs: fix mmap writes for NOCOW files/extents fdmanana
2025-07-09  8:53 ` [PATCH 1/3] btrfs: fix -ENOSPC mmap write failure on " fdmanana
2025-07-09 10:02   ` Qu Wenruo
2025-07-09 10:36     ` Filipe Manana
2025-07-09  8:53 ` [PATCH 2/3] btrfs: use variable for io_tree when clearing range in btrfs_page_mkwrite() fdmanana
2025-07-09 10:02   ` Qu Wenruo
2025-07-09  8:53 ` [PATCH 3/3] btrfs: use btrfs_inode local variable at btrfs_page_mkwrite() fdmanana
2025-07-09 10:04   ` Qu Wenruo
2025-07-09 10:39     ` Filipe Manana
2025-07-09 11:13 ` [PATCH v2 0/3] btrfs: fix mmap writes for NOCOW files/extents fdmanana
2025-07-09 11:13   ` [PATCH v2 1/3] btrfs: fix -ENOSPC mmap write failure on " fdmanana
2025-07-09 11:13   ` [PATCH v2 2/3] btrfs: use variable for io_tree when clearing range in btrfs_page_mkwrite() fdmanana
2025-07-09 11:13   ` [PATCH v2 3/3] btrfs: use btrfs_inode local variable at btrfs_page_mkwrite() fdmanana
2025-07-09 11:45 ` [PATCH v3 0/3] btrfs: fix mmap writes for NOCOW files/extents fdmanana
2025-07-09 11:45   ` [PATCH v3 1/3] btrfs: fix -ENOSPC mmap write failure on " fdmanana
2025-07-09 11:45   ` [PATCH v3 2/3] btrfs: use variable for io_tree when clearing range in btrfs_page_mkwrite() fdmanana
2025-07-09 11:45   ` [PATCH v3 3/3] btrfs: use btrfs_inode local variable at btrfs_page_mkwrite() fdmanana

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox