Subject: bug#7325: new test failure due to non-portability of printf formats like %05.3s

From: Paul Eggert <[email-masked]>
Date: Wed, 03 Nov 2010 15:58:25 -0700

The test-case part of that looks OK, but the change to stat.c
can be simplified.  Also, there's a similar problem with a format
like %020X, which should be fixed too.

While we're on the subject, there are other ways that stat invokers
can exercise undefined behavior (in the C sense) by passing
weird formats in.  This really should get fixed at some point.
The patch proposed below addresses just the immediate issue.

>From a24a9ce8f3711670a6413c60b5e9ebd3e51a4e06 Mon Sep 17 00:00:00 2001
From: Paul Eggert <[email-masked]>
Date: Wed, 3 Nov 2010 15:49:50 -0700
Subject: [PATCH] stat: handle leading '0' when formatting secs and ns

* src/stat.c (epoch_sec): Remove. All callers changed to use
out_epoch_sec instead.
(out_ns): Use numeric format, not string format, to output
the nanoseconds count; this avoids unportable use of (e.g.)
%05s in a printf format.
(out_epoch_sec): New function.  This also uses a numeric format,
instead of a string format, to output a number.
---
 src/stat.c |   56 ++++++++++++++++++++++++++++++++++++--------------------
 1 files changed, 36 insertions(+), 20 deletions(-)

diff --git a/src/stat.c b/src/stat.c
index d05a93b..c1def13 100644
--- a/src/stat.c
+++ b/src/stat.c
@@ -463,15 +463,6 @@ human_time (struct timespec t)
   return str;
 }
 
-/* Return a string representation (in static storage)
-   of the number of seconds in T since the epoch.  */
-static char * ATTRIBUTE_WARN_UNUSED_RESULT
-epoch_sec (struct timespec t)
-{
-  static char str[INT_BUFSIZE_BOUND (time_t)];
-  return timetostr (t.tv_sec, str);
-}
-
 /* Output the number of nanoseconds, ARG.tv_nsec, honoring a
    WIDTH.PRECISION format modifier, where PRECISION specifies
    how many leading digits(on a field of 9) to print.  */
@@ -491,13 +482,29 @@ out_ns (char *pformat, size_t prefix_len, struct timespec arg)
      on the default representation, i.e., a zero padded number
      of width 9.  */
   unsigned long int ns = arg.tv_nsec;
+  char *dot = memchr (pformat, '.', prefix_len);
 
-  if (memchr (pformat, '.', prefix_len)) /* precision specified.  */
+  if (dot)
     {
-      char tmp[INT_BUFSIZE_BOUND (uintmax_t)];
-      snprintf (tmp, sizeof tmp, "%09lu", ns);
-      strcpy (pformat + prefix_len, "s");
-      printf (pformat, tmp);
+      /* Precision specified.  */
+      size_t i;
+      int precision = 0;
+      for (i = dot - pformat + 1; i < prefix_len && ISDIGIT (pformat[i]); i++)
+        {
+          if (! precision)
+            precision = pformat[i] - '0';
+          else
+            {
+              /* If more than 9 digits are asked for, just output 9.  */
+              precision = 9;
+              break;
+            }
+        }
+      if (! precision)
+        return;
+      for (; precision < 9; precision++)
+        ns /= 10;
+      strcpy (dot, "lu");
     }
   else
     {
@@ -505,8 +512,9 @@ out_ns (char *pformat, size_t prefix_len, struct timespec arg)
       /* Note that pformat is big enough, as %:X -> %09lu
          and two extra bytes are already allocated.  */
       strcpy (pformat + prefix_len, fmt);
-      printf (pformat, ns);
     }
+
+  printf (pformat, ns);
 }
 
 static void
@@ -539,6 +547,14 @@ out_uint_x (char *pformat, size_t prefix_len, uintmax_t arg)
   strcpy (pformat + prefix_len, PRIxMAX);
   printf (pformat, arg);
 }
+static void
+out_epoch_sec (char *pformat, size_t prefix_len, struct timespec arg)
+{
+  if (TYPE_SIGNED (time_t))
+    out_int (pformat, prefix_len, arg.tv_sec);
+  else
+    out_uint (pformat, prefix_len, arg.tv_sec);
+}
 
 /* Print the context information of FILENAME, and return true iff the
    context could not be obtained.  */
@@ -853,8 +869,8 @@ print_stat (char *pformat, size_t prefix_len, unsigned int m,
       }
       break;
     case 'W':
-      out_string (pformat, prefix_len,
-                  epoch_sec (neg_to_zero (get_stat_birthtime (statbuf))));
+      out_epoch_sec (pformat, prefix_len,
+                     neg_to_zero (get_stat_birthtime (statbuf)));
       break;
     case 'W' + 256:
       out_ns (pformat, prefix_len, neg_to_zero (get_stat_birthtime (statbuf)));
@@ -863,7 +879,7 @@ print_stat (char *pformat, size_t prefix_len, unsigned int m,
       out_string (pformat, prefix_len, human_time (get_stat_atime (statbuf)));
       break;
     case 'X':
-      out_string (pformat, prefix_len, epoch_sec (get_stat_atime (statbuf)));
+      out_epoch_sec (pformat, prefix_len, get_stat_atime (statbuf));
       break;
     case 'X' + 256:
       out_ns (pformat, prefix_len, get_stat_atime (statbuf));
@@ -872,7 +888,7 @@ print_stat (char *pformat, size_t prefix_len, unsigned int m,
       out_string (pformat, prefix_len, human_time (get_stat_mtime (statbuf)));
       break;
     case 'Y':
-      out_string (pformat, prefix_len, epoch_sec (get_stat_mtime (statbuf)));
+      out_epoch_sec (pformat, prefix_len, get_stat_mtime (statbuf));
       break;
     case 'Y' + 256:
       out_ns (pformat, prefix_len, get_stat_mtime (statbuf));
@@ -881,7 +897,7 @@ print_stat (char *pformat, size_t prefix_len, unsigned int m,
       out_string (pformat, prefix_len, human_time (get_stat_ctime (statbuf)));
       break;
     case 'Z':
-      out_string (pformat, prefix_len, epoch_sec (get_stat_ctime (statbuf)));
+      out_epoch_sec (pformat, prefix_len, get_stat_ctime (statbuf));
       break;
     case 'Z' + 256:
       out_ns (pformat, prefix_len, get_stat_ctime (statbuf));
-- 
1.7.2