logo

utils-std

Collection of commonly available Unix tools
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:

Mcmd/touch.1.in33++++++++++++++++++++++++++-------
Mcmd/touch.c104++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Mtest-cmd/touch20++++++++++++++++++++
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