linux-2.6-microblaze.git
6 weeks agobtrfs: use smp_mb__after_atomic() when forcing COW in create_pending_snapshot()
Filipe Manana [Mon, 22 Sep 2025 11:09:14 +0000 (12:09 +0100)]
btrfs: use smp_mb__after_atomic() when forcing COW in create_pending_snapshot()

After setting the BTRFS_ROOT_FORCE_COW flag on the root we are doing a
full write barrier, smp_wmb(), but we don't need to, all we need is a
smp_mb__after_atomic().  The use of the smp_wmb() is from the old days
when we didn't use a bit and used instead an int field in the root to
signal if cow is forced. After the int field was changed to a bit in
the root's state (flags field), we forgot to update the memory barrier
in create_pending_snapshot() to smp_mb__after_atomic(), but we did the
change in commit_fs_roots() after clearing BTRFS_ROOT_FORCE_COW. That
happened in commit 27cdeb7096b8 ("Btrfs: use bitfield instead of integer
data type for the some variants in btrfs_root"). On the reader side, in
should_cow_block(), we also use the counterpart smp_mb__before_atomic()
which generates further confusion.

So change the smp_wmb() to smp_mb__after_atomic(). In fact we don't
even need any barrier at all since create_pending_snapshot() is called
in the critical section of a transaction commit and therefore no one
can concurrently join/attach the transaction, or start a new one, until
the transaction is unblocked. By the time someone starts a new transaction
and enters should_cow_block(), a lot of implicit memory barriers already
took place by having acquired several locks such as fs_info->trans_lock
and extent buffer locks on the root node at least. Nevertlheless, for
consistency use smp_mb__after_atomic() after setting the force cow bit
in create_pending_snapshot().

Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: add unlikely annotations to branches leading to transaction abort
David Sterba [Wed, 17 Sep 2025 17:53:56 +0000 (19:53 +0200)]
btrfs: add unlikely annotations to branches leading to transaction abort

The unlikely() annotation is a static prediction hint that compiler may
use to reorder code out of hot path. We use it elsewhere (namely
tree-checker.c) for error branches that almost never happen.

Transaction abort is one such error, the btrfs_abort_transaction()
inlines code to check the state and print a warning, this ought to be
out of the hot path.

The most common pattern is when transaction abort is called after
checking a return value and the control flow leads to a quick return.
In other cases it may not be necessary to add unlikely() e.g. when the
function returns anyway or the control flow is not changed noticeably.

Reviewed-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: add unlikely annotations to branches leading to EIO
David Sterba [Wed, 17 Sep 2025 17:53:55 +0000 (19:53 +0200)]
btrfs: add unlikely annotations to branches leading to EIO

The unlikely() annotation is a static prediction hint that compiler may
use to reorder code out of hot path. We use it elsewhere (namely
tree-checker.c) for error branches that almost never happen, where
EIO is one of them.

Reviewed-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: add unlikely annotations to branches leading to EUCLEAN
David Sterba [Wed, 17 Sep 2025 17:53:54 +0000 (19:53 +0200)]
btrfs: add unlikely annotations to branches leading to EUCLEAN

The unlikely() annotation is a static prediction hint that compiler may
use to reorder code out of hot path. We use it elsewhere (namely
tree-checker.c) for error branches that almost never happen, where
EUCLEAN (a corruption) is one of them.

Reviewed-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: more trivial BTRFS_PATH_AUTO_FREE conversions
Sun YangKai [Mon, 22 Sep 2025 11:30:07 +0000 (19:30 +0800)]
btrfs: more trivial BTRFS_PATH_AUTO_FREE conversions

Trivial pattern for the auto freeing with goto -> return conversions
if possible.

The following cases are considered trivial in this patch:

1. Cases where there are no operations between btrfs_free_path() and the
   function returns.
2. Cases where only simple cleanup operations (such as kfree(), kvfree(),
   clear_bit(), and fs_path_free()) are present between
   btrfs_free_path() and the function return.

Signed-off-by: Sun YangKai <sunk67188@gmail.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: zoned: don't fail mount needlessly due to too many active zones
Johannes Thumshirn [Thu, 18 Sep 2025 10:51:19 +0000 (12:51 +0200)]
btrfs: zoned: don't fail mount needlessly due to too many active zones

Previously BTRFS did not look at a device's reported max_open_zones limit,
but starting with commit 04147d8394e8 ("btrfs: zoned: limit active zones
to max_open_zones"), zoned BTRFS limited the number of concurrently used
block-groups to the number of max_open_zones a device reported, if it
hadn't already reported a number of max_active_zones.

Starting with commit 04147d8394e8 the number of open zones is treated the
same way as active zones. But this leads to mount failures on filesystems
which have been used before 04147d8394e8 because too many zones are in an
open state.

Ignore the new limitations on these filesystems, so zones can be finished
or evacuated.

Reported-by: Yuwei Han <hrx@bupt.moe>
Link: https://lore.kernel.org/all/2F48A90AF7DDF380+1790bcfd-cb6f-456b-870d-7982f21b5eae@bupt.moe/
Fixes: 04147d8394e8 ("btrfs: zoned: limit active zones to max_open_zones")
Reviewed-by: Naohiro Aota <naohiro.aota@wdc.com>
Signed-off-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: use kmalloc_array() for open-coded arithmetic in kmalloc()
Miquel Sabaté Solà [Fri, 19 Sep 2025 14:58:15 +0000 (16:58 +0200)]
btrfs: use kmalloc_array() for open-coded arithmetic in kmalloc()

As pointed out in the documentation, calling 'kmalloc' with open-coded
arithmetic can lead to unfortunate overflows and this particular way of
using it has been deprecated. Instead, it's preferred to use
'kmalloc_array' in cases where it might apply so an overflow check is
performed.

Note this is an API cleanup and is not fixing any overflows because in
all cases the multipliers are bounded small numbers derived from number
of items in leaves/nodes.

Signed-off-by: Miquel Sabaté Solà <mssola@mssola.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: enable experimental bs > ps support
Qu Wenruo [Tue, 9 Sep 2025 03:08:47 +0000 (12:38 +0930)]
btrfs: enable experimental bs > ps support

With all the preparation patches, we're able to finally enable btrfs
block size (sector size) larger than page size support and give it a
full fstests run.

And obviously this new feature is hidden behind experimental flags, and
should not be considered as a core feature yet as btrfs' default block
size is still 4K.

But this is still a feature that will shine in the future where 16K
block sized device are widely adopted.

For now there are some features explicitly disabled:

- Direct IO
  This is the most complex part to support, the root reason is we can
  not control the pages of iov iter passed in.

  User space programs can only ensure the virtual addresses are
  contiguous, but have no control on their physical addresses.

  Our bs > ps support heavily relies on large folios, and direct IO
  memory can easily break it.

  So direct IO is disabled and will always fall back to buffered IO.

- RAID56
  In theory we can convert RAID56 to use large folios, but it will need
  to be converted back to page based if we want to support direct IO in
  the future.
  So just reject it for now.

- Encoded send
- Encoded read
  Both are utilizing btrfs_encoded_read_regular_fill_pages(), and send
  is utilizing vmallocated memory.
  Unfortunately for vmallocated memory we can not guarantee the minimal
  folio order.

  For send, it will just always fallback to regular writes, which reads
  from page cache and will follow the existing folio order requirement.

- Encoded write
  Encoded write itself is allocating pages by themselves, and we can
  easily change it to follow the minimal order.
  But since encoded read is already disabled, there is no need to only
  enable encoded write.

Finally just like what we did for bs < ps support in the past, add a
warning message for bs > ps mounts.

Signed-off-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: add extra ASSERT()s to catch unaligned bios
Qu Wenruo [Mon, 15 Sep 2025 09:03:50 +0000 (18:33 +0930)]
btrfs: add extra ASSERT()s to catch unaligned bios

Btrfs uses btrfs_bio to handle read/write of logical address, for the
incoming bs > ps support, btrfs has extra requirements:

- One folio must contain at least one fs block
- No fs block can cross folio boundaries

This requirement is not hard to maintain, thanks to the address space's
minimal folio order.

But not all btrfs bios are generated through address space, e.g.
compression and scrub.

To catch possible unaligned bios, introduce a helper,
assert_bbio_alginment(), for each btrfs_bio in btrfs_submit_bbio().

This will check the following things:

- bv_offset is aligned to block size
- bv_len is aligned to block size

With a btrfs bio passing above checks, unless it's empty it will ensure
the requirements for bs > ps support.

Signed-off-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: fix symbolic link reading when bs > ps
Qu Wenruo [Mon, 15 Sep 2025 04:59:42 +0000 (14:29 +0930)]
btrfs: fix symbolic link reading when bs > ps

[BUG DURING BS > PS TEST]
When running the following script on a btrfs whose block size is larger
than page size, e.g. 8K block size and 4K page size, it will trigger a
kernel BUG:

  # mkfs.btrfs -s 8k $dev
  # mount $dev $mnt
  # mkdir $mnt/dir
  # ln -s dir $mnt/link
  # ls $mnt/link

The call trace looks like this:

  BTRFS warning (device dm-2): support for block size 8192 with page size 4096 is experimental, some features may be missing
  BTRFS info (device dm-2): checking UUID tree
  BTRFS info (device dm-2): enabling ssd optimizations
  BTRFS info (device dm-2): enabling free space tree
  ------------[ cut here ]------------
  kernel BUG at /home/adam/linux/include/linux/highmem.h:275!
  Oops: invalid opcode: 0000 [#1] SMP
  CPU: 8 UID: 0 PID: 667 Comm: ls Tainted: G           OE       6.17.0-rc4-custom+ #283 PREEMPT(full)
  Tainted: [O]=OOT_MODULE, [E]=UNSIGNED_MODULE
  Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS unknown 02/02/2022
  RIP: 0010:zero_user_segments.constprop.0+0xdc/0xe0 [btrfs]
  Call Trace:
   <TASK>
   btrfs_get_extent.cold+0x85/0x101 [btrfs 7453c70c03e631c8d8bfdd4264fa62d3e238da6f]
   btrfs_do_readpage+0x244/0x750 [btrfs 7453c70c03e631c8d8bfdd4264fa62d3e238da6f]
   btrfs_read_folio+0x9c/0x100 [btrfs 7453c70c03e631c8d8bfdd4264fa62d3e238da6f]
   filemap_read_folio+0x37/0xe0
   do_read_cache_folio+0x94/0x3e0
   __page_get_link.isra.0+0x20/0x90
   page_get_link+0x16/0x40
   step_into+0x69b/0x830
   path_lookupat+0xa7/0x170
   filename_lookup+0xf7/0x200
   ? set_ptes.isra.0+0x36/0x70
   vfs_statx+0x7a/0x160
   do_statx+0x63/0xa0
   __x64_sys_statx+0x90/0xe0
   do_syscall_64+0x82/0xae0
   entry_SYSCALL_64_after_hwframe+0x4b/0x53
   </TASK>

Please note bs > ps support is still under development and the
enablement patch is not even in btrfs development branch.

[CAUSE]
Btrfs reuses its data folio read path to handle symbolic links, as the
symbolic link target is stored as an inline data extent.

But for newly created inodes, btrfs only set the minimal order if the
target inode is a regular file.

Thus for above newly created symbolic link, it doesn't properly respect
the minimal folio order, and triggered the above crash.

[FIX]
Call btrfs_set_inode_mapping_order() unconditionally inside
btrfs_create_new_inode().

For symbolic links this will fix the crash as now the folio will meet
the minimal order.

For regular files this brings no change.

For directory/bdev/char and all the other types of inodes, they won't
go through the data read path, thus no effect either.

Fixes: cc38d178ff33 ("btrfs: enable large data folio support under CONFIG_BTRFS_EXPERIMENTAL")
Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: prepare scrub to support bs > ps cases
Qu Wenruo [Fri, 12 Sep 2025 03:42:54 +0000 (13:12 +0930)]
btrfs: prepare scrub to support bs > ps cases

This involves:

- Migrate scrub_stripe::pages[] to folios[]

- Use btrfs_alloc_folio_array() and folio_put() to alloc above array.

- Migrate scrub_stripe_get_kaddr() and scrub_stripe_get_paddr() to use
  folio interfaces

- Migrate raid56_parity_cache_data_pages() to
  raid56_parity_cache_data_folios()
  Since scrub is the only caller still using pages.

  This helper will copy the folio array contents into rbio::stripe_pages,
  with sector uptodate flags updated.

  And a new ASSERT() to make sure bs > ps cases will not hit this path.

Since most scrub code is based on kaddr/paddr, the migration itself is
pretty straightforward.

And since we're here, also move the loop to set the
stripe_sectors[].uptodate out of the copy loop.
As we always mark all the sectors as uptodate for the data stripe, it's
easier to do in one go, other than doing it inside the copy loop.

Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: prepare zlib to support bs > ps cases
Qu Wenruo [Mon, 8 Sep 2025 09:06:37 +0000 (18:36 +0930)]
btrfs: prepare zlib to support bs > ps cases

This involves converting the following functions to use correct folio
sizes/shifts:

- zlib_compress_folios()
- zlib_decompress_bio()

There is a special handling for s390 hardware acceleration.
With bs > ps cases, we can go with 16K block size on s390 (which uses
fixed 4K page size).
In that case we do not need to do the buffer copy as our folio is large
enough for hardware acceleration.

So factor out the s390 specific and folio size check into a helper,
need_special_buffer().

Signed-off-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: prepare lzo to support bs > ps cases
Qu Wenruo [Mon, 8 Sep 2025 08:51:47 +0000 (18:21 +0930)]
btrfs: prepare lzo to support bs > ps cases

This involves converting the following functions to use correct folio
sizes/shifts:

- copy_compress_data_to_page()
- lzo_compress_folios()
- lzo_decompress_bio()

Just like zstd, lzo has some extra incorrect usage of kmap_local_folio()
that the offset is always 0.

This will not handle HIGHMEM large folios correctly, but those cases are
already rejected explicitly so it should not cause problems when bs > ps
support is enabled.

Signed-off-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: prepare zstd to support bs > ps cases
Qu Wenruo [Mon, 8 Sep 2025 06:58:16 +0000 (16:28 +0930)]
btrfs: prepare zstd to support bs > ps cases

This involves converting the following functions to use proper folio
sizes/shifts:

- zstd_compress_folios()
- zstd_decompress_bio()

The function zstd_decompress() is already using block size correctly
without using page size, thus it needs no modification.

And since zstd compression is calling kmap_local_folio(), the existing
code cannot handle large folios with HIGHMEM, as kmap_local_folio()
requires us to handle one page range each time.

I do not really think it's worth to spend time on some feature that will
be deprecated eventually.  So here just add an extra explicit rejection
for bs > ps with HIGHMEM feature enabled kernels.

Signed-off-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: prepare compression folio alloc/free for bs > ps cases
Qu Wenruo [Mon, 8 Sep 2025 06:37:57 +0000 (16:07 +0930)]
btrfs: prepare compression folio alloc/free for bs > ps cases

This includes the following preparation for bs > ps cases:

- Always alloc/free the folio directly if bs > ps
  This adds a new @fs_info parameter for btrfs_alloc_compr_folio(), thus
  affecting all compression algorithms.

  For btrfs_free_compr_folio() it needs no parameter for now, as we can
  use the folio size to skip the caching part.

  For now the change is just to passing a @fs_info into the function,
  all the folio size assumption is still based on page size.

- Properly zero the last folio in compress_file_range()
  Since the compressed folios can be larger than a page, we need to
  properly zero the whole folio.

- Use correct folio size for btrfs_add_compressed_bio_folios()
  Instead of page size, use the correct folio size.

- Use correct folio size/shift for btrfs_compress_filemap_get_folio()
  As we are not only using simple page sized folios anymore.

- Use correct folio size for btrfs_decompress()
  There is an ASSERT() making sure the decompressed range is no larger
  than a page, which will be triggered for bs > ps cases.

- Skip readahead for compressed pages
  Similar to subpage cases.

- Make btrfs_alloc_folio_array() to accept a new @order parameter

- Add a helper to calculate the minimal folio size

All those changes should not affect the existing bs <= ps handling.

Signed-off-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: fix the incorrect max_bytes value for find_lock_delalloc_range()
Qu Wenruo [Fri, 19 Sep 2025 05:03:23 +0000 (14:33 +0930)]
btrfs: fix the incorrect max_bytes value for find_lock_delalloc_range()

[BUG]
With my local branch to enable bs > ps support for btrfs, sometimes I
hit the following ASSERT() inside submit_one_sector():

ASSERT(block_start != EXTENT_MAP_HOLE);

Please note that it's not yet possible to hit this ASSERT() in the wild
yet, as it requires btrfs bs > ps support, which is not even in the
development branch.

But on the other hand, there is also a very low chance to hit above
ASSERT() with bs < ps cases, so this is an existing bug affect not only
the incoming bs > ps support but also the existing bs < ps support.

[CAUSE]
Firstly that ASSERT() means we're trying to submit a dirty block but
without a real extent map nor ordered extent map backing it.

Furthermore with extra debugging, the folio triggering such ASSERT() is
always larger than the fs block size in my bs > ps case.
(8K block size, 4K page size)

After some more debugging, the ASSERT() is trigger by the following
sequence:

 extent_writepage()
 |  We got a 32K folio (4 fs blocks) at file offset 0, and the fs block
 |  size is 8K, page size is 4K.
 |  And there is another 8K folio at file offset 32K, which is also
 |  dirty.
 |  So the filemap layout looks like the following:
 |
 |  "||" is the filio boundary in the filemap.
 |  "//| is the dirty range.
 |
 |  0        8K       16K        24K         32K       40K
 |  |////////|        |//////////////////////||////////|
 |
 |- writepage_delalloc()
 |  |- find_lock_delalloc_range() for [0, 8K)
 |  |  Now range [0, 8K) is properly locked.
 |  |
 |  |- find_lock_delalloc_range() for [16K, 40K)
 |  |  |- btrfs_find_delalloc_range() returned range [16K, 40K)
 |  |  |- lock_delalloc_folios() locked folio 0 successfully
 |  |  |
 |  |  |  The filemap range [32K, 40K) got dropped from filemap.
 |  |  |
 |  |  |- lock_delalloc_folios() failed with -EAGAIN on folio 32K
 |  |  |  As the folio at 32K is dropped.
 |  |  |
 |  |  |- loops = 1;
 |  |  |- max_bytes = PAGE_SIZE;
 |  |  |- goto again;
 |  |  |  This will re-do the lookup for dirty delalloc ranges.
 |  |  |
 |  |  |- btrfs_find_delalloc_range() called with @max_bytes == 4K
 |  |  |  This is smaller than block size, so
 |  |  |  btrfs_find_delalloc_range() is unable to return any range.
 |  |  \- return false;
 |  |
 |  \- Now only range [0, 8K) has an OE for it, but for dirty range
 |     [16K, 32K) it's dirty without an OE.
 |     This breaks the assumption that writepage_delalloc() will find
 |     and lock all dirty ranges inside the folio.
 |
 |- extent_writepage_io()
    |- submit_one_sector() for [0, 8K)
    |  Succeeded
    |
    |- submit_one_sector() for [16K, 24K)
       Triggering the ASSERT(), as there is no OE, and the original
       extent map is a hole.

Please note that, this also exposed the same problem for bs < ps
support. E.g. with 64K page size and 4K block size.

If we failed to lock a folio, and falls back into the "loops = 1;"
branch, we will re-do the search using 64K as max_bytes.
Which may fail again to lock the next folio, and exit early without
handling all dirty blocks inside the folio.

[FIX]
Instead of using the fixed size PAGE_SIZE as @max_bytes, use
@sectorsize, so that we are ensured to find and lock any remaining
blocks inside the folio.

And since we're here, add an extra ASSERT() to
before calling btrfs_find_delalloc_range() to make sure the @max_bytes is
at least no smaller than a block to avoid false negative.

Cc: stable@vger.kernel.org # 5.15+
Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: remove pointless key offset setup in create_pending_snapshot()
Filipe Manana [Fri, 19 Sep 2025 16:46:05 +0000 (17:46 +0100)]
btrfs: remove pointless key offset setup in create_pending_snapshot()

There's no point in setting the key's offset to (u64)-1 since we never
use it before setting it to the current transaction's ID. So remove the
assignment of (u64)-1 to the key's offset and move the remainder of the
key initialization close to where it's used.

Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: annotate btrfs_is_testing() as unlikely and make it return bool
Filipe Manana [Fri, 19 Sep 2025 08:55:12 +0000 (09:55 +0100)]
btrfs: annotate btrfs_is_testing() as unlikely and make it return bool

We can annotate btrfs_is_testing() as unlikely since that's the most
expected scenario and it's desirable for the compiler to optimize for
the case we are not running the self tests. So add the annotation to
btrfs_is_testing() and while at it also make it return bool instead of
int.

Also make two of the existing callers use btrfs_is_testing() directly
instead of storing its result in a local variable.

On x86_64 with Debian's gcc 14.2.0-19 this resulted in a very tiny object
code reduction.

Before this change:

  $ size fs/btrfs/btrfs.ko
     text    data     bss     dec     hex filename
  1913263  161567   15592 2090422  1fe5b6 fs/btrfs/btrfs.ko

After this change:

  $ size fs/btrfs/btrfs.ko
     text    data     bss     dec     hex filename
  1913257  161567   15592 2090416  1fe5b0 fs/btrfs/btrfs.ko

Reviewed-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: make the rule checking more readable for should_cow_block()
Filipe Manana [Fri, 19 Sep 2025 08:16:20 +0000 (09:16 +0100)]
btrfs: make the rule checking more readable for should_cow_block()

It's quite hard and unreadable the way the rule checks are organized in
should_cow_block(). We have a single if statement that returns 0 (false)
and it checks several conditions, with one them being a negated compound
condition which is particularly hard to reason immediately.

Improve on this by using multiple if statements, each checking a single
condition and returning immediately. Also change the return type from an
integer to a boolean, since all we need is to return true or false.

At least on x86_64 with Debian's gcc 14.2.0-19, this also reduces the
object code size by 64 bytes.

Before this change:

   $ size fs/btrfs/btrfs.ko
      text    data     bss     dec     hex filename
   1913327  161567   15592 2090486  1fe5f6 fs/btrfs/btrfs.ko

After this change:

   $ size fs/btrfs/btrfs.ko
      text    data     bss     dec     hex filename
   1913263  161567   15592 2090422  1fe5b6 fs/btrfs/btrfs.ko

Reviewed-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: simplify inline extent end calculation at replay_one_extent()
Filipe Manana [Thu, 18 Sep 2025 12:29:16 +0000 (13:29 +0100)]
btrfs: simplify inline extent end calculation at replay_one_extent()

There is no need to store the extent's ram_bytes in two variables,
further more one of them, named 'size', is used only for the extent's end
offset calculation. So remove the 'size' variable and use 'nbytes' only.

Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: fix comment about nbytes increase at replay_one_extent()
Filipe Manana [Thu, 18 Sep 2025 11:58:53 +0000 (12:58 +0100)]
btrfs: fix comment about nbytes increase at replay_one_extent()

The comment is wrong about the part where it says a prealloc extent does
not contribute to an inode's nbytes - it does. Only holes don't contribute
and that's what we are checking for, as prealloc extents always have a
disk_bytenr different from 0. So fix the comment and re-organize the code
to not set nbytes twice and set it to the extent item's number of bytes
only if it doesn't represent a hole - in case it's a hole we have already
initialized nbytes to 0 when we declared it.

Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: return any hit error from extent_writepage_io()
Qu Wenruo [Wed, 17 Sep 2025 23:10:45 +0000 (08:40 +0930)]
btrfs: return any hit error from extent_writepage_io()

Since the support of bs < ps support, extent_writepage_io() will submit
multiple blocks inside the folio.

But if we hit error submitting one sector, but the next sector can still
be submitted successfully, the function extent_writepage_io() will still
return 0.

This will make btrfs to silently ignore the error without setting error
flag for the filemap.

Fix it by recording the first error hit, and always return that value.

Fixes: 8bf334beb349 ("btrfs: fix double accounting race when extent_writepage_io() failed")
Reviewed-by: Daniel Vacek <neelx@suse.com>
Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: mark leaf space and overflow checks as unlikely on insert and extension
Filipe Manana [Tue, 16 Sep 2025 15:33:00 +0000 (16:33 +0100)]
btrfs: mark leaf space and overflow checks as unlikely on insert and extension

We have several sanity checks when inserting or extending items in a btree
that verify we didn't overflow the leaf or access a slot beyond the last
one. These are cases that are never expected to be hit so mark them as
unlikely, allowing the compiler to potentially generate better code.

Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: mark as unlikely not uptodate extent buffer checks when navigating btrees
Filipe Manana [Tue, 16 Sep 2025 15:09:50 +0000 (16:09 +0100)]
btrfs: mark as unlikely not uptodate extent buffer checks when navigating btrees

We expect that after attempting to read an extent buffer we had no errors
therefore the extent buffer is up to date, so mark the checks for a not up
to date extent buffer as unlikely and allow the compiler to pontentially
generate better code.

Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: mark extent buffer alignment checks as unlikely
Filipe Manana [Tue, 16 Sep 2025 14:50:49 +0000 (15:50 +0100)]
btrfs: mark extent buffer alignment checks as unlikely

We are not expecting to ever fail the extent buffer alignment checks, so
mark them as unlikely to allow the compiler to potentially generate more
optimized code.

Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: store and use node size in local variable in check_eb_alignment()
Filipe Manana [Tue, 16 Sep 2025 11:13:39 +0000 (12:13 +0100)]
btrfs: store and use node size in local variable in check_eb_alignment()

Instead of dereferencing fs_info every time we need to access the node
size, store in a local variable to make the code less verbose and avoid
a line split too.

Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: print-tree: print key types as human readable strings
Filipe Manana [Mon, 15 Sep 2025 15:23:04 +0000 (16:23 +0100)]
btrfs: print-tree: print key types as human readable strings

Looking at a leaf dump from the kernel's print-tree implementation is not
so friendly to analyze since key types are printed as numbers. Improve on
this by printing key types as strings that are a diminutive of the macro
names for key types, just like we do in btrfs-progs.

Reviewed-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: print-tree: move code for processing file extent item into helper
Filipe Manana [Mon, 15 Sep 2025 14:23:43 +0000 (15:23 +0100)]
btrfs: print-tree: move code for processing file extent item into helper

The code for processing file extent items is quite large and it's better
to have it in a dedicated helper rather than in a huge switch statement,
just like we do in btrfs-progs.

Reviewed-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: print-tree: print compression type for file extent items
Filipe Manana [Mon, 15 Sep 2025 10:54:49 +0000 (11:54 +0100)]
btrfs: print-tree: print compression type for file extent items

We are not printing anything about the compression type, so add that
useful information in the same format as btrfs-progs.

Reviewed-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: print-tree: print correct inline extent data size
Filipe Manana [Mon, 15 Sep 2025 10:37:34 +0000 (11:37 +0100)]
btrfs: print-tree: print correct inline extent data size

We are advertising the ram_bytes of an inline extent as its data size, but
that is not true for compressed extents. The ram_bytes corresponds to the
uncompressed data size while the data size (compressed data) is given by
btrfs_file_extent_inline_item_len(). So fix this and print both values in
the same format as in btrfs-progs.

Reviewed-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: print-tree: print range information for extent csum items
Filipe Manana [Mon, 15 Sep 2025 10:09:10 +0000 (11:09 +0100)]
btrfs: print-tree: print range information for extent csum items

Currently we don't print anything for extent csum items other than the
generic line with the key, item offset and item size. While one can still
determine the range the extent csum covers by doing a few simple
computations, it makes it more time consuming to analyse a leaf dump.
So add a line that prints information about the range covered by the
checksum using the same format as btrfs-progs. This is useful when
debugging log tree issues since we log extent csum items for new extents.

Reviewed-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: print-tree: print information about dir log items
Filipe Manana [Fri, 12 Sep 2025 16:43:46 +0000 (17:43 +0100)]
btrfs: print-tree: print information about dir log items

We currently don't print information about dir log items (other than the
key, item offset and item size), which is useful to look at when debugging
problems with a log tree. So print their specific information (currently
they only have an end index number) in a format similar to btrfs-progs.

Reviewed-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: print-tree: print information about inode extref items
Filipe Manana [Fri, 12 Sep 2025 16:25:12 +0000 (17:25 +0100)]
btrfs: print-tree: print information about inode extref items

Currently we ignore inode extref items, we just print their key, item
offset in the leaf and their size, no information about their content
like the index number, parent inode, name length and name.

Improve on this by printing the index, parent and name length in the same
format as btrfs-progs. Note that we don't print the name, as that would
require some processing and escaping like we do in btrfs-progs, and that
could expose sensitive information for some users in case they share their
dmesg/syslog and it contains a leaf dump. So for now leave names out.

Reviewed-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: print-tree: print information about inode ref items
Filipe Manana [Thu, 11 Sep 2025 16:36:28 +0000 (17:36 +0100)]
btrfs: print-tree: print information about inode ref items

Currently we ignore inode ref items, we just print their key, item offset
in the leaf and their size, no information about their content like the
index number, name length and name.

Improve on this by printing the index and name length in the same format
as btrfs-progs. Note that we don't print the name, as that would require
some processing and escaping like we do in btrfs-progs, and that could
expose sensitive information for some users in case they share their
dmesg/syslog and it contains a leaf dump. So for now leave names out.

Reviewed-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: print-tree: print dir items for dir index and xattr keys too
Filipe Manana [Wed, 10 Sep 2025 16:10:37 +0000 (17:10 +0100)]
btrfs: print-tree: print dir items for dir index and xattr keys too

Currently we only print the dir items for BTRFS_DIR_ITEM_KEY keys, but
we also have dir items for BTRFS_DIR_INDEX_KEY and BTRFS_XATTR_ITEM_KEY
keys too. So print them for those keys too.

Reviewed-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: print-tree: print more information about dir items
Filipe Manana [Wed, 10 Sep 2025 15:44:15 +0000 (16:44 +0100)]
btrfs: print-tree: print more information about dir items

Currently we only print the object id component of the location key from a
dir item and the flags. We are missing the whole key, transid and the name
and data lengths. We are also ignoring the fact that we can have multiple
dir item objects encoded in a single item for a BTRFS_DIR_ITEM_KEY key, so
what we print is only for the first item.

Improve on this by iterating on all dir items and print the missing
information. This is done with the same format as in btrfs-progs, what
we miss is printing the names and data since not only that would require
some processing and escaping like in btrfs-progs, but it would also reveal
information that may be sensitive and users may not want to share that in
case that get a leaf dumped in dmesg.

Reviewed-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: print-tree: print missing fields for inode items
Filipe Manana [Wed, 10 Sep 2025 14:11:41 +0000 (15:11 +0100)]
btrfs: print-tree: print missing fields for inode items

We are not dumping a lot of fields for an inode item which are useful for
debugging whenever we dump a leaf (log replay failure for example), so add
them and make it as close as possible to the print tree implementation in
btrfs-progs (things like converting timespecs to human readable dates and
converting flags to strings are missing since they are not so practical to
do in the kernel).

Reviewed-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: tree-checker: add inode extref checks
Qu Wenruo [Mon, 15 Sep 2025 23:04:05 +0000 (08:34 +0930)]
btrfs: tree-checker: add inode extref checks

Like inode refs, inode extrefs have a variable length name, which means
we have to do a proper check to make sure no header nor name can exceed
the item limits.

The check itself is very similar to check_inode_ref(), just a different
structure (btrfs_inode_extref vs btrfs_inode_ref).

Reviewed-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: send: index backref cache by node number instead of by sector number
Filipe Manana [Mon, 8 Sep 2025 11:19:29 +0000 (12:19 +0100)]
btrfs: send: index backref cache by node number instead of by sector number

We now have a nodesize_bits member in fs_info so we can index an extent
buffer in the backref cache by node number instead of by sector number.
While this allows for a denser index space with the possibility of using
less maple tree nodes, in practice it's unlikely to hit such benefits
since we currently limit the maximum number of keys in the cache to 128,
so unless all extent buffers are contiguous we are unlikely to see a
memory usage reduction in the backing maple tree due to fewer nodes.
Nevertheless it doesn't cost anything to index by node number and it's
more logical.

Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: dump detailed info and specific messages on log replay failures
Filipe Manana [Fri, 5 Sep 2025 11:58:19 +0000 (12:58 +0100)]
btrfs: dump detailed info and specific messages on log replay failures

Currently debugging log replay failures can be harder than needed, since
all we do now is abort a transaction, which gives us a line number, a
stack trace and an error code. But that is most of the times not enough
to give some clue about what went wrong. So add a new helper to abort
log replay and provide contextual information:

1) Dump the current leaf of the log tree being processed and print the
   slot we are currently at and the key at that slot;

2) Dump the current subvolume tree leaf if we have any;

3) Print the current stage of log replay;

4) Print the id of the subvolume root associated with the log tree we
   are currently processing (as we can have multiple);

5) Print some error message to mention what we were trying to do when we
   got an error.

Replace all transaction abort calls (btrfs_abort_transaction()) with the
new helper btrfs_abort_log_replay(), which besides dumping all that extra
information, it also aborts the current transaction.

Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: abort transaction if we fail to update inode in log replay dir fixup
Filipe Manana [Wed, 3 Sep 2025 16:43:04 +0000 (17:43 +0100)]
btrfs: abort transaction if we fail to update inode in log replay dir fixup

If we fail to update the inode at link_to_fixup_dir(), we don't abort the
transaction and propagate the error up the call chain, which makes it hard
to pinpoint the error to the inode update. So abort the transaction if the
inode update call fails, so that if it happens we known immediately.

Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: abort transaction if we fail to find dir item during log replay
Filipe Manana [Wed, 3 Sep 2025 16:39:24 +0000 (17:39 +0100)]
btrfs: abort transaction if we fail to find dir item during log replay

At __add_inode_ref() if we get an error when trying to lookup a dir item
we don't abort the transaction and propagate the error up the call chain,
so that somewhere else up in the call chain the transaction is aborted.
This however makes it hard to know that the failure comes from looking up
a dir item, so add a transaction abort in case we fail there, so that we
immediately pinpoint where the problem comes from during log replay.

Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: remove pointless inode lookup when processing extrefs during log replay
Filipe Manana [Wed, 3 Sep 2025 11:10:01 +0000 (12:10 +0100)]
btrfs: remove pointless inode lookup when processing extrefs during log replay

At unlink_extrefs_not_in_log() we do an inode lookup of the directory but
we already have the directory inode accessible as a function argument, so
the lookup is redudant. Remove it and use the directory inode passed in as
an argument.

Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: stop passing inode object IDs to __add_inode_ref() in log replay
Filipe Manana [Tue, 2 Sep 2025 17:08:15 +0000 (18:08 +0100)]
btrfs: stop passing inode object IDs to __add_inode_ref() in log replay

There's no point in passing the inode and parent inode object IDs to
__add_inode_ref() and its helpers because we can get them by calling
btrfs_ino() against the inode and the directory inode, and we pass both
inodes to __add_inode_ref() and its helpers. So remove the object IDs
parameters to reduce arguments passed and to make things less confusing.

Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: add path for subvolume tree changes to struct walk_control
Filipe Manana [Tue, 2 Sep 2025 11:16:17 +0000 (12:16 +0100)]
btrfs: add path for subvolume tree changes to struct walk_control

While replaying log trees we need to do searches and updates to subvolume
trees and for that we use a path that we allocate in replay_one_buffer()
and then pass it as a parameter to other functions deeper in the log
replay call chain. Instead of passing it as parameter, add it to struct
walk_control since we pass a pointer to that structure for every log
replay function.

This reduces the number of arguments passed to the functions and it will
be needed and important for an upcoming changes that improves error
reporting for log replay. Also name the new filed in struct walk_control
to 'subvol_path' - while that is longer to type, the naming makes it clear
it's used for subvolume tree operations since many of the log replay
functions operate both on subvolume and log trees, and for the log tree
searches we have struct walk_control::log_leaf to also make it obvious
it's an extent buffer for a log tree extent buffer.

Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: remove redundant path release when overwriting item during log replay
Filipe Manana [Tue, 2 Sep 2025 16:08:19 +0000 (17:08 +0100)]
btrfs: remove redundant path release when overwriting item during log replay

At overwrite_item() we have a redundant btrfs_release_path() just before
failing with -ENOMEM, as the caller who passed in the path will free it
and therefore also release any refcounts and locks on the extent buffers
of the path. So remove it.

Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: remove redundant path release when processing dentry during log replay
Filipe Manana [Mon, 1 Sep 2025 16:03:46 +0000 (17:03 +0100)]
btrfs: remove redundant path release when processing dentry during log replay

At replay_one_one() we have a redundant btrfs_release_path() just before
calling insert_one_name(), as some lines above we have already released
the path with another btrfs_release_path() call. So remove it.

Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: avoid unnecessary path allocation when replaying a dir item
Filipe Manana [Mon, 1 Sep 2025 15:37:05 +0000 (16:37 +0100)]
btrfs: avoid unnecessary path allocation when replaying a dir item

There's no need to allocate 'fixup_path' at replay_one_dir_item(), as the
path passed as an argument is unused by the time link_to_fixup_dir() is
called (replay_one_name() releases the path before it returns).

Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: avoid path allocations when dropping extents during log replay
Filipe Manana [Mon, 1 Sep 2025 14:51:19 +0000 (15:51 +0100)]
btrfs: avoid path allocations when dropping extents during log replay

We can avoid a path allocation in the btrfs_drop_extents() calls we have
at replay_one_extent() and replay_one_buffer() by passing the path we
already have in those contextes as it's unused by the time they call
btrfs_drop_extents().

Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: avoid unnecessary path allocation at fixup_inode_link_count()
Filipe Manana [Mon, 1 Sep 2025 11:43:42 +0000 (12:43 +0100)]
btrfs: avoid unnecessary path allocation at fixup_inode_link_count()

There's no need to allocate a path as our single caller already has a
path that we can use. So pass the caller's path and use it.

Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: add current log leaf, key and slot to struct walk_control
Filipe Manana [Mon, 1 Sep 2025 11:13:48 +0000 (12:13 +0100)]
btrfs: add current log leaf, key and slot to struct walk_control

A lot of the log replay functions get passed the current log leaf being
processed as well as the current slot and the key at that slot. Instead
of passing them as parameters, add them to struct walk_control so that
we reduce the numbers of parameters. This is also going to be needed to
further changes that improve error reporting during log replay.

Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: use the inode item boolean everywhere in overwrite_item()
Filipe Manana [Fri, 29 Aug 2025 15:46:15 +0000 (16:46 +0100)]
btrfs: use the inode item boolean everywhere in overwrite_item()

We have this boolean 'inode_item' to tell if we are processing an inode
item key and we use it in a couple of places while in another two places
we open code by checking if the key type matches the inode item type.
Make this consistent and use the boolean everywhere. Also rename it from
'inode_item' to 'is_inode_item', which makes it more clear that it's a
boolean and not an instance of struct btrfs_inode_item, and make it const
too.

Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: use level argument in log tree walk callback replay_one_buffer()
Filipe Manana [Thu, 28 Aug 2025 16:46:18 +0000 (17:46 +0100)]
btrfs: use level argument in log tree walk callback replay_one_buffer()

We already have the extent buffer's level in an argument, there's no need
to first ensure the extent buffer's data is loaded (by calling
btrfs_read_extent_buffer()) and then call btrfs_header_level() to check
the level. So use the level argument and do the check before calling
btrfs_read_extent_buffer().

Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: use level argument in log tree walk callback process_one_buffer()
Filipe Manana [Thu, 28 Aug 2025 16:20:24 +0000 (17:20 +0100)]
btrfs: use level argument in log tree walk callback process_one_buffer()

We already have the extent buffer's level in an argument, there's no need
to call btrfs_header_level(). So use the level argument and make the code
shorter.

Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: pass walk_control structure to overwrite_item()
Filipe Manana [Thu, 28 Aug 2025 16:04:40 +0000 (17:04 +0100)]
btrfs: pass walk_control structure to overwrite_item()

Instead of passing the transaction and subvolume root as arguments to
overwrite_item(), pass the walk_control structure as we can grab them
from the structure. This reduces the number of arguments passed and it's
going to be needed by an incoming change that improves error reporting
for log replay.

Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: pass walk_control structure to drop_one_dir_item() and helpers
Filipe Manana [Thu, 28 Aug 2025 15:40:48 +0000 (16:40 +0100)]
btrfs: pass walk_control structure to drop_one_dir_item() and helpers

Instead of passing the transaction as an argument to drop_one_dir_item()
and its helpers (link_to_fixup_dir() and unlink_inode_for_log_replay()),
pass the walk_control structure as we can access the transaction from it
and the subvolume root. This is going to be needed by an incoming change
that improves error reporting for log replay and also reduces the number
of arguments passed to link_to_fixup_dir().

Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: pass walk_control structure to replay_one_dir_item() and replay_one_name()
Filipe Manana [Thu, 28 Aug 2025 12:54:40 +0000 (13:54 +0100)]
btrfs: pass walk_control structure to replay_one_dir_item() and replay_one_name()

Instead of passing the transaction and subvolume root and log tree as
arguments, pass the walk_control structure as we can grab all of those
from the structure. This reduces the number of arguments passed and it's
going to be needed by an incoming change that improves error reporting
for log replay.

Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: pass walk_control structure to add_inode_ref() and helpers
Filipe Manana [Thu, 28 Aug 2025 12:11:17 +0000 (13:11 +0100)]
btrfs: pass walk_control structure to add_inode_ref() and helpers

Instead of passing the transaction, subvolume root and log tree as
arguments to add_inode_ref() and its helpers (__add_inode_ref(),
unlink_refs_not_in_log(), unlink_extrefs_not_in_log() and
unlink_old_inode_refs()), pass the walk_control structure as we can
access all of those from the structure. This reduces the number of
arguments passed and it's going to be needed by an incoming change
that improves error reporting for log replay.

Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: pass walk_control structure to replay_one_extent()
Filipe Manana [Thu, 28 Aug 2025 12:05:52 +0000 (13:05 +0100)]
btrfs: pass walk_control structure to replay_one_extent()

Instead of passing the transaction and subvolume root as arguments to
replay_one_extent(), pass the walk_control structure as we can grab all
of those from the structure. This reduces the number of arguments passed
and it's going to be needed by an incoming change that improves error
reporting for log replay.

Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: pass walk_control structure to check_item_in_log()
Filipe Manana [Wed, 27 Aug 2025 15:27:57 +0000 (16:27 +0100)]
btrfs: pass walk_control structure to check_item_in_log()

Instead of passing the transaction and log tree as arguments to
check_item_in_log(), pass the walk_control structure as we can grab those
from the structure. This reduces the number of arguments passed and it's
going to be needed by an incoming change that improves error reporting for
log replay. Notice that a NULL log root argument to check_item_in_log()
makes it unconditionally delete a directory entry, so since the
walk_control always has a non-NULL log root, we add an extra boolean to
check_item_in_log() to tell it if it should unconditionally delete a
directory entry, preserving the behaviour and also making it a bit more
clear.

Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: pass walk_control structure to replay_dir_deletes()
Filipe Manana [Wed, 27 Aug 2025 15:10:39 +0000 (16:10 +0100)]
btrfs: pass walk_control structure to replay_dir_deletes()

Instead of passing the transaction, subvolume root and log tree as
arguments to replay_dir_deletes(), pass the walk_control structure as
we can grab all of those from the structure. This reduces the number of
arguments passed and it's going to be needed by an incoming change that
improves error reporting for log replay. This also requires changing
fixup_inode_link_counts() and fixup_inode_link_count() to take that
structure as an argument since fixup_inode_link_count() makes a call
to replay_dir_deletes().

Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: move up the definition of struct walk_control
Filipe Manana [Wed, 27 Aug 2025 14:48:49 +0000 (15:48 +0100)]
btrfs: move up the definition of struct walk_control

In upcoming changes we need to pass struct walk_control as an argument to
replay_dir_deletes() and link_to_fixup_dir() so we need to move its
definition above the prototypes of those functions. So move it up right
below the enum that defines log replay stages and before any functions and
function prototypes are declared. Also fixup the comments while moving it
so that they comply with the preferred code style (capitalize the first
word in a sentence, end sentences with punctuation, makes lines wider and
closer to the 80 characters limit).

Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: pass walk_control structure to replay_xattr_deletes()
Filipe Manana [Wed, 27 Aug 2025 14:24:08 +0000 (15:24 +0100)]
btrfs: pass walk_control structure to replay_xattr_deletes()

Instead of passing the transaction, subvolume root and log tree as
arguments to replay_xattr_deletes(), pass the walk_control structure as
we can grab all of those from the structure. This reduces the number of
arguments passed and it's going to be needed by an incoming change that
improves error reporting for log replay.

Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: always drop log root tree reference in btrfs_replay_log()
Filipe Manana [Wed, 27 Aug 2025 11:10:28 +0000 (12:10 +0100)]
btrfs: always drop log root tree reference in btrfs_replay_log()

Currently we have this odd behaviour:

1) At btrfs_replay_log() we drop the reference of the log root tree if
   the call to btrfs_recover_log_trees() failed;

2) But if the call to btrfs_recover_log_trees() did not fail, we don't
   drop the reference in btrfs_replay_log() - we expect that
   btrfs_recover_log_trees() does it in case it returns success.

Let's simplify this and make btrfs_replay_log() always drop the reference
on the log root tree, not only this simplifies code as it's what makes
sense since it's btrfs_replay_log() who grabbed the reference in the first
place.

Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: stop setting log_root_tree->log_root to NULL in btrfs_recover_log_trees()
Filipe Manana [Wed, 27 Aug 2025 11:03:52 +0000 (12:03 +0100)]
btrfs: stop setting log_root_tree->log_root to NULL in btrfs_recover_log_trees()

There's no point in setting log_root_tree->log_root to NULL as this is
already NULL, we never assigned anything to it before and it's meaningless
as a log root never has a value other than NULL for the ->log_root field,
that can be not NULL only for non log roots.

Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: stop passing transaction parameter to log tree walk functions
Filipe Manana [Wed, 27 Aug 2025 10:48:43 +0000 (11:48 +0100)]
btrfs: stop passing transaction parameter to log tree walk functions

It's unncessary to pass a transaction parameter since struct walk_control
already has a member that points to the transaction, so we can make the
functions access the structure.

Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: deduplicate log root free in error paths from btrfs_recover_log_trees()
Filipe Manana [Tue, 26 Aug 2025 16:30:16 +0000 (17:30 +0100)]
btrfs: deduplicate log root free in error paths from btrfs_recover_log_trees()

Instead of duplicating the dropping of a log tree in case we jump to the
'error' label, move the dropping under the 'error' label and get rid of the
the unnecessary setting of the log root to NULL since we return immediately
after.

Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: add and use a log root field to struct walk_control
Filipe Manana [Tue, 26 Aug 2025 16:25:56 +0000 (17:25 +0100)]
btrfs: add and use a log root field to struct walk_control

Instead of passing an extra log root parameter for the log tree walk
functions and callbacks, add the log tree to struct walk_control and
make those functions and callbacks extract the log root from that
structure, reducing the number of parameters. This also simplifies
further upcoming changes to report log tree replay failures.

Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: rename root to log in walk_down_log_tree() and walk_up_log_tree()
Filipe Manana [Tue, 26 Aug 2025 15:43:48 +0000 (16:43 +0100)]
btrfs: rename root to log in walk_down_log_tree() and walk_up_log_tree()

Everywhere we have a log root we name it as 'log' or 'log_root' except in
walk_down_log_tree() and walk_up_log_tree() where we name it as 'root',
which not only it's inconsistent, it's also confusing since we typically
use 'root' when naming variables that refer to a subvolume tree. So for
clairty and consistency rename the 'root' argument to 'log'.

Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: rename replay_dest member of struct walk_control to root
Filipe Manana [Tue, 26 Aug 2025 15:27:46 +0000 (16:27 +0100)]
btrfs: rename replay_dest member of struct walk_control to root

Everywhere else we refer to a subvolume root we are replaying to simply
as 'root', so rename from 'replay_dest' to 'root' for consistency and
having a more meaningful and shorter name. While at it also update the
comment to be more detailed and comply to preferred style (first word in
a sentence is capitalized and sentence ends with punctuation).

Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: use booleans in walk control structure for log replay
Filipe Manana [Tue, 26 Aug 2025 15:12:53 +0000 (16:12 +0100)]
btrfs: use booleans in walk control structure for log replay

The 'free' and 'pin' member of struct walk_control, used during log replay
and when freeing a log tree, are defined as integers but in practice are
used as booleans. Change their type to bool and while at it update their
comments to be more detailed and comply with the preferred comment style
(first word in a sentence is capitalized, sentences end with punctuation
and the comment opening (/*) is on a line of its own).

Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: cache max and min order inside btrfs_fs_info
Qu Wenruo [Sun, 31 Aug 2025 22:25:00 +0000 (07:55 +0930)]
btrfs: cache max and min order inside btrfs_fs_info

Inside btrfs_fs_info we cache several bits shift like sectorsize_bits.

Apply this to max and min folio orders so that every time mapping order
needs to be applied we can skip the calculation.

Signed-off-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: introduce btrfs_bio_for_each_block_all() helper
Qu Wenruo [Tue, 2 Sep 2025 07:15:02 +0000 (16:45 +0930)]
btrfs: introduce btrfs_bio_for_each_block_all() helper

Currently if we want to iterate all blocks inside a bio, we do something
like this:

bio_for_each_segment_all(bvec, bio, iter_all) {
for (off = 0; off < bvec->bv_len; off += sectorsize) {
/* Iterate blocks using bv + off */
}
}

That's fine for now, but it will not handle future bs > ps, as
bio_for_each_segment_all() is a single-page iterator, it will always
return a bvec that's no larger than a page.

But for bs > ps cases, we need a full folio (which covers at least one
block) so that we can work on the block.

To address this problem and handle future bs > ps cases better:

- Introduce a helper btrfs_bio_for_each_block_all()
  This helper will create a local bvec_iter, which has the size of the
  target bio. Then grab the current physical address of the current
  location, then advance the iterator by block size.

- Use btrfs_bio_for_each_block_all() to replace existing call sites
  Including:

  * set_bio_pages_uptodate() in raid56
  * verify_bio_data_sectors() in raid56

  Both will result much easier to read code.

Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: introduce btrfs_bio_for_each_block() helper
Qu Wenruo [Tue, 2 Sep 2025 05:21:49 +0000 (14:51 +0930)]
btrfs: introduce btrfs_bio_for_each_block() helper

Currently if we want to iterate a bio in block unit, we do something
like this:

while (iter->bi_size) {
struct bio_vec bv = bio_iter_iovec();

/* Do something with using the bv */

bio_advance_iter_single(&bbio->bio, iter, sectorsize);
}

That's fine for now, but it will not handle future bs > ps, as
bio_iter_iovec() returns a single-page bvec, meaning the bv_len will not
exceed page size.

This means the code using that bv can only handle a block if bs <= ps.

To address this problem and handle future bs > ps cases better:

- Introduce a helper btrfs_bio_for_each_block()
  Instead of bio_vec, which has single and multiple page version and
  multiple page version has quite some limits, use my favorite way to
  represent a block, phys_addr_t.

  For bs <= ps cases, nothing is changed, except we will do a very
  small overhead to convert phys_addr_t to a folio, then use the proper
  folio helpers to handle the possible highmem cases.

  For bs > ps cases, all blocks will be backed by large folios, meaning
  every folio will cover at least one block. And still use proper folio
  helpers to handle highmem cases.

  With phys_addr_t, we will handle both large folio and highmem
  properly. So there is no better single variable to present a btrfs
  block than phys_addr_t.

- Extract the data block csum calculation into a helper
  The new helper, btrfs_calculate_block_csum() will be utilized by
  btrfs_csum_one_bio().

- Use btrfs_bio_for_each_block() to replace existing call sites
  Including:

  * index_one_bio() from raid56.c
    Very straight-forward.

  * btrfs_check_read_bio()
    Also update repair_one_sector() to grab the folio using phys_addr_t,
    and do extra checks to make sure the folio covers at least one
    block.
    We do not need to bother bv_len at all now.

  * btrfs_csum_one_bio()
    Now we can move the highmem handling into a dedicated helper,
    calculate_block_csum(), and use btrfs_bio_for_each_block() helper.

There is one exception in btrfs_decompress_buf2page(), which is copying
decompressed data into the original bio, which is not iterating using
block size thus we don't need to bother.

Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: concentrate highmem handling for data verification
Qu Wenruo [Tue, 2 Sep 2025 01:21:25 +0000 (10:51 +0930)]
btrfs: concentrate highmem handling for data verification

Currently for btrfs checksum verification, we do it in the following
pattern:

kaddr = kmap_local_*();
ret = btrfs_check_csum_csum(kaddr);
kunmap_local(kaddr);

It's OK for now, but it's still not following the patterns of helpers
inside linux/highmem.h, which never requires a virt memory address.

In those highmem helpers, they mostly accept a folio, some offset/length
inside the folio, and in the implementation they check if the folio
needs partial kmap, and do the handling.

Inspired by those formal highmem helpers, enhance the highmem handling
of data checksum verification by:

- Rename btrfs_check_sector_csum() to btrfs_check_block_csum()
  To follow the more common term "block" used in all other major
  filesystems.

- Pass a physical address into btrfs_check_block_csum() and
  btrfs_data_csum_ok()
  The physical address is always available even for a highmem page.
  Since it's page frame number << PAGE_SHIFT + offset in page.

  And with that physical address, we can grab the folio covering the
  page, and do extra checks to ensure it covers at least one block.

  This also allows us to do the kmap inside btrfs_check_block_csum().
  This means all the extra HIGHMEM handling will be concentrated into
  btrfs_check_block_csum(), and no callers will need to bother highmem
  by themselves.

- Properly zero out the block if csum mismatch
  Since btrfs_data_csum_ok() only got a paddr, we can not and should not
  use memzero_bvec(), which only accepts single page bvec.
  Instead use paddr to grab the folio and call folio_zero_range()

Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: support all block sizes which is no larger than page size
Qu Wenruo [Sat, 9 Aug 2025 04:49:16 +0000 (14:19 +0930)]
btrfs: support all block sizes which is no larger than page size

Currently if block size < page size, btrfs only supports one single
config, 4K.

This is mostly to reduce the test configurations, as 4K is going to be
the default block size for all architectures.

However all other major filesystems have no artificial limits on the
support block size, and some are already supporting block size > page
sizes.

Since the btrfs subpage block support has been there for a long time,
it's time for us to enable all block size <= page size support.

So here enable all block sizes support as long as it's no larger than
page size for experimental builds.

Signed-off-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: scrub: replace max_t()/min_t() with clamp() in scrub_throttle_dev_io()
Thorsten Blum [Mon, 1 Sep 2025 15:01:44 +0000 (17:01 +0200)]
btrfs: scrub: replace max_t()/min_t() with clamp() in scrub_throttle_dev_io()

Replace max_t() followed by min_t() with a single clamp().

As was pointed by David Laight in
https://lore.kernel.org/linux-btrfs/20250906122458.75dfc8f0@pumpkin/
the calculation may overflow u32 when the input value is too large, so
clamp_t() is not used.  In practice the expected values are in range of
megabytes to gigabytes (throughput limit) so the bug would not happen.

Signed-off-by: Thorsten Blum <thorsten.blum@linux.dev>
Reviewed-by: David Sterba <dsterba@suse.com>
[ Use clamp() and add explanation. ]
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: fix typos in comments and strings
David Sterba [Thu, 21 Aug 2025 22:57:42 +0000 (00:57 +0200)]
btrfs: fix typos in comments and strings

Annual typo fixing pass. Strangely codespell found only about 30% of
what is in this patch, the rest was done manually using text
spellchecker with a custom dictionary of acceptable terms.

Reviewed-by: Neal Gompa <neal@gompa.dev>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: reduce compression workspace buffer space to block size
Qu Wenruo [Thu, 14 Aug 2025 01:14:48 +0000 (10:44 +0930)]
btrfs: reduce compression workspace buffer space to block size

Currently the compression workspace buffer size is always based on
PAGE_SIZE, but btrfs has support subpage sized block size for some time.

This means for one-shot compression algorithm like lzo, we're wasting
quite some memory if the block size is smaller than page size, as the
LZO only works on one block (thus one-shot).

On 64K page sized systems with 4K block size, it means we only need at
most 8K buffer space for lzo, but in reality we're allocating 64K
buffer.

So to reduce the memory usage, change all workspace buffer to base its
size based on block size other than page size.

Signed-off-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: rename btrfs_compress_op to btrfs_compress_levels
Qu Wenruo [Thu, 14 Aug 2025 01:05:23 +0000 (10:35 +0930)]
btrfs: rename btrfs_compress_op to btrfs_compress_levels

Since all workspace managers are per-fs, there is no need nor no way to
store them inside btrfs_compress_op::wsm anymore.

With that said, we can do the following modifications:

- Remove zstd_workspace_mananger::ops
  Zstd always grab the global btrfs_compress_op[].
- Remove btrfs_compress_op::wsm member
- Rename btrfs_compress_op to btrfs_compress_levels

This should make it more clear that btrfs_compress_levels structures are
only to indicate the levels of each compress algorithm.

Signed-off-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: cleanup the per-module compression workspace managers
Qu Wenruo [Thu, 14 Aug 2025 00:59:58 +0000 (10:29 +0930)]
btrfs: cleanup the per-module compression workspace managers

Since all workspaces are handled by the per-fs workspace managers, we
can safely remove the old per-module managers.

Signed-off-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: migrate to use per-fs workspace manager
Qu Wenruo [Thu, 14 Aug 2025 00:19:50 +0000 (09:49 +0930)]
btrfs: migrate to use per-fs workspace manager

There are several interfaces involved for each algorithm:

- alloc workspace
  All algorithms allocate a workspace without the need for workspace
  manager.
  So no change needs to be done.

- get workspace
  This involves checking the workspace manager to find a free one, and
  if not, allocate a new one.

  For none and lzo, they share the same generic btrfs_get_workspace()
  helper, only needs to update that function to use the per-fs manager.

  For zlib it uses a wrapper around btrfs_get_workspace(), so no special
  work needed.

  For zstd, update zstd_find_workspace() and zstd_get_workspace() to
  utilize the per-fs manager.

- put workspace
  For none/zlib/lzo they share the same btrfs_put_workspace(), update
  that function to use the per-fs manager.

  For zstd, it's zstd_put_workspace(), the same update.

- zstd specific timer
  This is the timer to reclaim workspace, change it to grab the per-fs
  workspace manager instead.

Now all workspace are managed by the per-fs manager.

Signed-off-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: add generic workspace manager initialization
Qu Wenruo [Wed, 13 Aug 2025 07:06:57 +0000 (16:36 +0930)]
btrfs: add generic workspace manager initialization

This involves:

- Add (alloc|free)_workspace_manager helpers.
  These are the helper to alloc/free workspace_manager structure.

  The allocator will allocate a workspace_manager structure, initialize
  it, and pre-allocate one workspace for it.

  The freer will do the cleanup and set the manager pointer to NULL.

- Call alloc_workspace_manager() inside btrfs_alloc_compress_wsm()
- Call alloc_workspace_manager() inside btrfs_free_compress_wsm()
  For none, zlib and lzo compression algorithms.

For now the generic per-fs workspace managers won't really have any effect,
and all compression is still going through the global workspace manager.

Signed-off-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: add workspace manager initialization for zstd
Qu Wenruo [Wed, 13 Aug 2025 05:35:20 +0000 (15:05 +0930)]
btrfs: add workspace manager initialization for zstd

This involves:

- Add zstd_alloc_workspace_manager() and zstd_free_workspace_manager()
  Those two functions will accept an fs_info pointer, and alloc/free
  fs_info->compr_wsm[BTRFS_COMPRESS_ZSTD] pointer.

- Add btrfs_alloc_compress_wsm() and btrfs_free_compress_wsm()
  Those are helpers allocating the workspace managers for all
  algorithms.
  For now only zstd is supported, and the timing is a little unusual,
  the btrfs_alloc_compress_wsm() should only be called after the
  sectorsize being initialized.

  Meanwhile btrfs_free_fs_info_compress() is called in
  btrfs_free_fs_info().

- Move the definition of btrfs_compression_type to "fs.h"
  The reason is that "compression.h" has already included "fs.h", thus
  we can not just include "compression.h" to get the definition of
  BTRFS_NR_COMPRESS_TYPES to define fs_info::compr_wsm[].

For now the per-fs zstd workspace manager won't really have any effect,
and all compression is still going through the global workspace manager.

Signed-off-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: add an fs_info parameter for compression workspace manager
Qu Wenruo [Wed, 13 Aug 2025 05:03:38 +0000 (14:33 +0930)]
btrfs: add an fs_info parameter for compression workspace manager

[BACKGROUND]
Currently btrfs shares workspaces and their managers for all filesystems,
this is mostly fine as all those workspaces are using page size based
buffers, and btrfs only support block size (bs) <= page size (ps).

This means even if bs < ps, we at most waste some buffer space in the
workspace, but everything will still work fine.

The problem here is that is limiting our support for bs > ps cases.

As now a workspace now may need larger buffer to handle bs > ps cases,
but since the pool has no way to distinguish different workspaces, a
regular workspace (which is still using buffer size based on ps) can be
passed to a btrfs whose bs > ps.

In that case the buffer is not large enough, and will cause various
problems.

[ENHANCEMENT]
To prepare for the per-fs workspace migration, add an fs_info parameter
to all workspace related functions.

For now this new fs_info parameter is not yet utilized.

Signed-off-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: keep folios locked inside run_delalloc_nocow()
Qu Wenruo [Mon, 28 Jul 2025 08:27:57 +0000 (17:57 +0930)]
btrfs: keep folios locked inside run_delalloc_nocow()

[BUG]
There is a very low chance that DEBUG_WARN() inside
btrfs_writepage_cow_fixup() can be triggered when
CONFIG_BTRFS_EXPERIMENTAL is enabled.

This only happens after run_delalloc_nocow() failed.

Unfortunately I haven't hit it for a while thus no real world dmesg for
now.

[CAUSE]
There is a race window where after run_delalloc_nocow() failed, error
handling can race with writeback thread.

Before we hit run_delalloc_nocow(), there is an inode with the following
dirty pages: (4K page size, 4K block size, no large folio)

  0         4K          8K          12K          16K
  |/////////|///////////|///////////|////////////|

The inode also have NODATACOW flag, and the above dirty range will go
through different extents during run_delalloc_range():

  0         4K          8K          12K          16K
  |  NOCOW  |    COW    |    COW    |   NOCOW    |

The race happen like this:

    writeback thread A            |        writeback thread B
----------------------------------+--------------------------------------
Writeback for folio 0             |
run_delalloc_nocow()              |
|- nocow_one_range()              |
|  For range [0, 4K), ret = 0     |
|                                 |
|- fallback_to_cow()              |
|  For range [4K, 8K), ret = 0    |
|  Folio 4K *UNLOCKED*            |
|                                 | Writeback for folio 4K
|- fallback_to_cow()              | extent_writepage()
|  For range [8K, 12K), failure   | |- writepage_delalloc()
|   | |
|- btrfs_cleanup_ordered_extents()| |
   |- btrfs_folio_clear_ordered() | |
   |  Folio 0 still locked, safe  | |
   |                              | |  Ordered extent already allocated.
   |                              | |  Nothing to do.
   |                              | |- extent_writepage_io()
   |                              |    |- btrfs_writepage_cow_fixup()
   |- btrfs_folio_clear_ordered() |    |
      Folio 4K hold by thread B,  |    |
      UNSAFE!                     |    |- btrfs_test_ordered()
                                  |    |  Cleared by thread A,
  |    |
                                  |    |- DEBUG_WARN();

This is only possible after run_delalloc_nocow() failure, as
cow_file_range() will keep all folios and io tree range locked, until
everything is finished or after error handling.

The root cause is we allow fallback_to_cow() and nocow_one_range() to
unlock the folios after a successful run, so that during error handling
we're no longer safe to use btrfs_cleanup_ordered_extents() as the
folios are already unlocked.

[FIX]
- Make fallback_to_cow() and nocow_one_range() to keep folios locked
  after a successful run

  For fallback_to_cow() we can pass COW_FILE_RANGE_KEEP_LOCKED flag
  into cow_file_range().

  For nocow_one_range() we have to remove the PAGE_UNLOCK flag from
  extent_clear_unlock_delalloc().

- Unlock folios if everything is fine in run_delalloc_nocow()

- Use extent_clear_unlock_delalloc() to handle range [@start,
  @cur_offset) inside run_delalloc_nocow()
  Since folios are still locked, we do not need
  cleanup_dirty_folios() to do the cleanup.

  extent_clear_unlock_delalloc() with "PAGE_START_WRITEBACK |
  PAGE_END_WRITEBACK" will clear the dirty flags.

- Remove cleanup_dirty_folios()

Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: make nocow_one_range() to do cleanup on error
Qu Wenruo [Mon, 28 Jul 2025 08:27:56 +0000 (17:57 +0930)]
btrfs: make nocow_one_range() to do cleanup on error

Currently if we hit an error inside nocow_one_range(), we do not clear
the page dirty, and let the caller to handle it.

This is very different compared to fallback_to_cow(), when that function
failed, everything will be cleaned up by cow_file_range().

Enhance the situation by:

- Use a common error handling for nocow_one_range()
  If we failed anything, use the same btrfs_cleanup_ordered_extents()
  and extent_clear_unlock_delalloc().

  btrfs_cleanup_ordered_extents() is safe even if we haven't created new
  ordered extent, in that case there should be no OE and that function
  will do nothing.

  The same applies to extent_clear_unlock_delalloc(), and since we're
  passing PAGE_UNLOCK | PAGE_START_WRITEBACK | PAGE_END_WRITEBACK, it
  will also clear folio dirty flag during error handling.

- Avoid touching the failed range of nocow_one_range()
  As the failed range will be cleaned up and unlocked by that function.

  Here we introduce a new variable @nocow_end to record the failed range,
  so that we can skip it during the error handling of run_delalloc_nocow().

Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: enhance error messages for delalloc range failure
Qu Wenruo [Mon, 28 Jul 2025 08:27:55 +0000 (17:57 +0930)]
btrfs: enhance error messages for delalloc range failure

When running emulated write error tests like generic/475, we can hit
error messages like this:

 BTRFS error (device dm-12 state EA): run_delalloc_nocow failed, root=596 inode=264 start=1605632 len=73728: -5
 BTRFS error (device dm-12 state EA): failed to run delalloc range, root=596 ino=264 folio=1605632 submit_bitmap=0-7 start=1605632 len=73728: -5

Which is normally buried by direct IO error messages.

However above error messages are not enough to determine which is the
real range that caused the error.
Considering we can have multiple different extents in one delalloc
range (e.g. some COW extents along with some NOCOW extents), just
outputting the error at the end of run_delalloc_nocow() is not enough.

To enhance the error messages:

- Remove the rate limit on the existing error messages
  In the generic/475 example, most error messages are from direct IO,
  not really from the delalloc range.
  Considering how useful the delalloc range error messages are, we don't
  want they to be rate limited.

- Add extra @cur_offset output for cow_file_range()
- Add extra variable output for run_delalloc_nocow()
  This is especially important for run_delalloc_nocow(), as there
  are extra error paths where we can hit error without into
  nocow_one_range() nor fallback_to_cow().

- Add an error message for nocow_one_range()
  That's the missing part.
  For fallback_to_cow(), we have error message from cow_file_range()
  already.

- Constify the @len and @end local variables for nocow_one_range()
  This makes it much easier to make sure @len and @end are not modified
  at runtime.

Reviewed-by: Boris Burkov <boris@bur.io>
Signed-off-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: rework error handling of run_delalloc_nocow()
Qu Wenruo [Mon, 28 Jul 2025 08:27:54 +0000 (17:57 +0930)]
btrfs: rework error handling of run_delalloc_nocow()

Currently the error handling of run_delalloc_nocow() modifies
@cur_offset to handle different parts of the delalloc range.

However the error handling can always be split into 3 parts:

1) The range with ordered extents allocated (OE cleanup)
   We have to cleanup the ordered extents and unlock the folios.

2) The range that have been cleaned up (skip)
   For now it's only for the range of fallback_to_cow().

   We should not touch it at all.

3) The range that is not yet touched (untouched)
   We have to unlock the folios and clear any reserved space.

This 3 ranges split has the same principle as cow_file_range(), however
the NOCOW/COW handling makes the above 3 range split much more complex:

a) Failure immediately after a successful OE allocation
   Thus no @cow_start nor @cow_end set.

   start         cur_offset               end
   | OE cleanup  |       untouched        |

b) Failure after hitting a COW range but before calling
   fallback_to_cow()

   start        cow_start    cur_offset   end
   | OE Cleanup |       untouched         |

c) Failure to call fallback_to_cow()

   start        cow_start    cow_end      end
   | OE Cleanup |    skip    |  untouched |

Instead of modifying @cur_offset, do proper range calculation for
OE-cleanup and untouched ranges using above 3 cases with proper range
charts.

This avoid updating @cur_offset, as it will an extra output for debug
purposes later, and explain the behavior easier.

Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: add mount option for ref_tracker
Leo Martins [Tue, 12 Aug 2025 23:04:41 +0000 (16:04 -0700)]
btrfs: add mount option for ref_tracker

The ref_tracker infrastructure aids debugging but is not enabled by
default as it has a performance impact. Add mount option 'ref_tracker'
so it can be selectively enabled on a filesystem. Currently it track
references of 'delayed inodes'.

Signed-off-by: Leo Martins <loemra.dev@gmail.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: print leaked references in kill_all_delayed_nodes()
Leo Martins [Tue, 12 Aug 2025 23:04:40 +0000 (16:04 -0700)]
btrfs: print leaked references in kill_all_delayed_nodes()

We are seeing soft lockups in kill_all_delayed_nodes due to a
delayed_node with a lingering reference count of 1. Printing at this
point will reveal the guilty stack trace. If the delayed_node has no
references there should be no output.

Signed-off-by: Leo Martins <loemra.dev@gmail.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: implement ref_tracker for delayed_nodes
Leo Martins [Tue, 12 Aug 2025 23:04:39 +0000 (16:04 -0700)]
btrfs: implement ref_tracker for delayed_nodes

Add ref_tracker infrastructure for struct btrfs_delayed_node.

It is a response to the largest btrfs related crash in our fleet.  We're
seeing soft lockups in btrfs_kill_all_delayed_nodes() that seem to be a
result of delayed_nodes not being released properly.

A ref_tracker object is allocated on reference count increases and freed
on reference count decreases. The ref_tracker object stores a stack
trace of where it is allocated. The ref_tracker_dir object is embedded
in btrfs_delayed_node and keeps track of all current and some old/freed
ref_tracker objects. When a leak is detected we can print the stack
traces for all ref_trackers that have not yet been freed.

Here is a common example of taking a reference to a delayed_node and
freeing it with ref_tracker.

    struct btrfs_ref_tracker tracker;
    struct btrfs_delayed_node *node;

    node = btrfs_get_delayed_node(inode, &tracker);
    // use delayed_node...
    btrfs_release_delayed_node(node, &tracker);

There are two special cases where the delayed_node reference is "long
lived", meaning that the thread that takes the reference and the thread
that releases the reference are different. The 'inode_cache_tracker'
tracks the delayed_node stored in btrfs_inode. The 'node_list_tracker'
tracks the delayed_node stored in the btrfs_delayed_root
node_list/prepare_list. These trackers are embedded in the
btrfs_delayed_node.

btrfs_ref_tracker and btrfs_ref_tracker_dir are wrappers that either
compile to the corresponding ref_tracker structs or empty structs
depending on CONFIG_BTRFS_DEBUG. There are also btrfs wrappers for
the ref_tracker API.

Signed-off-by: Leo Martins <loemra.dev@gmail.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: convert several int parameters to bool
David Sterba [Wed, 13 Aug 2025 10:41:11 +0000 (12:41 +0200)]
btrfs: convert several int parameters to bool

We're almost done cleaning misused int/bool parameters. Convert a bunch
of them, found by manual grepping.  Note that btrfs_sync_fs() needs an
int as it's mandated by the struct super_operations prototype.

Reviewed-by: Boris Burkov <boris@bur.io>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: move ref-verify under CONFIG_BTRFS_DEBUG
Leo Martins [Tue, 12 Aug 2025 23:28:27 +0000 (16:28 -0700)]
btrfs: move ref-verify under CONFIG_BTRFS_DEBUG

Remove CONFIG_BTRFS_FS_REF_VERIFY Kconfig and add it as part of
CONFIG_BTRFS_DEBUG. This should not be impactful to the performance
of debug. The struct btrfs_ref takes an additional u64, btrfs_fs_info
takes an additional spinlock_t and rb_root. All of the ref_verify logic
is still protected by a mount option.

Signed-off-by: Leo Martins <loemra.dev@gmail.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: use PTR_ERR_OR_ZERO() to simplify code inbtrfs_control_ioctl()
Xichao Zhao [Tue, 12 Aug 2025 08:25:54 +0000 (16:25 +0800)]
btrfs: use PTR_ERR_OR_ZERO() to simplify code inbtrfs_control_ioctl()

Use the standard error pointer macro to simplify the code.

Reviewed-by: Daniel Vacek <neelx@suse.com>
Signed-off-by: Xichao Zhao <zhao.xichao@vivo.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: simplify support block size check
Qu Wenruo [Sat, 9 Aug 2025 04:40:48 +0000 (14:10 +0930)]
btrfs: simplify support block size check

Currently we manually check the block size against 3 different values:

- 4K
- PAGE_SIZE
- MIN_BLOCKSIZE

Those 3 values can match or differ from each other.  This makes it
pretty complex to output the supported block sizes.

Considering we're going to add block size > page size support soon, this
can make the support block size sysfs attribute much harder to
implement.

To make it easier, factor out a helper, btrfs_supported_blocksize() to
do a simple check for the block size.

Then utilize it in the two locations:

- btrfs_validate_super()
  This is very straightforward

- supported_sectorsizes_show()
  Iterate through all valid block sizes, and only output supported ones.

  This is to make future full range block sizes support much easier.

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>
6 weeks agobtrfs: use blocksize to check if compression is making things larger
Qu Wenruo [Mon, 11 Aug 2025 00:13:42 +0000 (09:43 +0930)]
btrfs: use blocksize to check if compression is making things larger

[BEHAVIOR DIFFERENCE BETWEEN COMPRESSION ALGOS]
Currently LZO compression algorithm will check if we're making the
compressed data larger after compressing more than 2 blocks.

But zlib and zstd do the same checks after compressing more than 8192
bytes.

This is not a big deal, but since we're already supporting larger block
size (e.g. 64K block size if page size is also 64K), this check is not
suitable for all block sizes.

For example, if our page and block size are both 16KiB, and after the
first block compressed using zlib, the resulted compressed data is
slightly  larger than 16KiB, we will immediately abort the compression.

This makes zstd and zlib compression algorithms to behave slightly
different from LZO, which only aborts after compressing two blocks.

[ENHANCEMENT]
To unify the behavior, only abort the compression after compressing at
least two blocks.

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>
6 weeks agobtrfs: pass btrfs_inode pointer directly into btrfs_compress_folios()
Qu Wenruo [Sun, 10 Aug 2025 10:44:57 +0000 (20:14 +0930)]
btrfs: pass btrfs_inode pointer directly into btrfs_compress_folios()

For the 3 supported compression algorithms, two of them (zstd and zlib)
are already grabbing the btrfs inode for error messages.

It's more common to pass btrfs_inode and grab the address space from it.

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>
6 weeks agobtrfs: zoned: refine extent allocator hint selection
Naohiro Aota [Wed, 16 Jul 2025 02:13:15 +0000 (11:13 +0900)]
btrfs: zoned: refine extent allocator hint selection

The hint block group selection in the extent allocator is wrong in the
first place, as it can select the dedicated data relocation block group for
the normal data allocation.

Since we separated the normal data space_info and the data relocation
space_info, we can easily identify a block group is for data relocation or
not. Do not choose it for the normal data allocation.

Reviewed-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
Signed-off-by: Naohiro Aota <naohiro.aota@wdc.com>
Signed-off-by: David Sterba <dsterba@suse.com>
6 weeks agobtrfs: try to search for data csums in commit root
Boris Burkov [Mon, 21 Oct 2024 19:01:53 +0000 (12:01 -0700)]
btrfs: try to search for data csums in commit root

If you run a workload with:

- a cgroup that does tons of parallel data reading, with a working set
  much larger than its memory limit
- a second cgroup that writes relatively fewer files, with overwrites,
  with no memory limit
(see full code listing at the bottom for a reproducer)

Then what quickly occurs is:

- we have a large number of threads trying to read the csum tree
- we have a decent number of threads deleting csums running delayed refs
- we have a large number of threads in direct reclaim and thus high
  memory pressure

The result of this is that we writeback the csum tree repeatedly mid
transaction, to get back the extent_buffer folios for reclaim. As a
result, we repeatedly COW the csum tree for the delayed refs that are
deleting csums. This means repeatedly write locking the higher levels of
the tree.

As a result of this, we achieve an unpleasant priority inversion. We
have:

- a high degree of contention on the csum root node (and other upper
  nodes) eb rwsem
- a memory starved cgroup doing tons of reclaim on CPU.
- many reader threads in the memory starved cgroup "holding" the sem
  as readers, but not scheduling promptly. i.e., task __state == 0, but
  not running on a cpu.
- btrfs_commit_transaction stuck trying to acquire the sem as a writer.
  (running delayed_refs, deleting csums for unreferenced data extents)

This results in arbitrarily long transactions. This then results in
seriously degraded performance for any cgroup using the filesystem (the
victim cgroup in the script).

It isn't an academic problem, as we see this exact problem in production
at Meta with one cgroup over its memory limit ruining btrfs performance
for the whole system, stalling critical system services that depend on
btrfs syncs.

The underlying scheduling "problem" with global rwsems is sort of thorny
and apparently well known and was discussed at LPC 2024, for example.

As a result, our main lever in the short term is just trying to reduce
contention on our various rwsems with an eye to reducing the frequency
of write locking, to avoid disabling the read lock fast acquisition path.

Luckily, it seems likely that many reads are for old extents written
many transactions ago, and that for those we *can* in fact search the
commit root. The commit_root_sem only gets taken write once, near the
end of transaction commit, no matter how much memory pressure there is,
so we have much less contention between readers and writers.

This change detects when we are trying to read an old extent (according
to extent map generation) and then wires that through bio_ctrl to the
btrfs_bio, which unfortunately isn't allocated yet when we have this
information. When we go to lookup the csums in lookup_bio_sums we can
check this condition on the btrfs_bio and do the commit root lookup
accordingly.

Note that a single bio_ctrl might collect a few extent_maps into a single
bio, so it is important to track a maximum generation across all the
extent_maps used for each bio to make an accurate decision on whether it
is valid to look in the commit root. If any extent_map is updated in the
current generation, we can't use the commit root.

To test and reproduce this issue, I used the following script and
accompanying C program (to avoid bottlenecks in constantly forking
thousands of dd processes):

====== big-read.c ======
  #include <fcntl.h>
  #include <stdio.h>
  #include <stdlib.h>
  #include <sys/mman.h>
  #include <sys/stat.h>
  #include <unistd.h>
  #include <errno.h>

  #define BUF_SZ (128 * (1 << 10UL))

  int read_once(int fd, size_t sz) {
   char buf[BUF_SZ];
   size_t rd = 0;
   int ret = 0;

   while (rd < sz) {
   ret = read(fd, buf, BUF_SZ);
   if (ret < 0) {
   if (errno == EINTR)
   continue;
   fprintf(stderr, "read failed: %d\n", errno);
   return -errno;
   } else if (ret == 0) {
   break;
   } else {
   rd += ret;
   }
   }
   return rd;
  }

  int read_loop(char *fname) {
   int fd;
   struct stat st;
   size_t sz = 0;
   int ret;

   while (1) {
   fd = open(fname, O_RDONLY);
   if (fd == -1) {
   perror("open");
   return 1;
   }
   if (!sz) {
   if (!fstat(fd, &st)) {
   sz = st.st_size;
   } else {
   perror("stat");
   return 1;
   }
   }

                  ret = read_once(fd, sz);
   close(fd);
   }
  }

  int main(int argc, char *argv[]) {
   int fd;
   struct stat st;
   off_t sz;
   char *buf;
   int ret;

   if (argc != 2) {
   fprintf(stderr, "Usage: %s <filename>\n", argv[0]);
   return 1;
   }

   return read_loop(argv[1]);
  }

====== repro.sh ======
  #!/usr/bin/env bash

  SCRIPT=$(readlink -f "$0")
  DIR=$(dirname "$SCRIPT")

  dev=$1
  mnt=$2
  shift
  shift

  CG_ROOT=/sys/fs/cgroup
  BAD_CG=$CG_ROOT/bad-nbr
  GOOD_CG=$CG_ROOT/good-nbr
  NR_BIGGOS=1
  NR_LITTLE=10
  NR_VICTIMS=32
  NR_VILLAINS=512

  START_SEC=$(date +%s)

  _elapsed() {
   echo "elapsed: $(($(date +%s) - $START_SEC))"
  }

  _stats() {
   local sysfs=/sys/fs/btrfs/$(findmnt -no UUID $dev)

   echo "================"
   date
   _elapsed
   cat $sysfs/commit_stats
   cat $BAD_CG/memory.pressure
  }

  _setup_cgs() {
   echo "+memory +cpuset" > $CG_ROOT/cgroup.subtree_control
   mkdir -p $GOOD_CG
   mkdir -p $BAD_CG
   echo max > $BAD_CG/memory.max
   # memory.high much less than the working set will cause heavy reclaim
   echo $((1 << 30)) > $BAD_CG/memory.high

   # victims get a subset of villain CPUs
   echo 0 > $GOOD_CG/cpuset.cpus
   echo 0,1,2,3 > $BAD_CG/cpuset.cpus
  }

  _kill_cg() {
   local cg=$1
   local attempts=0
   echo "kill cgroup $cg"
   [ -f $cg/cgroup.procs ] || return
   while true; do
   attempts=$((attempts + 1))
   echo 1 > $cg/cgroup.kill
   sleep 1
   procs=$(wc -l $cg/cgroup.procs | cut -d' ' -f1)
   [ $procs -eq 0 ] && break
   done
   rmdir $cg
   echo "killed cgroup $cg in $attempts attempts"
  }

  _biggo_vol() {
   echo $mnt/biggo_vol.$1
  }

  _biggo_file() {
   echo $(_biggo_vol $1)/biggo
  }

  _subvoled_biggos() {
   total_sz=$((10 << 30))
   per_sz=$((total_sz / $NR_VILLAINS))
   dd_count=$((per_sz >> 20))
   echo "create $NR_VILLAINS subvols with a file of size $per_sz bytes for a total of $total_sz bytes."
   for i in $(seq $NR_VILLAINS)
   do
   btrfs subvol create $(_biggo_vol $i) &>/dev/null
   dd if=/dev/zero of=$(_biggo_file $i) bs=1M count=$dd_count &>/dev/null
   done
   echo "done creating subvols."
  }

  _setup() {
   [ -f .done ] && rm .done
   findmnt -n $dev && exit 1
        if [ -f .re-mkfs ]; then
mkfs.btrfs -f -m single -d single $dev >/dev/null || exit 2
else
echo "touch .re-mkfs to populate the test fs"
fi

   mount -o noatime $dev $mnt || exit 3
   [ -f .re-mkfs ] && _subvoled_biggos
   _setup_cgs
  }

  _my_cleanup() {
   echo "CLEANUP!"
   _kill_cg $BAD_CG
   _kill_cg $GOOD_CG
   sleep 1
   umount $mnt
  }

  _bad_exit() {
   _err "Unexpected Exit! $?"
   _stats
   exit $?
  }

  trap _my_cleanup EXIT
  trap _bad_exit INT TERM

  _setup

  # Use a lot of page cache reading the big file
  _villain() {
   local i=$1
   echo $BASHPID > $BAD_CG/cgroup.procs
   $DIR/big-read $(_biggo_file $i)
  }

  # Hit del_csum a lot by overwriting lots of small new files
  _victim() {
   echo $BASHPID > $GOOD_CG/cgroup.procs
   i=0;
   while (true)
   do
   local tmp=$mnt/tmp.$i

   dd if=/dev/zero of=$tmp bs=4k count=2 >/dev/null 2>&1
   i=$((i+1))
   [ $i -eq $NR_LITTLE ] && i=0
   done
  }

  _one_sync() {
   echo "sync..."
   before=$(date +%s)
   sync
   after=$(date +%s)
   echo "sync done in $((after - before))s"
   _stats
  }

  # sync in a loop
  _sync() {
   echo "start sync loop"
   syncs=0
   echo $BASHPID > $GOOD_CG/cgroup.procs
   while true
   do
   [ -f .done ] && break
   _one_sync
   syncs=$((syncs + 1))
   [ -f .done ] && break
   sleep 10
   done
   if [ $syncs -eq 0 ]; then
   echo "do at least one sync!"
   _one_sync
   fi
   echo "sync loop done."
  }

  _sleep() {
   local time=${1-60}
   local now=$(date +%s)
   local end=$((now + time))
   while [ $now -lt $end ];
   do
   echo "SLEEP: $((end - now))s left. Sleep 10."
   sleep 10
   now=$(date +%s)
   done
  }

  echo "start $NR_VILLAINS villains"
  for i in $(seq $NR_VILLAINS)
  do
   _villain $i &
   disown # get rid of annoying log on kill (done via cgroup anyway)
  done

  echo "start $NR_VICTIMS victims"
  for i in $(seq $NR_VICTIMS)
  do
   _victim &
   disown
  done

  _sync &
  SYNC_PID=$!

  _sleep $1
  _elapsed
  touch .done
  wait $SYNC_PID

  echo "OK"
  exit 0

Without this patch, that reproducer:

- Ran for 6+ minutes instead of 60s
- Hung hundreds of threads in D state on the csum reader lock
- Got a commit stuck for 3 minutes

sync done in 388s
================
Wed Jul  9 09:52:31 PM UTC 2025
elapsed: 420
commits 2
cur_commit_ms 0
last_commit_ms 159446
max_commit_ms 159446
total_commit_ms 160058
some avg10=99.03 avg60=98.97 avg300=75.43 total=418033386
full avg10=82.79 avg60=80.52 avg300=59.45 total=324995274

419 hits state R, D comms big-read
                 btrfs_tree_read_lock_nested
                 btrfs_read_lock_root_node
                 btrfs_search_slot
                 btrfs_lookup_csum
                 btrfs_lookup_bio_sums
                 btrfs_submit_bbio

1 hits state D comms btrfs-transacti
                 btrfs_tree_lock_nested
                 btrfs_lock_root_node
                 btrfs_search_slot
                 btrfs_del_csums
                 __btrfs_run_delayed_refs
                 btrfs_run_delayed_refs

With the patch, the reproducer exits naturally, in 65s, completing a
pretty decent 4 commits, despite heavy memory pressure. Occasionally you
can still trigger a rather long commit (couple seconds) but never one
that is minutes long.

sync done in 3s
================
elapsed: 65
commits 4
cur_commit_ms 0
last_commit_ms 485
max_commit_ms 689
total_commit_ms 2453
some avg10=98.28 avg60=64.54 avg300=19.39 total=64849893
full avg10=74.43 avg60=48.50 avg300=14.53 total=48665168

some random rwalker samples showed the most common stack in reclaim,
rather than the csum tree:
145 hits state R comms bash, sleep, dd, shuf
                 shrink_folio_list
                 shrink_lruvec
                 shrink_node
                 do_try_to_free_pages
                 try_to_free_mem_cgroup_pages
                 reclaim_high

Link: https://lpc.events/event/18/contributions/1883/
Reviewed-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: Boris Burkov <boris@bur.io>
Signed-off-by: David Sterba <dsterba@suse.com>