Index | Thread | Search

From:
Jeremie Courreges-Anglas <jca@wxcvbn.org>
Subject:
relatime mount option for ffs filesystems
To:
tech@openbsd.org
Date:
Mon, 4 Aug 2025 17:35:01 +0200

Download raw body.

Thread
By default ffs updates the access time of files each time they are
accessed, which results in disk writes as soon as you're reading
files.  Even if the files are in your buffer cache and the disk isn't
accessed for reading, you get disk writes.  This is not something I
want by default on my machines.

A possible workaround is noatime, but noatime drops access time
updates in an aggressive manner that breaks any application that cares
about atime - a well-documented example being mutt(1).  So another
scheme was designed and even made the default on Linux: relatime, for
"relative access time".  NetBSD has also implemented it.
With relatime, access time is updated on reads only if the file has
been modified (mtime/ctime) since the last read.  So merely reading
files no longer incurs pointless writes.

The diff below implements it for ffs only.  It doesn't implement
possible tweaks like updating atime if previous access happened more
than 24 hours ago.  If this goes in then I can work on making relatime
available on other filesystems.  Making relatime the default isn't on
my roadmap and is a discussion for another day.


ok?

To review the diff I'd split ufs_itimes() call sites in the following
categories:
- ffs_read() is my main interest since it sets IN_ACCESS
- various vop_close callbacks, shouldn't be a concern
- ufs_*read compound the existing IN_LAZYMOD handling for special
  devices with relatime, shouldn't be a concern
- ufs_setattr() implications for relatime are a bit contrived: if
  setattr tries to update atime *only*, IN_ACCESS is set but
  ufs_itimes() will skip updating atime and skip setting
  IN_MODIFIED/IN_LAZYMOD.  Then atime is forcefully updated in struct
  inode with whatever was passed to setattr, but UFS_UPDATE() may skip
  writing anything to disk.  To handle this fringe case, I just set
  IN_MODIFIED if explicitely requested, but it feels a bit paranoid.
  Do my analysis for this special case and the proposal make sense?

Feedback & oks welcome.


Index: lib/libc/sys/mount.2
===================================================================
RCS file: /cvs/src/lib/libc/sys/mount.2,v
diff -u -p -r1.52 mount.2
--- lib/libc/sys/mount.2	10 Nov 2023 00:25:59 -0000	1.52
+++ lib/libc/sys/mount.2	3 Aug 2025 16:08:07 -0000
@@ -78,6 +78,9 @@ suppress default semantics which affect 
 .It Dv MNT_RDONLY
 The filesystem should be treated as read-only:
 even the superuser may not write to it.
+.It Dv MNT_RELATIME
+Only update the access time on files in the filesystem if the modification
+or status change times are newer.
 .It Dv MNT_NOATIME
 Do not update the access time on files in the filesystem unless
 the modification or status change times are also being updated.
Index: sbin/mount/mntopts.h
===================================================================
RCS file: /cvs/src/sbin/mount/mntopts.h,v
diff -u -p -r1.18 mntopts.h
--- sbin/mount/mntopts.h	10 Sep 2016 16:53:30 -0000	1.18
+++ sbin/mount/mntopts.h	3 Aug 2025 16:08:07 -0000
@@ -61,6 +61,7 @@ union mntval {
 #define MOPT_NOPERM	{ "perm",	MNT_NOPERM, MFLAG_INVERSE | MFLAG_SET }
 #define MOPT_WXALLOWED	{ "wxallowed",	MNT_WXALLOWED, MFLAG_SET }
 #define MOPT_RDONLY	{ "rdonly",	MNT_RDONLY, MFLAG_SET }
+#define MOPT_RELATIME	{ "relatime",	MNT_RELATIME, MFLAG_SET }
 #define MOPT_SYNC	{ "sync",	MNT_SYNCHRONOUS, MFLAG_SET }
 #define MOPT_USERQUOTA	{ "userquota",	0, MFLAG_SET | MFLAG_STRVAL \
 					    | MFLAG_OPT }
Index: sbin/mount/mount.8
===================================================================
RCS file: /cvs/src/sbin/mount/mount.8,v
diff -u -p -r1.92 mount.8
--- sbin/mount/mount.8	10 Nov 2023 00:26:00 -0000	1.92
+++ sbin/mount/mount.8	3 Aug 2025 16:08:07 -0000
@@ -178,8 +178,16 @@ The same as
 .Fl f ;
 forces the revocation of write access when trying to downgrade
 a file system mount status from read-write to read-only.
+.It Cm relatime
+(FFS only)
+Only update the access time on files in the filesystem if it is older
+than the modification or change time.
+Like
+.Cm noatime ,
+it is useful to avoid extra disk activity, but is also more friendly
+to some applications.
 .It Cm noatime
-Do not update atime on files in the system unless the mtime or ctime
+Do not update atime on files in the filesystem unless the mtime or ctime
 is being changed as well.
 This option is useful for laptops and news servers where one does
 not want the extra disk activity associated with updating the atime.
Index: sbin/mount/mount.c
===================================================================
RCS file: /cvs/src/sbin/mount/mount.c,v
diff -u -p -r1.78 mount.c
--- sbin/mount/mount.c	9 May 2024 08:35:40 -0000	1.78
+++ sbin/mount/mount.c	3 Aug 2025 16:08:07 -0000
@@ -84,6 +84,7 @@ static struct opt {
 	{ MNT_EXPORTANON,	1,	"anon uid mapping",	"" },
 	{ MNT_EXRDONLY,		1,	"exported read-only",	"" },
 	{ MNT_LOCAL,		0,	"local",		"" },
+	{ MNT_RELATIME,		0,	"relatime",		"relatime" },
 	{ MNT_NOATIME,		0,	"noatime",		"noatime" },
 	{ MNT_NODEV,		0,	"nodev",		"nodev" },
 	{ MNT_NOEXEC,		0,	"noexec",		"noexec" },
Index: sbin/mount_ffs/mount_ffs.c
===================================================================
RCS file: /cvs/src/sbin/mount_ffs/mount_ffs.c,v
diff -u -p -r1.26 mount_ffs.c
--- sbin/mount_ffs/mount_ffs.c	4 Dec 2022 23:50:46 -0000	1.26
+++ sbin/mount_ffs/mount_ffs.c	3 Aug 2025 16:08:07 -0000
@@ -52,6 +52,7 @@ static const struct mntopt mopts[] = {
 	MOPT_ASYNC,
 	MOPT_SYNC,
 	MOPT_UPDATE,
+	MOPT_RELATIME,
 	MOPT_RELOAD,
 	MOPT_FORCE,
 	MOPT_SOFTDEP,
Index: sbin/newfs/newfs.c
===================================================================
RCS file: /cvs/src/sbin/newfs/newfs.c,v
diff -u -p -r1.118 newfs.c
--- sbin/newfs/newfs.c	9 Jan 2024 03:16:00 -0000	1.118
+++ sbin/newfs/newfs.c	3 Aug 2025 16:08:07 -0000
@@ -82,6 +82,7 @@ struct mntopt mopts[] = {
 	MOPT_ASYNC,
 	MOPT_UPDATE,
 	MOPT_FORCE,
+	MOPT_RELATIME,
 	{ NULL },
 };
 
Index: sys/kern/vfs_syscalls.c
===================================================================
RCS file: /cvs/src/sys/kern/vfs_syscalls.c,v
diff -u -p -r1.376 vfs_syscalls.c
--- sys/kern/vfs_syscalls.c	5 Jul 2025 04:25:43 -0000	1.376
+++ sys/kern/vfs_syscalls.c	4 Aug 2025 10:28:10 -0000
@@ -238,10 +238,11 @@ update:
 	else if (mp->mnt_flag & MNT_RDONLY)
 		mp->mnt_flag |= MNT_WANTRDWR;
 	mp->mnt_flag &=~ (MNT_NOSUID | MNT_NOEXEC | MNT_WXALLOWED | MNT_NODEV |
-	    MNT_SYNCHRONOUS | MNT_ASYNC | MNT_NOATIME | MNT_NOPERM | MNT_FORCE);
+	    MNT_SYNCHRONOUS | MNT_ASYNC | MNT_RELATIME | MNT_NOATIME |
+	    MNT_NOPERM | MNT_FORCE);
 	mp->mnt_flag |= flags & (MNT_NOSUID | MNT_NOEXEC | MNT_WXALLOWED |
-	    MNT_NODEV | MNT_SYNCHRONOUS | MNT_ASYNC | MNT_NOATIME | MNT_NOPERM |
-	    MNT_FORCE);
+	    MNT_NODEV | MNT_SYNCHRONOUS | MNT_ASYNC | MNT_RELATIME | MNT_NOATIME |
+	    MNT_NOPERM | MNT_FORCE);
 	/*
 	 * Mount the filesystem.
 	 */
Index: sys/sys/mount.h
===================================================================
RCS file: /cvs/src/sys/sys/mount.h,v
diff -u -p -r1.153 mount.h
--- sys/sys/mount.h	2 Jan 2025 01:19:22 -0000	1.153
+++ sys/sys/mount.h	4 Aug 2025 09:42:12 -0000
@@ -383,14 +383,14 @@ struct mount {
 /*
  * Mask of flags that are visible to statfs()
  */
-#define	MNT_VISFLAGMASK	0x0400ffff
+#define	MNT_VISFLAGMASK	0x0440ffff
 
 #define	MNT_BITS \
     "\20\001RDONLY\002SYNCHRONOUS\003NOEXEC\004NOSUID\005NODEV\006NOPERM" \
     "\007ASYNC\010EXRDONLY\011EXPORTED\012DEFEXPORTED\013EXPORTANON" \
     "\014WXALLOWED\015LOCAL\016QUOTA\017ROOTFS\020NOATIME\021UPDATE" \
-    "\022DELEXPORT\023RELOAD\024FORCE\025STALLED\026SWAPPABLE\031UNMOUNT" \
-    "\032WANTRDWR\033SOFTDEP\034DOOMED"
+    "\022DELEXPORT\023RELOAD\024FORCE\025STALLED\026SWAPPABLE\027RELATIME" \
+    "\031UNMOUNT\032WANTRDWR\033SOFTDEP\034DOOMED"
 
 /*
  * filesystem control flags.
@@ -401,6 +401,7 @@ struct mount {
 #define	MNT_FORCE	0x00080000	/* force unmount or readonly change */
 #define	MNT_STALLED	0x00100000	/* filesystem stalled */ 
 #define	MNT_SWAPPABLE	0x00200000	/* filesystem can be used for swap */
+#define MNT_RELATIME	0x00400000	/* update atime iff <= to ctime/mtime */
 #define MNT_UNMOUNT	0x01000000	/* unmount in progress */
 #define MNT_WANTRDWR	0x02000000	/* want upgrade to read/write */
 #define MNT_SOFTDEP     0x04000000      /* soft dependencies being done - now ignored */
Index: sys/ufs/ufs/ufs_vnops.c
===================================================================
RCS file: /cvs/src/sys/ufs/ufs/ufs_vnops.c,v
diff -u -p -r1.164 ufs_vnops.c
--- sys/ufs/ufs/ufs_vnops.c	18 Oct 2024 05:52:33 -0000	1.164
+++ sys/ufs/ufs/ufs_vnops.c	4 Aug 2025 15:03:11 -0000
@@ -94,6 +94,7 @@ ufs_itimes(struct vnode *vp)
 {
 	struct inode *ip;
 	struct timespec ts;
+	int modified = 0;
 
 	ip = VTOI(vp);
 	if ((ip->i_flag & (IN_ACCESS | IN_CHANGE | IN_UPDATE)) == 0)
@@ -109,27 +110,52 @@ ufs_itimes(struct vnode *vp)
 	}
 #endif
 
-	if ((vp->v_type == VBLK || vp->v_type == VCHR))
-		ip->i_flag |= IN_LAZYMOD;
-	else
-		ip->i_flag |= IN_MODIFIED;
-
 	getnanotime(&ts);
-	if (ip->i_flag & IN_ACCESS) {
-		DIP_ASSIGN(ip, atime, ts.tv_sec);
-		DIP_ASSIGN(ip, atimensec, ts.tv_nsec);
-	}
 	if (ip->i_flag & IN_UPDATE) {
 		DIP_ASSIGN(ip, mtime, ts.tv_sec);
 		DIP_ASSIGN(ip, mtimensec, ts.tv_nsec);
+		modified = 1;
 	}
 	if (ip->i_flag & IN_CHANGE) {
 		DIP_ASSIGN(ip, ctime, ts.tv_sec);
 		DIP_ASSIGN(ip, ctimensec, ts.tv_nsec);
 		ip->i_modrev++;
+		modified = 1;
+	}
+	if (ip->i_flag & IN_ACCESS) {
+		if (vp->v_mount->mnt_flag & MNT_RELATIME) {
+			struct timespec atime, ctime, mtime;
+
+			atime.tv_sec = DIP(ip, atime);
+			atime.tv_nsec = DIP(ip, atimensec);
+			ctime.tv_sec = DIP(ip, ctime);
+			ctime.tv_nsec = DIP(ip, ctimensec);
+			mtime.tv_sec = DIP(ip, mtime);
+			mtime.tv_nsec = DIP(ip, mtimensec);
+
+			if (timespeccmp(&atime, &ctime, <) ||
+			    timespeccmp(&atime, &mtime, <)) {
+				DIP_ASSIGN(ip, atime, ts.tv_sec);
+				DIP_ASSIGN(ip, atimensec, ts.tv_nsec);
+				modified = 1;
+			}
+		} else {
+			DIP_ASSIGN(ip, atime, ts.tv_sec);
+			DIP_ASSIGN(ip, atimensec, ts.tv_nsec);
+			modified = 1;
+		}
+
 	}
 
- out:
+	if (!modified)
+		goto out;
+
+	if ((vp->v_type == VBLK || vp->v_type == VCHR))
+		ip->i_flag |= IN_LAZYMOD;
+	else
+		ip->i_flag |= IN_MODIFIED;
+
+out:
 	ip->i_flag &= ~(IN_ACCESS | IN_CHANGE | IN_UPDATE);
 }
 
@@ -429,10 +455,14 @@ ufs_setattr(void *v)
 		if (vap->va_mtime.tv_nsec != VNOVAL) {
 			DIP_ASSIGN(ip, mtime, vap->va_mtime.tv_sec);
 			DIP_ASSIGN(ip, mtimensec, vap->va_mtime.tv_nsec);
+			/* mtime change explicitely requested */
+			ip->i_flag |= IN_MODIFIED;
 		}
 		if (vap->va_atime.tv_nsec != VNOVAL) {
 			DIP_ASSIGN(ip, atime, vap->va_atime.tv_sec);
 			DIP_ASSIGN(ip, atimensec, vap->va_atime.tv_nsec);
+			/* atime change explicitely requested */
+			ip->i_flag |= IN_MODIFIED;
 		}
 		error = UFS_UPDATE(ip, 0);
 		if (error)
Index: usr.sbin/pstat/pstat.c
===================================================================
RCS file: /cvs/src/usr.sbin/pstat/pstat.c,v
diff -u -p -r1.130 pstat.c
--- usr.sbin/pstat/pstat.c	10 Jul 2024 13:29:23 -0000	1.130
+++ usr.sbin/pstat/pstat.c	4 Aug 2025 10:29:10 -0000
@@ -789,6 +789,11 @@ mount_print(struct mount *mp)
 			flags &= ~MNT_ROOTFS;
 			comma = ",";
 		}
+		if (flags & MNT_RELATIME) {
+			(void)printf("%srelatime", comma);
+			flags &= ~MNT_RELATIME;
+			comma = ",";
+		}
 		if (flags & MNT_NOATIME) {
 			(void)printf("%snoatime", comma);
 			flags &= ~MNT_NOATIME;

-- 
jca