From: Marcus Glocker Subject: Re: relatime mount option for ffs filesystems To: Jeremie Courreges-Anglas Cc: tech@openbsd.org Date: Tue, 5 Aug 2025 06:59:10 +0200 On Mon, Aug 04, 2025 at 05:35:01PM +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. I like it. The diff works fine on one of my laptops: x1e$ mount /dev/sd0a on / type ffs (local, relatime) /dev/sd0n on /home type ffs (local, relatime, nodev, nosuid) /dev/sd0d on /tmp type ffs (local, relatime, nodev, nosuid) /dev/sd0f on /usr type ffs (local, relatime, nodev) /dev/sd0g on /usr/X11R6 type ffs (local, relatime, nodev) /dev/sd0h on /usr/local type ffs (local, relatime, nodev, wxallowed) /dev/sd0m on /usr/obj type ffs (local, relatime, nodev, nosuid) /dev/sd0l on /usr/src type ffs (local, relatime, nodev, nosuid) /dev/sd0e on /var type ffs (local, relatime, nodev, nosuid) > 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 >