Re: making test(1)'s -nt/-ot ignore nanosecond fractions

From: Bruce Evans <bde_at_zeta.org.au>
Date: Sun, 9 May 2004 18:24:47 +1000 (EST)
On Sun, 9 May 2004, Akinori MUSHA wrote:

> At Sun, 9 May 2004 00:48:27 +1000 (EST),
> Bruce Evans wrote:
> > ffs uses vfs_timestamp() which gives a timestamp with the precision
> > specified by vfs.timestamp_precision.  The default is 0 (TSP_SEC),
> > which means that timestamps on files are normally in seconds with
> > nanoseconds part 0.  This can be changed easily using sysctl, but
> > changing the precision to the highest (nanoseconds) gives the bugs
> > being discussed.  Changing it to microseconds precision is safer,
> > since utimes(2) (but not utime(2) supports this precision.
> >
> > The only other way to get ffs timestamps with a nonzero nanseconds
> > part is to use utimes(), but this gives microseconds precision which
> > utimes() can copy later.
>
> Hm, I never changed vfs.timestamp_precision to anything other than the
> default (nor even knew of it), but I acutually observed some files
> having ...032 and ...256 nanoseconds on an NFS exported FFS on
> 4-STABLE.  I'm not sure if the files were created directly on FFS or
> via NFS.
>
> Does that mean there could be a bug somewhere around nanotime() calls?

The most obvious bug is that nfsclient has no reference to VA_UTIMES_NULL.
I think this cause it to use stack garbage for tv_nsec.  This seems to be
fixed in NetBSD.

Demonstration of the bug:

%%%
Script started on Sun May  9 17:29:21 2004
ttyv2:bde_at_gamplex:/c/z> cat foo.c
main(int argc, char **argv) { utimes(argv[1], 0); }
ttyv2:bde_at_gamplex:/c/z> cc -o foo foo.c
ttyv2:bde_at_gamplex:/c/z> /usr/bin/stat -f "%N %Fm" foo
foo 1084087769.000000000
ttyv2:bde_at_gamplex:/c/z> ./foo foo
ttyv2:bde_at_gamplex:/c/z> /usr/bin/stat -f "%N %Fm" foo
foo 1084087802.503363000
ttyv2:bde_at_gamplex:/c/z> ./foo foo
ttyv2:bde_at_gamplex:/c/z> /usr/bin/stat -f "%N %Fm" foo
foo 1084087824.912722647
ttyv2:bde_at_gamplex:/c/z> exit

Script done on Sun May  9 17:30:36 2004
%%%

After cc -o foo, foo has a normal mtime set by writing on the server
(-current nfsv3 server with vfs.timestamp_precision=0).  Then running
utimes("foo", 0) on the client messes up foo's st_mtime.tv_nsec.
Somehow it avoids messing up foo's st_mtime.tv_sec, and on the first
run it rounds to the nearest usec (this behaviour seems to be consistent).

I think utimes("foo", 0) should run on the client and set the times to
the current time on the client just like utimes("foo", non_null) would
set to times obtained from somewhere on the client.  However, I think
the times should always be rounded (by the server) according to the
server's vfs.timestamp_precision.  Clients can't be expected to do this
since they might not support a timestamp precision.  More precision than
the server would set for writes would be less than useful.

Here is part of the NetBSD code for supporting VA_UTIMES_NULL (atimes are
handled similarly for TOSERVER, but not for TOCLIENT -- VA_UTIMES_NULL Is
not used then):

nfsm_subs.h 1.36:
% #define nfsm_srvsattr(a) \
% ... \
% 		switch (fxdr_unsigned(int, *tl)) { \
% 		case NFSV3SATTRTIME_TOCLIENT: \
% 			nfsm_dissect(tl, u_int32_t *, 2 * NFSX_UNSIGNED); \
% 			fxdr_nfsv3time(tl, &(a)->va_mtime); \
% 			(a)->va_vaflags &= ~VA_UTIMES_NULL; \
% 			break; \
% 		case NFSV3SATTRTIME_TOSERVER: \
% 			(a)->va_mtime.tv_sec = time.tv_sec; \
% 			(a)->va_mtime.tv_nsec = time.tv_usec * 1000; \
% 			(a)->va_vaflags |= VA_UTIMES_NULL; \
% 			break; \
% 		}; }

The corresponding code in -current is:

nfs_srvsubs.c:
% int
% nfsm_srvsattr_xx(struct vattr *a, struct mbuf **md, caddr_t *dpos)
% ...
% 	switch (fxdr_unsigned(int, *tl)) {
% 	case NFSV3SATTRTIME_TOCLIENT:
% 		tl = nfsm_dissect_xx(2 * NFSX_UNSIGNED, md, dpos);
% 		if (tl == NULL)
% 			return EBADRPC;
% 		fxdr_nfsv3time(tl, &(a)->va_mtime);
% 		break;
% 	case NFSV3SATTRTIME_TOSERVER:
% 		getnanotime(&(a)->va_mtime);
% 		break;
% 	}

Looks like I was wrong about the client handling VA_UTIMES_NULL -- the
above seems to run on the server.  I don't understand what it is doing
with VA_UTIMES_NULL.

Other aspects of the bug are clearer: using getnanotime() instead of
vfs_timestamp() breaks the policy set in vfs.timestamp_precision.  It
wrong anyway since it gives excessive precision -- getnanotime() has
at most 1/HZ accuracy.  `time' used to have the same accuracy and
perhaps still does in NetBSD, but using it gives 3 fewer digits of
excessive precision and is friendlier with utimes().

THe quick fix is to replace the above getnanotime() with vfs_timestamp().
Unfortunately, nfs has 16 other calls to getnanotime(), 2 calls to
microtime() and 3 calls to getmicrotime() that need to be checked.

Bruce
Received on Sat May 08 2004 - 23:24:55 UTC

This archive was generated by hypermail 2.4.0 : Wed May 19 2021 - 11:37:53 UTC