Thanks, I installed the attached more-ambitious patch into GNU Tar. On platforms lacking POSIX.1-2024's tm_gmtoff and tm_zone, when TZ is invalid it falls back on numeric timestamps rather than possibly incorrectly reporting localtime results as if they were Universal Time. Also, when falling back on numeric timestamps it reports nanosecond precision. Finally, instead of using fprintf (or fprintftime) it uses fwrite, so that partial writes are correctly counted.
From 2bbc58bf0b5b54b77bb40b336760f62531f12748 Mon Sep 17 00:00:00 2001
From: Paul Eggert <[email protected]>
Date: Sun, 23 Nov 2025 09:44:03 -0800
Subject: [PATCH] Support gnulib-style timestamps in checkpoint logs

* gnulib.modules: Add nstrftime-limited, time_rz.  Sort.
* src/checkpoint.c: Include <strftime.h>.
(format_checkpoint_string): Use nstrftime instead of strftime.
Also fix an obscure bug on platforms that lack tm_gmtoff+tm_zone by
calling tzalloc on those platforms; if it fails, fall back on gmtime.
Also, use fwrite instead of fprintf, since we typically know the
length already and this gives us a more-accurate byte count
in case there are partial writes.
---
 gnulib.modules   | 10 ++++++----
 src/checkpoint.c | 42 +++++++++++++++++++++++++++++++-----------
 2 files changed, 37 insertions(+), 15 deletions(-)

diff --git a/gnulib.modules b/gnulib.modules
index 93c88934..fbfc251b 100644
--- a/gnulib.modules
+++ b/gnulib.modules
@@ -37,8 +37,8 @@ dup2
 errno-h
 error
 exclude
-extern-inline
 exitfail
+extern-inline
 faccessat
 fchmodat
 fchownat
@@ -52,6 +52,7 @@ fnmatch-gnu
 free-posix
 fseeko
 fstatat
+full-read
 full-write
 futimens
 gendocs
@@ -81,6 +82,7 @@ mkdirat
 mkdtemp
 mkfifoat
 modechange
+nstrftime-limited
 obstack
 openat
 openat2
@@ -94,7 +96,6 @@ reallocarray
 renameat
 root-uid
 rpmatch
-full-read
 safe-read
 same-inode
 savedir
@@ -105,22 +106,23 @@ std-gnu23
 stdcountof-h
 stddef-h
 stdint-h
-stpcpy
 stdopen
+stpcpy
 strdup-posix
 strerror
 stringeq
 strnlen
 symlinkat
 sys_stat-h
+time_rz
 timespec
 timespec-sub
 unlinkat
 unlinkdir
 unlocked-io
 utimensat
-version-etc-fsf
 verror
+version-etc-fsf
 xalignalloc
 xalloc
 xalloc-die
diff --git a/src/checkpoint.c b/src/checkpoint.c
index 534e0d5b..9bd9fa05 100644
--- a/src/checkpoint.c
+++ b/src/checkpoint.c
@@ -22,6 +22,7 @@
 
 #include <wordsplit.h>
 #include <flexmember.h>
+#include <strftime.h>
 
 #include <sys/ioctl.h>
 #include <termios.h>
@@ -306,22 +307,41 @@ format_checkpoint_string (FILE *fp, intmax_t len,
 	      {
 		struct timespec ts = current_timespec ();
 		struct tm *tm = localtime (&ts.tv_sec);
-		char const *tmstr = NULL;
 
+#if HAVE_STRUCT_TM_TM_GMTOFF && HAVE_STRUCT_TM_TM_ZONE
+		/* struct tm has POSIX.1-2024 tm_gmtoff and tm_zone,
+		   so nstrftime ignores tz and any tz value will do.  */
+		timezone_t tz = 0;
+#else
+		static timezone_t tz;
+		if (tm && !tz)
+		  {
+		    tz = tzalloc (getenv ("TZ"));
+		    if (!tz)
+		      tm = NULL;
+		  }
+#endif
 		/* Keep BUF relatively small, as any text timestamp
 		   not fitting into BUF is likely a DoS attack.  */
-		char buf[max (SYSINT_BUFSIZE, 256)];
-
-		if (tm)
+		char buf[max (TIMESPEC_STRSIZE_BOUND, 256)];
+		ptrdiff_t buflen =
+		  (tm
+		   ? nstrftime (buf, sizeof buf, arg ? arg : "%c",
+				tm, tz, ts.tv_nsec)
+		   : -1);
+		char const *tmstr;
+		idx_t tmstrlen;
+		if (buflen < 0)
+		  {
+		    tmstr = code_timespec (ts, buf);
+		    tmstrlen = strlen (tmstr);
+		  }
+		else
 		  {
-		    buf[0] = '\0';
-		    char const *fmt = arg ? arg : "%c";
-		    if (strftime (buf, sizeof buf, fmt, tm) != 0 || !buf[0])
-		      tmstr = buf;
+		    tmstr = buf;
+		    tmstrlen = buflen;
 		  }
-		if (!tmstr)
-		  tmstr = timetostr (ts.tv_sec, buf);
-		len = add_printf (len, fprintf (fp, "%s", tmstr));
+		len = add_printf (len, fwrite (tmstr, 1, tmstrlen, stdout));
 	      }
 	      break;
 
-- 
2.51.0

Reply via email to