git: ae2888dd0e25 - stable/14 - touch: Allow setting the timestamp to -1.

From: Dag-Erling Smørgrav <des_at_FreeBSD.org>
Date: Thu, 04 Apr 2024 11:38:48 UTC
The branch stable/14 has been updated by des:

URL: https://cgit.FreeBSD.org/src/commit/?id=ae2888dd0e25e0334c89164454480d9c2ff9bd23

commit ae2888dd0e25e0334c89164454480d9c2ff9bd23
Author:     Dag-Erling Smørgrav <des@FreeBSD.org>
AuthorDate: 2024-03-27 10:03:40 +0000
Commit:     Dag-Erling Smørgrav <des@FreeBSD.org>
CommitDate: 2024-04-04 09:43:12 +0000

    touch: Allow setting the timestamp to -1.
    
    Note that VFS internally interprets a timestamp of -1 as “do not set”,
    so this has no effect, but at least touch won't incorrectly reject the
    given date / time (1969-12-31 23:59:59 UTC) as invalid.
    
    While here, fix some style issues.
    
    MFC after:      1 week
    Reviewed by:    allanjude
    Differential Revision:  https://reviews.freebsd.org/D44504
    
    (cherry picked from commit aa69e0f212630bd6d4ec1c3c54e117be16653e8a)
    
    touch: Add unit tests.
    
    MFC after:      1 week
    Reviewed by:    bapt
    Differential Revision:  https://reviews.freebsd.org/D44505
    
    (cherry picked from commit 74a4aa9b1517d92bfa85b0b1cd7d4c1262bb1ef9)
---
 etc/mtree/BSD.tests.dist          |   2 +
 usr.bin/touch/Makefile            |   4 +
 usr.bin/touch/tests/Makefile      |   4 +
 usr.bin/touch/tests/touch_test.sh | 157 ++++++++++++++++++++++++++++++++++++++
 usr.bin/touch/touch.c             |  21 +++--
 5 files changed, 181 insertions(+), 7 deletions(-)

diff --git a/etc/mtree/BSD.tests.dist b/etc/mtree/BSD.tests.dist
index 8a3b097c4162..a3c5cae09567 100644
--- a/etc/mtree/BSD.tests.dist
+++ b/etc/mtree/BSD.tests.dist
@@ -1133,6 +1133,8 @@
         ..
         tftp
         ..
+        touch
+        ..
         tr
         ..
         truncate
diff --git a/usr.bin/touch/Makefile b/usr.bin/touch/Makefile
index 5c153b3357a4..f4b74a033c36 100644
--- a/usr.bin/touch/Makefile
+++ b/usr.bin/touch/Makefile
@@ -1,5 +1,9 @@
 #	@(#)Makefile	8.1 (Berkeley) 6/6/93
 
+.include <src.opts.mk>
+
 PROG=	touch
+HAS_TESTS=
+SUBDIR.${MK_TESTS}=	tests
 
 .include <bsd.prog.mk>
diff --git a/usr.bin/touch/tests/Makefile b/usr.bin/touch/tests/Makefile
new file mode 100644
index 000000000000..543b682b96a0
--- /dev/null
+++ b/usr.bin/touch/tests/Makefile
@@ -0,0 +1,4 @@
+PACKAGE=	tests
+ATF_TESTS_SH=	touch_test
+
+.include <bsd.test.mk>
diff --git a/usr.bin/touch/tests/touch_test.sh b/usr.bin/touch/tests/touch_test.sh
new file mode 100644
index 000000000000..da39abef622e
--- /dev/null
+++ b/usr.bin/touch/tests/touch_test.sh
@@ -0,0 +1,157 @@
+#
+# Copyright (c) 2024 Dag-Erling Smørgrav
+#
+# SPDX-License-Identifier: BSD-2-Clause
+#
+
+export TZ=UTC
+
+atf_check_mtime()
+{
+	local mtime=$1 filename=$2
+	atf_check -o inline:"$((mtime))\n" stat -f%m "$filename"
+}
+
+atf_test_case touch_none
+touch_none_head()
+{
+	atf_set descr "No arguments"
+}
+touch_none_body()
+{
+	atf_check -s exit:1 -e match:"^usage" touch
+}
+
+atf_test_case touch_one
+touch_one_head()
+{
+	atf_set descr "One argument"
+}
+touch_one_body()
+{
+	atf_check touch foo
+	atf_check test -f foo
+}
+
+atf_test_case touch_multiple
+touch_multiple_head()
+{
+	atf_set descr "Multiple arguments"
+}
+touch_multiple_body()
+{
+	atf_check touch foo bar baz
+	atf_check test -f foo -a -f bar -a -f baz
+}
+
+atf_test_case touch_absolute
+touch_absolute_head()
+{
+	atf_set descr "Absolute date / time"
+}
+touch_absolute_body()
+{
+	atf_check touch -t 7001010101 foo
+	atf_check_mtime 3660 foo
+	atf_check rm foo
+
+	atf_check touch -t 7001010101.01 foo
+	atf_check_mtime 3661 foo
+	atf_check rm foo
+
+	atf_check touch -t 196912312359 foo
+	atf_check_mtime -60 foo
+	atf_check rm foo
+
+	atf_check touch -t 196912312359.58 foo
+	atf_check_mtime -2 foo
+	atf_check rm foo
+
+	atf_check touch -t 196912312359.59 foo
+	atf_expect_fail "VFS interprets -1 as “do not set”"
+	atf_check_mtime -1 foo
+	atf_check rm foo
+
+	atf_check touch -d1969-12-31T23:59:58 foo
+	atf_check_mtime -2 foo
+	atf_check rm foo
+
+	atf_check touch -d1969-12-31\ 23:59:58 foo
+	atf_check_mtime -2 foo
+	atf_check rm foo
+
+	atf_check env TZ=CET touch -d1970-01-01T00:59:58 foo
+	atf_check_mtime -2 foo
+	atf_check rm foo
+
+	atf_check env TZ=CET touch -d1970-01-01T00:59:58Z foo
+	atf_check_mtime 3598 foo
+	atf_check rm foo
+
+	atf_check touch -d1969-12-31T23:59:59Z foo
+	atf_expect_fail "VFS interprets -1 as “do not set”"
+	atf_check_mtime -1 foo
+	atf_check rm foo
+}
+
+atf_test_case touch_relative
+touch_relative_head()
+{
+	atf_set descr "Relative date / time"
+}
+touch_relative_body()
+{
+	atf_check touch -t 202403241234.56 foo
+	atf_check_mtime 1711283696 foo
+	atf_check touch -A -36 foo
+	atf_check_mtime 1711283660 foo
+	atf_check touch -A -0100 foo
+	atf_check_mtime 1711283600 foo
+	atf_check touch -A -010000 foo
+	atf_check_mtime 1711280000 foo
+	atf_check touch -A 010136 foo
+	atf_check_mtime 1711283696 foo
+}
+
+atf_test_case touch_copy
+touch_copy_head()
+{
+	atf_set descr "Copy time from another file"
+}
+touch_copy_body()
+{
+	atf_check touch -t 202403241234.56 foo
+	atf_check_mtime 1711283696 foo
+	atf_check touch -t 7001010000 bar
+	atf_check_mtime 0 bar
+	atf_check touch -r foo bar
+	atf_check_mtime 1711283696 bar
+}
+
+atf_test_case touch_nocreate
+touch_nocreate_head()
+{
+	atf_set descr "Do not create file"
+}
+touch_nocreate_body()
+{
+	atf_check touch -t 202403241234.56 foo
+	atf_check_mtime 1711283696 foo
+	atf_check touch -c -t 7001010000 foo bar
+	atf_check_mtime 0 foo
+	atf_check -s exit:1 test -f bar
+	atf_check touch -c bar
+	atf_check -s exit:1 test -f bar
+}
+
+atf_init_test_cases()
+{
+	atf_add_test_case touch_none
+	atf_add_test_case touch_one
+	atf_add_test_case touch_multiple
+	atf_add_test_case touch_absolute
+	atf_add_test_case touch_relative
+	atf_add_test_case touch_copy
+	atf_add_test_case touch_nocreate
+	# TODO: add test cases for -a, -h, -m
+}
diff --git a/usr.bin/touch/touch.c b/usr.bin/touch/touch.c
index 91abcfd447ea..ed5b2125ec36 100644
--- a/usr.bin/touch/touch.c
+++ b/usr.bin/touch/touch.c
@@ -243,7 +243,7 @@ stime_arg1(const char *arg, struct timespec *tvp)
 	}
 
 	yearset = 0;
-	switch(strlen(arg)) {
+	switch (strlen(arg)) {
 	case 12:			/* CCYYMMDDhhmm */
 		t->tm_year = ATOI2(arg);
 		t->tm_year *= 100;
@@ -274,15 +274,17 @@ stime_arg1(const char *arg, struct timespec *tvp)
 	}
 
 	t->tm_isdst = -1;		/* Figure out DST. */
+	t->tm_yday = -1;
 	tvp[0].tv_sec = tvp[1].tv_sec = mktime(t);
-	if (tvp[0].tv_sec == -1)
+	if (t->tm_yday == -1)
 		goto terr;
 
 	tvp[0].tv_nsec = tvp[1].tv_nsec = 0;
 	return;
 
 terr:
-	errx(1, "out of range or illegal time specification: [[CC]YY]MMDDhhmm[.SS]");
+	errx(1, "out of range or illegal time specification: "
+	    "[[CC]YY]MMDDhhmm[.SS]");
 }
 
 static void
@@ -307,10 +309,11 @@ stime_arg2(const char *arg, int year, struct timespec *tvp)
 	}
 
 	t->tm_isdst = -1;		/* Figure out DST. */
+	t->tm_yday = -1;
 	tvp[0].tv_sec = tvp[1].tv_sec = mktime(t);
-	if (tvp[0].tv_sec == -1)
-		errx(1,
-	"out of range or illegal time specification: MMDDhhmm[yy]");
+	if (t->tm_yday == -1)
+		errx(1, "out of range or illegal time specification: "
+		    "MMDDhhmm[yy]");
 
 	tvp[0].tv_nsec = tvp[1].tv_nsec = 0;
 }
@@ -350,13 +353,17 @@ stime_darg(const char *arg, struct timespec *tvp)
 	if (*p != '\0')
 		goto bad;
 
+	t.tm_yday = -1;
 	tvp[0].tv_sec = isutc ? timegm(&t) : mktime(&t);
+	if (t.tm_yday == -1)
+		goto bad;
 
 	tvp[1] = tvp[0];
 	return;
 
 bad:
-	errx(1, "out of range or illegal time specification: YYYY-MM-DDThh:mm:SS[.frac][tz]");
+	errx(1, "out of range or illegal time specification: "
+	    "YYYY-MM-DDThh:mm:SS[.frac][tz]");
 }
 
 /* Calculate a time offset in seconds, given an arg of the format [-]HHMMSS. */