From: Peter Hessler Subject: Re: relatime mount option for ffs filesystems To: tech@openbsd.org Date: Mon, 4 Aug 2025 19:00:09 +0200 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