Index | Thread | Search

From:
Marcus Glocker <marcus@nazgul.ch>
Subject:
Re: relatime mount option for ffs filesystems
To:
Jeremie Courreges-Anglas <jca@wxcvbn.org>
Cc:
tech@openbsd.org
Date:
Tue, 5 Aug 2025 06:59:10 +0200

Download raw body.

Thread
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
>