* [PATCH 02/10] libext2fs: fix checking for valid fds in mmp.c
From: Darrick J. Wong @ 2026-04-22 23:24 UTC (permalink / raw)
To: tytso
Cc: linux-ext4, linux-fsdevel, fuse-devel, linux-ext4, neal,
joannelkoong, miklos, bernd
In-Reply-To: <177689989498.3821326.15497525132012299039.stgit@frogsfrogsfrogs>
From: Darrick J. Wong <djwong@kernel.org>
File descriptors are non-negative numbers, which means that 0 is a valid
fd. Fix the code to be consistent with Unix behaviors.
Cc: <linux-ext4@vger.kernel.org> # v1.42
Fixes: 0f5eba7501f467 ("ext2fs: add multi-mount protection (INCOMPAT_MMP)")
Fixes: 76a6c8788c79e4 ("mmp: do not use O_DIRECT when working with regular file")
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
lib/ext2fs/mmp.c | 6 +++---
lib/ext2fs/openfs.c | 1 +
2 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/lib/ext2fs/mmp.c b/lib/ext2fs/mmp.c
index e2823732e2b6a2..cb15a18fce5547 100644
--- a/lib/ext2fs/mmp.c
+++ b/lib/ext2fs/mmp.c
@@ -59,11 +59,11 @@ errcode_t ext2fs_mmp_read(ext2_filsys fs, blk64_t mmp_blk, void *buf)
return EXT2_ET_MMP_BAD_BLOCK;
/* ext2fs_open() reserves fd0,1,2 to avoid stdio collision, so checking
- * mmp_fd <= 0 is OK to validate that the fd is valid. This opens its
+ * mmp_fd < 0 is OK to validate that the fd is valid. This opens its
* own fd to read the MMP block to ensure that it is using O_DIRECT,
* regardless of how the io_manager is doing reads, to avoid caching of
* the MMP block by the io_manager or the VM. It needs to be fresh. */
- if (fs->mmp_fd <= 0) {
+ if (fs->mmp_fd < 0) {
struct stat st;
int flags = O_RDONLY | O_DIRECT;
@@ -427,7 +427,7 @@ errcode_t ext2fs_mmp_stop(ext2_filsys fs)
retval = ext2fs_mmp_write(fs, fs->super->s_mmp_block, fs->mmp_cmp);
mmp_error:
- if (fs->mmp_fd > 0) {
+ if (fs->mmp_fd >= 0) {
close(fs->mmp_fd);
fs->mmp_fd = -1;
}
diff --git a/lib/ext2fs/openfs.c b/lib/ext2fs/openfs.c
index 2b8e0e753c46e8..41359d15740881 100644
--- a/lib/ext2fs/openfs.c
+++ b/lib/ext2fs/openfs.c
@@ -148,6 +148,7 @@ errcode_t ext2fs_open2(const char *name, const char *io_options,
/* don't overwrite sb backups unless flag is explicitly cleared */
fs->flags |= EXT2_FLAG_MASTER_SB_ONLY;
fs->umask = 022;
+ fs->mmp_fd = -1;
time_env = ext2fs_safe_getenv("SOURCE_DATE_EPOCH");
if (time_env) {
^ permalink raw reply related
* [PATCH 01/10] libext2fs: make it possible to extract the fd from an IO manager
From: Darrick J. Wong @ 2026-04-22 23:23 UTC (permalink / raw)
To: tytso
Cc: linux-fsdevel, fuse-devel, linux-ext4, neal, joannelkoong, miklos,
bernd
In-Reply-To: <177689989498.3821326.15497525132012299039.stgit@frogsfrogsfrogs>
From: Darrick J. Wong <djwong@kernel.org>
Make it so that we can extract the fd from an open IO manager. This
will be used in subsequent patches to register the open block device
with the fuse iomap kernel driver.
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
lib/ext2fs/ext2_io.h | 4 +++-
debian/libext2fs2t64.symbols | 1 +
lib/ext2fs/io_manager.c | 8 ++++++++
lib/ext2fs/unix_io.c | 20 ++++++++++++++++++++
4 files changed, 32 insertions(+), 1 deletion(-)
diff --git a/lib/ext2fs/ext2_io.h b/lib/ext2fs/ext2_io.h
index 61865d54d82490..c880ea2524f248 100644
--- a/lib/ext2fs/ext2_io.h
+++ b/lib/ext2fs/ext2_io.h
@@ -103,7 +103,8 @@ struct struct_io_manager {
errcode_t (*zeroout)(io_channel channel, unsigned long long block,
unsigned long long count);
errcode_t (*flock)(io_channel channel, unsigned int flock_flags);
- long reserved[13];
+ errcode_t (*get_fd)(io_channel channel, int *fd);
+ long reserved[12];
};
#define IO_FLAG_RW 0x0001
@@ -155,6 +156,7 @@ extern errcode_t io_channel_cache_readahead(io_channel io,
unsigned long long count);
extern errcode_t io_channel_flock(io_channel io, unsigned int flock_flags);
extern errcode_t io_channel_funlock(io_channel io);
+extern errcode_t io_channel_get_fd(io_channel io, int *fd);
#ifdef _WIN32
/* windows_io.c */
diff --git a/debian/libext2fs2t64.symbols b/debian/libext2fs2t64.symbols
index affe4c27d4e791..555fbbb0c98878 100644
--- a/debian/libext2fs2t64.symbols
+++ b/debian/libext2fs2t64.symbols
@@ -701,6 +701,7 @@ libext2fs.so.2 libext2fs2t64 #MINVER#
io_channel_discard@Base 1.42
io_channel_flock@Base 1.47.99
io_channel_funlock@Base 1.47.99
+ io_channel_get_fd@Base 1.47.99
io_channel_read_blk64@Base 1.41.1
io_channel_set_options@Base 1.37
io_channel_write_blk64@Base 1.41.1
diff --git a/lib/ext2fs/io_manager.c b/lib/ext2fs/io_manager.c
index 791ec7d14adbba..dff3d73552827f 100644
--- a/lib/ext2fs/io_manager.c
+++ b/lib/ext2fs/io_manager.c
@@ -166,3 +166,11 @@ errcode_t io_channel_funlock(io_channel io)
return io->manager->flock(io, 0);
}
+
+errcode_t io_channel_get_fd(io_channel io, int *fd)
+{
+ if (!io->manager->get_fd)
+ return EXT2_ET_OP_NOT_SUPPORTED;
+
+ return io->manager->get_fd(io, fd);
+}
diff --git a/lib/ext2fs/unix_io.c b/lib/ext2fs/unix_io.c
index f4307db0fb2b05..79bc9219f9515b 100644
--- a/lib/ext2fs/unix_io.c
+++ b/lib/ext2fs/unix_io.c
@@ -1786,6 +1786,24 @@ static errcode_t unix_zeroout(io_channel channel, unsigned long long block,
unimplemented:
return EXT2_ET_UNIMPLEMENTED;
}
+
+static errcode_t unix_get_fd(io_channel channel, int *fd)
+{
+ struct unix_private_data *data;
+
+ EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL);
+ data = (struct unix_private_data *) channel->private_data;
+ EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_UNIX_IO_CHANNEL);
+
+ if (data->offset) {
+ *fd = -1;
+ return EINVAL;
+ }
+
+ *fd = data->dev;
+ return 0;
+}
+
#if __GNUC_PREREQ (4, 6)
#pragma GCC diagnostic pop
#endif
@@ -1808,6 +1826,7 @@ static struct struct_io_manager struct_unix_manager = {
.cache_readahead = unix_cache_readahead,
.zeroout = unix_zeroout,
.flock = unix_flock,
+ .get_fd = unix_get_fd,
};
io_manager unix_io_manager = &struct_unix_manager;
@@ -1830,6 +1849,7 @@ static struct struct_io_manager struct_unixfd_manager = {
.cache_readahead = unix_cache_readahead,
.zeroout = unix_zeroout,
.flock = unix_flock,
+ .get_fd = unix_get_fd,
};
io_manager unixfd_io_manager = &struct_unixfd_manager;
^ permalink raw reply related
* [PATCH 3/3] libext2fs: only fsync the unix fd if we wrote to the device
From: Darrick J. Wong @ 2026-04-22 23:23 UTC (permalink / raw)
To: tytso; +Cc: linux-ext4
In-Reply-To: <177689989303.3821152.12873703999139555043.stgit@frogsfrogsfrogs>
From: Darrick J. Wong <djwong@kernel.org>
As an optimization, only fsync the block device fd if we tried to write
to the io channel.
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
lib/ext2fs/unix_io.c | 86 +++++++++++++++++++++++++++++++++++++++-----------
1 file changed, 67 insertions(+), 19 deletions(-)
diff --git a/lib/ext2fs/unix_io.c b/lib/ext2fs/unix_io.c
index 15d6d55ff7fdd4..f4307db0fb2b05 100644
--- a/lib/ext2fs/unix_io.c
+++ b/lib/ext2fs/unix_io.c
@@ -132,10 +132,13 @@ struct unix_cache {
#define WRITE_DIRECT_SIZE 4 /* Must be smaller than CACHE_SIZE */
#define READ_DIRECT_SIZE 4 /* Should be smaller than CACHE_SIZE */
+#define UNIX_STATE_DIRTY (1U << 0) /* device needs fsyncing */
+
struct unix_private_data {
int magic;
int dev;
int flags;
+ unsigned int state; /* UNIX_STATE_* */
int align;
int access_time;
int unix_flock_flags;
@@ -1198,10 +1201,65 @@ static errcode_t unix_open(const char *name, int flags,
return unix_open_channel(name, fd, flags, channel, unix_io_manager);
}
+#ifdef HAVE_FSYNC
+static void mark_dirty(io_channel channel)
+{
+ struct unix_private_data *data =
+ (struct unix_private_data *) channel->private_data;
+
+ mutex_lock(data, CACHE_MTX);
+ data->state |= UNIX_STATE_DIRTY;
+ mutex_unlock(data, CACHE_MTX);
+}
+
+static errcode_t maybe_fsync(io_channel channel, int force_fsync)
+{
+ struct unix_private_data *data =
+ (struct unix_private_data *) channel->private_data;
+ int need_fsync;
+ errcode_t retval = 0;
+
+#ifndef NO_IO_CACHE
+ retval = flush_cached_blocks(channel, data, 0);
+#endif
+
+ mutex_lock(data, CACHE_MTX);
+ need_fsync = force_fsync || (data->state & UNIX_STATE_DIRTY);
+ data->state &= ~UNIX_STATE_DIRTY;
+ mutex_unlock(data, CACHE_MTX);
+
+ if (need_fsync && fsync(data->dev) != 0) {
+ if (!retval)
+ retval = errno;
+ }
+ if (retval) {
+ /* redirty because writeback failed */
+ mark_dirty(channel);
+ return retval;
+ }
+
+ return 0;
+}
+#else
+# define mark_dirty(...) ((void)0)
+
+static errcode_t maybe_fsync(io_channel channel, int force_fsync)
+{
+ struct unix_private_data *data =
+ (struct unix_private_data *) channel->private_data;
+ errcode_t retval = 0;
+
+#ifndef NO_IO_CACHE
+ retval = flush_cached_blocks(channel, data, 0);
+#endif
+ return retval;
+}
+#endif
+
static errcode_t unix_close(io_channel channel)
{
struct unix_private_data *data;
- errcode_t retval = 0;
+ errcode_t retval;
EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL);
data = (struct unix_private_data *) channel->private_data;
@@ -1210,14 +1268,7 @@ static errcode_t unix_close(io_channel channel)
if (--channel->refcount > 0)
return 0;
-#ifndef NO_IO_CACHE
- retval = flush_cached_blocks(channel, data, 0);
-#endif
-#ifdef HAVE_FSYNC
- /* always fsync the device, even if flushing our own cache failed */
- if (fsync(data->dev) != 0 && !retval)
- retval = errno;
-#endif
+ retval = maybe_fsync(channel, 1);
unix_funlock(channel);
@@ -1388,6 +1439,8 @@ static errcode_t unix_write_blk64(io_channel channel, unsigned long long block,
data = (struct unix_private_data *) channel->private_data;
EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_UNIX_IO_CHANNEL);
+ mark_dirty(channel);
+
#ifdef NO_IO_CACHE
return raw_write_blk(channel, data, block, count, buf, 0);
#else
@@ -1512,6 +1565,8 @@ static errcode_t unix_write_byte(io_channel channel, unsigned long offset,
if (lseek(data->dev, offset + data->offset, SEEK_SET) < 0)
return errno;
+ mark_dirty(channel);
+
actual = write(data->dev, buf, size);
if (actual < 0)
return errno;
@@ -1527,21 +1582,12 @@ static errcode_t unix_write_byte(io_channel channel, unsigned long offset,
static errcode_t unix_flush(io_channel channel)
{
struct unix_private_data *data;
- errcode_t retval = 0;
EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL);
data = (struct unix_private_data *) channel->private_data;
EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_UNIX_IO_CHANNEL);
-#ifndef NO_IO_CACHE
- retval = flush_cached_blocks(channel, data, 0);
-#endif
-#ifdef HAVE_FSYNC
- /* always fsync the device, even if flushing our own cache failed */
- if (fsync(data->dev) != 0 && !retval)
- return errno;
-#endif
- return retval;
+ return maybe_fsync(channel, 0);
}
static errcode_t unix_set_option(io_channel channel, const char *option,
@@ -1653,6 +1699,7 @@ static errcode_t unix_discard(io_channel channel, unsigned long long block,
}
return errno;
}
+ mark_dirty(channel);
return 0;
unimplemented:
return EXT2_ET_UNIMPLEMENTED;
@@ -1734,6 +1781,7 @@ static errcode_t unix_zeroout(io_channel channel, unsigned long long block,
}
return errno;
}
+ mark_dirty(channel);
return 0;
unimplemented:
return EXT2_ET_UNIMPLEMENTED;
^ permalink raw reply related
* [PATCH 2/3] libext2fs: always fsync the device when closing the unix IO manager
From: Darrick J. Wong @ 2026-04-22 23:23 UTC (permalink / raw)
To: tytso; +Cc: linux-ext4
In-Reply-To: <177689989303.3821152.12873703999139555043.stgit@frogsfrogsfrogs>
From: Darrick J. Wong <djwong@kernel.org>
unix_close is the last chance that libext2fs has to report write
failures to users. Although it's likely that ext2fs_close already
called ext2fs_flush and told the IO manager to flush, we could do one
more sync before we close the file descriptor. Also don't override the
fsync's errno with the close's errno.
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
lib/ext2fs/unix_io.c | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/lib/ext2fs/unix_io.c b/lib/ext2fs/unix_io.c
index b6feebef93fa5b..15d6d55ff7fdd4 100644
--- a/lib/ext2fs/unix_io.c
+++ b/lib/ext2fs/unix_io.c
@@ -1213,10 +1213,16 @@ static errcode_t unix_close(io_channel channel)
#ifndef NO_IO_CACHE
retval = flush_cached_blocks(channel, data, 0);
#endif
+#ifdef HAVE_FSYNC
+ /* always fsync the device, even if flushing our own cache failed */
+ if (fsync(data->dev) != 0 && !retval)
+ retval = errno;
+#endif
unix_funlock(channel);
- if (channel->manager != unixfd_io_manager && close(data->dev) < 0)
+ if (channel->manager != unixfd_io_manager && close(data->dev) < 0 &&
+ !retval)
retval = errno;
free_cache(data);
free(data->cache);
^ permalink raw reply related
* [PATCH 1/3] libext2fs: always fsync the device when flushing the cache
From: Darrick J. Wong @ 2026-04-22 23:23 UTC (permalink / raw)
To: tytso; +Cc: linux-ext4
In-Reply-To: <177689989303.3821152.12873703999139555043.stgit@frogsfrogsfrogs>
From: Darrick J. Wong <djwong@kernel.org>
When we're flushing the unix IO manager's buffer cache, we should always
fsync the block device, because something could have written to the
block device -- either the buffer cache itself, or a direct write.
Regardless, the callers all want all dirtied regions to be persisted to
stable media.
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
lib/ext2fs/unix_io.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/lib/ext2fs/unix_io.c b/lib/ext2fs/unix_io.c
index abd33ba839f7e9..b6feebef93fa5b 100644
--- a/lib/ext2fs/unix_io.c
+++ b/lib/ext2fs/unix_io.c
@@ -1531,7 +1531,8 @@ static errcode_t unix_flush(io_channel channel)
retval = flush_cached_blocks(channel, data, 0);
#endif
#ifdef HAVE_FSYNC
- if (!retval && fsync(data->dev) != 0)
+ /* always fsync the device, even if flushing our own cache failed */
+ if (fsync(data->dev) != 0 && !retval)
return errno;
#endif
return retval;
^ permalink raw reply related
* [PATCHSET v5 2/2] fuse4fs: run servers as a contained service
From: Darrick J. Wong @ 2026-04-22 23:19 UTC (permalink / raw)
To: tytso
Cc: linux-ext4, linux-fsdevel, fuse-devel, linux-ext4, neal,
joannelkoong, miklos, bernd
In-Reply-To: <20260422231518.GA7717@frogsfrogsfrogs>
Hi all,
This series packages the newly created fuse4fs server into a systemd
socket service. This service can be used by the "mount.service" helper
in libfuse to implement untrusted unprivileged mounts.
If you're going to start using this code, I strongly recommend pulling
from my git trees, which are linked below.
Comments and questions are, as always, welcome.
e2fsprogs git tree:
https://git.kernel.org/cgit/linux/kernel/git/djwong/e2fsprogs.git/log/?h=fuse4fs-service-container
---
Commits in this patchset:
* libext2fs: make it possible to extract the fd from an IO manager
* libext2fs: fix checking for valid fds in mmp.c
* unix_io: allow passing /dev/fd/XXX paths to the unixfd IO manager
* libext2fs: fix MMP code to work with unixfd IO manager
* libext2fs: bump libfuse API version to 3.19
* fuse4fs: hoist some code out of fuse4fs_main
* fuse4fs: enable safe service mode
* fuse4fs: set proc title when in fuse service mode
* fuse4fs: make MMP work correctly in safe service mode
* debian: update packaging for fuse4fs service
---
lib/ext2fs/ext2_io.h | 4
lib/ext2fs/ext2fs.h | 1
lib/ext2fs/ext2fsP.h | 4
MCONFIG.in | 2
configure | 303 +++++++++++++++++++++++++++
configure.ac | 131 +++++++++++-
debian/e2fsprogs.install | 7 +
debian/fuse4fs.install | 3
debian/libext2fs2t64.symbols | 1
debian/rules | 3
fuse4fs/Makefile.in | 42 +++-
fuse4fs/fuse4fs.c | 466 +++++++++++++++++++++++++++++++++++-------
fuse4fs/fuse4fs.socket.in | 17 ++
fuse4fs/fuse4fs@.service.in | 102 +++++++++
lib/config.h.in | 12 +
lib/ext2fs/io_manager.c | 8 +
lib/ext2fs/mmp.c | 101 +++++++++
lib/ext2fs/openfs.c | 1
lib/ext2fs/unix_io.c | 50 ++++-
util/subst.conf.in | 3
20 files changed, 1164 insertions(+), 97 deletions(-)
mode change 100644 => 100755 debian/fuse4fs.install
create mode 100644 fuse4fs/fuse4fs.socket.in
create mode 100644 fuse4fs/fuse4fs@.service.in
^ permalink raw reply
* [PATCHSET 1/2] libext2fs: fix some missed fsync calls
From: Darrick J. Wong @ 2026-04-22 23:19 UTC (permalink / raw)
To: tytso; +Cc: linux-ext4
In-Reply-To: <20260422231518.GA7717@frogsfrogsfrogs>
Hi all,
Fix a few places (like device closing) where we really ought to tell the
block device to flush whatever's dirty to disk, even if we've failed to
flush all our cached buffers out to disk.
If you're going to start using this code, I strongly recommend pulling
from my git trees, which are linked below.
Comments and questions are, as always, welcome.
e2fsprogs git tree:
https://git.kernel.org/cgit/linux/kernel/git/djwong/e2fsprogs.git/log/?h=libext2fs-flushing-fixes
---
Commits in this patchset:
* libext2fs: always fsync the device when flushing the cache
* libext2fs: always fsync the device when closing the unix IO manager
* libext2fs: only fsync the unix fd if we wrote to the device
---
lib/ext2fs/unix_io.c | 83 ++++++++++++++++++++++++++++++++++++++++++--------
1 file changed, 69 insertions(+), 14 deletions(-)
^ permalink raw reply
* [PATCHBOMB v5] fuse/libfuse/e2fsprogs/etc: containerize ext4 for safer operation
From: Darrick J. Wong @ 2026-04-22 23:15 UTC (permalink / raw)
To: linux-fsdevel, linux-ext4, fuse-devel
Cc: Miklos Szeredi, Bernd Schubert, Joanne Koong, Theodore Ts'o,
Neal Gompa, Amir Goldstein, Christian Brauner, demiobenour
Hi everyone,
This *would have been* the eight public draft of the gigantic patchset
to connect the Linux fuse driver to fs-iomap for regular file IO
operations to and from files whose contents persist to locally attached
storage devices.
However, the previous submission was too large, and I didn't even send
half the patches! I have therefore split the work into two sections.
This first section covers setting up fuse servers to run as contained
systemd services; I previously sent only the libfuse changes, without
any of the surrounding pieces. Now I'm ready to send them all.
To summarize this patchbomb: fuse servers can now run as non-root users,
with no privilege, no access to the network or hardware, etc. The only
connection to the outside is an ephemeral AF_UNIX socket. The process
on the other end is a helper program that acquires resources and calls
fsmount().
Why would you want to do that? Most filesystem drivers are seriously
vulnerable to metadata parsing attacks, as syzbot has shown repeatedly
over almost a decade of its existence. Faulty code can lead to total
kernel compromise, and I think there's a very strong incentive to move
all that parsing out to userspace where we can containerize the fuse
server process. Runtime filesystem metadata parsing is no longer a
privileged (== risky) operation.
The consequences of a crashed driver is a dead mount, instead of a
crashed or corrupt OS kernel.
Note that contained fuse filesystem servers are no faster than regular
fuse. The redesign of the fuse IO path via iomap will be the subject of
the second patchbomb. The containerization code only requires changes
to libfuse and is ready to go today.
Since the seventh submission, I have made the following changes:
1) Added a couple of simple fuse service drivers to the example code
2) Adapted fuservicemount to be runnable as a setuid program so that
unprivileged users can start up a containerized filesystem driver
3) Fixed some endianness handling errors in the socket protocol between
the new mount helper and the fuse server
4) Added a high level fuse_main function so that fuse servers that use
the high level api can containerize without a total rewrite
5) Adapted mount.fuse to call the new mount helper code so that mount -t
fuse.XXX can try to start up a contained server
6) Cleaned up a lot of cppcheck complaints and refactored a bunch of
repetitious code
7) Started using codex to try to find bugs and security problems with
the new mount helper
There are a few unanswered questions:
a. How to integrate with the SYNC_INIT patches that Bernd is working on
merging into libfuse
b. If /any/ of the new fsopen/fsconfig/fsmount/move_mount calls fail,
do we fall back to the old mount syscall? Even after printing errors?
c. Are there any Linux systems where some inetd implementation can
actually handle AF_UNIX sockets? Does it make sense to try to do the
service isolation without the convenience of systemd directives?
d. meson/autoconf/cmake are a pain to deal with, hopefully the changes I
made are correct
I have also converted a handful more fuse servers (fat, exfat, iso,
http) to the new service architecture so that I can run a (virtual)
Debian system with EFI completely off of containerized fuse servers.
These will be sent at the end.
libfuse:
https://git.kernel.org/pub/scm/linux/kernel/git/djwong/libfuse.git/log/?h=fuse-service-container_2026-04-22
e2fsprogs:
https://git.kernel.org/pub/scm/linux/kernel/git/djwong/e2fsprogs.git/log/?h=fuse4fs-service-container_2026-04-22
fstests:
https://git.kernel.org/pub/scm/linux/kernel/git/djwong/xfstests-dev.git/log/?h=fuse2fs_2026-04-22
--Darrick
Unreviewed patches in this patchbomb:
[PATCHSET v5] libfuse: run fuse servers as a contained service
[PATCH 02/13] mount_service: add systemd socket service mounting
[PATCH 03/13] mount_service: create high level fuse helpers
[PATCH 04/13] mount_service: use the new mount api for the mount
[PATCH 05/13] mount_service: update mtab after a successful mount
[PATCH 06/13] util: hoist the fuse.conf parsing and setuid mode
[PATCH 07/13] util: fix checkpatch complaints in fuser_conf.[ch]
[PATCH 08/13] mount_service: enable unprivileged users in a similar
[PATCH 09/13] mount.fuse3: integrate systemd service startup
[PATCH 10/13] mount_service: allow installation as a setuid program
[PATCH 11/13] example/service_ll: create a sample systemd service
[PATCH 12/13] example/service: create a sample systemd service for a
[PATCH 13/13] nullfs: support fuse systemd service mode
[PATCHSET 1/2] libext2fs: fix some missed fsync calls
[PATCH 1/3] libext2fs: always fsync the device when flushing the
[PATCH 2/3] libext2fs: always fsync the device when closing the unix
[PATCH 3/3] libext2fs: only fsync the unix fd if we wrote to the
[PATCHSET v5 2/2] fuse4fs: run servers as a contained service
[PATCH 01/10] libext2fs: make it possible to extract the fd from an
[PATCH 02/10] libext2fs: fix checking for valid fds in mmp.c
[PATCH 03/10] unix_io: allow passing /dev/fd/XXX paths to the unixfd
[PATCH 04/10] libext2fs: fix MMP code to work with unixfd IO manager
[PATCH 05/10] libext2fs: bump libfuse API version to 3.19
[PATCH 06/10] fuse4fs: hoist some code out of fuse4fs_main
[PATCH 07/10] fuse4fs: enable safe service mode
[PATCH 08/10] fuse4fs: set proc title when in fuse service mode
[PATCH 09/10] fuse4fs: make MMP work correctly in safe service mode
[PATCH 10/10] debian: update packaging for fuse4fs service
^ permalink raw reply
* Re: [PATCH v8 03/22] ovl: use core fsverity ensure info interface
From: Eric Biggers @ 2026-04-22 22:46 UTC (permalink / raw)
To: Andrey Albershteyn
Cc: Andrey Albershteyn, linux-xfs, fsverity, linux-fsdevel, hch,
linux-ext4, linux-f2fs-devel, linux-btrfs, linux-unionfs, djwong,
Amir Goldstein
In-Reply-To: <gpmgtg2wkoo4vozzaaouhdp2df6zlifwi6gy4jvq7xc22zo7om@t3f2bl374nlr>
On Wed, Apr 22, 2026 at 11:59:11AM +0200, Andrey Albershteyn wrote:
> > The 'if (!fsverity_active(inode) && IS_VERITY(inode)) {' condition
> > should stay
>
> Why? With recent changes, the fsverity_active() now checks for
> IS_VERITY() instead of verity_descriptor.
>
Okay, I forgot that that had changed. Looks like the kerneldoc never
got updated, though. It still says "This checks whether the inode's
verity info has been set."
- Eric
^ permalink raw reply
* [syzbot] [ext4?] WARNING in jbd2_journal_dirty_metadata (3)
From: syzbot @ 2026-04-22 22:44 UTC (permalink / raw)
To: jack, linux-ext4, linux-kernel, syzkaller-bugs, tytso
Hello,
syzbot found the following issue on:
HEAD commit: eb5249b12507 Merge tag 'parisc-for-7.1-rc1' of git://git.k..
git tree: upstream
console output: https://syzkaller.appspot.com/x/log.txt?x=16ed0836580000
kernel config: https://syzkaller.appspot.com/x/.config?x=d120b114be21f79
dashboard link: https://syzkaller.appspot.com/bug?extid=a5f824f1c49dd97fcff0
compiler: Debian clang version 21.1.8 (++20251221033036+2078da43e25a-1~exp1~20251221153213.50), Debian LLD 21.1.8
Unfortunately, I don't have any reproducer for this issue yet.
Downloadable assets:
disk image (non-bootable): https://storage.googleapis.com/syzbot-assets/d900f083ada3/non_bootable_disk-eb5249b1.raw.xz
vmlinux: https://storage.googleapis.com/syzbot-assets/cb4e868ca1f9/vmlinux-eb5249b1.xz
kernel image: https://storage.googleapis.com/syzbot-assets/fb064cd651ff/bzImage-eb5249b1.xz
IMPORTANT: if you fix the issue, please add the following tag to the commit:
Reported-by: syzbot+a5f824f1c49dd97fcff0@syzkaller.appspotmail.com
loop0: detected capacity change from 0 to 32768
JBD2: Ignoring recovery information on journal
ocfs2: Mounting device (7,0) on (node local, slot 0) with ordered data mode.
------------[ cut here ]------------
jbd2_handle_buffer_credits(handle) <= 0
WARNING: fs/jbd2/transaction.c:1593 at jbd2_journal_dirty_metadata+0x9c8/0xd30 fs/jbd2/transaction.c:1593, CPU#0: syz.0.0/5322
Modules linked in:
CPU: 0 UID: 0 PID: 5322 Comm: syz.0.0 Not tainted syzkaller #0 PREEMPT(full)
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
RIP: 0010:jbd2_journal_dirty_metadata+0x9c8/0xd30 fs/jbd2/transaction.c:1593
Code: 26 03 00 00 45 8b 06 48 c7 c7 40 07 e6 8b 89 de 89 ea 4c 89 f9 e8 18 25 8a fe b8 ea ff ff ff e9 11 fa ff ff e8 f9 42 28 ff 90 <0f> 0b 90 b8 e4 ff ff ff e9 fe f9 ff ff e8 e6 42 28 ff 90 0f 0b 90
RSP: 0018:ffffc9000f85e640 EFLAGS: 00010287
RAX: ffffffff829d9bb7 RBX: 0000000000000000 RCX: 0000000000100000
RDX: ffffc9000ef42000 RSI: 000000000009fb83 RDI: 000000000009fb84
RBP: 1ffff1100ac32d99 R08: 0000000000000003 R09: 0000000000000004
R10: dffffc0000000000 R11: fffff52001f0bcb8 R12: ffff8880483456f8
R13: ffff888048345690 R14: 1ffff11009068adc R15: 0000000000000000
FS: 00007f04675f56c0(0000) GS:ffff88808c81a000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 000055a18f887168 CR3: 000000001f4fd000 CR4: 0000000000352ef0
Call Trace:
<TASK>
ocfs2_journal_dirty+0x130/0x700 fs/ocfs2/journal.c:831
ocfs2_alloc_dinode_update_counts+0x16e/0x2d0 fs/ocfs2/suballoc.c:1746
ocfs2_search_chain+0xe39/0x1e10 fs/ocfs2/suballoc.c:2002
ocfs2_claim_suballoc_bits+0x901/0x1f40 fs/ocfs2/suballoc.c:2113
__ocfs2_claim_clusters+0x31d/0x970 fs/ocfs2/suballoc.c:2540
ocfs2_make_clusters_writable fs/ocfs2/refcounttree.c:3243 [inline]
ocfs2_replace_cow+0x984/0x1c90 fs/ocfs2/refcounttree.c:3346
ocfs2_refcount_cow_hunk fs/ocfs2/refcounttree.c:3424 [inline]
ocfs2_refcount_cow+0x790/0xd40 fs/ocfs2/refcounttree.c:3467
ocfs2_prepare_inode_for_write fs/ocfs2/file.c:2347 [inline]
ocfs2_file_write_iter+0xee2/0x1e70 fs/ocfs2/file.c:2458
iter_file_splice_write+0x9a1/0x10f0 fs/splice.c:736
do_splice_from fs/splice.c:936 [inline]
direct_splice_actor+0x101/0x160 fs/splice.c:1159
splice_direct_to_actor+0x53a/0xc70 fs/splice.c:1103
do_splice_direct_actor fs/splice.c:1202 [inline]
do_splice_direct+0x195/0x290 fs/splice.c:1228
do_sendfile+0x535/0x7d0 fs/read_write.c:1372
__do_sys_sendfile64 fs/read_write.c:1433 [inline]
__se_sys_sendfile64+0x144/0x1a0 fs/read_write.c:1419
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x15f/0xf80 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f046b19c819
Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007f04675f4fe8 EFLAGS: 00000246 ORIG_RAX: 0000000000000028
RAX: ffffffffffffffda RBX: 00007f046b415fa0 RCX: 00007f046b19c819
RDX: 0000000000000000 RSI: 000000000000000b RDI: 000000000000000a
RBP: 00007f046b232c91 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000020fffe82 R11: 0000000000000246 R12: 0000000000000000
R13: 00007f046b416038 R14: 00007f046b415fa0 R15: 00007ffd208882e8
</TASK>
---
This report is generated by a bot. It may contain errors.
See https://goo.gl/tpsmEJ for more information about syzbot.
syzbot engineers can be reached at syzkaller@googlegroups.com.
syzbot will keep track of this issue. See:
https://goo.gl/tpsmEJ#status for how to communicate with syzbot.
If the report is already addressed, let syzbot know by replying with:
#syz fix: exact-commit-title
If you want to overwrite report's subsystems, reply with:
#syz set subsystems: new-subsystem
(See the list of subsystem names on the web dashboard)
If the report is a duplicate of another one, reply with:
#syz dup: exact-subject-of-another-report
If you want to undo deduplication, reply with:
#syz undup
^ permalink raw reply
* Re: [PATCH] generic/790: test post-EOF gap zeroing persistence
From: Brian Foster @ 2026-04-22 13:22 UTC (permalink / raw)
To: Zhang Yi
Cc: fstests, zlang, linux-ext4, linux-fsdevel, jack, yi.zhang,
yizhang089, yangerkun
In-Reply-To: <20260422015246.4132376-1-yi.zhang@huaweicloud.com>
On Wed, Apr 22, 2026 at 09:52:46AM +0800, Zhang Yi wrote:
> From: Zhang Yi <yi.zhang@huawei.com>
>
> Test that extending a file past a non-block-aligned EOF correctly
> zero-fills the gap [old_EOF, block_boundary), and that this zeroing
> persists through a filesystem shutdown+remount cycle.
>
> Stale data beyond EOF can persist on disk when append write data blocks
> are flushed before the i_size metadata update, or when concurrent append
> writeback and mmap writes persist non-zero data past EOF. Subsequent
> post-EOF operations (append write, fallocate, truncate up) must
> zero-fill and persist the gap to prevent exposing stale data.
>
> The test pollutes the file's last physical block (via FIEMAP + raw
> device write) with a sentinel pattern beyond i_size, then performs each
> extend operation and verifies the gap is zeroed both in memory and on
> disk.
>
> Signed-off-by: Zhang Yi <yi.zhang@huawei.com>
> ---
> This is the case Jan Kara pointed out during my work on the ext4
> buffered I/O to iomap conversion. This case is similar to generic/363,
> but generic/363 doesn't provide persistent testing. For details:
>
> https://lore.kernel.org/linux-ext4/jgotl7vzzuzm6dvz5zfgk6haodxvunb4hq556pzh4hqqwvnhxq@lr3jiedhqh7c/
>
> tests/generic/790 | 155 ++++++++++++++++++++++++++++++++++++++++++
> tests/generic/790.out | 4 ++
> 2 files changed, 159 insertions(+)
> create mode 100755 tests/generic/790
> create mode 100644 tests/generic/790.out
>
> diff --git a/tests/generic/790 b/tests/generic/790
> new file mode 100755
> index 00000000..5d8f61f9
> --- /dev/null
> +++ b/tests/generic/790
> @@ -0,0 +1,155 @@
> +#! /bin/bash
> +# SPDX-License-Identifier: GPL-2.0
> +# Copyright (c) 2026 Huawei. All Rights Reserved.
> +#
> +# FS QA Test No. 790
> +#
> +# Test that extending a file past a non-block-aligned EOF correctly zero-fills
> +# the gap [old_EOF, block_boundary), and that this zeroing persists through a
> +# filesystem shutdown+remount cycle.
> +#
Nice test! This is a great idea.
> +# Stale data beyond EOF can persist on disk when:
> +# 1) append write data blocks are flushed before the i_size metadata update,
> +# and the system crashes in this window.
Maybe it's wording or I'm missing something, but how would "append write
data blocks" be flushed before i_size updates? Wouldn't writeback toss
them or zero the post-eof range of a folio? Do you mean to refer to
"on-disk size update" specifically (where I'm reading it as
inode->i_isize)?
> +# 2) concurrent append writeback and mmap writes persist non-zero data past EOF.
> +#
> +# Subsequent post-EOF operations (append write, fallocate, truncate up) must
> +# zero-fill and persist the gap to prevent exposing stale data.
> +#
> +# The test pollutes the file's last physical block (via FIEMAP + raw device
> +# write) with a sentinel pattern beyond i_size, then performs each extend
> +# operation and verifies the gap is zeroed both in memory and on disk.
> +#
...
> +_test_eof_zeroing()
> +{
> + local test_name="$1"
> + local extend_cmd="$2"
> + local file=$SCRATCH_MNT/testfile_${test_name}
> +
> + echo "$test_name" | tee -a $seqres.full
> +
> + # Compute non-block-aligned EOF offset
> + local gap_bytes=16
> + local eof_offset=$((blksz - gap_bytes))
> +
> + # Step 1: Write one full block to ensure the filesystem allocates a
> + # physical block for the file instead of using inline data.
> + $XFS_IO_PROG -f -c "pwrite -S 0x5a 0 $blksz" -c fsync \
> + "$file" >> $seqres.full 2>&1
> +
> + # Step 2: Get physical block offset on device via FIEMAP
> + local phys_offset
> + phys_offset=$(_get_phys_offset "$file")
> + if [ -z "$phys_offset" ]; then
> + _fail "$test_name: failed to get physical block offset via fiemap"
> + fi
> +
> + # Step 3: Truncate file to non-block-aligned size and fsync.
> + # The on-disk region [eof_offset, blksz) may or may not be
> + # zeroed by the filesystem at this point.
> + $XFS_IO_PROG -c "truncate $eof_offset" -c fsync \
> + "$file" >> $seqres.full 2>&1
> +
> + # Step 4: Unmount and restore the physical block to all-0x5a on disk.
> + # This bypasses the kernel's pagecache EOF-zeroing to ensure
> + # the stale pattern is present on disk. Then remount.
> + _scratch_unmount
> + $XFS_IO_PROG -d -c "pwrite -S 0x5a $phys_offset $blksz" \
> + $SCRATCH_DEV >> $seqres.full 2>&1
> + _scratch_mount >> $seqres.full 2>&1
> +
> + # Verify file size is still eof_offset after remount
> + local sz
> + sz=$(stat -c %s "$file")
> + if [ "$sz" -ne "$eof_offset" ]; then
> + _fail "$test_name: file size wrong after remount: $sz != $eof_offset"
> + fi
I was initially curious why we'd want to do this, but after further
thought I wonder if it might make more sense to check file size against
the extended size after the shutdown/mount cycle below (but before
checking the gap range). That way we know the size update was
logged/recovered correctly and we're about to read from a file range
within eof. Hm?
Those couple nits aside this all looks pretty good to me.
Brian
> +
> + # Step 5: Execute the extend operation.
> + $XFS_IO_PROG -c "$extend_cmd" "$file" >> $seqres.full 2>&1
> +
> + # Step 6: Verify gap [eof_offset, blksz) is zeroed BEFORE shutdown
> + _check_gap_zero "$file" $eof_offset $gap_bytes "before shutdown" || return 1
> +
> + # Step 7: Sync the extended range and shutdown the filesystem with
> + # journal flush. This persists the file size extending, and
> + # the filesystem should persist the zeroed data in the gap
> + # range as well.
> + if [ "$extend_cmd" != "${extend_cmd#pwrite}" ]; then
> + $XFS_IO_PROG -c "sync_range -w $blksz $blksz" \
> + "$file" >> $seqres.full 2>&1
> + fi
> + _scratch_shutdown -f
> +
> + # Step 8: Remount and verify gap is still zeroed
> + _scratch_cycle_mount
> + _check_gap_zero "$file" $eof_offset $gap_bytes "after shutdown+remount" || return 1
> +}
> +
> +_scratch_mkfs >> $seqres.full 2>&1
> +_scratch_mount
> +
> +blksz=$(_get_block_size $SCRATCH_MNT)
> +
> +# Test three variants of EOF-extending operations
> +_test_eof_zeroing "append_write" "pwrite -S 0x42 $blksz $blksz"
> +_test_eof_zeroing "truncate_up" "truncate $((blksz * 2))"
> +_test_eof_zeroing "fallocate" "falloc $blksz $blksz"
> +
> +# success, all done
> +status=0
> +exit
> diff --git a/tests/generic/790.out b/tests/generic/790.out
> new file mode 100644
> index 00000000..e5e2cc09
> --- /dev/null
> +++ b/tests/generic/790.out
> @@ -0,0 +1,4 @@
> +QA output created by 790
> +append_write
> +truncate_up
> +fallocate
> --
> 2.52.0
>
>
^ permalink raw reply
* Re: [PATCH v2] iomap: avoid memset iomap when iter is done
From: Christian Brauner @ 2026-04-22 12:56 UTC (permalink / raw)
To: djwong, hch, linux-xfs, linux-fsdevel, linux-ext4, Fengnan Chang
Cc: Christian Brauner, lidiangang, Fengnan Chang
In-Reply-To: <20260420061630.62077-1-changfengnan@bytedance.com>
On Mon, 20 Apr 2026 14:16:30 +0800, Fengnan Chang wrote:
> When iomap_iter() finishes its iteration (returns <= 0), it is no longer
> necessary to memset the entire iomap and srcmap structures.
>
> In high-IOPS scenarios (like 4k randread NVMe polling with io_uring),
> where the majority of I/Os complete in a single extent map, this wasted
> memory write bandwidth, as the caller will just discard the iterator.
> Use this command to test:
> taskset -c 30 ./t/io_uring -p1 -d512 -b4096 -s32 -c32 -F1 -B1 -R1 -X1
> -n1 -P1 /mnt/testfile
> IOPS improve about 5% on ext4 and XFS.
>
> [...]
Applied to the vfs-7.2.iomap branch of the vfs/vfs.git tree.
Patches in the vfs-7.2.iomap branch should appear in linux-next soon.
Please report any outstanding bugs that were missed during review in a
new review to the original patch series allowing us to drop it.
It's encouraged to provide Acked-bys and Reviewed-bys even though the
patch has now been applied. If possible patch trailers will be updated.
Note that commit hashes shown below are subject to change due to rebase,
trailer updates or similar. If in doubt, please check the listed branch.
tree: https://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs.git
branch: master
[1/1] iomap: avoid memset iomap when iter is done
https://git.kernel.org/vfs/vfs/c/72fa5c7e5c81
^ permalink raw reply
* Re: [PATCH v2 3/3] ext4: derive f_fsid from block device to avoid collisions
From: Anand Jain @ 2026-04-22 11:39 UTC (permalink / raw)
To: Christoph Hellwig
Cc: Theodore Tso, Darrick J. Wong, linux-ext4, linux-btrfs, linux-xfs,
Anand Jain, dsterba
In-Reply-To: <aeHikJwRmFdDg5FW@infradead.org>
>> It seems we've reached the functional limits of f_fsid.
>> If we want to solve this properly for Overlayfs, NFS handles, or a
>> complex system monitoring..etc, we need a new identifier let's call
>> it f_fsid_v2, that meets the following requirements:
>>
>> System-wide Uniqueness: Must distinguish between cloned filesystems.
>>
>> Persistence: Must remain consistent across reboots/HW re-enumeration.
My mistake- this should be:
Consistency: Must remain consistent across reboots/HW re-enumeration.
>>
>> Non-On-Disk: Must not be stored on-disk.
>
> The third requirement doesn't make much sense to me. If it is
> persistent it. or something it can be derived from must be stored
> on-disk.
I hope it make sense now with "Persistence" replaced by
"Consistency" for the 2nd requirement (above).
>> One possible implementation for f_fsid_v2 could be:
>>
>> f_fsid_v2 = hash(s_uuid, block_device_serial, [subvol_id])
>>
>> For pseudo block devices (virtio-blk, loop, nbd, brd,..),
>> the serial could be derived recursively:
>>
>> serial_number = hash(backing_file.f_fsid_v2, backing_file.ino)
>
> What i the point in this? All of this seems to be better served
> by s_uuid.
The goal is to fix duplicate f_fsid issues in cloned filesystems
by creating a unique, reboot-consistent ID. This allows the
source and clone to remain identical (sharing same uuid) while
still being individually identifiable.
>> Note on Hardware Serials:
>> Standard storage protocols (T10, NVMe, SAS) mandate unique,
>> persistent serials per LUN. While I've seen T10 protocol
>> violations during my time authoring Solaris HBA drivers, I
>> believe these outliers shouldn't dictate the design.
>
> No, T10 does not actually mandate unique identifiers, NVMe does, but the
> implementations are often totally broken.
Right. Newer SPC-3 (and above) compliant devices must support
the Inquiry CDB EVPD flag and provide page 0x83 for identification,
which is what we typically use for multipathing.
These are globally unique. And, we can overlook legacy
drives, as they've probably been past their EOSL for a while now.
We can tweak the algorithm as follows:
ID = hash(s_uuid, device_identifier_id,
partition_start_lba, partition_end_lba,
[subvol_id], [file.ino])
This is an ID which remains consistent across reboots while
staying unique within the system, which we can use it for f_fsid.
Thanks.
^ permalink raw reply
* Re: [PATCH v2] iomap: avoid memset iomap when iter is done
From: Brian Foster @ 2026-04-22 10:59 UTC (permalink / raw)
To: Fengnan Chang
Cc: brauner, djwong, hch, linux-xfs, linux-fsdevel, linux-ext4,
lidiangang, Fengnan Chang
In-Reply-To: <20260420061630.62077-1-changfengnan@bytedance.com>
On Mon, Apr 20, 2026 at 02:16:30PM +0800, Fengnan Chang wrote:
> When iomap_iter() finishes its iteration (returns <= 0), it is no longer
> necessary to memset the entire iomap and srcmap structures.
>
> In high-IOPS scenarios (like 4k randread NVMe polling with io_uring),
> where the majority of I/Os complete in a single extent map, this wasted
> memory write bandwidth, as the caller will just discard the iterator.
> Use this command to test:
> taskset -c 30 ./t/io_uring -p1 -d512 -b4096 -s32 -c32 -F1 -B1 -R1 -X1
> -n1 -P1 /mnt/testfile
> IOPS improve about 5% on ext4 and XFS.
>
> However, we MUST still call iomap_iter_reset_iomap() to release the
> folio_batch if IOMAP_F_FOLIO_BATCH is set, otherwise we leak page
> references. Therefore, split the cleanup logic: always release the
> folio_batch, but skip the memset() when ret <= 0.
>
> Signed-off-by: Fengnan Chang <changfengnan@bytedance.com>
> ---
Reviewed-by: Brian Foster <bfoster@redhat.com>
> fs/iomap/iter.c | 12 ++++++------
> 1 file changed, 6 insertions(+), 6 deletions(-)
>
> diff --git a/fs/iomap/iter.c b/fs/iomap/iter.c
> index c04796f6e57f..e4a29829591a 100644
> --- a/fs/iomap/iter.c
> +++ b/fs/iomap/iter.c
> @@ -6,17 +6,13 @@
> #include <linux/iomap.h>
> #include "trace.h"
>
> -static inline void iomap_iter_reset_iomap(struct iomap_iter *iter)
> +static inline void iomap_iter_clean_fbatch(struct iomap_iter *iter)
> {
> if (iter->iomap.flags & IOMAP_F_FOLIO_BATCH) {
> folio_batch_release(iter->fbatch);
> folio_batch_reinit(iter->fbatch);
> iter->iomap.flags &= ~IOMAP_F_FOLIO_BATCH;
> }
> -
> - iter->status = 0;
> - memset(&iter->iomap, 0, sizeof(iter->iomap));
> - memset(&iter->srcmap, 0, sizeof(iter->srcmap));
> }
>
> /* Advance the current iterator position and decrement the remaining length */
> @@ -102,10 +98,14 @@ int iomap_iter(struct iomap_iter *iter, const struct iomap_ops *ops)
> ret = 0;
> else
> ret = 1;
> - iomap_iter_reset_iomap(iter);
> + iomap_iter_clean_fbatch(iter);
> + iter->status = 0;
> if (ret <= 0)
> return ret;
>
> + memset(&iter->iomap, 0, sizeof(iter->iomap));
> + memset(&iter->srcmap, 0, sizeof(iter->srcmap));
> +
> begin:
> ret = ops->iomap_begin(iter->inode, iter->pos, iter->len, iter->flags,
> &iter->iomap, &iter->srcmap);
> --
> 2.39.5 (Apple Git-154)
>
>
^ permalink raw reply
* Re: [PATCH v8 03/22] ovl: use core fsverity ensure info interface
From: Andrey Albershteyn @ 2026-04-22 9:59 UTC (permalink / raw)
To: Eric Biggers
Cc: Andrey Albershteyn, linux-xfs, fsverity, linux-fsdevel, hch,
linux-ext4, linux-f2fs-devel, linux-btrfs, linux-unionfs, djwong,
Amir Goldstein
In-Reply-To: <20260421214457.GC37143@quark>
On 2026-04-21 14:44:57, Eric Biggers wrote:
> On Mon, Apr 20, 2026 at 01:46:50PM +0200, Andrey Albershteyn wrote:
> > int ovl_ensure_verity_loaded(const struct path *datapath)
> > {
> > struct inode *inode = d_inode(datapath->dentry);
> > - struct file *filp;
> >
> > - if (!fsverity_active(inode) && IS_VERITY(inode)) {
> > - /*
> > - * If this inode was not yet opened, the verity info hasn't been
> > - * loaded yet, so we need to do that here to force it into memory.
> > - */
> > - filp = kernel_file_open(datapath, O_RDONLY, current_cred());
> > - if (IS_ERR(filp))
> > - return PTR_ERR(filp);
> > - fput(filp);
> > - }
> > + if (fsverity_active(inode))
> > + return fsverity_ensure_verity_info(inode);
>
> Not sure whether I should review this version or the version in git, but
> both seem wrong.
Sorry, I forgot to push, this one is the latest, the one on git is
v7. I will push v8 now.
> The 'if (!fsverity_active(inode) && IS_VERITY(inode)) {' condition
> should stay
Why? With recent changes, the fsverity_active() now checks for
IS_VERITY() instead of verity_descriptor.
> , but fsverity_ensure_verity_info() will need to
> gain a !CONFIG_FS_VERITY stub to fix the build error.
With "if (fsverity_active(inode))" I think this is not necessary as
this fsverity_active() will be always false, and this if-case is
optimized.
--
- Andrey
^ permalink raw reply
* Re: [PATCH v3 v3 0/2] add blocks_allocated to mb_stats and clear mb_stats
From: liubaolin @ 2026-04-22 9:55 UTC (permalink / raw)
To: tytso, adilger.kernel, ojaswin, ritesh.list, yi.zhang
Cc: linux-ext4, linux-kernel, wangguanyu
In-Reply-To: <20260422015026.7170-1-liubaolin12138@163.com>
在 2026/4/22 9:50, Baolin Liu 写道:
> The series contains two patches:
> - add blocks_allocated to /proc/fs/ext4/<dev>/mb_stats
> - allow writing 0 to /proc/fs/ext4/<dev>/mb_stats to clear the current
> mballoc statistics
>
> Changes since v2:
> - Add mb_stats documentation to patch 2
> - Add Reviewed-by tags
>
> Baolin Liu (2):
> ext4: add blocks_allocated to mb_stats output
> ext4: allow clearing mballoc stats through mb_stats
>
> Documentation/admin-guide/ext4.rst | 5 ++++
> Documentation/filesystems/proc.rst | 3 +++
> fs/ext4/ext4.h | 1 +
> fs/ext4/mballoc.c | 31 +++++++++++++++++++++++
> fs/ext4/sysfs.c | 40 ++++++++++++++++++++++++++++--
> 5 files changed, 78 insertions(+), 2 deletions(-)
>
Dear All,
This commit adds the description of the ext4 proc parameter mb_stats
to the corresponding documentation.
I noticed that the documentation also lacks descriptions for the
es_shrinker_info, fc_info, mb_structs_summary, and options parameters.
However, these parameters are irrelevant to the work done in this
series of patches.
I will commit a separate patch later to explain the other parameters.
This time, I only explain mb_stats.
Thanks,
Baolin
^ permalink raw reply
* Re: [PATCH v8 00/22] fs-verity support for XFS with post EOF merkle tree
From: Andrey Albershteyn @ 2026-04-22 8:58 UTC (permalink / raw)
To: Eric Biggers
Cc: Andrey Albershteyn, linux-xfs, fsverity, linux-fsdevel, hch,
linux-ext4, linux-f2fs-devel, linux-btrfs, linux-unionfs, djwong,
david
In-Reply-To: <20260421214316.GB37143@quark>
On 2026-04-21 14:43:16, Eric Biggers wrote:
> On Mon, Apr 20, 2026 at 01:46:47PM +0200, Andrey Albershteyn wrote:
> > This series based on v7.0 with Christoph's read ioends patchset [1].
> >
> > kernel:
> > https://git.kernel.org/pub/scm/linux/kernel/git/aalbersh/xfs-linux.git/log/?h=b4/fsverity
>
> FYI: the git repository doesn't match what was actually sent out. For
> example the patch "ovl: use core fsverity ensure info interface" is a
> bit different. The version in git (incorrectly, I think) ignores the
> error code, while the patch returns it.
>
> - Eric
>
Thanks, you're right, I forgot to push
--
- Andrey
^ permalink raw reply
* Re: [PATCH v2] iomap: avoid memset iomap when iter is done
From: Christoph Hellwig @ 2026-04-22 6:34 UTC (permalink / raw)
To: Fengnan Chang
Cc: brauner, djwong, hch, linux-xfs, linux-fsdevel, linux-ext4,
lidiangang, Fengnan Chang
In-Reply-To: <20260420061630.62077-1-changfengnan@bytedance.com>
Looks good:
Reviewed-by: Christoph Hellwig <hch@lst.de>
^ permalink raw reply
* Re: [RFC PATCH] iomap: add fast read path for small direct I/O
From: Fengnan @ 2026-04-22 2:43 UTC (permalink / raw)
To: Dave Chinner
Cc: Ojaswin Mujoo, Fengnan Chang, brauner, djwong, linux-xfs,
linux-fsdevel, linux-ext4, lidiangang
In-Reply-To: <aef79R24wFemmUdN@dread>
[-- Attachment #1: Type: text/plain, Size: 1163 bytes --]
在 2026/4/22 06:36, Dave Chinner 写道:
> On Tue, Apr 21, 2026 at 11:19:31AM +0800, Fengnan wrote:
>> 在 2026/4/21 07:59, Dave Chinner 写道:
>>> I'm clearly missing something here. I'm trying to work out why the
>>> profiles show what they do, but there's differences between them
>>> that do make obvious sense to me.
>>>
>>> It would also be useful to have XFS profiles, because it has a
>>> larger CPU cache footprint than ext4. If what the profiles are
>>> showing is a result of CPU cache residency artifacts, then we'll see
>>> different profile (and, potentially, performance) artifacts with
>>> XFS...
>> The XFS flame graph is also attached now.
>> IOPS: 1.92M->2.3M.
> The callchains in both XFS flame graphs are completely bogus:
>
> <io_uring entry>
> ....
> io_read
> __io_read
> xfs_inode_free_eofblocks
> xfs_prep_free_cowblocks
> iomap_dio_rw
> iomap_dio_simple_read
> xfs_mountfs
> ....
>
> Can you regenerate the profiles, please, and this time check that
> they make sense before posting them?
Sorry, I didn't check xfs before, new flame graph is attached
now, now the callchains make sense.
>
> -Dave.
[-- Attachment #2: xfs_base.svg --]
[-- Type: image/svg+xml, Size: 220396 bytes --]
[-- Attachment #3: xfs_patch.svg --]
[-- Type: image/svg+xml, Size: 201551 bytes --]
^ permalink raw reply
* [PATCH v3 21/22] ext4: update i_disksize to i_size on ordered I/O completion
From: Zhang Yi @ 2026-04-22 2:10 UTC (permalink / raw)
To: linux-ext4, linux-fsdevel
Cc: linux-kernel, tytso, adilger.kernel, libaokun, jack, ojaswin,
ritesh.list, djwong, hch, yi.zhang, yi.zhang, yizhang089,
yangerkun, yukuai
In-Reply-To: <20260422021042.4157510-1-yi.zhang@huaweicloud.com>
From: Zhang Yi <yi.zhang@huawei.com>
Currently, i_disksize is updated after ordered data writeback to prevent
exposing stale data in the post-EOF block. However, operations like
fallocate and truncate update i_disksize directly. If the new i_disksize
exceeds the original value, metadata may be written back before the
zeroed data is persisted. To avoid this, we defer i_disksize updates
when i_ordered_len is non-zero, only applying them after ordered I/O
completes.
But this deferral introduces a new problem: on ordered I/O completion,
i_disksize is updated only to the end of that specific I/O, discarding
any later updates (e.g., from fallocate) and causing filesystem
inconsistency. A potential fix would involve scanning for dirty or
writeback folios beyond the current position, then updating i_disksize
to the start of the first such folio or to i_size. However, folio
scanning is expensive and concurrency with operations like fallocate
makes this approach prohibitively complex.
Instead, update i_disksize directly to i_size upon ordered I/O
completion. This may expose zeroed data if dirty data within the range
is not yet written to disk after crash recovery, but it will never
expose stale data. The is limited to unaligned append writes and is
deemed acceptable.
Suggested-by: Jan Kara <jack@suse.cz>
Signed-off-by: Zhang Yi <yi.zhang@huawei.com>
---
fs/ext4/ext4.h | 40 +++++++++++++++++++++++++++++++---------
fs/ext4/extents.c | 9 +++------
fs/ext4/inode.c | 3 ---
fs/ext4/page-io.c | 23 ++++++++++++++++++-----
4 files changed, 52 insertions(+), 23 deletions(-)
diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index 760400395cb7..59dcec47675f 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -3495,13 +3495,21 @@ 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, a non-zero i_ordered_len indicates that
+ * an ordered I/O (zeroing the EOF partial block) is still in progress.
+ * In that case, i_disksize will be updated after the ordered 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 &&
+ READ_ONCE(EXT4_I(inode)->i_ordered_len) == 0)
WRITE_ONCE(EXT4_I(inode)->i_disksize, newsize);
up_write(&EXT4_I(inode)->i_data_sem);
}
@@ -3515,7 +3523,8 @@ static inline int ext4_update_inode_size(struct inode *inode, loff_t newsize)
i_size_write(inode, newsize);
changed = 1;
}
- if (newsize > EXT4_I(inode)->i_disksize) {
+ if (newsize > EXT4_I(inode)->i_disksize &&
+ READ_ONCE(EXT4_I(inode)->i_ordered_len) == 0) {
ext4_update_i_disksize(inode, newsize);
changed |= 2;
}
@@ -3523,19 +3532,32 @@ static inline int ext4_update_inode_size(struct inode *inode, loff_t newsize)
}
/*
- * Set i_size and i_disksize to 'newsize'.
+ * Set i_size and i_disksize to 'newsize'. In the iomap buffered I/O path,
+ * if i_ordered_len is non-zero and newsize exceeds the current i_disksize,
+ * the actual i_disksize update is deferred until after the ordered data is
+ * written out. In that case, i_disksize will be set to i_size upon I/O
+ * completion.
*
* Both i_rwsem and i_data_sem are required here to avoid races between
- * generic append writeback and concurrent truncate that also modify
- * i_size and i_disksize.
+ * generic append writeback (or ordered I/O writeback) and concurrent
+ * operations like fallocate and truncate that also modify i_size and
+ * i_disksize.
*/
-static inline void ext4_set_inode_size(struct inode *inode, loff_t newsize)
+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));
+ WARN_ON_ONCE(!rwsem_is_locked(&EXT4_I(inode)->i_data_sem));
- down_write(&EXT4_I(inode)->i_data_sem);
i_size_write(inode, newsize);
- EXT4_I(inode)->i_disksize = newsize;
+ if (READ_ONCE(EXT4_I(inode)->i_ordered_len) == 0 ||
+ newsize < EXT4_I(inode)->i_disksize)
+ EXT4_I(inode)->i_disksize = newsize;
+}
+
+static inline void ext4_set_inode_size(struct inode *inode, loff_t newsize)
+{
+ down_write(&EXT4_I(inode)->i_data_sem);
+ __ext4_set_inode_size(inode, newsize);
up_write(&EXT4_I(inode)->i_data_sem);
}
diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
index 125f628e738a..e0c36cd920bf 100644
--- a/fs/ext4/extents.c
+++ b/fs/ext4/extents.c
@@ -5531,7 +5531,7 @@ static int ext4_collapse_range(struct file *file, loff_t offset, loff_t len)
ext4_lblk_t start_lblk, end_lblk;
handle_t *handle;
unsigned int credits;
- loff_t start, new_size;
+ loff_t start;
int ret;
trace_ext4_collapse_range(inode, offset, len);
@@ -5597,9 +5597,7 @@ static int ext4_collapse_range(struct file *file, loff_t offset, loff_t len)
goto out_handle;
}
- new_size = inode->i_size - len;
- i_size_write(inode, new_size);
- EXT4_I(inode)->i_disksize = new_size;
+ __ext4_set_inode_size(inode, inode->i_size - len);
up_write(&EXT4_I(inode)->i_data_sem);
ret = ext4_mark_inode_dirty(handle, inode);
@@ -5671,8 +5669,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_set_inode_size(inode, inode->i_size + len);
ret = ext4_mark_inode_dirty(handle, inode);
if (ret)
goto out_handle;
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index 17bd4403c782..d983336390c7 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -4805,9 +4805,6 @@ 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.
- *
- * TODO: In the iomap path, handle this by updating i_disksize to
- * i_size after the zeroed data has been written back.
*/
if (did_zero && zero_written && !IS_DAX(inode)) {
if (ext4_should_order_data(inode)) {
diff --git a/fs/ext4/page-io.c b/fs/ext4/page-io.c
index 9c88671836fe..589c74b9f8a3 100644
--- a/fs/ext4/page-io.c
+++ b/fs/ext4/page-io.c
@@ -647,13 +647,13 @@ static void ext4_iomap_wb_ordered_wait(struct inode *inode,
}
static int ext4_iomap_wb_update_disksize(handle_t *handle, struct inode *inode,
- loff_t end)
+ loff_t end, bool is_ordered)
{
- loff_t new_disksize = end;
+ loff_t new_disksize, i_size;
struct ext4_inode_info *ei = EXT4_I(inode);
int ret;
- if (new_disksize <= READ_ONCE(ei->i_disksize))
+ if (end <= READ_ONCE(ei->i_disksize) && !is_ordered)
return 0;
/*
@@ -661,7 +661,18 @@ static int ext4_iomap_wb_update_disksize(handle_t *handle, struct inode *inode,
* are avoided by checking i_size under i_data_sem.
*/
down_write(&ei->i_data_sem);
- new_disksize = min(new_disksize, i_size_read(inode));
+ i_size = i_size_read(inode);
+
+ /*
+ * Update i_disksize to i_size when completing an ordered I/O that
+ * zeroes the old EOF partial block. This ensures i_disksize is
+ * correctly advanced during truncate-up on a blocksize-unaligned
+ * file, preventing it from remaining stale. A downside is that
+ * zeroed data may be exposed after crash recovery if the dirty
+ * data in this range is not yet on disk, but stale data will
+ * never be exposed.
+ */
+ new_disksize = is_ordered ? i_size : min(end, i_size);
if (new_disksize > ei->i_disksize)
ei->i_disksize = new_disksize;
up_write(&ei->i_data_sem);
@@ -678,6 +689,7 @@ static void ext4_iomap_finish_ioend(struct iomap_ioend *ioend)
struct super_block *sb = inode->i_sb;
loff_t pos = ioend->io_offset;
size_t size = ioend->io_size;
+ unsigned long io_mode = (unsigned long)ioend->io_private;
handle_t *handle;
int credits;
int ret, err;
@@ -707,7 +719,8 @@ static void ext4_iomap_finish_ioend(struct iomap_ioend *ioend)
goto out_journal;
}
- ret = ext4_iomap_wb_update_disksize(handle, inode, pos + size);
+ ret = ext4_iomap_wb_update_disksize(handle, inode, pos + size,
+ io_mode == EXT4_IOMAP_IOEND_ORDER_IO);
out_journal:
err = ext4_journal_stop(handle);
if (!ret)
--
2.52.0
^ permalink raw reply related
* [PATCH v3 14/22] ext4: implement partial block zero range path using iomap
From: Zhang Yi @ 2026-04-22 2:10 UTC (permalink / raw)
To: linux-ext4, linux-fsdevel
Cc: linux-kernel, tytso, adilger.kernel, libaokun, jack, ojaswin,
ritesh.list, djwong, hch, yi.zhang, yi.zhang, yizhang089,
yangerkun, yukuai
In-Reply-To: <20260422021042.4157510-1-yi.zhang@huaweicloud.com>
From: Zhang Yi <yi.zhang@huawei.com>
Introduce a new iomap_ops instance, ext4_iomap_zero_ops, along with
ext4_iomap_block_zero_range() to implement the iomap block zeroing range
for ext4. ext4_iomap_block_zero_range() invokes iomap_zero_range() and
passes ext4_iomap_zero_begin() to locate and zero out a mapped partial
block or a dirty, unwritten partial block.
Note that 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. Therefore,
ext4_iomap_block_zero_range() cannot be called under an active handle,
and we also cannot use data=order mode to ensure zeroed data to be
unwritten back before updating i_disksize when performing post-EOF
append write or performing truncate up as well.
Signed-off-by: Zhang Yi <yi.zhang@huawei.com>
---
fs/ext4/inode.c | 91 +++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 91 insertions(+)
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index 26e1366b85fd..701b912db6fb 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -4103,6 +4103,50 @@ static int ext4_iomap_buffered_da_write_end(struct inode *inode, loff_t offset,
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 = 0;
+ int ret;
+
+ ret = ext4_emergency_state(inode->i_sb);
+ if (unlikely(ret))
+ return ret;
+
+ if (WARN_ON_ONCE(!(flags & IOMAP_ZERO)))
+ return -EINVAL;
+
+ ret = ext4_iomap_map_blocks(inode, offset, length, NULL, &map);
+ 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.
+ */
+ if (map.m_flags & EXT4_MAP_UNWRITTEN) {
+ loff_t offset = ((loff_t)map.m_lblk) << blkbits;
+ loff_t end = ((loff_t)map.m_lblk + map.m_len) << blkbits;
+
+ iomap_fill_dirty_folios(iter, &offset, end, &iomap_flags);
+ if ((offset >> blkbits) < map.m_lblk + map.m_len)
+ map.m_len = (offset >> blkbits) - map.m_lblk;
+ }
+
+ ext4_set_iomap(inode, iomap, &map, offset, length, flags);
+ iomap->flags |= iomap_flags;
+
+ return 0;
+}
+
+static const struct iomap_ops ext4_iomap_zero_ops = {
+ .iomap_begin = ext4_iomap_zero_begin,
+};
const struct iomap_ops ext4_iomap_buffered_write_ops = {
.iomap_begin = ext4_iomap_buffered_write_begin,
@@ -4609,6 +4653,47 @@ static int ext4_block_journalled_zero_range(struct inode *inode, loff_t from,
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 and always sets zero_written if a zeroing operation is
+ * performed, which may result in unnecessary order operations.
+ */
+ 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.
@@ -4635,6 +4720,9 @@ 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);
@@ -4675,6 +4763,9 @@ 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.
+ *
+ * TODO: In the iomap path, handle this by updating i_disksize to
+ * i_size after the zeroed data has been written back.
*/
if (ext4_should_order_data(inode) &&
did_zero && zero_written && !IS_DAX(inode)) {
--
2.52.0
^ permalink raw reply related
* [PATCH v3 10/22] ext4: implement mmap path using iomap
From: Zhang Yi @ 2026-04-22 2:10 UTC (permalink / raw)
To: linux-ext4, linux-fsdevel
Cc: linux-kernel, tytso, adilger.kernel, libaokun, jack, ojaswin,
ritesh.list, djwong, hch, yi.zhang, yi.zhang, yizhang089,
yangerkun, yukuai
In-Reply-To: <20260422021042.4157510-1-yi.zhang@huaweicloud.com>
From: Zhang Yi <yi.zhang@huawei.com>
Introduce ext4_iomap_page_mkwrite() to implement the mmap iomap path for
ext4. Most of this work is delegated to iomap_page_mkwrite(), which only
needs to be called with ext4_iomap_buffer_write_ops and
ext4_iomap_buffer_da_write_ops as arguments to allocate and map the
blocks. However, the lock ordering of the folio lock and transaction
start is the opposite of that in the buffer_head buffered write path.
The locking documentation in super.c has been updated accordingly.
Signed-off-by: Zhang Yi <yi.zhang@huawei.com>
---
fs/ext4/inode.c | 32 +++++++++++++++++++++++++++++++-
fs/ext4/super.c | 8 ++++++--
2 files changed, 37 insertions(+), 3 deletions(-)
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index 76ce43c64c30..26e1366b85fd 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -4022,7 +4022,7 @@ static int ext4_iomap_buffered_do_write_begin(struct inode *inode,
/* Inline data support is not yet available. */
if (WARN_ON_ONCE(ext4_has_inline_data(inode)))
return -ERANGE;
- if (WARN_ON_ONCE(!(flags & IOMAP_WRITE)))
+ if (WARN_ON_ONCE(!(flags & (IOMAP_WRITE | IOMAP_FAULT))))
return -EINVAL;
if (delalloc)
@@ -4082,6 +4082,14 @@ static int ext4_iomap_buffered_da_write_end(struct inode *inode, loff_t offset,
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));
@@ -7167,6 +7175,23 @@ static int ext4_block_page_mkwrite(struct inode *inode, struct folio *folio,
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;
@@ -7189,6 +7214,11 @@ vm_fault_t ext4_page_mkwrite(struct vm_fault *vmf)
filemap_invalidate_lock_shared(mapping);
+ if (ext4_inode_buffered_iomap(inode)) {
+ ret = ext4_iomap_page_mkwrite(vmf);
+ goto out;
+ }
+
err = ext4_convert_inline_data(inode);
if (err)
goto out_ret;
diff --git a/fs/ext4/super.c b/fs/ext4/super.c
index 51d87db53543..62bfe05a64bc 100644
--- a/fs/ext4/super.c
+++ b/fs/ext4/super.c
@@ -100,8 +100,12 @@ 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_rwsem (w) -> mmap_lock
--
2.52.0
^ permalink raw reply related
* [PATCH v3 15/22] ext4: add block mapping tracepoints for iomap buffered I/O path
From: Zhang Yi @ 2026-04-22 2:10 UTC (permalink / raw)
To: linux-ext4, linux-fsdevel
Cc: linux-kernel, tytso, adilger.kernel, libaokun, jack, ojaswin,
ritesh.list, djwong, hch, yi.zhang, yi.zhang, yizhang089,
yangerkun, yukuai
In-Reply-To: <20260422021042.4157510-1-yi.zhang@huaweicloud.com>
From: Zhang Yi <yi.zhang@huawei.com>
Add tracepoints for iomap buffered read, write, partial block zeroing,
and writeback operations to help debug the iomap buffered I/O path.
Signed-off-by: Zhang Yi <yi.zhang@huawei.com>
---
fs/ext4/inode.c | 6 +++++
include/trace/events/ext4.h | 45 +++++++++++++++++++++++++++++++++++++
2 files changed, 51 insertions(+)
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index 701b912db6fb..53fdcb50f3dd 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -3961,6 +3961,8 @@ static int ext4_iomap_buffered_read_begin(struct inode *inode, loff_t offset,
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;
}
@@ -4036,6 +4038,8 @@ static int ext4_iomap_buffered_do_write_begin(struct inode *inode,
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;
}
@@ -4138,6 +4142,7 @@ static int ext4_iomap_zero_begin(struct inode *inode,
map.m_len = (offset >> blkbits) - map.m_lblk;
}
+ trace_ext4_iomap_zero_begin(inode, &map, offset, length, flags);
ext4_set_iomap(inode, iomap, &map, offset, length, flags);
iomap->flags |= iomap_flags;
@@ -4306,6 +4311,7 @@ static int ext4_iomap_map_writeback_range(struct iomap_writepage_ctx *wpc,
return ret;
}
out:
+ 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;
}
diff --git a/include/trace/events/ext4.h b/include/trace/events/ext4.h
index f493642cf121..ebafa06cd191 100644
--- a/include/trace/events/ext4.h
+++ b/include/trace/events/ext4.h
@@ -3096,6 +3096,51 @@ 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);
+
#endif /* _TRACE_EXT4_H */
/* This part must be outside protection */
--
2.52.0
^ permalink raw reply related
* [PATCH v3 18/22] ext4: introduce a mount option for iomap buffered I/O path
From: Zhang Yi @ 2026-04-22 2:10 UTC (permalink / raw)
To: linux-ext4, linux-fsdevel
Cc: linux-kernel, tytso, adilger.kernel, libaokun, jack, ojaswin,
ritesh.list, djwong, hch, yi.zhang, yi.zhang, yizhang089,
yangerkun, yukuai
In-Reply-To: <20260422021042.4157510-1-yi.zhang@huaweicloud.com>
From: Zhang Yi <yi.zhang@huawei.com>
Since the iomap buffered I/O path does not yet support all existing
features, it cannot be enabled by default. Introduce 'buffered_iomap'
and 'nobuffered_iomap' mount options to enable and disable the iomap
buffered I/O path for regular files.
Signed-off-by: Zhang Yi <yi.zhang@huawei.com>
---
fs/ext4/ext4.h | 1 +
fs/ext4/inode.c | 2 ++
fs/ext4/super.c | 7 +++++++
3 files changed, 10 insertions(+)
diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index 80d086d40990..60ba488b01c5 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -1281,6 +1281,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
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index 57b5708235cf..d2f7af7922d7 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -5750,6 +5750,8 @@ 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))
diff --git a/fs/ext4/super.c b/fs/ext4/super.c
index 62bfe05a64bc..b2da4834b6bb 100644
--- a/fs/ext4/super.c
+++ b/fs/ext4/super.c
@@ -1722,6 +1722,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
@@ -1860,6 +1861,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 */
@@ -1953,6 +1956,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},
--
2.52.0
^ permalink raw reply related
* [PATCH v3 17/22] ext4: partially enable iomap for the buffered I/O path of regular files
From: Zhang Yi @ 2026-04-22 2:10 UTC (permalink / raw)
To: linux-ext4, linux-fsdevel
Cc: linux-kernel, tytso, adilger.kernel, libaokun, jack, ojaswin,
ritesh.list, djwong, hch, yi.zhang, yi.zhang, yizhang089,
yangerkun, yukuai
In-Reply-To: <20260422021042.4157510-1-yi.zhang@huaweicloud.com>
From: Zhang Yi <yi.zhang@huawei.com>
Partially enable iomap for the buffered I/O path of regular files. We
now support default filesystem features, mount options, and the bigalloc
feature. However, inline data, fsverity, fscrypt, online
defragmentation, and data=journal mode are not yet supported. Some of
these features are expected to be gradually supported in the future. The
filesystem will automatically fall back to the original buffer_head path
if these mount options or features are enabled.
Signed-off-by: Zhang Yi <yi.zhang@huawei.com>
---
fs/ext4/ext4.h | 1 +
fs/ext4/ext4_jbd2.c | 1 +
fs/ext4/ialloc.c | 1 +
fs/ext4/inode.c | 36 ++++++++++++++++++++++++++++++++++++
4 files changed, 39 insertions(+)
diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index 0ffa81f86bc5..80d086d40990 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -3059,6 +3059,7 @@ 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 CONVERT_INLINE_DATA 2
diff --git a/fs/ext4/ext4_jbd2.c b/fs/ext4/ext4_jbd2.c
index 9a8c225f2753..9b25a1c414b9 100644
--- a/fs/ext4/ext4_jbd2.c
+++ b/fs/ext4/ext4_jbd2.c
@@ -16,6 +16,7 @@ int ext4_inode_journal_mode(struct inode *inode)
ext4_test_inode_flag(inode, EXT4_INODE_EA_INODE) ||
test_opt(inode->i_sb, DATA_FLAGS) == EXT4_MOUNT_JOURNAL_DATA ||
(ext4_test_inode_flag(inode, EXT4_INODE_JOURNAL_DATA) &&
+ !ext4_inode_buffered_iomap(inode) &&
!test_opt(inode->i_sb, DELALLOC))) {
/* We do not support data journalling for encrypted data */
if (S_ISREG(inode->i_mode) && IS_ENCRYPTED(inode))
diff --git a/fs/ext4/ialloc.c b/fs/ext4/ialloc.c
index 3fd8f0099852..ea64b9e9e382 100644
--- a/fs/ext4/ialloc.c
+++ b/fs/ext4/ialloc.c
@@ -1340,6 +1340,7 @@ struct inode *__ext4_new_inode(struct mnt_idmap *idmap,
}
}
+ ext4_enable_buffered_iomap(inode);
ext4_set_inode_mapping_order(inode);
ext4_update_inode_fsync_trans(handle, inode, 1);
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index 53fdcb50f3dd..57b5708235cf 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -918,6 +918,9 @@ static int _ext4_get_block(struct inode *inode, sector_t iblock,
if (ext4_has_inline_data(inode))
return -ERANGE;
+ /* inodes 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;
@@ -2797,6 +2800,12 @@ static int ext4_do_writepages(struct mpage_da_data *mpd)
if (!mapping->nrpages || !mapping_tagged(mapping, PAGECACHE_TAG_DIRTY))
goto out_writepages;
+ /* inodes using the iomap buffered I/O path should not go here. */
+ if (WARN_ON_ONCE(ext4_inode_buffered_iomap(inode))) {
+ ret = -EINVAL;
+ goto out_writepages;
+ }
+
/*
* If the filesystem has aborted, it is read-only, so return
* right away instead of dumping stack traces later on that
@@ -5737,6 +5746,31 @@ static int check_igot_inode(struct inode *inode, ext4_iget_flags flags,
return -EFSCORRUPTED;
}
+void ext4_enable_buffered_iomap(struct inode *inode)
+{
+ struct super_block *sb = inode->i_sb;
+
+ 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);
+}
+
void ext4_set_inode_mapping_order(struct inode *inode)
{
struct super_block *sb = inode->i_sb;
@@ -6022,6 +6056,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;
--
2.52.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox