commit: 71d3de59c8d6274d2fc43156c077dcb998c1fede
parent bb456c484706990d3db59994940b62e89ab67642
Author: Haelwenn (lanodan) Monnier <contact@hacktivis.me>
Date: Fri, 7 Jun 2024 09:44:19 +0200
cmd/touch: Add support for -t option
Used by pdpmake testsuite, and part of POSIX anyway.
Diffstat:
3 files changed, 146 insertions(+), 11 deletions(-)
diff --git a/cmd/touch.1.in b/cmd/touch.1.in
@@ -10,7 +10,7 @@
.Sh SYNOPSIS
.Nm
.Op Fl achm
-.Op Fl d Ar datetime | Fl r Ar ref_file
+.Op Fl d Ar isotime | Fl t Ar datetime | Fl r Ar ref_file
.Ar file...
.Sh DESCRIPTION
.Nm
@@ -25,14 +25,33 @@ is also given.
.It Fl c
Do not create
.Ar file .
+.It Fl d Ar isotime
+include(lib/iso_parse.mdoc)
.It Fl h
Do not follow symlinks.
.It Fl m
Change the modification time, no changes to access time unless
.Fl a
is also given.
-.It Fl d Ar datetime
-include(lib/iso_parse.mdoc)
+.It Fl t Ar datetime
+Use the specified
+.Ar datetime
+instead of the current time, with the form
+.Oo Oo CC Oc Ns YY Oc Ns MMDDhhmm Ns Oo \.SS Oc
+where:
+.Bl -tag -width _MMDDhhmm_
+.It Ql CC
+Corresponds to the first 2 digits of the year, aka %C
+.It Ql YY
+Corresponds to the last 2 digits of the year, aka %y
+.It Ql MMDDhhmm
+Corresponds to month, day, hours, minutes aka %m%d%H%M
+.It Ql .SS
+Corresponds to the seconds
+.El
+.Pp
+For example:
+.Ql 200306021337.42
.It Fl r Ar ref_file
Use the corresponding times of the file at
.Ar ref_file
@@ -45,14 +64,14 @@ Note: Will exit with failure when
is given but the file doesn't exists.
.Sh SEE ALSO
.Xr stat 1 ,
-.Xr futimens 3
+.Xr futimens 3 ,
+.Xr strptime 3
.Sh STANDARDS
.Nm
-is mostly compliant with the
+should be compliant with the
.St -p1003.1-2008
specification.
-.Fl t
-is intentionally missing.
+.Pp
.Fl h
is an extension.
.Sh AUTHORS
diff --git a/cmd/touch.c b/cmd/touch.c
@@ -15,9 +15,102 @@
#include <stdbool.h> /* bool */
#include <stdio.h> /* perror */
#include <stdlib.h> /* exit, EXIT_FAILURE */
+#include <string.h> /* strrchr, strlen */
#include <sys/stat.h> /* futimens, stat, utimensat */
+#include <time.h> /* mktime */
#include <unistd.h> /* getopt, opt*, close */
+// [[CC]YY]MMDDhhmm[.SS]
+static struct timespec
+opt_t_parse(char *arg, char **errstr)
+{
+ struct timespec res = {.tv_sec = 0, .tv_nsec = 0};
+ struct tm tm = {
+ .tm_year = 0,
+ .tm_mon = 0,
+ .tm_mday = 0,
+ .tm_hour = 0,
+ .tm_min = 0,
+ .tm_sec = 0,
+ .tm_isdst = -1, // unknown if DST is in effect
+ };
+
+ size_t len = strlen(arg);
+
+ char *secs = strrchr(arg, '.');
+ if(secs != NULL)
+ {
+ if(arg[len - 3] != '.')
+ {
+ *errstr = "Wrong location of second separator";
+ return res;
+ }
+
+ secs[0] = 0;
+ secs++;
+ len -= 3;
+
+ char *s = strptime(secs, "%S", &tm);
+ if(s == NULL)
+ {
+ *errstr = strerror(errno);
+ errno = 0;
+ return res;
+ }
+ if(s[0] != 0)
+ {
+ *errstr = "Extraneous character in seconds";
+ return res;
+ }
+ }
+
+ char *fmt = NULL;
+
+ switch(len)
+ {
+ case 8:
+ fmt = "%m%d%H%M"; // MMDDhhmm
+ break;
+ case 10:
+ fmt = "%y%m%d%H%M"; // YYMMDDhhmm
+ break;
+ case 12:
+ fmt = "%Y%m%d%H%M"; // CCYYMMDDhhmm
+ break;
+ default:
+ *errstr = "Invalid datetime";
+ return res;
+ }
+ assert(fmt != NULL);
+
+ char *dt = strptime(arg, fmt, &tm);
+ if(dt == NULL)
+ {
+ *errstr = strerror(errno);
+ errno = 0;
+ return res;
+ }
+ if(dt[0] != 0)
+ {
+ *errstr = "Extraneous character in datetime";
+ return res;
+ }
+
+ res.tv_sec = mktime(&tm);
+ if(res.tv_sec == (time_t)-1)
+ {
+ *errstr = strerror(errno);
+ errno = 0;
+ return res;
+ }
+
+ // As observed on FreeBSD 14.0, non-errorneous mktime can still end up setting errno
+ // cf. https://builds.sr.ht/~lanodan/job/1181509
+ errno = 0;
+
+ return res;
+}
+
int
main(int argc, char *argv[])
{
@@ -54,10 +147,13 @@ main(int argc, char *argv[])
case 'r':
ref_file = optarg;
break;
- case 't': // [[CC]YY]MMDDhhmm[.SS]
- // Too legacy of a format, too annoying to parse
- fprintf(stderr, "touch: Option -d not supported, use -t\n");
- return 1;
+ case 't':
+ target = opt_t_parse(optarg, &errstr);
+ if(errstr != NULL)
+ {
+ fprintf(stderr, "touch: opt_t_parse(\"%s\", …): %s\n", optarg, errstr);
+ exit(EXIT_FAILURE);
+ }
break;
case 'd':
target = iso_parse(optarg, &errstr);
diff --git a/test-cmd/touch b/test-cmd/touch
@@ -178,6 +178,24 @@ optd_frac_body() {
../cmd/touch -d 2003-06-02T13:37:42.1234567890Z ./foo
}
+atf_test_case optt
+optt_body() {
+ atf_check touch -a ./foo
+ maybe_sleep
+ atf_check touch -m ./foo
+ maybe_sleep
+ atime="$(./stat_atime ./foo)"
+ mtime="$(./stat_mtime ./foo)"
+
+ unset TZ
+
+ atf_check ../cmd/touch -t 200306021337.42 ./foo
+ atf_check -o "not-inline:${atime}\n" ./stat_atime ./foo
+ atf_check -o "not-inline:${mtime}\n" ./stat_mtime ./foo
+ atf_check -o 'match:^2003-06-02[T ]13:37:42(\.0+)? ?(Z|[\+\-]00:?00)$' ./stat_atime ./foo
+ atf_check -o 'match:^2003-06-02[T ]13:37:42(\.0+)? ?(Z|[\+\-]00:?00)$' ./stat_mtime ./foo
+}
+
atf_init_test_cases() {
cd "$(atf_get_srcdir)" || exit 1
@@ -195,6 +213,8 @@ atf_init_test_cases() {
atf_add_test_case optd
+ atf_add_test_case optt
+
# No support for displaying fractional seconds on FreeBSD stat(1)
if uname -s | grep -iq FreeBSD; then
# shellcheck disable=SC2317