summaryrefslogtreecommitdiff
path: root/fs/btrfs
AgeCommit message (Collapse)Author
2026-06-21Merge tag 'mm-nonmm-stable-2026-06-21-10-22' of ↵Linus Torvalds
git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm Pull non-MM updates from Andrew Morton: - "taskstats: fix TGID dead-thread stat retention" (Yiyang Chen) Fix a taskstats TGID aggregation bug where fields added in the TGID query path were not preserved after thread exit, and adds a kselftest covering the regression. - "lib/tests: string_helpers: Slight improvements" (Andy Shevchenko) Improve lib/tests/string_helpers_kunit.c a little - "lib/base64: decode fixes" (Josh Law) Address minor issues in lib/base64.c - "selftests/filelock: Make output more kselftestish" (Mark Brown) Make the output from the ofdlocks test a bit easier for tooling to work with. Also ignore the generated file - "uaccess: unify inline vs outline copy_{from,to}_user() selection" (Yury Norov) Simplify the usercopy code by removing the selectability of inlining copy_{from,to}_user(). - "ocfs2: validate inline xattr header consumers" (ZhengYuan Huang) Fix a number of possible issues in the ocfs2 xattr code - "lib and lib/cmdline enhancements" (Dmitry Antipov) Provide additional robustness checking in the cmdline handling code and its in-kernel testing and selftests - "cleanup the RAID6 P/Q library" (Christoph Hellwig) Clean up the RAID6 P/Q library to match the recent updates to the RAID 5 XOR library and other CRC/crypto libraries - "ocfs2: harden inode validators against forged metadata" (Michael Bommarito) Add three structural checks to OCFS2 dinode validation so malformed on-disk fields are rejected before ocfs2_populate_inode() copies them into the in-core inode - "lib/raid: replace __get_free_pages() call with kmalloc()" (Mike Rapoport) Clean up the lib/raid code by using kmalloc() in more places * tag 'mm-nonmm-stable-2026-06-21-10-22' of git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm: (108 commits) ocfs2: fix circular locking dependency in ocfs2_dio_end_io_write ocfs2: fix NULL h_transaction deref in ocfs2_assure_trans_credits lib: interval_tree_test: validate benchmark parameters ocfs2: avoid moving extents to occupied clusters treewide: fix transposed "sign" typos and update spelling.txt ocfs2: fix UBSAN array-index-out-of-bounds in ocfs2_sum_rightmost_rec fat: reject BPB volumes whose data area starts beyond total sectors selftests/uevent: increase __UEVENT_BUFFER_SIZE to avoid ENOBUFS on busy systems lib/test_firmware: allocate the configured into_buf size fs: efs: remove unneeded debug prints checkpatch: cuppress warnings when Reported-by: is followed by Link: MAINTAINERS: add Alexander as a kcov reviewer mailmap: update Alexander Sverdlin's Email addresses fs: fat: inode: replace sprintf() with scnprintf() ocfs2: fix out-of-bounds write in ocfs2_remove_refcount_extent ocfs2: fix race between ocfs2_control_install_private() and ocfs2_control_release() ocfs2/dlm: require a ref for locking_state debugfs open ocfs2: reject FITRIM ranges shorter than a cluster ocfs2: validate fast symlink target during inode read ocfs2: add journal NULL check in ocfs2_checkpoint_inode() ...
2026-06-17Merge tag 'wq-for-7.2' of git://git.kernel.org/pub/scm/linux/kernel/git/tj/wqLinus Torvalds
Pull workqueue updates from Tejun Heo: - Continued progress toward making alloc_workqueue() unbound by default: more callers converted to WQ_PERCPU / system_percpu_wq / system_dfl_wq, and new warnings for queues that use neither WQ_PERCPU nor WQ_UNBOUND or the legacy system_wq / system_unbound_wq. - Misc: drop the now-trivial apply_wqattrs_lock()/unlock() wrappers, forbid the TEST_WORKQUEUE benchmark from being built-in, and fix a spurious pointer level in the worker debug-dump path. * tag 'wq-for-7.2' of git://git.kernel.org/pub/scm/linux/kernel/git/tj/wq: drm/bridge: anx7625: Add WQ_PERCPU add to alloc_workqueue wifi: ath6kl: fix invalid workqueue flags in ath6kl_usb_create() btrfs: Drop WQ_PERCPU from ordered_flags in btrfs_init_workqueues() workqueue: Add warnings and ensure one among WQ_PERCPU or WQ_UNBOUND is present workqueue: Add warnings and fallback if system_{unbound}_wq is used workqueue: drop spurious '*' from print_worker_info() fn declaration workqueue: forbid TEST_WORKQUEUE from being built-in workqueue: drop apply_wqattrs_lock()/unlock() wrappers umh: replace use of system_unbound_wq with system_dfl_wq rapidio: rio: add WQ_PERCPU to alloc_workqueue users media: ddbridge: add WQ_PERCPU to alloc_workqueue users platform: cznic: turris-omnia-mcu: replace use of system_wq with system_percpu_wq media: synopsys: hdmirx: replace use of system_unbound_wq with system_dfl_wq virt: acrn: Add WQ_PERCPU to alloc_workqueue users
2026-06-16Merge tag 'for-7.2-tag' of ↵Linus Torvalds
git://git.kernel.org/pub/scm/linux/kernel/git/kdave/linux Pull btrfs updates from David Sterba: "The most noticeable change is to enable large folios by default, it's been in testing for a few releases. Related to that is huge folio support (still under experimental config). Otherwise a few ioctl updates, performance improvements and usual fixes and core changes. User visible changes: - enable large folios by default, added in 6.17 (under experimental build), no feature limitations, a big change internally - new ioctl to return raw checksums to userspace (a bit tricky given compression and tail extents), can be used for mkfs and deduplication optimizations - provide stable UUID for e.g. overlayfs and temp_fsid, also reflected in statvfs() field f_fsid, internal dev_t is hashed in to allow cloning - add 32bit compat version of GET_SUBVOL_INFO ioctl - in experimental build, support huge folios (up to 2M) Performance related improvements/changes: - limit bio size to the estimated optimum derived from the queue, this prevents build up of too much data for writeback, which could cause latency spikes (reported improvement 15% on sequential writes) - don't force direct IO to be serialized, forgotten change during mount API port, brings back +60% of throughput - lockless calculation of number of shrinkable extent maps, improve performance with many memcg allocated objects Notable fixes: - in zoned mode, fix a deadlock due to zone reclaim and relocation when space needs to be flushed - don't trim device which is internally not tracked as writeable (e.g. when missing device is being rescanned) - fix deadlock when cloning inline extent and mounted with flushoncommit - fix false IO failures after direct IO falls back to buffered write in some cases Core: - remove COW fixup mechanism completely; detect and fix changes to pages outside of filesystem tracking, guaranteed since 5.8, grace period is over - remove 2K block size support, experimental to test subpage code on x86_64 but now it would block folio changes - tree-checker improvements of: - free-space cache and tree items - root reference and backref items - extent state exceptions in reloc tree - subpage mode updates: - code optimizations, simplify tracking bitmaps - re-enable readahead of compressed extent - extend bitmap size to cover huge folios - add tracepoints related to sync, tree-log and transactions - device stats item tracking unification, remove item if there are no stats recorded, also don't leave stale stats on replaced device - allow extent buffer pages to be allocated as movable, to help page migration - added checks for proper extent buffer release - btrfs.ko code size reduction due to transaction abort call simplifications - several struct size reductions - more auto free conversions - more verbose assertions" * tag 'for-7.2-tag' of git://git.kernel.org/pub/scm/linux/kernel/git/kdave/linux: (130 commits) btrfs: fix use-after-free after relocation failure with concurrent COW btrfs: move WARN_ON on unexpected error in __add_tree_block() btrfs: move locking into btrfs_get_reloc_bg_bytenr() btrfs: lzo: reject compressed segment that overflows the compressed input btrfs: retry faulting in the pages after a zero sized short direct write btrfs: fix incorrect buffered IO fallback for append direct writes btrfs: fix false IO failure after falling back to buffered write btrfs: use verbose assertions in backref.c btrfs: print a message when a missing device re-appears btrfs: do not trim a device which is not writeable btrfs: return real error after lookup failure in btrfs_ioctl_default_subvol() btrfs: use mapping shared locking for reading super block btrfs: use lockless read in nr_cached_objects shrinker callback btrfs: switch local indicator variables to bools btrfs: send: pass bool for pending_move and refs_processed parameters btrfs: use shifts for sectorsize and nodesize btrfs: fix deadlock cloning inline extent when using flushoncommit btrfs: allocate eb-attached btree pages as movable btrfs: add 32-bit compat ioctl for BTRFS_IOC_GET_SUBVOL_INFO btrfs: derive f_fsid from on-disk fsid and dev_t ...
2026-06-14btrfs: Drop WQ_PERCPU from ordered_flags in btrfs_init_workqueues()Nathan Chancellor
After commit 21c05ca88a54 ("workqueue: Add warnings and ensure one among WQ_PERCPU or WQ_UNBOUND is present"), there is a warning from the btrfs-qgroup-rescan workqueue at run time: workqueue: btrfs-qgroup-rescan uses both WQ_PERCPU and WQ_UNBOUND. Dropped WQ_PERCPU, keeping WQ_UNBOUND. WQ_PERCPU is included in ordered_flags after commit 69635d7f4b34 ("fs: WQ_PERCPU added to alloc_workqueue users") and WQ_UNBOUND is set in alloc_ordered_workqueue(), which btrfs_alloc_ordered_workqueue() calls. Drop WQ_PERCPU from ordered_flags, as alloc_ordered_workqueue() notes that only WQ_FREEZABLE and WQ_MEM_RECLAIM are meaningful. Fixes: 69635d7f4b34 ("fs: WQ_PERCPU added to alloc_workqueue users") Fixes: 21c05ca88a54 ("workqueue: Add warnings and ensure one among WQ_PERCPU or WQ_UNBOUND is present") Signed-off-by: Nathan Chancellor <nathan@kernel.org> Reviewed-by: Breno Leitao <leitao@debian.org> Acked-by: Marco Crivellari <marco.crivellari@suse.com> Acked-by: David Sterba <dsterba@suse.com> Signed-off-by: Tejun Heo <tj@kernel.org>
2026-06-15Merge tag 'vfs-7.2-rc1.super' of ↵Linus Torvalds
git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs Pull vfs superblock updates from Christian Brauner: "This retires sget(). CIFS plus the two ext4 KUnit tests (extents-test, mballoc-test) were the last in-tree callers, and all three convert cleanly to sget_fc(). That lets sget() and its prototype come out, taking ~60 lines that only existed to be kept in lockstep with sget_fc() on every publish-path change" * tag 'vfs-7.2-rc1.super' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs: fs: retire sget() smb: client: convert cifs_smb3_do_mount() to sget_fc() ext4: convert mballoc KUnit test to sget_fc() ext4: convert extents KUnit test to sget_fc()
2026-06-15Merge tag 'vfs-7.2-rc1.inode' of ↵Linus Torvalds
git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs Pull vfs inode updates from Christian Brauner: "This extends the lockless ->i_count handling. iput() could already decrement any value greater than one locklessly but acquiring a reference always required taking inode->i_lock. Now acquiring a reference is lockless as long as the count was already at least 1, i.e., only the 0->1 and 1->0 transitions take the lock. This avoids the lock for the common cases of nfs calling into the inode hash and btrfs using igrab(). Cleanup-wise icount_read_once() is added to line up with inode_state_read_once() and the open-coded ->i_count loads across the tree are converted, and ihold() is relocated and tidied up. On top of that some stale lock ordering annotations are retired from the inode hash code: iunique() no longer takes the hash lock since the inode hash became RCU-searchable and s_inode_list_lock is no longer taken under the hash lock either" * tag 'vfs-7.2-rc1.inode' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs: fs: retire stale lock ordering annotations from inode hash fs: allow lockless ->i_count bumps as long as it does not transition 0->1 fs: relocate and tidy up ihold() fs: add icount_read_once() and stop open-coding ->i_count loads
2026-06-09btrfs: fix use-after-free after relocation failure with concurrent COWFilipe Manana
If we get a failure during relocation, before we update all the extent buffers that have file extent items pointing to extents from the block group being relocated, we can trigger a user-after-free on the reloc control structure (fs_info->reloc_control) if we have a concurrent task that is COWing a subvolume leaf. This happens like this: 1) Relocation of data block group X starts; 2) Relocation changes its state to UPDATE_DATA_PTRS; 3) A task doing a rename for example, COWs leaf A from a subvolume tree and ends up at btrfs_reloc_cow_block() and extracts fs_info->reloc_ctl into a local variable, which then passes to replace_file_extents(); 4) The relocation task gets an error and under the label 'out_put_bg' in btrfs_relocate_block_group() calls free_reloc_control(), which frees the reloc control structure that the rename task is using; 5) The rename task triggers a use-after-free on the reloc control structure that was just freed. Syzbot reported this recently, with the following stack trace: [ 88.389822][ T5325] BTRFS error (device loop0 state A): Transaction aborted (error -5) [ 88.389842][ T5325] BTRFS: error (device loop0 state A) in cleanup_transaction:2067: errno=-5 IO failure [ 88.389864][ T5325] BTRFS info (device loop0 state EA): forced readonly [ 88.392277][ T5324] BTRFS: error (device loop0 state EA) in btrfs_sync_log:3572: errno=-5 IO failure [ 88.396630][ T5325] BTRFS info (device loop0 state EA): balance: ended with status: -5 [ 88.400135][ T5346] ================================================================== [ 88.400148][ T5346] BUG: KASAN: slab-use-after-free in replace_file_extents+0x85f/0x1590 [ 88.400288][ T5346] Read of size 8 at addr ffff888012312010 by task syz.0.0/5346 [ 88.400299][ T5346] [ 88.400306][ T5346] CPU: 0 UID: 0 PID: 5346 Comm: syz.0.0 Not tainted syzkaller #0 PREEMPT(full) [ 88.400319][ T5346] Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 [ 88.400325][ T5346] Call Trace: [ 88.400331][ T5346] <TASK> [ 88.400336][ T5346] dump_stack_lvl+0xe8/0x150 [ 88.400351][ T5346] print_address_description+0x55/0x1e0 [ 88.400364][ T5346] ? replace_file_extents+0x85f/0x1590 [ 88.400378][ T5346] print_report+0x58/0x70 [ 88.400389][ T5346] kasan_report+0x117/0x150 [ 88.400405][ T5346] ? replace_file_extents+0x85f/0x1590 [ 88.400420][ T5346] replace_file_extents+0x85f/0x1590 [ 88.400440][ T5346] ? __pfx_replace_file_extents+0x10/0x10 [ 88.400452][ T5346] ? update_ref_for_cow+0xa71/0x1270 [ 88.400473][ T5346] btrfs_force_cow_block+0xa4d/0x2450 [ 88.400492][ T5346] ? __pfx_btrfs_force_cow_block+0x10/0x10 [ 88.400508][ T5346] ? __pfx_btrfs_get_32+0x10/0x10 [ 88.400523][ T5346] btrfs_cow_block+0x3c4/0xa90 [ 88.400542][ T5346] push_leaf_left+0x2ac/0x4a0 [ 88.400561][ T5346] split_leaf+0xd16/0x12e0 [ 88.400574][ T5346] ? btrfs_bin_search+0x924/0xc70 [ 88.400592][ T5346] ? __pfx_split_leaf+0x10/0x10 [ 88.400602][ T5346] ? leaf_space_used+0x177/0x1e0 [ 88.400618][ T5346] ? btrfs_leaf_free_space+0x14a/0x2f0 [ 88.400634][ T5346] btrfs_search_slot+0x2641/0x2d20 [ 88.400654][ T5346] ? __pfx_btrfs_search_slot+0x10/0x10 [ 88.400669][ T5346] ? rcu_is_watching+0x15/0xb0 [ 88.400681][ T5346] ? trace_kmem_cache_alloc+0x29/0xe0 [ 88.400694][ T5346] btrfs_insert_empty_items+0x9c/0x190 [ 88.400711][ T5346] btrfs_insert_inode_ref+0x229/0xcb0 [ 88.400724][ T5346] ? __pfx_btrfs_insert_inode_ref+0x10/0x10 [ 88.400736][ T5346] ? __pfx_btrfs_qgroup_convert_reserved_meta+0x10/0x10 [ 88.400751][ T5346] ? btrfs_record_root_in_trans+0x124/0x180 [ 88.400767][ T5346] ? start_transaction+0x8a0/0x1820 [ 88.400778][ T5346] ? btrfs_set_inode_index+0x5e/0x100 [ 88.400787][ T5346] btrfs_rename2+0x17bb/0x40d0 [ 88.400800][ T5346] ? check_noncircular+0xda/0x150 [ 88.400814][ T5346] ? add_lock_to_list+0xc7/0x100 [ 88.400828][ T5346] ? __pfx_btrfs_rename2+0x10/0x10 [ 88.400842][ T5346] ? lockdep_hardirqs_on+0x7a/0x110 [ 88.400901][ T5346] ? lock_acquire+0x221/0x350 [ 88.400915][ T5346] ? down_write_nested+0x174/0x210 [ 88.400931][ T5346] ? __pfx_down_write_nested+0x10/0x10 [ 88.400941][ T5346] ? do_raw_spin_unlock+0x4d/0x210 [ 88.400952][ T5346] ? try_break_deleg+0x5b/0x180 [ 88.400963][ T5346] ? __pfx_btrfs_rename2+0x10/0x10 [ 88.400973][ T5346] vfs_rename+0xa96/0xeb0 [ 88.400992][ T5346] ? __pfx_vfs_rename+0x10/0x10 [ 88.401010][ T5346] ovl_fill_super+0x46b7/0x5e20 [ 88.401030][ T5346] ? __pfx_ovl_fill_super+0x10/0x10 [ 88.401042][ T5346] ? xas_create+0x1902/0x1b90 [ 88.401060][ T5346] ? __pfx___mutex_trylock_common+0x10/0x10 [ 88.401076][ T5346] ? trace_contention_end+0x3d/0x140 [ 88.401094][ T5346] ? shrinker_register+0x124/0x230 [ 88.401111][ T5346] ? __mutex_unlock_slowpath+0x1be/0x6f0 [ 88.401127][ T5346] ? shrinker_register+0x61/0x230 [ 88.401143][ T5346] ? __pfx___mutex_lock+0x10/0x10 [ 88.401158][ T5346] ? __pfx___mutex_unlock_slowpath+0x10/0x10 [ 88.401177][ T5346] ? __raw_spin_lock_init+0x45/0x100 [ 88.401196][ T5346] ? sget_fc+0x962/0xa40 [ 88.401208][ T5346] ? __pfx_set_anon_super_fc+0x10/0x10 [ 88.401222][ T5346] ? __pfx_ovl_fill_super+0x10/0x10 [ 88.401241][ T5346] get_tree_nodev+0xbb/0x150 [ 88.401257][ T5346] vfs_get_tree+0x92/0x2a0 [ 88.401272][ T5346] do_new_mount+0x341/0xd30 [ 88.401283][ T5346] ? apparmor_capable+0x126/0x170 [ 88.401301][ T5346] ? __pfx_do_new_mount+0x10/0x10 [ 88.401311][ T5346] ? ns_capable+0x89/0xe0 [ 88.401322][ T5346] ? path_mount+0x690/0x10e0 [ 88.401333][ T5346] ? user_path_at+0xd4/0x160 [ 88.401346][ T5346] __se_sys_mount+0x31d/0x420 [ 88.401358][ T5346] ? __pfx___se_sys_mount+0x10/0x10 [ 88.401370][ T5346] ? __x64_sys_mount+0x20/0xc0 [ 88.401381][ T5346] ? entry_SYSCALL_64_after_hwframe+0x77/0x7f [ 88.401391][ T5346] do_syscall_64+0x15f/0xf80 [ 88.401403][ T5346] ? trace_irq_disable+0x3b/0x140 [ 88.401413][ T5346] ? clear_bhb_loop+0x40/0x90 [ 88.401421][ T5346] entry_SYSCALL_64_after_hwframe+0x77/0x7f [ 88.401429][ T5346] RIP: 0033:0x7fa1ff79ce59 [ 88.401436][ T5346] Code: ff c3 66 (...) [ 88.401443][ T5346] RSP: 002b:00007fa2005affe8 EFLAGS: 00000246 ORIG_RAX: 00000000000000a5 [ 88.401456][ T5346] RAX: ffffffffffffffda RBX: 00007fa1ffa16180 RCX: 00007fa1ff79ce59 [ 88.401464][ T5346] RDX: 0000200000000100 RSI: 0000200000002240 RDI: 0000000000000000 [ 88.401474][ T5346] RBP: 00007fa1ff832d6f R08: 0000200000000440 R09: 0000000000000000 [ 88.401481][ T5346] R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000 [ 88.401488][ T5346] R13: 00007fa1ffa16218 R14: 00007fa1ffa16180 R15: 00007ffc734fba78 [ 88.401500][ T5346] </TASK> [ 88.401506][ T5346] [ 88.401510][ T5346] Allocated by task 5325: [ 88.401516][ T5346] kasan_save_track+0x3e/0x80 [ 88.401529][ T5346] __kasan_kmalloc+0x93/0xb0 [ 88.401542][ T5346] __kmalloc_cache_noprof+0x31c/0x660 [ 88.401554][ T5346] btrfs_relocate_block_group+0x217/0xc40 [ 88.401568][ T5346] btrfs_relocate_chunk+0x115/0x820 [ 88.401577][ T5346] __btrfs_balance+0x1db0/0x2ae0 [ 88.401587][ T5346] btrfs_balance+0xaf3/0x11b0 [ 88.401596][ T5346] btrfs_ioctl_balance+0x3d3/0x610 [ 88.401612][ T5346] __se_sys_ioctl+0xfc/0x170 [ 88.401626][ T5346] do_syscall_64+0x15f/0xf80 [ 88.401640][ T5346] entry_SYSCALL_64_after_hwframe+0x77/0x7f [ 88.401650][ T5346] [ 88.401653][ T5346] Freed by task 5325: [ 88.401659][ T5346] kasan_save_track+0x3e/0x80 [ 88.401671][ T5346] kasan_save_free_info+0x46/0x50 [ 88.401680][ T5346] __kasan_slab_free+0x5c/0x80 [ 88.401692][ T5346] kfree+0x1c5/0x640 [ 88.401703][ T5346] btrfs_relocate_block_group+0x95d/0xc40 [ 88.401715][ T5346] btrfs_relocate_chunk+0x115/0x820 [ 88.401724][ T5346] __btrfs_balance+0x1db0/0x2ae0 [ 88.401733][ T5346] btrfs_balance+0xaf3/0x11b0 [ 88.401742][ T5346] btrfs_ioctl_balance+0x3d3/0x610 [ 88.401757][ T5346] __se_sys_ioctl+0xfc/0x170 [ 88.401770][ T5346] do_syscall_64+0x15f/0xf80 [ 88.401785][ T5346] entry_SYSCALL_64_after_hwframe+0x77/0x7f [ 88.401795][ T5346] [ 88.401798][ T5346] The buggy address belongs to the object at ffff888012312000 [ 88.401798][ T5346] which belongs to the cache kmalloc-2k of size 2048 [ 88.401807][ T5346] The buggy address is located 16 bytes inside of [ 88.401807][ T5346] freed 2048-byte region [ffff888012312000, ffff888012312800) [ 88.401819][ T5346] [ 88.401822][ T5346] The buggy address belongs to the physical page: [ 88.401829][ T5346] page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x12310 [ 88.401840][ T5346] head: order:3 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0 [ 88.401849][ T5346] flags: 0xfff00000000040(head|node=0|zone=1|lastcpupid=0x7ff) [ 88.401860][ T5346] page_type: f5(slab) [ 88.401871][ T5346] raw: 00fff00000000040 ffff88801ac42000 dead000000000100 dead000000000122 [ 88.401881][ T5346] raw: 0000000000000000 0000000800080008 00000000f5000000 0000000000000000 [ 88.401892][ T5346] head: 00fff00000000040 ffff88801ac42000 dead000000000100 dead000000000122 [ 88.401902][ T5346] head: 0000000000000000 0000000800080008 00000000f5000000 0000000000000000 [ 88.401913][ T5346] head: 00fff00000000003 fffffffffffffe01 00000000ffffffff 00000000ffffffff [ 88.401923][ T5346] head: ffffffffffffffff 0000000000000000 00000000ffffffff 0000000000000008 [ 88.401929][ T5346] page dumped because: kasan: bad access detected [ 88.401935][ T5346] page_owner tracks the page as allocated [ 88.401941][ T5346] page last allocated via order 3, migratetype Unmovable, gfp_mask 0xd20c0(__GFP_IO|__GFP_FS|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC), pid 9, tgid 9 (kworker/0:0), ts 83905464494, free_ts 83674944822 [ 88.401961][ T5346] post_alloc_hook+0x231/0x280 [ 88.401975][ T5346] get_page_from_freelist+0x24ba/0x2540 [ 88.401990][ T5346] __alloc_frozen_pages_noprof+0x18d/0x380 [ 88.402004][ T5346] allocate_slab+0x77/0x660 [ 88.402019][ T5346] refill_objects+0x339/0x3d0 [ 88.402033][ T5346] __pcs_replace_empty_main+0x321/0x720 [ 88.402043][ T5346] __kmalloc_node_track_caller_noprof+0x572/0x7b0 [ 88.402055][ T5346] __alloc_skb+0x2c1/0x7d0 [ 88.402067][ T5346] mld_newpack+0x14c/0xc90 [ 88.402080][ T5346] add_grhead+0x5a/0x2a0 [ 88.402093][ T5346] add_grec+0x1452/0x1740 [ 88.402105][ T5346] mld_ifc_work+0x6e6/0xe70 [ 88.402116][ T5346] process_scheduled_works+0xb5d/0x1860 [ 88.402127][ T5346] worker_thread+0xa53/0xfc0 [ 88.402138][ T5346] kthread+0x389/0x470 [ 88.402150][ T5346] ret_from_fork+0x514/0xb70 [ 88.402161][ T5346] page last free pid 5282 tgid 5282 stack trace: [ 88.402168][ T5346] __free_frozen_pages+0xbc7/0xd30 [ 88.402180][ T5346] __slab_free+0x274/0x2c0 [ 88.402191][ T5346] qlist_free_all+0x99/0x100 [ 88.402201][ T5346] kasan_quarantine_reduce+0x148/0x160 [ 88.402211][ T5346] __kasan_slab_alloc+0x22/0x80 [ 88.402221][ T5346] __kmalloc_cache_noprof+0x2ba/0x660 [ 88.402231][ T5346] kernfs_fop_open+0x3f0/0xda0 [ 88.402253][ T5346] do_dentry_open+0x785/0x14e0 [ 88.402262][ T5346] vfs_open+0x3b/0x340 [ 88.402270][ T5346] path_openat+0x2e08/0x3860 [ 88.402281][ T5346] do_file_open+0x23e/0x4a0 [ 88.402292][ T5346] do_sys_openat2+0x113/0x200 [ 88.402300][ T5346] __x64_sys_openat+0x138/0x170 [ 88.402309][ T5346] do_syscall_64+0x15f/0xf80 [ 88.402326][ T5346] entry_SYSCALL_64_after_hwframe+0x77/0x7f [ 88.402336][ T5346] [ 88.402339][ T5346] Memory state around the buggy address: [ 88.402345][ T5346] ffff888012311f00: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [ 88.402352][ T5346] ffff888012311f80: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [ 88.402359][ T5346] >ffff888012312000: fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb [ 88.402365][ T5346] ^ [ 88.402370][ T5346] ffff888012312080: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb [ 88.402380][ T5346] ffff888012312100: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb [ 88.402385][ T5346] ================================================================== Fix this by: 1) Making the reloc control structure ref counted; 2) Make revery place that access fs_info->reloc_ctl outside the relocation code, which at the moment it's only replace_file_extents() and btrfs_init_reloc_root(), get a reference count on the structure. There's also btrfs_update_reloc_root() that is called outside the relocation code, but this case is safe because it's only called in the transaction commit path while under the fs_info->reloc_mutex protection, but nevertheless grab a reference to make the code more consistent and avoid false alerts from AI reviews; 3) Add a spinlock to protect fs_info->reloc_ctl, since we can not take the fs_info->reloc_mutex as that would cause a deadlock since that lock is taken in the transaction commit path. That spinlock is taken before setting fs_info->reloc_ctl to an allocated structure, setting it to NULL and reading fs_info->reloc_ctl; 4) Make sure the structure is freed only when its reference count drops to zero. Reported-by: syzbot+0eea49bba18051dea35e@syzkaller.appspotmail.com Link: https://lore.kernel.org/linux-btrfs/6a1df323.bb0696ed.125a22.000a.GAE@google.com/ Reviewed-by: Qu Wenruo <wqu@suse.com> Signed-off-by: Filipe Manana <fdmanana@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-09btrfs: move WARN_ON on unexpected error in __add_tree_block()Filipe Manana
There's no point in having the WARN_ON(1) inside the if statement for the unexpected error. Move it into the if statement's condition, which brings a couple benefits: 1) It marks the branch as unlikely, hinting the compiler to generate better code; 2) The WARN_ON() produces a stack trace after the dumped leaf and error message which can hide that more important information in case we get a truncated dmesg/syslog. Reviewed-by: Qu Wenruo <wqu@suse.com> Signed-off-by: Filipe Manana <fdmanana@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-09btrfs: move locking into btrfs_get_reloc_bg_bytenr()Filipe Manana
It does not make sense for the single caller to have the responsability to lock the relocation mutex before calling the function and then have the function to assert the lock is held. As this is a function in relocation.c, move the locking details into it. Reviewed-by: Qu Wenruo <wqu@suse.com> Signed-off-by: Filipe Manana <fdmanana@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-09btrfs: lzo: reject compressed segment that overflows the compressed inputWeiming Shi
lzo_decompress_bio() validates each on-disk segment length seg_len only against the workspace cbuf size, not against the compressed input size (compressed_len, the total folio bytes of the bio). A crafted extent can carry a segment whose seg_len passes the cbuf check but runs past the end of the bio, so copy_compressed_segment() walks off the last folio: get_current_folio() then returns the NULL folio from bio_next_folio(), and with CONFIG_BTRFS_ASSERT disabled (default) folio_size(NULL) faults. BUG: KASAN: null-ptr-deref in lzo_decompress_bio (fs/btrfs/lzo.c:383) Read of size 8 at addr 0000000000000000 by task kworker/u8:1/29 Workqueue: btrfs-endio simple_end_io_work kasan_report (mm/kasan/report.c:590) lzo_decompress_bio (fs/btrfs/lzo.c:383) end_bbio_compressed_read (fs/btrfs/compression.c:1065) btrfs_bio_end_io (fs/btrfs/bio.c:135) btrfs_check_read_bio (fs/btrfs/bio.c:180 fs/btrfs/bio.c:285) simple_end_io_work process_one_work worker_thread Reject any segment whose payload would extend beyond compressed_len before copying it, treating it as corruption like the other on-disk validation failures in this function. Reported-by: Xiang Mei <xmei5@asu.edu> Fixes: a6e66e6f8c1b ("btrfs: rework lzo_decompress_bio() to make it subpage compatible") Assisted-by: Claude:claude-opus-4-8 Reviewed-by: Qu Wenruo <wqu@suse.com> Signed-off-by: Weiming Shi <bestswngs@gmail.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-09btrfs: retry faulting in the pages after a zero sized short direct writeQu Wenruo
Currently btrfs_direct_write() will not try to fault in the pages, but directly fall back to buffered writes, if the first page of the buffer can not be faulted in. For example, during generic/362 with nodatasum mount option, there is a write at file offset 0, length PAGE_SIZE, and the page is not faulted in. Then we go the following callchain and directly fall back to buffered IO: btrfs_direct_write() |- btrfs_dio_write() |- __iomap_dio_rw() | |- iomap_iter() | | |- btrfs_dio_iomap_begin() | | Now an ordered extent is allocated for the 4K write. | | | |- iomi.status = iomap_dio_iter() | | Where iomap_dio_iter() returned -EFAULT. | | | |- ret = iomap_iter() | | |- btrfs_dio_iomap_end() | | | | return -ENOTBLK | | |- return -ENOTBLK | |- if (ret == -ENOTBLK) { ret = 0; } | Now the return value is reset to 0. | |- ret = iomap_dio_complete() | Since no byte is submitted, @ret is now zero. | |- if (iov_iter_count() > 0 && (ret == -EFAULT || ret > 0)) | @ret is zero, thus not meeting the above retry condition | |- Fallback to buffered Just slightly loosen the condition to allow retry faulting in pages after a zero sized short write. Unlike the previous two bug fixes, this one is not really cause any real bug, but only reducing the chance to do zero-copy direct IO. Thus it doesn't really require stable-CC nor fixes-tag. Reviewed-by: Boris Burkov <boris@bur.io> Signed-off-by: Qu Wenruo <wqu@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-09btrfs: fix incorrect buffered IO fallback for append direct writesQu Wenruo
[BUG] With the previous bug of short direct writes fixed, test case generic/362 (*) still fails with the following error with nodatasum mount option: generic/362 0s ... - output mismatch (see /home/adam/xfstests/results//generic/362.out.bad) - output mismatch (see /home/adam/xfstests/results//generic/362.out.bad) --- tests/generic/362.out 2024-08-24 15:31:37.200000000 +0930 +++ /home/adam/xfstests/results//generic/362.out.bad 2026-05-27 10:13:09.072485767 +0930 @@ -1,2 +1,3 @@ QA output created by 362 +Wrong file size after first write, got 8192 expected 4096 Silence is golden ... *: If the test case has been executed before with default data checksum, the failure will not reproduce. Need the following fix to make it reliably reproducible: https://lore.kernel.org/linux-btrfs/20260528111659.87113-1-wqu@suse.com/ [CAUSE] Inside btrfs_dio_iomap_begin() for a direct write, we increase the isize if it's beyond the current isize. But if the direct io finished short, we do not revert the isize to the previous value nor to the short write end. Then if we need to fall back to buffered writes, and the write has IOCB_APPEND flag, then the buffered write will be positioned at the incorrect isize. The call chain looks like this: btrfs_direct_write(pos=0, length=4K) |- __iomap_dio_rw() | |- iomap_iter() | | |- btrfs_dio_iomap_begin() | | |- btrfs_get_blocks_direct_write() | | |- i_size_write() | | Which updates the isize to the write end (4K). | | | |- iomap_dio_iter() | | Failed with -EFAULT on the first page. | | | |- iomap_iter() | | |- btrfs_dio_iomap_end() | | Detects a short write, return -ENOTBLK | |- if (ret == -ENOTBLK) { ret = 0;} | Which resets the return value. | |- ret = iomap_dio_complet() | Which returns 0. | |- btrfs_buffered_write(iocb, from); |- generic_write_checks() |- iocb->ki_pos = i_size_read() Which is still the new size (4K), other than the original isize 0. [FIX] Introduce the following btrfs_dio_data members: - old_isize - updated_isize If the direct write has enlarged the isize. Then if we got a short write, and btrfs_dio_data::updated_isize is set, revert to the correct isize based on old_isize and current file position. And here we call i_size_write() without holding an extent lock, which is a very special case that we're safe to do: - Only a single writer can be enlarging isize Enlarging isize will take the exclusive inode lock. - Buffered readers need to wait for the OE we're holding Buffered readers will lock extent and wait for OE of the folio range. Sometimes we can skip the OE wait, but since all page cache is invalidated, the OE wait can not be skipped. But I do not think this is the most elegant solution, nor covers all cases. E.g. if the bio is submitted but IO failed, we are unable to do the revert. I believe the more elegant one would be extend the EXTENT_DIO_LOCKED lifespan for direct writes, so that we can update the isize when a write beyond EOF finished successfully. However that change is too huge for a small bug fix. So only implement the minimal partial fix for now. [REASON FOR NO FIXES TAG] The bug is again very old, before commit f85781fb505e ("btrfs: switch to iomap for direct IO") we are already increasing isize without a proper rollback for short writes. Thus only a CC to stable. CC: stable@vger.kernel.org # 5.15+ Reviewed-by: Boris Burkov <boris@bur.io> Signed-off-by: Qu Wenruo <wqu@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-09btrfs: fix false IO failure after falling back to buffered writeQu Wenruo
[BUG] The test case generic/362 will fail with "nodatasum" mount option (*): MOUNT_OPTIONS -- -o nodatasum /dev/mapper/test-scratch1 /mnt/scratch generic/362 0s ... - output mismatch (see /home/adam/xfstests/results//generic/362.out.bad) --- tests/generic/362.out 2024-08-24 15:31:37.200000000 +0930 +++ /home/adam/xfstests/results//generic/362.out.bad 2026-05-27 10:21:17.574771567 +0930 @@ -1,2 +1,3 @@ QA output created by 362 +First write failed: Input/output error Silence is golden ... *: If the test case has been executed before with default data checksum, the failure will not reproduce. Need the following fix to make it reliably reproducible: https://lore.kernel.org/linux-btrfs/20260528111659.87113-1-wqu@suse.com/ [CAUSE] Inside __iomap_dio_rw(), the -EFAULT/-ENOTBLK error is not directly returned. Thus we never got an error pointer from __iomap_dio_rw(). The call chain looks like this: btrfs_direct_write() |- btrfs_dio_write() |- __iomap_dio_rw() | |- iomap_iter() | | |- btrfs_dio_iomap_begin() | | Now an ordered extent is allocated for the 4K write. | | | |- iomi.status = iomap_dio_iter() | | Where iomap_dio_iter() returned -EFAULT. | | | |- ret = iomap_iter() | | |- btrfs_dio_iomap_end() | | | |- btrfs_finish_ordered_extent(uptodate = false) | | | | |- can_finish_ordered_extent() | | | | |- btrfs_mark_ordered_extent_error() | | | | |- mapping_set_error() | | | | Now the address space is marked error. | | | | return -ENOTBLK | | |- return -ENOTBLK | |- if (ret == -ENOTBLK) { ret = 0; } | Now the return value is reset to 0. | Thus no error pointer will be returned. | |- ret = iomap_dio_complete() | Since no byte is submitted, @ret is 0. | |- Fallback to buffered IO | And the buffered write finished without error | |- filemap_fdatawait_range() |- filemap_check_errors() The previous error is recorded, thus an error is returned However the buffered write is properly submitted and finished, the error is from the btrfs_finish_ordered_extent() call with @uptodate = false. [FIX] When a short dio write happened, any range that is submitted will have btrfs_extract_ordered_extent() to be called, thus the submitted range will always have an OE just covering the submitted range. The remaining OE range is never submitted, thus they should be treated as truncated, not an error. So that we can properly reclaim and not insert an unnecessary file extent item, without marking the mapping as error. Extract a helper, btrfs_mark_ordered_extent_truncated(), and utilize that helper to mark the direct IO ordered extent as truncated, so it won't cause failure for the later buffered fallback. [REASON FOR NO FIXES TAG] The bug itself is pretty old, at commit f85781fb505e ("btrfs: switch to iomap for direct IO") we're already passing @uptodate=false finishing the OE. But at that time OE with IOERR won't call mapping_set_error(), so it's not exposed. Later commit d61bec08b904 ("btrfs: mark ordered extent and inode with error if we fail to finish") finally exposed the bug, but that commit is doing a correct job, not the root cause. Anyway the bug is very old, dating back to 5.1x days, thus only CC to stable. CC: stable@vger.kernel.org # 5.15+ Reviewed-by: Boris Burkov <boris@bur.io> Signed-off-by: Qu Wenruo <wqu@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-09btrfs: use verbose assertions in backref.cFilipe Manana
While debugging a relocation issue I hit an assertion in backref.c but it was not super useful, since it could not tell what was the unexpected value that triggered the assertion. The stack trace was this: [583246.338097] assertion failed: !cache->nr_nodes, in fs/btrfs/backref.c:3158 [583246.339588] ------------[ cut here ]------------ [583246.340573] kernel BUG at fs/btrfs/backref.c:3158! [583246.342075] Oops: invalid opcode: 0000 [#1] SMP PTI [583246.343294] CPU: 5 UID: 0 PID: 677957 Comm: btrfs Not tainted 7.1.0-rc4-btrfs-next-234+ #1 PREEMPT(full) [583246.345715] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.16.2-0-gea1b7a073390-prebuilt.qemu.org 04/01/2014 [583246.348694] RIP: 0010:btrfs_backref_release_cache.cold+0x61/0x84 [btrfs] [583246.350759] Code: 90 d5 7c (...) [583246.354923] RSP: 0018:ffffd4fc88c93ad8 EFLAGS: 00010246 [583246.355982] RAX: 000000000000003e RBX: ffff8dec90d97020 RCX: 0000000000000000 [583246.357459] RDX: 0000000000000000 RSI: 0000000000000001 RDI: 00000000ffffffff [583246.359517] RBP: ffff8dec8eeb78c0 R08: 0000000000000000 R09: 3fffffffffefffff [583246.361180] R10: ffffd4fc88c93970 R11: 0000000000000003 R12: ffff8decd21f3470 [583246.363184] R13: 00000000fffffffe R14: ffff8decd21f3000 R15: ffff8decd21f3000 [583246.364666] FS: 00007f9a51751400(0000) GS:ffff8df3f4255000(0000) knlGS:0000000000000000 [583246.366287] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [583246.367443] CR2: 00007f9a518ed8f5 CR3: 00000004467c8002 CR4: 0000000000370ef0 [583246.368969] Call Trace: [583246.369541] <TASK> [583246.370040] relocate_block_group+0xf2/0x520 [btrfs] [583246.371243] btrfs_relocate_block_group+0x9a9/0x22e0 [btrfs] [583246.372443] ? preempt_count_add+0x47/0xa0 [583247.532978] ? btrfs_tree_read_lock_nested+0x19/0x90 [btrfs] [583247.534520] ? mutex_lock+0x1a/0x40 [583247.602233] ? btrfs_scrub_pause+0x2e/0x120 [btrfs] [583247.603543] btrfs_relocate_chunk+0x3b/0x1a0 [btrfs] [583247.604893] btrfs_balance+0x9d5/0x1920 [btrfs] [583247.606189] ? preempt_count_add+0x69/0xa0 [583247.607030] btrfs_ioctl+0x260c/0x2a20 [btrfs] [583247.608015] ? __memcg_slab_free_hook+0x156/0x1a0 [583247.636971] __x64_sys_ioctl+0x92/0xe0 [583247.679247] do_syscall_64+0x60/0xf20 [583247.753297] ? clear_bhb_loop+0x60/0xb0 [583247.756321] entry_SYSCALL_64_after_hwframe+0x76/0x7e [583247.787018] RIP: 0033:0x7f9a5186a8db [583247.787787] Code: 00 48 89 (...) [583247.791410] RSP: 002b:00007fff2ffa6ac0 EFLAGS: 00000246 ORIG_RAX: 0000000000000010 [583247.792897] RAX: ffffffffffffffda RBX: 0000000000000003 RCX: 00007f9a5186a8db [583247.794319] RDX: 00007fff2ffa6bb0 RSI: 00000000c4009420 RDI: 0000000000000003 [583247.795714] RBP: 0000000000000000 R08: 0000000000000000 R09: 0000000000000000 [583247.797149] R10: 0000000000000000 R11: 0000000000000246 R12: 00007fff2ffa903f [583247.798685] R13: 00007fff2ffa6bb0 R14: 0000000000000002 R15: 0000000000000002 [583247.800136] </TASK> So update all simple assertions in backref.c to print out the values when they aren't testing simple boolean conditions. Reviewed-by: Qu Wenruo <wqu@suse.com> Signed-off-by: Filipe Manana <fdmanana@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-09btrfs: print a message when a missing device re-appearsQu Wenruo
There is a bug report that fstrim crashed, and that crash is eventually pinned down to a missing device which re-appeared and screwed up callers that only checks BTRFS_DEV_STATE_MISSING, but not BTRFS_DEV_STATE_WRITEABLE nor device->bdev. A missing device re-appearing can be very tricky, as for now it will result in a device without WRITEABLE or MISSING flag, and still no bdev pointer. As the first step to enhance handling of such re-appearing missing devices, add a dmesg output when a missing device re-appeared. 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-09btrfs: do not trim a device which is not writeableQu Wenruo
[BUG] There is a bug report that btrfs/242 can randomly fail with the following NULL pointer dereference: run fstests btrfs/242 at 2026-06-01 10:25:08 BTRFS: device fsid d4d7f234-487c-4787-88e4-47a8b68c9874 devid 1 transid 9 /dev/sdc (8:32) scanned by mount (122609) BTRFS info (device sdc): first mount of filesystem d4d7f234-487c-4787-88e4-47a8b68c9874 BTRFS info (device sdc): using crc32c checksum algorithm BTRFS warning (device sdc): devid 2 uuid fbe72d72-3272-482d-80fb-ab88ed398192 is missing BTRFS warning (device sdc): devid 2 uuid fbe72d72-3272-482d-80fb-ab88ed398192 is missing BTRFS info (device sdc): allowing degraded mounts BTRFS info (device sdc): turning on async discard BTRFS info (device sdc): enabling free space tree Unable to handle kernel NULL pointer dereference at virtual address 0000000000000018 user pgtable: 4k pages, 48-bit VAs, pgdp=000000013fd6b000 CPU: 4 UID: 0 PID: 122625 Comm: fstrim Not tainted 7.0.10-2-default #1 PREEMPT(full) openSUSE Tumbleweed e9a5f6b24978fba3bf015a992f865837fdfff3dd Hardware name: QEMU KVM Virtual Machine, BIOS edk2-20250812-19.fc42 08/12/2025 pstate: 01400005 (nzcv daif +PAN -UAO -TCO +DIT -SSBS BTYPE=--) pc : btrfs_trim_fs+0x34c/0xa00 [btrfs] lr : btrfs_trim_fs+0x1f0/0xa00 [btrfs] Call trace: btrfs_trim_fs+0x34c/0xa00 [btrfs f02c1d570ceea621c69d302ba75dd61868083840] (P) btrfs_ioctl_fitrim+0xe8/0x178 [btrfs f02c1d570ceea621c69d302ba75dd61868083840] btrfs_ioctl+0xdd4/0x2bd8 [btrfs f02c1d570ceea621c69d302ba75dd61868083840] __arm64_sys_ioctl+0xac/0x108 invoke_syscall.constprop.0+0x5c/0xd0 el0_svc_common.constprop.0+0x40/0xf0 do_el0_svc+0x24/0x40 el0_svc+0x40/0x1d0 el0t_64_sync_handler+0xa0/0xe8 el0t_64_sync+0x1b0/0x1b8 Code: 17ffff83 f94017e0 f9002be0 f9402ea0 (f9400c00) ---[ end trace 0000000000000000 ]--- Also the reporter is very kind to test the following ASSERT() added to btrfs_trim_free_extents_throttle(): ASSERT(device->bdev, "devid=%llu path=%s dev_state=0x%lx\n", device->devid, btrfs_dev_name(device), device->dev_state); And it shows the following output: assertion failed: device->bdev, in extent-tree.c:6630 (devid=2 path=/dev/sdd dev_state=0x82) Which means the device->bdev is NULL, and the dev_state is BTRFS_DEV_STATE_IN_FS_METADATA | BTRFS_DEV_STATE_ITEM_FOUND, without BTRFS_DEV_STATE_WRITEABLE flag set. [CAUSE] The pc points to the following call chain: btrfs_trim_fs() |- btrfs_trim_free_extents() |- btrfs_trim_free_extents_throttle() |- bdev_max_discard_sectors(device->bdev) So the NULL pointer dereference is caused by device->bdev being NULL. This looks impossible by a quick glance, as just before calling btrfs_trim_free_extents_throttle(), we have skipped any device that has BTRFS_DEV_STATE_MISSING flag set. However in this particular case, there is a window where the missing device is later re-scanned, causing btrfs to remove the BTRFS_DEV_STATE_MISSING flag: btrfs_control_ioctl() |- btrfs_scan_one_device() |- device_list_add() |- rcu_assign_pointer(device->name, name); | This updates the missing device's path to the new good path. | |- clear_bit(BTRFS_DEV_STATE_MISSING, &device->dev_state) This removes the BTRFS_DEV_STATE_MISSING flag. This allows the missing device to re-appear and clear the BTRFS_DEV_STATE_MISSING flag. However the device still does not have the BTRFS_DEV_STATE_WRITEABLE flag set, nor is its bdev pointer updated. The bdev pointer remains NULL, triggering the crash later. [FIX] This is a big de-synchronization between BTRFS_DEV_STATE_MISSING and device->bdev pointer, and shows a gap in btrfs's re-appearing-device handling. The proper handling of re-appearing device will need quite some extra work, which is out of the context of this small fix. Thankfully the regular bbio submission path has already handled it well by checking if the device->bdev is NULL before submitting. So here we just fix the crash by checking if the device is writeable and has a bdev pointer before calling bdev_max_discard_sectors(). Reported-by: Su Yue <glass.su@suse.com> Link: https://lore.kernel.org/linux-btrfs/wlwir19t.fsf@damenly.org/ Fixes: 499f377f49f0 ("btrfs: iterate over unused chunk space in FITRIM") CC: stable@vger.kernel.org # 5.10+ 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-09btrfs: return real error after lookup failure in btrfs_ioctl_default_subvol()Filipe Manana
If we fail to lookup the dir item, we are always returning -ENOENT but that may not be the reason for the failure, as btrfs_lookup_dir_item() can return many different errors, such as -EIO or -ENOMEM for example. Fix this by returning the real error, and also fixup the silly error message, including the id of the directory and the error. 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-09btrfs: use mapping shared locking for reading super blockFilipe Manana
There's no need to exclusively lock the mapping, shared locking is enough to protect from a concurrent set block size operation (BLKBSZSET ioctl). Reviewed-by: Qu Wenruo <wqu@suse.com> Signed-off-by: Filipe Manana <fdmanana@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-09btrfs: use lockless read in nr_cached_objects shrinker callbackBen Maurer
Under heavy memcg-driven slab reclaim with many memcgs and CPUs, shrink_slab_memcg() invokes the per-superblock count callback once per (memcg, NUMA node) tuple. For btrfs that callback reaches percpu_counter_sum_positive() on fs_info->evictable_extent_maps, which takes the percpu_counter's raw spinlock with IRQs disabled and walks every online CPU. With hundreds of memcgs driving reclaim on a host with dozens of CPUs, this counter lock becomes a global serialization point: profiles show CPU pinned in the spin_lock_irqsave acquire under __percpu_counter_sum, with cross-CPU IPIs hitting csd_lock_wait_toolong while waiting for spinning vCPUs. The shrinker count is advisory -- super_cache_count() already notes "counts can change between super_cache_count and super_cache_scan, so we really don't need locks here." Use percpu_counter_read_positive(), which is lockless. Worst-case skew is bounded by batch * num_online_cpus (a few thousand), negligible compared to the millions of extent maps a busy filesystem accumulates and well within the noise that the shrinker already tolerates. Tested-by: Boris Burkov <boris@bur.io> Reviewed-by: Qu Wenruo <wqu@suse.com> Reviewed-by: Shakeel Butt <shakeel.butt@linux.dev> Signed-off-by: Ben Maurer <bmaurer@meta.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-09btrfs: switch local indicator variables to boolsDavid Sterba
For all local indicator variables do simple switch to bool, done on all files. Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-09btrfs: send: pass bool for pending_move and refs_processed parametersDavid Sterba
We're passing simple indicators as int, switch them to bool types. Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-09btrfs: use shifts for sectorsize and nodesizeDavid Sterba
Convert more multiplications of sectorsize or nodesize to use the shifts. The remaining cases are multiplications by constants that compiler can optimize by itself, and in tests. Reviewed-by: Qu Wenruo <wqu@suse.com> Reviewed-by: Boris Burkov <boris@bur.io> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-09btrfs: fix deadlock cloning inline extent when using flushoncommitFilipe Manana
In commit b48c980b6a7e ("btrfs: fix deadlock between reflink and transaction commit when using flushoncommit") a deadlock was fixed between reflinks and transaction commits when the fs is mounted with the flushoncommit option. This happened when we had to copy an inline extent's data to the destination file. However the issue was fixed only for the case where the destination offset is 0, it missed the case when the offset is greater than zero. Fix this by ensuring we get i_size update whenever we copied an inline extent's data into the destination file. Syzbot reported this with the following trace: INFO: task kworker/u8:3:57 blocked for more than 143 seconds. Not tainted syzkaller #0 "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message. task:kworker/u8:3 state:D stack:21600 pid:57 tgid:57 ppid:2 task_flags:0x4208160 flags:0x00080000 Workqueue: writeback wb_workfn (flush-btrfs-129) Call Trace: <TASK> context_switch kernel/sched/core.c:5402 [inline] __schedule+0x16f9/0x5500 kernel/sched/core.c:7204 __schedule_loop kernel/sched/core.c:7283 [inline] schedule+0x164/0x360 kernel/sched/core.c:7298 wait_extent_bit fs/btrfs/extent-io-tree.c:905 [inline] btrfs_lock_extent_bits+0x59c/0x700 fs/btrfs/extent-io-tree.c:2008 btrfs_lock_extent fs/btrfs/extent-io-tree.h:152 [inline] btrfs_invalidate_folio+0x440/0xc00 fs/btrfs/inode.c:7718 extent_writepage fs/btrfs/extent_io.c:1848 [inline] extent_write_cache_pages fs/btrfs/extent_io.c:2552 [inline] btrfs_writepages+0x12f3/0x2410 fs/btrfs/extent_io.c:2684 do_writepages+0x32e/0x550 mm/page-writeback.c:2571 __writeback_single_inode+0x133/0x10e0 fs/fs-writeback.c:1764 writeback_sb_inodes+0x97f/0x1980 fs/fs-writeback.c:2056 wb_writeback+0x445/0xb00 fs/fs-writeback.c:2241 wb_do_writeback fs/fs-writeback.c:2388 [inline] wb_workfn+0x3fd/0xf20 fs/fs-writeback.c:2428 process_one_work+0x98b/0x1630 kernel/workqueue.c:3318 process_scheduled_works kernel/workqueue.c:3401 [inline] worker_thread+0xb49/0x1140 kernel/workqueue.c:3482 kthread+0x388/0x470 kernel/kthread.c:436 ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245 </TASK> INFO: task syz.0.145:8523 blocked for more than 143 seconds. Not tainted syzkaller #0 "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message. task:syz.0.145 state:D stack:22752 pid:8523 tgid:8522 ppid:5850 task_flags:0x400140 flags:0x00080002 Call Trace: <TASK> context_switch kernel/sched/core.c:5402 [inline] __schedule+0x16f9/0x5500 kernel/sched/core.c:7204 __schedule_loop kernel/sched/core.c:7283 [inline] schedule+0x164/0x360 kernel/sched/core.c:7298 wb_wait_for_completion+0x3e8/0x790 fs/fs-writeback.c:227 __writeback_inodes_sb_nr+0x24c/0x2d0 fs/fs-writeback.c:2847 try_to_writeback_inodes_sb+0x9a/0xc0 fs/fs-writeback.c:2895 btrfs_start_delalloc_flush fs/btrfs/transaction.c:2182 [inline] btrfs_commit_transaction+0x813/0x2fc0 fs/btrfs/transaction.c:2371 btrfs_sync_file+0xdf4/0x1230 fs/btrfs/file.c:1822 generic_write_sync include/linux/fs.h:2663 [inline] btrfs_do_write_iter+0x6a9/0x840 fs/btrfs/file.c:1473 new_sync_write fs/read_write.c:595 [inline] vfs_write+0x629/0xba0 fs/read_write.c:688 ksys_write+0x156/0x270 fs/read_write.c:740 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0x15f/0x560 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f RIP: 0033:0x7f5a0bdece59 RSP: 002b:00007f5a0b446028 EFLAGS: 00000246 ORIG_RAX: 0000000000000001 RAX: ffffffffffffffda RBX: 00007f5a0c065fa0 RCX: 00007f5a0bdece59 RDX: 000000000000029f RSI: 0000200000000200 RDI: 0000000000000004 RBP: 00007f5a0be82d6f R08: 0000000000000000 R09: 0000000000000000 R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000 R13: 00007f5a0c066038 R14: 00007f5a0c065fa0 R15: 00007ffe149206b8 </TASK> INFO: task syz.0.145:8539 blocked for more than 143 seconds. Not tainted syzkaller #0 "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message. task:syz.0.145 state:D stack:23704 pid:8539 tgid:8522 ppid:5850 task_flags:0x400140 flags:0x00080002 Call Trace: <TASK> context_switch kernel/sched/core.c:5402 [inline] __schedule+0x16f9/0x5500 kernel/sched/core.c:7204 __schedule_loop kernel/sched/core.c:7283 [inline] schedule+0x164/0x360 kernel/sched/core.c:7298 wait_current_trans+0x39f/0x590 fs/btrfs/transaction.c:536 start_transaction+0xbd8/0x1820 fs/btrfs/transaction.c:716 clone_copy_inline_extent fs/btrfs/reflink.c:299 [inline] btrfs_clone+0x1316/0x2540 fs/btrfs/reflink.c:574 btrfs_clone_files+0x271/0x3f0 fs/btrfs/reflink.c:795 btrfs_remap_file_range+0x76b/0x1320 fs/btrfs/reflink.c:948 vfs_clone_file_range+0x435/0x7b0 fs/remap_range.c:403 ioctl_file_clone fs/ioctl.c:239 [inline] ioctl_file_clone_range fs/ioctl.c:257 [inline] do_vfs_ioctl+0xe15/0x1540 fs/ioctl.c:544 __do_sys_ioctl fs/ioctl.c:595 [inline] __se_sys_ioctl+0x82/0x170 fs/ioctl.c:583 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0x15f/0x560 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f RIP: 0033:0x7f5a0bdece59 RSP: 002b:00007f5a0b425028 EFLAGS: 00000246 ORIG_RAX: 0000000000000010 RAX: ffffffffffffffda RBX: 00007f5a0c066090 RCX: 00007f5a0bdece59 RDX: 00002000000000c0 RSI: 000000004020940d RDI: 0000000000000004 RBP: 00007f5a0be82d6f R08: 0000000000000000 R09: 0000000000000000 R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000 R13: 00007f5a0c066128 R14: 00007f5a0c066090 R15: 00007ffe149206b8 </TASK> Reported-by: syzbot+c7443384724bb0f9e913@syzkaller.appspotmail.com Link: https://lore.kernel.org/linux-btrfs/6a150a09.820a0220.e7972.0006.GAE@google.com/ Fixes: 05a5a7621ce6 ("Btrfs: implement full reflink support for inline extents") Reviewed-by: Boris Burkov <boris@bur.io> Signed-off-by: Filipe Manana <fdmanana@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-09btrfs: allocate eb-attached btree pages as movableRik van Riel
Extent buffer pages allocated by alloc_extent_buffer() are attached to btree_inode->i_mapping (the buffer_tree path), reach the LRU, and are served by the btree_migrate_folio aops in fs/btrfs/disk-io.c. They are migratable in practice once their owning extent buffer hits refs == 1, which happens naturally. The buddy allocator classifies them by GFP, however, and bare GFP_NOFS lands them in MIGRATE_UNMOVABLE pageblocks. The result: every btree_inode page we read in pins an unmovable pageblock from the page-superblock allocator's perspective, even though the page itself can be moved. Have each caller of btrfs_alloc_page_array, btrfs_alloc_folio_array, and alloc_eb_folio_array pass in the full GFP mask directly, instead of having the functions calculate it from boolean flags. The alloc_extent_buffer call site passes GFP_NOFS | __GFP_NOFAIL | __GFP_MOVABLE. All other call sites pass plain GFP_NOFS. Three categories of caller stay on bare GFP_NOFS, deliberately: - alloc_dummy_extent_buffer / btrfs_clone_extent_buffer: the resulting eb is EXTENT_BUFFER_UNMAPPED, folio->mapping stays NULL, the folios never enter LRU, never get migrate_folio aops. Tagging them __GFP_MOVABLE would violate the page allocator's migrability contract and they would defeat compaction in MOVABLE pageblocks where isolate_migratepages_block skips non-LRU non-movable_ops pages outright. - btrfs_alloc_page_array callers in fs/btrfs/raid56.c (stripe pages), fs/btrfs/inode.c (encoded reads), fs/btrfs/ioctl.c (io_uring encoded reads), fs/btrfs/relocation.c (relocation buffers): same contract violation. raid56 stripe_pages additionally persist in the stripe cache (RBIO_CACHE_SIZE=1024) well beyond a single I/O, so they are not transient enough to hand-wave the contract. - btrfs_alloc_folio_array caller in fs/btrfs/scrub.c (stripe folios): same -- stripe->folios[] are private buffers freed via folio_put in release_scrub_stripe. This change targets the dominant fragmentation source observed on the page-superblock series: ~28 GB of btree_inode pages parked across many tainted superpageblocks on a 250 GB test system with btrfs root, preventing 1 GiB hugepage allocation from those regions. With the movable hint, those pages now land in MOVABLE pageblocks where the existing background defragger drains them through the standard PB_has_movable gate, no LRU-sample fallback needed. Assisted-by: Claude:claude-opus-4-6 Signed-off-by: Rik van Riel <riel@surriel.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-09btrfs: add 32-bit compat ioctl for BTRFS_IOC_GET_SUBVOL_INFODaan De Meyer
On 64-bit kernels with 32-bit userspace, struct btrfs_ioctl_timespec is laid out as 16 bytes (8B sec + 4B nsec + 4B trailing padding) instead of the 12 bytes a 32-bit userspace expects, because the surrounding struct is not packed. As a result, struct btrfs_ioctl_get_subvol_info_args has a different size and layout in 32-bit userspace than in the 64-bit kernel, and BTRFS_IOC_GET_SUBVOL_INFO returns garbage to 32-bit callers. Mirror what was done for BTRFS_IOC_SET_RECEIVED_SUBVOL: add a packed btrfs_ioctl_get_subvol_info_args_32 with btrfs_ioctl_timespec_32 fields, define BTRFS_IOC_GET_SUBVOL_INFO_32 with that struct as the size argument, factor the existing handler into a shared _btrfs_ioctl_get_ subvol_info() helper, and add btrfs_ioctl_get_subvol_info_32() which fills the kernel struct and translates field-by-field into the 32-bit struct before copy_to_user(). Signed-off-by: Daan De Meyer <daan@amutable.com> Reviewed-by: David Sterba <dsterba@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-09btrfs: derive f_fsid from on-disk fsid and dev_tAnand Jain
The f_fsid was originally derived from fs_devices->fsid and the subvolume root ID. However, when temp_fsid is active, fs_devices->fsid is randomized, making the standard derivation inconsistent. Since metadata_uuid is optional, it is not a reliable alternative. This patch instead retrieves the on-disk UUID from fs_info->super_copy->fsid. To prevent f_fsid collisions between original and cloned filesystems, this implementation hashes the dev_t for single-device btrfs filesystems to ensure uniqueness. This is limited to single-device filesystems as cloned mounts are currently only supported for that configuration. Note that f_fsid will change if the device is replaced. Additionally, since the kernel cannot distinguish between the original and the cloned filesystem, this new f_fsid derivation is applied to both. Link: https://lore.kernel.org/linux-btrfs/cover.1772095546.git.asj@kernel.org/ Link: https://lore.kernel.org/linux-btrfs/cover.1774092915.git.asj@kernel.org/ Signed-off-by: Anand Jain <asj@kernel.org> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-09btrfs: use on-disk uuid for s_uuid in temp_fsid mountsAnand Jain
When mounting a cloned filesystem with a temporary fsuuid (temp_fsid), layered modules like overlayfs require a persistent identifier. While internal in-memory fs_devices->fsid must remain unique to the kernel module, let s_uuid carry the original on-disk UUID. Signed-off-by: Anand Jain <asj@kernel.org> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-09btrfs: avoid unnecessary dev stats updatesQu Wenruo
[MINOR PROBLEM] When mounting a filesystem with a valid DEV_STATS item, we will always update the DEV_STATS again in the next transaction commit, even if there is no change the values. [CAUSE] During the mount, btrfs_device_init_dev_stats() will read out the on-disk DEV_STATS item for each device. Then it calls btrfs_dev_stat_set() to update the in-memory structure. However btrfs_dev_stat_set() does not only set the dev stats value, but also increase device->dev_stats_ccnt. That member determines if we should update the device item at the next transaction commit. Since we have called btrfs_dev_stat_set() for each dev status member, dev_stats_ccnt will be non-zero and we will update the dev stats item even it doesn't change at all. [FIX] Instead of using btrfs_dev_stat_set() for valid on-disk DEV_STATUS values, directly call atomic_set() to set the in-memory values. For other call sites, we still want to use btrfs_dev_stat_set() so that we will force updating/creating the dev stats item. Signed-off-by: Qu Wenruo <wqu@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-09btrfs: always update/create the dev stats item when adding a new deviceQu Wenruo
[MINOR PROBLEM] When adding a new btrfs device, the corresponding DEV_STATS item creation can only triggered by a mount cycle if there is no other error triggered: # mkfs.btrfs -f $dev1 $mnt # mount $dev1 $mnt # btrfs dev add $dev2 $mnt # sync # btrfs ins dump-tree -t dev $dev1 device tree key (DEV_TREE ROOT_ITEM 0) leaf 30588928 items 6 free space 15853 generation 9 owner DEV_TREE item 0 key (DEV_STATS PERSISTENT_ITEM 1) itemoff 16243 itemsize 40 <<< persistent item objectid DEV_STATS offset 1 device stats write_errs 0 read_errs 0 flush_errs 0 corruption_errs 0 generation 0 item 1 key (1 DEV_EXTENT 13631488) itemoff 16195 itemsize 48 Only after a mount cycle and a new transaction, the DEV_STATS for devid 2 can show up: # umount $mnt # mount $dev1 $mnt # touch $mnt # sync # btrfs ins dump-tree -t dev $dev1 device tree key (DEV_TREE ROOT_ITEM 0) leaf 30605312 items 7 free space 15788 generation 10 owner DEV_TREE item 0 key (DEV_STATS PERSISTENT_ITEM 1) itemoff 16243 itemsize 40 persistent item objectid DEV_STATS offset 1 device stats write_errs 0 read_errs 0 flush_errs 0 corruption_errs 0 generation 0 item 1 key (DEV_STATS PERSISTENT_ITEM 2) itemoff 16203 itemsize 40 persistent item objectid DEV_STATS offset 2 device stats write_errs 0 read_errs 0 flush_errs 0 corruption_errs 0 generation 0 [CAUSE] Btrfs only updates the DEV_STATS item when the device->dev_stats_ccnt counter is not 0. This is to reduce COW for the device tree. However that dev_stats_ccnt is only increased at the following call sites: - btrfs_dev_stat_inc() This happens when some IO error happened. - btrfs_dev_stat_read_and_reset() This happens for GET_DEV_STATS ioctl with BTRFS_DEV_STATS_RESET flag. - btrfs_dev_stat_set() This happens inside btrfs_device_init_dev_stats(). So when a new device is added, its dev_stats_ccnt is just initialized to 0, and btrfs won't create nor update the corresponding DEV_STATS item at all. [ENHANCEMENT] When a new device is added, also increase the dev_stats_ccnt by one. This includes both device add ioctl and dev-replace. This will force btrfs to create a new DEV_STATS item or update the existing one with the correct values. This not only makes the DEV_STATS creation early, but also prevents old DEV_STATS left from older kernels to cause false alerts for the newly added device. Signed-off-by: Qu Wenruo <wqu@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-09btrfs: remove the dev stats item when removing a deviceQu Wenruo
[MINOR BUG] The following script will cause DEV_STATS item to be left after the corresponding device is removed: # mkfs.btrfs -f $dev1 # mount $dev1 $mnt # btrfs dev add $dev2 $mnt # umount $mnt ## Without real errors, only at mount time btrfs will update ## dev->dev_stats_ccnt, thus we need a mount cycle to create the ## DEV_STATS item for the new device. # mount $dev1 $mnt # touch $mnt/foobar # sync # btrfs dev remove $dev2 $mnt # umount $mnt This will result the DEV_STATS item for devid 2 still left in device tree: device tree key (DEV_TREE ROOT_ITEM 0) leaf 31064064 items 7 free space 15788 generation 18 owner DEV_TREE leaf 31064064 flags 0x1(WRITTEN) backref revision 1 fs uuid 4bd853ed-f6ef-45fd-bbf1-1c3a2d9987cb chunk uuid b496eab1-ec23-46b5-81c1-2f1b3503ca07 item 0 key (DEV_STATS PERSISTENT_ITEM 1) itemoff 16243 itemsize 40 persistent item objectid DEV_STATS offset 1 device stats write_errs 0 read_errs 0 flush_errs 0 corruption_errs 0 generation 0 item 1 key (DEV_STATS PERSISTENT_ITEM 2) itemoff 16203 itemsize 40 persistent item objectid DEV_STATS offset 2 device stats write_errs 0 read_errs 0 flush_errs 0 corruption_errs 0 generation 0 This is not a huge problem, but if the existing DEV_STATS contains errors, and a new device is added into the fs taking the old devid, then after a mount cycle, the new device will suddenly inherit old errors which can give false alerts. [CAUSE] Btrfs never has the ability to delete DEV_STATS items. It either create a new one through update_dev_stat_item(), or read an existing one through btrfs_device_init_dev_stats(). However update_dev_stat_item() is only called lazily, if a new device is created and no new update to dev stats, then it will skip the update of the on-disk item. So if the old DEV_STATS item exists and a new device is added, and no errors during the remaining operations, the old DEV_STATS will not be updated. Then at the next mount cycle, btrfs_device_init_dev_stats() is called at mount time, which will read out the old records, causing false alerts to the newly added device. [FIX] Manually remove the DEV_STATS item during btrfs_rm_device(). Signed-off-by: Qu Wenruo <wqu@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-09btrfs: remove the dev stats item for replace target deviceQu Wenruo
[MINOR PROBLEM] When a running dev-replace hits some error for the target device (devid 0), there will be a DEV_STATS with error records created at the next transaction commit. Unfortunately that item will never to be deleted. This means at the next dev-replace, if the replace is interrupted, then at the next mount, the target device will suddenly inherit the old error records from that DEV_STATS item, which can give some false alerts on that device. This shouldn't affect end users that much, as it requires all the following conditions to be met, which is pretty rare: - The initial dev-replace hits some error on the target device E.g. write errors, but those errors itself is already a big problem for a running replace. This is required to create the DEV_STATS item in the first place. - The next replace is interrupted This is required to allow btrfs to read from the old records. [CAUSE] Btrfs just never deletes the DEV_STATS after a replace is finished. [FIX] Remove the DEV_STATS item for devid 0 after the replace is finished. This is not going to completely fix the error, as we still have other error paths, e.g. by somehow the fs flips RO and can not start a new transaction for the DEV_STATS item removal. But those corner cases will be addressed by later patches which provide a more generic fix to DEV_STATS related problems. Signed-off-by: Qu Wenruo <wqu@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-09btrfs: validate data reloc tree file extent item membersTeng Liu
get_new_location() uses BUG_ON() to crash the kernel if the file extent item it looks up has any of offset, compression, encryption, or other_encoding set non-zero. The data reloc inode is only written by relocation's own paths and the four fields are always 0 in what the kernel writes: - insert_prealloc_file_extent() memsets the stack item to zero and only fills in type, disk_bytenr, disk_num_bytes and num_bytes, so offset/compression/encryption/other_encoding stay 0. - insert_ordered_extent_file_extent() copies oe->compress_type into the file extent's compression field, but the data reloc inode is created with BTRFS_INODE_NOCOMPRESS so compress_type is always 0; encryption and other_encoding are reserved-and-zero in btrfs. A non-zero value here means the leaf decoded from disk does not match what the kernel wrote, i.e. on-disk corruption. A malformed image reaches this code via balance and panics the kernel. A previous attempt to enforce all four constraints in tree-checker's check_extent_data_item() was merged as commit 7d0ee95979e9 ("btrfs: validate data reloc tree file extent item members in tree-checker") and then reverted by commit 1c034697fcaa after btrfs/061 produced false positives on arm64 with 64K pages. The reason: relocation writeback legitimately produces REG file_extent_items with offset != 0 in the data reloc tree. When an ordered extent covers only the back portion of an underlying PREALLOC (num_bytes < ram_bytes on the input file_extent), insert_ordered_extent_file_extent() inserts a REG with offset = oe->offset num_bytes = oe->num_bytes ram_bytes preserved from the original PREALLOC, and this item can reach disk if a transaction commit fires while it is present in the leaf. The four fields belong in different layers: - compression, encryption and other_encoding are universal invariants for every item in the data reloc tree, regardless of cluster geometry. Enforce them in tree-checker's check_extent_data_item() so a corrupt leaf is rejected at read time. - offset is only an invariant at the cluster-boundary keys that get_new_location() searches (the key is computed as src_disk_bytenr - reloc_block_group_start). Partial-PREALLOC writebacks legitimately place REG items at non-boundary keys with offset != 0; tree-checker cannot reject these. The cluster- boundary item is always written by either insert_prealloc_file_extent() (offset=0 by memset) or by the front portion of a partial writeback (offset=0 by construction), so a non-zero offset there is corruption. Enforce the universal invariants in check_extent_data_item() with a file_extent_err() rejection. Convert the BUG_ON() in get_new_location() to a -EUCLEAN return paired with btrfs_print_leaf() and btrfs_err() so the offending leaf is logged. The caller in replace_file_extents() already handles non-zero returns from get_new_location() by breaking out of the loop without aborting the transaction. Suggested-by: Qu Wenruo <wqu@suse.com> Suggested-by: David Sterba <dsterba@suse.com> Reported-by: syzbot+3e20d8f3d41bac5dc9a2@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=3e20d8f3d41bac5dc9a2 Signed-off-by: Teng Liu <27rabbitlt@gmail.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-09btrfs: annotate lockless read of defrag_bytes in should_nocow()Cen Zhang
should_nocow() reads inode->defrag_bytes without holding inode->lock, while btrfs_set_delalloc_extent() and btrfs_clear_delalloc_extent() update it under that spinlock. This is a data race. The read is a quick check used to decide whether to fall back to COW for a NOCOW inode: if defrag_bytes is non-zero and the range is tagged EXTENT_DEFRAG, we force COW so that defragmentation can rewrite the extent. Reading a stale value is harmless because: - A missed increment may skip COW once, but the defrag pass will redo the extent later. - A stale non-zero may force an unnecessary COW, which is a minor efficiency loss, not a correctness issue. On 64-bit platforms an aligned u64 load is naturally atomic so tearing cannot happen. On 32-bit platforms u64 may tear, but we only test for zero vs non-zero, so the heuristic stays correct regardless. Use data_race() annotation. Fixes: 47059d930f0e ("Btrfs: make defragment work with nodatacow option") Signed-off-by: Cen Zhang <zzzccc427@gmail.com> [ Use data_race() instead of READ_ONCXE() ] Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-09btrfs: send: switch struct fs_path to auto freeingDavid Sterba
The fs_path can use the auto freeing pattern and it's completely contained in send. Define the freeing wrapper and add the cleanup attributes. Almost all conversions are straightforward, replacing goto with direct return. Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-09btrfs: add message format for qgroupidDavid Sterba
The qgroupid has a specific format, add common format specifier, similar to what we have for checksums and keys. Reviewed-by: Boris Burkov <boris@bur.io> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-09btrfs: zoned: always set max_active_zones for zoned devicesJohannes Thumshirn
When a block device does not report a maximum number of open or active zones, currently assign BTRFS_DEFAULT_MAX_ACTIVE_ZONES (128) to the internal limit, if the device has more than BTRFS_DEFAULT_MAX_ACTIVE_ZONES zones. But if the device has less than BTRFS_DEFAULT_MAX_ACTIVE_ZONES the internal max_active_zones limit will stay at 0, even if the device has zone resource limits. Furthermore, if the device has a total number of zones that is less than BTRFS_DEFAULT_MAX_ACTIVE_ZONE, max_active_zones should be set to at most the number of zones. Also move the max_active_zone calculation and setting into a dedicated helper, to shrink btrfs_get_dev_zone_info(). Fixes: 04147d8394e8 ("btrfs: zoned: limit active zones to max_open_zones") Reviewed-by: Damien Le Moal <dlemoal@kernel.org> Signed-off-by: Johannes Thumshirn <johannes.thumshirn@wdc.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-09btrfs: use bvec_phys() in compressed_bio_last_folio()Matthew Wilcox (Oracle)
This is open-coded bvec_phys(), also remove direct use of bv_page. Reviewed-by: Qu Wenruo <wqu@suse.com> Reviewed-by: Boris Burkov <boris@bur.io> Tested-by: Boris Burkov <boris@bur.io> Signed-off-by: Matthew Wilcox (Oracle) <willy@infradead.org> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-09btrfs: replace __free_page with folio_put() in attach_eb_folio_to_filemap()Matthew Wilcox (Oracle)
Calling __free_page() on folio_page() happens to work today, but won't always. Besides, it's far simpler to call folio_put(). Reviewed-by: Boris Burkov <boris@bur.io> Reviewed-by: Qu Wenruo <wqu@suse.com> Tested-by: Boris Burkov <boris@bur.io> Signed-off-by: Matthew Wilcox (Oracle) <willy@infradead.org> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-09Revert "btrfs: fix the file offset calculation inside ↵Matthew Wilcox (Oracle)
btrfs_decompress_buf2page()" It seems that af566bdaff54 was tested against a tree which did not contain commit 12851bd921d4 ("fs: Turn page_offset() into a wrapper around folio_pos()). Unfortunately it has a bug of its own; on 32-bit systems, shifting by PAGE_SHIFT will overflow on files larger than 4GiB. Since page_offset() is now fixed, just revert af566bdaff54. Fixes: af566bdaff54 (btrfs: fix the file offset calculation inside btrfs_decompress_buf2page()) Signed-off-by: Matthew Wilcox (Oracle) <willy@infradead.org> Reviewed-by: Qu Wenruo <wqu@suse.com> Reviewed-by: Boris Burkov <boris@bur.io> Tested-by: Boris Burkov <boris@bur.io> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-09btrfs: zoned: fix deadlock waiting for ticket during data relocationJohannes Thumshirn
When performing data relocation on a zoned filesystem, BTRFS can deadlock in handle_reserve_tickets(). The relocation process is waiting on a space reservation ticket that can never be fulfilled, because the relocation itself is the operation responsible for freeing up that space. Fix this by introducing a new flush state, BTRFS_RESERVE_FLUSH_ZONED_RELOCATION, specifically for data chunk allocation during zoned relocation. Like BTRFS_RESERVE_FLUSH_FREE_SPACE_INODE, this state uses priority_reclaim_data_space() instead of the normal flushing path, which avoids re-entering the relocation code and breaking the deadlock cycle. In btrfs_alloc_data_chunk_ondemand(), select this new flush state when the inode belongs to a data relocation root on a zoned filesystem. Fixes: e2a7fd22378f ("btrfs: zoned: add zone reclaim flush state for DATA space_info") Reviewed-by: Boris Burkov <boris@bur.io> 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>
2026-06-09btrfs: zoned: don't account data relocation space-info in statfs free spaceJohannes Thumshirn
Don't account the free space in a data relocation space-info sub-group as usable free space in statfs. This is misleading as no user allocations can be made in this space-info sub-group. It is only a target for relocation. Fixes: f92ee31e031c ("btrfs: introduce btrfs_space_info sub-group") Reviewed-by: Boris Burkov <boris@bur.io> 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>
2026-06-09btrfs: zoned: always set data_relocation_bgJohannes Thumshirn
When searching for a data relocation block-group on mount, btrfs_zoned_reserve_data_reloc_bg() is looking for the first empty DATA block-group. But it first checks if the block-group is empty and if yes continues the search, and then checks if it is the first DATA block-group. There is actually no point in looking for the second empty DATA block group as new DATA allocations will just allocate a new chunk for it. Pick the first DATA block-group without any allocations done and set it as relocation block-group. At first, the commit 694ce5e143d6 ("btrfs: zoned: reserve data_reloc block group on mount") introduced the functionality. At that time, we took second unused (used == 0) block group, as the first one might be a block group used for normal data. Later, commit daa0fde32235 ("btrfs: zoned: fix data relocation block group reservation") switched to look for an empty block group (alloc_offset == 0). At this point, there is no reason taking the second one anymore. So, this commit is fixing an issue in commit daa0fde32235. Reviewed-by: Boris Burkov <boris@bur.io> 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>
2026-06-09btrfs: zoned: document RECLAIM_ZONES flush stateJohannes Thumshirn
Document the purpose of the RECLAIM_ZONES flush state. Reviewed-by: Boris Burkov <boris@bur.io> 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>
2026-06-09btrfs: introduce support for huge foliosQu Wenruo
With all the previous preparations, it's finally time to enable the huge folio support. - The max folio size Here we define BTRFS_MAX_FOLIO_SIZE, which is fixed at 2MiB. This will ensure we have a large enough but not too large folio for btrfs. This limit applies to all systems regardless of page size. Then we also define BTRFS_MAX_BLOCKS_PER_FOLIO, which depends on CONFIG_BTRFS_EXPERIMENTAL. If it's an experimental build, BTRFS_MAX_BLOCKS_PER_FOLIO is 512, otherwise it's BITS_PER_LONG. The filemap max order will be calculated using both BTRFS_MAX_FOLIO_SIZE and BTRFS_MAX_BLOCKS_PER_FOLIO. E.g. for 64K page size with 64K fs block size, the limit will be BTRFS_MAX_FOLIO_SIZE (2M), which limits the filemap max order to 5. This will be lower than the old order (6), but folios larger than 2M are rarely any better for IO performance. Meanwhile excessively large folios can cause other problems like stalling the IO pipeline for too long. For 4K page size and 4K fs block size, the limit will be increased to 2M from the old 256K. This new size is constrained by both BTRFS_MAX_FOLIO_SIZE (2M) and BTRFS_MAX_BLOCKS_PER_FOLIO (512 * 4K), allowing x86_64 to achieve huge folio support, and the filemap max order will be 9. - btrfs_bio_ctrl::submit_bitmap This will be enlarged to contain BTRFS_MAX_BLOCKS_PER_FOLIO bits, and this will be on-stack memory. This will increase on-stack memory usage by 56 bytes compared to the baseline (before the first patch in the series). - Local @delalloc_bitmap inside writepage_delalloc() Unfortunately we cannot afford to handle an allocation error here, thus again we use on-stack memory. Thus this will increase on-stack memory usage by 56 bytes again. So unfortunately this means during the delalloc window, the writeback path will have +112 bytes on-stack memory usage, and for other cases the writeback path will have +56 bytes on-stack memory usage. The +56 bytes (btrfs_bio_ctrl::submit_bitmap) can be removed after we have reworked the compression submission, so the current on-stack submit_bitmap is mostly a workaround until then. Signed-off-by: Qu Wenruo <wqu@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-09btrfs: migrate btrfs_bio_ctrl::submit_bitmap to support larger bitmapsQu Wenruo
[CURRENT LIMIT] Btrfs currently only supports sub-bitmaps (e.g. dirty bitmap) no larger than BITS_PER_LONG. One call site that utilizes this limit is btrfs_bio_ctrl::submit_bitmap, which makes it very simple and straightforward to just grab an unsigned long value and assign it to submit_bitmap. Unfortunately that limit prevents us from supporting huge folios. For 4K page size and block size, a huge folio (order 9) means 512 blocks inside a 2M folio. [ENHANCEMENT] Instead of using a fixed unsigned long value, change btrfs_bio_ctrl::submit_bitmap to an unsigned long pointer. And for cases where an unsigned long can hold the whole bitmap, introduce @submit_bitmap_value, and just point that pointer to that unsigned long. Then update all direct users of bio_ctrl->submit_bitmap to use the pointer version. There are several call sites that get extra changes: - @range_bitmap inside extent_writepage_io() Which is only utilized to truncate the bitmap. Since we do not want to allocate new memory just for such temporary usage, change the original bitmap_set() and bitmap_and() into bitmap_clear() for the ranges outside of the target range. - Getting dirty subpage bitmap inside writepage_delalloc() Since we're passing an unsigned long pointer now, we need to go with different handling (bs == ps, blocks_per_folio <= BITS_PER_LONG, blocks_per_folio > BITS_PER_LONG). Signed-off-by: Qu Wenruo <wqu@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-09btrfs: prepare subpage operations to support more than BITS_PER_LONG sub-bitmapsQu Wenruo
[CURRENT LIMIT] Btrfs currently only supports sub-bitmaps (e.g. dirty bitmap) no larger than BITS_PER_LONG. That limit allows us to easily grab an unsigned long without the need to properly allocate memory for a larger bitmap. Unfortunately that limit prevents us from supporting huge folios. For 4K page size and block size, a huge folio (order 9) means 512 blocks inside a 2M folio. [ENHANCEMENT] To allow direct bitmap operations without allocating new memory, introduce two different ways to access the subpage bitmaps: - Return an unsigned long value This only happens if blocks_per_folio <= BITS_PER_LONG. We read out the sub-bitmap into an unsigned long, and return the value. This is the old existing method. This involves get_bitmap_value_##name() helper functions. And this time the helper functions are defined as inline functions instead of macros to provide better type checks. - Return a pointer where the sub-bitmap starts This only happens if blocks_per_folio >= BITS_PER_LONG. This is the new method for sub-bitmaps larger than BITS_PER_LONG. Since the sizes of sub-bitmaps are all aligned to BITS_PER_LONG, we can directly access the start word of the sub-bitmap. This involves get_bitmap_pointer_##name() helper functions. Then change the existing sub-bitmaps users to use the new helpers: - Bitmap dumping Switch between get_bitmap_value_##name() and get_bitmap_pointer_##name() depending on the sub-bitmap size. - btrfs_get_subpage_dirty_bitmap() Rename it to btrfs_get_subpage_dirty_bitmap_value() to follow the new value/pointer naming. Since we do not support huge folios yet, there is no pointer version for the dirty bitmap. Furthermore, add the support for block size == page size cases for btrfs_get_subpage_dirty_bitmap_value(), so that the caller no longer needs to check if the folio needs subpage handling. Signed-off-by: Qu Wenruo <wqu@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-09btrfs: update the out-of-date comments on subpageQu Wenruo
The comments at the beginning of subpage.c are out-of-date, a lot of the limitations have been already resolved. Update them to reflect the latest status. Signed-off-by: Qu Wenruo <wqu@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-09btrfs: simplify how first hit is passed to __btrfs_abort_transaction()David Sterba
Optimize the btrfs_abort_transaction() for size as it (by our convention) must be put right after the error condition is detected. The exact file:line is reported so there's a portion that must be inlined. As this is cold code it bloats functions. In previous patch "btrfs: move transaction abort message to __btrfs_abort_transaction()" the error message was moved to the common helper, saving like 20KiB of btrfs.ko and several instructions per call site and some stack space. There's little left to be optimized, we need to keep the atomic test_and_set_bit() and to convey that as 'first hit' to __btrfs_abort_transaction(). Right now it's a bool, which takes 8 bytes on stack for each call but it's 1 bit of information. We can encode that to some of the other parameters. For that let's use the 'error' parameter, by convention it's negative errno so we can reliably detect if it's the first hit or a later error. Also the negation is usually implemented by a single instruction (NEG on x86_64) so the resulting object code is kept short. This reduces btrfs.ko by 8K and stack in several functions by 8 bytes. Cumulative effect with the other commit is -30K of btrfs.ko. While the encoding is an implementation detail, it's contained within the API. Making the transaction abort calls very light is desired. Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-09btrfs: validate negative error number passed to btrfs_abort_transaction()David Sterba
In preparation to encode more information to the error value add a step that verifies if the value is valid (i.e. < 0). This works for compile-time and runtime (in debugging mode). The compile-time check recognizes direct constants and defines an array type. An invalid condition leads to negative array size which is caught by compiler. The runtime check constructs the array type from the condition and only verifies the correct size, as we don't need to tweak the size to be negative. The sizeof() expressions do not generate any code. In the debugging config the warning adds about 9KiB of btrfs.ko code size. The array size trick is needed as we can't use static_array(), not even with __builtin_constant_p(). Sample error message: In file included from inode.c:40: inode.c: In function ‘__cow_file_range_inline’: transaction.h:261:26: error: size of unnamed array is negative 261 | (void)sizeof(char[-!(__builtin_constant_p(error) ? (error) < 0 : 1)]); \ | ^ transaction.h:275:9: note: in expansion of macro ‘VERIFY_NEGATIVE_ERROR’ 275 | VERIFY_NEGATIVE_ERROR(error); \ | ^~~~~~~~~~~~~~~~~~~~~ inode.c:665:17: note: in expansion of macro ‘btrfs_abort_transaction’ 665 | btrfs_abort_transaction(trans, 17); | ^~~~~~~~~~~~~~~~~~~~~~~ Signed-off-by: David Sterba <dsterba@suse.com>
2026-06-09btrfs: fix invalid pointer dereference in __btrfs_run_delayed_refs()Filipe Manana
In the beginning of the loop, we try to obtain a locked delayed ref head, if 'locked_ref' is currently NULL, by calling btrfs_select_ref_head(), which can return an error pointer. If the error pointer is -EAGAIN we do a continue and go back to the beginning of the loop, which will not try again to call btrfs_select_ref_head() since 'locked_ref' is no longer NULL but it's ERR_PTR(-EAGAIN), and then we do: spin_lock(&locked_ref->lock); against a ERR_PTR(-EAGAIN) value, generating an invalid pointer dereference. Fix this by ensuring that 'locked_ref' is set to NULL when btrfs_select_ref_head() returns ERR_PTR(-EAGAIN) and incrementing 'count' as well, to prevent infinite looping. We do this by doing a goto to the bottom of the loop that already sets 'locked_ref' to NULL and does a cond_resched(), with an increment to 'count' right before the goto. These measures were in place before the refactoring in commit 0110a4c43451 ("btrfs: refactor __btrfs_run_delayed_refs loop") but were unintentionally lost afterwards. Reported-by: Dan Carpenter <error27@gmail.com> Link: https://lore.kernel.org/linux-btrfs/ag8ARRwykv8bpJ87@stanley.mountain/ Fixes: 0110a4c43451 ("btrfs: refactor __btrfs_run_delayed_refs loop") Reviewed-by: Boris Burkov <boris@bur.io> Reviewed-by: Qu Wenruo <wqu@suse.com> Signed-off-by: Filipe Manana <fdmanana@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>