[PATCH 08/11] fs: add support for non-blocking timestamp updates

Christoph Hellwig <[email protected]>
Newsgroups dev.linux.lists.gfs2,org.infradead.lists.linux-mtd,org.kernel.vger.io-uring,org.kernel.vger.linux-btrfs,org.kernel.vger.linux-fsdevel,org.kernel.vger.linux-kernel,org.kernel.vger.linux-nfs,org.kernel.vger.linux-unionfs,org.kernel.vger.linux-xfs
Message-ID <[email protected]>
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 <[email protected]>
Reviewed-by: Jeff Layton <[email protected]>
---
 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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.