Properly implement the POSIX format -d option.

Previously we have hacked that using parsedate(3) - but parsedate()
returns a time_t and consequently while it "handles" fractional seconds,
all that meant (all it really can mean) is that they're ignored.

The POSIX spec expects that (at least if the filesystem supports them)
fractional seconds can be set using the -d option.

Handle that by first attempting to parse the -d arg as a posix format
date-time string (using a reasonably strict parser), and if that fails,
then fall back on parsedate(3) to parse the arg.

If the posix format parse succeeds, the result will be the same as
parsedate(3) would return for the same string - except any fractional
seconds will be handled properly.   If it fails, then nothing changes
from what we currently do.

Note the POSIX string is
	YYYY-MM-DDThh:mm:ss[.frac][Z]
where YYYY is (at least) 4 digits (leading 0's are acceptable if
you really must!) all the MM DD hh mm ss fields are exactly 2
digits, T is either 'T' or ' ', '.' is either itself, or ',',
and 'frac' is one or more digits.  Z (if given) is 'Z'.  The
[.,]frac and Z fields are optional.   Specify a time in a
slight shorthand like 2024-2-8T7:44:20  and the POSIX parse
will fail, leaving parsedate() to handle that (which it should).
But any fractional seconds which were given would be ignored.

Doc update coming - note the doc will call the YYYY field CCYY
instead, that's just a convenience to make other parts of what
is there make more sense - it is still one 4 (or more) digit field.

This should be an almost invisible change.
This commit is contained in:
kre 2024-02-08 02:53:53 +00:00
parent bda400655a
commit 7724ab9123
2 changed files with 151 additions and 4 deletions

View File

@ -1,8 +1,9 @@
# $NetBSD: Makefile,v 1.4 2012/07/25 01:23:46 christos Exp $ # $NetBSD: Makefile,v 1.5 2024/02/08 02:53:53 kre Exp $
# @(#)Makefile 8.1 (Berkeley) 6/6/93 # @(#)Makefile 8.1 (Berkeley) 6/6/93
PROG= touch PROG= touch
LDADD+= -lutil LDADD+= -lutil
LDADD+= -lm
DPADD+= ${LIBUTIL} DPADD+= ${LIBUTIL}
.include <bsd.prog.mk> .include <bsd.prog.mk>

View File

@ -1,4 +1,4 @@
/* $NetBSD: touch.c,v 1.36 2024/02/08 02:53:40 kre Exp $ */ /* $NetBSD: touch.c,v 1.37 2024/02/08 02:53:53 kre Exp $ */
/* /*
* Copyright (c) 1993 * Copyright (c) 1993
@ -39,16 +39,19 @@ __COPYRIGHT("@(#) Copyright (c) 1993\
#if 0 #if 0
static char sccsid[] = "@(#)touch.c 8.2 (Berkeley) 4/28/95"; static char sccsid[] = "@(#)touch.c 8.2 (Berkeley) 4/28/95";
#endif #endif
__RCSID("$NetBSD: touch.c,v 1.36 2024/02/08 02:53:40 kre Exp $"); __RCSID("$NetBSD: touch.c,v 1.37 2024/02/08 02:53:53 kre Exp $");
#endif /* not lint */ #endif /* not lint */
#include <sys/types.h> #include <sys/types.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/time.h> #include <sys/time.h>
#include <ctype.h>
#include <err.h> #include <err.h>
#include <errno.h> #include <errno.h>
#include <fcntl.h> #include <fcntl.h>
#include <limits.h>
#include <math.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@ -63,6 +66,7 @@ static void stime_arg0(const char *, struct timespec *);
static void stime_arg1(char *, struct timespec *); static void stime_arg1(char *, struct timespec *);
static void stime_arg2(const char *, int, struct timespec *); static void stime_arg2(const char *, int, struct timespec *);
static void stime_file(const char *, struct timespec *); static void stime_file(const char *, struct timespec *);
static int stime_posix(const char *, struct timespec *);
__dead static void usage(void); __dead static void usage(void);
struct option touch_longopts[] = { struct option touch_longopts[] = {
@ -107,6 +111,7 @@ main(int argc, char *argv[])
break; break;
case 'd': case 'd':
timeset = 1; timeset = 1;
if (!stime_posix(optarg, ts))
stime_arg0(optarg, ts); stime_arg0(optarg, ts);
break; break;
case 'f': case 'f':
@ -337,6 +342,147 @@ stime_file(const char *fname, struct timespec *tsp)
tsp[1] = sb.st_mtimespec; tsp[1] = sb.st_mtimespec;
} }
static int
stime_posix(const char *arg, struct timespec *tsp)
{
struct tm tm;
const char *p;
char *ep;
int utc = 0;
long val;
#define isdigch(c) (isdigit((int)((unsigned char)(c))))
if ((p = strchr(arg, '-')) == NULL)
return 0;
if (p - arg < 4) /* at least 4 year digits required */
return 0;
if (!isdigch(arg[0])) /* and the first must be a digit! */
return 0;
errno = 0;
val = strtol(arg, &ep, 10); /* YYYY */
if (val < 0 || val > INT_MAX)
return 0;
if (*ep != '-')
return 0;
tm.tm_year = (int)val - 1900;
p = ep + 1;
if (!isdigch(*p))
return 0;
val = strtol(p, &ep, 10); /* MM */
if (val < 1 || val > 12)
return 0;
if (*ep != '-' || ep != p + 2)
return 0;
tm.tm_mon = (int)val - 1;
p = ep + 1;
if (!isdigch(*p))
return 0;
val = strtol(p, &ep, 10); /* DD */
if (val < 1 || val > 31)
return 0;
if ((*ep != 'T' && *ep != ' ') || ep != p + 2)
return 0;
tm.tm_mday = (int)val;
p = ep + 1;
if (!isdigch(*p))
return 0;
val = strtol(p, &ep, 10); /* hh */
if (val < 0 || val > 23)
return 0;
if (*ep != ':' || ep != p + 2)
return 0;
tm.tm_hour = (int)val;
p = ep + 1;
if (!isdigch(*p))
return 0;
val = strtol(p, &ep, 10); /* mm */
if (val < 0 || val > 59)
return 0;
if (*ep != ':' || ep != p + 2)
return 0;
tm.tm_min = (int)val;
p = ep + 1;
if (!isdigch(*p))
return 0;
val = strtol(p, &ep, 10); /* ss (or in POSIX, SS) */
if (val < 0 || val > 60)
return 0;
if ((*ep != '.' && *ep != ',' && *ep != 'Z' && *ep != '\0') ||
ep != p + 2)
return 0;
tm.tm_sec = (int)val;
if (*ep == ',' || *ep == '.') {
double frac;
ptrdiff_t fdigs;
p = ep + 1;
if (!isdigch(*p))
return 0;
val = strtol(p, &ep, 10);
if (val < 0)
return 0;
if (ep == p) /* at least 1 digit required */
return 0;
if (*ep != 'Z' && *ep != '\0')
return 0;
if (errno != 0)
return 0;
fdigs = ep - p;
if (fdigs > 15) {
/* avoid being absurd */
/* don't want to risk 10^fdigs being INF */
if (val == 0)
fdigs = 1;
else while (fdigs > 15) {
val = (val + 5) / 10;
fdigs--;
}
}
frac = pow(10.0, (double)fdigs);
tsp[0].tv_nsec = tsp[1].tv_nsec =
(long)round(((double)val / frac) * 1000000000.0);
} else
tsp[0].tv_nsec = tsp[1].tv_nsec = 0;
if (*ep == 'Z') {
if (ep[1] != '\0')
return 0;
utc = 1;
}
if (errno != 0)
return 0;
tm.tm_isdst = -1;
if (utc)
tsp[0].tv_sec = tsp[1].tv_sec = timegm(&tm);
else
tsp[0].tv_sec = tsp[1].tv_sec = mktime(&tm);
if (errno != 0 && tsp[1].tv_sec == NO_TIME)
return 0;
return 1;
}
static void static void
usage(void) usage(void)
{ {