Index | Thread | Search

From:
Peter Hessler <phessler@theapt.org>
Subject:
Re: relatime mount option for ffs filesystems
To:
tech@openbsd.org
Date:
Mon, 4 Aug 2025 19:00:09 +0200

Download raw body.

Thread
Hilariously, I was working on exactly this same feature over the
weekend, but you've done it a lot better than I did.

I've always liked relatime, because I do think it is very helpful for
software to be able to detect if a file was accessed since it was
modified, but constant atime is useless disk writes.  I frequently run
home/src/reposync partitions with noatime but are frustrated by the lack
of atime, relatime would be a very happy medium.

OK



On 2025 Aug 04 (Mon) at 17:35:01 +0200 (+0200), Jeremie Courreges-Anglas wrote:
:
: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
:

-- 
The makers may make
And the users may use,
But the fixers must fix
With but minimal clues