diff options
| author | Mark Brown <broonie@kernel.org> | 2026-10-14 12:45:06 +0100 |
|---|---|---|
| committer | Mark Brown <broonie@kernel.org> | 2026-10-14 12:45:06 +0100 |
| commit | 4bf16fec7a178dbc5e8d521091dbcae9dfe22cbd (patch) | |
| tree | 8baec61a2e632f777df7633fa1f5058969b7dee4 | |
| parent | 72704dd26c8c8f7b11e78c9f7ed18831db88be62 (diff) | |
| parent | 35aefefd7aba0801bab635b11943f932626be3b2 (diff) | |
| download | linux-next-4bf16fec7a178dbc5e8d521091dbcae9dfe22cbd.tar.gz linux-next-4bf16fec7a178dbc5e8d521091dbcae9dfe22cbd.zip | |
Merge branch 'dev' of https://git.kernel.org/pub/scm/linux/kernel/git/tytso/ext4.git
| -rw-r--r-- | fs/ext4/balloc.c | 2 | ||||
| -rw-r--r-- | fs/ext4/ext4.h | 96 | ||||
| -rw-r--r-- | fs/ext4/ext4_jbd2.c | 8 | ||||
| -rw-r--r-- | fs/ext4/ext4_jbd2.h | 7 | ||||
| -rw-r--r-- | fs/ext4/extents.c | 77 | ||||
| -rw-r--r-- | fs/ext4/file.c | 20 | ||||
| -rw-r--r-- | fs/ext4/ialloc.c | 1 | ||||
| -rw-r--r-- | fs/ext4/indirect.c | 17 | ||||
| -rw-r--r-- | fs/ext4/inode.c | 1182 | ||||
| -rw-r--r-- | fs/ext4/ioctl.c | 3 | ||||
| -rw-r--r-- | fs/ext4/mballoc.c | 6 | ||||
| -rw-r--r-- | fs/ext4/migrate.c | 2 | ||||
| -rw-r--r-- | fs/ext4/move_extent.c | 11 | ||||
| -rw-r--r-- | fs/ext4/namei.c | 11 | ||||
| -rw-r--r-- | fs/ext4/page-io.c | 201 | ||||
| -rw-r--r-- | fs/ext4/resize.c | 19 | ||||
| -rw-r--r-- | fs/ext4/super.c | 67 | ||||
| -rw-r--r-- | fs/jbd2/journal.c | 2 | ||||
| -rw-r--r-- | include/linux/jbd2.h | 2 | ||||
| -rw-r--r-- | include/trace/events/ext4.h | 207 |
20 files changed, 1747 insertions, 194 deletions
diff --git a/fs/ext4/balloc.c b/fs/ext4/balloc.c index 52f4c5169f91..778fe8788f06 100644 --- a/fs/ext4/balloc.c +++ b/fs/ext4/balloc.c @@ -329,7 +329,7 @@ struct ext4_group_info *ext4_get_group_info(struct super_block *sb, struct ext4_group_info **grp_info; long indexv, indexh; - if (unlikely(group >= EXT4_SB(sb)->s_groups_count)) + if (unlikely(group >= ext4_get_groups_count(sb))) return NULL; if (unlikely(!EXT4_SB(sb)->s_group_info)) return NULL; diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h index af70bb26bb54..e6db5a687a5f 100644 --- a/fs/ext4/ext4.h +++ b/fs/ext4/ext4.h @@ -1208,8 +1208,10 @@ struct ext4_inode_info { /* Lock protecting lists below */ spinlock_t i_completed_io_lock; /* - * Completed IOs that need unwritten extents handling and have - * transaction reserved + * Completed IOs that need unwritten extents handling and have a + * transaction reserved for the buffer_head writeback path, and + * also used by the iomap writeback path to queue ioends needing + * unwritten extents conversion, i_disksize update, etc. */ struct list_head i_rsv_conversion_list; struct work_struct i_rsv_conversion_work; @@ -1319,6 +1321,7 @@ struct ext4_inode_info { * scanning in mballoc */ #define EXT4_MOUNT2_ABORT 0x00000100 /* Abort filesystem */ +#define EXT4_MOUNT2_BUFFERED_IOMAP 0x00000200 /* Use iomap for buffered I/O */ #define clear_opt(sb, opt) EXT4_SB(sb)->s_mount_opt &= \ ~EXT4_MOUNT_##opt @@ -2051,6 +2054,10 @@ enum { EXT4_STATE_FC_FLUSHING_DATA, /* Fast commit flushing data */ EXT4_STATE_ORPHAN_FILE, /* Inode orphaned in orphan file */ EXT4_STATE_FC_REQUEUE, /* Inode modified during fast commit */ + EXT4_STATE_BUFFERED_IOMAP, /* Inode use iomap for buffered IO */ + EXT4_STATE_DISKSIZE_GROW_PENDING, + /* Has zeroed EOF block straddles + * i_disksize awaiting writeback */ }; #define EXT4_INODE_BIT_FNS(name, field, offset) \ @@ -2150,6 +2157,12 @@ static inline struct mapping_metadata_bhs *ext4_i_metadata_bhs( return READ_ONCE(EXT4_I(inode)->i_metadata_bhs); } +/* Whether the inode pass through the iomap infrastructure for buffered I/O */ +static inline bool ext4_inode_buffered_iomap(struct inode *inode) +{ + return ext4_test_inode_state(inode, EXT4_STATE_BUFFERED_IOMAP); +} + /* * Codes for operating systems */ @@ -3160,6 +3173,8 @@ int ext4_walk_page_buffers(handle_t *handle, int do_journal_get_write_access(handle_t *handle, struct inode *inode, struct buffer_head *bh); void ext4_set_inode_mapping_order(struct inode *inode); +void ext4_enable_buffered_iomap(struct inode *inode); +int ext4_nonda_switch(struct super_block *sb); #define FALL_BACK_TO_NONDELALLOC 1 #define EXT4_WRITE_DATA_INLINE 2 @@ -3211,6 +3226,12 @@ extern int ext4_chunk_trans_blocks(struct inode *, int nrblocks); extern int ext4_chunk_trans_extent(struct inode *inode, int nrblocks); extern int ext4_meta_trans_blocks(struct inode *inode, int lblocks, int pextents, int alloc_extents); +void ext4_iomap_clear_disksize_pending(struct inode *inode); +void ext4_iomap_wait_disksize_pending(struct inode *inode); +unsigned int ext4_iomap_get_disksize_pending_range(struct inode *inode, + loff_t *start); +extern int ext4_iomap_sync_zeroed_eof(struct inode *inode, + loff_t offset, loff_t end); extern int ext4_block_zero_eof(struct inode *inode, loff_t from, loff_t end); #define EXT4_PARTIAL_ZERO_START 0x1 @@ -3544,16 +3565,12 @@ static inline void ext4_isize_set(struct ext4_inode *raw_inode, loff_t i_size) } /* - * Reading s_groups_count requires using smp_rmb() afterwards. See - * the locking protocol documented in the comments of ext4_group_add() - * in resize.c + * Reading s_groups_count uses acquire semantics. */ static inline ext4_group_t ext4_get_groups_count(struct super_block *sb) { - ext4_group_t ngroups = EXT4_SB(sb)->s_groups_count; - - smp_rmb(); - return ngroups; + /* Pairs with smp_store_release() in ext4_update_super() */ + return smp_load_acquire(&EXT4_SB(sb)->s_groups_count); } static inline ext4_group_t ext4_flex_group(struct ext4_sb_info *sbi, @@ -3590,30 +3607,67 @@ do { \ #define EXT4_FREECLUSTERS_WATERMARK 0 #endif -/* Update i_disksize. Requires i_rwsem to avoid races with truncate */ +/* + * Update i_disksize. Requires i_rwsem to avoid races with truncate. + * + * In the iomap buffered I/O path, the EXT4_STATE_DISKSIZE_GROW_PENDING + * inode state bit indicates that the zeroed EOF partial block which + * straddles i_disksize is still waiting writeback. In that case, + * i_disksize will be updated after the pending zeroed data has been + * written out. + */ static inline void ext4_update_i_disksize(struct inode *inode, loff_t newsize) { WARN_ON_ONCE(S_ISREG(inode->i_mode) && !inode_is_locked(inode)); down_write(&EXT4_I(inode)->i_data_sem); - if (newsize > EXT4_I(inode)->i_disksize) + if (newsize > EXT4_I(inode)->i_disksize && + !ext4_test_inode_state(inode, EXT4_STATE_DISKSIZE_GROW_PENDING)) WRITE_ONCE(EXT4_I(inode)->i_disksize, newsize); up_write(&EXT4_I(inode)->i_data_sem); } -/* Update i_size, i_disksize. Requires i_rwsem to avoid races with truncate */ +static inline void __ext4_set_i_disksize(struct inode *inode, loff_t newsize) +{ + WARN_ON_ONCE(!rwsem_is_locked(&EXT4_I(inode)->i_data_sem)); + + if (newsize < EXT4_I(inode)->i_disksize || + !ext4_test_inode_state(inode, EXT4_STATE_DISKSIZE_GROW_PENDING)) + WRITE_ONCE(EXT4_I(inode)->i_disksize, newsize); +} + +/* + * Update i_size and i_disksize to @newsize. Requires i_rwsem to avoid + * races with truncate. + * + * In the iomap buffered I/O path, i_disksize is updated only if no zeroed + * pending block straddles i_disksize (EXT4_STATE_DISKSIZE_GROW_PENDING + * clear), otherwise the ioend worker for the pending block will advance + * i_disksize once the pending block is written back. Both updates happen + * under i_data_sem so that the writeback ioend worker can always see the + * latest i_size under the same semaphore. + * + * Returns 0 if nothing changed, 1 if i_size was raised, 2 if i_disksize + * was raised, or 3 if both were. + */ static inline int ext4_update_inode_size(struct inode *inode, loff_t newsize) { int changed = 0; + if (newsize <= inode->i_size && newsize <= EXT4_I(inode)->i_disksize) + return 0; + + down_write(&EXT4_I(inode)->i_data_sem); if (newsize > inode->i_size) { i_size_write(inode, newsize); changed = 1; } - if (newsize > EXT4_I(inode)->i_disksize) { - ext4_update_i_disksize(inode, newsize); + if (newsize > EXT4_I(inode)->i_disksize && + !ext4_test_inode_state(inode, EXT4_STATE_DISKSIZE_GROW_PENDING)) { + WRITE_ONCE(EXT4_I(inode)->i_disksize, newsize); changed |= 2; } + up_write(&EXT4_I(inode)->i_data_sem); return changed; } @@ -3907,6 +3961,9 @@ extern void ext4_ext_tree_init(handle_t *handle, struct inode *inode); extern int ext4_ext_index_trans_blocks(struct inode *inode, int extents); extern int ext4_ext_map_blocks(handle_t *handle, struct inode *inode, struct ext4_map_blocks *map, int flags); +ext4_lblk_t ext4_determine_insert_hole(struct inode *inode, ext4_lblk_t lblk, + ext4_lblk_t hole_start, + ext4_lblk_t hole_len); extern int ext4_ext_truncate(handle_t *, struct inode *); extern int ext4_ext_remove_space(struct inode *inode, ext4_lblk_t start, ext4_lblk_t end); @@ -3971,6 +4028,12 @@ extern int ext4_move_extents(struct file *o_filp, struct file *d_filp, __u64 len, __u64 *moved_len); /* page-io.c */ +/* + * The I/O range covers the zeroed EOF block that straddles i_disksize + * and will advance it upon completion. + */ +#define EXT4_IOMAP_IOEND_DISKSIZE_GROW_IO 1UL + extern int __init ext4_init_pageio(void); extern void ext4_exit_pageio(void); extern ext4_io_end_t *ext4_init_io_end(struct inode *inode, gfp_t flags); @@ -3985,6 +4048,8 @@ void ext4_bio_write_folio(struct ext4_io_submit *io, struct folio *page, size_t len); extern struct ext4_io_end_vec *ext4_alloc_io_end_vec(ext4_io_end_t *io_end); extern struct ext4_io_end_vec *ext4_last_io_end_vec(ext4_io_end_t *io_end); +extern void ext4_iomap_end_io(struct work_struct *work); +extern void ext4_iomap_end_bio(struct bio *bio); /* mmp.c */ extern int ext4_multi_mount_protect(struct super_block *, ext4_fsblk_t); @@ -4042,6 +4107,9 @@ static inline void ext4_clear_io_unwritten_flag(ext4_io_end_t *io_end) extern const struct iomap_ops ext4_iomap_ops; extern const struct iomap_ops ext4_iomap_report_ops; +extern const struct iomap_ops ext4_iomap_buffered_write_ops; +extern const struct iomap_ops ext4_iomap_buffered_da_write_ops; +extern const struct iomap_write_ops ext4_iomap_write_ops; int ext4_iomap_begin(struct inode *inode, loff_t offset, loff_t length, unsigned flags, struct iomap *iomap, struct iomap *srcmap); diff --git a/fs/ext4/ext4_jbd2.c b/fs/ext4/ext4_jbd2.c index 53ddedb52a6f..a4664ddecdcd 100644 --- a/fs/ext4/ext4_jbd2.c +++ b/fs/ext4/ext4_jbd2.c @@ -17,8 +17,12 @@ int ext4_inode_journal_mode(struct inode *inode) test_opt(inode->i_sb, DATA_FLAGS) == EXT4_MOUNT_JOURNAL_DATA || (ext4_test_inode_flag(inode, EXT4_INODE_JOURNAL_DATA) && !test_opt(inode->i_sb, DELALLOC))) { - /* We do not support data journalling for encrypted data */ - if (S_ISREG(inode->i_mode) && IS_ENCRYPTED(inode)) + /* + * We do not support data journalling for encrypted data + * and buffered IOMAP path. + */ + if (S_ISREG(inode->i_mode) && + (IS_ENCRYPTED(inode) || ext4_inode_buffered_iomap(inode))) return EXT4_INODE_ORDERED_DATA_MODE; /* ordered */ return EXT4_INODE_JOURNAL_DATA_MODE; /* journal data */ } diff --git a/fs/ext4/ext4_jbd2.h b/fs/ext4/ext4_jbd2.h index 2fbf48b3dfe2..be54e93bde0b 100644 --- a/fs/ext4/ext4_jbd2.h +++ b/fs/ext4/ext4_jbd2.h @@ -379,7 +379,12 @@ static inline int ext4_should_journal_data(struct inode *inode) static inline int ext4_should_order_data(struct inode *inode) { - return ext4_inode_journal_mode(inode) & EXT4_INODE_ORDERED_DATA_MODE; + /* + * inodes using the iomap buffered I/O path do not use the + * data=ordered mode. + */ + return !ext4_inode_buffered_iomap(inode) && + (ext4_inode_journal_mode(inode) & EXT4_INODE_ORDERED_DATA_MODE); } static inline int ext4_should_writeback_data(struct inode *inode) diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c index 3bf0a6cdbec2..4ea68200b945 100644 --- a/fs/ext4/extents.c +++ b/fs/ext4/extents.c @@ -4190,21 +4190,19 @@ static int get_implied_cluster_alloc(struct super_block *sb, } /* - * Determine hole length around the given logical block, first try to - * locate and expand the hole from the given @path, and then adjust it - * if it's partially or completely converted to delayed extents, insert - * it into the extent cache tree if it's indeed a hole, finally return - * the length of the determined extent. + * Adjust the hole [@hole_start, @hole_start + @hole_len) the caller found + * around the queried block @lblk if it's partially or completely converted + * to delayed extents, insert it into the extent cache tree if it's indeed a + * hole, finally return the length of the determined extent starting at + * @lblk. @hole_start must not be behind @lblk. */ -static ext4_lblk_t ext4_ext_determine_insert_hole(struct inode *inode, - struct ext4_ext_path *path, - ext4_lblk_t lblk) +ext4_lblk_t ext4_determine_insert_hole(struct inode *inode, ext4_lblk_t lblk, + ext4_lblk_t hole_start, + ext4_lblk_t hole_len) { - ext4_lblk_t hole_start, len; + ext4_lblk_t len = hole_len; struct extent_status es; - hole_start = lblk; - len = ext4_ext_find_hole(inode, path, &hole_start); again: ext4_es_find_extent_range(inode, &ext4_es_is_delayed, hole_start, hole_start + len - 1, &es); @@ -4371,9 +4369,11 @@ int ext4_ext_map_blocks(handle_t *handle, struct inode *inode, * we couldn't try to create block if flags doesn't contain EXT4_GET_BLOCKS_CREATE */ if ((flags & EXT4_GET_BLOCKS_CREATE) == 0) { - ext4_lblk_t len; + ext4_lblk_t hole_start = map->m_lblk, len; - len = ext4_ext_determine_insert_hole(inode, path, map->m_lblk); + len = ext4_ext_find_hole(inode, path, &hole_start); + len = ext4_determine_insert_hole(inode, map->m_lblk, + hole_start, len); map->m_pblk = 0; map->m_len = min_t(unsigned int, map->m_len, len); @@ -4561,7 +4561,7 @@ int ext4_ext_truncate(handle_t *handle, struct inode *inode) */ /* we have to know where to truncate from in crash case */ - EXT4_I(inode)->i_disksize = inode->i_size; + __ext4_set_i_disksize(inode, inode->i_size); err = ext4_mark_inode_dirty(handle, inode); if (err) return err; @@ -4876,6 +4876,16 @@ static long ext4_zero_range(struct file *file, loff_t offset, return ret; } + /* + * In SYNC mode, sync the pending zeroed EOF block to ensure the + * i_disksize update is persisted. + */ + if (((file->f_flags & O_SYNC) || IS_SYNC(inode)) && new_size) { + ret = ext4_iomap_sync_zeroed_eof(inode, 0, LLONG_MAX); + if (ret) + return ret; + } + handle = ext4_journal_start(inode, EXT4_HT_MISC, 1); if (IS_ERR(handle)) { ret = PTR_ERR(handle); @@ -4928,10 +4938,20 @@ static long ext4_do_fallocate(struct file *file, loff_t offset, if (ret) goto out; - if (((file->f_flags & O_SYNC) || IS_SYNC(inode)) && - EXT4_SB(inode->i_sb)->s_journal) { - ret = ext4_fc_commit(EXT4_SB(inode->i_sb)->s_journal, - EXT4_I(inode)->i_sync_tid); + if ((file->f_flags & O_SYNC) || IS_SYNC(inode)) { + /* + * Sync the pending zeroed EOF block to ensure the + * i_disksize update is persisted. + */ + if (new_size) { + ret = ext4_iomap_sync_zeroed_eof(inode, 0, LLONG_MAX); + if (ret) + goto out; + } + if (EXT4_SB(inode->i_sb)->s_journal) { + ret = ext4_fc_commit(EXT4_SB(inode->i_sb)->s_journal, + EXT4_I(inode)->i_sync_tid); + } } out: trace_ext4_fallocate_exit(inode, offset, @@ -5663,6 +5683,14 @@ static int ext4_collapse_range(struct file *file, loff_t offset, loff_t len) return -EINVAL; /* + * Persist the pending zeroed EOF block to ensure i_disksize + * can be safely updated thereafter. + */ + ret = ext4_iomap_sync_zeroed_eof(inode, 0, LLONG_MAX); + if (ret) + return ret; + + /* * Write tail of the last page before removed range and data that * will be shifted since they will get removed from the page cache * below. We are also protected from pages becoming dirty by @@ -5711,7 +5739,7 @@ static int ext4_collapse_range(struct file *file, loff_t offset, loff_t len) new_size = inode->i_size - len; i_size_write(inode, new_size); - EXT4_I(inode)->i_disksize = new_size; + __ext4_set_i_disksize(inode, new_size); up_write(&EXT4_I(inode)->i_data_sem); ret = ext4_mark_inode_dirty(handle, inode); @@ -5765,6 +5793,14 @@ static int ext4_insert_range(struct file *file, loff_t offset, loff_t len) return -EFBIG; /* + * Persist the pending zeroed EOF block to ensure i_disksize + * can be safely updated thereafter. + */ + ret = ext4_iomap_sync_zeroed_eof(inode, 0, LLONG_MAX); + if (ret) + return ret; + + /* * Write out all dirty pages. Need to round down to align start offset * to page size boundary for page size > block size. */ @@ -5783,8 +5819,7 @@ static int ext4_insert_range(struct file *file, loff_t offset, loff_t len) ext4_fc_mark_ineligible(sb, EXT4_FC_REASON_FALLOC_RANGE, handle); /* Expand file to avoid data loss if there is error while shifting */ - inode->i_size += len; - EXT4_I(inode)->i_disksize += len; + ext4_update_inode_size(inode, inode->i_size + len); ret = ext4_mark_inode_dirty(handle, inode); if (ret) goto out_handle; diff --git a/fs/ext4/file.c b/fs/ext4/file.c index 374b4bc25bd5..50d3c92709c8 100644 --- a/fs/ext4/file.c +++ b/fs/ext4/file.c @@ -330,6 +330,21 @@ static ssize_t ext4_write_checks(struct kiocb *iocb, struct iov_iter *from) return count; } +static ssize_t ext4_iomap_buffered_write(struct kiocb *iocb, + struct iov_iter *from) +{ + struct inode *inode = file_inode(iocb->ki_filp); + const struct iomap_ops *iomap_ops; + + if (test_opt(inode->i_sb, DELALLOC) && !ext4_nonda_switch(inode->i_sb)) + iomap_ops = &ext4_iomap_buffered_da_write_ops; + else + iomap_ops = &ext4_iomap_buffered_write_ops; + + return iomap_file_buffered_write(iocb, from, iomap_ops, + &ext4_iomap_write_ops, NULL); +} + static ssize_t ext4_buffered_write_iter(struct kiocb *iocb, struct iov_iter *from) { @@ -351,7 +366,10 @@ static ssize_t ext4_buffered_write_iter(struct kiocb *iocb, if (ret <= 0) goto out; - ret = generic_perform_write(iocb, from); + if (ext4_inode_buffered_iomap(inode)) + ret = ext4_iomap_buffered_write(iocb, from); + else + ret = generic_perform_write(iocb, from); out: inode_unlock(inode); diff --git a/fs/ext4/ialloc.c b/fs/ext4/ialloc.c index a5831fc536db..f97a2f4904eb 100644 --- a/fs/ext4/ialloc.c +++ b/fs/ext4/ialloc.c @@ -1346,6 +1346,7 @@ got: } } + ext4_enable_buffered_iomap(inode); ext4_set_inode_mapping_order(inode); ext4_update_inode_fsync_trans(handle, inode, 1); diff --git a/fs/ext4/indirect.c b/fs/ext4/indirect.c index 5aec759eed70..a495080df710 100644 --- a/fs/ext4/indirect.c +++ b/fs/ext4/indirect.c @@ -586,6 +586,23 @@ int ext4_ind_map_blocks(handle_t *handle, struct inode *inode, for (i = partial - chain + 1; i < depth; i++) count = count * epb + (epb - offsets[i] - 1); count++; + + /* + * The count knows nothing about delayed allocation, so let + * the common helper reconcile it with the extent status + * tree. Only when the lookup really did find a hole: + * ext4_get_branch() also returns a non-NULL partial when it + * failed, and caching a hole for a range it could not read + * would answer every later read from that cache instead of + * retrying. Clamp the count first, a subtree can be bigger + * than the logical block space when the block size is large. + */ + if (!err) { + count = umin(count, EXT_MAX_BLOCKS - map->m_lblk); + count = ext4_determine_insert_hole(inode, map->m_lblk, + map->m_lblk, count); + } + /* Fill in size of a hole we found */ map->m_pblk = 0; map->m_len = umin(map->m_len, count); diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c index 26f0f9714f03..69d7050f41ab 100644 --- a/fs/ext4/inode.c +++ b/fs/ext4/inode.c @@ -44,6 +44,7 @@ #include <linux/iversion.h> #include "ext4_jbd2.h" +#include "ext4_extents.h" #include "xattr.h" #include "acl.h" #include "truncate.h" @@ -125,6 +126,72 @@ void ext4_inode_csum_set(struct inode *inode, struct ext4_inode *raw, raw->i_checksum_hi = cpu_to_le16(csum >> 16); } +/* + * Clear the disksize-grow-pending state and wake up all waiters. + * Called when the pending zeroed EOF block which straddles i_disksize + * has completed writeback or its folio is discarded. + */ +void ext4_iomap_clear_disksize_pending(struct inode *inode) +{ + trace_ext4_iomap_clear_disksize_pending(inode); + ext4_clear_inode_state(inode, EXT4_STATE_DISKSIZE_GROW_PENDING); + /* + * Make sure clearing of EXT4_STATE_DISKSIZE_GROW_PENDING is + * visible before we send the wakeup. Pairs with the implicit + * barrier in prepare_to_wait() inside wait_on_bit() in + * ext4_iomap_wait_disksize_pending(). + */ + smp_mb(); + wake_up_bit(ext4_inode_state_wait_word(inode), + ext4_inode_state_wait_bit(EXT4_STATE_DISKSIZE_GROW_PENDING)); +} + +/* + * Wait for the disksize-grow-pending zeroed EOF block which straddles + * i_disksize to be written back or cleared. + */ +void ext4_iomap_wait_disksize_pending(struct inode *inode) +{ + trace_ext4_iomap_wait_disksize_pending(inode); + wait_on_bit(ext4_inode_state_wait_word(inode), + ext4_inode_state_wait_bit(EXT4_STATE_DISKSIZE_GROW_PENDING), + TASK_UNINTERRUPTIBLE); +} + +/* + * Get the range of the disksize-grow-pending zeroed EOF block range + * which straddles i_disksize if the EXT4_STATE_DISKSIZE_GROW_PENDING + * bit is set. + * + * Return the pending range, or zero if the BIT has already been cleared. + */ +unsigned int ext4_iomap_get_disksize_pending_range(struct inode *inode, + loff_t *start) +{ + unsigned int blocksize = i_blocksize(inode); + loff_t disksize; + + if (!ext4_test_inode_state(inode, EXT4_STATE_DISKSIZE_GROW_PENDING)) + return 0; + + /* + * The pending bit should be set only when i_disksize is not + * block-size aligned. While set, i_disksize must not be advanced, + * and the bit must be cleared when i_disksize is shrunk. + */ + down_read(&EXT4_I(inode)->i_data_sem); + disksize = READ_ONCE(EXT4_I(inode)->i_disksize); + if (!ext4_test_inode_state(inode, EXT4_STATE_DISKSIZE_GROW_PENDING) || + WARN_ON_ONCE(IS_ALIGNED(disksize, blocksize))) { + up_read(&EXT4_I(inode)->i_data_sem); + return 0; + } + + up_read(&EXT4_I(inode)->i_data_sem); + *start = disksize; + return blocksize - (disksize & (blocksize - 1)); +} + static inline int ext4_begin_ordered_truncate(struct inode *inode, loff_t new_size) { @@ -208,6 +275,8 @@ void ext4_evict_inode(struct inode *inode) if (ext4_should_order_data(inode)) ext4_begin_ordered_truncate(inode, 0); + if (ext4_inode_buffered_iomap(inode)) + ext4_iomap_clear_disksize_pending(inode); truncate_inode_pages_final(&inode->i_data); /* @@ -703,7 +772,8 @@ int ext4_map_blocks(handle_t *handle, struct inode *inode, struct extent_status es; int retval; int ret = 0; - unsigned int orig_mlen = map->m_len; + bool internal_handle = false; + unsigned int orig_mlen; #ifdef ES_AGGRESSIVE_TEST struct ext4_map_blocks orig_map; @@ -719,6 +789,7 @@ int ext4_map_blocks(handle_t *handle, struct inode *inode, */ if (unlikely(map->m_len > INT_MAX)) map->m_len = INT_MAX; + orig_mlen = map->m_len; /* We can handle the block number less than EXT_MAX_BLOCKS */ if (unlikely(map->m_lblk >= EXT_MAX_BLOCKS)) @@ -734,6 +805,7 @@ int ext4_map_blocks(handle_t *handle, struct inode *inode, else ext4_check_map_extents_env(inode); +create_retry: /* Lookup extent status tree firstly */ if (ext4_es_lookup_extent(inode, map->m_lblk, NULL, &es, &map->m_seq)) { if (ext4_es_is_written(&es) || ext4_es_is_unwritten(&es)) { @@ -784,12 +856,16 @@ int ext4_map_blocks(handle_t *handle, struct inode *inode, down_read(&EXT4_I(inode)->i_data_sem); retval = ext4_map_query_blocks(handle, inode, map, flags); up_read((&EXT4_I(inode)->i_data_sem)); + if (retval < 0) + goto out_handle; found: if (retval > 0 && map->m_flags & EXT4_MAP_MAPPED) { ret = check_block_validity(inode, map); - if (ret != 0) - return ret; + if (ret != 0) { + retval = ret; + goto out_handle; + } } /* If it is only a block(s) look up */ @@ -802,15 +878,38 @@ found: * Note that if blocks have been preallocated * ext4_ext_map_blocks() returns with buffer head unmapped */ - if (retval > 0 && map->m_flags & EXT4_MAP_MAPPED) + if (retval > 0) { /* - * If we need to convert extent to unwritten - * we continue and do the actual work in - * ext4_ext_map_blocks() + * If we need to convert written extent to unwritten or + * convert unwritten extent to written, continue and do + * the actual work in ext4_ext_map_blocks(). */ - if (!(flags & EXT4_GET_BLOCKS_CONVERT_UNWRITTEN)) - return retval; + if (map->m_flags & EXT4_MAP_MAPPED && + !(flags & EXT4_GET_BLOCKS_CONVERT_UNWRITTEN)) + goto out_handle; + if (map->m_flags & EXT4_MAP_UNWRITTEN && + (flags & EXT4_GET_BLOCKS_UNWRIT_EXT) && + !(flags & EXT4_GET_BLOCKS_CONVERT)) { + /* Contains EXT4_GET_BLOCKS_CREATE - mark mapped. */ + map->m_flags |= EXT4_MAP_MAPPED; + goto out_handle; + } + } else if (retval == 0) { + /* + * Do not allocate blocks for holes in the context of + * data submission path. + */ + if (!map->m_flags && (flags & EXT4_GET_BLOCKS_IO_SUBMIT)) + goto out_handle; + } + if (!handle) { + handle = ext4_journal_start(inode, EXT4_HT_MAP_BLOCKS, + ext4_chunk_trans_blocks(inode, orig_mlen)); + if (IS_ERR(handle)) + return PTR_ERR(handle); + internal_handle = true; + } ext4_fc_track_inode(handle, inode); /* @@ -820,18 +919,33 @@ found: * with create == 1 flag. */ down_write(&EXT4_I(inode)->i_data_sem); + + /* + * Check the validity of the mapping found via the extent status + * tree or the disk query. A racing truncate may have changed the + * extent, since writeback does not hold i_rwsem or the folio locks + * covering the full extent. + */ + if (map->m_seq != READ_ONCE(EXT4_I(inode)->i_es_seq)) { + up_write(&EXT4_I(inode)->i_data_sem); + map->m_flags = 0; + map->m_len = orig_mlen; + goto create_retry; + } retval = ext4_map_create_blocks(handle, inode, map, flags); up_write((&EXT4_I(inode)->i_data_sem)); if (retval < 0) ext_debug(inode, "failed with err %d\n", retval); if (retval <= 0) - return retval; + goto out_handle; if (map->m_flags & EXT4_MAP_MAPPED) { ret = check_block_validity(inode, map); - if (ret != 0) - return ret; + if (ret != 0) { + retval = ret; + goto out_handle; + } /* * Inodes with freshly allocated blocks where contents will be @@ -852,12 +966,18 @@ found: else ret = ext4_jbd2_inode_add_write(handle, inode, start_byte, length); - if (ret) - return ret; + if (ret) { + retval = ret; + goto out_handle; + } } } ext4_fc_track_range(handle, inode, map->m_lblk, map->m_lblk + map->m_len - 1); + +out_handle: + if (internal_handle) + ext4_journal_stop(handle); return retval; } @@ -916,6 +1036,9 @@ static int _ext4_get_block(struct inode *inode, sector_t iblock, if (ext4_has_inline_data(inode)) return -ERANGE; + /* inode using the iomap buffered I/O path should not go here. */ + if (WARN_ON_ONCE(ext4_inode_buffered_iomap(inode))) + return -EINVAL; map.m_lblk = iblock; map.m_len = bh->b_size >> inode->i_blkbits; @@ -1921,7 +2044,7 @@ static int ext4_da_map_blocks(struct inode *inode, struct ext4_map_blocks *map) ext4_check_map_extents_env(inode); /* Lookup extent status tree firstly */ - if (ext4_es_lookup_extent(inode, map->m_lblk, NULL, &es, NULL)) { + if (ext4_es_lookup_extent(inode, map->m_lblk, NULL, &es, &map->m_seq)) { map->m_len = min_t(unsigned int, map->m_len, es.es_len - (map->m_lblk - es.es_lblk)); @@ -1974,7 +2097,7 @@ add_delayed: * is held in write mode, before inserting a new da entry in * the extent status tree. */ - if (ext4_es_lookup_extent(inode, map->m_lblk, NULL, &es, NULL)) { + if (ext4_es_lookup_extent(inode, map->m_lblk, NULL, &es, &map->m_seq)) { map->m_len = min_t(unsigned int, map->m_len, es.es_len - (map->m_lblk - es.es_lblk)); @@ -1990,7 +2113,7 @@ add_delayed: } } - map->m_flags |= EXT4_MAP_DELAYED; + map->m_flags |= EXT4_MAP_DELAYED | EXT4_MAP_NEW; retval = ext4_insert_delayed_blocks(inode, map->m_lblk, map->m_len); if (!retval) map->m_seq = READ_ONCE(EXT4_I(inode)->i_es_seq); @@ -2787,6 +2910,13 @@ static int ext4_do_writepages(struct mpage_da_data *mpd) goto out_writepages; /* + * Does ext4_change_inode_journal_flag() change the inode's + * buffered I/O path? + */ + if (ext4_inode_buffered_iomap(inode)) + goto out_writepages; + + /* * If the filesystem has aborted, it is read-only, so return * right away instead of dumping stack traces later on that * will obscure the real source of the problem. We test @@ -3087,7 +3217,7 @@ static int ext4_dax_writepages(struct address_space *mapping, return ret; } -static int ext4_nonda_switch(struct super_block *sb) +int ext4_nonda_switch(struct super_block *sb) { s64 free_clusters, dirty_clusters; struct ext4_sb_info *sbi = EXT4_SB(sb); @@ -3459,6 +3589,15 @@ static bool ext4_inode_datasync_dirty(struct inode *inode) return inode_state_read_once(inode) & I_DIRTY_DATASYNC; } +static bool ext4_iomap_valid(struct inode *inode, const struct iomap *iomap) +{ + return iomap->validity_cookie == READ_ONCE(EXT4_I(inode)->i_es_seq); +} + +const struct iomap_write_ops ext4_iomap_write_ops = { + .iomap_valid = ext4_iomap_valid, +}; + static void ext4_set_iomap(struct inode *inode, struct iomap *iomap, struct ext4_map_blocks *map, loff_t offset, loff_t length, unsigned int flags) @@ -3493,6 +3632,8 @@ static void ext4_set_iomap(struct inode *inode, struct iomap *iomap, !ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS)) iomap->flags |= IOMAP_F_MERGED; + iomap->validity_cookie = map->m_seq; + /* * Flags passed to ext4_map_blocks() for direct I/O writes can result * in m_flags having both EXT4_MAP_MAPPED and EXT4_MAP_UNWRITTEN bits @@ -3907,6 +4048,468 @@ const struct iomap_ops ext4_iomap_report_ops = { .iomap_next = ext4_iomap_next_report, }; +static int ext4_iomap_map_blocks(struct inode *inode, loff_t offset, + loff_t length, struct ext4_map_blocks *map, + int flags) +{ + u8 blkbits = inode->i_blkbits; + + /* inode using the buffer_head buffered I/O path should not go here. */ + if (WARN_ON_ONCE(!ext4_inode_buffered_iomap(inode))) + return -EINVAL; + if ((offset >> blkbits) > EXT4_MAX_LOGICAL_BLOCK) + return -EINVAL; + + /* Calculate the first and last logical blocks respectively. */ + map->m_lblk = offset >> blkbits; + map->m_len = min_t(loff_t, (offset + length - 1) >> blkbits, + EXT4_MAX_LOGICAL_BLOCK) - map->m_lblk + 1; + + if (flags & EXT4_GET_BLOCKS_DELALLOC_RESERVE) + return ext4_da_map_blocks(inode, map); + + return ext4_map_blocks(NULL, inode, map, flags); +} + +static int ext4_iomap_buffered_read_begin(struct inode *inode, loff_t offset, + loff_t length, unsigned int flags, struct iomap *iomap, + struct iomap *srcmap) +{ + struct ext4_map_blocks map; + int ret; + + if (unlikely(ext4_forced_shutdown(inode->i_sb))) + return -EIO; + + /* Inline data support is not yet available. */ + if (WARN_ON_ONCE(ext4_has_inline_data(inode))) + return -ERANGE; + + ret = ext4_iomap_map_blocks(inode, offset, length, &map, 0); + if (ret < 0) + return ret; + + trace_ext4_iomap_buffered_read_begin(inode, &map, offset, length, + flags); + ext4_set_iomap(inode, iomap, &map, offset, length, flags); + return 0; +} + +static int ext4_iomap_buffered_do_write_begin(struct inode *inode, + loff_t offset, loff_t length, unsigned int flags, + struct iomap *iomap, struct iomap *srcmap, bool delalloc) +{ + int ret, retries = 0; + struct ext4_map_blocks map; + int map_flags; + + ret = ext4_emergency_state(inode->i_sb); + if (unlikely(ret)) + return ret; + + /* Inline data and non-extent are not supported. */ + if (WARN_ON_ONCE(ext4_has_inline_data(inode))) + return -ERANGE; + if (WARN_ON_ONCE(!ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS))) + return -EINVAL; + if (WARN_ON_ONCE(!(flags & (IOMAP_WRITE | IOMAP_FAULT)))) + return -EINVAL; + + map_flags = delalloc ? EXT4_GET_BLOCKS_DELALLOC_RESERVE : + EXT4_GET_BLOCKS_CREATE_UNWRIT_EXT; +retry: + ret = ext4_iomap_map_blocks(inode, offset, length, &map, map_flags); + if (ret == -ENOSPC && ext4_should_retry_alloc(inode->i_sb, &retries)) + goto retry; + if (ret < 0) + return ret; + + trace_ext4_iomap_buffered_write_begin(inode, &map, offset, length, + flags); + ext4_set_iomap(inode, iomap, &map, offset, length, flags); + return 0; +} + +static int ext4_iomap_buffered_write_begin(struct inode *inode, + loff_t offset, loff_t length, unsigned int flags, + struct iomap *iomap, struct iomap *srcmap) +{ + return ext4_iomap_buffered_do_write_begin(inode, offset, length, flags, + iomap, srcmap, false); +} + +static int ext4_iomap_buffered_da_write_begin(struct inode *inode, + loff_t offset, loff_t length, unsigned int flags, + struct iomap *iomap, struct iomap *srcmap) +{ + return ext4_iomap_buffered_do_write_begin(inode, offset, length, flags, + iomap, srcmap, true); +} + +/* + * On write failure, drop the stale delayed allocation range and release + * its reserved space for both start and end blocks. Otherwise, we may + * leave a range of delayed extents covered by a clean folio, which can + * result in inaccurate space reservation accounting. + */ +static void ext4_iomap_punch_delalloc(struct inode *inode, loff_t offset, + loff_t length, struct iomap *iomap) +{ + down_write(&EXT4_I(inode)->i_data_sem); + ext4_es_remove_extent(inode, offset >> inode->i_blkbits, + DIV_ROUND_UP_ULL(length, EXT4_BLOCK_SIZE(inode->i_sb))); + up_write(&EXT4_I(inode)->i_data_sem); +} + +static int ext4_iomap_buffered_da_write_end(struct inode *inode, loff_t offset, + loff_t length, ssize_t written, + unsigned int flags, + struct iomap *iomap) +{ + loff_t start_byte, end_byte; + + /* If we didn't reserve the blocks, we're not allowed to punch them. */ + if (iomap->type != IOMAP_DELALLOC || !(iomap->flags & IOMAP_F_NEW)) + return 0; + + /* + * iomap_page_mkwrite() will never fail in a way that requires delalloc + * extents that it allocated to be revoked. Hence never try to release + * them here. + */ + if (flags & IOMAP_FAULT) + return 0; + + /* Nothing to do if we've written the entire delalloc extent */ + start_byte = iomap_last_written_block(inode, offset, written); + end_byte = round_up(offset + length, i_blocksize(inode)); + if (start_byte >= end_byte) + return 0; + + filemap_invalidate_lock(inode->i_mapping); + iomap_write_delalloc_release(inode, start_byte, end_byte, flags, + iomap, ext4_iomap_punch_delalloc); + filemap_invalidate_unlock(inode->i_mapping); + return 0; +} + +static int ext4_iomap_zero_begin(struct inode *inode, + loff_t offset, loff_t length, unsigned int flags, + struct iomap *iomap, struct iomap *srcmap) +{ + struct iomap_iter *iter = container_of(iomap, struct iomap_iter, iomap); + struct ext4_map_blocks map; + u8 blkbits = inode->i_blkbits; + unsigned int iomap_flags; + int ret; + + ret = ext4_emergency_state(inode->i_sb); + if (unlikely(ret)) + return ret; + + if (WARN_ON_ONCE(!(flags & IOMAP_ZERO))) + return -EINVAL; + +again: + ret = ext4_iomap_map_blocks(inode, offset, length, &map, 0); + if (ret < 0) + return ret; + + /* + * Look up dirty folios for unwritten mappings within EOF. Providing + * this bypasses the flush iomap uses to trigger extent conversion + * when unwritten mappings have dirty pagecache in need of zeroing. + */ + iomap_flags = 0; + if (map.m_flags & EXT4_MAP_UNWRITTEN) { + loff_t start = ((loff_t)map.m_lblk) << blkbits; + loff_t end = ((loff_t)map.m_lblk + map.m_len) << blkbits; + unsigned int count; + + count = iomap_fill_dirty_folios(iter, &start, end, + &iomap_flags); + if ((start >> blkbits) < map.m_lblk + map.m_len) + map.m_len = (start >> blkbits) - map.m_lblk; + + /* + * This can be raced by a concurrent writeback that cleans + * the folio and converts the unwritten extent to written. + * Recheck the mapping after a folio lock round in + * iomap_fill_dirty_folios(). + */ + if (count == 0 && + map.m_seq != READ_ONCE(EXT4_I(inode)->i_es_seq)) + goto again; + } + + trace_ext4_iomap_zero_begin(inode, &map, offset, length, flags); + ext4_set_iomap(inode, iomap, &map, offset, length, flags); + iomap->flags |= iomap_flags; + + return 0; +} + +static DEFINE_IOMAP_ITER_NEXT(ext4_iomap_zero_next, ext4_iomap_zero_begin); + +static const struct iomap_ops ext4_iomap_zero_ops = { + .iomap_next = ext4_iomap_zero_next, +}; + +/* + * Since we always allocate unwritten extents, there is no need for + * iomap_end to clean up allocated blocks on a short write. + */ +static DEFINE_IOMAP_ITER_NEXT(ext4_iomap_buffered_write_next, + ext4_iomap_buffered_write_begin); + +const struct iomap_ops ext4_iomap_buffered_write_ops = { + .iomap_next = ext4_iomap_buffered_write_next, +}; + +static DEFINE_IOMAP_ITER_NEXT_END(ext4_iomap_buffered_da_write_next, + ext4_iomap_buffered_da_write_begin, + ext4_iomap_buffered_da_write_end); + +const struct iomap_ops ext4_iomap_buffered_da_write_ops = { + .iomap_next = ext4_iomap_buffered_da_write_next, +}; + +static DEFINE_IOMAP_ITER_NEXT(ext4_iomap_buffered_read_next, + ext4_iomap_buffered_read_begin); + +const struct iomap_ops ext4_iomap_buffered_read_ops = { + .iomap_next = ext4_iomap_buffered_read_next, +}; + +static int ext4_iomap_read_folio(struct file *file, struct folio *folio) +{ + iomap_bio_read_folio(folio, &ext4_iomap_buffered_read_ops); + return 0; +} + +static void ext4_iomap_readahead(struct readahead_control *rac) +{ + iomap_bio_readahead(rac, &ext4_iomap_buffered_read_ops); +} + + +static int ext4_iomap_map_writeback_range(struct iomap_writepage_ctx *wpc, + loff_t offset, unsigned int dirty_len) +{ + struct inode *inode = wpc->inode; + struct super_block *sb = inode->i_sb; + struct journal_s *journal = EXT4_SB(sb)->s_journal; + struct ext4_map_blocks map; + ext4_lblk_t index = offset >> inode->i_blkbits; + unsigned int blk_len, blk_end; + int ret; + + ret = ext4_emergency_state(sb); + if (unlikely(ret)) + return ret; + + /* Check validity of the cached writeback mapping. */ + if (offset >= wpc->iomap.offset && + offset < wpc->iomap.offset + wpc->iomap.length && + ext4_iomap_valid(inode, &wpc->iomap)) + return 0; + + blk_len = dirty_len >> inode->i_blkbits; + blk_end = umin(wpc->wbc->range_end >> inode->i_blkbits, UINT_MAX - 1); + if (blk_end > index + blk_len) + blk_len = blk_end - index + 1; + +retry: + map.m_lblk = index; + map.m_len = min_t(unsigned int, MAX_WRITEPAGES_EXTENT_LEN, blk_len); + ret = ext4_map_blocks(NULL, inode, &map, + EXT4_GET_BLOCKS_CREATE_UNWRIT_EXT | + EXT4_GET_BLOCKS_METADATA_NOFAIL | + EXT4_GET_BLOCKS_IO_SUBMIT | + EXT4_EX_NOCACHE); + if (ret < 0) { + if (ext4_emergency_state(sb)) + return ret; + + /* + * Retry transient ENOSPC errors, if + * ext4_count_free_blocks() is non-zero, a commit + * should free up blocks. + */ + if (ret == -ENOSPC && journal && ext4_count_free_clusters(sb)) { + jbd2_journal_force_commit_nested(journal); + goto retry; + } + + ext4_msg(sb, KERN_CRIT, + "Delayed block allocation failed for inode %llu at logical offset %llu with max blocks %u with error %d", + inode->i_ino, (unsigned long long)map.m_lblk, + (unsigned int)map.m_len, -ret); + ext4_msg(sb, KERN_CRIT, + "This should not happen!! Data will be lost\n"); + if (ret == -ENOSPC) + ext4_print_free_blocks(inode); + return ret; + } + + trace_ext4_iomap_map_writeback_range(inode, &map, offset, dirty_len, 0); + ext4_set_iomap(inode, &wpc->iomap, &map, offset, dirty_len, 0); + return 0; +} + +static void ext4_iomap_discard_folio(struct folio *folio, loff_t pos) +{ + struct inode *inode = folio->mapping->host; + loff_t length = folio_pos(folio) + folio_size(folio) - pos; + loff_t pstart, plen; + + ext4_iomap_punch_delalloc(inode, pos, length, NULL); + + /* + * Clear the disksize-grow-pending state if the zeroed EOF block + * fails to write back and is discarded. + */ + plen = ext4_iomap_get_disksize_pending_range(inode, &pstart); + if (plen && pos <= pstart && folio_next_pos(folio) >= pstart + plen) + ext4_iomap_clear_disksize_pending(inode); +} + +static ssize_t ext4_iomap_writeback_range(struct iomap_writepage_ctx *wpc, + struct folio *folio, u64 offset, + unsigned int len, u64 end_pos) +{ + ssize_t ret; + + ret = ext4_iomap_map_writeback_range(wpc, offset, len); + if (!ret) + ret = iomap_add_to_ioend(wpc, folio, offset, end_pos, len); + if (ret < 0) + ext4_iomap_discard_folio(folio, offset); + return ret; +} + +static int ext4_iomap_writeback_submit(struct iomap_writepage_ctx *wpc, + int error) +{ + struct iomap_ioend *ioend = wpc->wb_ctx; + struct inode *inode = ioend->io_inode; + struct ext4_inode_info *ei = EXT4_I(inode); + unsigned int blocksize = i_blocksize(inode); + loff_t pstart, plen; + + /* + * After I/O completion, a worker needs to be scheduled when: + * 1) Unwritten extents require conversion. + * 2) The file size needs to be extended. + * 3) The journal needs to be aborted due to an I/O error. + */ + if ((ioend->io_flags & IOMAP_IOEND_UNWRITTEN) || + (ioend->io_offset + ioend->io_size > READ_ONCE(ei->i_disksize)) || + test_opt(ioend->io_inode->i_sb, DATA_ERR_ABORT)) + ioend->io_bio.bi_end_io = ext4_iomap_end_bio; + + /* + * Mark the I/O as DISKSIZE_GROW_IO by setting io_private to + * EXT4_IOMAP_IOEND_DISKSIZE_GROW_IO if it covers the pending range. + * Such I/O will allow or trigger i_disksize advancement in the + * ioend worker. + */ + plen = ext4_iomap_get_disksize_pending_range(inode, &pstart); + if (plen && + round_down(ioend->io_offset, blocksize) <= pstart && + round_up(ioend->io_offset + ioend->io_size, blocksize) >= + pstart + plen) { + trace_ext4_iomap_wb_disksize_pending_submit(inode, + ioend->io_offset, ioend->io_size); + ioend->io_bio.bi_end_io = ext4_iomap_end_bio; + ioend->io_private = (void *)EXT4_IOMAP_IOEND_DISKSIZE_GROW_IO; + } + + /* + * ext4_iomap_end_bio() always defers endio processing, disable + * generic BIO in task to avoid double deferral since we will use + * a private defer endio handler in process context. + * + * TODO: Switch all defer handlers to the generic bio complete + * in task framework. + */ + if (ioend->io_bio.bi_end_io) + bio_clear_flag(&ioend->io_bio, BIO_COMPLETE_IN_TASK); + + return iomap_ioend_writeback_submit(wpc, error); +} + +static const struct iomap_writeback_ops ext4_writeback_ops = { + .writeback_range = ext4_iomap_writeback_range, + .writeback_submit = ext4_iomap_writeback_submit, +}; + +/* + * If the current writeback range begins after the pending zeroed EOF + * block range which straddles i_disksize, issue a separate writeback to + * flush it first, so as to avoid prolonged waiting. + */ +static void ext4_iomap_wb_submit_zeroed_eof(struct inode *inode, + struct writeback_control *wbc) +{ + struct address_space *mapping = inode->i_mapping; + loff_t pstart, plen, range_start; + + if (wbc->range_cyclic) + range_start = (loff_t)mapping->writeback_index << PAGE_SHIFT; + else + range_start = wbc->range_start; + + plen = ext4_iomap_get_disksize_pending_range(inode, &pstart); + if (!plen || range_start < pstart + plen) + return; + + /* Keep the caller's sync mode to avoid stalling the background flusher. */ + if (wbc->sync_mode == WB_SYNC_ALL) + filemap_fdatawrite_range(mapping, pstart, pstart + plen - 1); + else + filemap_flush_range(mapping, pstart, pstart + plen - 1); +} + +static int ext4_iomap_writepages(struct address_space *mapping, + struct writeback_control *wbc) +{ + struct inode *inode = mapping->host; + struct super_block *sb = inode->i_sb; + long nr = wbc->nr_to_write; + int alloc_ctx, ret; + struct iomap_writepage_ctx wpc = { + .inode = inode, + .wbc = wbc, + .ops = &ext4_writeback_ops, + }; + + ret = ext4_emergency_state(sb); + if (unlikely(ret)) + return ret; + + /* + * Submit the pending zeroed EOF block range if the entire + * writeback range lies beyond it. + */ + ext4_iomap_wb_submit_zeroed_eof(inode, wbc); + + alloc_ctx = ext4_writepages_down_read(sb); + /* + * Does ext4_change_inode_journal_flag() change the inode's + * buffered I/O path? + */ + if (!ext4_inode_buffered_iomap(inode)) + goto out; + + trace_ext4_writepages(inode, wbc); + ret = iomap_writepages(&wpc); + trace_ext4_writepages_result(inode, wbc, ret, nr - wbc->nr_to_write); +out: + ext4_writepages_up_read(sb, alloc_ctx); + return ret; +} + /* * For data=journal mode, folio should be marked dirty only when it was * writeably mapped. When that happens, it was already attached to the @@ -3993,6 +4596,20 @@ static const struct address_space_operations ext4_da_aops = { .swap_activate = ext4_iomap_swap_activate, }; +static const struct address_space_operations ext4_iomap_aops = { + .read_folio = ext4_iomap_read_folio, + .readahead = ext4_iomap_readahead, + .writepages = ext4_iomap_writepages, + .dirty_folio = iomap_dirty_folio, + .bmap = ext4_bmap, + .invalidate_folio = iomap_invalidate_folio, + .release_folio = iomap_release_folio, + .migrate_folio = filemap_migrate_folio, + .is_partially_uptodate = iomap_is_partially_uptodate, + .error_remove_folio = generic_error_remove_folio, + .swap_activate = ext4_iomap_swap_activate, +}; + static const struct address_space_operations ext4_dax_aops = { .writepages = ext4_dax_writepages, .dirty_folio = noop_dirty_folio, @@ -4014,6 +4631,8 @@ void ext4_set_aops(struct inode *inode) } if (IS_DAX(inode)) inode->i_mapping->a_ops = &ext4_dax_aops; + else if (ext4_inode_buffered_iomap(inode)) + inode->i_mapping->a_ops = &ext4_iomap_aops; else if (test_opt(inode->i_sb, DELALLOC)) inode->i_mapping->a_ops = &ext4_da_aops; else @@ -4167,6 +4786,48 @@ out_handle: return err; } +static int ext4_block_iomap_zero_range(struct inode *inode, loff_t from, + loff_t length, bool *did_zero, + bool *zero_written) +{ + int ret; + + /* + * Zeroing out under an active handle can cause deadlock since + * the order of acquiring the folio lock and starting a handle is + * inconsistent with the iomap writeback procedure. + */ + if (WARN_ON_ONCE(ext4_handle_valid(journal_current_handle()))) + return -EINVAL; + + /* The zeroing scope should not extend across a block. */ + if (WARN_ON_ONCE((from >> inode->i_blkbits) != + ((from + length - 1) >> inode->i_blkbits))) + return -EINVAL; + + if (!(EXT4_SB(inode->i_sb)->s_mount_state & EXT4_ORPHAN_FS) && + !(inode_state_read_once(inode) & (I_NEW | I_FREEING))) + WARN_ON_ONCE(!inode_is_locked(inode) && + !rwsem_is_locked(&inode->i_mapping->invalidate_lock)); + + ret = iomap_zero_range(inode, from, length, did_zero, + &ext4_iomap_zero_ops, &ext4_iomap_write_ops, + NULL); + if (ret) + return ret; + + /* + * TODO: The iomap does not distinguish between different types + * of zeroing operations. So we always set zero_written whenever + * zeroing is performed, which may cause unnecessary folio + * flushing when zeroing occurs on delayed-allocated blocks. + */ + if (did_zero && zero_written) + *zero_written = *did_zero; + + return 0; +} + /* * Zeros out a mapping of length 'length' starting from file offset * 'from'. The range to be zero'd must be contained with in one block. @@ -4193,12 +4854,112 @@ static int ext4_block_zero_range(struct inode *inode, } else if (ext4_should_journal_data(inode)) { return ext4_block_journalled_zero_range(inode, from, length, did_zero); + } else if (ext4_inode_buffered_iomap(inode)) { + return ext4_block_iomap_zero_range(inode, from, length, + did_zero, zero_written); } return ext4_block_do_zero_range(inode, from, length, did_zero, zero_written); } /* + * Inodes using the iomap buffered I/O path do not use data=ordered mode. + * Therefore, we mark the inode as disksize-grow-pending after zeroing the + * EOF block. The zeroed block will be submitted before any subsequent + * data. + * + * In the I/O completion path, ext4_iomap_wb_disksize_pending_wait() will + * wait for I/O completion before advancing i_disksize if the write + * extends beyond the zeroed boundary. + * + * When zeroed I/O is in progress, operations that extend i_disksize are + * handled as follows: + * + * - Truncate up, append fallocate and zero_range: + * Defer the update. The file size will be updated to i_size by the + * end_io handler once the ongoing pending I/O completes. + * + * - Insert range and collapse range operations: + * Wait synchronously for the relevant I/O to complete before updating + * i_disksize. + */ +static int ext4_iomap_mark_disksize_pending(struct inode *inode, loff_t from) +{ + struct folio *folio; + + folio = filemap_lock_folio(inode->i_mapping, from >> PAGE_SHIFT); + if (IS_ERR(folio)) + /* Already in writeback and cleared? */ + return PTR_ERR(folio) == -ENOENT ? 0 : PTR_ERR(folio); + + /* + * Ensure that in-flight writeback, possibly started after + * iomap_zero_range() unlocked the folio, has completed. Without + * this wait folio_test_dirty() below may miss the zeroed data + * (writeback clears PG_dirty), causing us to skip the + * disksize-grow-pending tracking and potentially expose stale + * on-disk data. + */ + folio_wait_writeback(folio); + WARN_ON_ONCE(folio_test_writeback(folio)); + + /* + * Mark the inode as disksize-grow-pending. The zeroed block will + * be written out by the generic writepages cycle or any other + * syncing operation. + * + * Multiple overlapping unaligned EOF writes should not happen, + * because we only mark the pending state after zeroing the on-disk + * EOF block, and i_disksize can only be updated after the previous + * zeroed pending block has been written back or the dirty folio + * has been discared. + */ + if (likely(folio_test_dirty(folio) && + !ext4_test_inode_state(inode, + EXT4_STATE_DISKSIZE_GROW_PENDING))) { + trace_ext4_iomap_mark_disksize_pending(inode); + ext4_set_inode_state(inode, EXT4_STATE_DISKSIZE_GROW_PENDING); + } + + folio_unlock(folio); + folio_put(folio); + return 0; +} + +/* + * Submit and wait for the pending zeroed EOF block range to complete + * if the given range [@offset, @end) fully covers it. Must be called + * outside the context of an active journal handle and hold the i_rwsem. + */ +int ext4_iomap_sync_zeroed_eof(struct inode *inode, loff_t offset, loff_t end) +{ + loff_t pstart, plen; + int ret; + + if (!ext4_inode_buffered_iomap(inode)) + return 0; + + plen = ext4_iomap_get_disksize_pending_range(inode, &pstart); + if (!plen || offset > pstart || end < pstart + plen) + return 0; + + ret = filemap_write_and_wait_range(inode->i_mapping, pstart, + pstart + plen - 1); + if (ret) + return ret; + + /* + * The pending bit should have been cleared by the ioend worker, + * which runs before the folio writeback flag is cleared. The + * caller holds i_rwsem, so no concurrent ext4_block_zero_eof() + * can re-set it. + */ + WARN_ON_ONCE(ext4_test_inode_state(inode, + EXT4_STATE_DISKSIZE_GROW_PENDING)); + return 0; +} + +/* * Zero out a mapping from file offset 'from' up to the end of the block * which corresponds to 'from' or to the given 'end' inside this block. * This required during truncate up and performing append writes. We need @@ -4241,21 +5002,38 @@ int ext4_block_zero_eof(struct inode *inode, loff_t from, loff_t end) * truncating up or performing an append write, because there might be * exposing stale on-disk data which may caused by concurrent post-EOF * mmap write during folio writeback. + * + * Ordered I/O is required only when zeroing the tail of a block that + * overlaps with i_disksize. If the zeroed range falls outside that + * block, the zeroed data lies beyond the existing on-disk data. It + * will be written out before i_disksize is later extended past + * i_size, so no stale data can be exposed. + * + * Note that it's safe to read i_disksize without holding i_data_sem + * here. Since we already hold i_rwsem, the only possible race is with + * concurrent writeback that updates i_disksize. And if such a race + * occurs, it means the previous unaligned EOF block has already been + * zeroed (if needed) and persisted to disk. */ - if (ext4_should_order_data(inode) && - did_zero && zero_written && !IS_DAX(inode)) { - handle_t *handle; + if (did_zero && zero_written && !IS_DAX(inode) && + from < round_up(READ_ONCE(EXT4_I(inode)->i_disksize), blocksize)) { + if (ext4_should_order_data(inode)) { + handle_t *handle; - handle = ext4_journal_start(inode, EXT4_HT_MISC, 1); - if (IS_ERR(handle)) - return PTR_ERR(handle); + handle = ext4_journal_start(inode, EXT4_HT_MISC, 1); + if (IS_ERR(handle)) + return PTR_ERR(handle); - err = ext4_jbd2_inode_add_write(handle, inode, from, length); - ext4_journal_stop(handle); + err = ext4_jbd2_inode_add_write(handle, inode, from, + length); + ext4_journal_stop(handle); + } else if (ext4_inode_buffered_iomap(inode)) + err = ext4_iomap_mark_disksize_pending(inode, from); if (err) return err; } + trace_ext4_block_zero_eof(inode, from, length, did_zero, zero_written); return 0; } @@ -4352,6 +5130,14 @@ int ext4_update_disksize_before_punch(struct inode *inode, loff_t offset, if (offset > size) return 0; + /* + * We are going to punch the pending zeroed EOF block, persist + * it to ensure i_disksize can be safely updated thereafter. + */ + ret = ext4_iomap_sync_zeroed_eof(inode, offset, offset + len); + if (ret) + return ret; + if (offset + len < size) size = offset + len; if (EXT4_I(inode)->i_disksize >= size) @@ -4503,7 +5289,14 @@ int ext4_punch_hole(struct file *file, loff_t offset, loff_t length) ret = ext4_zero_partial_blocks(inode, offset, length, &partial_zeroed); if (ret) return ret; - if (((file->f_flags & O_SYNC) || IS_SYNC(inode)) && partial_zeroed) { + /* + * On the iomap path, partial invalidate of a folio without an ifs + * leaves sub-block dirty bits uncleared. Drain writeback before + * removing extents so that any bio in flight on a partial folio + * completes against blocks still owned by this inode. + */ + if (ext4_inode_buffered_iomap(inode) || + (((file->f_flags & O_SYNC) || IS_SYNC(inode)) && partial_zeroed)) { ret = filemap_write_and_wait_range(inode->i_mapping, offset, end - 1); if (ret) @@ -5281,6 +6074,87 @@ error: return -EFSCORRUPTED; } +/* + * Determine whether an inode should use the iomap buffered I/O path. + * EXT4_STATE_BUFFERED_IOMAP is generally set at inode initialization + * time. Online switching of 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 inline data, fsverity, and encryption that can be + * dynamically enabled or disabled, we check the superblock-level + * feature flags. If any of these is globally enabled, no inode is + * allowed into the iomap buffered I/O path. This avoids the complexity + * of dynamic toggling. + * + * For the global data journal mode (EXT4_MOUNT_JOURNAL_DATA), dynamic + * change through remount is deferred. It will only become available + * after the inode is re-initialized (i.e., after the last reference + * drops and the inode is re-read from disk with the journal flag + * cleared). + * + * For the per-inode data journal mode (EXT4_INODE_JOURNAL_DATA), + * dynamic changes take effect immediately. This is safe because + * address_space operations can be switched and all page cache can be + * dropped under i_rwsem and filemap_invalidate_lock. + * + * For extent-to-indirect block migration (via EXT4_IOC_SETFLAGS + * clearing EXT4_EXTENTS_FL), this operation is directly rejected for + * inodes using the iomap path. + * + * When remounting to toggle the buffered_iomap mount option, the change + * of I/O path is deferred as well, it will be available after the inode + * is re-initialized. + */ +void ext4_enable_buffered_iomap(struct inode *inode) +{ + struct super_block *sb = inode->i_sb; + + if (!test_opt2(sb, BUFFERED_IOMAP)) + return; + if (!S_ISREG(inode->i_mode)) + return; + if (ext4_test_inode_flag(inode, EXT4_INODE_EA_INODE)) + return; + + /* Unsupported Features */ + if (ext4_has_feature_inline_data(sb)) + return; + if (ext4_has_feature_verity(sb)) + return; + if (ext4_has_feature_encrypt(sb)) + return; + if (test_opt(sb, DATA_FLAGS) == EXT4_MOUNT_JOURNAL_DATA || + ext4_test_inode_flag(inode, EXT4_INODE_JOURNAL_DATA)) + return; + if (!(ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS))) + return; + + ext4_set_inode_state(inode, EXT4_STATE_BUFFERED_IOMAP); + + /* + * Install the iomap end_io handler on the shared conversion + * work. This is safe at inode initialization and during the + * buffered I/O path changes where we flush all pending + * writebacks and drop page cache under i_rwsem and + * filemap_invalidate_lock. + */ + INIT_WORK(&EXT4_I(inode)->i_rsv_conversion_work, ext4_iomap_end_io); +} + +static void ext4_disable_buffered_iomap(struct inode *inode) +{ + ext4_clear_inode_state(inode, EXT4_STATE_BUFFERED_IOMAP); + + /* + * Reinstall the buffer_head end_io handler on the shared + * conversion work. This is safe during the buffered I/O path + * changes where we flush all pending writebacks and drop page + * cache under i_rwsem and filemap_invalidate_lock. + */ + INIT_WORK(&EXT4_I(inode)->i_rsv_conversion_work, ext4_end_io_rsv_work); +} + void ext4_set_inode_mapping_order(struct inode *inode) { struct super_block *sb = inode->i_sb; @@ -5595,6 +6469,8 @@ struct inode *__ext4_iget(struct super_block *sb, unsigned long ino, if (ret) goto bad_inode; + ext4_enable_buffered_iomap(inode); + if (S_ISREG(inode->i_mode)) { inode->i_op = &ext4_file_inode_operations; inode->i_fop = &ext4_file_operations; @@ -5983,6 +6859,125 @@ static void ext4_wait_for_tail_page_commit(struct inode *inode) } /* + * Set i_size and i_disksize to 'newsize'. + * + * Both i_rwsem and i_data_sem are required here to avoid races between + * generic append writeback (or zeroed pending I/O writeback) and + * concurrent operations (e.g., fallocate, truncate) that also modify + * i_size and i_disksize. This also ensures that the writeback ioend worker + * observes the latest i_size under the same lock protection. + */ +static inline void ext4_set_inode_size(struct inode *inode, loff_t newsize) +{ + WARN_ON_ONCE(S_ISREG(inode->i_mode) && !inode_is_locked(inode)); + + down_write(&EXT4_I(inode)->i_data_sem); + i_size_write(inode, newsize); + __ext4_set_i_disksize(inode, newsize); + up_write(&EXT4_I(inode)->i_data_sem); +} + +static int ext4_truncate_up(struct inode *inode, loff_t oldsize, loff_t newsize) +{ + ext4_lblk_t old_lblk, new_lblk; + handle_t *handle; + int ret; + + if (!IS_ALIGNED(oldsize | newsize, i_blocksize(inode))) { + ret = ext4_inode_attach_jinode(inode); + if (ret) + return ret; + } + + inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode)); + if (!IS_ALIGNED(oldsize, i_blocksize(inode))) { + ret = ext4_block_zero_eof(inode, oldsize, LLONG_MAX); + if (ret) + return ret; + } + + handle = ext4_journal_start(inode, EXT4_HT_INODE, 3); + if (IS_ERR(handle)) + return PTR_ERR(handle); + + old_lblk = oldsize > 0 ? (oldsize - 1) >> inode->i_blkbits : 0; + new_lblk = newsize > 0 ? (newsize - 1) >> inode->i_blkbits : 0; + ext4_fc_track_range(handle, inode, old_lblk, new_lblk); + + ext4_set_inode_size(inode, newsize); + + ret = ext4_mark_inode_dirty(handle, inode); + ext4_journal_stop(handle); + if (ret) + return ret; + /* + * isize extend must be called outside an active handle due to + * the lock ordering of transaction start and folio lock in the + * iomap buffered I/O path (folio lock -> transaction start). + */ + pagecache_isize_extended(inode, oldsize, newsize); + return 0; +} + +static int ext4_truncate_down(struct inode *inode, loff_t oldsize, + loff_t newsize, int *orphan) +{ + ext4_lblk_t start_lblk; + handle_t *handle; + int ret; + + /* Do not change i_size. */ + if (newsize == oldsize) + goto truncate; + + /* Shrink. */ + handle = ext4_journal_start(inode, EXT4_HT_INODE, 3); + if (IS_ERR(handle)) + return PTR_ERR(handle); + + if (ext4_handle_valid(handle)) { + ret = ext4_orphan_add(handle, inode); + *orphan = 1; + if (ret) { + ext4_journal_stop(handle); + return ret; + } + } + + start_lblk = newsize > 0 ? (newsize - 1) >> inode->i_blkbits : 0; + ext4_fc_track_range(handle, inode, start_lblk, EXT_MAX_BLOCKS - 1); + + down_write(&EXT4_I(inode)->i_data_sem); + /* + * Truncate the zeroed EOF block invalidates the pending disksize + * update, so clear the disksize-grow-pending state. + */ + if (ext4_test_inode_state(inode, EXT4_STATE_DISKSIZE_GROW_PENDING) && + (newsize <= EXT4_I(inode)->i_disksize)) + ext4_iomap_clear_disksize_pending(inode); + + i_size_write(inode, newsize); + __ext4_set_i_disksize(inode, newsize); + up_write(&EXT4_I(inode)->i_data_sem); + + ret = ext4_mark_inode_dirty(handle, inode); + ext4_journal_stop(handle); + if (ret) + return ret; + + if (ext4_should_journal_data(inode)) + ext4_wait_for_tail_page_commit(inode); +truncate: + /* + * Truncate pagecache after we've waited for commit in data=journal + * mode to make pages freeable. Call ext4_truncate() even if + * i_size didn't change to truncate possible preallocated blocks. + */ + truncate_pagecache(inode, newsize); + return ext4_truncate(inode); +} + +/* * ext4_setattr() * * Called from notify_change. @@ -6078,9 +7073,7 @@ int ext4_setattr(struct mnt_idmap *idmap, struct dentry *dentry, } if (attr->ia_valid & ATTR_SIZE) { - handle_t *handle; loff_t oldsize = inode->i_size; - loff_t old_disksize; int shrink = (attr->ia_size < inode->i_size); if (!(ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS))) { @@ -6131,97 +7124,14 @@ int ext4_setattr(struct mnt_idmap *idmap, struct dentry *dentry, goto err_out; } - if (attr->ia_size != inode->i_size) { - /* attach jbd2 jinode for EOF folio tail zeroing */ - if (attr->ia_size & (inode->i_sb->s_blocksize - 1) || - oldsize & (inode->i_sb->s_blocksize - 1)) { - error = ext4_inode_attach_jinode(inode); - if (error) - goto out_mmap_sem; - } - - /* - * Update c/mtime and tail zero the EOF folio on - * truncate up. ext4_truncate() handles the shrink case - * below. - */ - if (!shrink) { - inode_set_mtime_to_ts(inode, - inode_set_ctime_current(inode)); - if (oldsize & (inode->i_sb->s_blocksize - 1)) { - error = ext4_block_zero_eof(inode, - oldsize, LLONG_MAX); - if (error) - goto out_mmap_sem; - } - } - - handle = ext4_journal_start(inode, EXT4_HT_INODE, 3); - if (IS_ERR(handle)) { - error = PTR_ERR(handle); - goto out_mmap_sem; - } - if (ext4_handle_valid(handle) && shrink) { - error = ext4_orphan_add(handle, inode); - orphan = 1; - } - - if (shrink) - ext4_fc_track_range(handle, inode, - (attr->ia_size > 0 ? attr->ia_size - 1 : 0) >> - inode->i_sb->s_blocksize_bits, - EXT_MAX_BLOCKS - 1); - else - ext4_fc_track_range( - handle, inode, - (oldsize > 0 ? oldsize - 1 : oldsize) >> - inode->i_sb->s_blocksize_bits, - (attr->ia_size > 0 ? attr->ia_size - 1 : 0) >> - inode->i_sb->s_blocksize_bits); - - down_write(&EXT4_I(inode)->i_data_sem); - old_disksize = EXT4_I(inode)->i_disksize; - EXT4_I(inode)->i_disksize = attr->ia_size; - - /* - * We have to update i_size under i_data_sem together - * with i_disksize to avoid races with writeback code - * running ext4_wb_update_i_disksize(). - */ - if (!error) - i_size_write(inode, attr->ia_size); - else - EXT4_I(inode)->i_disksize = old_disksize; - up_write(&EXT4_I(inode)->i_data_sem); - rc = ext4_mark_inode_dirty(handle, inode); - if (!error) - error = rc; - ext4_journal_stop(handle); - if (error) - goto out_mmap_sem; - if (!shrink) { - pagecache_isize_extended(inode, oldsize, - inode->i_size); - } else if (ext4_should_journal_data(inode)) { - ext4_wait_for_tail_page_commit(inode); - } + if (attr->ia_size > oldsize) + error = ext4_truncate_up(inode, oldsize, attr->ia_size); + else { + /* Shrink or do not change i_size. */ + error = ext4_truncate_down(inode, oldsize, + attr->ia_size, &orphan); } - /* - * Truncate pagecache after we've waited for commit - * in data=journal mode to make pages freeable. - */ - truncate_pagecache(inode, inode->i_size); - /* - * Call ext4_truncate() even if i_size didn't change to - * truncate possible preallocated blocks. - */ - if (attr->ia_size <= oldsize) { - rc = ext4_truncate(inode); - if (rc) - error = rc; - } -out_mmap_sem: filemap_invalidate_unlock(inode->i_mapping); } @@ -6603,8 +7513,14 @@ static int ext4_try_to_expand_extra_isize(struct inode *inode, * When !SB_ACTIVE, iput triggers write_inode_now() which acquires * s_writepages_rwsem, causing a deadlock with the caller's active * jbd2 handle (lock order: s_writepages_rwsem -> jbd2_handle). + * + * Skip it while unmounting as well. shrink_dcache_for_umount() + * clears s_root before generic_shutdown_super() clears SB_ACTIVE, and + * the last iput() of a lazytime inode in that window redirties it and + * lands here. Moving xattrs out to a block then needs a new EA inode, + * which ext4_xattr_inode_create() refuses without s_root. */ - if (unlikely(!(inode->i_sb->s_flags & SB_ACTIVE))) + if (unlikely(!(inode->i_sb->s_flags & SB_ACTIVE) || !inode->i_sb->s_root)) return -EBUSY; /* @@ -6791,9 +7707,10 @@ int ext4_change_inode_journal_flag(struct inode *inode, int val) * the inode's in-core data-journaling state flag now. */ - if (val) + if (val) { ext4_set_inode_flag(inode, EXT4_INODE_JOURNAL_DATA); - else { + ext4_disable_buffered_iomap(inode); + } else { err = jbd2_journal_flush(journal, 0); if (err < 0) { jbd2_journal_unlock_updates(journal); @@ -6802,6 +7719,7 @@ int ext4_change_inode_journal_flag(struct inode *inode, int val) return err; } ext4_clear_inode_flag(inode, EXT4_INODE_JOURNAL_DATA); + ext4_enable_buffered_iomap(inode); } ext4_set_aops(inode); ext4_set_inode_mapping_order(inode); @@ -6881,6 +7799,23 @@ out_error: return ret; } +static vm_fault_t ext4_iomap_page_mkwrite(struct vm_fault *vmf) +{ + struct inode *inode = file_inode(vmf->vma->vm_file); + const struct iomap_ops *iomap_ops; + + /* + * ext4_nonda_switch() could writeback this folio, so have to + * call it before lock folio. + */ + if (test_opt(inode->i_sb, DELALLOC) && !ext4_nonda_switch(inode->i_sb)) + iomap_ops = &ext4_iomap_buffered_da_write_ops; + else + iomap_ops = &ext4_iomap_buffered_write_ops; + + return iomap_page_mkwrite(vmf, iomap_ops, NULL); +} + vm_fault_t ext4_page_mkwrite(struct vm_fault *vmf) { struct vm_area_struct *vma = vmf->vma; @@ -6907,6 +7842,11 @@ vm_fault_t ext4_page_mkwrite(struct vm_fault *vmf) if (err) goto out_ret; + if (ext4_inode_buffered_iomap(inode)) { + ret = ext4_iomap_page_mkwrite(vmf); + goto out; + } + /* * On data journalling we skip straight to the transaction handle: * there's no delalloc; page truncated will be checked later; the diff --git a/fs/ext4/ioctl.c b/fs/ext4/ioctl.c index c8387e6a2c6e..1f6d7afb62ec 100644 --- a/fs/ext4/ioctl.c +++ b/fs/ext4/ioctl.c @@ -1362,7 +1362,8 @@ static void ext4_sb_setparams(struct ext4_sb_info *sbi, es->s_encoding = cpu_to_le16(params->encoding); if (params->set_flags & EXT4_TUNE_FL_ENCODING_FLAGS) es->s_encoding_flags = cpu_to_le16(params->encoding_flags); - strscpy_pad(es->s_mount_opts, params->mount_opts); + if (params->set_flags & EXT4_TUNE_FL_MOUNT_OPTS) + strscpy_pad(es->s_mount_opts, params->mount_opts); if (params->set_flags & EXT4_TUNE_FL_EDIT_FEATURES) { es->s_feature_compat |= cpu_to_le32(params->set_feature_compat_mask); diff --git a/fs/ext4/mballoc.c b/fs/ext4/mballoc.c index 06171a11db12..a15285224cdf 100644 --- a/fs/ext4/mballoc.c +++ b/fs/ext4/mballoc.c @@ -899,10 +899,8 @@ static ext4_group_t ext4_get_allocation_groups_count( /* non-extent files are limited to low blocks/groups */ if (!(ext4_test_inode_flag(ac->ac_inode, EXT4_INODE_EXTENTS))) - ngroups = EXT4_SB(ac->ac_sb)->s_blockfile_groups; - - /* Pairs with smp_wmb() in ext4_update_super() */ - smp_rmb(); + /* Pairs with smp_store_release() in ext4_update_super() */ + ngroups = smp_load_acquire(&EXT4_SB(ac->ac_sb)->s_blockfile_groups); return ngroups; } diff --git a/fs/ext4/migrate.c b/fs/ext4/migrate.c index 5d60ef10fe11..09931d3ba2c6 100644 --- a/fs/ext4/migrate.c +++ b/fs/ext4/migrate.c @@ -621,6 +621,8 @@ int ext4_ind_migrate(struct inode *inode) if (ext4_has_feature_bigalloc(inode->i_sb)) return -EOPNOTSUPP; + if (ext4_inode_buffered_iomap(inode)) + return -EOPNOTSUPP; /* * In order to get correct extent info, force all delayed allocation diff --git a/fs/ext4/move_extent.c b/fs/ext4/move_extent.c index 3329b7ad5dbd..948ef3f44df5 100644 --- a/fs/ext4/move_extent.c +++ b/fs/ext4/move_extent.c @@ -476,6 +476,17 @@ static int mext_check_validity(struct inode *orig_inode, return -EOPNOTSUPP; } + /* + * TODO: support online defrag for inodes that use the buffered + * I/O iomap path. + */ + if (ext4_inode_buffered_iomap(orig_inode) || + ext4_inode_buffered_iomap(donor_inode)) { + ext4_msg(sb, KERN_ERR, + "Online defrag not supported for inode with iomap buffered IO path"); + return -EOPNOTSUPP; + } + if (donor_inode->i_mode & (S_ISUID|S_ISGID)) { ext4_debug("ext4 move extent: suid or sgid is set to donor file [ino:orig %llu, donor %llu]\n", orig_inode->i_ino, donor_inode->i_ino); diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c index a6386c1d237f..231a274b814e 100644 --- a/fs/ext4/namei.c +++ b/fs/ext4/namei.c @@ -3209,19 +3209,20 @@ static int ext4_rmdir(struct inode *dir, struct dentry *dentry) ext4_fc_track_unlink(handle, dentry); retval = ext4_mark_inode_dirty(handle, dir); +end_rmdir: + brelse(bh); + if (handle) + ext4_journal_stop(handle); + /* VFS negative dentries are incompatible with Encoding and * Case-insensitiveness. Eventually we'll want avoid * invalidating the dentries here, alongside with returning the * negative dentries at ext4_lookup(), when it is better * supported by the VFS for the CI case. */ - if (IS_ENABLED(CONFIG_UNICODE) && IS_CASEFOLDED(dir)) + if (!retval && IS_ENABLED(CONFIG_UNICODE) && IS_CASEFOLDED(dir)) d_invalidate(dentry); -end_rmdir: - brelse(bh); - if (handle) - ext4_journal_stop(handle); return retval; } diff --git a/fs/ext4/page-io.c b/fs/ext4/page-io.c index 0236b6b9785a..734d856a6d47 100644 --- a/fs/ext4/page-io.c +++ b/fs/ext4/page-io.c @@ -22,6 +22,7 @@ #include <linux/bio.h> #include <linux/workqueue.h> #include <linux/kernel.h> +#include <linux/iomap.h> #include <linux/slab.h> #include <linux/mm.h> #include <linux/sched/mm.h> @@ -30,6 +31,8 @@ #include "xattr.h" #include "acl.h" +#include <trace/events/ext4.h> + static struct kmem_cache *io_end_cachep; static struct kmem_cache *io_end_vec_cachep; @@ -547,3 +550,201 @@ void ext4_bio_write_folio(struct ext4_io_submit *io, struct folio *folio, io_submit_add_bh(io, inode, folio, bh); } while ((bh = bh->b_this_page) != head); } + +/* + * If the current writeback range starts beyond the zeroed EOF pending + * range that straddles i_disksize, wait for the zeroed data from + * ext4_block_zero_eof() to be written out first. Otherwise, extending + * i_disksize may expose stale data in the old EOF block. + */ +static void ext4_iomap_wb_disksize_pending_wait(struct inode *inode, + loff_t pos, size_t size) +{ + loff_t disksize = READ_ONCE(EXT4_I(inode)->i_disksize); + loff_t pstart, plen; + + /* + * Overwrite I/Os and I/Os covering the EOF block do not need to + * wait: the former do not advance i_disksize past the pending + * boundary, and the latter are the pending I/O itself (cleared in + * the bio completion path). + */ + if (pos < round_up(disksize, i_blocksize(inode))) + return; + + plen = ext4_iomap_get_disksize_pending_range(inode, &pstart); + if (!plen || pos < pstart + plen) + return; + + trace_ext4_iomap_wb_disksize_pending_wait(inode, pos, size); + ext4_iomap_wait_disksize_pending(inode); +} + +static int ext4_iomap_wb_update_disksize(handle_t *handle, struct inode *inode, + loff_t end, bool is_disksize_grow) +{ + loff_t new_disksize, i_size; + struct ext4_inode_info *ei = EXT4_I(inode); + int ret; + + /* + * Races with truncate are avoided by checking i_size under + * i_data_sem. + */ + down_write(&ei->i_data_sem); + i_size = i_size_read(inode); + + /* + * EXT4_STATE_DISKSIZE_GROW_PENDING is cleared when the pending + * I/O completes. However, another thread may have re-set the bit + * between that point and here, meaning i_disksize has already + * been advanced and a new EOF zeroing has been initiated. In that + * case, do not advance i_disksize to i_size; leave it to the + * next pending grow ioend. + */ + if (is_disksize_grow && + ext4_test_inode_state(inode, EXT4_STATE_DISKSIZE_GROW_PENDING)) + is_disksize_grow = false; + + /* + * Update i_disksize to i_size when EXT4_IOMAP_IOEND_DISKSIZE_GROW_IO + * completes. This is safe because we never directly allocate written + * blocks during buffered writes. + * + * This ensures that i_disksize is correctly advanced during + * truncate-up or append fallocate on a block-unaligned file, + * preventing it from remaining stale. The tradeoff is that zeroed + * data may be exposed after crash recovery if dirty data in this + * range is not yet on disk, but stale data will never be exposed. + * This is because the extent is only converted to written state + * after the data has been persisted. + */ + new_disksize = is_disksize_grow ? i_size : min(end, i_size); + if (new_disksize > ei->i_disksize) { + trace_ext4_iomap_wb_update_disksize(inode, end, i_size, + ei->i_disksize, new_disksize, is_disksize_grow); + WRITE_ONCE(ei->i_disksize, new_disksize); + } + up_write(&ei->i_data_sem); + ret = ext4_mark_inode_dirty(handle, inode); + if (ret) + EXT4_ERROR_INODE_ERR(inode, -ret, "Failed to mark inode dirty"); + + return ret; +} + +static void ext4_iomap_finish_ioend(struct iomap_ioend *ioend) +{ + struct inode *inode = ioend->io_inode; + struct super_block *sb = inode->i_sb; + loff_t pos = ioend->io_offset; + size_t size = ioend->io_size; + loff_t end = pos + size; + unsigned long io_mode = (unsigned long)ioend->io_private; + bool is_disksize_grow = (io_mode == EXT4_IOMAP_IOEND_DISKSIZE_GROW_IO); + handle_t *handle; + int credits; + int ret, err; + + ret = blk_status_to_errno(ioend->io_bio.bi_status); + if (is_disksize_grow) + trace_ext4_iomap_wb_disksize_pending_complete(ioend->io_inode, + ioend->io_offset, ioend->io_size, ret); + if (unlikely(ret)) { + if (test_opt(sb, DATA_ERR_ABORT) && !ext4_emergency_state(sb)) + jbd2_journal_abort(EXT4_SB(sb)->s_journal, ret); + goto out; + } + + if (!(ioend->io_flags & IOMAP_IOEND_UNWRITTEN) && + end <= READ_ONCE(EXT4_I(inode)->i_disksize) && !is_disksize_grow) + goto out; + + /* Wait for disksize-pending zeroed data to be written out. */ + ext4_iomap_wb_disksize_pending_wait(inode, pos, size); + + /* + * We may need to convert one extent, update the i_disksize and + * dirty the inode. + */ + credits = ext4_chunk_trans_blocks(inode, + EXT4_MAX_BLOCKS(size, pos, inode->i_blkbits)); + handle = ext4_journal_start(inode, EXT4_HT_EXT_CONVERT, credits); + if (IS_ERR(handle)) { + ret = PTR_ERR(handle); + goto out_err; + } + + /* Update on-disk size after I/O is completed. */ + if (end > READ_ONCE(EXT4_I(inode)->i_disksize) || is_disksize_grow) { + ret = ext4_iomap_wb_update_disksize(handle, inode, end, + is_disksize_grow); + if (ret) + goto out_journal; + } + + if (ioend->io_flags & IOMAP_IOEND_UNWRITTEN) + ret = ext4_convert_unwritten_extents(handle, inode, pos, + size, NULL); + +out_journal: + err = ext4_journal_stop(handle); + if (!ret) + ret = err; +out_err: + if (ret < 0 && !ext4_emergency_state(sb)) { + ext4_msg(sb, KERN_EMERG, + "failed to convert unwritten extents to written extents or update inode size -- potential data loss! (inode %llu, error %d)", + inode->i_ino, ret); + } +out: + iomap_finish_ioends(ioend, ret); +} + +/* + * Work on buffered iomap completed IO, to convert unwritten extents to + * mapped extents + */ +void ext4_iomap_end_io(struct work_struct *work) +{ + struct ext4_inode_info *ei = container_of(work, struct ext4_inode_info, + i_rsv_conversion_work); + struct iomap_ioend *ioend; + struct list_head ioend_list; + unsigned long flags; + + spin_lock_irqsave(&ei->i_completed_io_lock, flags); + list_replace_init(&ei->i_rsv_conversion_list, &ioend_list); + spin_unlock_irqrestore(&ei->i_completed_io_lock, flags); + + iomap_sort_ioends(&ioend_list); + while (!list_empty(&ioend_list)) { + ioend = list_entry(ioend_list.next, struct iomap_ioend, io_list); + list_del_init(&ioend->io_list); + iomap_ioend_try_merge(ioend, &ioend_list); + ext4_iomap_finish_ioend(ioend); + } +} + +void ext4_iomap_end_bio(struct bio *bio) +{ + struct iomap_ioend *ioend = iomap_ioend_from_bio(bio); + struct ext4_inode_info *ei = EXT4_I(ioend->io_inode); + unsigned long io_mode = (unsigned long)ioend->io_private; + unsigned long flags; + + /* + * This is a disksize-pending I/O: clear the disksize-pending + * state set in ext4_block_zero_eof() and wake up all waiters + * that will update the inode i_disksize. + */ + if (io_mode == EXT4_IOMAP_IOEND_DISKSIZE_GROW_IO) + ext4_iomap_clear_disksize_pending(ioend->io_inode); + + spin_lock_irqsave(&ei->i_completed_io_lock, flags); + if (list_empty(&ei->i_rsv_conversion_list)) + queue_work(EXT4_SB(ioend->io_inode->i_sb)->rsv_conversion_wq, + &ei->i_rsv_conversion_work); + list_add_tail(&ioend->io_list, &ei->i_rsv_conversion_list); + spin_unlock_irqrestore(&ei->i_completed_io_lock, flags); +} diff --git a/fs/ext4/resize.c b/fs/ext4/resize.c index 2c5b851c552a..5e3ad8393cf8 100644 --- a/fs/ext4/resize.c +++ b/fs/ext4/resize.c @@ -1462,11 +1462,11 @@ static void ext4_update_super(struct super_block *sb, * * The precise rules we use are: * - * * Writers must perform a smp_wmb() after updating all - * dependent data and before modifying the groups count + * * Writers must use a release store when updating the groups count + * after all dependent data has been updated * - * * Readers must perform an smp_rmb() after reading the groups - * count and before reading any dependent data. + * * Readers must use an acquire load when reading the groups + * count before reading any dependent data. * * NB. These rules can be relaxed when checking the group count * while freeing data, as we can only allocate from a block @@ -1474,12 +1474,15 @@ static void ext4_update_super(struct super_block *sb, * only then free after serialising in turn against that * allocation. */ - smp_wmb(); /* Update the global fs size fields */ - sbi->s_groups_count += flex_gd->count; - sbi->s_blockfile_groups = min(sbi->s_groups_count, - (EXT4_MAX_BLOCK_FILE_PHYS / EXT4_BLOCKS_PER_GROUP(sb))); + /* Pairs with smp_load_acquire() in ext4_get_groups_count() */ + smp_store_release(&sbi->s_groups_count, + sbi->s_groups_count + flex_gd->count); + /* Pairs with smp_load_acquire() in ext4_get_allocation_groups_count() */ + smp_store_release(&sbi->s_blockfile_groups, + min(sbi->s_groups_count, + (EXT4_MAX_BLOCK_FILE_PHYS / EXT4_BLOCKS_PER_GROUP(sb)))); /* Update the reserved block counts only once the new group is * active. */ diff --git a/fs/ext4/super.c b/fs/ext4/super.c index 3ace06b4f9e3..9cae52471dee 100644 --- a/fs/ext4/super.c +++ b/fs/ext4/super.c @@ -100,13 +100,21 @@ static const struct fs_parameter_spec ext4_param_specs[]; * Lock ordering * * page fault path: - * mmap_lock -> sb_start_pagefault -> invalidate_lock (r) -> transaction start - * -> page lock -> i_data_sem (rw) + * - buffer_head path: + * mmap_lock -> sb_start_pagefault -> invalidate_lock (r) -> + * transaction start -> folio lock -> i_data_sem (rw) + * - iomap path: + * mmap_lock -> sb_start_pagefault -> invalidate_lock (r) -> + * folio lock -> transaction start -> i_data_sem (rw) * * buffered write path: - * sb_start_write -> i_mutex -> mmap_lock - * sb_start_write -> i_mutex -> transaction start -> page lock -> - * i_data_sem (rw) + * sb_start_write -> i_rwsem (w) -> mmap_lock + * - buffer_head path: + * sb_start_write -> i_rwsem (w) -> transaction start -> folio lock -> + * i_data_sem (rw) + * - iomap path: + * sb_start_write -> i_rwsem (w) -> transaction start -> i_data_sem (rw) + * sb_start_write -> i_rwsem (w) -> folio lock (not under an active handle) * * truncate: * sb_start_write -> i_mutex -> invalidate_lock (w) -> i_mmap_rwsem (w) -> @@ -119,7 +127,10 @@ static const struct fs_parameter_spec ext4_param_specs[]; * sb_start_write -> i_mutex -> transaction start -> i_data_sem (rw) * * writepages: - * transaction start -> page lock(s) -> i_data_sem (rw) + * - buffer_head path: + * transaction start -> folio lock(s) -> i_data_sem (rw) + * - iomap path: + * folio lock -> transaction start -> i_data_sem (rw) */ static const struct fs_context_operations ext4_context_ops = { @@ -1494,6 +1505,14 @@ static void ext4_destroy_inode(struct inode *inode) "Inode %llu (%p): i_reserved_data_blocks (%u) not cleared!", inode->i_ino, EXT4_I(inode), EXT4_I(inode)->i_reserved_data_blocks); + + if (!(EXT4_SB(inode->i_sb)->s_mount_state & EXT4_ERROR_FS) && + !ext4_emergency_state(inode->i_sb) && + WARN_ON_ONCE(ext4_test_inode_state(inode, + EXT4_STATE_DISKSIZE_GROW_PENDING))) + ext4_msg(inode->i_sb, KERN_ERR, + "Inode %llu (%p): EXT4_STATE_DISKSIZE_GROW_PENDING not cleared!", + inode->i_ino, EXT4_I(inode)); } static void ext4_shutdown(struct super_block *sb) @@ -1736,6 +1755,7 @@ enum { Opt_discard, Opt_nodiscard, Opt_init_itable, Opt_noinit_itable, Opt_max_dir_size_kb, Opt_nojournal_checksum, Opt_nombcache, Opt_no_prefetch_block_bitmaps, Opt_mb_optimize_scan, + Opt_buffered_iomap, Opt_nobuffered_iomap, Opt_errors, Opt_data, Opt_data_err, Opt_jqfmt, Opt_dax_type, #ifdef CONFIG_EXT4_DEBUG Opt_fc_debug_max_replay, Opt_fc_debug_force @@ -1874,6 +1894,8 @@ static const struct fs_parameter_spec ext4_param_specs[] = { fsparam_flag ("no_prefetch_block_bitmaps", Opt_no_prefetch_block_bitmaps), fsparam_s32 ("mb_optimize_scan", Opt_mb_optimize_scan), + fsparam_flag ("buffered_iomap", Opt_buffered_iomap), + fsparam_flag ("nobuffered_iomap", Opt_nobuffered_iomap), fsparam_string ("check", Opt_removed), /* mount option from ext2/3 */ fsparam_flag ("nocheck", Opt_removed), /* mount option from ext2/3 */ fsparam_flag ("reservation", Opt_removed), /* mount option from ext2/3 */ @@ -1967,6 +1989,10 @@ static const struct mount_opts { {Opt_nombcache, EXT4_MOUNT_NO_MBCACHE, MOPT_SET}, {Opt_no_prefetch_block_bitmaps, EXT4_MOUNT_NO_PREFETCH_BLOCK_BITMAPS, MOPT_SET}, + {Opt_buffered_iomap, EXT4_MOUNT2_BUFFERED_IOMAP, + MOPT_SET | MOPT_2 | MOPT_EXT4_ONLY}, + {Opt_nobuffered_iomap, EXT4_MOUNT2_BUFFERED_IOMAP, + MOPT_CLEAR | MOPT_2 | MOPT_EXT4_ONLY}, #ifdef CONFIG_EXT4_DEBUG {Opt_fc_debug_force, EXT4_MOUNT2_JOURNAL_FAST_COMMIT, MOPT_SET | MOPT_2 | MOPT_EXT4_ONLY}, @@ -2840,6 +2866,14 @@ static int ext4_check_opt_consistency(struct fs_context *fc, } if (is_remount) { + if (test_opt(sb, DELALLOC) && + (ctx->mask_s_mount_opt & EXT4_MOUNT_DELALLOC) && + !ctx_test_mount_opt(ctx, EXT4_MOUNT_DELALLOC)) { + ext4_msg(sb, KERN_ERR, + "can't disable delalloc during remount"); + return -EINVAL; + } + if (!sbi->s_journal && ctx_test_mount_opt(ctx, EXT4_MOUNT_DATA_ERR_ABORT)) { ext4_msg(NULL, KERN_WARNING, @@ -6133,6 +6167,7 @@ static int ext4_load_journal(struct super_block *sb, int err = 0; int really_read_only; int journal_dev_ro; + bool enable_write = false; if (WARN_ON_ONCE(!ext4_has_feature_journal(sb))) return -EFSCORRUPTED; @@ -6189,6 +6224,7 @@ static int ext4_load_journal(struct super_block *sb, } ext4_msg(sb, KERN_INFO, "write access will " "be enabled during recovery"); + enable_write = true; } } @@ -6205,7 +6241,19 @@ static int ext4_load_journal(struct super_block *sb, if (save) memcpy(save, ((char *) es) + EXT4_S_ERR_START, EXT4_S_ERR_LEN); + /* + * Fast commit replay performs regular ext4 metadata updates + * (see ext4_fc_replay()) which refuse to run on a read-only + * superblock. We promised write access above, so make that + * true for the duration of the recovery, the same way + * ext4_orphan_cleanup() does. The superblock is not published + * yet, so nothing can observe the transient state. + */ + if (enable_write) + sb->s_flags &= ~SB_RDONLY; err = jbd2_journal_load(journal); + if (enable_write) + sb->s_flags |= SB_RDONLY; if (save && memcmp(((char *) es) + EXT4_S_ERR_START, save, EXT4_S_ERR_LEN)) { memcpy(((char *) es) + EXT4_S_ERR_START, @@ -6698,13 +6746,6 @@ static int __ext4_remount(struct fs_context *fc, struct super_block *sb) goto restore_opts; } - if ((old_opts.s_mount_opt & EXT4_MOUNT_DELALLOC) && - !test_opt(sb, DELALLOC)) { - ext4_msg(sb, KERN_ERR, "can't disable delalloc during remount"); - err = -EINVAL; - goto restore_opts; - } - sb->s_flags = (sb->s_flags & ~SB_POSIXACL) | (test_opt(sb, POSIX_ACL) ? SB_POSIXACL : 0); diff --git a/fs/jbd2/journal.c b/fs/jbd2/journal.c index 00f5a98f3d4f..55b4ce010228 100644 --- a/fs/jbd2/journal.c +++ b/fs/jbd2/journal.c @@ -843,7 +843,6 @@ int jbd2_fc_get_buf(journal_t *journal, struct buffer_head **bh_out) fc_off = journal->j_fc_off; blocknr = journal->j_fc_first + fc_off; - journal->j_fc_off++; ret = jbd2_journal_bmap(journal, blocknr, &pblock); if (ret) return ret; @@ -853,6 +852,7 @@ int jbd2_fc_get_buf(journal_t *journal, struct buffer_head **bh_out) return -ENOMEM; journal->j_fc_wbuf[fc_off] = bh; + journal->j_fc_off++; *bh_out = bh; diff --git a/include/linux/jbd2.h b/include/linux/jbd2.h index 1b42fe47c26b..e3886963ba00 100644 --- a/include/linux/jbd2.h +++ b/include/linux/jbd2.h @@ -795,7 +795,7 @@ struct journal_s /** * @j_state_lock: Protect the various scalars in the journal. */ - rwlock_t j_state_lock; + rwlock_t j_state_lock ____cacheline_aligned_in_smp; /** * @j_barrier_count: diff --git a/include/trace/events/ext4.h b/include/trace/events/ext4.h index 7028a28316fa..74d09a2a58cc 100644 --- a/include/trace/events/ext4.h +++ b/include/trace/events/ext4.h @@ -3157,6 +3157,213 @@ TRACE_EVENT(ext4_move_extent_exit, __entry->ret) ); +DECLARE_EVENT_CLASS(ext4_set_iomap_class, + TP_PROTO(struct inode *inode, struct ext4_map_blocks *map, + loff_t offset, loff_t length, unsigned int flags), + TP_ARGS(inode, map, offset, length, flags), + TP_STRUCT__entry( + __field(dev_t, dev) + __field(u64, ino) + __field(ext4_lblk_t, m_lblk) + __field(unsigned int, m_len) + __field(unsigned int, m_flags) + __field(u64, m_seq) + __field(loff_t, offset) + __field(loff_t, length) + __field(unsigned int, iomap_flags) + ), + TP_fast_assign( + __entry->dev = inode->i_sb->s_dev; + __entry->ino = inode->i_ino; + __entry->m_lblk = map->m_lblk; + __entry->m_len = map->m_len; + __entry->m_flags = map->m_flags; + __entry->m_seq = map->m_seq; + __entry->offset = offset; + __entry->length = length; + __entry->iomap_flags = flags; + + ), + TP_printk("dev %d:%d ino %llu m_lblk %u m_len %u m_flags %s m_seq %llu orig_off 0x%llx orig_len 0x%llx iomap_flags 0x%x", + MAJOR(__entry->dev), MINOR(__entry->dev), + __entry->ino, __entry->m_lblk, __entry->m_len, + show_mflags(__entry->m_flags), __entry->m_seq, + __entry->offset, __entry->length, __entry->iomap_flags) +) + +#define DEFINE_SET_IOMAP_EVENT(name) \ +DEFINE_EVENT(ext4_set_iomap_class, name, \ + TP_PROTO(struct inode *inode, struct ext4_map_blocks *map, \ + loff_t offset, loff_t length, unsigned int flags), \ + TP_ARGS(inode, map, offset, length, flags)) + +DEFINE_SET_IOMAP_EVENT(ext4_iomap_buffered_read_begin); +DEFINE_SET_IOMAP_EVENT(ext4_iomap_buffered_write_begin); +DEFINE_SET_IOMAP_EVENT(ext4_iomap_map_writeback_range); +DEFINE_SET_IOMAP_EVENT(ext4_iomap_zero_begin); + +DECLARE_EVENT_CLASS(ext4_iomap_disksize_pending, + TP_PROTO(struct inode *inode), + TP_ARGS(inode), + TP_STRUCT__entry( + __field(dev_t, dev) + __field(u64, ino) + __field(loff_t, i_disksize) + ), + TP_fast_assign( + __entry->dev = inode->i_sb->s_dev; + __entry->ino = inode->i_ino; + __entry->i_disksize = READ_ONCE(EXT4_I(inode)->i_disksize); + ), + TP_printk("dev %d:%d ino %llu i_disksize %lld", + MAJOR(__entry->dev), MINOR(__entry->dev), + __entry->ino, __entry->i_disksize) +); + +DEFINE_EVENT(ext4_iomap_disksize_pending, ext4_iomap_mark_disksize_pending, + TP_PROTO(struct inode *inode), + TP_ARGS(inode) +); + +DEFINE_EVENT(ext4_iomap_disksize_pending, ext4_iomap_clear_disksize_pending, + TP_PROTO(struct inode *inode), + TP_ARGS(inode) +); + +DEFINE_EVENT(ext4_iomap_disksize_pending, ext4_iomap_wait_disksize_pending, + TP_PROTO(struct inode *inode), + TP_ARGS(inode) +); + +/* disksize pending I/O tracepoints for iomap Buffered I/O path */ +DECLARE_EVENT_CLASS(ext4_iomap_wb_disksize_pending, + TP_PROTO(struct inode *inode, loff_t io_offset, size_t io_size), + TP_ARGS(inode, io_offset, io_size), + TP_STRUCT__entry( + __field(dev_t, dev) + __field(u64, ino) + __field(loff_t, io_offset) + __field(size_t, io_size) + __field(loff_t, i_size) + __field(loff_t, i_disksize) + ), + TP_fast_assign( + __entry->dev = inode->i_sb->s_dev; + __entry->ino = inode->i_ino; + __entry->io_offset = io_offset; + __entry->io_size = io_size; + __entry->i_size = i_size_read(inode); + __entry->i_disksize = READ_ONCE(EXT4_I(inode)->i_disksize); + ), + TP_printk("dev %d:%d ino %llu io_offset %lld io_size %zu i_size %lld i_disksize %lld", + MAJOR(__entry->dev), MINOR(__entry->dev), + __entry->ino, __entry->io_offset, __entry->io_size, + __entry->i_size, __entry->i_disksize) +); + +DEFINE_EVENT(ext4_iomap_wb_disksize_pending, + ext4_iomap_wb_disksize_pending_submit, + TP_PROTO(struct inode *inode, loff_t io_offset, size_t io_size), + TP_ARGS(inode, io_offset, io_size) +); + +DEFINE_EVENT(ext4_iomap_wb_disksize_pending, + ext4_iomap_wb_disksize_pending_wait, + TP_PROTO(struct inode *inode, loff_t io_offset, size_t io_size), + TP_ARGS(inode, io_offset, io_size) +); + +TRACE_EVENT(ext4_iomap_wb_disksize_pending_complete, + TP_PROTO(struct inode *inode, loff_t io_offset, size_t io_size, + int ret), + TP_ARGS(inode, io_offset, io_size, ret), + TP_STRUCT__entry( + __field(dev_t, dev) + __field(u64, ino) + __field(loff_t, io_offset) + __field(size_t, io_size) + __field(loff_t, i_size) + __field(loff_t, i_disksize) + __field(int, ret) + ), + TP_fast_assign( + __entry->dev = inode->i_sb->s_dev; + __entry->ino = inode->i_ino; + __entry->io_offset = io_offset; + __entry->io_size = io_size; + __entry->i_size = i_size_read(inode); + __entry->i_disksize = READ_ONCE(EXT4_I(inode)->i_disksize); + __entry->ret = ret; + ), + TP_printk("dev %d:%d ino %llu io_offset %lld io_size %zu ret %d i_size %lld i_disksize %lld", + MAJOR(__entry->dev), MINOR(__entry->dev), + __entry->ino, __entry->io_offset, __entry->io_size, + __entry->ret, __entry->i_size, __entry->i_disksize) +); + +/* i_disksize update tracepoint */ +TRACE_EVENT(ext4_iomap_wb_update_disksize, + TP_PROTO(struct inode *inode, loff_t end, loff_t i_size, + loff_t i_disksize, loff_t new_disksize, + bool is_disksize_grow), + TP_ARGS(inode, end, i_size, i_disksize, new_disksize, + is_disksize_grow), + TP_STRUCT__entry( + __field(dev_t, dev) + __field(u64, ino) + __field(loff_t, end) + __field(loff_t, i_size) + __field(loff_t, i_disksize) + __field(loff_t, new_disksize) + __field(bool, is_disksize_grow) + ), + TP_fast_assign( + __entry->dev = inode->i_sb->s_dev; + __entry->ino = inode->i_ino; + __entry->end = end; + __entry->i_size = i_size; + __entry->i_disksize = i_disksize; + __entry->new_disksize = new_disksize; + __entry->is_disksize_grow = is_disksize_grow; + ), + TP_printk("dev %d:%d ino %llu end %lld i_size %lld i_disksize %lld new_disksize %lld is_disksize_grow %d", + MAJOR(__entry->dev), MINOR(__entry->dev), + __entry->ino, __entry->end, __entry->i_size, + __entry->i_disksize, __entry->new_disksize, + __entry->is_disksize_grow) +); + +/* Block zero EOF tracepoint */ +TRACE_EVENT(ext4_block_zero_eof, + TP_PROTO(struct inode *inode, loff_t from, loff_t length, + bool did_zero, bool zero_written), + TP_ARGS(inode, from, length, did_zero, zero_written), + TP_STRUCT__entry( + __field(dev_t, dev) + __field(u64, ino) + __field(loff_t, from) + __field(loff_t, length) + __field(loff_t, i_size) + __field(loff_t, i_disksize) + __field(bool, did_zero) + __field(bool, zero_written) + ), + TP_fast_assign( + __entry->dev = inode->i_sb->s_dev; + __entry->ino = inode->i_ino; + __entry->from = from; + __entry->length = length; + __entry->i_size = inode->i_size; + __entry->i_disksize = READ_ONCE(EXT4_I(inode)->i_disksize); + __entry->did_zero = did_zero; + __entry->zero_written = zero_written; + ), + TP_printk("dev %d:%d ino %llu zero EOF from %lld length %lld i_size %lld i_disksize %lld did_zero %d zero_written %d", + MAJOR(__entry->dev), MINOR(__entry->dev), __entry->ino, + __entry->from, __entry->length, __entry->i_size, + __entry->i_disksize, __entry->did_zero, __entry->zero_written) +); + #endif /* _TRACE_EXT4_H */ /* This part must be outside protection */ |
