Download raw body.
relatime mount option for ffs filesystems
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
relatime mount option for ffs filesystems