* [PATCH 08/11] fs: add support for non-blocking timestamp updates
2025-12-23 0:37 re-enable IOCB_NOWAIT writes to files v4 Christoph Hellwig
@ 2025-12-23 0:37 ` Christoph Hellwig
2025-12-23 5:38 ` Chaitanya Kulkarni
0 siblings, 1 reply; 20+ messages in thread
From: Christoph Hellwig @ 2025-12-23 0:37 UTC (permalink / raw)
To: Christian Brauner
Cc: Al Viro, David Sterba, Jan Kara, Mike Marshall,
Martin Brandenburg, Carlos Maiolino, Stefan Roesch, Jeff Layton,
linux-kernel, linux-btrfs, linux-fsdevel, gfs2, io-uring, devel,
linux-unionfs, linux-mtd, linux-xfs, linux-nfs
Currently file_update_time_flags unconditionally returns -EAGAIN if any
timestamp needs to be updated and IOCB_NOWAIT is passed. This makes
non-blocking direct writes impossible on file systems with granular
enough timestamps.
Add a S_NOWAIT to ask for timestamps to not block, and return -EAGAIN in
all methods for now.
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Jeff Layton <jlayton@kernel.org>
---
fs/fat/misc.c | 3 +++
fs/gfs2/inode.c | 3 +++
fs/inode.c | 29 ++++++++++++++++++++++++++---
fs/overlayfs/inode.c | 2 ++
fs/ubifs/file.c | 3 +++
fs/xfs/xfs_iops.c | 3 +++
include/linux/fs.h | 21 +++++++++++++++++----
7 files changed, 57 insertions(+), 7 deletions(-)
diff --git a/fs/fat/misc.c b/fs/fat/misc.c
index 950da09f0961..5df3193c35f9 100644
--- a/fs/fat/misc.c
+++ b/fs/fat/misc.c
@@ -346,6 +346,9 @@ int fat_update_time(struct inode *inode, int flags)
if (inode->i_ino == MSDOS_ROOT_INO)
return 0;
+ if (flags & S_NOWAIT)
+ return -EAGAIN;
+
if (flags & (S_ATIME | S_CTIME | S_MTIME)) {
fat_truncate_time(inode, NULL, flags);
if (inode->i_sb->s_flags & SB_LAZYTIME)
diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c
index e08eb419347c..0dce2af533b2 100644
--- a/fs/gfs2/inode.c
+++ b/fs/gfs2/inode.c
@@ -2249,6 +2249,9 @@ static int gfs2_update_time(struct inode *inode, int flags)
struct gfs2_holder *gh;
int error;
+ if (flags & S_NOWAIT)
+ return -EAGAIN;
+
gh = gfs2_glock_is_locked_by_me(gl);
if (gh && gl->gl_state != LM_ST_EXCLUSIVE) {
gfs2_glock_dq(gh);
diff --git a/fs/inode.c b/fs/inode.c
index 212dab5c65ad..98a878427ecb 100644
--- a/fs/inode.c
+++ b/fs/inode.c
@@ -2111,6 +2111,13 @@ int inode_update_timestamps(struct inode *inode, int flags, int *dirty_flags)
*dirty_flags = 0;
+ /*
+ * Non-blocking timestamp updates require an explicit opt-in from the
+ * file system.
+ */
+ if ((flags & S_NOWAIT) && !(flags & S_CAN_NOWAIT_LAZYTIME))
+ return -EAGAIN;
+
if (flags & (S_MTIME | S_CTIME | S_VERSION)) {
struct timespec64 ctime = inode_get_ctime(inode);
struct timespec64 mtime = inode_get_mtime(inode);
@@ -2120,8 +2127,24 @@ int inode_update_timestamps(struct inode *inode, int flags, int *dirty_flags)
updated |= S_CTIME;
if (!timespec64_equal(&now, &mtime))
updated |= S_MTIME;
- if (IS_I_VERSION(inode) && inode_maybe_inc_iversion(inode, updated))
- updated |= S_VERSION;
+
+ /*
+ * Pure timestamp updates can be recorded in the inode without
+ * blocking by not dirtying the inode. But when the file system
+ * requires i_version updates, actual i_version update may block
+ * despite that. Error out if we'd actually have to update
+ * i_version or don't support lazytime.
+ */
+ if (IS_I_VERSION(inode)) {
+ if (flags & S_NOWAIT) {
+ if (!(inode->i_sb->s_flags & SB_LAZYTIME) ||
+ inode_iversion_need_inc(inode))
+ return -EAGAIN;
+ } else {
+ if (inode_maybe_inc_iversion(inode, updated))
+ updated |= S_NOWAIT;
+ }
+ }
} else {
now = current_time(inode);
}
@@ -2391,7 +2414,7 @@ static int file_update_time_flags(struct file *file, unsigned int flags)
return 0;
if (flags & IOCB_NOWAIT)
- return -EAGAIN;
+ sync_mode |= S_NOWAIT;
if (mnt_get_write_access_file(file))
return 0;
diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c
index bdbf86b56a9b..6d23cacbf776 100644
--- a/fs/overlayfs/inode.c
+++ b/fs/overlayfs/inode.c
@@ -565,6 +565,8 @@ int ovl_update_time(struct inode *inode, int flags)
};
if (upperpath.dentry) {
+ if (flags & S_NOWAIT)
+ return -EAGAIN;
touch_atime(&upperpath);
inode_set_atime_to_ts(inode,
inode_get_atime(d_inode(upperpath.dentry)));
diff --git a/fs/ubifs/file.c b/fs/ubifs/file.c
index fe236886484c..b74dd4d21330 100644
--- a/fs/ubifs/file.c
+++ b/fs/ubifs/file.c
@@ -1382,6 +1382,9 @@ int ubifs_update_time(struct inode *inode, int flags)
if (!IS_ENABLED(CONFIG_UBIFS_ATIME_SUPPORT))
return generic_update_time(inode, flags);
+ if (flags & S_NOWAIT)
+ return -EAGAIN;
+
err = ubifs_budget_space(c, &req);
if (err)
return err;
diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c
index 9dedb54e3cb0..626a541b247b 100644
--- a/fs/xfs/xfs_iops.c
+++ b/fs/xfs/xfs_iops.c
@@ -1195,6 +1195,9 @@ xfs_vn_update_time(
trace_xfs_update_time(ip);
+ if (flags & S_NOWAIT)
+ return -EAGAIN;
+
if (inode->i_sb->s_flags & SB_LAZYTIME) {
if (!((flags & S_VERSION) &&
inode_maybe_inc_iversion(inode, false)))
diff --git a/include/linux/fs.h b/include/linux/fs.h
index d1d57149aa93..0ea175e19a8b 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -2239,10 +2239,23 @@ static inline void inode_dec_link_count(struct inode *inode)
}
enum file_time_flags {
- S_ATIME = 1,
- S_MTIME = 2,
- S_CTIME = 4,
- S_VERSION = 8,
+ /* update atime: */
+ S_ATIME = 1U << 0,
+
+ /* update mtime */
+ S_MTIME = 1U << 1,
+
+ /* update ctime */
+ S_CTIME = 1U << 2,
+
+ /* force update i_version even if no timestamp changes */
+ S_VERSION = 1U << 3,
+
+ /* only update timestamps or i_version if it doesn't require blocking */
+ S_NOWAIT = 1U << 14,
+
+ /* support S_NOWAIT for SB_LAZYTIME mounts in inode_update_timestamps */
+ S_CAN_NOWAIT_LAZYTIME = 1U << 15,
};
extern bool atime_needs_update(const struct path *, struct inode *);
--
2.47.3
^ permalink raw reply related [flat|nested] 20+ messages in thread
* Re: [PATCH 08/11] fs: add support for non-blocking timestamp updates
2025-12-23 0:37 ` [PATCH 08/11] fs: add support for non-blocking timestamp updates Christoph Hellwig
@ 2025-12-23 5:38 ` Chaitanya Kulkarni
0 siblings, 0 replies; 20+ messages in thread
From: Chaitanya Kulkarni @ 2025-12-23 5:38 UTC (permalink / raw)
To: Christoph Hellwig, Christian Brauner
Cc: Al Viro, David Sterba, Jan Kara, Mike Marshall,
Martin Brandenburg, Carlos Maiolino, Stefan Roesch, Jeff Layton,
linux-kernel, linux-btrfs, linux-fsdevel, gfs2, io-uring, devel,
linux-unionfs, linux-mtd, linux-xfs, linux-nfs
On 12/22/25 16:37, Christoph Hellwig wrote:
> Currently file_update_time_flags unconditionally returns -EAGAIN if any
> timestamp needs to be updated and IOCB_NOWAIT is passed. This makes
> non-blocking direct writes impossible on file systems with granular
> enough timestamps.
>
> Add a S_NOWAIT to ask for timestamps to not block, and return -EAGAIN in
> all methods for now.
>
> Signed-off-by: Christoph Hellwig<hch@lst.de>
> Reviewed-by: Jeff Layton<jlayton@kernel.org>
Looks good.
Reviewed-by: Chaitanya Kulkarni <kch@nvidia.com>
-ck
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH 08/11] fs: add support for non-blocking timestamp updates
2026-01-06 7:49 re-enable IOCB_NOWAIT writes to files v5 Christoph Hellwig
@ 2026-01-06 7:50 ` Christoph Hellwig
2026-01-06 11:58 ` Jan Kara
2026-01-06 12:13 ` Jeff Layton
0 siblings, 2 replies; 20+ messages in thread
From: Christoph Hellwig @ 2026-01-06 7:50 UTC (permalink / raw)
To: Christian Brauner
Cc: Al Viro, David Sterba, Jan Kara, Mike Marshall,
Martin Brandenburg, Carlos Maiolino, Stefan Roesch, Jeff Layton,
OGAWA Hirofumi, Trond Myklebust, Anna Schumaker, linux-kernel,
linux-btrfs, linux-fsdevel, gfs2, io-uring, devel, linux-unionfs,
linux-mtd, linux-xfs, linux-nfs
Currently file_update_time_flags unconditionally returns -EAGAIN if any
timestamp needs to be updated and IOCB_NOWAIT is passed. This makes
non-blocking direct writes impossible on file systems with granular
enough timestamps.
Pass IOCB_NOWAIT to ->update_time and return -EAGAIN if it could block.
Signed-off-by: Christoph Hellwig <hch@lst.de>
---
fs/btrfs/inode.c | 2 ++
fs/gfs2/inode.c | 3 +++
fs/inode.c | 45 +++++++++++++++++++++++++++++++++-----------
fs/orangefs/inode.c | 3 +++
fs/overlayfs/inode.c | 2 ++
fs/ubifs/file.c | 3 +++
fs/xfs/xfs_iops.c | 3 +++
7 files changed, 50 insertions(+), 11 deletions(-)
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index 23fc38de9be5..241727459c0a 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -6362,6 +6362,8 @@ static int btrfs_update_time(struct inode *inode, enum fs_update_time type,
if (btrfs_root_readonly(root))
return -EROFS;
+ if (flags & IOCB_NOWAIT)
+ return -EAGAIN;
dirty = inode_update_time(inode, type, flags);
if (dirty <= 0)
diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c
index 4ef39ff6889d..c02ebf0ca625 100644
--- a/fs/gfs2/inode.c
+++ b/fs/gfs2/inode.c
@@ -2250,6 +2250,9 @@ static int gfs2_update_time(struct inode *inode, enum fs_update_time type,
struct gfs2_holder *gh;
int error;
+ if (flags & IOCB_NOWAIT)
+ return -EAGAIN;
+
gh = gfs2_glock_is_locked_by_me(gl);
if (gh && gl->gl_state != LM_ST_EXCLUSIVE) {
gfs2_glock_dq(gh);
diff --git a/fs/inode.c b/fs/inode.c
index c08682524a8d..01e4f6b9b46e 100644
--- a/fs/inode.c
+++ b/fs/inode.c
@@ -2090,7 +2090,7 @@ static int inode_update_atime(struct inode *inode)
return inode_time_dirty_flag(inode);
}
-static int inode_update_cmtime(struct inode *inode)
+static int inode_update_cmtime(struct inode *inode, unsigned int flags)
{
struct timespec64 now = inode_set_ctime_current(inode);
struct timespec64 ctime = inode_get_ctime(inode);
@@ -2101,12 +2101,27 @@ static int inode_update_cmtime(struct inode *inode)
mtime_changed = !timespec64_equal(&now, &mtime);
if (mtime_changed || !timespec64_equal(&now, &ctime))
dirty = inode_time_dirty_flag(inode);
- if (mtime_changed)
- inode_set_mtime_to_ts(inode, now);
- if (IS_I_VERSION(inode) && inode_maybe_inc_iversion(inode, !!dirty))
- dirty |= I_DIRTY_SYNC;
+ /*
+ * Pure timestamp updates can be recorded in the inode without blocking
+ * by not dirtying the inode. But when the file system requires
+ * i_version updates, the update of i_version can still block.
+ * Error out if we'd actually have to update i_version or don't support
+ * lazytime.
+ */
+ if (IS_I_VERSION(inode)) {
+ if (flags & IOCB_NOWAIT) {
+ if (!(inode->i_sb->s_flags & SB_LAZYTIME) ||
+ inode_iversion_need_inc(inode))
+ return -EAGAIN;
+ } else {
+ if (inode_maybe_inc_iversion(inode, !!dirty))
+ dirty |= I_DIRTY_SYNC;
+ }
+ }
+ if (mtime_changed)
+ inode_set_mtime_to_ts(inode, now);
return dirty;
}
@@ -2131,7 +2146,7 @@ int inode_update_time(struct inode *inode, enum fs_update_time type,
case FS_UPD_ATIME:
return inode_update_atime(inode);
case FS_UPD_CMTIME:
- return inode_update_cmtime(inode);
+ return inode_update_cmtime(inode, flags);
default:
WARN_ON_ONCE(1);
return -EIO;
@@ -2152,6 +2167,16 @@ int generic_update_time(struct inode *inode, enum fs_update_time type,
{
int dirty;
+ /*
+ * ->dirty_inode is what could make generic timestamp updates block.
+ * Don't support non-blocking timestamp updates here if it is set.
+ * File systems that implement ->dirty_inode but want to support
+ * non-blocking timestamp updates should call inode_update_time
+ * directly.
+ */
+ if ((flags & IOCB_NOWAIT) && inode->i_sb->s_op->dirty_inode)
+ return -EAGAIN;
+
dirty = inode_update_time(inode, type, flags);
if (dirty <= 0)
return dirty;
@@ -2380,15 +2405,13 @@ static int file_update_time_flags(struct file *file, unsigned int flags)
if (!need_update)
return 0;
- if (flags & IOCB_NOWAIT)
- return -EAGAIN;
-
+ flags &= IOCB_NOWAIT;
if (mnt_get_write_access_file(file))
return 0;
if (inode->i_op->update_time)
- ret = inode->i_op->update_time(inode, FS_UPD_CMTIME, 0);
+ ret = inode->i_op->update_time(inode, FS_UPD_CMTIME, flags);
else
- ret = generic_update_time(inode, FS_UPD_CMTIME, 0);
+ ret = generic_update_time(inode, FS_UPD_CMTIME, flags);
mnt_put_write_access_file(file);
return ret;
}
diff --git a/fs/orangefs/inode.c b/fs/orangefs/inode.c
index eab16afb5b8a..f420f48fc069 100644
--- a/fs/orangefs/inode.c
+++ b/fs/orangefs/inode.c
@@ -878,6 +878,9 @@ int orangefs_update_time(struct inode *inode, enum fs_update_time type,
struct iattr iattr = { };
int dirty;
+ if (flags & IOCB_NOWAIT)
+ return -EAGAIN;
+
switch (type) {
case FS_UPD_ATIME:
iattr.ia_valid = ATTR_ATIME;
diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c
index c0ce3519e4af..00c69707bda9 100644
--- a/fs/overlayfs/inode.c
+++ b/fs/overlayfs/inode.c
@@ -566,6 +566,8 @@ int ovl_update_time(struct inode *inode, enum fs_update_time type,
};
if (upperpath.dentry) {
+ if (flags & IOCB_NOWAIT)
+ return -EAGAIN;
touch_atime(&upperpath);
inode_set_atime_to_ts(inode,
inode_get_atime(d_inode(upperpath.dentry)));
diff --git a/fs/ubifs/file.c b/fs/ubifs/file.c
index 0cc44ad142de..3dc3ca1cd803 100644
--- a/fs/ubifs/file.c
+++ b/fs/ubifs/file.c
@@ -1377,6 +1377,9 @@ int ubifs_update_time(struct inode *inode, enum fs_update_time type,
if (!IS_ENABLED(CONFIG_UBIFS_ATIME_SUPPORT))
return generic_update_time(inode, type, flags);
+ if (flags & IOCB_NOWAIT)
+ return -EAGAIN;
+
err = ubifs_budget_space(c, &req);
if (err)
return err;
diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c
index d9eae1af14a8..aef5b05c1b76 100644
--- a/fs/xfs/xfs_iops.c
+++ b/fs/xfs/xfs_iops.c
@@ -1195,6 +1195,9 @@ xfs_vn_update_time(
trace_xfs_update_time(ip);
+ if (flags & IOCB_NOWAIT)
+ return -EAGAIN;
+
if (inode->i_sb->s_flags & SB_LAZYTIME) {
if (type == FS_UPD_ATIME ||
!inode_maybe_inc_iversion(inode, false))
--
2.47.3
^ permalink raw reply related [flat|nested] 20+ messages in thread
* Re: [PATCH 08/11] fs: add support for non-blocking timestamp updates
2026-01-06 7:50 ` [PATCH 08/11] fs: add support for non-blocking timestamp updates Christoph Hellwig
@ 2026-01-06 11:58 ` Jan Kara
2026-01-06 12:13 ` Jeff Layton
1 sibling, 0 replies; 20+ messages in thread
From: Jan Kara @ 2026-01-06 11:58 UTC (permalink / raw)
To: Christoph Hellwig
Cc: Christian Brauner, Al Viro, David Sterba, Jan Kara, Mike Marshall,
Martin Brandenburg, Carlos Maiolino, Stefan Roesch, Jeff Layton,
OGAWA Hirofumi, Trond Myklebust, Anna Schumaker, linux-kernel,
linux-btrfs, linux-fsdevel, gfs2, io-uring, devel, linux-unionfs,
linux-mtd, linux-xfs, linux-nfs
On Tue 06-01-26 08:50:02, Christoph Hellwig wrote:
> Currently file_update_time_flags unconditionally returns -EAGAIN if any
> timestamp needs to be updated and IOCB_NOWAIT is passed. This makes
> non-blocking direct writes impossible on file systems with granular
> enough timestamps.
>
> Pass IOCB_NOWAIT to ->update_time and return -EAGAIN if it could block.
>
> Signed-off-by: Christoph Hellwig <hch@lst.de>
Much nicer now! Thanks for the refactoring. Feel free to add:
Reviewed-by: Jan Kara <jack@suse.cz>
Honza
> ---
> fs/btrfs/inode.c | 2 ++
> fs/gfs2/inode.c | 3 +++
> fs/inode.c | 45 +++++++++++++++++++++++++++++++++-----------
> fs/orangefs/inode.c | 3 +++
> fs/overlayfs/inode.c | 2 ++
> fs/ubifs/file.c | 3 +++
> fs/xfs/xfs_iops.c | 3 +++
> 7 files changed, 50 insertions(+), 11 deletions(-)
>
> diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
> index 23fc38de9be5..241727459c0a 100644
> --- a/fs/btrfs/inode.c
> +++ b/fs/btrfs/inode.c
> @@ -6362,6 +6362,8 @@ static int btrfs_update_time(struct inode *inode, enum fs_update_time type,
>
> if (btrfs_root_readonly(root))
> return -EROFS;
> + if (flags & IOCB_NOWAIT)
> + return -EAGAIN;
>
> dirty = inode_update_time(inode, type, flags);
> if (dirty <= 0)
> diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c
> index 4ef39ff6889d..c02ebf0ca625 100644
> --- a/fs/gfs2/inode.c
> +++ b/fs/gfs2/inode.c
> @@ -2250,6 +2250,9 @@ static int gfs2_update_time(struct inode *inode, enum fs_update_time type,
> struct gfs2_holder *gh;
> int error;
>
> + if (flags & IOCB_NOWAIT)
> + return -EAGAIN;
> +
> gh = gfs2_glock_is_locked_by_me(gl);
> if (gh && gl->gl_state != LM_ST_EXCLUSIVE) {
> gfs2_glock_dq(gh);
> diff --git a/fs/inode.c b/fs/inode.c
> index c08682524a8d..01e4f6b9b46e 100644
> --- a/fs/inode.c
> +++ b/fs/inode.c
> @@ -2090,7 +2090,7 @@ static int inode_update_atime(struct inode *inode)
> return inode_time_dirty_flag(inode);
> }
>
> -static int inode_update_cmtime(struct inode *inode)
> +static int inode_update_cmtime(struct inode *inode, unsigned int flags)
> {
> struct timespec64 now = inode_set_ctime_current(inode);
> struct timespec64 ctime = inode_get_ctime(inode);
> @@ -2101,12 +2101,27 @@ static int inode_update_cmtime(struct inode *inode)
> mtime_changed = !timespec64_equal(&now, &mtime);
> if (mtime_changed || !timespec64_equal(&now, &ctime))
> dirty = inode_time_dirty_flag(inode);
> - if (mtime_changed)
> - inode_set_mtime_to_ts(inode, now);
>
> - if (IS_I_VERSION(inode) && inode_maybe_inc_iversion(inode, !!dirty))
> - dirty |= I_DIRTY_SYNC;
> + /*
> + * Pure timestamp updates can be recorded in the inode without blocking
> + * by not dirtying the inode. But when the file system requires
> + * i_version updates, the update of i_version can still block.
> + * Error out if we'd actually have to update i_version or don't support
> + * lazytime.
> + */
> + if (IS_I_VERSION(inode)) {
> + if (flags & IOCB_NOWAIT) {
> + if (!(inode->i_sb->s_flags & SB_LAZYTIME) ||
> + inode_iversion_need_inc(inode))
> + return -EAGAIN;
> + } else {
> + if (inode_maybe_inc_iversion(inode, !!dirty))
> + dirty |= I_DIRTY_SYNC;
> + }
> + }
>
> + if (mtime_changed)
> + inode_set_mtime_to_ts(inode, now);
> return dirty;
> }
>
> @@ -2131,7 +2146,7 @@ int inode_update_time(struct inode *inode, enum fs_update_time type,
> case FS_UPD_ATIME:
> return inode_update_atime(inode);
> case FS_UPD_CMTIME:
> - return inode_update_cmtime(inode);
> + return inode_update_cmtime(inode, flags);
> default:
> WARN_ON_ONCE(1);
> return -EIO;
> @@ -2152,6 +2167,16 @@ int generic_update_time(struct inode *inode, enum fs_update_time type,
> {
> int dirty;
>
> + /*
> + * ->dirty_inode is what could make generic timestamp updates block.
> + * Don't support non-blocking timestamp updates here if it is set.
> + * File systems that implement ->dirty_inode but want to support
> + * non-blocking timestamp updates should call inode_update_time
> + * directly.
> + */
> + if ((flags & IOCB_NOWAIT) && inode->i_sb->s_op->dirty_inode)
> + return -EAGAIN;
> +
> dirty = inode_update_time(inode, type, flags);
> if (dirty <= 0)
> return dirty;
> @@ -2380,15 +2405,13 @@ static int file_update_time_flags(struct file *file, unsigned int flags)
> if (!need_update)
> return 0;
>
> - if (flags & IOCB_NOWAIT)
> - return -EAGAIN;
> -
> + flags &= IOCB_NOWAIT;
> if (mnt_get_write_access_file(file))
> return 0;
> if (inode->i_op->update_time)
> - ret = inode->i_op->update_time(inode, FS_UPD_CMTIME, 0);
> + ret = inode->i_op->update_time(inode, FS_UPD_CMTIME, flags);
> else
> - ret = generic_update_time(inode, FS_UPD_CMTIME, 0);
> + ret = generic_update_time(inode, FS_UPD_CMTIME, flags);
> mnt_put_write_access_file(file);
> return ret;
> }
> diff --git a/fs/orangefs/inode.c b/fs/orangefs/inode.c
> index eab16afb5b8a..f420f48fc069 100644
> --- a/fs/orangefs/inode.c
> +++ b/fs/orangefs/inode.c
> @@ -878,6 +878,9 @@ int orangefs_update_time(struct inode *inode, enum fs_update_time type,
> struct iattr iattr = { };
> int dirty;
>
> + if (flags & IOCB_NOWAIT)
> + return -EAGAIN;
> +
> switch (type) {
> case FS_UPD_ATIME:
> iattr.ia_valid = ATTR_ATIME;
> diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c
> index c0ce3519e4af..00c69707bda9 100644
> --- a/fs/overlayfs/inode.c
> +++ b/fs/overlayfs/inode.c
> @@ -566,6 +566,8 @@ int ovl_update_time(struct inode *inode, enum fs_update_time type,
> };
>
> if (upperpath.dentry) {
> + if (flags & IOCB_NOWAIT)
> + return -EAGAIN;
> touch_atime(&upperpath);
> inode_set_atime_to_ts(inode,
> inode_get_atime(d_inode(upperpath.dentry)));
> diff --git a/fs/ubifs/file.c b/fs/ubifs/file.c
> index 0cc44ad142de..3dc3ca1cd803 100644
> --- a/fs/ubifs/file.c
> +++ b/fs/ubifs/file.c
> @@ -1377,6 +1377,9 @@ int ubifs_update_time(struct inode *inode, enum fs_update_time type,
> if (!IS_ENABLED(CONFIG_UBIFS_ATIME_SUPPORT))
> return generic_update_time(inode, type, flags);
>
> + if (flags & IOCB_NOWAIT)
> + return -EAGAIN;
> +
> err = ubifs_budget_space(c, &req);
> if (err)
> return err;
> diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c
> index d9eae1af14a8..aef5b05c1b76 100644
> --- a/fs/xfs/xfs_iops.c
> +++ b/fs/xfs/xfs_iops.c
> @@ -1195,6 +1195,9 @@ xfs_vn_update_time(
>
> trace_xfs_update_time(ip);
>
> + if (flags & IOCB_NOWAIT)
> + return -EAGAIN;
> +
> if (inode->i_sb->s_flags & SB_LAZYTIME) {
> if (type == FS_UPD_ATIME ||
> !inode_maybe_inc_iversion(inode, false))
> --
> 2.47.3
>
--
Jan Kara <jack@suse.com>
SUSE Labs, CR
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 08/11] fs: add support for non-blocking timestamp updates
2026-01-06 7:50 ` [PATCH 08/11] fs: add support for non-blocking timestamp updates Christoph Hellwig
2026-01-06 11:58 ` Jan Kara
@ 2026-01-06 12:13 ` Jeff Layton
1 sibling, 0 replies; 20+ messages in thread
From: Jeff Layton @ 2026-01-06 12:13 UTC (permalink / raw)
To: Christoph Hellwig, Christian Brauner
Cc: Al Viro, David Sterba, Jan Kara, Mike Marshall,
Martin Brandenburg, Carlos Maiolino, Stefan Roesch,
OGAWA Hirofumi, Trond Myklebust, Anna Schumaker, linux-kernel,
linux-btrfs, linux-fsdevel, gfs2, io-uring, devel, linux-unionfs,
linux-mtd, linux-xfs, linux-nfs
On Tue, 2026-01-06 at 08:50 +0100, Christoph Hellwig wrote:
> Currently file_update_time_flags unconditionally returns -EAGAIN if any
> timestamp needs to be updated and IOCB_NOWAIT is passed. This makes
> non-blocking direct writes impossible on file systems with granular
> enough timestamps.
>
> Pass IOCB_NOWAIT to ->update_time and return -EAGAIN if it could block.
>
> Signed-off-by: Christoph Hellwig <hch@lst.de>
> ---
> fs/btrfs/inode.c | 2 ++
> fs/gfs2/inode.c | 3 +++
> fs/inode.c | 45 +++++++++++++++++++++++++++++++++-----------
> fs/orangefs/inode.c | 3 +++
> fs/overlayfs/inode.c | 2 ++
> fs/ubifs/file.c | 3 +++
> fs/xfs/xfs_iops.c | 3 +++
> 7 files changed, 50 insertions(+), 11 deletions(-)
>
LGTM:
Reviewed-by: Jeff Layton <jlayton@kernel.org>
^ permalink raw reply [flat|nested] 20+ messages in thread
* re-enable IOCB_NOWAIT writes to files v6
@ 2026-01-08 14:19 Christoph Hellwig
2026-01-08 14:19 ` [PATCH 01/11] fs: remove inode_update_time Christoph Hellwig
` (11 more replies)
0 siblings, 12 replies; 20+ messages in thread
From: Christoph Hellwig @ 2026-01-08 14:19 UTC (permalink / raw)
To: Christian Brauner
Cc: Al Viro, David Sterba, Jan Kara, Mike Marshall,
Martin Brandenburg, Carlos Maiolino, Stefan Roesch, Jeff Layton,
OGAWA Hirofumi, Trond Myklebust, Anna Schumaker, linux-kernel,
linux-btrfs, linux-fsdevel, gfs2, io-uring, devel, linux-unionfs,
linux-mtd, linux-xfs, linux-nfs
Hi all,
commit 66fa3cedf16a ("fs: Add async write file modification handling.")
effectively disabled IOCB_NOWAIT writes as timestamp updates currently
always require blocking, and the modern timestamp resolution means we
always update timestamps. This leads to a lot of context switches from
applications using io_uring to submit file writes, making it often worse
than using the legacy aio code that is not using IOCB_NOWAIT.
This series allows non-blocking updates for lazytime if the file system
supports it, and adds that support for XFS.
Changes since v5:
- sample ctime before calling inode_set_ctime_current
- fix a mild bisection hazard in fat
Changes since v4:
- replace the S_* flags with an enum indicating either access or
modification time updates to make the logic less fragile and to
fix a bug in the previous version
Changes since v3:
- fix was_dirty_time handling in __mark_inode_dirty for the racy flag
update case
- refactor inode_update_timestamps to make the lazytime vs blocking
logical more clear
- allow non-blocking timestamp updates for fat
Changes since v2:
- drop patches merged upstream
- adjust for the inode state accesors
- keep a check in __writeback_single_inode instead of exercising
potentially undefined behavior
- more spelling fixes
Changes since v1:
- more regular numbering of the S_* flags
- fix XFS to actually not block
- don't ignore the generic_update_time return value in
file_update_time_flags
- fix the sync_lazytime return value
- fix an out of data comment in btrfs
- fix a race that would update i_version before returning -EAGAIN in XFS
Diffstat:
Documentation/filesystems/locking.rst | 2
Documentation/filesystems/vfs.rst | 6 +
fs/btrfs/inode.c | 8 +-
fs/fs-writeback.c | 33 +++++++---
fs/gfs2/inode.c | 6 +
fs/inode.c | 111 +++++++++++++++++++++-------------
fs/internal.h | 3
fs/nfs/inode.c | 4 -
fs/orangefs/inode.c | 5 +
fs/overlayfs/inode.c | 2
fs/sync.c | 4 -
fs/ubifs/file.c | 13 ++-
fs/xfs/xfs_iops.c | 34 +++++++++-
fs/xfs/xfs_super.c | 29 --------
include/linux/fs.h | 27 ++++++--
include/trace/events/writeback.h | 6 -
16 files changed, 182 insertions(+), 111 deletions(-)
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH 01/11] fs: remove inode_update_time
2026-01-08 14:19 re-enable IOCB_NOWAIT writes to files v6 Christoph Hellwig
@ 2026-01-08 14:19 ` Christoph Hellwig
2026-01-08 14:19 ` [PATCH 02/11] fs: allow error returns from generic_update_time Christoph Hellwig
` (10 subsequent siblings)
11 siblings, 0 replies; 20+ messages in thread
From: Christoph Hellwig @ 2026-01-08 14:19 UTC (permalink / raw)
To: Christian Brauner
Cc: Al Viro, David Sterba, Jan Kara, Mike Marshall,
Martin Brandenburg, Carlos Maiolino, Stefan Roesch, Jeff Layton,
OGAWA Hirofumi, Trond Myklebust, Anna Schumaker, linux-kernel,
linux-btrfs, linux-fsdevel, gfs2, io-uring, devel, linux-unionfs,
linux-mtd, linux-xfs, linux-nfs, Chaitanya Kulkarni
The only external user is gone now, open code it in the two VFS
callers.
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Jan Kara <jack@suse.cz>
Reviewed-by: Chaitanya Kulkarni <kch@nvidia.com>
Reviewed-by: Jeff Layton <jlayton@kernel.org>
---
fs/inode.c | 23 ++++++++---------------
include/linux/fs.h | 1 -
2 files changed, 8 insertions(+), 16 deletions(-)
diff --git a/fs/inode.c b/fs/inode.c
index 521383223d8a..07effa0cb999 100644
--- a/fs/inode.c
+++ b/fs/inode.c
@@ -2157,19 +2157,6 @@ int generic_update_time(struct inode *inode, int flags)
}
EXPORT_SYMBOL(generic_update_time);
-/*
- * This does the actual work of updating an inodes time or version. Must have
- * had called mnt_want_write() before calling this.
- */
-int inode_update_time(struct inode *inode, int flags)
-{
- if (inode->i_op->update_time)
- return inode->i_op->update_time(inode, flags);
- generic_update_time(inode, flags);
- return 0;
-}
-EXPORT_SYMBOL(inode_update_time);
-
/**
* atime_needs_update - update the access time
* @path: the &struct path to update
@@ -2237,7 +2224,10 @@ void touch_atime(const struct path *path)
* We may also fail on filesystems that have the ability to make parts
* of the fs read only, e.g. subvolumes in Btrfs.
*/
- inode_update_time(inode, S_ATIME);
+ if (inode->i_op->update_time)
+ inode->i_op->update_time(inode, S_ATIME);
+ else
+ generic_update_time(inode, S_ATIME);
mnt_put_write_access(mnt);
skip_update:
sb_end_write(inode->i_sb);
@@ -2392,7 +2382,10 @@ static int file_update_time_flags(struct file *file, unsigned int flags)
if (mnt_get_write_access_file(file))
return 0;
- ret = inode_update_time(inode, sync_mode);
+ if (inode->i_op->update_time)
+ ret = inode->i_op->update_time(inode, sync_mode);
+ else
+ ret = generic_update_time(inode, sync_mode);
mnt_put_write_access_file(file);
return ret;
}
diff --git a/include/linux/fs.h b/include/linux/fs.h
index f5c9cf28c4dc..ee623c16d835 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -2246,7 +2246,6 @@ enum file_time_flags {
extern bool atime_needs_update(const struct path *, struct inode *);
extern void touch_atime(const struct path *);
-int inode_update_time(struct inode *inode, int flags);
static inline void file_accessed(struct file *file)
{
--
2.47.3
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH 02/11] fs: allow error returns from generic_update_time
2026-01-08 14:19 re-enable IOCB_NOWAIT writes to files v6 Christoph Hellwig
2026-01-08 14:19 ` [PATCH 01/11] fs: remove inode_update_time Christoph Hellwig
@ 2026-01-08 14:19 ` Christoph Hellwig
2026-01-08 14:19 ` [PATCH 03/11] nfs: split nfs_update_timestamps Christoph Hellwig
` (9 subsequent siblings)
11 siblings, 0 replies; 20+ messages in thread
From: Christoph Hellwig @ 2026-01-08 14:19 UTC (permalink / raw)
To: Christian Brauner
Cc: Al Viro, David Sterba, Jan Kara, Mike Marshall,
Martin Brandenburg, Carlos Maiolino, Stefan Roesch, Jeff Layton,
OGAWA Hirofumi, Trond Myklebust, Anna Schumaker, linux-kernel,
linux-btrfs, linux-fsdevel, gfs2, io-uring, devel, linux-unionfs,
linux-mtd, linux-xfs, linux-nfs, Chaitanya Kulkarni
Now that no caller looks at the updated flags, switch generic_update_time
to the same calling convention as the ->update_time method and return 0
or a negative errno.
This prepares for adding non-blocking timestamp updates that could return
-EAGAIN.
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Jan Kara <jack@suse.cz>
Reviewed-by: Chaitanya Kulkarni <kch@nvidia.com>
Reviewed-by: Jeff Layton <jlayton@kernel.org>
---
fs/gfs2/inode.c | 3 +--
fs/inode.c | 4 ++--
fs/ubifs/file.c | 6 ++----
fs/xfs/xfs_iops.c | 6 ++----
include/linux/fs.h | 2 +-
5 files changed, 8 insertions(+), 13 deletions(-)
diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c
index 36618e353199..e08eb419347c 100644
--- a/fs/gfs2/inode.c
+++ b/fs/gfs2/inode.c
@@ -2257,8 +2257,7 @@ static int gfs2_update_time(struct inode *inode, int flags)
if (error)
return error;
}
- generic_update_time(inode, flags);
- return 0;
+ return generic_update_time(inode, flags);
}
static const struct inode_operations gfs2_file_iops = {
diff --git a/fs/inode.c b/fs/inode.c
index 07effa0cb999..7eb28dd45a5a 100644
--- a/fs/inode.c
+++ b/fs/inode.c
@@ -2141,7 +2141,7 @@ EXPORT_SYMBOL(inode_update_timestamps);
* or S_VERSION need to be updated we attempt to update all three of them. S_ATIME
* updates can be handled done independently of the rest.
*
- * Returns a S_* mask indicating which fields were updated.
+ * Returns a negative error value on error, else 0.
*/
int generic_update_time(struct inode *inode, int flags)
{
@@ -2153,7 +2153,7 @@ int generic_update_time(struct inode *inode, int flags)
if (updated & S_VERSION)
dirty_flags |= I_DIRTY_SYNC;
__mark_inode_dirty(inode, dirty_flags);
- return updated;
+ return 0;
}
EXPORT_SYMBOL(generic_update_time);
diff --git a/fs/ubifs/file.c b/fs/ubifs/file.c
index c3265b8804f5..ec1bb9f43acc 100644
--- a/fs/ubifs/file.c
+++ b/fs/ubifs/file.c
@@ -1379,10 +1379,8 @@ int ubifs_update_time(struct inode *inode, int flags)
.dirtied_ino_d = ALIGN(ui->data_len, 8) };
int err, release;
- if (!IS_ENABLED(CONFIG_UBIFS_ATIME_SUPPORT)) {
- generic_update_time(inode, flags);
- return 0;
- }
+ if (!IS_ENABLED(CONFIG_UBIFS_ATIME_SUPPORT))
+ return generic_update_time(inode, flags);
err = ubifs_budget_space(c, &req);
if (err)
diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c
index ad94fbf55014..9dedb54e3cb0 100644
--- a/fs/xfs/xfs_iops.c
+++ b/fs/xfs/xfs_iops.c
@@ -1197,10 +1197,8 @@ xfs_vn_update_time(
if (inode->i_sb->s_flags & SB_LAZYTIME) {
if (!((flags & S_VERSION) &&
- inode_maybe_inc_iversion(inode, false))) {
- generic_update_time(inode, flags);
- return 0;
- }
+ inode_maybe_inc_iversion(inode, false)))
+ return generic_update_time(inode, flags);
/* Capture the iversion update that just occurred */
log_flags |= XFS_ILOG_CORE;
diff --git a/include/linux/fs.h b/include/linux/fs.h
index ee623c16d835..fccb0a38cb74 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -2399,7 +2399,7 @@ extern void ihold(struct inode * inode);
extern void iput(struct inode *);
void iput_not_last(struct inode *);
int inode_update_timestamps(struct inode *inode, int flags);
-int generic_update_time(struct inode *, int);
+int generic_update_time(struct inode *inode, int flags);
/* /sys/fs */
extern struct kobject *fs_kobj;
--
2.47.3
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH 03/11] nfs: split nfs_update_timestamps
2026-01-08 14:19 re-enable IOCB_NOWAIT writes to files v6 Christoph Hellwig
2026-01-08 14:19 ` [PATCH 01/11] fs: remove inode_update_time Christoph Hellwig
2026-01-08 14:19 ` [PATCH 02/11] fs: allow error returns from generic_update_time Christoph Hellwig
@ 2026-01-08 14:19 ` Christoph Hellwig
2026-01-08 14:19 ` [PATCH 04/11] fat: cleanup the flags for fat_truncate_time Christoph Hellwig
` (8 subsequent siblings)
11 siblings, 0 replies; 20+ messages in thread
From: Christoph Hellwig @ 2026-01-08 14:19 UTC (permalink / raw)
To: Christian Brauner
Cc: Al Viro, David Sterba, Jan Kara, Mike Marshall,
Martin Brandenburg, Carlos Maiolino, Stefan Roesch, Jeff Layton,
OGAWA Hirofumi, Trond Myklebust, Anna Schumaker, linux-kernel,
linux-btrfs, linux-fsdevel, gfs2, io-uring, devel, linux-unionfs,
linux-mtd, linux-xfs, linux-nfs
The VFS paths update either the atime or ctime and mtime but never mix
between atime and the others. Split nfs_update_timestamps to match this
to prepare for cleaning up the VFS interfaces.
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Jan Kara <jack@suse.cz>
Reviewed-by: Jeff Layton <jlayton@kernel.org>
---
fs/nfs/inode.c | 31 +++++++++++++++----------------
1 file changed, 15 insertions(+), 16 deletions(-)
diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c
index 84049f3cd340..3be8ba7b98c5 100644
--- a/fs/nfs/inode.c
+++ b/fs/nfs/inode.c
@@ -669,35 +669,31 @@ static void nfs_set_timestamps_to_ts(struct inode *inode, struct iattr *attr)
NFS_I(inode)->cache_validity &= ~cache_flags;
}
-static void nfs_update_timestamps(struct inode *inode, unsigned int ia_valid)
+static void nfs_update_atime(struct inode *inode)
{
- enum file_time_flags time_flags = 0;
- unsigned int cache_flags = 0;
+ inode_update_timestamps(inode, S_ATIME);
+ NFS_I(inode)->cache_validity &= ~NFS_INO_INVALID_ATIME;
+}
- if (ia_valid & ATTR_MTIME) {
- time_flags |= S_MTIME | S_CTIME;
- cache_flags |= NFS_INO_INVALID_CTIME | NFS_INO_INVALID_MTIME;
- }
- if (ia_valid & ATTR_ATIME) {
- time_flags |= S_ATIME;
- cache_flags |= NFS_INO_INVALID_ATIME;
- }
- inode_update_timestamps(inode, time_flags);
- NFS_I(inode)->cache_validity &= ~cache_flags;
+static void nfs_update_mtime(struct inode *inode)
+{
+ inode_update_timestamps(inode, S_MTIME | S_CTIME);
+ NFS_I(inode)->cache_validity &=
+ ~(NFS_INO_INVALID_CTIME | NFS_INO_INVALID_MTIME);
}
void nfs_update_delegated_atime(struct inode *inode)
{
spin_lock(&inode->i_lock);
if (nfs_have_delegated_atime(inode))
- nfs_update_timestamps(inode, ATTR_ATIME);
+ nfs_update_atime(inode);
spin_unlock(&inode->i_lock);
}
void nfs_update_delegated_mtime_locked(struct inode *inode)
{
if (nfs_have_delegated_mtime(inode))
- nfs_update_timestamps(inode, ATTR_MTIME);
+ nfs_update_mtime(inode);
}
void nfs_update_delegated_mtime(struct inode *inode)
@@ -747,7 +743,10 @@ nfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
ATTR_ATIME|ATTR_ATIME_SET);
}
} else {
- nfs_update_timestamps(inode, attr->ia_valid);
+ if (attr->ia_valid & ATTR_MTIME)
+ nfs_update_mtime(inode);
+ if (attr->ia_valid & ATTR_ATIME)
+ nfs_update_atime(inode);
attr->ia_valid &= ~(ATTR_MTIME|ATTR_ATIME);
}
spin_unlock(&inode->i_lock);
--
2.47.3
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH 04/11] fat: cleanup the flags for fat_truncate_time
2026-01-08 14:19 re-enable IOCB_NOWAIT writes to files v6 Christoph Hellwig
` (2 preceding siblings ...)
2026-01-08 14:19 ` [PATCH 03/11] nfs: split nfs_update_timestamps Christoph Hellwig
@ 2026-01-08 14:19 ` Christoph Hellwig
2026-01-08 14:19 ` [PATCH 05/11] fs: refactor ->update_time handling Christoph Hellwig
` (7 subsequent siblings)
11 siblings, 0 replies; 20+ messages in thread
From: Christoph Hellwig @ 2026-01-08 14:19 UTC (permalink / raw)
To: Christian Brauner
Cc: Al Viro, David Sterba, Jan Kara, Mike Marshall,
Martin Brandenburg, Carlos Maiolino, Stefan Roesch, Jeff Layton,
OGAWA Hirofumi, Trond Myklebust, Anna Schumaker, linux-kernel,
linux-btrfs, linux-fsdevel, gfs2, io-uring, devel, linux-unionfs,
linux-mtd, linux-xfs, linux-nfs
Fat only has a single on-disk timestamp covering ctime and mtime. Add
fat-specific flags that indicate which timestamp fat_truncate_time should
update to make this more clear. This allows removing no-op
fat_truncate_time calls with the S_CTIME flag and prepares for removing
the S_* flags.
Signed-off-by: Christoph Hellwig <hch@lst.de>
Acked-by: OGAWA Hirofumi <hirofumi@mail.parknet.co.jp>
---
fs/fat/dir.c | 2 +-
fs/fat/fat.h | 8 ++++----
fs/fat/file.c | 14 ++++++-------
fs/fat/inode.c | 2 +-
fs/fat/misc.c | 47 ++++++++++++++++++++++----------------------
fs/fat/namei_msdos.c | 13 +++++-------
fs/fat/namei_vfat.c | 9 ++++-----
7 files changed, 44 insertions(+), 51 deletions(-)
diff --git a/fs/fat/dir.c b/fs/fat/dir.c
index 92b091783966..3d03bff40944 100644
--- a/fs/fat/dir.c
+++ b/fs/fat/dir.c
@@ -1080,7 +1080,7 @@ int fat_remove_entries(struct inode *dir, struct fat_slot_info *sinfo)
}
}
- fat_truncate_time(dir, NULL, S_ATIME|S_MTIME);
+ fat_truncate_time(dir, NULL, FAT_UPDATE_ATIME | FAT_UPDATE_CMTIME);
if (IS_DIRSYNC(dir))
(void)fat_sync_inode(dir);
else
diff --git a/fs/fat/fat.h b/fs/fat/fat.h
index d3e426de5f01..767b566b1cab 100644
--- a/fs/fat/fat.h
+++ b/fs/fat/fat.h
@@ -468,10 +468,10 @@ extern void fat_time_unix2fat(struct msdos_sb_info *sbi, struct timespec64 *ts,
__le16 *time, __le16 *date, u8 *time_cs);
extern struct timespec64 fat_truncate_atime(const struct msdos_sb_info *sbi,
const struct timespec64 *ts);
-extern struct timespec64 fat_truncate_mtime(const struct msdos_sb_info *sbi,
- const struct timespec64 *ts);
-extern int fat_truncate_time(struct inode *inode, struct timespec64 *now,
- int flags);
+#define FAT_UPDATE_ATIME (1u << 0)
+#define FAT_UPDATE_CMTIME (1u << 1)
+void fat_truncate_time(struct inode *inode, struct timespec64 *now,
+ unsigned int flags);
extern int fat_update_time(struct inode *inode, int flags);
extern int fat_sync_bhs(struct buffer_head **bhs, int nr_bhs);
diff --git a/fs/fat/file.c b/fs/fat/file.c
index 4fc49a614fb8..f9bc93411aa2 100644
--- a/fs/fat/file.c
+++ b/fs/fat/file.c
@@ -224,7 +224,7 @@ static int fat_cont_expand(struct inode *inode, loff_t size)
if (err)
goto out;
- fat_truncate_time(inode, NULL, S_CTIME|S_MTIME);
+ fat_truncate_time(inode, NULL, FAT_UPDATE_CMTIME);
mark_inode_dirty(inode);
if (IS_SYNC(inode)) {
int err2;
@@ -327,7 +327,7 @@ static int fat_free(struct inode *inode, int skip)
MSDOS_I(inode)->i_logstart = 0;
}
MSDOS_I(inode)->i_attrs |= ATTR_ARCH;
- fat_truncate_time(inode, NULL, S_CTIME|S_MTIME);
+ fat_truncate_time(inode, NULL, FAT_UPDATE_CMTIME);
if (wait) {
err = fat_sync_inode(inode);
if (err) {
@@ -553,15 +553,13 @@ int fat_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
}
/*
- * setattr_copy can't truncate these appropriately, so we'll
- * copy them ourselves
+ * setattr_copy can't truncate these appropriately, so we'll copy them
+ * ourselves. See fat_truncate_time for the c/mtime logic on fat.
*/
if (attr->ia_valid & ATTR_ATIME)
- fat_truncate_time(inode, &attr->ia_atime, S_ATIME);
- if (attr->ia_valid & ATTR_CTIME)
- fat_truncate_time(inode, &attr->ia_ctime, S_CTIME);
+ fat_truncate_time(inode, &attr->ia_atime, FAT_UPDATE_ATIME);
if (attr->ia_valid & ATTR_MTIME)
- fat_truncate_time(inode, &attr->ia_mtime, S_MTIME);
+ fat_truncate_time(inode, &attr->ia_mtime, FAT_UPDATE_CMTIME);
attr->ia_valid &= ~(ATTR_ATIME|ATTR_CTIME|ATTR_MTIME);
setattr_copy(idmap, inode, attr);
diff --git a/fs/fat/inode.c b/fs/fat/inode.c
index 0b6009cd1844..59fa90617b5b 100644
--- a/fs/fat/inode.c
+++ b/fs/fat/inode.c
@@ -246,7 +246,7 @@ static int fat_write_end(const struct kiocb *iocb,
if (err < len)
fat_write_failed(mapping, pos + len);
if (!(err < 0) && !(MSDOS_I(inode)->i_attrs & ATTR_ARCH)) {
- fat_truncate_time(inode, NULL, S_CTIME|S_MTIME);
+ fat_truncate_time(inode, NULL, FAT_UPDATE_CMTIME);
MSDOS_I(inode)->i_attrs |= ATTR_ARCH;
mark_inode_dirty(inode);
}
diff --git a/fs/fat/misc.c b/fs/fat/misc.c
index 950da09f0961..78c620e9b3fd 100644
--- a/fs/fat/misc.c
+++ b/fs/fat/misc.c
@@ -299,54 +299,53 @@ struct timespec64 fat_truncate_atime(const struct msdos_sb_info *sbi,
}
/*
- * truncate mtime to 2 second granularity
- */
-struct timespec64 fat_truncate_mtime(const struct msdos_sb_info *sbi,
- const struct timespec64 *ts)
-{
- return fat_timespec64_trunc_2secs(*ts);
-}
-
-/*
- * truncate the various times with appropriate granularity:
- * all times in root node are always 0
+ * Update the in-inode atime and/or mtime after truncating the timestamp to the
+ * granularity. All timestamps in root inode are always 0.
+ *
+ * ctime and mtime share the same on-disk field, and should be identical in
+ * memory. All mtime updates will be applied to ctime, but ctime updates are
+ * ignored.
*/
-int fat_truncate_time(struct inode *inode, struct timespec64 *now, int flags)
+void fat_truncate_time(struct inode *inode, struct timespec64 *now,
+ unsigned int flags)
{
struct msdos_sb_info *sbi = MSDOS_SB(inode->i_sb);
struct timespec64 ts;
if (inode->i_ino == MSDOS_ROOT_INO)
- return 0;
+ return;
if (now == NULL) {
now = &ts;
ts = current_time(inode);
}
- if (flags & S_ATIME)
+ if (flags & FAT_UPDATE_ATIME)
inode_set_atime_to_ts(inode, fat_truncate_atime(sbi, now));
- /*
- * ctime and mtime share the same on-disk field, and should be
- * identical in memory. all mtime updates will be applied to ctime,
- * but ctime updates are ignored.
- */
- if (flags & S_MTIME)
- inode_set_mtime_to_ts(inode,
- inode_set_ctime_to_ts(inode, fat_truncate_mtime(sbi, now)));
+ if (flags & FAT_UPDATE_CMTIME) {
+ /* truncate mtime to 2 second granularity */
+ struct timespec64 mtime = fat_timespec64_trunc_2secs(*now);
- return 0;
+ inode_set_mtime_to_ts(inode, mtime);
+ inode_set_ctime_to_ts(inode, mtime);
+ }
}
EXPORT_SYMBOL_GPL(fat_truncate_time);
int fat_update_time(struct inode *inode, int flags)
{
+ unsigned int fat_flags = 0;
int dirty_flags = 0;
if (inode->i_ino == MSDOS_ROOT_INO)
return 0;
- if (flags & (S_ATIME | S_CTIME | S_MTIME)) {
+ if (flags & S_ATIME)
+ fat_flags |= FAT_UPDATE_ATIME;
+ if (flags & (S_CTIME | S_MTIME))
+ fat_flags |= FAT_UPDATE_CMTIME;
+
+ if (fat_flags) {
fat_truncate_time(inode, NULL, flags);
if (inode->i_sb->s_flags & SB_LAZYTIME)
dirty_flags |= I_DIRTY_TIME;
diff --git a/fs/fat/namei_msdos.c b/fs/fat/namei_msdos.c
index 0b920ee40a7f..ba0152ed0810 100644
--- a/fs/fat/namei_msdos.c
+++ b/fs/fat/namei_msdos.c
@@ -251,7 +251,7 @@ static int msdos_add_entry(struct inode *dir, const unsigned char *name,
if (err)
return err;
- fat_truncate_time(dir, ts, S_CTIME|S_MTIME);
+ fat_truncate_time(dir, ts, FAT_UPDATE_CMTIME);
if (IS_DIRSYNC(dir))
(void)fat_sync_inode(dir);
else
@@ -295,7 +295,7 @@ static int msdos_create(struct mnt_idmap *idmap, struct inode *dir,
err = PTR_ERR(inode);
goto out;
}
- fat_truncate_time(inode, &ts, S_ATIME|S_CTIME|S_MTIME);
+ fat_truncate_time(inode, &ts, FAT_UPDATE_ATIME | FAT_UPDATE_CMTIME);
/* timestamp is already written, so mark_inode_dirty() is unneeded. */
d_instantiate(dentry, inode);
@@ -328,7 +328,6 @@ static int msdos_rmdir(struct inode *dir, struct dentry *dentry)
drop_nlink(dir);
clear_nlink(inode);
- fat_truncate_time(inode, NULL, S_CTIME);
fat_detach(inode);
out:
mutex_unlock(&MSDOS_SB(sb)->s_lock);
@@ -382,7 +381,7 @@ static struct dentry *msdos_mkdir(struct mnt_idmap *idmap, struct inode *dir,
goto out;
}
set_nlink(inode, 2);
- fat_truncate_time(inode, &ts, S_ATIME|S_CTIME|S_MTIME);
+ fat_truncate_time(inode, &ts, FAT_UPDATE_ATIME | FAT_UPDATE_CMTIME);
/* timestamp is already written, so mark_inode_dirty() is unneeded. */
d_instantiate(dentry, inode);
@@ -415,7 +414,6 @@ static int msdos_unlink(struct inode *dir, struct dentry *dentry)
if (err)
goto out;
clear_nlink(inode);
- fat_truncate_time(inode, NULL, S_CTIME);
fat_detach(inode);
out:
mutex_unlock(&MSDOS_SB(sb)->s_lock);
@@ -480,7 +478,7 @@ static int do_msdos_rename(struct inode *old_dir, unsigned char *old_name,
mark_inode_dirty(old_inode);
inode_inc_iversion(old_dir);
- fat_truncate_time(old_dir, NULL, S_CTIME|S_MTIME);
+ fat_truncate_time(old_dir, NULL, FAT_UPDATE_CMTIME);
if (IS_DIRSYNC(old_dir))
(void)fat_sync_inode(old_dir);
else
@@ -540,7 +538,7 @@ static int do_msdos_rename(struct inode *old_dir, unsigned char *old_name,
if (err)
goto error_dotdot;
inode_inc_iversion(old_dir);
- fat_truncate_time(old_dir, &ts, S_CTIME|S_MTIME);
+ fat_truncate_time(old_dir, &ts, FAT_UPDATE_CMTIME);
if (IS_DIRSYNC(old_dir))
(void)fat_sync_inode(old_dir);
else
@@ -550,7 +548,6 @@ static int do_msdos_rename(struct inode *old_dir, unsigned char *old_name,
drop_nlink(new_inode);
if (is_dir)
drop_nlink(new_inode);
- fat_truncate_time(new_inode, &ts, S_CTIME);
}
out:
brelse(sinfo.bh);
diff --git a/fs/fat/namei_vfat.c b/fs/fat/namei_vfat.c
index 5dbc4cbb8fce..e46f34cade1a 100644
--- a/fs/fat/namei_vfat.c
+++ b/fs/fat/namei_vfat.c
@@ -676,7 +676,7 @@ static int vfat_add_entry(struct inode *dir, const struct qstr *qname,
goto cleanup;
/* update timestamp */
- fat_truncate_time(dir, ts, S_CTIME|S_MTIME);
+ fat_truncate_time(dir, ts, FAT_UPDATE_CMTIME);
if (IS_DIRSYNC(dir))
(void)fat_sync_inode(dir);
else
@@ -806,7 +806,7 @@ static int vfat_rmdir(struct inode *dir, struct dentry *dentry)
drop_nlink(dir);
clear_nlink(inode);
- fat_truncate_time(inode, NULL, S_ATIME|S_MTIME);
+ fat_truncate_time(inode, NULL, FAT_UPDATE_ATIME | FAT_UPDATE_CMTIME);
fat_detach(inode);
vfat_d_version_set(dentry, inode_query_iversion(dir));
out:
@@ -832,7 +832,7 @@ static int vfat_unlink(struct inode *dir, struct dentry *dentry)
if (err)
goto out;
clear_nlink(inode);
- fat_truncate_time(inode, NULL, S_ATIME|S_MTIME);
+ fat_truncate_time(inode, NULL, FAT_UPDATE_ATIME | FAT_UPDATE_CMTIME);
fat_detach(inode);
vfat_d_version_set(dentry, inode_query_iversion(dir));
out:
@@ -918,7 +918,7 @@ static int vfat_update_dotdot_de(struct inode *dir, struct inode *inode,
static void vfat_update_dir_metadata(struct inode *dir, struct timespec64 *ts)
{
inode_inc_iversion(dir);
- fat_truncate_time(dir, ts, S_CTIME | S_MTIME);
+ fat_truncate_time(dir, ts, FAT_UPDATE_CMTIME);
if (IS_DIRSYNC(dir))
(void)fat_sync_inode(dir);
else
@@ -996,7 +996,6 @@ static int vfat_rename(struct inode *old_dir, struct dentry *old_dentry,
drop_nlink(new_inode);
if (is_dir)
drop_nlink(new_inode);
- fat_truncate_time(new_inode, &ts, S_CTIME);
}
out:
brelse(sinfo.bh);
--
2.47.3
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH 05/11] fs: refactor ->update_time handling
2026-01-08 14:19 re-enable IOCB_NOWAIT writes to files v6 Christoph Hellwig
` (3 preceding siblings ...)
2026-01-08 14:19 ` [PATCH 04/11] fat: cleanup the flags for fat_truncate_time Christoph Hellwig
@ 2026-01-08 14:19 ` Christoph Hellwig
2026-01-08 15:20 ` Jan Kara
2026-01-08 14:19 ` [PATCH 06/11] fs: factor out a sync_lazytime helper Christoph Hellwig
` (6 subsequent siblings)
11 siblings, 1 reply; 20+ messages in thread
From: Christoph Hellwig @ 2026-01-08 14:19 UTC (permalink / raw)
To: Christian Brauner
Cc: Al Viro, David Sterba, Jan Kara, Mike Marshall,
Martin Brandenburg, Carlos Maiolino, Stefan Roesch, Jeff Layton,
OGAWA Hirofumi, Trond Myklebust, Anna Schumaker, linux-kernel,
linux-btrfs, linux-fsdevel, gfs2, io-uring, devel, linux-unionfs,
linux-mtd, linux-xfs, linux-nfs
Pass the type of update (atime vs c/mtime plus version) as an enum
instead of a set of flags that caused all kinds of confusion.
Because inode_update_timestamps now can't return a modified version
of those flags, return the I_DIRTY_* flags needed to persist the
update, which is what the main caller in generic_update_time wants
anyway, and which is suitable for the other callers that only want
to know if an update happened.
The whole update_time path keeps the flags argument, which will be used
to support non-blocking updates soon even if it is unused, and (the
slightly renamed) inode_update_time also gains the possibility to return
a negative errno to support this.
Signed-off-by: Christoph Hellwig <hch@lst.de>
---
Documentation/filesystems/locking.rst | 3 +-
Documentation/filesystems/vfs.rst | 3 +-
fs/bad_inode.c | 3 +-
fs/btrfs/inode.c | 11 ++-
fs/fat/fat.h | 3 +-
fs/fat/misc.c | 26 ++---
fs/gfs2/inode.c | 5 +-
fs/inode.c | 134 ++++++++++++++------------
fs/nfs/inode.c | 10 +-
fs/orangefs/inode.c | 28 +++---
fs/orangefs/orangefs-kernel.h | 3 +-
fs/overlayfs/inode.c | 5 +-
fs/overlayfs/overlayfs.h | 3 +-
fs/ubifs/file.c | 21 ++--
fs/ubifs/ubifs.h | 3 +-
fs/xfs/xfs_iops.c | 22 ++---
include/linux/fs.h | 28 ++++--
17 files changed, 157 insertions(+), 154 deletions(-)
diff --git a/Documentation/filesystems/locking.rst b/Documentation/filesystems/locking.rst
index 77704fde9845..37a4a7fa8094 100644
--- a/Documentation/filesystems/locking.rst
+++ b/Documentation/filesystems/locking.rst
@@ -80,7 +80,8 @@ prototypes::
int (*getattr) (struct mnt_idmap *, const struct path *, struct kstat *, u32, unsigned int);
ssize_t (*listxattr) (struct dentry *, char *, size_t);
int (*fiemap)(struct inode *, struct fiemap_extent_info *, u64 start, u64 len);
- void (*update_time)(struct inode *, struct timespec *, int);
+ void (*update_time)(struct inode *inode, enum fs_update_time type,
+ int flags);
int (*atomic_open)(struct inode *, struct dentry *,
struct file *, unsigned open_flag,
umode_t create_mode);
diff --git a/Documentation/filesystems/vfs.rst b/Documentation/filesystems/vfs.rst
index 670ba66b60e4..51aa9db64784 100644
--- a/Documentation/filesystems/vfs.rst
+++ b/Documentation/filesystems/vfs.rst
@@ -485,7 +485,8 @@ As of kernel 2.6.22, the following members are defined:
int (*setattr) (struct mnt_idmap *, struct dentry *, struct iattr *);
int (*getattr) (struct mnt_idmap *, const struct path *, struct kstat *, u32, unsigned int);
ssize_t (*listxattr) (struct dentry *, char *, size_t);
- void (*update_time)(struct inode *, struct timespec *, int);
+ void (*update_time)(struct inode *inode, enum fs_update_time type,
+ int flags);
int (*atomic_open)(struct inode *, struct dentry *, struct file *,
unsigned open_flag, umode_t create_mode);
int (*tmpfile) (struct mnt_idmap *, struct inode *, struct file *, umode_t);
diff --git a/fs/bad_inode.c b/fs/bad_inode.c
index 0ef9bcb744dd..acf8613f5e36 100644
--- a/fs/bad_inode.c
+++ b/fs/bad_inode.c
@@ -133,7 +133,8 @@ static int bad_inode_fiemap(struct inode *inode,
return -EIO;
}
-static int bad_inode_update_time(struct inode *inode, int flags)
+static int bad_inode_update_time(struct inode *inode, enum fs_update_time type,
+ unsigned int flags)
{
return -EIO;
}
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index 599c03a1c573..23fc38de9be5 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -6354,16 +6354,19 @@ static int btrfs_dirty_inode(struct btrfs_inode *inode)
* We need our own ->update_time so that we can return error on ENOSPC for
* updating the inode in the case of file write and mmap writes.
*/
-static int btrfs_update_time(struct inode *inode, int flags)
+static int btrfs_update_time(struct inode *inode, enum fs_update_time type,
+ unsigned int flags)
{
struct btrfs_root *root = BTRFS_I(inode)->root;
- bool dirty;
+ int dirty;
if (btrfs_root_readonly(root))
return -EROFS;
- dirty = inode_update_timestamps(inode, flags);
- return dirty ? btrfs_dirty_inode(BTRFS_I(inode)) : 0;
+ dirty = inode_update_time(inode, type, flags);
+ if (dirty <= 0)
+ return dirty;
+ return btrfs_dirty_inode(BTRFS_I(inode));
}
/*
diff --git a/fs/fat/fat.h b/fs/fat/fat.h
index 767b566b1cab..0d269dba897b 100644
--- a/fs/fat/fat.h
+++ b/fs/fat/fat.h
@@ -472,7 +472,8 @@ extern struct timespec64 fat_truncate_atime(const struct msdos_sb_info *sbi,
#define FAT_UPDATE_CMTIME (1u << 1)
void fat_truncate_time(struct inode *inode, struct timespec64 *now,
unsigned int flags);
-extern int fat_update_time(struct inode *inode, int flags);
+int fat_update_time(struct inode *inode, enum fs_update_time type,
+ unsigned int flags);
extern int fat_sync_bhs(struct buffer_head **bhs, int nr_bhs);
int fat_cache_init(void);
diff --git a/fs/fat/misc.c b/fs/fat/misc.c
index 78c620e9b3fd..b154a5162764 100644
--- a/fs/fat/misc.c
+++ b/fs/fat/misc.c
@@ -332,28 +332,14 @@ void fat_truncate_time(struct inode *inode, struct timespec64 *now,
}
EXPORT_SYMBOL_GPL(fat_truncate_time);
-int fat_update_time(struct inode *inode, int flags)
+int fat_update_time(struct inode *inode, enum fs_update_time type,
+ unsigned int flags)
{
- unsigned int fat_flags = 0;
- int dirty_flags = 0;
-
- if (inode->i_ino == MSDOS_ROOT_INO)
- return 0;
-
- if (flags & S_ATIME)
- fat_flags |= FAT_UPDATE_ATIME;
- if (flags & (S_CTIME | S_MTIME))
- fat_flags |= FAT_UPDATE_CMTIME;
-
- if (fat_flags) {
- fat_truncate_time(inode, NULL, flags);
- if (inode->i_sb->s_flags & SB_LAZYTIME)
- dirty_flags |= I_DIRTY_TIME;
- else
- dirty_flags |= I_DIRTY_SYNC;
+ if (inode->i_ino != MSDOS_ROOT_INO) {
+ fat_truncate_time(inode, NULL, type == FS_UPD_ATIME ?
+ FAT_UPDATE_ATIME : FAT_UPDATE_CMTIME);
+ __mark_inode_dirty(inode, inode_time_dirty_flag(inode));
}
-
- __mark_inode_dirty(inode, dirty_flags);
return 0;
}
EXPORT_SYMBOL_GPL(fat_update_time);
diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c
index e08eb419347c..4ef39ff6889d 100644
--- a/fs/gfs2/inode.c
+++ b/fs/gfs2/inode.c
@@ -2242,7 +2242,8 @@ loff_t gfs2_seek_hole(struct file *file, loff_t offset)
return vfs_setpos(file, ret, inode->i_sb->s_maxbytes);
}
-static int gfs2_update_time(struct inode *inode, int flags)
+static int gfs2_update_time(struct inode *inode, enum fs_update_time type,
+ unsigned int flags)
{
struct gfs2_inode *ip = GFS2_I(inode);
struct gfs2_glock *gl = ip->i_gl;
@@ -2257,7 +2258,7 @@ static int gfs2_update_time(struct inode *inode, int flags)
if (error)
return error;
}
- return generic_update_time(inode, flags);
+ return generic_update_time(inode, type, flags);
}
static const struct inode_operations gfs2_file_iops = {
diff --git a/fs/inode.c b/fs/inode.c
index 7eb28dd45a5a..a0dd11a05473 100644
--- a/fs/inode.c
+++ b/fs/inode.c
@@ -2081,78 +2081,84 @@ static bool relatime_need_update(struct vfsmount *mnt, struct inode *inode,
return false;
}
+static int inode_update_atime(struct inode *inode)
+{
+ struct timespec64 atime = inode_get_atime(inode);
+ struct timespec64 now = current_time(inode);
+
+ if (timespec64_equal(&now, &atime))
+ return 0;
+
+ inode_set_atime_to_ts(inode, now);
+ return inode_time_dirty_flag(inode);
+}
+
+static int inode_update_cmtime(struct inode *inode)
+{
+ struct timespec64 ctime = inode_get_ctime(inode);
+ struct timespec64 mtime = inode_get_mtime(inode);
+ struct timespec64 now = inode_set_ctime_current(inode);
+ unsigned int dirty = 0;
+ bool mtime_changed;
+
+ mtime_changed = !timespec64_equal(&now, &mtime);
+ if (mtime_changed || !timespec64_equal(&now, &ctime))
+ dirty = inode_time_dirty_flag(inode);
+ if (mtime_changed)
+ inode_set_mtime_to_ts(inode, now);
+
+ if (IS_I_VERSION(inode) && inode_maybe_inc_iversion(inode, !!dirty))
+ dirty |= I_DIRTY_SYNC;
+
+ return dirty;
+}
+
/**
- * inode_update_timestamps - update the timestamps on the inode
+ * inode_update_time - update either atime or c/mtime and i_version on the inode
* @inode: inode to be updated
- * @flags: S_* flags that needed to be updated
+ * @type: timestamp to be updated
+ * @flags: flags for the update
*
- * The update_time function is called when an inode's timestamps need to be
- * updated for a read or write operation. This function handles updating the
- * actual timestamps. It's up to the caller to ensure that the inode is marked
- * dirty appropriately.
+ * Update either atime or c/mtime and version in a inode if needed for a file
+ * access or modification. It is up to the caller to mark the inode dirty
+ * appropriately.
*
- * In the case where any of S_MTIME, S_CTIME, or S_VERSION need to be updated,
- * attempt to update all three of them. S_ATIME updates can be handled
- * independently of the rest.
- *
- * Returns a set of S_* flags indicating which values changed.
+ * Returns the positive I_DIRTY_* flags for __mark_inode_dirty() if the inode
+ * needs to be marked dirty, 0 if it did not, or a negative errno if an error
+ * happened.
*/
-int inode_update_timestamps(struct inode *inode, int flags)
+int inode_update_time(struct inode *inode, enum fs_update_time type,
+ unsigned int flags)
{
- int updated = 0;
- struct timespec64 now;
-
- if (flags & (S_MTIME|S_CTIME|S_VERSION)) {
- struct timespec64 ctime = inode_get_ctime(inode);
- struct timespec64 mtime = inode_get_mtime(inode);
-
- now = inode_set_ctime_current(inode);
- if (!timespec64_equal(&now, &ctime))
- updated |= S_CTIME;
- if (!timespec64_equal(&now, &mtime)) {
- inode_set_mtime_to_ts(inode, now);
- updated |= S_MTIME;
- }
- if (IS_I_VERSION(inode) && inode_maybe_inc_iversion(inode, updated))
- updated |= S_VERSION;
- } else {
- now = current_time(inode);
- }
-
- if (flags & S_ATIME) {
- struct timespec64 atime = inode_get_atime(inode);
-
- if (!timespec64_equal(&now, &atime)) {
- inode_set_atime_to_ts(inode, now);
- updated |= S_ATIME;
- }
+ switch (type) {
+ case FS_UPD_ATIME:
+ return inode_update_atime(inode);
+ case FS_UPD_CMTIME:
+ return inode_update_cmtime(inode);
+ default:
+ WARN_ON_ONCE(1);
+ return -EIO;
}
- return updated;
}
-EXPORT_SYMBOL(inode_update_timestamps);
+EXPORT_SYMBOL(inode_update_time);
/**
* generic_update_time - update the timestamps on the inode
* @inode: inode to be updated
- * @flags: S_* flags that needed to be updated
- *
- * The update_time function is called when an inode's timestamps need to be
- * updated for a read or write operation. In the case where any of S_MTIME, S_CTIME,
- * or S_VERSION need to be updated we attempt to update all three of them. S_ATIME
- * updates can be handled done independently of the rest.
+ * @type: timestamp to be updated
+ * @flags: flags for the update
*
* Returns a negative error value on error, else 0.
*/
-int generic_update_time(struct inode *inode, int flags)
+int generic_update_time(struct inode *inode, enum fs_update_time type,
+ unsigned int flags)
{
- int updated = inode_update_timestamps(inode, flags);
- int dirty_flags = 0;
+ int dirty;
- if (updated & (S_ATIME|S_MTIME|S_CTIME))
- dirty_flags = inode->i_sb->s_flags & SB_LAZYTIME ? I_DIRTY_TIME : I_DIRTY_SYNC;
- if (updated & S_VERSION)
- dirty_flags |= I_DIRTY_SYNC;
- __mark_inode_dirty(inode, dirty_flags);
+ dirty = inode_update_time(inode, type, flags);
+ if (dirty <= 0)
+ return dirty;
+ __mark_inode_dirty(inode, dirty);
return 0;
}
EXPORT_SYMBOL(generic_update_time);
@@ -2225,9 +2231,9 @@ void touch_atime(const struct path *path)
* of the fs read only, e.g. subvolumes in Btrfs.
*/
if (inode->i_op->update_time)
- inode->i_op->update_time(inode, S_ATIME);
+ inode->i_op->update_time(inode, FS_UPD_ATIME, 0);
else
- generic_update_time(inode, S_ATIME);
+ generic_update_time(inode, FS_UPD_ATIME, 0);
mnt_put_write_access(mnt);
skip_update:
sb_end_write(inode->i_sb);
@@ -2354,7 +2360,7 @@ static int file_update_time_flags(struct file *file, unsigned int flags)
{
struct inode *inode = file_inode(file);
struct timespec64 now, ts;
- int sync_mode = 0;
+ bool need_update = false;
int ret = 0;
/* First try to exhaust all avenues to not sync */
@@ -2367,14 +2373,14 @@ static int file_update_time_flags(struct file *file, unsigned int flags)
ts = inode_get_mtime(inode);
if (!timespec64_equal(&ts, &now))
- sync_mode |= S_MTIME;
+ need_update = true;
ts = inode_get_ctime(inode);
if (!timespec64_equal(&ts, &now))
- sync_mode |= S_CTIME;
+ need_update = true;
if (IS_I_VERSION(inode) && inode_iversion_need_inc(inode))
- sync_mode |= S_VERSION;
+ need_update = true;
- if (!sync_mode)
+ if (!need_update)
return 0;
if (flags & IOCB_NOWAIT)
@@ -2383,9 +2389,9 @@ static int file_update_time_flags(struct file *file, unsigned int flags)
if (mnt_get_write_access_file(file))
return 0;
if (inode->i_op->update_time)
- ret = inode->i_op->update_time(inode, sync_mode);
+ ret = inode->i_op->update_time(inode, FS_UPD_CMTIME, 0);
else
- ret = generic_update_time(inode, sync_mode);
+ ret = generic_update_time(inode, FS_UPD_CMTIME, 0);
mnt_put_write_access_file(file);
return ret;
}
diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c
index 3be8ba7b98c5..cd6d7c6e1237 100644
--- a/fs/nfs/inode.c
+++ b/fs/nfs/inode.c
@@ -649,15 +649,15 @@ static void nfs_set_timestamps_to_ts(struct inode *inode, struct iattr *attr)
struct timespec64 ctime = inode_get_ctime(inode);
struct timespec64 mtime = inode_get_mtime(inode);
struct timespec64 now;
- int updated = 0;
+ bool updated = false;
now = inode_set_ctime_current(inode);
if (!timespec64_equal(&now, &ctime))
- updated |= S_CTIME;
+ updated = true;
inode_set_mtime_to_ts(inode, attr->ia_mtime);
if (!timespec64_equal(&now, &mtime))
- updated |= S_MTIME;
+ updated = true;
inode_maybe_inc_iversion(inode, updated);
cache_flags |= NFS_INO_INVALID_CTIME | NFS_INO_INVALID_MTIME;
@@ -671,13 +671,13 @@ static void nfs_set_timestamps_to_ts(struct inode *inode, struct iattr *attr)
static void nfs_update_atime(struct inode *inode)
{
- inode_update_timestamps(inode, S_ATIME);
+ inode_update_time(inode, FS_UPD_ATIME, 0);
NFS_I(inode)->cache_validity &= ~NFS_INO_INVALID_ATIME;
}
static void nfs_update_mtime(struct inode *inode)
{
- inode_update_timestamps(inode, S_MTIME | S_CTIME);
+ inode_update_time(inode, FS_UPD_CMTIME, 0);
NFS_I(inode)->cache_validity &=
~(NFS_INO_INVALID_CTIME | NFS_INO_INVALID_MTIME);
}
diff --git a/fs/orangefs/inode.c b/fs/orangefs/inode.c
index d7275990ffa4..eab16afb5b8a 100644
--- a/fs/orangefs/inode.c
+++ b/fs/orangefs/inode.c
@@ -872,22 +872,24 @@ int orangefs_permission(struct mnt_idmap *idmap,
return generic_permission(&nop_mnt_idmap, inode, mask);
}
-int orangefs_update_time(struct inode *inode, int flags)
+int orangefs_update_time(struct inode *inode, enum fs_update_time type,
+ unsigned int flags)
{
- struct iattr iattr;
+ struct iattr iattr = { };
+ int dirty;
- gossip_debug(GOSSIP_INODE_DEBUG, "orangefs_update_time: %pU\n",
- get_khandle_from_ino(inode));
-
- flags = inode_update_timestamps(inode, flags);
+ switch (type) {
+ case FS_UPD_ATIME:
+ iattr.ia_valid = ATTR_ATIME;
+ break;
+ case FS_UPD_CMTIME:
+ iattr.ia_valid = ATTR_CTIME | ATTR_MTIME;
+ break;
+ }
- memset(&iattr, 0, sizeof iattr);
- if (flags & S_ATIME)
- iattr.ia_valid |= ATTR_ATIME;
- if (flags & S_CTIME)
- iattr.ia_valid |= ATTR_CTIME;
- if (flags & S_MTIME)
- iattr.ia_valid |= ATTR_MTIME;
+ dirty = inode_update_time(inode, type, flags);
+ if (dirty <= 0)
+ return dirty;
return __orangefs_setattr(inode, &iattr);
}
diff --git a/fs/orangefs/orangefs-kernel.h b/fs/orangefs/orangefs-kernel.h
index 29c6da43e396..1451fc2c1917 100644
--- a/fs/orangefs/orangefs-kernel.h
+++ b/fs/orangefs/orangefs-kernel.h
@@ -360,7 +360,8 @@ int orangefs_getattr(struct mnt_idmap *idmap, const struct path *path,
int orangefs_permission(struct mnt_idmap *idmap,
struct inode *inode, int mask);
-int orangefs_update_time(struct inode *, int);
+int orangefs_update_time(struct inode *inode, enum fs_update_time type,
+ unsigned int flags);
/*
* defined in xattr.c
diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c
index bdbf86b56a9b..c0ce3519e4af 100644
--- a/fs/overlayfs/inode.c
+++ b/fs/overlayfs/inode.c
@@ -555,9 +555,10 @@ int ovl_set_acl(struct mnt_idmap *idmap, struct dentry *dentry,
}
#endif
-int ovl_update_time(struct inode *inode, int flags)
+int ovl_update_time(struct inode *inode, enum fs_update_time type,
+ unsigned int flags)
{
- if (flags & S_ATIME) {
+ if (type == FS_UPD_ATIME) {
struct ovl_fs *ofs = OVL_FS(inode->i_sb);
struct path upperpath = {
.mnt = ovl_upper_mnt(ofs),
diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h
index f9ac9bdde830..315882a360ce 100644
--- a/fs/overlayfs/overlayfs.h
+++ b/fs/overlayfs/overlayfs.h
@@ -820,7 +820,8 @@ static inline struct posix_acl *ovl_get_acl_path(const struct path *path,
}
#endif
-int ovl_update_time(struct inode *inode, int flags);
+int ovl_update_time(struct inode *inode, enum fs_update_time type,
+ unsigned int flags);
bool ovl_is_private_xattr(struct super_block *sb, const char *name);
struct ovl_inode_params {
diff --git a/fs/ubifs/file.c b/fs/ubifs/file.c
index ec1bb9f43acc..0cc44ad142de 100644
--- a/fs/ubifs/file.c
+++ b/fs/ubifs/file.c
@@ -1361,17 +1361,8 @@ static inline int mctime_update_needed(const struct inode *inode,
return 0;
}
-/**
- * ubifs_update_time - update time of inode.
- * @inode: inode to update
- * @flags: time updating control flag determines updating
- * which time fields of @inode
- *
- * This function updates time of the inode.
- *
- * Returns: %0 for success or a negative error code otherwise.
- */
-int ubifs_update_time(struct inode *inode, int flags)
+int ubifs_update_time(struct inode *inode, enum fs_update_time type,
+ unsigned int flags)
{
struct ubifs_inode *ui = ubifs_inode(inode);
struct ubifs_info *c = inode->i_sb->s_fs_info;
@@ -1379,15 +1370,19 @@ int ubifs_update_time(struct inode *inode, int flags)
.dirtied_ino_d = ALIGN(ui->data_len, 8) };
int err, release;
+ /* ubifs sets S_NOCMTIME on all inodes, this should not happen. */
+ if (WARN_ON_ONCE(type != FS_UPD_ATIME))
+ return -EIO;
+
if (!IS_ENABLED(CONFIG_UBIFS_ATIME_SUPPORT))
- return generic_update_time(inode, flags);
+ return generic_update_time(inode, type, flags);
err = ubifs_budget_space(c, &req);
if (err)
return err;
mutex_lock(&ui->ui_mutex);
- inode_update_timestamps(inode, flags);
+ inode_update_time(inode, type, flags);
release = ui->dirty;
__mark_inode_dirty(inode, I_DIRTY_SYNC);
mutex_unlock(&ui->ui_mutex);
diff --git a/fs/ubifs/ubifs.h b/fs/ubifs/ubifs.h
index 118392aa9f2a..b62a154c7bd4 100644
--- a/fs/ubifs/ubifs.h
+++ b/fs/ubifs/ubifs.h
@@ -2018,7 +2018,8 @@ int ubifs_calc_dark(const struct ubifs_info *c, int spc);
int ubifs_fsync(struct file *file, loff_t start, loff_t end, int datasync);
int ubifs_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
struct iattr *attr);
-int ubifs_update_time(struct inode *inode, int flags);
+int ubifs_update_time(struct inode *inode, enum fs_update_time type,
+ unsigned int flags);
/* dir.c */
struct inode *ubifs_new_inode(struct ubifs_info *c, struct inode *dir,
diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c
index 9dedb54e3cb0..d9eae1af14a8 100644
--- a/fs/xfs/xfs_iops.c
+++ b/fs/xfs/xfs_iops.c
@@ -1184,21 +1184,21 @@ xfs_vn_setattr(
STATIC int
xfs_vn_update_time(
struct inode *inode,
- int flags)
+ enum fs_update_time type,
+ unsigned int flags)
{
struct xfs_inode *ip = XFS_I(inode);
struct xfs_mount *mp = ip->i_mount;
int log_flags = XFS_ILOG_TIMESTAMP;
struct xfs_trans *tp;
int error;
- struct timespec64 now;
trace_xfs_update_time(ip);
if (inode->i_sb->s_flags & SB_LAZYTIME) {
- if (!((flags & S_VERSION) &&
- inode_maybe_inc_iversion(inode, false)))
- return generic_update_time(inode, flags);
+ if (type == FS_UPD_ATIME ||
+ !inode_maybe_inc_iversion(inode, false))
+ return generic_update_time(inode, type, flags);
/* Capture the iversion update that just occurred */
log_flags |= XFS_ILOG_CORE;
@@ -1209,16 +1209,10 @@ xfs_vn_update_time(
return error;
xfs_ilock(ip, XFS_ILOCK_EXCL);
- if (flags & (S_CTIME|S_MTIME))
- now = inode_set_ctime_current(inode);
+ if (type == FS_UPD_ATIME)
+ inode_set_atime_to_ts(inode, current_time(inode));
else
- now = current_time(inode);
-
- if (flags & S_MTIME)
- inode_set_mtime_to_ts(inode, now);
- if (flags & S_ATIME)
- inode_set_atime_to_ts(inode, now);
-
+ inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
xfs_trans_ijoin(tp, ip, XFS_ILOCK_EXCL);
xfs_trans_log_inode(tp, ip, log_flags);
return xfs_trans_commit(tp);
diff --git a/include/linux/fs.h b/include/linux/fs.h
index fccb0a38cb74..35b3e6c6b084 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -1717,6 +1717,13 @@ static inline struct timespec64 inode_set_ctime(struct inode *inode,
struct timespec64 simple_inode_init_ts(struct inode *inode);
+static inline int inode_time_dirty_flag(struct inode *inode)
+{
+ if (inode->i_sb->s_flags & SB_LAZYTIME)
+ return I_DIRTY_TIME;
+ return I_DIRTY_SYNC;
+}
+
/*
* Snapshotting support.
*/
@@ -1983,6 +1990,11 @@ int wrap_directory_iterator(struct file *, struct dir_context *,
static int shared_##x(struct file *file , struct dir_context *ctx) \
{ return wrap_directory_iterator(file, ctx, x); }
+enum fs_update_time {
+ FS_UPD_ATIME,
+ FS_UPD_CMTIME,
+};
+
struct inode_operations {
struct dentry * (*lookup) (struct inode *,struct dentry *, unsigned int);
const char * (*get_link) (struct dentry *, struct inode *, struct delayed_call *);
@@ -2010,7 +2022,8 @@ struct inode_operations {
ssize_t (*listxattr) (struct dentry *, char *, size_t);
int (*fiemap)(struct inode *, struct fiemap_extent_info *, u64 start,
u64 len);
- int (*update_time)(struct inode *, int);
+ int (*update_time)(struct inode *inode, enum fs_update_time type,
+ unsigned int flags);
int (*atomic_open)(struct inode *, struct dentry *,
struct file *, unsigned open_flag,
umode_t create_mode);
@@ -2237,13 +2250,6 @@ static inline void inode_dec_link_count(struct inode *inode)
mark_inode_dirty(inode);
}
-enum file_time_flags {
- S_ATIME = 1,
- S_MTIME = 2,
- S_CTIME = 4,
- S_VERSION = 8,
-};
-
extern bool atime_needs_update(const struct path *, struct inode *);
extern void touch_atime(const struct path *);
@@ -2398,8 +2404,10 @@ static inline void super_set_sysfs_name_generic(struct super_block *sb, const ch
extern void ihold(struct inode * inode);
extern void iput(struct inode *);
void iput_not_last(struct inode *);
-int inode_update_timestamps(struct inode *inode, int flags);
-int generic_update_time(struct inode *inode, int flags);
+int inode_update_time(struct inode *inode, enum fs_update_time type,
+ unsigned int flags);
+int generic_update_time(struct inode *inode, enum fs_update_time type,
+ unsigned int flags);
/* /sys/fs */
extern struct kobject *fs_kobj;
--
2.47.3
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH 06/11] fs: factor out a sync_lazytime helper
2026-01-08 14:19 re-enable IOCB_NOWAIT writes to files v6 Christoph Hellwig
` (4 preceding siblings ...)
2026-01-08 14:19 ` [PATCH 05/11] fs: refactor ->update_time handling Christoph Hellwig
@ 2026-01-08 14:19 ` Christoph Hellwig
2026-01-08 14:19 ` [PATCH 07/11] fs: add a ->sync_lazytime method Christoph Hellwig
` (5 subsequent siblings)
11 siblings, 0 replies; 20+ messages in thread
From: Christoph Hellwig @ 2026-01-08 14:19 UTC (permalink / raw)
To: Christian Brauner
Cc: Al Viro, David Sterba, Jan Kara, Mike Marshall,
Martin Brandenburg, Carlos Maiolino, Stefan Roesch, Jeff Layton,
OGAWA Hirofumi, Trond Myklebust, Anna Schumaker, linux-kernel,
linux-btrfs, linux-fsdevel, gfs2, io-uring, devel, linux-unionfs,
linux-mtd, linux-xfs, linux-nfs, Chaitanya Kulkarni
Centralize how we synchronize a lazytime update into the actual on-disk
timestamp into a single helper.
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Jan Kara <jack@suse.cz>
Reviewed-by: Chaitanya Kulkarni <kch@nvidia.com>
Reviewed-by: Jeff Layton <jlayton@kernel.org>
---
fs/fs-writeback.c | 22 +++++++++++++++-------
fs/inode.c | 5 +----
fs/internal.h | 3 ++-
fs/sync.c | 4 ++--
include/trace/events/writeback.h | 6 ------
5 files changed, 20 insertions(+), 20 deletions(-)
diff --git a/fs/fs-writeback.c b/fs/fs-writeback.c
index 6800886c4d10..3d68b757136c 100644
--- a/fs/fs-writeback.c
+++ b/fs/fs-writeback.c
@@ -1711,6 +1711,16 @@ static void requeue_inode(struct inode *inode, struct bdi_writeback *wb,
}
}
+bool sync_lazytime(struct inode *inode)
+{
+ if (!(inode_state_read_once(inode) & I_DIRTY_TIME))
+ return false;
+
+ trace_writeback_lazytime(inode);
+ mark_inode_dirty_sync(inode);
+ return true;
+}
+
/*
* Write out an inode and its dirty pages (or some of its dirty pages, depending
* on @wbc->nr_to_write), and clear the relevant dirty flags from i_state.
@@ -1750,17 +1760,15 @@ __writeback_single_inode(struct inode *inode, struct writeback_control *wbc)
}
/*
- * If the inode has dirty timestamps and we need to write them, call
- * mark_inode_dirty_sync() to notify the filesystem about it and to
- * change I_DIRTY_TIME into I_DIRTY_SYNC.
+ * For data integrity writeback, or when the dirty interval expired,
+ * ask the file system to propagata lazy timestamp updates into real
+ * dirty state.
*/
if ((inode_state_read_once(inode) & I_DIRTY_TIME) &&
(wbc->sync_mode == WB_SYNC_ALL ||
time_after(jiffies, inode->dirtied_time_when +
- dirtytime_expire_interval * HZ))) {
- trace_writeback_lazytime(inode);
- mark_inode_dirty_sync(inode);
- }
+ dirtytime_expire_interval * HZ)))
+ sync_lazytime(inode);
/*
* Get and clear the dirty flags from i_state. This needs to be done
diff --git a/fs/inode.c b/fs/inode.c
index a0dd11a05473..0cafe74bff2d 100644
--- a/fs/inode.c
+++ b/fs/inode.c
@@ -1979,11 +1979,8 @@ void iput(struct inode *inode)
if (atomic_add_unless(&inode->i_count, -1, 1))
return;
- if ((inode_state_read_once(inode) & I_DIRTY_TIME) && inode->i_nlink) {
- trace_writeback_lazytime_iput(inode);
- mark_inode_dirty_sync(inode);
+ if (inode->i_nlink && sync_lazytime(inode))
goto retry;
- }
spin_lock(&inode->i_lock);
if (unlikely((inode_state_read(inode) & I_DIRTY_TIME) && inode->i_nlink)) {
diff --git a/fs/internal.h b/fs/internal.h
index ab638d41ab81..18a062c1b5b0 100644
--- a/fs/internal.h
+++ b/fs/internal.h
@@ -214,7 +214,8 @@ bool in_group_or_capable(struct mnt_idmap *idmap,
/*
* fs-writeback.c
*/
-extern long get_nr_dirty_inodes(void);
+long get_nr_dirty_inodes(void);
+bool sync_lazytime(struct inode *inode);
/*
* dcache.c
diff --git a/fs/sync.c b/fs/sync.c
index 431fc5f5be06..4283af7119d1 100644
--- a/fs/sync.c
+++ b/fs/sync.c
@@ -183,8 +183,8 @@ int vfs_fsync_range(struct file *file, loff_t start, loff_t end, int datasync)
if (!file->f_op->fsync)
return -EINVAL;
- if (!datasync && (inode_state_read_once(inode) & I_DIRTY_TIME))
- mark_inode_dirty_sync(inode);
+ if (!datasync)
+ sync_lazytime(inode);
return file->f_op->fsync(file, start, end, datasync);
}
EXPORT_SYMBOL(vfs_fsync_range);
diff --git a/include/trace/events/writeback.h b/include/trace/events/writeback.h
index 311a341e6fe4..7162d03e69a5 100644
--- a/include/trace/events/writeback.h
+++ b/include/trace/events/writeback.h
@@ -856,12 +856,6 @@ DEFINE_EVENT(writeback_inode_template, writeback_lazytime,
TP_ARGS(inode)
);
-DEFINE_EVENT(writeback_inode_template, writeback_lazytime_iput,
- TP_PROTO(struct inode *inode),
-
- TP_ARGS(inode)
-);
-
DEFINE_EVENT(writeback_inode_template, writeback_dirty_inode_enqueue,
TP_PROTO(struct inode *inode),
--
2.47.3
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH 07/11] fs: add a ->sync_lazytime method
2026-01-08 14:19 re-enable IOCB_NOWAIT writes to files v6 Christoph Hellwig
` (5 preceding siblings ...)
2026-01-08 14:19 ` [PATCH 06/11] fs: factor out a sync_lazytime helper Christoph Hellwig
@ 2026-01-08 14:19 ` Christoph Hellwig
2026-01-08 15:24 ` Jan Kara
2026-01-08 14:19 ` [PATCH 08/11] fs: add support for non-blocking timestamp updates Christoph Hellwig
` (4 subsequent siblings)
11 siblings, 1 reply; 20+ messages in thread
From: Christoph Hellwig @ 2026-01-08 14:19 UTC (permalink / raw)
To: Christian Brauner
Cc: Al Viro, David Sterba, Jan Kara, Mike Marshall,
Martin Brandenburg, Carlos Maiolino, Stefan Roesch, Jeff Layton,
OGAWA Hirofumi, Trond Myklebust, Anna Schumaker, linux-kernel,
linux-btrfs, linux-fsdevel, gfs2, io-uring, devel, linux-unionfs,
linux-mtd, linux-xfs, linux-nfs, Chaitanya Kulkarni
Allow the file system to explicitly implement lazytime syncing instead
of pigging back on generic inode dirtying. This allows to simplify
the XFS implementation and prepares for non-blocking lazytime timestamp
updates.
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Chaitanya Kulkarni <kch@nvidia.com>
Reviewed-by: Jeff Layton <jlayton@kernel.org>
---
Documentation/filesystems/locking.rst | 2 ++
Documentation/filesystems/vfs.rst | 6 ++++++
fs/fs-writeback.c | 13 +++++++++++--
include/linux/fs.h | 1 +
4 files changed, 20 insertions(+), 2 deletions(-)
diff --git a/Documentation/filesystems/locking.rst b/Documentation/filesystems/locking.rst
index 37a4a7fa8094..0312fba6d73b 100644
--- a/Documentation/filesystems/locking.rst
+++ b/Documentation/filesystems/locking.rst
@@ -82,6 +82,7 @@ prototypes::
int (*fiemap)(struct inode *, struct fiemap_extent_info *, u64 start, u64 len);
void (*update_time)(struct inode *inode, enum fs_update_time type,
int flags);
+ void (*sync_lazytime)(struct inode *inode);
int (*atomic_open)(struct inode *, struct dentry *,
struct file *, unsigned open_flag,
umode_t create_mode);
@@ -118,6 +119,7 @@ getattr: no
listxattr: no
fiemap: no
update_time: no
+sync_lazytime: no
atomic_open: shared (exclusive if O_CREAT is set in open flags)
tmpfile: no
fileattr_get: no or exclusive
diff --git a/Documentation/filesystems/vfs.rst b/Documentation/filesystems/vfs.rst
index 51aa9db64784..d8cb181f69f8 100644
--- a/Documentation/filesystems/vfs.rst
+++ b/Documentation/filesystems/vfs.rst
@@ -487,6 +487,7 @@ As of kernel 2.6.22, the following members are defined:
ssize_t (*listxattr) (struct dentry *, char *, size_t);
void (*update_time)(struct inode *inode, enum fs_update_time type,
int flags);
+ void (*sync_lazytime)(struct inode *inode);
int (*atomic_open)(struct inode *, struct dentry *, struct file *,
unsigned open_flag, umode_t create_mode);
int (*tmpfile) (struct mnt_idmap *, struct inode *, struct file *, umode_t);
@@ -643,6 +644,11 @@ otherwise noted.
an inode. If this is not defined the VFS will update the inode
itself and call mark_inode_dirty_sync.
+``sync_lazytime``:
+ called by the writeback code to update the lazy time stamps to
+ regular time stamp updates that get syncing into the on-disk
+ inode.
+
``atomic_open``
called on the last component of an open. Using this optional
method the filesystem can look up, possibly create and open the
diff --git a/fs/fs-writeback.c b/fs/fs-writeback.c
index 3d68b757136c..62658be2578b 100644
--- a/fs/fs-writeback.c
+++ b/fs/fs-writeback.c
@@ -1717,7 +1717,10 @@ bool sync_lazytime(struct inode *inode)
return false;
trace_writeback_lazytime(inode);
- mark_inode_dirty_sync(inode);
+ if (inode->i_op->sync_lazytime)
+ inode->i_op->sync_lazytime(inode);
+ else
+ mark_inode_dirty_sync(inode);
return true;
}
@@ -2569,6 +2572,8 @@ void __mark_inode_dirty(struct inode *inode, int flags)
trace_writeback_mark_inode_dirty(inode, flags);
if (flags & I_DIRTY_INODE) {
+ bool was_dirty_time = false;
+
/*
* Inode timestamp update will piggback on this dirtying.
* We tell ->dirty_inode callback that timestamps need to
@@ -2579,6 +2584,7 @@ void __mark_inode_dirty(struct inode *inode, int flags)
if (inode_state_read(inode) & I_DIRTY_TIME) {
inode_state_clear(inode, I_DIRTY_TIME);
flags |= I_DIRTY_TIME;
+ was_dirty_time = true;
}
spin_unlock(&inode->i_lock);
}
@@ -2591,9 +2597,12 @@ void __mark_inode_dirty(struct inode *inode, int flags)
* for just I_DIRTY_PAGES or I_DIRTY_TIME.
*/
trace_writeback_dirty_inode_start(inode, flags);
- if (sb->s_op->dirty_inode)
+ if (sb->s_op->dirty_inode) {
sb->s_op->dirty_inode(inode,
flags & (I_DIRTY_INODE | I_DIRTY_TIME));
+ } else if (was_dirty_time && inode->i_op->sync_lazytime) {
+ inode->i_op->sync_lazytime(inode);
+ }
trace_writeback_dirty_inode(inode, flags);
/* I_DIRTY_INODE supersedes I_DIRTY_TIME. */
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 35b3e6c6b084..7837db1ba1d2 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -2024,6 +2024,7 @@ struct inode_operations {
u64 len);
int (*update_time)(struct inode *inode, enum fs_update_time type,
unsigned int flags);
+ void (*sync_lazytime)(struct inode *inode);
int (*atomic_open)(struct inode *, struct dentry *,
struct file *, unsigned open_flag,
umode_t create_mode);
--
2.47.3
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH 08/11] fs: add support for non-blocking timestamp updates
2026-01-08 14:19 re-enable IOCB_NOWAIT writes to files v6 Christoph Hellwig
` (6 preceding siblings ...)
2026-01-08 14:19 ` [PATCH 07/11] fs: add a ->sync_lazytime method Christoph Hellwig
@ 2026-01-08 14:19 ` Christoph Hellwig
2026-01-08 14:19 ` [PATCH 09/11] fs: refactor file_update_time_flags Christoph Hellwig
` (3 subsequent siblings)
11 siblings, 0 replies; 20+ messages in thread
From: Christoph Hellwig @ 2026-01-08 14:19 UTC (permalink / raw)
To: Christian Brauner
Cc: Al Viro, David Sterba, Jan Kara, Mike Marshall,
Martin Brandenburg, Carlos Maiolino, Stefan Roesch, Jeff Layton,
OGAWA Hirofumi, Trond Myklebust, Anna Schumaker, linux-kernel,
linux-btrfs, linux-fsdevel, gfs2, io-uring, devel, linux-unionfs,
linux-mtd, linux-xfs, linux-nfs
Currently file_update_time_flags unconditionally returns -EAGAIN if any
timestamp needs to be updated and IOCB_NOWAIT is passed. This makes
non-blocking direct writes impossible on file systems with granular
enough timestamps.
Pass IOCB_NOWAIT to ->update_time and return -EAGAIN if it could block.
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Jan Kara <jack@suse.cz>
Reviewed-by: Jeff Layton <jlayton@kernel.org>
---
fs/btrfs/inode.c | 2 ++
fs/gfs2/inode.c | 3 +++
fs/inode.c | 45 +++++++++++++++++++++++++++++++++-----------
fs/orangefs/inode.c | 3 +++
fs/overlayfs/inode.c | 2 ++
fs/ubifs/file.c | 3 +++
fs/xfs/xfs_iops.c | 3 +++
7 files changed, 50 insertions(+), 11 deletions(-)
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index 23fc38de9be5..241727459c0a 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -6362,6 +6362,8 @@ static int btrfs_update_time(struct inode *inode, enum fs_update_time type,
if (btrfs_root_readonly(root))
return -EROFS;
+ if (flags & IOCB_NOWAIT)
+ return -EAGAIN;
dirty = inode_update_time(inode, type, flags);
if (dirty <= 0)
diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c
index 4ef39ff6889d..c02ebf0ca625 100644
--- a/fs/gfs2/inode.c
+++ b/fs/gfs2/inode.c
@@ -2250,6 +2250,9 @@ static int gfs2_update_time(struct inode *inode, enum fs_update_time type,
struct gfs2_holder *gh;
int error;
+ if (flags & IOCB_NOWAIT)
+ return -EAGAIN;
+
gh = gfs2_glock_is_locked_by_me(gl);
if (gh && gl->gl_state != LM_ST_EXCLUSIVE) {
gfs2_glock_dq(gh);
diff --git a/fs/inode.c b/fs/inode.c
index 0cafe74bff2d..cd3ca98e8355 100644
--- a/fs/inode.c
+++ b/fs/inode.c
@@ -2090,7 +2090,7 @@ static int inode_update_atime(struct inode *inode)
return inode_time_dirty_flag(inode);
}
-static int inode_update_cmtime(struct inode *inode)
+static int inode_update_cmtime(struct inode *inode, unsigned int flags)
{
struct timespec64 ctime = inode_get_ctime(inode);
struct timespec64 mtime = inode_get_mtime(inode);
@@ -2101,12 +2101,27 @@ static int inode_update_cmtime(struct inode *inode)
mtime_changed = !timespec64_equal(&now, &mtime);
if (mtime_changed || !timespec64_equal(&now, &ctime))
dirty = inode_time_dirty_flag(inode);
- if (mtime_changed)
- inode_set_mtime_to_ts(inode, now);
- if (IS_I_VERSION(inode) && inode_maybe_inc_iversion(inode, !!dirty))
- dirty |= I_DIRTY_SYNC;
+ /*
+ * Pure timestamp updates can be recorded in the inode without blocking
+ * by not dirtying the inode. But when the file system requires
+ * i_version updates, the update of i_version can still block.
+ * Error out if we'd actually have to update i_version or don't support
+ * lazytime.
+ */
+ if (IS_I_VERSION(inode)) {
+ if (flags & IOCB_NOWAIT) {
+ if (!(inode->i_sb->s_flags & SB_LAZYTIME) ||
+ inode_iversion_need_inc(inode))
+ return -EAGAIN;
+ } else {
+ if (inode_maybe_inc_iversion(inode, !!dirty))
+ dirty |= I_DIRTY_SYNC;
+ }
+ }
+ if (mtime_changed)
+ inode_set_mtime_to_ts(inode, now);
return dirty;
}
@@ -2131,7 +2146,7 @@ int inode_update_time(struct inode *inode, enum fs_update_time type,
case FS_UPD_ATIME:
return inode_update_atime(inode);
case FS_UPD_CMTIME:
- return inode_update_cmtime(inode);
+ return inode_update_cmtime(inode, flags);
default:
WARN_ON_ONCE(1);
return -EIO;
@@ -2152,6 +2167,16 @@ int generic_update_time(struct inode *inode, enum fs_update_time type,
{
int dirty;
+ /*
+ * ->dirty_inode is what could make generic timestamp updates block.
+ * Don't support non-blocking timestamp updates here if it is set.
+ * File systems that implement ->dirty_inode but want to support
+ * non-blocking timestamp updates should call inode_update_time
+ * directly.
+ */
+ if ((flags & IOCB_NOWAIT) && inode->i_sb->s_op->dirty_inode)
+ return -EAGAIN;
+
dirty = inode_update_time(inode, type, flags);
if (dirty <= 0)
return dirty;
@@ -2380,15 +2405,13 @@ static int file_update_time_flags(struct file *file, unsigned int flags)
if (!need_update)
return 0;
- if (flags & IOCB_NOWAIT)
- return -EAGAIN;
-
+ flags &= IOCB_NOWAIT;
if (mnt_get_write_access_file(file))
return 0;
if (inode->i_op->update_time)
- ret = inode->i_op->update_time(inode, FS_UPD_CMTIME, 0);
+ ret = inode->i_op->update_time(inode, FS_UPD_CMTIME, flags);
else
- ret = generic_update_time(inode, FS_UPD_CMTIME, 0);
+ ret = generic_update_time(inode, FS_UPD_CMTIME, flags);
mnt_put_write_access_file(file);
return ret;
}
diff --git a/fs/orangefs/inode.c b/fs/orangefs/inode.c
index eab16afb5b8a..f420f48fc069 100644
--- a/fs/orangefs/inode.c
+++ b/fs/orangefs/inode.c
@@ -878,6 +878,9 @@ int orangefs_update_time(struct inode *inode, enum fs_update_time type,
struct iattr iattr = { };
int dirty;
+ if (flags & IOCB_NOWAIT)
+ return -EAGAIN;
+
switch (type) {
case FS_UPD_ATIME:
iattr.ia_valid = ATTR_ATIME;
diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c
index c0ce3519e4af..00c69707bda9 100644
--- a/fs/overlayfs/inode.c
+++ b/fs/overlayfs/inode.c
@@ -566,6 +566,8 @@ int ovl_update_time(struct inode *inode, enum fs_update_time type,
};
if (upperpath.dentry) {
+ if (flags & IOCB_NOWAIT)
+ return -EAGAIN;
touch_atime(&upperpath);
inode_set_atime_to_ts(inode,
inode_get_atime(d_inode(upperpath.dentry)));
diff --git a/fs/ubifs/file.c b/fs/ubifs/file.c
index 0cc44ad142de..3dc3ca1cd803 100644
--- a/fs/ubifs/file.c
+++ b/fs/ubifs/file.c
@@ -1377,6 +1377,9 @@ int ubifs_update_time(struct inode *inode, enum fs_update_time type,
if (!IS_ENABLED(CONFIG_UBIFS_ATIME_SUPPORT))
return generic_update_time(inode, type, flags);
+ if (flags & IOCB_NOWAIT)
+ return -EAGAIN;
+
err = ubifs_budget_space(c, &req);
if (err)
return err;
diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c
index d9eae1af14a8..aef5b05c1b76 100644
--- a/fs/xfs/xfs_iops.c
+++ b/fs/xfs/xfs_iops.c
@@ -1195,6 +1195,9 @@ xfs_vn_update_time(
trace_xfs_update_time(ip);
+ if (flags & IOCB_NOWAIT)
+ return -EAGAIN;
+
if (inode->i_sb->s_flags & SB_LAZYTIME) {
if (type == FS_UPD_ATIME ||
!inode_maybe_inc_iversion(inode, false))
--
2.47.3
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH 09/11] fs: refactor file_update_time_flags
2026-01-08 14:19 re-enable IOCB_NOWAIT writes to files v6 Christoph Hellwig
` (7 preceding siblings ...)
2026-01-08 14:19 ` [PATCH 08/11] fs: add support for non-blocking timestamp updates Christoph Hellwig
@ 2026-01-08 14:19 ` Christoph Hellwig
2026-01-08 14:19 ` [PATCH 10/11] xfs: implement ->sync_lazytime Christoph Hellwig
` (2 subsequent siblings)
11 siblings, 0 replies; 20+ messages in thread
From: Christoph Hellwig @ 2026-01-08 14:19 UTC (permalink / raw)
To: Christian Brauner
Cc: Al Viro, David Sterba, Jan Kara, Mike Marshall,
Martin Brandenburg, Carlos Maiolino, Stefan Roesch, Jeff Layton,
OGAWA Hirofumi, Trond Myklebust, Anna Schumaker, linux-kernel,
linux-btrfs, linux-fsdevel, gfs2, io-uring, devel, linux-unionfs,
linux-mtd, linux-xfs, linux-nfs
Split all the inode timestamp flags into a helper. This not only
makes the code a bit more readable, but also optimizes away the
further checks as soon as know we need an update.
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Jan Kara <jack@suse.cz>
Reviewed-by: Jeff Layton <jlayton@kernel.org>
---
fs/inode.c | 31 +++++++++++++++----------------
1 file changed, 15 insertions(+), 16 deletions(-)
diff --git a/fs/inode.c b/fs/inode.c
index cd3ca98e8355..5913e1993e4a 100644
--- a/fs/inode.c
+++ b/fs/inode.c
@@ -2378,31 +2378,30 @@ struct timespec64 current_time(struct inode *inode)
}
EXPORT_SYMBOL(current_time);
+static inline bool need_cmtime_update(struct inode *inode)
+{
+ struct timespec64 now = current_time(inode), ts;
+
+ ts = inode_get_mtime(inode);
+ if (!timespec64_equal(&ts, &now))
+ return true;
+ ts = inode_get_ctime(inode);
+ if (!timespec64_equal(&ts, &now))
+ return true;
+ return IS_I_VERSION(inode) && inode_iversion_need_inc(inode);
+}
+
static int file_update_time_flags(struct file *file, unsigned int flags)
{
struct inode *inode = file_inode(file);
- struct timespec64 now, ts;
- bool need_update = false;
- int ret = 0;
+ int ret;
/* First try to exhaust all avenues to not sync */
if (IS_NOCMTIME(inode))
return 0;
if (unlikely(file->f_mode & FMODE_NOCMTIME))
return 0;
-
- now = current_time(inode);
-
- ts = inode_get_mtime(inode);
- if (!timespec64_equal(&ts, &now))
- need_update = true;
- ts = inode_get_ctime(inode);
- if (!timespec64_equal(&ts, &now))
- need_update = true;
- if (IS_I_VERSION(inode) && inode_iversion_need_inc(inode))
- need_update = true;
-
- if (!need_update)
+ if (!need_cmtime_update(inode))
return 0;
flags &= IOCB_NOWAIT;
--
2.47.3
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH 10/11] xfs: implement ->sync_lazytime
2026-01-08 14:19 re-enable IOCB_NOWAIT writes to files v6 Christoph Hellwig
` (8 preceding siblings ...)
2026-01-08 14:19 ` [PATCH 09/11] fs: refactor file_update_time_flags Christoph Hellwig
@ 2026-01-08 14:19 ` Christoph Hellwig
2026-01-08 14:19 ` [PATCH 11/11] xfs: enable non-blocking timestamp updates Christoph Hellwig
2026-01-12 13:02 ` re-enable IOCB_NOWAIT writes to files v6 Christian Brauner
11 siblings, 0 replies; 20+ messages in thread
From: Christoph Hellwig @ 2026-01-08 14:19 UTC (permalink / raw)
To: Christian Brauner
Cc: Al Viro, David Sterba, Jan Kara, Mike Marshall,
Martin Brandenburg, Carlos Maiolino, Stefan Roesch, Jeff Layton,
OGAWA Hirofumi, Trond Myklebust, Anna Schumaker, linux-kernel,
linux-btrfs, linux-fsdevel, gfs2, io-uring, devel, linux-unionfs,
linux-mtd, linux-xfs, linux-nfs
Switch to the new explicit lazytime syncing method instead of trying
to second guess what could be a lazytime update in ->dirty_inode.
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Jeff Layton <jlayton@kernel.org>
---
fs/xfs/xfs_iops.c | 20 ++++++++++++++++++++
fs/xfs/xfs_super.c | 29 -----------------------------
2 files changed, 20 insertions(+), 29 deletions(-)
diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c
index aef5b05c1b76..338f3113f674 100644
--- a/fs/xfs/xfs_iops.c
+++ b/fs/xfs/xfs_iops.c
@@ -1221,6 +1221,22 @@ xfs_vn_update_time(
return xfs_trans_commit(tp);
}
+static void
+xfs_vn_sync_lazytime(
+ struct inode *inode)
+{
+ struct xfs_inode *ip = XFS_I(inode);
+ struct xfs_mount *mp = ip->i_mount;
+ struct xfs_trans *tp;
+
+ if (xfs_trans_alloc(mp, &M_RES(mp)->tr_fsyncts, 0, 0, 0, &tp))
+ return;
+ xfs_ilock(ip, XFS_ILOCK_EXCL);
+ xfs_trans_ijoin(tp, ip, XFS_ILOCK_EXCL);
+ xfs_trans_log_inode(tp, ip, XFS_ILOG_TIMESTAMP);
+ xfs_trans_commit(tp);
+}
+
STATIC int
xfs_vn_fiemap(
struct inode *inode,
@@ -1264,6 +1280,7 @@ static const struct inode_operations xfs_inode_operations = {
.listxattr = xfs_vn_listxattr,
.fiemap = xfs_vn_fiemap,
.update_time = xfs_vn_update_time,
+ .sync_lazytime = xfs_vn_sync_lazytime,
.fileattr_get = xfs_fileattr_get,
.fileattr_set = xfs_fileattr_set,
};
@@ -1290,6 +1307,7 @@ static const struct inode_operations xfs_dir_inode_operations = {
.setattr = xfs_vn_setattr,
.listxattr = xfs_vn_listxattr,
.update_time = xfs_vn_update_time,
+ .sync_lazytime = xfs_vn_sync_lazytime,
.tmpfile = xfs_vn_tmpfile,
.fileattr_get = xfs_fileattr_get,
.fileattr_set = xfs_fileattr_set,
@@ -1317,6 +1335,7 @@ static const struct inode_operations xfs_dir_ci_inode_operations = {
.setattr = xfs_vn_setattr,
.listxattr = xfs_vn_listxattr,
.update_time = xfs_vn_update_time,
+ .sync_lazytime = xfs_vn_sync_lazytime,
.tmpfile = xfs_vn_tmpfile,
.fileattr_get = xfs_fileattr_get,
.fileattr_set = xfs_fileattr_set,
@@ -1328,6 +1347,7 @@ static const struct inode_operations xfs_symlink_inode_operations = {
.setattr = xfs_vn_setattr,
.listxattr = xfs_vn_listxattr,
.update_time = xfs_vn_update_time,
+ .sync_lazytime = xfs_vn_sync_lazytime,
.fileattr_get = xfs_fileattr_get,
.fileattr_set = xfs_fileattr_set,
};
diff --git a/fs/xfs/xfs_super.c b/fs/xfs/xfs_super.c
index bc71aa9dcee8..094f257eff15 100644
--- a/fs/xfs/xfs_super.c
+++ b/fs/xfs/xfs_super.c
@@ -712,34 +712,6 @@ xfs_fs_destroy_inode(
xfs_inode_mark_reclaimable(ip);
}
-static void
-xfs_fs_dirty_inode(
- struct inode *inode,
- int flags)
-{
- struct xfs_inode *ip = XFS_I(inode);
- struct xfs_mount *mp = ip->i_mount;
- struct xfs_trans *tp;
-
- if (!(inode->i_sb->s_flags & SB_LAZYTIME))
- return;
-
- /*
- * Only do the timestamp update if the inode is dirty (I_DIRTY_SYNC)
- * and has dirty timestamp (I_DIRTY_TIME). I_DIRTY_TIME can be passed
- * in flags possibly together with I_DIRTY_SYNC.
- */
- if ((flags & ~I_DIRTY_TIME) != I_DIRTY_SYNC || !(flags & I_DIRTY_TIME))
- return;
-
- if (xfs_trans_alloc(mp, &M_RES(mp)->tr_fsyncts, 0, 0, 0, &tp))
- return;
- xfs_ilock(ip, XFS_ILOCK_EXCL);
- xfs_trans_ijoin(tp, ip, XFS_ILOCK_EXCL);
- xfs_trans_log_inode(tp, ip, XFS_ILOG_TIMESTAMP);
- xfs_trans_commit(tp);
-}
-
/*
* Slab object creation initialisation for the XFS inode.
* This covers only the idempotent fields in the XFS inode;
@@ -1304,7 +1276,6 @@ xfs_fs_show_stats(
static const struct super_operations xfs_super_operations = {
.alloc_inode = xfs_fs_alloc_inode,
.destroy_inode = xfs_fs_destroy_inode,
- .dirty_inode = xfs_fs_dirty_inode,
.drop_inode = xfs_fs_drop_inode,
.evict_inode = xfs_fs_evict_inode,
.put_super = xfs_fs_put_super,
--
2.47.3
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH 11/11] xfs: enable non-blocking timestamp updates
2026-01-08 14:19 re-enable IOCB_NOWAIT writes to files v6 Christoph Hellwig
` (9 preceding siblings ...)
2026-01-08 14:19 ` [PATCH 10/11] xfs: implement ->sync_lazytime Christoph Hellwig
@ 2026-01-08 14:19 ` Christoph Hellwig
2026-01-12 13:02 ` re-enable IOCB_NOWAIT writes to files v6 Christian Brauner
11 siblings, 0 replies; 20+ messages in thread
From: Christoph Hellwig @ 2026-01-08 14:19 UTC (permalink / raw)
To: Christian Brauner
Cc: Al Viro, David Sterba, Jan Kara, Mike Marshall,
Martin Brandenburg, Carlos Maiolino, Stefan Roesch, Jeff Layton,
OGAWA Hirofumi, Trond Myklebust, Anna Schumaker, linux-kernel,
linux-btrfs, linux-fsdevel, gfs2, io-uring, devel, linux-unionfs,
linux-mtd, linux-xfs, linux-nfs, Chaitanya Kulkarni
The lazytime path using the generic helpers can never block in XFS
because there is no ->dirty_inode method that could block. Allow
non-blocking timestamp updates for this case by replacing
generic_update_time with the open coded version without the S_NOWAIT
check.
Fixes: 66fa3cedf16a ("fs: Add async write file modification handling.")
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Chaitanya Kulkarni <kch@nvidia.com>
Reviewed-by: Jeff Layton <jlayton@kernel.org>
---
fs/xfs/xfs_iops.c | 18 ++++++++++++------
1 file changed, 12 insertions(+), 6 deletions(-)
diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c
index 338f3113f674..1cdd8a360510 100644
--- a/fs/xfs/xfs_iops.c
+++ b/fs/xfs/xfs_iops.c
@@ -1195,16 +1195,22 @@ xfs_vn_update_time(
trace_xfs_update_time(ip);
- if (flags & IOCB_NOWAIT)
- return -EAGAIN;
-
if (inode->i_sb->s_flags & SB_LAZYTIME) {
- if (type == FS_UPD_ATIME ||
- !inode_maybe_inc_iversion(inode, false))
- return generic_update_time(inode, type, flags);
+ int dirty;
+
+ dirty = inode_update_time(inode, type, flags);
+ if (dirty <= 0)
+ return dirty;
+ if (dirty == I_DIRTY_TIME) {
+ __mark_inode_dirty(inode, I_DIRTY_TIME);
+ return 0;
+ }
/* Capture the iversion update that just occurred */
log_flags |= XFS_ILOG_CORE;
+ } else {
+ if (flags & IOCB_NOWAIT)
+ return -EAGAIN;
}
error = xfs_trans_alloc(mp, &M_RES(mp)->tr_fsyncts, 0, 0, 0, &tp);
--
2.47.3
^ permalink raw reply related [flat|nested] 20+ messages in thread
* Re: [PATCH 05/11] fs: refactor ->update_time handling
2026-01-08 14:19 ` [PATCH 05/11] fs: refactor ->update_time handling Christoph Hellwig
@ 2026-01-08 15:20 ` Jan Kara
0 siblings, 0 replies; 20+ messages in thread
From: Jan Kara @ 2026-01-08 15:20 UTC (permalink / raw)
To: Christoph Hellwig
Cc: Christian Brauner, Al Viro, David Sterba, Jan Kara, Mike Marshall,
Martin Brandenburg, Carlos Maiolino, Stefan Roesch, Jeff Layton,
OGAWA Hirofumi, Trond Myklebust, Anna Schumaker, linux-kernel,
linux-btrfs, linux-fsdevel, gfs2, io-uring, devel, linux-unionfs,
linux-mtd, linux-xfs, linux-nfs
On Thu 08-01-26 15:19:05, Christoph Hellwig wrote:
> Pass the type of update (atime vs c/mtime plus version) as an enum
> instead of a set of flags that caused all kinds of confusion.
> Because inode_update_timestamps now can't return a modified version
> of those flags, return the I_DIRTY_* flags needed to persist the
> update, which is what the main caller in generic_update_time wants
> anyway, and which is suitable for the other callers that only want
> to know if an update happened.
>
> The whole update_time path keeps the flags argument, which will be used
> to support non-blocking updates soon even if it is unused, and (the
> slightly renamed) inode_update_time also gains the possibility to return
> a negative errno to support this.
>
> Signed-off-by: Christoph Hellwig <hch@lst.de>
Looks good. Feel free to add:
Reviewed-by: Jan Kara <jack@suse.cz>
Honza
> ---
> Documentation/filesystems/locking.rst | 3 +-
> Documentation/filesystems/vfs.rst | 3 +-
> fs/bad_inode.c | 3 +-
> fs/btrfs/inode.c | 11 ++-
> fs/fat/fat.h | 3 +-
> fs/fat/misc.c | 26 ++---
> fs/gfs2/inode.c | 5 +-
> fs/inode.c | 134 ++++++++++++++------------
> fs/nfs/inode.c | 10 +-
> fs/orangefs/inode.c | 28 +++---
> fs/orangefs/orangefs-kernel.h | 3 +-
> fs/overlayfs/inode.c | 5 +-
> fs/overlayfs/overlayfs.h | 3 +-
> fs/ubifs/file.c | 21 ++--
> fs/ubifs/ubifs.h | 3 +-
> fs/xfs/xfs_iops.c | 22 ++---
> include/linux/fs.h | 28 ++++--
> 17 files changed, 157 insertions(+), 154 deletions(-)
>
> diff --git a/Documentation/filesystems/locking.rst b/Documentation/filesystems/locking.rst
> index 77704fde9845..37a4a7fa8094 100644
> --- a/Documentation/filesystems/locking.rst
> +++ b/Documentation/filesystems/locking.rst
> @@ -80,7 +80,8 @@ prototypes::
> int (*getattr) (struct mnt_idmap *, const struct path *, struct kstat *, u32, unsigned int);
> ssize_t (*listxattr) (struct dentry *, char *, size_t);
> int (*fiemap)(struct inode *, struct fiemap_extent_info *, u64 start, u64 len);
> - void (*update_time)(struct inode *, struct timespec *, int);
> + void (*update_time)(struct inode *inode, enum fs_update_time type,
> + int flags);
> int (*atomic_open)(struct inode *, struct dentry *,
> struct file *, unsigned open_flag,
> umode_t create_mode);
> diff --git a/Documentation/filesystems/vfs.rst b/Documentation/filesystems/vfs.rst
> index 670ba66b60e4..51aa9db64784 100644
> --- a/Documentation/filesystems/vfs.rst
> +++ b/Documentation/filesystems/vfs.rst
> @@ -485,7 +485,8 @@ As of kernel 2.6.22, the following members are defined:
> int (*setattr) (struct mnt_idmap *, struct dentry *, struct iattr *);
> int (*getattr) (struct mnt_idmap *, const struct path *, struct kstat *, u32, unsigned int);
> ssize_t (*listxattr) (struct dentry *, char *, size_t);
> - void (*update_time)(struct inode *, struct timespec *, int);
> + void (*update_time)(struct inode *inode, enum fs_update_time type,
> + int flags);
> int (*atomic_open)(struct inode *, struct dentry *, struct file *,
> unsigned open_flag, umode_t create_mode);
> int (*tmpfile) (struct mnt_idmap *, struct inode *, struct file *, umode_t);
> diff --git a/fs/bad_inode.c b/fs/bad_inode.c
> index 0ef9bcb744dd..acf8613f5e36 100644
> --- a/fs/bad_inode.c
> +++ b/fs/bad_inode.c
> @@ -133,7 +133,8 @@ static int bad_inode_fiemap(struct inode *inode,
> return -EIO;
> }
>
> -static int bad_inode_update_time(struct inode *inode, int flags)
> +static int bad_inode_update_time(struct inode *inode, enum fs_update_time type,
> + unsigned int flags)
> {
> return -EIO;
> }
> diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
> index 599c03a1c573..23fc38de9be5 100644
> --- a/fs/btrfs/inode.c
> +++ b/fs/btrfs/inode.c
> @@ -6354,16 +6354,19 @@ static int btrfs_dirty_inode(struct btrfs_inode *inode)
> * We need our own ->update_time so that we can return error on ENOSPC for
> * updating the inode in the case of file write and mmap writes.
> */
> -static int btrfs_update_time(struct inode *inode, int flags)
> +static int btrfs_update_time(struct inode *inode, enum fs_update_time type,
> + unsigned int flags)
> {
> struct btrfs_root *root = BTRFS_I(inode)->root;
> - bool dirty;
> + int dirty;
>
> if (btrfs_root_readonly(root))
> return -EROFS;
>
> - dirty = inode_update_timestamps(inode, flags);
> - return dirty ? btrfs_dirty_inode(BTRFS_I(inode)) : 0;
> + dirty = inode_update_time(inode, type, flags);
> + if (dirty <= 0)
> + return dirty;
> + return btrfs_dirty_inode(BTRFS_I(inode));
> }
>
> /*
> diff --git a/fs/fat/fat.h b/fs/fat/fat.h
> index 767b566b1cab..0d269dba897b 100644
> --- a/fs/fat/fat.h
> +++ b/fs/fat/fat.h
> @@ -472,7 +472,8 @@ extern struct timespec64 fat_truncate_atime(const struct msdos_sb_info *sbi,
> #define FAT_UPDATE_CMTIME (1u << 1)
> void fat_truncate_time(struct inode *inode, struct timespec64 *now,
> unsigned int flags);
> -extern int fat_update_time(struct inode *inode, int flags);
> +int fat_update_time(struct inode *inode, enum fs_update_time type,
> + unsigned int flags);
> extern int fat_sync_bhs(struct buffer_head **bhs, int nr_bhs);
>
> int fat_cache_init(void);
> diff --git a/fs/fat/misc.c b/fs/fat/misc.c
> index 78c620e9b3fd..b154a5162764 100644
> --- a/fs/fat/misc.c
> +++ b/fs/fat/misc.c
> @@ -332,28 +332,14 @@ void fat_truncate_time(struct inode *inode, struct timespec64 *now,
> }
> EXPORT_SYMBOL_GPL(fat_truncate_time);
>
> -int fat_update_time(struct inode *inode, int flags)
> +int fat_update_time(struct inode *inode, enum fs_update_time type,
> + unsigned int flags)
> {
> - unsigned int fat_flags = 0;
> - int dirty_flags = 0;
> -
> - if (inode->i_ino == MSDOS_ROOT_INO)
> - return 0;
> -
> - if (flags & S_ATIME)
> - fat_flags |= FAT_UPDATE_ATIME;
> - if (flags & (S_CTIME | S_MTIME))
> - fat_flags |= FAT_UPDATE_CMTIME;
> -
> - if (fat_flags) {
> - fat_truncate_time(inode, NULL, flags);
> - if (inode->i_sb->s_flags & SB_LAZYTIME)
> - dirty_flags |= I_DIRTY_TIME;
> - else
> - dirty_flags |= I_DIRTY_SYNC;
> + if (inode->i_ino != MSDOS_ROOT_INO) {
> + fat_truncate_time(inode, NULL, type == FS_UPD_ATIME ?
> + FAT_UPDATE_ATIME : FAT_UPDATE_CMTIME);
> + __mark_inode_dirty(inode, inode_time_dirty_flag(inode));
> }
> -
> - __mark_inode_dirty(inode, dirty_flags);
> return 0;
> }
> EXPORT_SYMBOL_GPL(fat_update_time);
> diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c
> index e08eb419347c..4ef39ff6889d 100644
> --- a/fs/gfs2/inode.c
> +++ b/fs/gfs2/inode.c
> @@ -2242,7 +2242,8 @@ loff_t gfs2_seek_hole(struct file *file, loff_t offset)
> return vfs_setpos(file, ret, inode->i_sb->s_maxbytes);
> }
>
> -static int gfs2_update_time(struct inode *inode, int flags)
> +static int gfs2_update_time(struct inode *inode, enum fs_update_time type,
> + unsigned int flags)
> {
> struct gfs2_inode *ip = GFS2_I(inode);
> struct gfs2_glock *gl = ip->i_gl;
> @@ -2257,7 +2258,7 @@ static int gfs2_update_time(struct inode *inode, int flags)
> if (error)
> return error;
> }
> - return generic_update_time(inode, flags);
> + return generic_update_time(inode, type, flags);
> }
>
> static const struct inode_operations gfs2_file_iops = {
> diff --git a/fs/inode.c b/fs/inode.c
> index 7eb28dd45a5a..a0dd11a05473 100644
> --- a/fs/inode.c
> +++ b/fs/inode.c
> @@ -2081,78 +2081,84 @@ static bool relatime_need_update(struct vfsmount *mnt, struct inode *inode,
> return false;
> }
>
> +static int inode_update_atime(struct inode *inode)
> +{
> + struct timespec64 atime = inode_get_atime(inode);
> + struct timespec64 now = current_time(inode);
> +
> + if (timespec64_equal(&now, &atime))
> + return 0;
> +
> + inode_set_atime_to_ts(inode, now);
> + return inode_time_dirty_flag(inode);
> +}
> +
> +static int inode_update_cmtime(struct inode *inode)
> +{
> + struct timespec64 ctime = inode_get_ctime(inode);
> + struct timespec64 mtime = inode_get_mtime(inode);
> + struct timespec64 now = inode_set_ctime_current(inode);
> + unsigned int dirty = 0;
> + bool mtime_changed;
> +
> + mtime_changed = !timespec64_equal(&now, &mtime);
> + if (mtime_changed || !timespec64_equal(&now, &ctime))
> + dirty = inode_time_dirty_flag(inode);
> + if (mtime_changed)
> + inode_set_mtime_to_ts(inode, now);
> +
> + if (IS_I_VERSION(inode) && inode_maybe_inc_iversion(inode, !!dirty))
> + dirty |= I_DIRTY_SYNC;
> +
> + return dirty;
> +}
> +
> /**
> - * inode_update_timestamps - update the timestamps on the inode
> + * inode_update_time - update either atime or c/mtime and i_version on the inode
> * @inode: inode to be updated
> - * @flags: S_* flags that needed to be updated
> + * @type: timestamp to be updated
> + * @flags: flags for the update
> *
> - * The update_time function is called when an inode's timestamps need to be
> - * updated for a read or write operation. This function handles updating the
> - * actual timestamps. It's up to the caller to ensure that the inode is marked
> - * dirty appropriately.
> + * Update either atime or c/mtime and version in a inode if needed for a file
> + * access or modification. It is up to the caller to mark the inode dirty
> + * appropriately.
> *
> - * In the case where any of S_MTIME, S_CTIME, or S_VERSION need to be updated,
> - * attempt to update all three of them. S_ATIME updates can be handled
> - * independently of the rest.
> - *
> - * Returns a set of S_* flags indicating which values changed.
> + * Returns the positive I_DIRTY_* flags for __mark_inode_dirty() if the inode
> + * needs to be marked dirty, 0 if it did not, or a negative errno if an error
> + * happened.
> */
> -int inode_update_timestamps(struct inode *inode, int flags)
> +int inode_update_time(struct inode *inode, enum fs_update_time type,
> + unsigned int flags)
> {
> - int updated = 0;
> - struct timespec64 now;
> -
> - if (flags & (S_MTIME|S_CTIME|S_VERSION)) {
> - struct timespec64 ctime = inode_get_ctime(inode);
> - struct timespec64 mtime = inode_get_mtime(inode);
> -
> - now = inode_set_ctime_current(inode);
> - if (!timespec64_equal(&now, &ctime))
> - updated |= S_CTIME;
> - if (!timespec64_equal(&now, &mtime)) {
> - inode_set_mtime_to_ts(inode, now);
> - updated |= S_MTIME;
> - }
> - if (IS_I_VERSION(inode) && inode_maybe_inc_iversion(inode, updated))
> - updated |= S_VERSION;
> - } else {
> - now = current_time(inode);
> - }
> -
> - if (flags & S_ATIME) {
> - struct timespec64 atime = inode_get_atime(inode);
> -
> - if (!timespec64_equal(&now, &atime)) {
> - inode_set_atime_to_ts(inode, now);
> - updated |= S_ATIME;
> - }
> + switch (type) {
> + case FS_UPD_ATIME:
> + return inode_update_atime(inode);
> + case FS_UPD_CMTIME:
> + return inode_update_cmtime(inode);
> + default:
> + WARN_ON_ONCE(1);
> + return -EIO;
> }
> - return updated;
> }
> -EXPORT_SYMBOL(inode_update_timestamps);
> +EXPORT_SYMBOL(inode_update_time);
>
> /**
> * generic_update_time - update the timestamps on the inode
> * @inode: inode to be updated
> - * @flags: S_* flags that needed to be updated
> - *
> - * The update_time function is called when an inode's timestamps need to be
> - * updated for a read or write operation. In the case where any of S_MTIME, S_CTIME,
> - * or S_VERSION need to be updated we attempt to update all three of them. S_ATIME
> - * updates can be handled done independently of the rest.
> + * @type: timestamp to be updated
> + * @flags: flags for the update
> *
> * Returns a negative error value on error, else 0.
> */
> -int generic_update_time(struct inode *inode, int flags)
> +int generic_update_time(struct inode *inode, enum fs_update_time type,
> + unsigned int flags)
> {
> - int updated = inode_update_timestamps(inode, flags);
> - int dirty_flags = 0;
> + int dirty;
>
> - if (updated & (S_ATIME|S_MTIME|S_CTIME))
> - dirty_flags = inode->i_sb->s_flags & SB_LAZYTIME ? I_DIRTY_TIME : I_DIRTY_SYNC;
> - if (updated & S_VERSION)
> - dirty_flags |= I_DIRTY_SYNC;
> - __mark_inode_dirty(inode, dirty_flags);
> + dirty = inode_update_time(inode, type, flags);
> + if (dirty <= 0)
> + return dirty;
> + __mark_inode_dirty(inode, dirty);
> return 0;
> }
> EXPORT_SYMBOL(generic_update_time);
> @@ -2225,9 +2231,9 @@ void touch_atime(const struct path *path)
> * of the fs read only, e.g. subvolumes in Btrfs.
> */
> if (inode->i_op->update_time)
> - inode->i_op->update_time(inode, S_ATIME);
> + inode->i_op->update_time(inode, FS_UPD_ATIME, 0);
> else
> - generic_update_time(inode, S_ATIME);
> + generic_update_time(inode, FS_UPD_ATIME, 0);
> mnt_put_write_access(mnt);
> skip_update:
> sb_end_write(inode->i_sb);
> @@ -2354,7 +2360,7 @@ static int file_update_time_flags(struct file *file, unsigned int flags)
> {
> struct inode *inode = file_inode(file);
> struct timespec64 now, ts;
> - int sync_mode = 0;
> + bool need_update = false;
> int ret = 0;
>
> /* First try to exhaust all avenues to not sync */
> @@ -2367,14 +2373,14 @@ static int file_update_time_flags(struct file *file, unsigned int flags)
>
> ts = inode_get_mtime(inode);
> if (!timespec64_equal(&ts, &now))
> - sync_mode |= S_MTIME;
> + need_update = true;
> ts = inode_get_ctime(inode);
> if (!timespec64_equal(&ts, &now))
> - sync_mode |= S_CTIME;
> + need_update = true;
> if (IS_I_VERSION(inode) && inode_iversion_need_inc(inode))
> - sync_mode |= S_VERSION;
> + need_update = true;
>
> - if (!sync_mode)
> + if (!need_update)
> return 0;
>
> if (flags & IOCB_NOWAIT)
> @@ -2383,9 +2389,9 @@ static int file_update_time_flags(struct file *file, unsigned int flags)
> if (mnt_get_write_access_file(file))
> return 0;
> if (inode->i_op->update_time)
> - ret = inode->i_op->update_time(inode, sync_mode);
> + ret = inode->i_op->update_time(inode, FS_UPD_CMTIME, 0);
> else
> - ret = generic_update_time(inode, sync_mode);
> + ret = generic_update_time(inode, FS_UPD_CMTIME, 0);
> mnt_put_write_access_file(file);
> return ret;
> }
> diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c
> index 3be8ba7b98c5..cd6d7c6e1237 100644
> --- a/fs/nfs/inode.c
> +++ b/fs/nfs/inode.c
> @@ -649,15 +649,15 @@ static void nfs_set_timestamps_to_ts(struct inode *inode, struct iattr *attr)
> struct timespec64 ctime = inode_get_ctime(inode);
> struct timespec64 mtime = inode_get_mtime(inode);
> struct timespec64 now;
> - int updated = 0;
> + bool updated = false;
>
> now = inode_set_ctime_current(inode);
> if (!timespec64_equal(&now, &ctime))
> - updated |= S_CTIME;
> + updated = true;
>
> inode_set_mtime_to_ts(inode, attr->ia_mtime);
> if (!timespec64_equal(&now, &mtime))
> - updated |= S_MTIME;
> + updated = true;
>
> inode_maybe_inc_iversion(inode, updated);
> cache_flags |= NFS_INO_INVALID_CTIME | NFS_INO_INVALID_MTIME;
> @@ -671,13 +671,13 @@ static void nfs_set_timestamps_to_ts(struct inode *inode, struct iattr *attr)
>
> static void nfs_update_atime(struct inode *inode)
> {
> - inode_update_timestamps(inode, S_ATIME);
> + inode_update_time(inode, FS_UPD_ATIME, 0);
> NFS_I(inode)->cache_validity &= ~NFS_INO_INVALID_ATIME;
> }
>
> static void nfs_update_mtime(struct inode *inode)
> {
> - inode_update_timestamps(inode, S_MTIME | S_CTIME);
> + inode_update_time(inode, FS_UPD_CMTIME, 0);
> NFS_I(inode)->cache_validity &=
> ~(NFS_INO_INVALID_CTIME | NFS_INO_INVALID_MTIME);
> }
> diff --git a/fs/orangefs/inode.c b/fs/orangefs/inode.c
> index d7275990ffa4..eab16afb5b8a 100644
> --- a/fs/orangefs/inode.c
> +++ b/fs/orangefs/inode.c
> @@ -872,22 +872,24 @@ int orangefs_permission(struct mnt_idmap *idmap,
> return generic_permission(&nop_mnt_idmap, inode, mask);
> }
>
> -int orangefs_update_time(struct inode *inode, int flags)
> +int orangefs_update_time(struct inode *inode, enum fs_update_time type,
> + unsigned int flags)
> {
> - struct iattr iattr;
> + struct iattr iattr = { };
> + int dirty;
>
> - gossip_debug(GOSSIP_INODE_DEBUG, "orangefs_update_time: %pU\n",
> - get_khandle_from_ino(inode));
> -
> - flags = inode_update_timestamps(inode, flags);
> + switch (type) {
> + case FS_UPD_ATIME:
> + iattr.ia_valid = ATTR_ATIME;
> + break;
> + case FS_UPD_CMTIME:
> + iattr.ia_valid = ATTR_CTIME | ATTR_MTIME;
> + break;
> + }
>
> - memset(&iattr, 0, sizeof iattr);
> - if (flags & S_ATIME)
> - iattr.ia_valid |= ATTR_ATIME;
> - if (flags & S_CTIME)
> - iattr.ia_valid |= ATTR_CTIME;
> - if (flags & S_MTIME)
> - iattr.ia_valid |= ATTR_MTIME;
> + dirty = inode_update_time(inode, type, flags);
> + if (dirty <= 0)
> + return dirty;
> return __orangefs_setattr(inode, &iattr);
> }
>
> diff --git a/fs/orangefs/orangefs-kernel.h b/fs/orangefs/orangefs-kernel.h
> index 29c6da43e396..1451fc2c1917 100644
> --- a/fs/orangefs/orangefs-kernel.h
> +++ b/fs/orangefs/orangefs-kernel.h
> @@ -360,7 +360,8 @@ int orangefs_getattr(struct mnt_idmap *idmap, const struct path *path,
> int orangefs_permission(struct mnt_idmap *idmap,
> struct inode *inode, int mask);
>
> -int orangefs_update_time(struct inode *, int);
> +int orangefs_update_time(struct inode *inode, enum fs_update_time type,
> + unsigned int flags);
>
> /*
> * defined in xattr.c
> diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c
> index bdbf86b56a9b..c0ce3519e4af 100644
> --- a/fs/overlayfs/inode.c
> +++ b/fs/overlayfs/inode.c
> @@ -555,9 +555,10 @@ int ovl_set_acl(struct mnt_idmap *idmap, struct dentry *dentry,
> }
> #endif
>
> -int ovl_update_time(struct inode *inode, int flags)
> +int ovl_update_time(struct inode *inode, enum fs_update_time type,
> + unsigned int flags)
> {
> - if (flags & S_ATIME) {
> + if (type == FS_UPD_ATIME) {
> struct ovl_fs *ofs = OVL_FS(inode->i_sb);
> struct path upperpath = {
> .mnt = ovl_upper_mnt(ofs),
> diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h
> index f9ac9bdde830..315882a360ce 100644
> --- a/fs/overlayfs/overlayfs.h
> +++ b/fs/overlayfs/overlayfs.h
> @@ -820,7 +820,8 @@ static inline struct posix_acl *ovl_get_acl_path(const struct path *path,
> }
> #endif
>
> -int ovl_update_time(struct inode *inode, int flags);
> +int ovl_update_time(struct inode *inode, enum fs_update_time type,
> + unsigned int flags);
> bool ovl_is_private_xattr(struct super_block *sb, const char *name);
>
> struct ovl_inode_params {
> diff --git a/fs/ubifs/file.c b/fs/ubifs/file.c
> index ec1bb9f43acc..0cc44ad142de 100644
> --- a/fs/ubifs/file.c
> +++ b/fs/ubifs/file.c
> @@ -1361,17 +1361,8 @@ static inline int mctime_update_needed(const struct inode *inode,
> return 0;
> }
>
> -/**
> - * ubifs_update_time - update time of inode.
> - * @inode: inode to update
> - * @flags: time updating control flag determines updating
> - * which time fields of @inode
> - *
> - * This function updates time of the inode.
> - *
> - * Returns: %0 for success or a negative error code otherwise.
> - */
> -int ubifs_update_time(struct inode *inode, int flags)
> +int ubifs_update_time(struct inode *inode, enum fs_update_time type,
> + unsigned int flags)
> {
> struct ubifs_inode *ui = ubifs_inode(inode);
> struct ubifs_info *c = inode->i_sb->s_fs_info;
> @@ -1379,15 +1370,19 @@ int ubifs_update_time(struct inode *inode, int flags)
> .dirtied_ino_d = ALIGN(ui->data_len, 8) };
> int err, release;
>
> + /* ubifs sets S_NOCMTIME on all inodes, this should not happen. */
> + if (WARN_ON_ONCE(type != FS_UPD_ATIME))
> + return -EIO;
> +
> if (!IS_ENABLED(CONFIG_UBIFS_ATIME_SUPPORT))
> - return generic_update_time(inode, flags);
> + return generic_update_time(inode, type, flags);
>
> err = ubifs_budget_space(c, &req);
> if (err)
> return err;
>
> mutex_lock(&ui->ui_mutex);
> - inode_update_timestamps(inode, flags);
> + inode_update_time(inode, type, flags);
> release = ui->dirty;
> __mark_inode_dirty(inode, I_DIRTY_SYNC);
> mutex_unlock(&ui->ui_mutex);
> diff --git a/fs/ubifs/ubifs.h b/fs/ubifs/ubifs.h
> index 118392aa9f2a..b62a154c7bd4 100644
> --- a/fs/ubifs/ubifs.h
> +++ b/fs/ubifs/ubifs.h
> @@ -2018,7 +2018,8 @@ int ubifs_calc_dark(const struct ubifs_info *c, int spc);
> int ubifs_fsync(struct file *file, loff_t start, loff_t end, int datasync);
> int ubifs_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
> struct iattr *attr);
> -int ubifs_update_time(struct inode *inode, int flags);
> +int ubifs_update_time(struct inode *inode, enum fs_update_time type,
> + unsigned int flags);
>
> /* dir.c */
> struct inode *ubifs_new_inode(struct ubifs_info *c, struct inode *dir,
> diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c
> index 9dedb54e3cb0..d9eae1af14a8 100644
> --- a/fs/xfs/xfs_iops.c
> +++ b/fs/xfs/xfs_iops.c
> @@ -1184,21 +1184,21 @@ xfs_vn_setattr(
> STATIC int
> xfs_vn_update_time(
> struct inode *inode,
> - int flags)
> + enum fs_update_time type,
> + unsigned int flags)
> {
> struct xfs_inode *ip = XFS_I(inode);
> struct xfs_mount *mp = ip->i_mount;
> int log_flags = XFS_ILOG_TIMESTAMP;
> struct xfs_trans *tp;
> int error;
> - struct timespec64 now;
>
> trace_xfs_update_time(ip);
>
> if (inode->i_sb->s_flags & SB_LAZYTIME) {
> - if (!((flags & S_VERSION) &&
> - inode_maybe_inc_iversion(inode, false)))
> - return generic_update_time(inode, flags);
> + if (type == FS_UPD_ATIME ||
> + !inode_maybe_inc_iversion(inode, false))
> + return generic_update_time(inode, type, flags);
>
> /* Capture the iversion update that just occurred */
> log_flags |= XFS_ILOG_CORE;
> @@ -1209,16 +1209,10 @@ xfs_vn_update_time(
> return error;
>
> xfs_ilock(ip, XFS_ILOCK_EXCL);
> - if (flags & (S_CTIME|S_MTIME))
> - now = inode_set_ctime_current(inode);
> + if (type == FS_UPD_ATIME)
> + inode_set_atime_to_ts(inode, current_time(inode));
> else
> - now = current_time(inode);
> -
> - if (flags & S_MTIME)
> - inode_set_mtime_to_ts(inode, now);
> - if (flags & S_ATIME)
> - inode_set_atime_to_ts(inode, now);
> -
> + inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
> xfs_trans_ijoin(tp, ip, XFS_ILOCK_EXCL);
> xfs_trans_log_inode(tp, ip, log_flags);
> return xfs_trans_commit(tp);
> diff --git a/include/linux/fs.h b/include/linux/fs.h
> index fccb0a38cb74..35b3e6c6b084 100644
> --- a/include/linux/fs.h
> +++ b/include/linux/fs.h
> @@ -1717,6 +1717,13 @@ static inline struct timespec64 inode_set_ctime(struct inode *inode,
>
> struct timespec64 simple_inode_init_ts(struct inode *inode);
>
> +static inline int inode_time_dirty_flag(struct inode *inode)
> +{
> + if (inode->i_sb->s_flags & SB_LAZYTIME)
> + return I_DIRTY_TIME;
> + return I_DIRTY_SYNC;
> +}
> +
> /*
> * Snapshotting support.
> */
> @@ -1983,6 +1990,11 @@ int wrap_directory_iterator(struct file *, struct dir_context *,
> static int shared_##x(struct file *file , struct dir_context *ctx) \
> { return wrap_directory_iterator(file, ctx, x); }
>
> +enum fs_update_time {
> + FS_UPD_ATIME,
> + FS_UPD_CMTIME,
> +};
> +
> struct inode_operations {
> struct dentry * (*lookup) (struct inode *,struct dentry *, unsigned int);
> const char * (*get_link) (struct dentry *, struct inode *, struct delayed_call *);
> @@ -2010,7 +2022,8 @@ struct inode_operations {
> ssize_t (*listxattr) (struct dentry *, char *, size_t);
> int (*fiemap)(struct inode *, struct fiemap_extent_info *, u64 start,
> u64 len);
> - int (*update_time)(struct inode *, int);
> + int (*update_time)(struct inode *inode, enum fs_update_time type,
> + unsigned int flags);
> int (*atomic_open)(struct inode *, struct dentry *,
> struct file *, unsigned open_flag,
> umode_t create_mode);
> @@ -2237,13 +2250,6 @@ static inline void inode_dec_link_count(struct inode *inode)
> mark_inode_dirty(inode);
> }
>
> -enum file_time_flags {
> - S_ATIME = 1,
> - S_MTIME = 2,
> - S_CTIME = 4,
> - S_VERSION = 8,
> -};
> -
> extern bool atime_needs_update(const struct path *, struct inode *);
> extern void touch_atime(const struct path *);
>
> @@ -2398,8 +2404,10 @@ static inline void super_set_sysfs_name_generic(struct super_block *sb, const ch
> extern void ihold(struct inode * inode);
> extern void iput(struct inode *);
> void iput_not_last(struct inode *);
> -int inode_update_timestamps(struct inode *inode, int flags);
> -int generic_update_time(struct inode *inode, int flags);
> +int inode_update_time(struct inode *inode, enum fs_update_time type,
> + unsigned int flags);
> +int generic_update_time(struct inode *inode, enum fs_update_time type,
> + unsigned int flags);
>
> /* /sys/fs */
> extern struct kobject *fs_kobj;
> --
> 2.47.3
>
--
Jan Kara <jack@suse.com>
SUSE Labs, CR
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 07/11] fs: add a ->sync_lazytime method
2026-01-08 14:19 ` [PATCH 07/11] fs: add a ->sync_lazytime method Christoph Hellwig
@ 2026-01-08 15:24 ` Jan Kara
0 siblings, 0 replies; 20+ messages in thread
From: Jan Kara @ 2026-01-08 15:24 UTC (permalink / raw)
To: Christoph Hellwig
Cc: Christian Brauner, Al Viro, David Sterba, Jan Kara, Mike Marshall,
Martin Brandenburg, Carlos Maiolino, Stefan Roesch, Jeff Layton,
OGAWA Hirofumi, Trond Myklebust, Anna Schumaker, linux-kernel,
linux-btrfs, linux-fsdevel, gfs2, io-uring, devel, linux-unionfs,
linux-mtd, linux-xfs, linux-nfs, Chaitanya Kulkarni
On Thu 08-01-26 15:19:07, Christoph Hellwig wrote:
> Allow the file system to explicitly implement lazytime syncing instead
> of pigging back on generic inode dirtying. This allows to simplify
> the XFS implementation and prepares for non-blocking lazytime timestamp
> updates.
>
> Signed-off-by: Christoph Hellwig <hch@lst.de>
> Reviewed-by: Chaitanya Kulkarni <kch@nvidia.com>
> Reviewed-by: Jeff Layton <jlayton@kernel.org>
Looks good. Feel free to add:
Reviewed-by: Jan Kara <jack@suse.cz>
Honza
> ---
> Documentation/filesystems/locking.rst | 2 ++
> Documentation/filesystems/vfs.rst | 6 ++++++
> fs/fs-writeback.c | 13 +++++++++++--
> include/linux/fs.h | 1 +
> 4 files changed, 20 insertions(+), 2 deletions(-)
>
> diff --git a/Documentation/filesystems/locking.rst b/Documentation/filesystems/locking.rst
> index 37a4a7fa8094..0312fba6d73b 100644
> --- a/Documentation/filesystems/locking.rst
> +++ b/Documentation/filesystems/locking.rst
> @@ -82,6 +82,7 @@ prototypes::
> int (*fiemap)(struct inode *, struct fiemap_extent_info *, u64 start, u64 len);
> void (*update_time)(struct inode *inode, enum fs_update_time type,
> int flags);
> + void (*sync_lazytime)(struct inode *inode);
> int (*atomic_open)(struct inode *, struct dentry *,
> struct file *, unsigned open_flag,
> umode_t create_mode);
> @@ -118,6 +119,7 @@ getattr: no
> listxattr: no
> fiemap: no
> update_time: no
> +sync_lazytime: no
> atomic_open: shared (exclusive if O_CREAT is set in open flags)
> tmpfile: no
> fileattr_get: no or exclusive
> diff --git a/Documentation/filesystems/vfs.rst b/Documentation/filesystems/vfs.rst
> index 51aa9db64784..d8cb181f69f8 100644
> --- a/Documentation/filesystems/vfs.rst
> +++ b/Documentation/filesystems/vfs.rst
> @@ -487,6 +487,7 @@ As of kernel 2.6.22, the following members are defined:
> ssize_t (*listxattr) (struct dentry *, char *, size_t);
> void (*update_time)(struct inode *inode, enum fs_update_time type,
> int flags);
> + void (*sync_lazytime)(struct inode *inode);
> int (*atomic_open)(struct inode *, struct dentry *, struct file *,
> unsigned open_flag, umode_t create_mode);
> int (*tmpfile) (struct mnt_idmap *, struct inode *, struct file *, umode_t);
> @@ -643,6 +644,11 @@ otherwise noted.
> an inode. If this is not defined the VFS will update the inode
> itself and call mark_inode_dirty_sync.
>
> +``sync_lazytime``:
> + called by the writeback code to update the lazy time stamps to
> + regular time stamp updates that get syncing into the on-disk
> + inode.
> +
> ``atomic_open``
> called on the last component of an open. Using this optional
> method the filesystem can look up, possibly create and open the
> diff --git a/fs/fs-writeback.c b/fs/fs-writeback.c
> index 3d68b757136c..62658be2578b 100644
> --- a/fs/fs-writeback.c
> +++ b/fs/fs-writeback.c
> @@ -1717,7 +1717,10 @@ bool sync_lazytime(struct inode *inode)
> return false;
>
> trace_writeback_lazytime(inode);
> - mark_inode_dirty_sync(inode);
> + if (inode->i_op->sync_lazytime)
> + inode->i_op->sync_lazytime(inode);
> + else
> + mark_inode_dirty_sync(inode);
> return true;
> }
>
> @@ -2569,6 +2572,8 @@ void __mark_inode_dirty(struct inode *inode, int flags)
> trace_writeback_mark_inode_dirty(inode, flags);
>
> if (flags & I_DIRTY_INODE) {
> + bool was_dirty_time = false;
> +
> /*
> * Inode timestamp update will piggback on this dirtying.
> * We tell ->dirty_inode callback that timestamps need to
> @@ -2579,6 +2584,7 @@ void __mark_inode_dirty(struct inode *inode, int flags)
> if (inode_state_read(inode) & I_DIRTY_TIME) {
> inode_state_clear(inode, I_DIRTY_TIME);
> flags |= I_DIRTY_TIME;
> + was_dirty_time = true;
> }
> spin_unlock(&inode->i_lock);
> }
> @@ -2591,9 +2597,12 @@ void __mark_inode_dirty(struct inode *inode, int flags)
> * for just I_DIRTY_PAGES or I_DIRTY_TIME.
> */
> trace_writeback_dirty_inode_start(inode, flags);
> - if (sb->s_op->dirty_inode)
> + if (sb->s_op->dirty_inode) {
> sb->s_op->dirty_inode(inode,
> flags & (I_DIRTY_INODE | I_DIRTY_TIME));
> + } else if (was_dirty_time && inode->i_op->sync_lazytime) {
> + inode->i_op->sync_lazytime(inode);
> + }
> trace_writeback_dirty_inode(inode, flags);
>
> /* I_DIRTY_INODE supersedes I_DIRTY_TIME. */
> diff --git a/include/linux/fs.h b/include/linux/fs.h
> index 35b3e6c6b084..7837db1ba1d2 100644
> --- a/include/linux/fs.h
> +++ b/include/linux/fs.h
> @@ -2024,6 +2024,7 @@ struct inode_operations {
> u64 len);
> int (*update_time)(struct inode *inode, enum fs_update_time type,
> unsigned int flags);
> + void (*sync_lazytime)(struct inode *inode);
> int (*atomic_open)(struct inode *, struct dentry *,
> struct file *, unsigned open_flag,
> umode_t create_mode);
> --
> 2.47.3
>
--
Jan Kara <jack@suse.com>
SUSE Labs, CR
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: re-enable IOCB_NOWAIT writes to files v6
2026-01-08 14:19 re-enable IOCB_NOWAIT writes to files v6 Christoph Hellwig
` (10 preceding siblings ...)
2026-01-08 14:19 ` [PATCH 11/11] xfs: enable non-blocking timestamp updates Christoph Hellwig
@ 2026-01-12 13:02 ` Christian Brauner
11 siblings, 0 replies; 20+ messages in thread
From: Christian Brauner @ 2026-01-12 13:02 UTC (permalink / raw)
To: Christoph Hellwig
Cc: Christian Brauner, Al Viro, David Sterba, Jan Kara, Mike Marshall,
Martin Brandenburg, Carlos Maiolino, Stefan Roesch, Jeff Layton,
OGAWA Hirofumi, Trond Myklebust, Anna Schumaker, linux-kernel,
linux-btrfs, linux-fsdevel, gfs2, io-uring, devel, linux-unionfs,
linux-mtd, linux-xfs, linux-nfs
On Thu, 08 Jan 2026 15:19:00 +0100, Christoph Hellwig wrote:
> commit 66fa3cedf16a ("fs: Add async write file modification handling.")
> effectively disabled IOCB_NOWAIT writes as timestamp updates currently
> always require blocking, and the modern timestamp resolution means we
> always update timestamps. This leads to a lot of context switches from
> applications using io_uring to submit file writes, making it often worse
> than using the legacy aio code that is not using IOCB_NOWAIT.
>
> [...]
Applied to the vfs-7.0.nonblocking_timestamps branch of the vfs/vfs.git tree.
Patches in the vfs-7.0.nonblocking_timestamps 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: vfs-7.0.nonblocking_timestamps
[01/11] fs: remove inode_update_time
https://git.kernel.org/vfs/vfs/c/20b781834ea0
[02/11] fs: allow error returns from generic_update_time
https://git.kernel.org/vfs/vfs/c/dc9629faef0a
[03/11] nfs: split nfs_update_timestamps
https://git.kernel.org/vfs/vfs/c/b8b3002fbfef
[04/11] fat: cleanup the flags for fat_truncate_time
https://git.kernel.org/vfs/vfs/c/1cbc82281675
[05/11] fs: refactor ->update_time handling
https://git.kernel.org/vfs/vfs/c/761475268fa8
[06/11] fs: factor out a sync_lazytime helper
https://git.kernel.org/vfs/vfs/c/188344c8ac0b
[07/11] fs: add a ->sync_lazytime method
https://git.kernel.org/vfs/vfs/c/5cf06ea56ee6
[08/11] fs: add support for non-blocking timestamp updates
https://git.kernel.org/vfs/vfs/c/85c871a02b03
[09/11] fs: refactor file_update_time_flags
https://git.kernel.org/vfs/vfs/c/2d72003ba244
[10/11] xfs: implement ->sync_lazytime
https://git.kernel.org/vfs/vfs/c/f92f8eddbbfb
[11/11] xfs: enable non-blocking timestamp updates
https://git.kernel.org/vfs/vfs/c/08489c4f4133
^ permalink raw reply [flat|nested] 20+ messages in thread
end of thread, other threads:[~2026-01-12 13:02 UTC | newest]
Thread overview: 20+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-01-08 14:19 re-enable IOCB_NOWAIT writes to files v6 Christoph Hellwig
2026-01-08 14:19 ` [PATCH 01/11] fs: remove inode_update_time Christoph Hellwig
2026-01-08 14:19 ` [PATCH 02/11] fs: allow error returns from generic_update_time Christoph Hellwig
2026-01-08 14:19 ` [PATCH 03/11] nfs: split nfs_update_timestamps Christoph Hellwig
2026-01-08 14:19 ` [PATCH 04/11] fat: cleanup the flags for fat_truncate_time Christoph Hellwig
2026-01-08 14:19 ` [PATCH 05/11] fs: refactor ->update_time handling Christoph Hellwig
2026-01-08 15:20 ` Jan Kara
2026-01-08 14:19 ` [PATCH 06/11] fs: factor out a sync_lazytime helper Christoph Hellwig
2026-01-08 14:19 ` [PATCH 07/11] fs: add a ->sync_lazytime method Christoph Hellwig
2026-01-08 15:24 ` Jan Kara
2026-01-08 14:19 ` [PATCH 08/11] fs: add support for non-blocking timestamp updates Christoph Hellwig
2026-01-08 14:19 ` [PATCH 09/11] fs: refactor file_update_time_flags Christoph Hellwig
2026-01-08 14:19 ` [PATCH 10/11] xfs: implement ->sync_lazytime Christoph Hellwig
2026-01-08 14:19 ` [PATCH 11/11] xfs: enable non-blocking timestamp updates Christoph Hellwig
2026-01-12 13:02 ` re-enable IOCB_NOWAIT writes to files v6 Christian Brauner
-- strict thread matches above, loose matches on Subject: below --
2026-01-06 7:49 re-enable IOCB_NOWAIT writes to files v5 Christoph Hellwig
2026-01-06 7:50 ` [PATCH 08/11] fs: add support for non-blocking timestamp updates Christoph Hellwig
2026-01-06 11:58 ` Jan Kara
2026-01-06 12:13 ` Jeff Layton
2025-12-23 0:37 re-enable IOCB_NOWAIT writes to files v4 Christoph Hellwig
2025-12-23 0:37 ` [PATCH 08/11] fs: add support for non-blocking timestamp updates Christoph Hellwig
2025-12-23 5:38 ` Chaitanya Kulkarni
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox