summaryrefslogtreecommitdiff
path: root/fs/ext4
AgeCommit message (Collapse)Author
11 hoursMerge branch 'vfs.all' of ↵Mark Brown
https://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs.git
12 hoursMerge branch 'for-next' of https://git.kernel.org/pub/scm/fs/fsverity/linux.gitMark Brown
12 hoursMerge branch 'dev' of ↵Mark Brown
https://git.kernel.org/pub/scm/linux/kernel/git/tytso/ext4.git
26 hoursMerge branch 'vfs-7.4.idmap' into vfs.allChristian Brauner
26 hoursMerge branch 'vfs-7.4.bh' into vfs.allChristian Brauner
Signed-off-by: Christian Brauner <brauner@kernel.org>
27 hoursext4: free the sbi after a grace periodChristian Brauner
ext4_get_link() follows a symlink that keeps its target in a data block in rcu pathwalk mode with ext4_getblk(EXT4_GET_BLOCKS_CACHED_NOWAIT). That reads s_mount_state in the ASSERT() of ext4_getblk() and again in ext4_es_lookup_extent(), and the lookup bumps the extent status hit or miss counter, all through sb->s_fs_info. ext4_put_super() destroys those counters, clears sb->s_fs_info and frees the sbi without waiting for a grace period. A pathwalk that is still in ext4_get_link() when the last reference to the mount goes away then dereferences a NULL s_fs_info, the freed sbi or the destroyed percpu counters. With a pathwalk held up in ext4_get_link() while the last reference goes away: KASAN: null-ptr-deref in range [0x00000000000000a8-0x00000000000000af] RIP: 0010:ext4_getblk+0xf0/0x640 Call Trace: <TASK> ext4_get_link+0x26c/0x410 pick_link+0xf96/0x1780 step_into_slowpath+0x9b8/0x1250 path_lookupat+0x484/0xb00 filename_lookup+0x1f1/0x5b0 vfs_statx+0xd7/0x3b0 vfs_fstatat+0x4d/0x90 __do_sys_newfstatat+0xa0/0x100 do_syscall_64+0x10e/0x5e0 entry_SYSCALL_64_after_hwframe+0x77/0x7f So far that has been hard to hit. umount(2) waits for a grace period in namespace_unlock() before it drops the mount, so only a mount whose last reference goes away elsewhere, like the close of a file or a chdir out of a lazily unmounted directory, can race a walker. Keep sb->s_fs_info and free the sbi together with the extent status counters after a grace period. Split the counters out of ext4_es_unregister_shrinker() for that. The shrinker still gets wasted in ext4_put_super(), the counters go with the sbi. Fixes: 6493792d3299 ("ext4: convert symlink external data block mapping to bdev") Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20261009-work-fixes-rcu-v1-1-7446d2d335e6@kernel.org Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
7 daysext4: mark data=journal as deprecated and will be removed in January 2028.Theodore Ts'o
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
7 daysext4: introduce a mount option for iomap buffered I/O pathZhang Yi
Since the iomap buffered I/O path does not yet support all existing ext4 features, it cannot be enabled by default. Introduce the 'buffered_iomap' and 'nobuffered_iomap' mount options to explicitly enable or disable the iomap buffered I/O path for regular files. Toggling this option via remount is allowed. The change of I/O path will not take effect immediately. It will be deferred. The new setting will only take effect after the inode is re-initialized (i.e., after the last reference is dropped and the inode is re-read from disk). Signed-off-by: Zhang Yi <yi.zhang@huawei.com> Link: https://patch.msgid.link/20260903124017.2325538-3-yi.zhang@huaweicloud.com Signed-off-by: Theodore Ts'o <tytso@mit.edu>
7 daysext4: partially enable iomap for the buffered I/O path of regular filesZhang Yi
Introduce ext4_enable_buffered_iomap() to determine whether a regular file inode should use the iomap buffered I/O path. We now support the default filesystem features, mount options, and the bigalloc feature. However, inline data, fsverity, fscrypt, indirect inode type, and data=journal mode are not fully supported. The decision is made at inode initialization time in __ext4_new_inode() and __ext4_iget() by setting the EXT4_STATE_BUFFERED_IOMAP state flag. If any of these unsupported features are met, the inode silently falls back to the traditional buffer_head path. Switching the buffered I/O path on an active inode is not supported, with the exception of changing a per-inode journal flag. For features like encryption, verity, and inline data that can be dynamically enabled at the superblock level, checking the global feature flag avoids the complexity of toggling the path on individual inodes. Additionally: - Extend ext4_inode_journal_mode() to force ordered mode for inodes using the iomap path under a data=journal mount. For the global data journal mode (EXT4_MOUNT_JOURNAL_DATA), dynamic enablement is deferred until the next inode re-initialization. For the per-inode data journal mode (EXT4_INODE_JOURNAL_DATA), dynamic changes take effect immediately, as it is safe to switch address_space operations and drop all page cache under i_rwsem and filemap_invalidate_lock. - Add a WARN_ON_ONCE() guard in _ext4_get_block() to catch inodes using the iomap path from accidentally entering the legacy buffer_head writeback path. - Place silent BUFFERED_IOMAP checks in ext4_do_writepages() and ext4_iomap_writepages() under the writepages rwsem. Unlike _ext4_get_block(), the writeback path does not hold i_rwsem or invalidate_lock and can race with ext4_change_inode_journal_flag() switching the inode's a_ops. - Reject extent-to-indirect migration via ext4_ind_migrate() for inodes on the iomap path. Signed-off-by: Zhang Yi <yi.zhang@huawei.com> Link: https://patch.msgid.link/20260903124017.2325538-2-yi.zhang@huaweicloud.com Signed-off-by: Theodore Ts'o <tytso@mit.edu>
7 daysext4: add tracepoints for EOF block zeroing and disksize-grow I/OZhang Yi
Add tracepoints to track the disksize-grow-pending lifecycle in the writeback path and the block-zero-EOF entry point: - ext4_iomap_wb_disksize_pending_submit: ioend is marked as DISKSIZE_GROW_IO in ext4_iomap_writeback_submit(). - ext4_iomap_wb_disksize_pending_complete: ioend of type DISKSIZE_GROW_IO completes in ext4_iomap_finish_ioend(). - ext4_iomap_wb_disksize_pending_wait: ioend worker waits for the pending zeroed EOF block to complete. - ext4_iomap_wb_update_disksize: i_disksize is advanced in ext4_iomap_wb_update_disksize, including the new value and whether the update is a disksize-grow completion. - ext4_block_zero_eof: ext4_block_zero_eof is called with the range and zeroing outcome, capturing the producer-side entry point. Together with the previous mark/clear/wait tracepoints, these cover the full lifetime of the disksize-grow-pending state. Signed-off-by: Zhang Yi <yi.zhang@huawei.com> Link: https://patch.msgid.link/20260903124017.2325538-1-yi.zhang@huaweicloud.com Signed-off-by: Theodore Ts'o <tytso@mit.edu>
7 daysext4: add tracepoints for DISKSIZE_GROW_PENDING set, clear, and waitZhang Yi
Add trace events ext4_iomap_mark_disksize_pending(), ext4_iomap_clear_disksize_pending(), and ext4_iomap_wait_disksize_pending() to track disksize-grow-pending state changes and waiting. Signed-off-by: Zhang Yi <yi.zhang@huawei.com> Link: https://patch.msgid.link/20260903123543.2302999-29-yi.zhang@huaweicloud.com Signed-off-by: Theodore Ts'o <tytso@mit.edu>
7 daysext4: set DISKSIZE_GROW_PENDING after zeroing unaligned EOF blockZhang Yi
In the iomap buffered I/O path, data=ordered mode is not used, so the zeroed EOF block has no implicit ordering with later i_disksize updates. Without the pending state being set, i_disksize can be advanced past the zeroed block before writeback completes, exposing stale data after a crash. Previous patches added the consumer side of the disksize-grow-pending mechanism: the state bit, clear and wait helpers, and ioend tagging. Now add ext4_iomap_mark_disksize_pending() and call it from ext4_block_zero_eof() after zeroing the tail of the block that straddles i_disksize. The helper locks the folio, waits for any in-flight writeback on it to complete, then sets EXT4_STATE_DISKSIZE_GROW_PENDING only if the folio is still dirty. Waiting for writeback prevents folio_test_dirty() from returning false mid-writeback, which would cause us to skip the pending state while zeroed data is still in flight. The dirty check then avoids setting the bit when the data has already been written back. Suggested-by: Jan Kara <jack@suse.cz> Signed-off-by: Zhang Yi <yi.zhang@huawei.com> Link: https://patch.msgid.link/20260903123543.2302999-28-yi.zhang@huaweicloud.com Signed-off-by: Theodore Ts'o <tytso@mit.edu>
7 daysext4: clear DISKSIZE_GROW_PENDING on truncate or errorZhang Yi
The disksize-grow-pending state is set when a zeroed EOF block is queued for writeback and cleared by the ioend completion path once writeback finishes. However, the zeroed block may be discarded before writeback completes — through folio discard, truncate, or unlink and inode eviction. In any of these cases, leaving the bit set would block subsequent writeback indefinitely. Therefore, we must clear it on all paths that invalidate the pending block before writeback completes: - ext4_iomap_discard_folio() on folio discard. - ext4_evict_inode() when an unlinked inode is destroyed. In ext4_truncate_down(), truncating past the pending zeroed EOF block also invalidates the pending disksize update, so the bit must be cleared there as well. Finally, add a WARN_ON in ext4_destroy_inode() to catch any inode destroyed with the bit still set. The check is skipped when the filesystem is in an error or emergency state, as the pending block may not have been written back in those cases. Note that EXT4_STATE_DISKSIZE_GROW_PENDING is not set for now, and it will be set after everthing is done. Signed-off-by: Zhang Yi <yi.zhang@huawei.com> Link: https://patch.msgid.link/20260903123543.2302999-27-yi.zhang@huaweicloud.com Signed-off-by: Theodore Ts'o <tytso@mit.edu>
7 daysext4: submit and wait for disksize-grow I/O in fallocate pathsZhang Yi
Collapse range and insert range update i_disksize directly under i_data_sem. If the operation runs while the zeroed EOF block is still awaiting writeback, i_disksize could advance past the zeroed boundary before the zeroed data is persisted, exposing stale data on crash. Deferring i_disksize updates like fallocate and zero_range is not an option here because the shift would move written extents beyond the current i_disksize. So flush and wait for the pending zeroed EOF block before these operations advance i_disksize. Since these operations already perform writeback, the extra flush does not add significant overhead. In addition, for ext4_update_disksize_before_punch(), if the punch discards the pending block, the zeroed data will never be written back before advancing i_disksize, so it is also necessary to sync the pending EOF range there. Finally, for the SYNC variants of zero_range and fallocate, this also guarantees the i_disksize update is persisted on the synchronous return. Note that EXT4_STATE_DISKSIZE_GROW_PENDING is not set for now, and it will be set after everthing is done. Signed-off-by: Zhang Yi <yi.zhang@huawei.com> Link: https://patch.msgid.link/20260903123543.2302999-26-yi.zhang@huaweicloud.com Signed-off-by: Theodore Ts'o <tytso@mit.edu>
7 daysext4: defer i_disksize update while DISKSIZE_GROW_PENDING is setZhang Yi
Operations like append allocate, zero range, and truncate update i_disksize directly. If the new i_disksize exceeds the original value while the zeroed EOF block is still awaiting writeback, metadata may be persisted before the zeroed data, exposing stale data on crash. Defer i_disksize updates while EXT4_STATE_DISKSIZE_GROW_PENDING is set; the ioend worker for the pending block will advance i_disksize to i_size once the zeroed data is written back. The tradeoff is that i_disksize may lag i_size transiently, but this is observable only to callers that read i_disksize directly. Introduce __ext4_set_i_disksize() to centralize the bit check for callers already holding i_data_sem (ext4_ext_truncate and ext4_set_inode_size), and refactor ext4_update_inode_size() to take i_data_sem itself and check the bit atomically with i_size_write(), so the ioend worker observes the latest i_size under the same lock. Note that in the O_SYNC mode of ext4_do_fallocate() or ext4_zero_range(), we need to flush out pending blocks that can update i_disksize synchronously. This will be handled later, and EXT4_STATE_DISKSIZE_GROW_PENDING will also be set after everything is done. Suggested-by: Jan Kara <jack@suse.cz> Signed-off-by: Zhang Yi <yi.zhang@huawei.com> Link: https://patch.msgid.link/20260903123543.2302999-25-yi.zhang@huaweicloud.com Signed-off-by: Theodore Ts'o <tytso@mit.edu>
7 daysext4: advance i_disksize to i_size upon disksize-grow I/O completionZhang Yi
When the disksize-grow-pending zeroed EOF I/O completes, i_disksize has to be advanced. Advancing it only to the end of that specific I/O would discard any later i_disksize updates from concurrent fallocate or similar operations, causing filesystem inconsistency. Scanning dirty or writeback folios beyond the current position to compute a safe advance target is expensive and racy with concurrent fallocate, so instead advance i_disksize directly to i_size. This may expose zeroed data (not stale data) after crash recovery when dirty data in the range is not yet on disk, but only for unaligned append writes, which is deemed acceptable. To support this, teach ext4_iomap_wb_update_disksize() to take an is_disksize_grow flag and advance i_disksize to i_size when set, and have ext4_iomap_finish_ioend() pass the flag based on the EXT4_IOMAP_IOEND_DISKSIZE_GROW_IO tag of the completing ioend. Note that EXT4_STATE_DISKSIZE_GROW_PENDING is not set for now, and it will be set after everthing is done. Suggested-by: Jan Kara <jack@suse.cz> Signed-off-by: Zhang Yi <yi.zhang@huawei.com> Link: https://patch.msgid.link/20260903123543.2302999-24-yi.zhang@huaweicloud.com Signed-off-by: Theodore Ts'o <tytso@mit.edu>
7 daysext4: submit and wait for pending disksize-grow I/O on writebackZhang Yi
When the current writeback pass begins beyond the disksize-grow-pending zeroed EOF block, the ioend worker would otherwise have to wait for the pending EOF block to complete before it can advance i_disksize. Otherwise the old EOF block could be exposed as stale data once i_disksize advances past it. Therefore, introduce the ioend mechanism for the pending range, tag ioends that cover the pending zeroed EOF block which straddles i_disksize with EXT4_IOMAP_IOEND_DISKSIZE_GROW_IO in ext4_iomap_writeback_submit(), and clear the bit and wake up all waiters in ext4_iomap_end_bio() when such an ioend completes. Clearing EXT4_IOMAP_IOEND_DISKSIZE_GROW_IO does not depend on whether the disksize grow I/O succeeds. That is, even if the I/O fails, we still allow subsequent writes in the range to update i_disksize. This is consistent with the previous behavior, and we rely on data_err=abort to prevent metadata updates when data write failures occur. In order to avoid the ioend that passes the pending range waiting for a long time, proactively submit the pending range first in ext4_iomap_writepages() so it completes in parallel with the rest of the writeback. Note that the handling of discarding the zeroed EOF folio will be processed later, otherwise the bit will be set forever. EXT4_STATE_DISKSIZE_GROW_PENDING will be set after everthing is done. Signed-off-by: Zhang Yi <yi.zhang@huawei.com> Link: https://patch.msgid.link/20260903123543.2302999-23-yi.zhang@huaweicloud.com Signed-off-by: Theodore Ts'o <tytso@mit.edu>
7 daysext4: add EXT4_STATE_DISKSIZE_GROW_PENDING state bit and helpersZhang Yi
Inodes using the iomap buffered I/O path do not use data=ordered mode, so the zeroed EOF block that straddles i_disksize needs explicit tracking to ensure it is written back before i_disksize is advanced. Add the EXT4_STATE_DISKSIZE_GROW_PENDING inode state bit and three helpers: ext4_iomap_clear_disksize_pending() to atomically clear the bit and wake waiters, ext4_iomap_wait_disksize_pending() to block until the bit is cleared, and ext4_iomap_get_disksize_pending_range() to compute the pending range from i_disksize. These will be used by subsequent patches to serialize i_disksize updates with the writeback of the zeroed EOF block. Suggested-by: Jan Kara <jack@suse.cz> Signed-off-by: Zhang Yi <yi.zhang@huawei.com> Link: https://patch.msgid.link/20260903123543.2302999-22-yi.zhang@huaweicloud.com Signed-off-by: Theodore Ts'o <tytso@mit.edu>
7 daysext4: disable online defrag when inode using iomap buffered I/O pathZhang Yi
Online defragmentation does not currently support inodes using the iomap buffered I/O path. The existing implementation relies on buffer_head for sub-folio block management and data=ordered mode for data consistency, both of which are incompatible with the iomap path. Signed-off-by: Zhang Yi <yi.zhang@huawei.com> Reviewed-by: Ojaswin Mujoo <ojaswin@linux.ibm.com> Reviewed-by: Jan Kara <jack@suse.cz> Link: https://patch.msgid.link/20260903123543.2302999-21-yi.zhang@huaweicloud.com Signed-off-by: Theodore Ts'o <tytso@mit.edu>
7 daysext4: add block mapping tracepoints for iomap buffered I/O pathZhang Yi
Add tracepoints for iomap buffered read, write, partial block zeroing, and writeback operations to help debug the iomap buffered I/O path. Signed-off-by: Zhang Yi <yi.zhang@huawei.com> Reviewed-by: Ojaswin Mujoo <ojaswin@linux.ibm.com> Reviewed-by: Jan Kara <jack@suse.cz> Link: https://patch.msgid.link/20260903123543.2302999-20-yi.zhang@huaweicloud.com Signed-off-by: Theodore Ts'o <tytso@mit.edu>
7 daysext4: drain writeback before removing extents on the iomap pathZhang Yi
Because the iomap infrastructure does not always create an ifs to manage sub-folio state when folio size is larger than blocksize, invalidating a partial dirty folio during punch hole may fail to clear the dirty state of the affected range. As a result, writeback of that folio may observe a hole. At writeback submit time, ext4_map_blocks() already handles this case and will not allocate blocks. However, when punch hole races with writeback, the following scenario can cause I/O completion to encounter a hole. punch hole writeback ---------- --------- ext4_punch_hole() ext4_truncate_page_cache_block_range() iomap_invalidate_folio() [partial folio] iomap_clear_range_dirty() -- no ifs, sub-block dirty bits NOT cleared ext4_iomap_writepages() iomap_writepages() ext4_iomap_writeback_submit() ext4_iomap_map_writeback_range() ext4_map_blocks(IO_SUBMIT) -> extent exists, not a hole submit_io() -> bio in flight down_write(&i_data_sem) ext4_es_remove_extent() ext4_ext_remove_space() -> extent removed, hole inserted up_write(&i_data_sem) [ transaction commits, freed blocks released to buddy ] [ allocator reuses freed blocks for another inode ] [bio completes] ext4_iomap_finish_ioend() ext4_convert_unwritten_extents() ext4_map_blocks(IO_CONVERT_EXT) -> returns 0 (hole found) [bio writes to reallocated blocks ] -> silent data corruption If the bio completes after the freed blocks have been reallocated to another inode, it silently overwrites them and causes data corruption. Therefore, on the iomap path, drain writeback in ext4_punch_hole() after partial zeroing and before starting the freeing transaction, so any bio in flight on a partial folio completes against blocks still owned by this inode before extent removal. Once extent removal begins, the ES hole insert and the existing IO_SUBMIT short-circuit prevent further bio submission. This is a workaround. The proper fix is for the iomap infrastructure to always maintain an ifs when folio size is larger than blocksize, so that partial invalidate reliably clears sub-block dirty bits and no draining is needed. Signed-off-by: Zhang Yi <yi.zhang@huawei.com> Reviewed-by: Ojaswin Mujoo <ojaswin@linux.ibm.com> Link: https://patch.msgid.link/20260903123543.2302999-19-yi.zhang@huaweicloud.com Signed-off-by: Theodore Ts'o <tytso@mit.edu>
7 daysext4: implement partial block zero range path using iomapZhang Yi
Introduce a new iomap_ops instance, ext4_iomap_zero_ops, along with ext4_iomap_block_zero_range() to implement block zeroing via the iomap infrastructure for ext4. ext4_iomap_block_zero_range() calls iomap_zero_range() with ext4_iomap_zero_begin() as the callback. The callback locates the range and populates the iomap mapping. If the range is mapped, iomap_zero_iter() in the iomap core zeros the partial block directly. If the range is an unwritten extent within EOF, the callback collects a dirty folio batch via iomap_fill_dirty_folios() so that iomap_zero_iter() can zero those folios directly, bypassing a separate slow flush operation that would otherwise be needed to convert the unwritten extent. Note that ext4_iomap_zero_begin() can race with concurrent writeback: after it queries an unwritten extent, writeback may convert it to written and complete on the folio before iomap_fill_dirty_folios() scans the range. The empty batch then makes iomap_zero_iter() skip zeroing, leaving stale on-disk data. zero_range writeback ---------------------- ---------------------- ext4_block_zero_range() iomap_zero_range() iomap_iter() ext4_iomap_zero_begin() ext4_iomap_map_blocks() -> extent is UNWRITTEN ext4_convert_unwritten_extents_endio() -> extent is converted to WRITTEN -> folio is clean iomap_fill_dirty_folios() filemap_get_folios_dirty() -> folio is clean, not added to batch ext4_set_iomap() -> IOMAP_UNWRITTEN iomap_zero_iter() __iomap_get_folio() -> NULL (empty batch) iomap_iter_advance_full() <-- zeroing skipped [later read returns stale on-disk data] <-- CORRUPTION Therefore, we retry the extent lookup when iomap_fill_dirty_folios() adds nothing and the i_es_seq cookie captured at the first lookup has advanced, indicating the race actually occurred. Other important constraints: Zeroing out under an active journal handle can cause deadlock, as the lock/handle ordering is inconsistent with the iomap writeback path. Therefore, ext4_iomap_block_zero_range() must not be called under an active handle. In addition, for post-EOF zeroing, the caller cannot rely on data=ordered mode to persist the zeroed data before i_disksize is updated. Subsequent patches will address this by deferring i_disksize update to i_size until after the zeroed data has been written back. Signed-off-by: Zhang Yi <yi.zhang@huawei.com> Reviewed-by: Ojaswin Mujoo <ojaswin@linux.ibm.com> Link: https://patch.msgid.link/20260903123543.2302999-18-yi.zhang@huaweicloud.com Signed-off-by: Theodore Ts'o <tytso@mit.edu>
7 daysext4: implement mmap path using iomapZhang Yi
Introduce ext4_iomap_page_mkwrite() to implement the mmap iomap path for ext4. The heavy lifting is delegated to iomap_page_mkwrite(), which only requires ext4_iomap_buffered_write_ops and ext4_iomap_buffered_da_write_ops to allocate and map blocks. Note that the lock ordering between folio lock and transaction start in this path is reversed compared to the buffer_head buffered write path. The lock ordering documentation in super.c has been updated accordingly. Signed-off-by: Zhang Yi <yi.zhang@huawei.com> Reviewed-by: Ojaswin Mujoo <ojaswin@linux.ibm.com> Reviewed-by: Jan Kara <jack@suse.cz> Link: https://patch.msgid.link/20260903123543.2302999-17-yi.zhang@huaweicloud.com Signed-off-by: Theodore Ts'o <tytso@mit.edu>
7 daysext4: implement writeback path using iomapZhang Yi
Add the iomap writeback path for ext4 buffered I/O. This introduces: - ext4_iomap_writepages(): the main writeback entry point. - ext4_writeback_ops: a new iomap_writeback_ops instance to handle block mapping and I/O submission. - A new end I/O work handler ext4_iomap_finish_ioend() is added for converting unwritten extents, updating file size, and handling DATA_ERR_ABORT after I/O completion. Core implementation details: - ->writeback_range() callback Calls ext4_iomap_map_writeback_range() to map and allocate blocks using the enhanced ext4_map_blocks(). ext4_map_blocks() now starts its own transaction internally when it needs to allocate blocks. For performance, when a block range is not yet allocated, it allocates based on the writeback length and delalloc extent length (ext4_map_blocks() will trim the allocated length), rather than allocating a single folio at a time. The folio is then added to an iomap_ioend instance. - ->writeback_submit() callback Registers ext4_iomap_end_bio() as the end bio callback. This callback schedules a worker to handle: - Unwritten extent conversion. - i_disksize update after data is written back. - Journal abort on writeback I/O failure. - The new end I/O work handler ext4_iomap_finish_ioend() This reuses i_rsv_conversion_work and i_rsv_conversion_list, which are now used in the buffer_head writeback path for memory saving, because an inode can never go through both buffer_head and iomap paths. Those are not initialized in this patch but will be when the iomap path is formally enabled later. Key changes and considerations: - Append write and unwritten extents Since data=ordered mode is not used to prevent stale data exposure during append writebacks, new blocks are always allocated as unwritten extents (i.e. always enable dioread_nolock), and i_disksize update is postponed until I/O completion. Additionally, the deadlock that the reserve handle was expected to resolve does not occur anymore. Therefore, the end I/O worker can start a normal journal handle instead of a reserve handle when converting unwritten extents. - Lock ordering The ->writeback_range() callback runs under the folio lock, requiring the journal handle to be started under that same lock. This reverses the order compared to the buffer_head writeback path. The lock ordering documentation in super.c has been updated accordingly. - Don't cache writes The iomap infrastructure sets the BIO_COMPLETE_IN_TASK flag when submitting I/O, so the ioend will be processed in task context. However, if a private defer worker is to be started, this flag must be cleared explicitly to avoid double deferral. In the future, all private defer work should be moved to the generic bio complete in task framework. Signed-off-by: Zhang Yi <yi.zhang@huawei.com> Link: https://patch.msgid.link/20260903123543.2302999-16-yi.zhang@huaweicloud.com Signed-off-by: Theodore Ts'o <tytso@mit.edu>
7 daysext4: implement buffered write path using iomapZhang Yi
Introduce two new iomap_ops instances for ext4 buffered writes: - ext4_iomap_buffered_da_write_ops: for delayed allocation mode, using ext4_da_map_blocks() to map delalloc extents. - ext4_iomap_buffered_write_ops: for non-delayed allocation mode, using ext4_map_blocks() to directly allocate blocks. Also add ext4_iomap_valid() for the iomap infrastructure to check extent validity. This is useful for the iomap buffered write path because it does not hold the folio lock between mapping the inode extent and copying data. So it can race with writeback, which may modify or stale the extent type. Key changes and considerations: - Unwritten extents for new blocks (dioread_nolock always on) Since data=ordered mode is not used to prevent stale data exposure in the non-delayed allocation path, new blocks are always allocated as unwritten extents. - Short write and write failure handling a. Delalloc path: On short write or failure, the stale delalloc range must be dropped and its space reservation released. Otherwise, a clean folio may cover leftover delalloc extents, causing inaccurate space reservation accounting. b. Non-delalloc path: No cleanup of allocated blocks is needed on short write since leaving the preallocated blocks behind doesn't cause any real issue. These blocks are visible to users and are not permanently leaked. - Lock ordering reversal The folio lock and transaction start ordering is reversed compared to the buffer_head buffered write path. To handle this, the journal handle must be stopped in iomap_begin() callbacks. The lock ordering documentation in super.c has been updated accordingly. Signed-off-by: Zhang Yi <yi.zhang@huawei.com> Reviewed-by: Ojaswin Mujoo <ojaswin@linux.ibm.com> Link: https://patch.msgid.link/20260903123543.2302999-15-yi.zhang@huaweicloud.com Signed-off-by: Theodore Ts'o <tytso@mit.edu>
7 daysext4: do not use data=ordered mode for inodes using buffered iomap pathZhang Yi
The data=ordered mode introduces two fundamental conflicts with the iomap buffered write path, leading to potential deadlocks. 1) Lock ordering conflict In the iomap writeback path, each folio is processed sequentially: the folio lock is acquired first, followed by starting a transaction to create block mappings. In data=ordered mode, writeback triggered by the journal commit process may attempt to acquire a folio lock that is already held by iomap background writeback process. Meanwhile, iomap, under that same folio lock, may start a new transaction to map other blocks on this folio and wait for the currently committing transaction to finish, resulting in a deadlock. Trans N commit background writeback(via iomap) journal_submit_data_buffers() ext4_journal_submit_inode_data_buffers() iomap_writepages() iomap_writepages() folio_lock() folio_lock() -- wait iomap_writeback_folio() iomap_writeback_range() ext4_journal_start() start new transaction -- wait for trans N commit, DEADLOCK ext4_map_blocks() Currently, in the buffer_head writeback path, this is handled by starting the transaction before taking any folio locks for writeback. 2) Partial folio submission not supported When block size < folio size, a folio may contain both mapped and unmapped blocks. In data=ordered mode, a deadlock can occur if the journal waits (pure JI_WAIT_DATA) for such a folio to be written back while background writeback has already started on it (with the writeback flag set). The problem is that mapping the remaining delalloc blocks can deadlock because the writeback flag is not cleared until the entire folio is processed and committed. T0: Assume we have a folio contains four blocks, from front to back, they are A, B, C, D. The block B and C are holes, and the last block D is written in delalloc mode (the block is not allocated yet). T1: The background writeback process starts to write back data, set writeback flag on the folio, allocates block D, and adds it to transaction N's order list of jbd2 in pure JI_WAIT_DATA mode. T2: This folio completes the writeback and clears the writeback flag. T3: Before transaction N commit, we buffered write block A to C. T4: Transaction N commit and folio writeback are running concurrently. Trans N commit background writeback(via iomap) iomap_writeback_folio() folio_start_writeback() -- set writeback flag jbd2_journal_finish_inode_data_buffers() __filemap_fdatawait_range() -- wait writeback flag to clear iomap_writeback_range() ext4_journal_start() start new transaction -- wait for trans N commit, DEADLOCK ext4_map_block() (B, C) Currently, in the buffer_head writeback path, this is handled by: 1. Partial folio submission — already-allocated buffers can be submitted first. The writeback flag is cleared after I/O completes, preventing block allocation while the writeback flag is set. 2. Allocation order — the transaction is started first, then blocks are allocated, the writeback flag is set, and finally the allocated buffers submission begins. To support data=ordered mode, the iomap core would need two invasive changes: - Acquire the transaction handle before locking any folio for writeback. - Support partial folio submission. Both changes are complicated and risk performance regressions. Therefore, we must avoid using data=ordered mode when converting to the iomap path. Currently, data=ordered mode is used in three scenarios: - Append write - Post-EOF partial block truncate-up followed by append write - Online defragmentation We can address the first two without data=ordered mode: - For append write: always allocate unwritten blocks (i.e. always enable dioread_nolock), preserving the behavior of current extent-type inodes. - For post-EOF truncate-up + append write: postpone updating i_disksize until after the zeroed partial block has been written back. Online defragmentation does not yet support iomap; this can be resolved separately in the future. Signed-off-by: Zhang Yi <yi.zhang@huawei.com> Reviewed-by: Jan Kara <jack@suse.cz> Reviewed-by: Ojaswin Mujoo <ojaswin@linux.ibm.com> Link: https://patch.msgid.link/20260903123543.2302999-14-yi.zhang@huaweicloud.com Signed-off-by: Theodore Ts'o <tytso@mit.edu>
7 daysext4: pass out extent seq counter when mapping da blocksZhang Yi
The iomap buffered write path does not hold the folio lock between mapping the inode extent and copying data. Therefore, it can race with writeback that modifies the extent type (e.g., from unwritten to written). This can lead to data corruption on partial writes, as iomap_block_needs_zeroing() may return a false positive based on a stale extent. The iomap infrastructure uses the sequence counter stored in the inode to detect such stale mappings. Commit 07c440e8da8f ("ext4: pass out extent seq counter when mapping blocks") added the m_seq field to ext4_map_blocks to pass out extent sequence numbers, but it missed two callsites within ext4_da_map_blocks(). These callsites are on the delayed allocation path, which is needed in the iomap buffered write path. Pass out the sequence counter to ensure stale mappings can be detected. Signed-off-by: Zhang Yi <yi.zhang@huawei.com> Reviewed-by: Jan Kara <jack@suse.cz> Reviewed-by: Ojaswin Mujoo <ojaswin@linux.ibm.com> Link: https://patch.msgid.link/20260903123543.2302999-13-yi.zhang@huaweicloud.com Signed-off-by: Theodore Ts'o <tytso@mit.edu>
7 daysext4: implement buffered read path using iomapZhang Yi
Implement the iomap read path for ext4 by introducing a new ext4_iomap_buffered_read_ops instance. This provides the read_folio() and readahead() callbacks for ext4_iomap_aops. The implementation introduces: - ext4_iomap_map_blocks(): Helper function to query extent mappings for a given read range using ext4_map_blocks() and convert the mapping information to iomap type - ext4_iomap_buffered_read_begin(): The iomap_begin callbacks that maps blocks, validates filesystem state, and populates the iomap. It returns -ERANGE for inline data which is not yet supported. Signed-off-by: Zhang Yi <yi.zhang@huawei.com> Reviewed-by: Jan Kara <jack@suse.cz> Reviewed-by: Ojaswin Mujoo <ojaswin@linux.ibm.com> Link: https://patch.msgid.link/20260903123543.2302999-12-yi.zhang@huaweicloud.com Signed-off-by: Theodore Ts'o <tytso@mit.edu>
7 daysext4: add iomap address space operations for buffered I/OZhang Yi
Introduce initial support for iomap in the buffered I/O path for regular files on ext4. - Add a new inode state flag EXT4_STATE_BUFFERED_IOMAP to indicate the inode uses iomap instead of buffer_head for buffered I/O - Add helper ext4_inode_buffered_iomap() to check the flag - Add new address space operations ext4_iomap_aops with callbacks that will use generic iomap implementations - Add ext4_iomap_aops to ext4_set_aops() when the flag is set The following callbacks(read_folio(), readahead(), writepages()) are provided as placeholders and will be implemented in later patches. Signed-off-by: Zhang Yi <yi.zhang@huawei.com> Reviewed-by: Jan Kara <jack@suse.cz> Reviewed-by: Ojaswin Mujoo <ojaswin@linux.ibm.com> Link: https://patch.msgid.link/20260903123543.2302999-11-yi.zhang@huaweicloud.com Signed-off-by: Theodore Ts'o <tytso@mit.edu>
7 daysext4: skip block allocation for holes in the data submission pathZhang Yi
When ext4_map_blocks() is called from the data submission path and I/O end extent conversion path (EXT4_GET_BLOCKS_IO_SUBMIT), it should not allocate blocks if the lookup returns a hole. The writeback path can legitimately encounter dirty ranges that map to holes. For example, when a folio straddles i_size and the tail beyond i_size is dirtied via a mmap write. Allocating blocks for such ranges is wrong because there is no data to write back, the dirty bits should simply be discarded without submitting I/O. This prepares for the buffered iomap writeback conversion, mirrors the existing buffer_head writeback path, where mpage_add_bh_to_extent() skip unmapped buffers and ext4_bio_write_folio() clears their dirty bits. In the ioend extent conversion path, holes are also not expected because we should wait for folio writeback before punching hole. If one is encountered, it likely indicates a failure in the concurrency protection, so ext4_map_blocks() returns zero, and we keep warning and bail out with -EINVAL to surface the failure rather than continuing conversion on torn data. Atomic writes in ext4_convert_unwritten_extents_atomic() already bail out similarly. Signed-off-by: Zhang Yi <yi.zhang@huawei.com> Reviewed-by: Ojaswin Mujoo <ojaswin@linux.ibm.com> Link: https://patch.msgid.link/20260903123543.2302999-10-yi.zhang@huaweicloud.com Signed-off-by: Theodore Ts'o <tytso@mit.edu>
7 daysext4: avoid unnecessary transaction in ext4_map_blocks() for unwritten extentsZhang Yi
When ext4_map_blocks() finds an unwritten extent in the extent cache and the caller is willing to accept unwritten extents without conversion, there is no need to start a journal transaction since no metadata update is required. This avoids unnecessary transaction overhead in the upcoming iomap writeback path when overwriting already-allocated unwritten extents. One thing to be careful about, as the comment in ext4_map_blocks() states, if the flags contain EXT4_GET_BLOCKS_CREATE, the function will mark @map as mapped. Signed-off-by: Zhang Yi <yi.zhang@huawei.com> Reviewed-by: Ojaswin Mujoo <ojaswin@linux.ibm.com> Link: https://patch.msgid.link/20260903123543.2302999-9-yi.zhang@huaweicloud.com Signed-off-by: Theodore Ts'o <tytso@mit.edu>
7 daysext4: allow ext4_map_blocks() to start its own transaction handleZhang Yi
Make ext4_map_blocks() start its own transaction handle when the caller does not provide one. The handle is started after the lookup path confirms that allocation is actually needed, and is stopped at the unified out_handle exit path. This avoids unnecessarily starting a handle for pure mapping queries. This prepares for the buffered iomap writeback conversion, which improves performance for fragile overwrite cases. Suggested-by: Jan Kara <jack@suse.cz> Signed-off-by: Zhang Yi <yi.zhang@huawei.com> Reviewed-by: Ojaswin Mujoo <ojaswin@linux.ibm.com> Link: https://patch.msgid.link/20260903123543.2302999-8-yi.zhang@huaweicloud.com Signed-off-by: Theodore Ts'o <tytso@mit.edu>
7 daysext4: fix orig_mlen initialization in ext4_map_blocks()Zhang Yi
Save orig_mlen after clamping map->m_len to INT_MAX. Otherwise, the unclamped value may be passed below, bypassing its overflow protection. Cc: stable@kernel.org Fixes: 5bb12b1837c0 ("ext4: Add support for EXT4_GET_BLOCKS_QUERY_LEAF_BLOCKS") Signed-off-by: Zhang Yi <yi.zhang@huawei.com> Reviewed-by: Ojaswin Mujoo <ojaswin@linux.ibm.com> Link: https://patch.msgid.link/20260903123543.2302999-7-yi.zhang@huaweicloud.com Signed-off-by: Theodore Ts'o <tytso@mit.edu>
7 daysext4: recheck extent status tree before block allocationZhang Yi
After acquiring i_data_sem in write mode, recheck that the mapping found via the extent status tree or disk query has not changed. A racing truncate may have trimmed the extent between the earlier lookup and the write lock acquisition, since writeback does not hold i_rwsem or the folio locks covering the full extent. This could cause ext4_map_create_blocks() to allocate blocks beyond the truncated range, potentially leading to quota leaks in the upcomming iomap buffered writeback path since the iomap writeback infrastructure caches extents beyond the folio range. Therefore, if we find a valid extent and the sequence number has changed, retry the entire lookup to obtain the correct trimmed mapping. Cc: stable@kernel.org Suggested-by: Jan Kara <jack@suse.cz> Signed-off-by: Zhang Yi <yi.zhang@huawei.com> Reviewed-by: Ojaswin Mujoo <ojaswin@linux.ibm.com> Link: https://lore.kernel.org/linux-ext4/b0781809-4759-4e12-be17-71555b764f48@gmail.com/ Link: https://patch.msgid.link/20260903123543.2302999-6-yi.zhang@huaweicloud.com Signed-off-by: Theodore Ts'o <tytso@mit.edu>
7 daysext4: set EXT4_MAP_NEW flag for delayed allocated blocksZhang Yi
Set EXT4_MAP_NEW in ext4_da_map_blocks() to properly indicate that a new delayed allocation block has been inserted, allowing callers to distinguish newly created delayed extents from existing ones. Currently, the buffer_head caller, ext4_da_get_block_prep(), does not consume this flag. It intercepts EXT4_MAP_DELAYED and returns early, and unconditionally calls set_buffer_new(bh) so EXT4_MAP_NEW is not used. The flag is prepared for the iomap buffered I/O path added later. Reported-by: Ojaswin Mujoo <ojaswin@linux.ibm.com> Link: https://lore.kernel.org/linux-ext4/cc05c17d-163e-4251-b2c9-aa3a6f9555d7@huaweicloud.com/ Signed-off-by: Zhang Yi <yi.zhang@huawei.com> Reviewed-by: Ojaswin Mujoo <ojaswin@linux.ibm.com> Link: https://patch.msgid.link/20260903123543.2302999-5-yi.zhang@huaweicloud.com Signed-off-by: Theodore Ts'o <tytso@mit.edu>
7 daysext4: skip ordered I/O wait when zeroing beyond i_disksize blockZhang Yi
ext4_block_zero_eof() zeros the tail of a partial block beyond EOF. After zeroing, it waits for ordered I/O completion to prevent stale data exposure from concurrent post-EOF mmap writes during folio writeback. However, if the zeroed range lies entirely beyond the block containing i_disksize, no stale data can be exposed because the zeroed region is beyond existing on-disk data. The zeroed pages will be written out before i_disksize is later extended past i_size, so the ordered I/O wait is unnecessary. Add a condition to skip it. Suggested-by: Ojaswin Mujoo <ojaswin@linux.ibm.com> Signed-off-by: Zhang Yi <yi.zhang@huawei.com> Link: https://patch.msgid.link/20260903123543.2302999-4-yi.zhang@huaweicloud.com Signed-off-by: Theodore Ts'o <tytso@mit.edu>
7 daysext4: factor out ext4_truncate_[up|down]()Zhang Yi
Refactor ext4_setattr() by introducing two helper functions, ext4_truncate_up() and ext4_truncate_down(), to handle size changes. The current ATTR_SIZE processing consolidates checks for both shrinking and non-shrinking cases, leading to cluttered code. Separating the truncation paths improves readability. Signed-off-by: Zhang Yi <yi.zhang@huawei.com> Reviewed-by: Ojaswin Mujoo <ojaswin@linux.ibm.com> Reviewed-by: Jan Kara <jack@suse.cz> Link: https://patch.msgid.link/20260903123543.2302999-3-yi.zhang@huaweicloud.com Signed-off-by: Theodore Ts'o <tytso@mit.edu>
7 daysext4: simplify size updating in ext4_setattr()Zhang Yi
The logic for updating the file size in ext4_setattr() is currently somewhat messy. By directly entering the error-handling path after failing to add an orphan inode, the unnecessary recovery process involving old_disksize and the file size can be avoided. Signed-off-by: Zhang Yi <yi.zhang@huawei.com> Reviewed-by: Jan Kara <jack@suse.cz> Reviewed-by: Ojaswin Mujoo <ojaswin@linux.ibm.com> Link: https://patch.msgid.link/20260903123543.2302999-2-yi.zhang@huaweicloud.com Signed-off-by: Theodore Ts'o <tytso@mit.edu>
7 daysext4: convert group-count barrier protocol to acquire/releaseJinjie Ruan
During an online resize, reading s_groups_count and s_blockfile_groups requires strict ordering against subsequent initialized block group metadata. Replace the historical smp_wmb()/smp_rmb() pairs with smp_store_release() and smp_load_acquire(). This formalizes the publish-subscribe pattern and allows weakly-ordered architectures (e.g., arm64) to utilize native STLR/LDAR instructions instead of full DMB fences. Update the documentation in resize.c and ext4.h accordingly. Cc: "Theodore Ts'o" <tytso@mit.edu> Cc: Andreas Dilger <adilger.kernel@dilger.ca> Cc: Baokun Li <libaokun@linux.alibaba.com> Cc: Jan Kara <jack@suse.cz> Cc: Ojaswin Mujoo <ojaswin@linux.ibm.com> Cc: "Ritesh Harjani (IBM)" <ritesh.list@gmail.com> Cc: Zhang Yi <yi.zhang@huawei.com> Assisted-by: Gemini:Gemini-3.1 Assisted-by: DeepSeek:DeepSeek-V3 Reviewed-by: Zhang Yi <yi.zhang@huawei.com> Reviewed-by: Jan Kara <jack@suse.cz> Signed-off-by: Jinjie Ruan <ruanjinjie@huawei.com> Link: https://patch.msgid.link/20260902074805.398540-9-ruanjinjie@huawei.com Signed-off-by: Theodore Ts'o <tytso@mit.edu>
7 daysext4: fix out-of-bounds read in ext4_get_group_info()Jinjie Ruan
A plain read of s_groups_count in ext4_get_group_info() allows CPU load-load reordering. On weak memory models, speculative prefetch of s_group_info prior to the boundary check could lead to an out-of-bounds read if a concurrent online resize expands the array and increments s_groups_count. The data race occurs between the ioctl configuration path (holding the resize lock via ext4_resize_begin) and the lockless metadata lookup path: CPU 0 (Writer, Resize Lock) CPU 1 (Reader, Lockless) --------------------------- ------------------------ ext4_ioctl() [EXT4_IOC_GROUP_ADD] ext4_ioctl_group_add() ext4_resize_begin() // Takes lock ext4_group_add() ext4_mb_alloc_groupinfo() // Publishes expanded array via RCU rcu_assign_pointer(s_group_info, ...) ext4_flex_group_add() ext4_update_super() ext4_get_group_info() // Speculative / out-of-order read [Loads old/smaller s_group_info pointer] [Plain C store / smp_wmb()] sbi->s_groups_count += ...; // Reads new s_groups_count, // boundary check passes if (group >= s_groups_count) // Out-of-bounds array access! sbi_array_rcu_deref(..., s_group_info) Fix this by using ext4_get_groups_count() to enforce acquire semantics. Cc: stable@vger.kernel.org Cc: "Theodore Ts'o" <tytso@mit.edu> Cc: Andreas Dilger <adilger.kernel@dilger.ca> Cc: Baokun Li <libaokun@linux.alibaba.com> Cc: Jan Kara <jack@suse.cz> Cc: Ojaswin Mujoo <ojaswin@linux.ibm.com> Cc: "Ritesh Harjani (IBM)" <ritesh.list@gmail.com> Cc: Zhang Yi <yi.zhang@huawei.com> Fixes: 5354b2af3406 ("ext4: allow ext4_get_group_info() to fail") Link: https://sashiko.dev/#/patchset/20260825095422.3166067-1-ruanjinjie%40huawei.com Reviewed-by: Zhang Yi <yi.zhang@huawei.com> Reviewed-by: Jan Kara <jack@suse.cz> Signed-off-by: Jinjie Ruan <ruanjinjie@huawei.com> Link: https://patch.msgid.link/20260902074805.398540-8-ruanjinjie@huawei.com Signed-off-by: Theodore Ts'o <tytso@mit.edu>
7 daysext4: fix ABBA deadlock in ext4_rmdir()Sergey Senozhatsky
We observe the following ABBA deadlock (casefolding enabled): INFO: task jbd2/dm-13-8:447 blocked for more than 720 seconds. task:jbd2/dm-13-8 state:D stack:0 pid:447 tgid:447 ppid:2 Call Trace: <TASK> __schedule+0xc2c/0xe60 schedule+0x40/0xe0 jbd2_journal_wait_updates+0x8f/0xf0 jbd2_journal_commit_transaction+0x321/0x1850 kjournald2+0xa9/0x230 kthread+0x226/0x2a0 ret_from_fork+0x101/0x1e0 ret_from_fork_asm+0x1a/0x30 INFO: task Thread-10:4161 blocked for more than 720 seconds. task:Thread-10 state:D stack:0 pid:4161 tgid:3848 ppid:540 Call Trace: <TASK> __schedule+0xc2c/0xe60 schedule+0x40/0xe0 wait_transaction_locked+0x8c/0xd0 start_this_handle+0x252/0x870 jbd2__journal_start+0x120/0x280 __ext4_journal_start_sb+0x11a/0x250 ext4_evict_inode+0x208/0x760 iput+0x222/0x5c0 dput+0x293/0x690 ____fput+0x145/0x2b0 task_work_run+0x7a/0xb0 exit_to_user_mode_loop+0xc0/0xd0 do_syscall_64+0x14b/0xf10 entry_SYSCALL_64_after_hwframe+0x76/0x7e NMI backtrace for cpu 0 CPU: 0 UID: 1010216 PID: 4160 Comm: Thread-7 RIP: 0010:d_walk+0x0/0x290 [..] Call Trace: <TASK> shrink_dcache_parent+0xb2/0x100 d_invalidate+0x50/0x110 ext4_rmdir+0x3a2/0x3d0 vfs_rmdir+0x9d/0x1d0 do_rmdir+0xf0/0x330 __x64_sys_unlinkat+0x34/0x50 do_syscall_64+0x61/0xf10 The problem is that ext4_rmdir() calls d_invalidate() before stopping the jbd2 transaction handle. Because d_invalidate() traverses child dentries it can encounter dying or in-use dentries. If a concurrent thread executing __dentry_kill() on a child dentry invokes ext4_evict_inode(), it requests a new transaction handle via ext4_journal_start(), which waits for all running handles to close. However, the thread executing ext4_rmdir() still holds its active transaction handle while looping in shrink_dcache_parent() waiting for the dying child dentry to complete eviction. Stop transaction handle in ext4_rmdir() before calling d_invalidate(). Cc: stable@kernel.org Fixes: b886ee3e778e ("ext4: Support case-insensitive file name lookups") Co-developed-by: Sarthak Kukreti <sarthakkukreti@google.com> Signed-off-by: Sarthak Kukreti <sarthakkukreti@google.com> Signed-off-by: Sergey Senozhatsky <senozhatsky@chromium.org> Reviewed-by: Jan Kara <jack@suse.cz> Reviewed-by: Baokun Li <libaokun@linux.alibaba.com> Reviewed-by: Zhang Yi <yi.zhang@huawei.com> Link: https://patch.msgid.link/20260827125158.4030598-1-senozhatsky@chromium.org Signed-off-by: Theodore Ts'o <tytso@mit.edu>
7 daysext4: don't report delalloc data as a hole on indirect-mapped inodesDaejun Park
When a plain lookup finds no block, ext4_ind_map_blocks() sizes the hole it reports by counting the empty subtrees under 'partial' in the on-disk indirect tree. That count knows nothing about delayed allocation, so a range holding delalloc data that has not been written back yet is reported as a plain hole. The extent-mapped path does not have this problem: it trims the hole it found at the first delayed extent before returning it. ext4_map_blocks() consults the extent status tree before it calls into the mapping layer, so a query that starts exactly on the delayed block still finds it. A query that starts earlier does not, because the hole reported for the earlier block already spans the delayed one. On a 4k-block filesystem, lseek(SEEK_DATA) from offset 0 on a file whose only data block is at logical block N and is still dirty returns: N = 0..11 direct blocks N << 12 N = 12 first single-indirect block N << 12 N = 13..1035 inside that indirect block -1 ENXIO N = 1036 first double-indirect block N << 12 Blocks 12 and 1036 survive because the query lands on the delayed block itself. Blocks 13..1035 are swallowed by the 1024-block hole reported for block 12. fiemap loses the same data for the same reason. An ext3 filesystem mounted as ext4 hits this during ordinary use: ext3 inodes stay indirect mapped, while mount -t ext4 turns on delayed allocation even though mounting ext3 with -o delalloc is explicitly rejected. install(1) from coreutils uses SEEK_DATA to locate data in its source file, so installing a sparse file that has not been written back silently produces a destination of the right size holding none of the data: truncate -s 1G img && mkfs.ext3 -F img && mount -t ext4 img mnt echo | dd of=mnt/src bs=1 count=1 seek=64K install mnt/src mnt/dst cmp mnt/src mnt/dst # differ: char 65537 Reconciling a hole with the extent status tree does not depend on how the hole was found, so instead of open coding it a second time, split the delalloc handling out of ext4_ext_determine_insert_hole() into ext4_determine_insert_hole(), which takes the hole the caller located, and call that from ext4_ind_map_blocks() as well. All the special cases and the comments explaining them stay in one place. Two things change for indirect-mapped inodes. They now cache the holes they report, as the extent-mapped path already does: ext4_punch_hole() already puts EXTENT_STATUS_HOLE on such inodes, ext4_map_blocks() and ext4_da_map_blocks() already handle a cached hole, the latter by replacing it with the delayed extent, and consecutive hole entries merge, so the tree does not grow with the length of a scan. And when a delayed extent covers the queried block itself - a race, since ext4_map_blocks() found nothing in the tree just before - the hole is now reported only up to the end of that extent instead of in full. The indirect path passes hole_start == lblk, so the "delalloc extent in front of the queried range" case cannot be reached there: __es_tree_search() never returns an extent ending before the block it was searched for. With this the sweep above returns N << 12 for every N, while the nodelalloc and extent-mapped controls are unchanged. generic/225, generic/285, generic/448 and generic/490 pass on ext4 made both with and without the extent feature. generic/286 and generic/436 pass on extent-mapped ext4 and are skipped without it, since they need fallocate. Fixes: facab4d9711e ("ext4: return hole from ext4_map_blocks()") Reported-by: Alexander Monakov <amonakov@ispras.ru> Closes: https://lore.kernel.org/linux-ext4/594c17d9-c00f-e485-96fb-cedf27ce3aa3@ispras.ru/ Suggested-by: Jan Kara <jack@suse.cz> Cc: stable@vger.kernel.org Signed-off-by: Daejun Park <daejun7.park@samsung.com> Reviewed-by: Jan Kara <jack@suse.cz> Reviewed-by: Zhang Yi <yi.zhang@huawei.com> Link: https://patch.msgid.link/20260827084829epcms2p6ecc0c39a71d0c3eebf9cb1ade77314f6@epcms2p6 Signed-off-by: Theodore Ts'o <tytso@mit.edu>
7 daysext4: skip extra isize expansion while unmountingHemanth Selam
syzbot reports a WARN from ext4_xattr_inode_create() reached through the unmount path: EXT4-fs warning (device loop0): ext4_xattr_inode_create:1485: refuse to create EA inode when umounting WARNING: fs/ext4/xattr.c:1486 at ext4_xattr_inode_lookup_create ext4_xattr_block_set ext4_expand_extra_isize_ea __ext4_expand_extra_isize __ext4_mark_inode_dirty ext4_dirty_inode __mark_inode_dirty sync_lazytime iput dentry_kill shrink_dentry_list shrink_dcache_for_umount generic_shutdown_super kill_block_super ext4_kill_sb shrink_dcache_for_umount() clears s_root before generic_shutdown_super() clears SB_ACTIVE, so during the dcache shrink the last iput() of a lazytime inode still redirties it and reaches the isize expansion. The expansion can move xattrs out to a block, and creating the EA inode for them needs s_root, which ext4_xattr_inode_create() refuses without. ext4_try_to_expand_extra_isize() already declines to expand when the superblock is not active, but that test does not cover this window. Decline while s_root is gone as well. The expansion is best effort and __ext4_mark_inode_dirty() ignores its return value, so nothing else changes; the inode can be expanded on a later mount. Running the syzbot reproducer for 60 seconds produced 3583 splats before this change and none after it, with the same number of mount cycles. Cc: stable@kernel.org Reported-by: syzbot+4b03894b6ec5753ddf24@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=4b03894b6ec5753ddf24 Fixes: f31173c19901 ("ext4: refuse to create ea block when umounted") Signed-off-by: Hemanth Selam <hemanth.selam@gmail.com> Reviewed-by: Jan Kara <jack@suse.cz> Reviewed-by: Jun Nie <jun.nie@linaro.org> Link: https://patch.msgid.link/20260824094512.2436628-1-hemanth.selam@gmail.com Signed-off-by: Theodore Ts'o <tytso@mit.edu>
7 daysext4: reject delalloc to nodelalloc before applying remount optionsguzebing
Commit 97f5ec3b166d ("ext4: prevent delalloc to nodelalloc on remount") rejects switching a mounted filesystem from delalloc to nodelalloc. However, it performs the check after ext4_apply_options() has already cleared EXT4_MOUNT_DELALLOC in the live superblock. The failure path eventually restores the bit, but leaves a window where other CPUs can observe nodelalloc. test_opt() reads s_mount_opt directly without a lock shared with remount. If a concurrent truncate's __es_remove_extent() hits this window and observes nodelalloc, it sets count_reserved to false. Delayed extent status entries are then removed without calculating their cluster reservations, leaving reserved at zero. ext4_es_remove_extent() therefore calls ext4_da_release_space() with zero, leaving i_reserved_data_blocks and s_dirtyclusters_counter elevated. When the inode is later evicted after unlink, ext4_destroy_inode() reports: i_reserved_data_blocks (...) not cleared! CPU 0 CPU 1 ksys_truncate() ... ext4_es_remove_extent() ext4_reconfigure() ext4_check_opt_consistency() __ext4_remount() ext4_apply_options() clear EXT4_MOUNT_DELALLOC __es_remove_extent() test_opt() sees !DELALLOC count_reserved = false reject delalloc -> nodelalloc restore EXT4_MOUNT_DELALLOC remove delayed ES ext4_da_release_space(0) Follow the pre-apply validation approach used by ext4_check_quota_consistency() and reject the transition in ext4_check_opt_consistency(), before ext4_apply_options() changes live state. Use mask_s_mount_opt to determine whether delalloc/nodelalloc was specified and ctx_test_mount_opt() to check the final parsed value. Fixes: 97f5ec3b166d ("ext4: prevent delalloc to nodelalloc on remount") Cc: stable@vger.kernel.org Signed-off-by: guzebing <guzebing1612@gmail.com> Reviewed-by: Ojaswin Mujoo <ojaswin@linux.ibm.com> Reviewed-by: Jan Kara <jack@suse.cz> Link: https://patch.msgid.link/20260814034855.1573759-1-guzebing1612@gmail.com Signed-off-by: Theodore Ts'o <tytso@mit.edu>
7 daysext4: fix fast commit replay failing on a read-only mountHsiu-Hsien Lee
A filesystem with fast_commit that needs recovery cannot be mounted read-only: EXT4-fs (dm-0): INFO: recovery required on readonly filesystem EXT4-fs (dm-0): write access will be enabled during recovery WARNING: CPU: 22 PID: 5544 at fs/ext4/ext4_jbd2.c:73 ext4_journal_check_start __ext4_journal_start_sb __ext4_unlink ext4_fc_replay do_one_pass jbd2_journal_recover jbd2_journal_load __ext4_fill_super JBD2: journal recovery failed EXT4-fs (dm-0): error loading journal Fast commit replay runs ext4 metadata operations instead of writing blocks through the buffer cache: ext4_fc_replay_{unlink,link,create}() reach __ext4_unlink() and __ext4_link(), which start a handle. ext4_journal_check_start() returns -EROFS on a read-only sb, and as that is not -ENOENT it propagates out of jbd2_journal_recover() and kills the whole recovery. The EXT4_FC_REPLAY check that would hand out a no-journal handle sits after the sb_rdonly() test, so replay can never complete read-only. ext4_load_journal() has already promised that write access will be enabled during recovery, so make that true for the superblock as well: clear SB_RDONLY across jbd2_journal_load() when recovery is needed on a read-only mount and the devices are writable, as ext4_orphan_cleanup() does. Unlike ext4_handle_error(), which avoids SB_RDONLY because it would need s_umount, the sb here is still inside ext4_fill_super() and not published, so nothing can observe it. The failure is not clean either: the replay handlers passing a NULL handle (ext4_fc_replay_inode(), _add_range(), _del_range()) never hit ext4_journal_check_start() and do write, leaving a partially applied fast commit behind. Reproducer, where the unlink only ever reaches the fast commit area: mke2fs -q -F -t ext4 -O fast_commit -b 4096 /dev/sdb3 262144 mount /dev/sdb3 /mnt dd if=/dev/zero of=/mnt/victim bs=4k count=1 conv=fsync sync # victim now in a full commit rm /mnt/victim dd if=/dev/zero of=/mnt/trigger bs=4k count=1 conv=fsync <crash, or snapshot the device while mounted and write it back> mount -o ro /dev/sdb3 /mnt Without this patch that mount fails; with it recovery completes and victim is gone, i.e. the UNLINK record was really replayed. Fixes: 8016e29f4362 ("ext4: fast commit recovery path") Cc: stable@vger.kernel.org Signed-off-by: Hsiu-Hsien Lee <swinds24@gmail.com> Reviewed-by: Jan Kara <jack@suse.cz> Link: https://patch.msgid.link/20260812044353.1018268-1-swinds24@gmail.com Signed-off-by: Theodore Ts'o <tytso@mit.edu>
7 daysext4: only update tune mount options when requestedguzebing
EXT4_IOC_SET_TUNE_SB_PARAM uses set_flags to indicate which superblock fields the caller wants to update. ext4_sb_setparams() checks set_flags before updating the other supported fields, but always copied mount_opts into the on-disk s_mount_opts field. As a result, callers that do not set EXT4_TUNE_FL_MOUNT_OPTS can still clear or alter the on-disk default mount options through their unused mount_opts buffer. This can affect subsequent mounts by changing the filesystem's stored default mount options. Only copy mount_opts into s_mount_opts when EXT4_TUNE_FL_MOUNT_OPTS is set, matching the set_flags semantics used by the other tuneable superblock fields. Cc: stable@kernel.org Fixes: 04a91570ac67 ("ext4: implemet new ioctls to set and get superblock parameters") Signed-off-by: guzebing <guzebing1612@gmail.com> Reviewed-by: "Darrick J. Wong" <djwong@kernel.org> Link: https://patch.msgid.link/20260810151200.1405562-1-guzebing1612@gmail.com Signed-off-by: Theodore Ts'o <tytso@mit.edu>
11 daysfsverity: hoist statx reporting of fs-verity flagAndrey Albershteyn
All filesystems, supporting fsverity, report this status by checking inode flag. Also, BTRFS was missing stat->attributes_mask, which is fixed now. Fixes: 146054090b08 ("btrfs: initial fsverity support") Cc: stable@vger.kernel.org Signed-off-by: Andrey Albershteyn <aalbersh@kernel.org> Acked-by: Eric Biggers <ebiggers@kernel.org> Reviewed-by: Christoph Hellwig <hch@lst.de> Reviewed-by: "Darrick J. Wong" <djwong@kernel.org> Link: https://patch.msgid.link/20261002223705.2175542-7-aalbersh@kernel.org Signed-off-by: Eric Biggers <ebiggers@kernel.org>
11 daysfsverity: hoist pagecache_read from f2fs/ext4 to fsverityAndrey Albershteyn
This is the same function to read from pageache. XFS will also need this, so move this to core fsverity. Note that f2fs and ext4 functions diverged a bit, as ext4 operated over folios and f2fs operated over pages. The common one will operate over folios. Reviewed-by: Darrick J. Wong <djwong@kernel.org> Reviewed-by: Christoph Hellwig <hch@lst.de> Acked-by: Eric Biggers <ebiggers@kernel.org> Signed-off-by: Andrey Albershteyn <aalbersh@kernel.org> Link: https://patch.msgid.link/20261002223705.2175542-5-aalbersh@kernel.org Signed-off-by: Eric Biggers <ebiggers@kernel.org>
11 daysfsverity: pass digest size and hash of the all-zeroes block to ->writeAndrey Albershteyn
Let filesystem iterate over hashes in the block and check if these are hashes of zeroed data blocks. XFS will use this to decide if it want to store tree block full of these hashes. Signed-off-by: Andrey Albershteyn <aalbersh@kernel.org> Reviewed-by: "Darrick J. Wong" <djwong@kernel.org> Acked-by: Eric Biggers <ebiggers@kernel.org> Acked-by: David Sterba <dsterba@suse.com> Link: https://patch.msgid.link/20261002223705.2175542-4-aalbersh@kernel.org Signed-off-by: Eric Biggers <ebiggers@kernel.org>
2026-09-23fs: port mnt_idmap() and file_mnt_idmap() to const mnt_idmapChristian Brauner
Now that everything takes a mnt_idmap as const store a const pointer in struct vfsmount, struct mount_kattr and struct kstatmount and return one from mnt_idmap(), file_mnt_idmap() and ovl_upper_mnt_idmap(). Finally, also make mnt_idmap_get() return a const pointer. Also convert the remaining local variables that are initialized from the accessors. alloc_mnt_idmap() keeps returning a non-const pointer. It is the only place where an idmapping is actually written to. Link: https://patch.msgid.link/20260901-work-idmap-const-v1-26-54ccd48e100b@kernel.org Reviewed-by: Jan Kara <jack@suse.cz> Reviewed-by: Seth Forshee <sforshee@kernel.org> Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>