summaryrefslogtreecommitdiff
AgeCommit message (Collapse)Author
2026-06-08btrfs: tree-checker: add more cross checks for free space treeQu Wenruo
This introduces extra checks using the previous key. If there is a previous key, we can do extra validations: - The previous key is FREE_SPACE_INFO This means the current extent/bitmap should be inside the free space info key range. And matches the type of the free space info. - The previous key is FREE_SPACE_EXTENT or FREE_SPACE_BITMAP In that case both the current and previous key should belong to the same block group. Thus the key type must match, and no overlap between the two keys. These extra checks are inspired by the recently added type checks during free space tree loading. The new tree-checker checks will allow earlier detection, but the loading time checks are still needed, as the tree-checker checks are still inside the same leaf, not matching per-bg level checks. Signed-off-by: Qu Wenruo <wqu@suse.com> Reviewed-by: David Sterba <dsterba@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-08btrfs: tree-checker: ensure free space tree entries won't overflowQu Wenruo
Add an extra check to ensure the free space extent/bitmap and space info keys won't overflow. Signed-off-by: Qu Wenruo <wqu@suse.com> Reviewed-by: David Sterba <dsterba@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-08btrfs: tree-checker: extract the shared key check for free space entriesQu Wenruo
Currently both check_free_space_extent() and check_free_space_bitmap() share a very common validation on the keys. Extract them into a helper, check_free_space_common_key(), and change the output string ("extent" or "bitmap") depending on the key type. Signed-off-by: Qu Wenruo <wqu@suse.com> Reviewed-by: David Sterba <dsterba@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-08btrfs: remove folio ordered flag and subpage bitmapQu Wenruo
Btrfs has an internal flag/subpage bitmap called ordered, which is to indicate that a block has corresponding ordered extent covering it. However this requires extra synchronization between the inode ordered tree, and the folio flag/subpage bitmap, not to mention we need to maintain the extra folio flag with subpage bitmap. As a step to align btrfs_folio_state more closely to iomap_folio_state, remove the btrfs specific ordered flag/bitmap. This will also save us 64 bytes for the bitmap of a huge folio. Since we're here, also update the ASCII graph of the bitmap, as there are only 3 sub-bitmaps now, show all sub-bitmaps directly. Signed-off-by: Qu Wenruo <wqu@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-08btrfs: remove folio_test_ordered() usageQu Wenruo
This involves: - The ASSERT() inside end_bbio_data_write() It's only an ASSERT() and it has never been triggered as far as I know. - btrfs_migrate_folio() Since all folio_test_ordered() usage will be removed, there is no need to copy the folio ordered flag. - The ASSERT() inside btrfs_invalidate_folio() This one has its usefulness as it indeed caught some bugs during development. But that's the last user and will not be worth the folio flag or the subpage bitmap. This will allow btrfs to finally remove the ordered flags. Signed-off-by: Qu Wenruo <wqu@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-08btrfs: use dirty flag to check if an ordered extent needs to be truncatedQu Wenruo
Currently there are only two folio ordered flag users: - extent_writepage_io() To ensure the folio range has an ordered extent covering it. This is from the legacy COW fixup mechanism, which is already removed and only a simple check is left. - btrfs_invalidate_folio() This is to avoid race with end_bbio_data_write(), where btrfs_finish_ordered_extent() will be called to handle the OE finishing. But for btrfs_invalidate_folio() we have already waited for the folio writeback to finish, and locked the folio. This means we can use the dirty flag to check if a range is already submitted or not. If the OE range is not dirty, it means the range has been submitted and its dirty flag was cleared. And since we have already waited for writeback, the endio function will handle the OE finishing. Thus if the range is not dirty, we must skip the range. If the OE range is dirty, it means we have allocated an ordered extent but have not yet submitted the range. And that's exactly the case where we need to truncate the ordered extent. Signed-off-by: Qu Wenruo <wqu@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-08btrfs: unify folio dirty flag clearingQu Wenruo
Currently during folio writeback, we call folio_clear_dirty_for_io() before extent_writepage(), which causes folio dirty flag to be cleared, but without touching the subpage bitmaps. This works fine for the bio submission path, as we always call btrfs_folio_clear_dirty() to clear the subpage bitmap. But this is far from consistent, thus this patch is going to unify the behavior to always use btrfs_folio_clear_dirty() helper to clear both folio flag and subpage bitmap. This involves: - Replace folio_clear_dirty_for_io() with folio_test_dirty() There is only one call site calling folio_clear_dirty_for_io() outside of subpage.c, that's inside extent_write_cache_pages() just before extent_writepage(). - Make btrfs_invalidate_folio() clear dirty range for the whole folio The function btrfs_invalidate_folio() is also called during extent_writepage(). If we had a folio completely beyond isize, we call folio_invalidate() -> btrfs_invalidate_folio() to free the folio. Since we no longer have folio_clear_dirty_for_io() to clear the folio dirty flag, we must manually clear the folio dirty flag for the to-be-invalidated folio, and also clear the PAGECACHE_TAG_DIRTY tag. The tag clearing is done using a new helper, btrfs_clear_folio_dirty_tag(), which is almost the same as the old btree_clear_folio_dirty_tag(), but with minor improvements including: * Remove the folio_test_dirty() check We have already done an ASSERT(). * Add an ASSERT() to make sure folio is mapped - Add extra ASSERT()s before clearing folio private During development I hit dirty folios without the private flag set, and that caused a lot of ASSERT()s. The reason is that btrfs_invalidate_folio() is relying on the dirty flag being cleared when it's called from extent_writepage(). Add extra ASSERT()s inside clear_folio_extent_mapped() to catch wild dirty/writeback flags. Signed-off-by: Qu Wenruo <wqu@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-08btrfs: detect dirty blocks without an ordered extent more reliablyQu Wenruo
Currently btrfs detects dirty folio which doesn't have an ordered extent at extent_writepage_io(), but that is not ideal: - The check is not handling all dirty blocks We can have multiple blocks inside a large folio, but the whole folio is marked ordered as long as there is one ordered extent in the range. We can still hit cases where some dirty blocks do not have corresponding ordered extents. Instead of checking the folio ordered flags, do the check at alloc_new_bio(), where we're already searching for ordered extents for writebacks. If we didn't find an ordered extent, we should already give an error message and notify the caller there is something wrong. This allows us to check every block that goes through submit_extent_folio(). With this new and more reliable check, we can remove the old check. Signed-off-by: Qu Wenruo <wqu@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-08btrfs: remove locked subpage bitmapQu Wenruo
Currently there are two members inside btrfs_folio_state that are related to locked bitmap: - locked sub-bitmap inside btrfs_folio_state::bitmaps[] The enum btrfs_bitmap_nr_locked determines the sub-bitmap. - btrfs_folio_state::nr_locked Which records how many blocks are locked inside the folio. The locked sub-bitmap is a btrfs specific per-block tracking mechanism, which is mostly for async-submission, utilized by compressed writes. The sub-bitmap itself is a super set of nr_locked, as it can provide a more reliable tracking. But the sub-bitmap itself can be pretty large for the incoming huge folio, 2M sized folio for 4K page size, meaning 512 bits for one sub-bitmap. Furthermore, in the long run compression will be reworked to get rid of async-submission completely, there is not much need for a full sub-bitmap to track the locked status. This patch removes the locked sub-bitmap and only relies on @nr_locked atomic to do the tracking. This can also save 64 bytes from btrfs_folio_state::bitmaps[] for a huge folio. This will reduce some safety checks, as previously if a block is not locked, btrfs_folio_end_lock()/btrfs_folio_end_lock_bitmap() will find out that, and skip reducing @nr_locked for that block, and avoid under-flow. But this safety net itself shouldn't be necessary in the first place. If we're unlocking a block that is not locked, it's a bug in the logic, and we should catch it, not silently ignoring it. Thus I believe the removal of the extra safety net should not be a problem. Reviewed-by: David Sterba <dsterba@suse.com> Signed-off-by: Qu Wenruo <wqu@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-08btrfs: tree-checker: validate names in ROOT_REF and ROOT_BACKREFZhang Cen
ROOT_REF and ROOT_BACKREF items contain a struct btrfs_root_ref followed by the subvolume name. Several readers assume that this layout is already valid and then use the on-disk name length directly. A corrupted item can therefore make those readers address bytes outside the item, and BTRFS_IOC_GET_SUBVOL_INFO can copy too many bytes into its fixed-size UAPI name buffer. Validate ROOT_REF and ROOT_BACKREF items in tree-checker before any reader uses them. Reject records that do not contain a non-empty name, whose name_len does not exactly describe the remaining item payload, or whose name exceeds BTRFS_NAME_LEN. For BTRFS_IOC_GET_SUBVOL_INFO, copy only the validated on-disk name_len instead of deriving the copy length from the item size. The ioctl result is zeroed when allocated. That leaves the existing trailing zero byte untouched. Reviewed-by: Qu Wenruo <wqu@suse.com> Signed-off-by: Zhang Cen <rollkingzzc@gmail.com> Reviewed-by: David Sterba <dsterba@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-08btrfs: free-space-tree: reject mismatched extent and bitmap itemsZhang Cen
btrfs_load_free_space_tree() reads FREE_SPACE_INFO once and then chooses the bitmap or extent loader for all following free-space records until the next FREE_SPACE_INFO item. Those loaders currently enforce the selected record type only with ASSERT(). On production builds without CONFIG_BTRFS_ASSERT, a malformed free-space tree can therefore be decoded in the wrong mode. An EXTENT item can reach btrfs_free_space_test_bit() as bitmap data, while a BITMAP item can be added as a full free extent. The latter corrupts the in-memory free-space cache and the former can read beyond the item payload. Sanitizer validation reported: general protection fault Call trace: assert_eb_folio_uptodate() (fs/btrfs/extent_io.c:4134) extent_buffer_test_bit() (?:?) btrfs_free_space_test_bit() (fs/btrfs/free-space-tree.c:518) srso_alias_return_thunk() (arch/x86/include/asm/nospec-branch.h:375) __entry_text_end() (?:?) __asan_memcpy() (mm/kasan/shadow.c:103) read_extent_buffer() (?:?) load_free_space_bitmaps() (fs/btrfs/free-space-tree.c:1548) btrfs_get_32() (fs/btrfs/free-space-tree.c:?) btrfs_set_16() (fs/btrfs/free-space-tree.c:?) kmem_cache_alloc_noprof() (?:?) btrfs_load_free_space_tree() (fs/btrfs/free-space-tree.c:1685) load_free_space_tree_for_test() (?:?) rcu_disable_urgency_upon_qs() (kernel/rcu/tree.c:721) vprintk_emit() (?:?) __up_write() (kernel/locking/rwsem.c:1401) clone_commit_root_for_test() (?:?) test_extent_as_bitmap_mode_mismatch() (?:?) kmem_cache_free() (?:?) btrfs_free_path() (fs/btrfs/free-space-tree.c:1449) __add_block_group_free_space() (fs/btrfs/free-space-tree.c:20) run_test() (?:?) do_raw_spin_unlock() (?:?) btrfs_test_free_space_tree() (fs/btrfs/tests/free-space-tree-tests.c:547) btrfs_test_qgroups() (fs/btrfs/tests/qgroup-tests.c:462) btrfs_run_sanity_tests() (fs/btrfs/free-space-tree.c:?) init_btrfs_fs() (fs/btrfs/super.c:2690) do_one_initcall() (init/main.c:1382) __kasan_kmalloc() (?:?) rcu_is_watching() (?:?) do_initcalls() (init/main.c:1457) kernel_init_freeable() (init/main.c:1674) kernel_init() (init/main.c:1584) ret_from_fork() (?:?) __switch_to() (?:?) ret_from_fork_asm() (?:?) Validate every post-info key before decoding it. Reject keys whose type does not match the mode selected by FREE_SPACE_INFO, and reject keys whose range extends past the block group, returning -EUCLEAN instead of feeding the wrong record type to the bitmap or extent decoder. Also reject zero-length FREE_SPACE_EXTENT items in tree-checker, matching the existing FREE_SPACE_BITMAP zero-length check. This keeps the loader range check simple and prevents a zero-length extent item from being a valid on-disk free-space record. Reviewed-by: Qu Wenruo <wqu@suse.com> Signed-off-by: Zhang Cen <rollkingzzc@gmail.com> Reviewed-by: David Sterba <dsterba@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-08btrfs: use on stack backref iterator in build_backref_tree()David Sterba
The iterator is used only once and within build_backref_tree() so we can avoid one allocation and place it on stack. Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-08btrfs: remove fs_info from struct btrfs_backref_iterDavid Sterba
The fs_info is available everywhere and we don't need to store it inside a structure that is used within one function only, which is build_backref_tree(). The size of btrfs_backref_iter is now 48 bytes. Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-08btrfs: simplify the btree folio wait during invalidationQu Wenruo
The btree inode is very different from regular data inodes, as the btree inode is never exposed to user space operations. All operations are either initiated by btrfs metadata operations, or MM layer like memory pressure to release folios. This means we never need to handle partial folio invalidation inside btree_invalidate_folio(). With that said, we can slightly simplify the btree folio invalidation by: - Add ASSERT()s to make sure the range covers the whole folio - Remove "if (start > end)" check As the range always covers the full folio, that check is always false and can be removed. - Open code extent_invalidate_folio() Signed-off-by: Qu Wenruo <wqu@suse.com> Reviewed-by: David Sterba <dsterba@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-08btrfs: unexport and move extent_invalidate_folio()Qu Wenruo
The function extent_invalidate_folio() has only a single caller inside btree_invalidate_folio(). There is no need to export such a function just for a single caller inside another file. Unexport extent_invalidate_folio() and move it to disk-io.c. And since we're moving the code, update the commit to match the current style, and remove the seemingly stale comment on the extent state removal, it's better explained by the comment just before btrfs_unlock_extent(). Signed-off-by: Qu Wenruo <wqu@suse.com> Reviewed-by: David Sterba <dsterba@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-08btrfs: optimize fill_holes() to merge a new hole with both adjacent itemsDave Chen
fill_holes() currently merges a punched hole with either the previous or the next file extent item, but never both in the same call. When holes are punched in a non-sequential order this leaves consecutive hole items in the inode's subvolume tree that should have been collapsed into a single one. This is a minor metadata optimization that reduces the number of file extent items when holes are punched in non-sequential order. While having extra file extent items is harmless and has no functional impact, reducing metadata overhead can benefit workloads with heavily fragmented hole patterns. For example: fallocate -p -o 4K -l 4K ${FILE} fallocate -p -o 12K -l 4K ${FILE} fallocate -p -o 8K -l 4K ${FILE} After the third punch the [4K, 8K) and [12K, 16K) holes become adjacent to the new [8K, 12K) hole, but fill_holes() merges only one side and leaves two separate hole items ([4K, 12K) and [12K, 16K)) instead of the expected single [4K, 16K) hole item. Fix this by checking both path->slots[0] - 1 and path->slots[0] in one pass: - If only the previous slot is mergeable, extend it forward as before. - If only the next slot is mergeable, extend it backward and update its key offset as before. - If both are mergeable, extend the previous item to cover the new hole plus the next item, and remove the redundant next item with btrfs_del_items(). Because the merge path may now delete an item, switch the initial btrfs_search_slot() call from a plain lookup (ins_len = 0) to a search-for-deletion (ins_len = -1), so the leaf is prepared for a possible item removal. Note: This optimization only applies to filesystems without the NO_HOLES feature enabled. Since NO_HOLES is now the default, this primarily benefits older filesystems or those explicitly created with NO_HOLES disabled. Signed-off-by: Dave Chen <davechen@synology.com> Reviewed-by: Filipe Manana <fdmanana@suse.com> Signed-off-by: Filipe Manana <fdmanana@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-08btrfs: warn about extent buffer that can not be releasedQu Wenruo
When we unmount the fs or during mount failures, btrfs will call invalidate_inode_pages() to release all btree inode folios. However that function can return -EBUSY if any folios can not be invalidated. This can be caused by: - Some extent buffers are still held by btrfs This is a logic error, as we should release all tree root nodes during unmount and mount failure handling. - Some extent buffers are under readahead and haven't yet finished These are much rarer but valid cases. In that case we should wait for those extent buffers. Introduce a new helper invalidate_and_check_btree_folios() which will: - Call invalidate_inode_pages2() and catch its return value If it returned 0 as expected, that's great and we can call it a day. - Otherwise go through each extent buffer in buffer_tree Increase the ref by one first for the eb we're checking. This is to ensure the eb won't be freed after the readahead is finished. For ebs that still have EXTENT_BUFFER_READING flag, wait for them to finish first. After waiting for the readahead, check the refs of the eb and if it's still dirty. If the eb ref count is greater than 2 (one for the buffer tree, one held by us), it means we are still holding the extent buffer somewhere else, which is a code bug. If the eb is still dirty, it means a bug in transaction handling, e.g. the bug fixed by patch "btrfs: only release the dirty pages io tree after successful writes". For either case, show a warning message about the eb, including its bytenr, owner, refs and flags. And if it's a debug build, also trigger WARN_ON_ONCE() so that fstests can properly catch such situation. Link: https://bugzilla.kernel.org/show_bug.cgi?id=221270 Reported-by: AHN SEOK-YOUNG <iamsyahn@gmail.com> CC: Teng Liu <27rabbitlt@gmail.com> Tested-by: Teng Liu <27rabbitlt@gmail.com> Reviewed-by: Filipe Manana <fdmanana@suse.com> Signed-off-by: Qu Wenruo <wqu@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-08btrfs: make sure report_eb_range() is not inlinedFilipe Manana
If report_rb_range() is inlined into its single caller (check_eb_range()), we end up with a larger module size, which is undesirable and does not provide any advantage since this code is for a cold path which we don't expect to ever hit. Add the noinline attribute to report_rb_range() and while at it also make it return void as it always returns true. Before this change (with gcc 14.2.0-19 from Debian): $ size fs/btrfs/btrfs.ko text data bss dec hex filename 2018267 176232 15592 2210091 21b92b fs/btrfs/btrfs.ko After this change: $ size fs/btrfs/btrfs.ko text data bss dec hex filename 2017835 176048 15592 2209475 21b6c3 fs/btrfs/btrfs.ko Also, replacing the noinline with __cold, yields slighty worse results: $ size fs/btrfs/btrfs.ko text data bss dec hex filename 2017889 176048 15592 2209529 21b6f9 fs/btrfs/btrfs.ko Signed-off-by: Filipe Manana <fdmanana@suse.com> Reviewed-by: David Sterba <dsterba@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-08btrfs: move transaction abort message to __btrfs_abort_transaction()David Sterba
The btrfs_abort_transaction() is called at the location where we want to report the abort. It must be a macro so we get the correct line and stack trace. This inlines the necessary code and the rest is pushed to __btrfs_abort_transaction(). There's a possibility to reduce the inlined code if we move the message to the helper function as well, without loss of information. The difference is only that the WARN will not print it inside the stack report but after: --[ cut here ]-- WARNING: fs/btrfs/transaction.c:2045 at btrfs_commit_transaction+0xa21/0xd30 [btrfs], CPU#11: bonnie++/3377975 ... --[ end trace ] -- BTRFS error (device dm-0 state A): Transaction aborted (error -28) While previously there would be one more line like: --[ cut here ]-- BTRFS: Transaction aborted (error -28) WARNING: fs/btrfs/transaction.c:2045 at btrfs_commit_transaction+0xa21/0xd30 [btrfs], CPU#11: bonnie++/3377975 ... --[ end trace ] -- This removes about 20KiB of btrfs.ko on a release config. Reviewed-by: Filipe Manana <fdmanana@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-08btrfs: don't force DIO writes to be serializedMark Harmstone
Before btrfs switched to the new mount API in 2023, we were setting SB_NOSEC in btrfs_mount_root(). This flag tells the VFS that the filesystem may have files which don't have security xattrs, enabling it to do some optimizations. Unfortunately this was missed in the transition, meaning that IS_NOSEC will always return false for a btrfs inode. This means that btrfs_direct_write() calls will always get the inode lock exclusively, meaning that DIO writes to the same file will be serialized. On my machine, this one-line change results in a ~59% improvement in DIO throughput: Before patch: test: (g=0): rw=randwrite, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=io_uring, iodepth=64 ... fio-3.39 Starting 32 processes test: Laying out IO file (1 file / 1024MiB) Jobs: 32 (f=32): [w(32)][100.0%][w=764MiB/s][w=195k IOPS][eta 00m:00s] test: (groupid=0, jobs=32): err= 0: pid=586: Wed Apr 22 13:03:04 2026 write: IOPS=202k, BW=787MiB/s (826MB/s)(46.1GiB/60012msec); 0 zone resets bw ( KiB/s): min=498714, max=1199892, per=100.00%, avg=806659.03, stdev=4229.94, samples=3808 iops : min=124677, max=299971, avg=201661.82, stdev=1057.49, samples=3808 cpu : usr=0.32%, sys=1.27%, ctx=8329204, majf=0, minf=1163 IO depths : 1=0.0%, 2=0.0%, 4=0.0%, 8=0.0%, 16=0.0%, 32=0.0%, >=64=100.0% submit : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0% complete : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.1%, >=64=0.0% issued rwts: total=0,12094328,0,0 short=0,0,0,0 dropped=0,0,0,0 latency : target=0, window=0, percentile=100.00%, depth=64 Run status group 0 (all jobs): WRITE: bw=787MiB/s (826MB/s), 787MiB/s-787MiB/s (826MB/s-826MB/s), io=46.1GiB (49.5GB), run=60012-60012msec After patch: test: (g=0): rw=randwrite, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=io_uring, iodepth=64 ... fio-3.39 Starting 32 processes test: Laying out IO file (1 file / 1024MiB) Jobs: 32 (f=32): [w(32)][100.0%][w=1255MiB/s][w=321k IOPS][eta 00m:00s] test: (groupid=0, jobs=32): err= 0: pid=572: Wed Apr 22 13:13:46 2026 write: IOPS=320k, BW=1250MiB/s (1311MB/s)(73.3GiB/60003msec); 0 zone resets bw ( MiB/s): min= 619, max= 2289, per=100.00%, avg=1251.28, stdev= 9.64, samples=3808 iops : min=158538, max=586025, avg=320320.80, stdev=2468.97, samples=3808 cpu : usr=0.35%, sys=11.50%, ctx=1584847, majf=0, minf=1160 IO depths : 1=0.0%, 2=0.0%, 4=0.0%, 8=0.0%, 16=0.0%, 32=0.0%, >=64=100.0% submit : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0% complete : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.1%, >=64=0.0% issued rwts: total=0,19203309,0,0 short=0,0,0,0 dropped=0,0,0,0 latency : target=0, window=0, percentile=100.00%, depth=64 Run status group 0 (all jobs): WRITE: bw=1250MiB/s (1311MB/s), 1250MiB/s-1250MiB/s (1311MB/s-1311MB/s), io=73.3GiB (78.7GB), run=60003-60003msec The script to reproduce that: #!/bin/bash mkfs.btrfs -f /dev/nvme0n1 mount /dev/nvme0n1 /mnt/test mkdir /mnt/test/nocow chattr +C /mnt/test/nocow fio /root/test.fio # cat /root/test.fio [global] rw=randwrite ioengine=io_uring iodepth=64 size=1g direct=1 startdelay=20 force_async=4 ramp_time=5 runtime=60 group_reporting=1 numjobs=32 time_based disk_util=0 clat_percentiles=0 disable_lat=1 disable_clat=1 disable_slat=1 filename=/mnt/test/nocow/fiofile [test] name=test bs=4k stonewall This was on a VM with 8 cores and 8GB of RAM, with a real NVMe exposed through PCI passthrough. The figures for XFS and ext4 in comparison are both about ~3GB/s. Fixes: ad21f15b0f79 ("btrfs: switch to the new mount API") Signed-off-by: Mark Harmstone <mark@harmstone.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-08btrfs: move large data folios out of experimental featuresQu Wenruo
This feature was introduced in v6.17 under experimental, and we had several small bugs related to or exposed by that: e9e3b22ddfa7 ("btrfs: fix beyond-EOF write handling") 18de34daa7c6 ("btrfs: truncate ordered extent when skipping writeback past i_size") Otherwise, the feature has been frequently tested by btrfs developers. The latest fix only arrived in v6.19. After three releases, I think it's time to move this feature out of experimental. And since we're here, also remove the comment about the bitmap size limit, which is no longer relevant in the context. It will soon be outdated for the incoming huge folio support. Reviewed-by: Neal Gompa <neal@gompa.dev> Signed-off-by: Qu Wenruo <wqu@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-08btrfs: refresh add_ra_bio_pages() to indicate it's using foliosQu Wenruo
The function add_ra_bio_folios() has been utilizing folio interfaces since c808c1dcb1b2 ("btrfs: convert add_ra_bio_pages() to use only folios"), but we are still referring to "pages" inside the function name and all comments. Furthermore, such folio/page mixing can even be confusing, e.g. the variable @page_end is very confusing as we're not really referring to the end of the page, but the end of the folio, especially when we already have large folio support. Enhance that function by: - Rename "page" to "folio" to avoid confusion - Skip to the folio end if there is already a folio in the page cache The existing skip is: cur += folio_size(folio); This is incorrect if @cur is not folio size aligned, and can be common with large folio support. Thankfully this is not going to cause any real bugs, but at most will skip some blocks that can be added to readahead. Signed-off-by: Qu Wenruo <wqu@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-08btrfs: enable cross-folio readahead for bs < ps and large folio casesQu Wenruo
[BACKGROUND] When bs < ps support was initially introduced, the compressed data readahead was disabled as at that time the target page size was 64K. This means a compressed data extent can span at most 3 64K pages (the head and tail parts are not aligned to 64K), meaning the benefit is pretty minimal. [UNEXPECTED WORKING SITUATION] But with the already merged large folio support, we're already enabling readahead with subpage routine unintentionally, e.g.: 0 4K 8K 12K 16K | Folio 0 | Folio 8K | |<----- Compressed data ------->| We have 2 8K sized folios, all backed by a single compressed data. In that case add_ra_bio_pages() will continue to add folio 8K into the read bio, as the condition to skip is only (bs < ps), not taking the newer large folio support into consideration at all. So for folio 8K, it is added to the read bio, but without subpage lock bitmap populated. Then at end_bbio_data_read(), folio 0 has proper locked bitmap set, but folio 8K does not. This inconsistency is handled by the extra safety net at btrfs_subpage_end_and_test_lock() where if a folio has no @nr_locked, it will just be unlocked without touching the locked bitmap. [ENHANCEMENT] Make add_ra_bio_pages() support bs < ps and large folio cases, by removing the check and calling btrfs_folio_set_lock() unconditionally. This won't make any difference on 4K page sized systems with large folios, as the readahead is already working, although unexpectedly. But this will enable true compressed data readahead for bs < ps cases properly. Please note that such readahead will only work if the compressed extent is crossing folio boundaries, which is also the existing limitation. Signed-off-by: Qu Wenruo <wqu@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-08btrfs: remove 32bit compat code for VFS inode numberDavid Sterba
Commit 0b2600f81cefcd ("treewide: change inode->i_ino from unsigned long to u64") sets the inode number type to u64 unconditionally, so we can use it directly as there's no difference on 32bit and 64bit platform. We used to have a copy of the number in our btrfs_inode. The size of btrfs_inode on 32bit platform is about 688 bytes (after the change). Reviewed-by: Qu Wenruo <wqu@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-08btrfs: limit size of bios submitted from writebackJan Kara
Currently btrfs_writepages() just accumulates as large bio as possible (within writeback_control constraints) and then submits it. This can however lead to significant latency in writeback IO submission (I have observed tens of milliseconds) because the submitted bio easily has over hundred of megabytes. Consequently this leads to IO pipeline stalls and reduced throughput. At the same time beyond certain size submitting so large bio provides diminishing returns because the bio is split by the block layer immediately anyway. So compute (estimate of) bio size beyond which we are unlikely to improve performance and just submit the bio for writeback once we accumulate that much to keep the IO pipeline busy. This improves writeback throughput for sequential writes by about 15% on the test machine I was using. Reviewed-by: Qu Wenruo <wqu@suse.com> Signed-off-by: Jan Kara <jack@suse.cz> [ Fix the handling of missing device to avoid NULL pointer dereference. ] Signed-off-by: Qu Wenruo <wqu@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-08btrfs: remove 2K block size supportQu Wenruo
Originally 2K block size support was introduced to test subpage (block size < page size) on x86_64 where the page size is exactly the original minimal block size. However that 2K block size support has some problems: - No 2K nodesize support This is critical, as there is still no way to exercise the subpage metadata routine. - Very easy to test subpage data path now With the currently experimental large folio support, it's very easy to test the subpage data folio path already, as when a folio larger than 4K is encountered on x86_64, we will need all the subpage folio states and bitmaps. So there is no need to use 2K block size just to verify subpage data path even on x86_64. And with the incoming huge folio (2M on x86_64) support, the 2K block size will easily double the bitmap size, considering the burden to maintain and the limited extra coverage, I believe it's time to remove it for the incoming huge folio support. Signed-off-by: Qu Wenruo <wqu@suse.com> Reviewed-by: David Sterba <dsterba@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-08btrfs: change return type from int to bool in check_eb_range()Filipe Manana
The function always returns true or false but the its return type is defined as int, which makes no sense. Change it to bool. 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>
2026-06-08btrfs: add missing unlikely to if branches leading to a DEBUG_WARN()Filipe Manana
If statement branches that lead to a DEBUG_WARN() are unexpected to happen and in most places we surround their expressions with the unlikely tag, however a few places are missing. Add the unlikely tag to those missing places to make it explicit to a reader that it's not expected and to hint the compiler to 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>
2026-06-08btrfs: use QSTR() in __btrfs_ioctl_snap_create()Thorsten Blum
Drop the length argument and use the simpler QSTR(). Signed-off-by: Thorsten Blum <thorsten.blum@linux.dev> Reviewed-by: David Sterba <dsterba@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-08btrfs: use the enums instead of int type in struct btrfs_block_group fieldsFilipe Manana
The 'disk_cache_state' and 'cached' fields are defined with an int type but all the values we assigned to them come from the enums btrfs_disk_cache_state and btrfs_caching_type. So change the type in the btrfs_block_group structure from int to these enums - in practice an enum is an int, so this is more for readability and clarity. Reviewed-by: Qu Wenruo <wqu@suse.com> Reviewed-by: Sun YangKai <sunk67188@gmail.com> Signed-off-by: Filipe Manana <fdmanana@suse.com> Reviewed-by: David Sterba <dsterba@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-08btrfs: use min_size variable to setup block rsv in btrfs_replace_file_extents()Filipe Manana
There's no need to calculate again the size for the temporary block reserve in btrfs_replace_file_extents() - we have already calculated it and stored it in the 'min_size' variable. So use the variable to make it more clear and also make the variable const since it's not supposed to change during the whole function. 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>
2026-06-08btrfs: balance: fix potential bg lookup failure in btrfs_may_alloc_data_chunk()ZhengYuan Huang
[BUG] Running btrfs balance can trigger a null-ptr-deref before relocating a data chunk when metadata corruption leaves a chunk in the chunk tree without a corresponding block group in the in-memory cache: KASAN: null-ptr-deref in range [0x0000000000000088-0x000000000000008f] RIP: 0010:btrfs_may_alloc_data_chunk+0x40/0x1c0 fs/btrfs/volumes.c:3601 Call Trace: __btrfs_balance fs/btrfs/volumes.c:4217 [inline] btrfs_balance+0x2516/0x42b0 fs/btrfs/volumes.c:4604 btrfs_ioctl_balance fs/btrfs/ioctl.c:3577 [inline] btrfs_ioctl+0x25cf/0x5b90 fs/btrfs/ioctl.c:5313 ... [CAUSE] __btrfs_balance() iterates the on-disk chunk tree and passes the chunk logical bytenr to btrfs_may_alloc_data_chunk() before relocating a data chunk. That helper then queries the in-memory block group cache: cache = btrfs_lookup_block_group(fs_info, chunk_offset); chunk_type = cache->flags; /* cache may be NULL */ A corrupt image can contain a chunk item whose matching block group item is missing, so no block group is ever inserted into the cache. In that case btrfs_lookup_block_group() returns NULL. The code only guards this with ASSERT(cache), which becomes a no-op when CONFIG_BTRFS_ASSERT is disabled. The subsequent dereference of cache->flags therefore crashes the kernel. [FIX] Add a NULL check after btrfs_lookup_block_group() in btrfs_may_alloc_data_chunk() and print and error message for clarity. Signed-off-by: ZhengYuan Huang <gality369@gmail.com> Reviewed-by: David Sterba <dsterba@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-08btrfs: balance: fix potential bg lookup failure in chunk_usage_range_filter()ZhengYuan Huang
[BUG] Running btrfs balance with a usage range filter (-dusage=min..max) can trigger a null-ptr-deref when metadata corruption causes a chunk to have no corresponding block group in the in-memory cache: KASAN: null-ptr-deref in range [0x0000000000000070-0x0000000000000077] RIP: 0010:chunk_usage_range_filter fs/btrfs/volumes.c:3845 [inline] RIP: 0010:should_balance_chunk fs/btrfs/volumes.c:4031 [inline] RIP: 0010:__btrfs_balance fs/btrfs/volumes.c:4182 [inline] RIP: 0010:btrfs_balance+0x249e/0x4320 fs/btrfs/volumes.c:4618 ... Call Trace: btrfs_ioctl_balance fs/btrfs/ioctl.c:3577 [inline] btrfs_ioctl+0x25cf/0x5b90 fs/btrfs/ioctl.c:5313 vfs_ioctl fs/ioctl.c:51 [inline] ... The bug is reproducible on recent development branch. [CAUSE] Two separate data structures are involved: 1. The on-disk chunk tree, which records every chunk (logical address space region) and is iterated by __btrfs_balance(). 2. The in-memory block group cache (fs_info->block_group_cache_tree), which is built at mount time by btrfs_read_block_groups() and holds a struct btrfs_block_group for each chunk. This cache is what the usage range filter queries. On a well-formed filesystem, these two are kept in 1:1 correspondence. However, btrfs_read_block_groups() builds the cache from block group items in the extent tree, not directly from the chunk tree. A corrupted image can therefore contain a chunk item in the chunk tree whose corresponding block group item is absent from the extent tree; that chunk's block group is then never inserted into the in-memory cache. When balance iterates the chunk tree and reaches such an orphaned chunk, should_balance_chunk() calls chunk_usage_range_filter(), which queries the block group cache: cache = btrfs_lookup_block_group(fs_info, chunk_offset); chunk_used = cache->used; /* cache may be NULL */ btrfs_lookup_block_group() returns NULL silently when no cached entry covers chunk_offset. chunk_usage_range_filter() does not check the return value, so the immediately following dereference of cache->used triggers the crash. [FIX] Add a NULL check after btrfs_lookup_block_group() in chunk_usage_range_filter(). When the lookup fails, emit a btrfs_err() message identifying the affected bytenr and return -EUCLEAN to indicate filesystem corruption. Since chunk_usage_range_filter() now has an error path, change its return type from bool to error pointer, return 0 if the chunk matches the usage range, and 1 if it should be filtered out. Signed-off-by: ZhengYuan Huang <gality369@gmail.com> Reviewed-by: David Sterba <dsterba@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-08btrfs: balance: fix potential bg lookup failure in chunk_usage_filter()ZhengYuan Huang
[BUG] Running btrfs balance with a usage filter (-dusage=N) can trigger a null-ptr-deref when metadata corruption causes a chunk to have no corresponding block group in the in-memory cache: KASAN: null-ptr-deref in range [0x0000000000000070-0x0000000000000077] RIP: 0010:chunk_usage_filter fs/btrfs/volumes.c:3874 [inline] RIP: 0010:should_balance_chunk fs/btrfs/volumes.c:4018 [inline] RIP: 0010:__btrfs_balance fs/btrfs/volumes.c:4172 [inline] RIP: 0010:btrfs_balance+0x2024/0x42b0 fs/btrfs/volumes.c:4604 ... Call Trace: btrfs_ioctl_balance fs/btrfs/ioctl.c:3577 [inline] btrfs_ioctl+0x25cf/0x5b90 fs/btrfs/ioctl.c:5313 vfs_ioctl fs/ioctl.c:51 [inline] ... The bug is reproducible on current development branch. [CAUSE] Two separate data structures are involved: 1. The on-disk chunk tree, which records every chunk (logical address space region) and is iterated by __btrfs_balance(). 2. The in-memory block group cache (fs_info->block_group_cache_tree), which is built at mount time by btrfs_read_block_groups() and holds a struct btrfs_block_group for each chunk. This cache is what the usage filter queries. On a well-formed filesystem, these two are kept in 1:1 correspondence. However, btrfs_read_block_groups() builds the cache from block group items in the extent tree, not directly from the chunk tree. A corrupted image can therefore contain a chunk item in the chunk tree whose corresponding block group item is absent from the extent tree; that chunk's block group is then never inserted into the in-memory cache. When balance iterates the chunk tree and reaches such an orphaned chunk, should_balance_chunk() calls chunk_usage_filter(), which queries the block group cache: cache = btrfs_lookup_block_group(fs_info, chunk_offset); chunk_used = cache->used; /* cache may be NULL */ btrfs_lookup_block_group() returns NULL silently when no cached entry covers chunk_offset. chunk_usage_filter() does not check the return value, so the immediately following dereference of cache->used triggers the crash. [FIX] Add a NULL check after btrfs_lookup_block_group() in chunk_usage_filter(). When the lookup fails, emit a btrfs_err() message identifying the affected bytenr and return -EUCLEAN to indicate filesystem corruption. Since chunk_usage_filter() now has an error path, change its return type from bool to error pointer and 0 if the chunk passes the usage filter, and 1 if it should be skipped. Update should_balance_chunk() accordingly to propagate negative errors from the usage filter. Signed-off-by: ZhengYuan Huang <gality369@gmail.com> Reviewed-by: David Sterba <dsterba@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-08btrfs: add ioctl GET_CSUMS to read raw checksums from file rangeMark Harmstone
Add a new unprivileged BTRFS_IOC_GET_CSUMS ioctl, which can be used to query the on-disk csums for a file range. The ioctl is deliberately per-file rather than exposing raw csum tree lookups, to avoid leaking information to users about files they may not have access to. This is done by userspace passing a struct btrfs_ioctl_get_csums_args to the kernel, which details the offset and length we're interested in, and a buffer for the kernel to write its results into. The kernel writes a struct btrfs_ioctl_get_csums_entry into the buffer, followed by the csums if available. The maximum size of the user buffer is capped to 16MiB. If the extent is an uncompressed, non-NODATASUM extent, the kernel sets the entry type to BTRFS_GET_CSUMS_HAS_CSUMS and follows it with the csums. If it is sparse, preallocated, or beyond the EOF, it sets the type to BTRFS_GET_CSUMS_ZEROED - this is so userspace knows it can use the precomputed hash of the zero sector. Otherwise, it sets the type to BTRFS_GET_CSUMS_NODATASUM, BTRFS_GET_CSUMS_COMPRESSED, BTRFS_GET_CSUM_ENCRYPTED, or BTRFS_GET_CSUM_INLINE. For example, a file with a [0, 4K) hole and [4K, 12K) data extent would produce the following output buffer: | [0, 4K) ZEROED | [4K, 12K) HAS_CSUMS | csum data | We do store the csums of compressed extents, but we deliberately don't return them here: they're calculated over the compressed data, not the uncompressed data that's returned to userspace. Similarly for encrypted data, once encryption is supported, in which the csums will be on the ciphertext. The main use case for this is for speeding up mkfs.btrfs --rootdir. For the case when the source FS is btrfs and using the same csum algorithm, we can avoid having to recalculate the csums - in my synthetic benchmarks (16GB file on a spinning-rust drive), this resulted in a ~11% speed-up (218s to 196s). When using the --reflink option added in btrfs-progs v6.16.1, we can forgo reading the data entirely, resulting a ~2200% speed-up on the same test (128s to 6s). # mkdir rootdir # dd if=/dev/urandom of=rootdir/file bs=4096 count=4194304 (without ioctl) # echo 3 > /proc/sys/vm/drop_caches # time mkfs.btrfs --rootdir rootdir testimg ... real 3m37.965s user 0m5.496s sys 0m6.125s # echo 3 > /proc/sys/vm/drop_caches # time mkfs.btrfs --rootdir rootdir --reflink testimg ... real 2m8.342s user 0m5.472s sys 0m1.667s (with ioctl) # echo 3 > /proc/sys/vm/drop_caches # time mkfs.btrfs --rootdir rootdir testimg ... real 3m15.865s user 0m4.258s sys 0m6.261s # echo 3 > /proc/sys/vm/drop_caches # time mkfs.btrfs --rootdir rootdir --reflink testimg ... real 0m5.847s user 0m2.899s sys 0m0.097s Another notable use case is for deduplication, where reading the checksums may serve as a hint instead of reading the whole file data. Reviewed-by: Qu Wenruo <wqu@suse.com> Signed-off-by: Mark Harmstone <mark@harmstone.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-08btrfs: check and set EXTENT_DELALLOC_NEW before clearing EXTENT_DELALLOCQu Wenruo
[WARNING] When running test cases with injected errors or shutdown, e.g. generic/388 or generic/475, there is a chance that the following kernel warning is triggered: BTRFS info (device dm-2): first mount of filesystem d8a19a28-3232-4809-b0df-38df83e71bff BTRFS info (device dm-2): using crc32c checksum algorithm BTRFS info (device dm-2): checking UUID tree BTRFS info (device dm-2): turning on async discard BTRFS info (device dm-2): enabling free space tree BTRFS critical (device dm-2 state E): emergency shutdown ------------[ cut here ]------------ WARNING: extent_io.c:1742 at extent_writepage_io+0x437/0x520 [btrfs], CPU#2: kworker/u43:2/651591 CPU: 2 UID: 0 PID: 651591 Comm: kworker/u43:2 Tainted: G W OE 7.0.0-rc6-custom+ #365 PREEMPT(full) 5804053f02137e627472d94b5128cc9fcb110e88 RIP: 0010:extent_writepage_io+0x437/0x520 [btrfs] Call Trace: <TASK> extent_write_cache_pages+0x2a5/0x820 [btrfs 70299925d0856939e93b17d480651713b3cbba58] btrfs_writepages+0x74/0x130 [btrfs 70299925d0856939e93b17d480651713b3cbba58] do_writepages+0xd0/0x160 __writeback_single_inode+0x42/0x340 writeback_sb_inodes+0x22d/0x580 wb_writeback+0xc6/0x360 wb_workfn+0xbd/0x470 process_one_work+0x198/0x3b0 worker_thread+0x1c8/0x330 kthread+0xee/0x120 ret_from_fork+0x2a6/0x330 ret_from_fork_asm+0x11/0x20 </TASK> ---[ end trace 0000000000000000 ]--- BTRFS error (device dm-2 state E): root 5 ino 259 folio 1323008 is marked dirty without notifying the fs BTRFS error (device dm-2 state E): failed to submit blocks, root=5 inode=259 folio=1323008 submit_bitmap=0: -117 BTRFS info (device dm-2 state E): last unmount of filesystem d8a19a28-3232-4809-b0df-38df83e71bff [CAUSE] Inside btrfs we have the following pattern in several locations, for example inside btrfs_dirty_folio(): btrfs_clear_extent_bit(&inode->io_tree, start_pos, end_of_last_block, EXTENT_DELALLOC | EXTENT_DO_ACCOUNTING | EXTENT_DEFRAG, cached); ret = btrfs_set_extent_delalloc(inode, start_pos, end_of_last_block, extra_bits, cached); if (ret) return ret; However btrfs_set_extent_delalloc() can return IO errors other than -ENOMEM through the following callchain: btrfs_set_extent_delalloc() \- btrfs_find_new_delalloc_bytes() \- btrfs_get_extent() \- btrfs_lookup_file_extent() \- btrfs_search_slot() When such IO error happened, the previous btrfs_clear_extent_bit() has cleared the EXTENT_DELALLOC for the range, and we're expecting btrfs_set_extent_delalloc() to re-set EXTENT_DELALLOC. But since btrfs_set_extent_delalloc() failed before btrfs_set_extent_bit(), EXTENT_DELALLOC flag is no longer present. And if the folio range is dirty before entering btrfs_set_extent_delalloc(), we got a dirty folio but no EXTENT_DELALLOC flag now. Then we hit the folio writeback: extent_writepage() |- writepage_delalloc() | No ordered extent is created, as there is no EXTENT_DELALLOC set | for the folio range. | This also means the folio has no ordered flag set. | |- extent_writepage_io() \- if (unlikely(!folio_test_ordered(folio)) Now we hit the warning. [FIX] Introduce a new helper, btrfs_reset_extent_delalloc() to replace the currently open-coded btrfs_clear_extent_bit() + btrfs_set_extent_delalloc() combination. Instead of calling btrfs_clear_extent_bit() first, update EXTENT_DELALLOC_NEW first, as that part can fail due to metadata IO, meanwhile btrfs_clear_extent_bit() and btrfs_set_extent_bit() won't return any error but retry memory allocation until succeeded. This allows us to fail early without clearing EXTENT_DELALLOC bit, so even if that new btrfs_reset_extent_delalloc() failed before touching EXTENT_DELALLOC, the existing dirty range will still have their old EXTENT_DELALLOC flag present, thus avoid the warning. CC: stable@vger.kernel.org # 6.1+ Reviewed-by: Filipe Manana <fdmanana@suse.com> Signed-off-by: Qu Wenruo <wqu@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-08btrfs: remove unnecessary ctl argument from write_cache_extent_entries()Filipe Manana
There is no need to pass the free space control structure as an argument because we can grab it from the given block group. Signed-off-by: Filipe Manana <fdmanana@suse.com> Reviewed-by: David Sterba <dsterba@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-08btrfs: remove unnecessary ctl argument from __btrfs_write_out_cache()Filipe Manana
We can get the free space control structure from the given block group, so there is no need to pass it 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>
2026-06-08btrfs: remove block group argument from copy_free_space_cache()Filipe Manana
It's not necessary since we can get the block group from the given free space control structure. Signed-off-by: Filipe Manana <fdmanana@suse.com> Reviewed-by: David Sterba <dsterba@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-08btrfs: remove op field from struct btrfs_free_space_ctlFilipe Manana
The op field always points to the same use_bitmap function, the only exception is during self tests where we make it temporarily point to a different function. So just because of this op pointer field we are increasing the structure size by 8 bytes. Instead of storing a pointer to a use_bitmap function in struct btrfs_free_space_ctl, move the pointer to struct btrfs_info, make insert_into_bitmap() use that pointer if we are running the self tests and initialize that pointer to the current, default use_bitmap function (now exported for the tests as btrfs_use_bitmap). This way we reduce the size of struct btrfs_free_space_ctl from 136 to 128 bytes and can now fit 32 structures in a 4K page instead of 30. This also avoids the cost of the indirection of a function pointer call when we are not running the self tests. Signed-off-by: Filipe Manana <fdmanana@suse.com> Reviewed-by: David Sterba <dsterba@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-08btrfs: reduce size of struct btrfs_free_space_ctlFilipe Manana
We have a 4 bytes hole in the structure, reorder some fields so that we eliminate the hole and reduce the structure size from 144 bytes down to 136 bytes. This way on a 4K page system, we can fit 30 structures per page instead of 28. Signed-off-by: Filipe Manana <fdmanana@suse.com> Reviewed-by: David Sterba <dsterba@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-08btrfs: remove unit field from struct btrfs_free_space_ctlFilipe Manana
The unit field always has a value matching the sector size, and since we have a block group pointer in the structure, we can access the block group and then its fs_info field to get to the sector size. So remove the field, which will allow us later to shrink the structure size. Signed-off-by: Filipe Manana <fdmanana@suse.com> Reviewed-by: David Sterba <dsterba@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-08btrfs: remove start field from struct btrfs_free_space_ctlFilipe Manana
There's no need for the start field, we can take it from the block group. This reduces the structure size from 152 bytes down to 144 bytes, so on a 4K page system we can now fit 28 structures instead of 26. Signed-off-by: Filipe Manana <fdmanana@suse.com> Reviewed-by: David Sterba <dsterba@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-08btrfs: use a kmem_cache for free space control structuresFilipe Manana
We are currently allocating the free space control structures for block groups using the generic slabs, and given that the size of the btrfs_free_space_ctl structure is 152 bytes (on a release kernel), we end up using the kmalloc-192 slab and therefore waste quite some memory since on a 4K page system we can only fit 21 free space control structures per page. These structures are allocated and delallocated every time we create and remove block groups. So use a kmem_cache for free space control structures, this way on a 4K page system we can fit 26 structures instead of 21. Signed-off-by: Filipe Manana <fdmanana@suse.com> Reviewed-by: David Sterba <dsterba@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-08btrfs: reduce size of struct btrfs_block_groupFilipe Manana
We currently have several holes in the structure: struct btrfs_block_group { struct btrfs_fs_info * fs_info; /* 0 8 */ struct btrfs_inode * inode; /* 8 8 */ spinlock_t lock __attribute__((__aligned__(4))); /* 16 4 */ /* XXX 4 bytes hole, try to pack */ u64 start; /* 24 8 */ u64 length; /* 32 8 */ u64 pinned; /* 40 8 */ u64 reserved; /* 48 8 */ u64 used; /* 56 8 */ /* --- cacheline 1 boundary (64 bytes) --- */ u64 delalloc_bytes; /* 64 8 */ u64 bytes_super; /* 72 8 */ u64 flags; /* 80 8 */ u64 cache_generation; /* 88 8 */ u64 global_root_id; /* 96 8 */ u64 remap_bytes; /* 104 8 */ u32 identity_remap_count; /* 112 4 */ /* XXX 4 bytes hole, try to pack */ u64 last_used; /* 120 8 */ /* --- cacheline 2 boundary (128 bytes) --- */ u64 last_remap_bytes; /* 128 8 */ u32 last_identity_remap_count; /* 136 4 */ /* XXX 4 bytes hole, try to pack */ u64 last_flags; /* 144 8 */ u32 bitmap_high_thresh; /* 152 4 */ u32 bitmap_low_thresh; /* 156 4 */ struct rw_semaphore data_rwsem __attribute__((__aligned__(8))); /* 160 40 */ /* --- cacheline 3 boundary (192 bytes) was 8 bytes ago --- */ long unsigned int full_stripe_len; /* 200 8 */ long unsigned int runtime_flags; /* 208 8 */ unsigned int ro; /* 216 4 */ int disk_cache_state; /* 220 4 */ int cached; /* 224 4 */ /* XXX 4 bytes hole, try to pack */ struct btrfs_caching_control * caching_ctl; /* 232 8 */ struct btrfs_space_info * space_info; /* 240 8 */ struct btrfs_free_space_ctl * free_space_ctl; /* 248 8 */ /* --- cacheline 4 boundary (256 bytes) --- */ struct rb_node cache_node __attribute__((__aligned__(8))); /* 256 24 */ struct list_head list; /* 280 16 */ refcount_t refs __attribute__((__aligned__(4))); /* 296 4 */ /* XXX 4 bytes hole, try to pack */ struct list_head cluster_list; /* 304 16 */ /* --- cacheline 5 boundary (320 bytes) --- */ struct list_head bg_list; /* 320 16 */ struct list_head ro_list; /* 336 16 */ atomic_t frozen __attribute__((__aligned__(4))); /* 352 4 */ /* XXX 4 bytes hole, try to pack */ struct list_head discard_list; /* 360 16 */ int discard_index; /* 376 4 */ /* XXX 4 bytes hole, try to pack */ /* --- cacheline 6 boundary (384 bytes) --- */ u64 discard_eligible_time; /* 384 8 */ u64 discard_cursor; /* 392 8 */ enum btrfs_discard_state discard_state; /* 400 4 */ /* XXX 4 bytes hole, try to pack */ struct list_head dirty_list; /* 408 16 */ struct list_head io_list; /* 424 16 */ struct btrfs_io_ctl io_ctl; /* 440 72 */ /* --- cacheline 8 boundary (512 bytes) --- */ atomic_t reservations __attribute__((__aligned__(4))); /* 512 4 */ atomic_t nocow_writers __attribute__((__aligned__(4))); /* 516 4 */ struct mutex free_space_lock __attribute__((__aligned__(8))); /* 520 32 */ bool using_free_space_bitmaps; /* 552 1 */ bool using_free_space_bitmaps_cached; /* 553 1 */ /* XXX 2 bytes hole, try to pack */ int swap_extents; /* 556 4 */ u64 alloc_offset; /* 560 8 */ u64 zone_unusable; /* 568 8 */ /* --- cacheline 9 boundary (576 bytes) --- */ u64 zone_capacity; /* 576 8 */ u64 meta_write_pointer; /* 584 8 */ struct btrfs_chunk_map * physical_map; /* 592 8 */ struct list_head active_bg_list; /* 600 16 */ struct work_struct zone_finish_work; /* 616 32 */ /* --- cacheline 10 boundary (640 bytes) was 8 bytes ago --- */ struct extent_buffer * last_eb; /* 648 8 */ enum btrfs_block_group_size_class size_class; /* 656 4 */ /* XXX 4 bytes hole, try to pack */ u64 reclaim_mark; /* 664 8 */ /* size: 672, cachelines: 11, members: 61 */ /* sum members: 634, holes: 10, sum holes: 38 */ /* forced alignments: 8 */ /* last cacheline: 32 bytes */ } __attribute__((__aligned__(8))); Reorder some fields to eliminate the holes while keeping closely related or frequently accessed fields together. After the reordering the size of the structure is reduced down to 632 bytes and the number of cache lines decreases from 11 to 10. We can still only pack 6 block groups per 4K page but on a 64K page system we will now be able to pack 103 block groups instead of 97. The new structure layout, on a release kernel, is the following: struct btrfs_block_group { struct btrfs_fs_info * fs_info; /* 0 8 */ struct btrfs_inode * inode; /* 8 8 */ spinlock_t lock __attribute__((__aligned__(4))); /* 16 4 */ unsigned int ro; /* 20 4 */ u64 start; /* 24 8 */ u64 length; /* 32 8 */ u64 pinned; /* 40 8 */ u64 reserved; /* 48 8 */ u64 used; /* 56 8 */ /* --- cacheline 1 boundary (64 bytes) --- */ u64 delalloc_bytes; /* 64 8 */ u64 bytes_super; /* 72 8 */ u64 flags; /* 80 8 */ u64 cache_generation; /* 88 8 */ u64 global_root_id; /* 96 8 */ u64 remap_bytes; /* 104 8 */ u32 identity_remap_count; /* 112 4 */ u32 last_identity_remap_count; /* 116 4 */ u64 last_used; /* 120 8 */ /* --- cacheline 2 boundary (128 bytes) --- */ u64 last_remap_bytes; /* 128 8 */ u64 last_flags; /* 136 8 */ u32 bitmap_high_thresh; /* 144 4 */ u32 bitmap_low_thresh; /* 148 4 */ struct rw_semaphore data_rwsem __attribute__((__aligned__(8))); /* 152 40 */ /* --- cacheline 3 boundary (192 bytes) --- */ long unsigned int full_stripe_len; /* 192 8 */ long unsigned int runtime_flags; /* 200 8 */ int disk_cache_state; /* 208 4 */ int cached; /* 212 4 */ struct btrfs_caching_control * caching_ctl; /* 216 8 */ struct btrfs_space_info * space_info; /* 224 8 */ struct btrfs_free_space_ctl * free_space_ctl; /* 232 8 */ struct rb_node cache_node __attribute__((__aligned__(8))); /* 240 24 */ /* --- cacheline 4 boundary (256 bytes) was 8 bytes ago --- */ struct list_head list; /* 264 16 */ refcount_t refs __attribute__((__aligned__(4))); /* 280 4 */ atomic_t frozen __attribute__((__aligned__(4))); /* 284 4 */ struct list_head cluster_list; /* 288 16 */ struct list_head bg_list; /* 304 16 */ /* --- cacheline 5 boundary (320 bytes) --- */ struct list_head ro_list; /* 320 16 */ struct list_head discard_list; /* 336 16 */ int discard_index; /* 352 4 */ enum btrfs_discard_state discard_state; /* 356 4 */ u64 discard_eligible_time; /* 360 8 */ u64 discard_cursor; /* 368 8 */ struct list_head dirty_list; /* 376 16 */ /* --- cacheline 6 boundary (384 bytes) was 8 bytes ago --- */ struct list_head io_list; /* 392 16 */ struct btrfs_io_ctl io_ctl; /* 408 72 */ /* --- cacheline 7 boundary (448 bytes) was 32 bytes ago --- */ atomic_t reservations __attribute__((__aligned__(4))); /* 480 4 */ atomic_t nocow_writers __attribute__((__aligned__(4))); /* 484 4 */ struct mutex free_space_lock __attribute__((__aligned__(8))); /* 488 32 */ /* --- cacheline 8 boundary (512 bytes) was 8 bytes ago --- */ bool using_free_space_bitmaps; /* 520 1 */ bool using_free_space_bitmaps_cached; /* 521 1 */ /* XXX 2 bytes hole, try to pack */ /* Bitfield combined with previous fields */ static enum btrfs_block_group_size_class size_class; /* 0: 0 0 */ int swap_extents; /* 524 4 */ u64 alloc_offset; /* 528 8 */ u64 zone_unusable; /* 536 8 */ u64 zone_capacity; /* 544 8 */ u64 meta_write_pointer; /* 552 8 */ struct btrfs_chunk_map * physical_map; /* 560 8 */ struct list_head active_bg_list; /* 568 16 */ /* --- cacheline 9 boundary (576 bytes) was 8 bytes ago --- */ struct work_struct zone_finish_work; /* 584 32 */ struct extent_buffer * last_eb; /* 616 8 */ u64 reclaim_mark; /* 624 8 */ /* size: 632, cachelines: 10, members: 60, static members: 1 */ /* sum members: 630, holes: 1, sum holes: 2 */ /* sum bitfield members: 8 bits (1 bytes) */ /* forced alignments: 8 */ /* last cacheline: 56 bytes */ } __attribute__((__aligned__(8))); Signed-off-by: Filipe Manana <fdmanana@suse.com> Reviewed-by: David Sterba <dsterba@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-08btrfs: use a kmem_cache for block groupsFilipe Manana
We are currently allocating block groups using the generic slabs, and given that the size of btrfs_block_group structure is 672 bytes (on a release kernel), we end up using the kmalloc-1024 slab and therefore waste quite some memory since on a 4K page system we can only fit 4 block groups per page. The block groups are also allocated and delallocated with some frequency, specially if we have auto reclaim enabled. So use a kmem_cache for block groups, this way on a 4K page system we can fit 6 block groups per page instead of 4. Signed-off-by: Filipe Manana <fdmanana@suse.com> Reviewed-by: David Sterba <dsterba@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-08btrfs: make more ASSERTs verbose, part 3David Sterba
We have support for optional string to be printed in ASSERT() (added in 19468a623a9109 ("btrfs: enhance ASSERT() to take optional format string")), it's not yet everywhere it could be so add a few more files. Try to finish what was left after 1c094e6ccead7a ("btrfs: make a few more ASSERTs verbose"). Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-08btrfs: convert ioctl handlers to AUTO_KFREEDavid Sterba
Many ioctl handlers are suitable for the AUTO_KFREE conversions as the data are temporary and short lived. The conversions are trivial or the collateral changes are straightforward. A kfree() preceding mnt_drop_write_file() is slightly more efficient but in the reverse order (i.e. the automatic kfree) does not cause any significant change as the write drop does only a few simple operations. Note: __free() handles also error pointers, so this is safe for the memdup_user() errors too. Reviewed-by: Qu Wenruo <wqu@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-08btrfs: convert kmalloc_array to kmalloc_objs in btrfs_calc_avail_data_space()David Sterba
There's one use of kmalloc_array() that can be transformed to kmalloc_objs() in the same way as suggested in commit 69050f8d6d075d ("treewide: Replace kmalloc with kmalloc_obj for non-scalar types"), swap the arguments and drop GFP flags. All the other cases of kmalloc_array() do not use a simple type so this is the only one. Reviewed-by: Johannes Thumshirn <johannes.thumshirn@wdc.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-08btrfs: do more kmalloc_obj()/kmalloc_objs() conversionsDavid Sterba
Do a few more (trivial) conversions that started in commit 69050f8d6d075d ("treewide: Replace kmalloc with kmalloc_obj for non-scalar types"). Reviewed-by: Johannes Thumshirn <johannes.thumshirn@wdc.com> Signed-off-by: David Sterba <dsterba@suse.com>