summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Brown <broonie@kernel.org>2026-10-14 12:45:06 +0100
committerMark Brown <broonie@kernel.org>2026-10-14 12:45:06 +0100
commit4bf16fec7a178dbc5e8d521091dbcae9dfe22cbd (patch)
tree8baec61a2e632f777df7633fa1f5058969b7dee4
parent72704dd26c8c8f7b11e78c9f7ed18831db88be62 (diff)
parent35aefefd7aba0801bab635b11943f932626be3b2 (diff)
downloadlinux-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.c2
-rw-r--r--fs/ext4/ext4.h96
-rw-r--r--fs/ext4/ext4_jbd2.c8
-rw-r--r--fs/ext4/ext4_jbd2.h7
-rw-r--r--fs/ext4/extents.c77
-rw-r--r--fs/ext4/file.c20
-rw-r--r--fs/ext4/ialloc.c1
-rw-r--r--fs/ext4/indirect.c17
-rw-r--r--fs/ext4/inode.c1182
-rw-r--r--fs/ext4/ioctl.c3
-rw-r--r--fs/ext4/mballoc.c6
-rw-r--r--fs/ext4/migrate.c2
-rw-r--r--fs/ext4/move_extent.c11
-rw-r--r--fs/ext4/namei.c11
-rw-r--r--fs/ext4/page-io.c201
-rw-r--r--fs/ext4/resize.c19
-rw-r--r--fs/ext4/super.c67
-rw-r--r--fs/jbd2/journal.c2
-rw-r--r--include/linux/jbd2.h2
-rw-r--r--include/trace/events/ext4.h207
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 */