coreutils
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [coreutils] stat: reverting recent %X %Y %Z change


From: Jim Meyering
Subject: Re: [coreutils] stat: reverting recent %X %Y %Z change
Date: Tue, 26 Oct 2010 19:59:48 +0200

Jim Meyering wrote:
> Jim Meyering wrote:
>
>> Pádraig Brady wrote:
>>> On 22/10/10 18:43, Jim Meyering wrote:
>>>> Part of reverting this change:
>>>>
>>>>     stat now outputs the full sub-second resolution for the atime,
>>>>     mtime, and ctime values since the Epoch, when using the %X, %Y, and
>>>>     %Z directives of the --format option.  This matches the fact that
>>>>     %x, %y, and %z were already doing so for the human-readable variant.
>>>>
>>>> means considering whether to do the same sort of thing with the
>>>> newly-added %W (birth time/crtime).  Note that %W currently expands to "-"
>>>> on systems with no support (e.g., linux).
>>>>
>>>> I don't particularly like the idea of making stat do this:
>>>>
>>>>     $ src/stat -c %W.%:W .
>>>>     -.-
>>>>
>>>> Rather, I have a slight preference to make it do this:
>>>>
>>>>     $ src/stat -c %W.%:W .
>>>>     0.000000000
>>>
>>> I prefer that as it would mean less special
>>> cases in code that uses the output.
>>
>> Exactly.
>>
>>>> In any case, I think %w should still expand to "-".
>>>>
>>>> The alternative is to leave %W separate, with no %:W variant.
>>>> I.e., %W would continue to print floating point seconds.
>>>> In that case, it would be inconsistent with %X, %Y and %Z.
>>>
>>> I don't like that.
>>
>> Thanks for the feedback!
>
> Here's a mostly-ready proposed change.
>
> I've just realized it doesn't mention the nanosecond field width issue:
> the default is 9, and it's always zero-padded, but only to a width of 9,
> which is surprising if you ever specify a larger field width:
>
>     $ src/stat -c %12:W .
>        000000000
>
> I dislike such surprises...

I've adjusted things so that it's not so bad.

Setup:

    $ touch -d '2010-10-21 18:43:33.000000789' k

With the incremental patch below, you get "09" modifiers only
when you specify no other modifier:

    $ stat -c ns_%12:X_ k
    ns_         789_
    $ stat -c ns_%:X_ k
    ns_000000789_
    $ stat -c ns_%9:X_ k
    ns_      789_

diff --git a/src/stat.c b/src/stat.c
index 9a6bb8f..9d3db1b 100644
--- a/src/stat.c
+++ b/src/stat.c
@@ -471,14 +471,19 @@ epoch_sec (struct timespec t)
   return timetostr (t.tv_sec, str);
 }

-/* Return a string representation (in static storage)
-   of nanoseconds part of T.  */
-static char * ATTRIBUTE_WARN_UNUSED_RESULT
-epoch_ns (struct timespec t)
+/* Output the number of nanoseconds, ARG.tv_nsec.  */
+static void
+out_ns (char *pformat, size_t prefix_len, struct timespec arg)
 {
-  static char str[10];
-  sprintf (str, "%09ld", t.tv_nsec);
-  return str;
+  /* If no format modifier is specified, i.e., nothing between the
+     "%" and ":" of "%:X", then use the default of zero-padding and
+     a width of 9.  Otherwise, use the specified modifier(s).
+     This is to avoid the mistake of omitting the zero padding on
+     a number with fewer digits than the field width: when printing
+     nanoseconds after a decimal place, the resulting floating point
+     fraction would off by a factor of 10 or more.  */
+  strcpy (pformat + prefix_len, prefix_len == 1 ? "09ld" : "ld");
+  printf (pformat, arg.tv_nsec);
 }

 static void
@@ -829,8 +834,7 @@ print_stat (char *pformat, size_t prefix_len, unsigned int 
m,
                   epoch_sec (neg_to_zero (get_stat_birthtime (statbuf))));
       break;
     case 'W' + 256:
-      out_string (pformat, prefix_len,
-                  epoch_ns (neg_to_zero (get_stat_birthtime (statbuf))));
+      out_ns (pformat, prefix_len, neg_to_zero (get_stat_birthtime (statbuf)));
       break;
     case 'x':
       out_string (pformat, prefix_len, human_time (get_stat_atime (statbuf)));
@@ -839,7 +843,7 @@ print_stat (char *pformat, size_t prefix_len, unsigned int 
m,
       out_string (pformat, prefix_len, epoch_sec (get_stat_atime (statbuf)));
       break;
     case 'X' + 256:
-      out_string (pformat, prefix_len, epoch_ns (get_stat_atime (statbuf)));
+      out_ns (pformat, prefix_len, get_stat_atime (statbuf));
       break;
     case 'y':
       out_string (pformat, prefix_len, human_time (get_stat_mtime (statbuf)));
@@ -848,7 +852,7 @@ print_stat (char *pformat, size_t prefix_len, unsigned int 
m,
       out_string (pformat, prefix_len, epoch_sec (get_stat_mtime (statbuf)));
       break;
     case 'Y' + 256:
-      out_string (pformat, prefix_len, epoch_ns (get_stat_mtime (statbuf)));
+      out_ns (pformat, prefix_len, get_stat_mtime (statbuf));
       break;
     case 'z':
       out_string (pformat, prefix_len, human_time (get_stat_ctime (statbuf)));
@@ -857,7 +861,7 @@ print_stat (char *pformat, size_t prefix_len, unsigned int 
m,
       out_string (pformat, prefix_len, epoch_sec (get_stat_ctime (statbuf)));
       break;
     case 'Z' + 256:
-      out_string (pformat, prefix_len, epoch_ns (get_stat_ctime (statbuf)));
+      out_ns (pformat, prefix_len, get_stat_ctime (statbuf));
       break;
     case 'C':
       fail |= out_file_context (pformat, prefix_len, filename);

diff --git a/doc/coreutils.texi b/doc/coreutils.texi
index 94e8ebf..58a700b 100644
--- a/doc/coreutils.texi
+++ b/doc/coreutils.texi
@@ -10699,18 +10699,23 @@ stat invocation
 @item %U - User name of owner
 @item %w - Time of file birth, or @samp{-} if unknown
 @item %W - Time of file birth as seconds since Epoch, or @samp{0}
-@item %:W - Time of file birth: 0-padded nanoseconds remainder, or @samp{0}
+@item %:W - Time of file birth: nanoseconds remainder, or @samp{0}
 @item %x - Time of last access
 @item %X - Time of last access as seconds since Epoch
-@item %:X - Time of last access: 0-padded nanoseconds remainder
+@item %:X - Time of last access: nanoseconds remainder
 @item %y - Time of last modification
 @item %Y - Time of last modification as seconds since Epoch
-@item %:Y - Time of last modification: 0-padded nanoseconds remainder
+@item %:Y - Time of last modification: nanoseconds remainder
 @item %z - Time of last change
 @item %Z - Time of last change as seconds since Epoch
-@item %:Z - Time of last change: 0-padded nanoseconds remainder
+@item %:Z - Time of last change: nanoseconds remainder
 @end itemize

+Note that each of @samp{%:W}, @samp{%:X}, @samp{%:Y} and @samp{%:Z}
+prints its zero-padded number of nanoseconds on a field of width 9.
+However, if you specify anything between the @samp{%} and @samp{:},
+e.g., @samp{%10:X}, your modifier replaces the default of @samp{09}.
+
 The mount point printed by @samp{%m} is similar to that output
 by @command{df}, except that:
 @itemize @bullet

Here's the combined patch:

>From eba6292f45c9b46fb8a64f8a017bf2e9437cc60e Mon Sep 17 00:00:00 2001
From: Jim Meyering <address@hidden>
Date: Thu, 21 Oct 2010 18:41:24 +0200
Subject: [PATCH] stat: revert %X-%Y-%Z change; use e.g., %:X to print 
fractional seconds

This reverts part of the recent commit 9069af45,
"stat: print timestamps to full resolution", which made %X, %Y, %Z
print floating point numbers.  We prefer to retain portability of
%X, %Y and %Z uses, while still providing access to full-resolution
time stamps via modified format strings.  Also make the new
%W consistent.
* src/stat.c (print_it): Accept a new %...:[XYZ] format directive,
e.g., %:X, to print the nanoseconds portion of the corresponding time.
For example, %3.3:Y prints the zero-padded, truncated, milliseconds
part of the time of last modification.
(print_it): Update print_func signature to match.
(neg_to_zero): New helper function.
(epoch_time): Remove function; replace with...
(epoch_sec): New function; use timetostr.
(out_ns): New function.  Use "09" only when no other modifier
is specified.
(print_statfs): Change type of "m" to unsigned int,
now that it must accommodate values larger than 255.
(print_stat): Likewise.
Map :X to a code of 'X' + 256.  Likewise for Y, Z and W.
(usage): Update.
* tests/touch/60-seconds: Use %Y.%:Y in place of %Y.
* NEWS (Changes in behavior): Mention this.
Thanks to Andreas Schwab for raising the issue.
---
 NEWS                   |   15 ++++++
 doc/coreutils.texi     |   11 ++++-
 src/stat.c             |  118 ++++++++++++++++++++++++++++++++----------------
 tests/touch/60-seconds |    2 +-
 4 files changed, 105 insertions(+), 41 deletions(-)

diff --git a/NEWS b/NEWS
index 7dbbf1f..2a21109 100644
--- a/NEWS
+++ b/NEWS
@@ -7,6 +7,21 @@ GNU coreutils NEWS                                    -*- 
outline -*-
   tail -F once again notices changes in a currently unavailable
   remote directory [bug introduced in coreutils-7.5]

+** Changes in behavior
+
+  stat's %X, %Y, and %Z directives once again print only the integer
+  part of seconds since the epoch.  This reverts a change, introduced
+  in coreutils-8.6, that was deemed unnecessarily disruptive.  To obtain
+  the nanoseconds portion corresponding to %X, you may now use %:X.
+  I.e., to print the floating point number of seconds using maximum
+  precision, use this format string: %X.%:X.  Likewise for %Y, %Z and %W.
+
+  stat's new %W format directive would print floating point seconds.
+  However, with the above change to %X, %Y and %Z, we've made %W work
+  the same way:  %W now expands to seconds since the epoch (or 0 when
+  not supported), and %:W expands to the nanoseconds portion, or to
+  0 if not supported.
+

 * Noteworthy changes in release 8.6 (2010-10-15) [stable]

diff --git a/doc/coreutils.texi b/doc/coreutils.texi
index 4d17ed1..58a700b 100644
--- a/doc/coreutils.texi
+++ b/doc/coreutils.texi
@@ -10698,15 +10698,24 @@ stat invocation
 @item %u - User ID of owner
 @item %U - User name of owner
 @item %w - Time of file birth, or @samp{-} if unknown
-@item %W - Time of file birth as seconds since Epoch, or @samp{-}
+@item %W - Time of file birth as seconds since Epoch, or @samp{0}
+@item %:W - Time of file birth: nanoseconds remainder, or @samp{0}
 @item %x - Time of last access
 @item %X - Time of last access as seconds since Epoch
+@item %:X - Time of last access: nanoseconds remainder
 @item %y - Time of last modification
 @item %Y - Time of last modification as seconds since Epoch
+@item %:Y - Time of last modification: nanoseconds remainder
 @item %z - Time of last change
 @item %Z - Time of last change as seconds since Epoch
+@item %:Z - Time of last change: nanoseconds remainder
 @end itemize

+Note that each of @samp{%:W}, @samp{%:X}, @samp{%:Y} and @samp{%:Z}
+prints its zero-padded number of nanoseconds on a field of width 9.
+However, if you specify anything between the @samp{%} and @samp{:},
+e.g., @samp{%10:X}, your modifier replaces the default of @samp{09}.
+
 The mount point printed by @samp{%m} is similar to that output
 by @command{df}, except that:
 @itemize @bullet
diff --git a/src/stat.c b/src/stat.c
index fabbc17..9d3db1b 100644
--- a/src/stat.c
+++ b/src/stat.c
@@ -462,24 +462,28 @@ 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_time (struct timespec t)
+epoch_sec (struct timespec t)
 {
-  static char str[INT_STRLEN_BOUND (time_t) + sizeof ".NNNNNNNNN"];
-  /* Note that time_t can technically be a floating point value, such
-     that casting to [u]intmax_t could lose a fractional value or
-     suffer from overflow.  However, most porting targets have an
-     integral time_t; also, we know of no file systems that store
-     valid time values outside the bounds of intmax_t even if that
-     value were represented as a floating point.  Besides, the cost of
-     converting to struct tm just to use nstrftime (str, len, "%s.%N",
-     tm, 0, t.tv_nsec) is pointless, since nstrftime would have to
-     convert back to seconds as time_t.  */
-  if (TYPE_SIGNED (time_t))
-    sprintf (str, "%" PRIdMAX ".%09ld", (intmax_t) t.tv_sec, t.tv_nsec);
-  else
-    sprintf (str, "%" PRIuMAX ".%09ld", (uintmax_t) t.tv_sec, t.tv_nsec);
-  return str;
+  static char str[INT_BUFSIZE_BOUND (time_t)];
+  return timetostr (t.tv_sec, str);
+}
+
+/* Output the number of nanoseconds, ARG.tv_nsec.  */
+static void
+out_ns (char *pformat, size_t prefix_len, struct timespec arg)
+{
+  /* If no format modifier is specified, i.e., nothing between the
+     "%" and ":" of "%:X", then use the default of zero-padding and
+     a width of 9.  Otherwise, use the specified modifier(s).
+     This is to avoid the mistake of omitting the zero padding on
+     a number with fewer digits than the field width: when printing
+     nanoseconds after a decimal place, the resulting floating point
+     fraction would off by a factor of 10 or more.  */
+  strcpy (pformat + prefix_len, prefix_len == 1 ? "09ld" : "ld");
+  printf (pformat, arg.tv_nsec);
 }

 static void
@@ -539,7 +543,8 @@ out_file_context (char *pformat, size_t prefix_len, char 
const *filename)

 /* Print statfs info.  Return zero upon success, nonzero upon failure.  */
 static bool ATTRIBUTE_WARN_UNUSED_RESULT
-print_statfs (char *pformat, size_t prefix_len, char m, char const *filename,
+print_statfs (char *pformat, size_t prefix_len, unsigned int m,
+              char const *filename,
               void const *data)
 {
   STRUCT_STATVFS const *statfsbuf = data;
@@ -711,9 +716,19 @@ print_mount_point:
   return fail;
 }

+/* Map a TS with negative TS.tv_nsec to {0,0}.  */
+static inline struct
+timespec neg_to_zero (struct timespec ts)
+{
+  if (0 <= ts.tv_nsec)
+    return ts;
+  struct timespec z = {0, 0};
+  return z;
+}
+
 /* Print stat info.  Return zero upon success, nonzero upon failure.  */
 static bool
-print_stat (char *pformat, size_t prefix_len, char m,
+print_stat (char *pformat, size_t prefix_len, unsigned int m,
             char const *filename, void const *data)
 {
   struct stat *statbuf = (struct stat *) data;
@@ -815,31 +830,38 @@ print_stat (char *pformat, size_t prefix_len, char m,
       }
       break;
     case 'W':
-      {
-        struct timespec t = get_stat_birthtime (statbuf);
-        if (t.tv_nsec < 0)
-          out_string (pformat, prefix_len, "-");
-        else
-          out_string (pformat, prefix_len, epoch_time (t));
-      }
+      out_string (pformat, prefix_len,
+                  epoch_sec (neg_to_zero (get_stat_birthtime (statbuf))));
+      break;
+    case 'W' + 256:
+      out_ns (pformat, prefix_len, neg_to_zero (get_stat_birthtime (statbuf)));
       break;
     case 'x':
       out_string (pformat, prefix_len, human_time (get_stat_atime (statbuf)));
       break;
     case 'X':
-      out_string (pformat, prefix_len, epoch_time (get_stat_atime (statbuf)));
+      out_string (pformat, prefix_len, epoch_sec (get_stat_atime (statbuf)));
+      break;
+    case 'X' + 256:
+      out_ns (pformat, prefix_len, get_stat_atime (statbuf));
       break;
     case 'y':
       out_string (pformat, prefix_len, human_time (get_stat_mtime (statbuf)));
       break;
     case 'Y':
-      out_string (pformat, prefix_len, epoch_time (get_stat_mtime (statbuf)));
+      out_string (pformat, prefix_len, epoch_sec (get_stat_mtime (statbuf)));
+      break;
+    case 'Y' + 256:
+      out_ns (pformat, prefix_len, get_stat_mtime (statbuf));
       break;
     case 'z':
       out_string (pformat, prefix_len, human_time (get_stat_ctime (statbuf)));
       break;
     case 'Z':
-      out_string (pformat, prefix_len, epoch_time (get_stat_ctime (statbuf)));
+      out_string (pformat, prefix_len, epoch_sec (get_stat_ctime (statbuf)));
+      break;
+    case 'Z' + 256:
+      out_ns (pformat, prefix_len, get_stat_ctime (statbuf));
       break;
     case 'C':
       fail |= out_file_context (pformat, prefix_len, filename);
@@ -897,7 +919,8 @@ print_esc_char (char c)
    Return zero upon success, nonzero upon failure.  */
 static bool ATTRIBUTE_WARN_UNUSED_RESULT
 print_it (char const *format, char const *filename,
-          bool (*print_func) (char *, size_t, char, char const *, void const 
*),
+          bool (*print_func) (char *, size_t, unsigned int,
+                              char const *, void const *),
           void const *data)
 {
   bool fail = false;
@@ -922,10 +945,23 @@ print_it (char const *format, char const *filename,
           {
             size_t len = strspn (b + 1, "#-+.I 0123456789");
             char const *fmt_char = b + len + 1;
+            unsigned int fmt_code;
             memcpy (dest, b, len + 1);

+            /* The ":" modifier just before the letter in %W, %X, %Y, %Z
+               tells stat to print the nanoseconds portion of the date.  */
+            if (*fmt_char == ':' && strchr ("WXYZ", fmt_char[1]))
+              {
+                fmt_code = fmt_char[1] + 256;
+                ++fmt_char;
+              }
+            else
+              {
+                fmt_code = fmt_char[0];
+              }
+
             b = fmt_char;
-            switch (*fmt_char)
+            switch (fmt_code)
               {
               case '\0':
                 --b;
@@ -941,7 +977,7 @@ print_it (char const *format, char const *filename,
                 putchar ('%');
                 break;
               default:
-                fail |= print_func (dest, len + 1, *fmt_char, filename, data);
+                fail |= print_func (dest, len + 1, fmt_code, filename, data);
                 break;
               }
             break;
@@ -1215,14 +1251,18 @@ The valid format sequences for files (without 
--file-system):\n\
       fputs (_("\
   %u   User ID of owner\n\
   %U   User name of owner\n\
-  %w   Time of file birth, or - if unknown\n\
-  %W   Time of file birth as seconds since Epoch, or - if unknown\n\
-  %x   Time of last access\n\
-  %X   Time of last access as seconds since Epoch\n\
-  %y   Time of last modification\n\
-  %Y   Time of last modification as seconds since Epoch\n\
-  %z   Time of last change\n\
-  %Z   Time of last change as seconds since Epoch\n\
+  %w   Time of file birth, human-readable; - if unknown\n\
+  %W   Time of file birth, seconds since Epoch; 0 if unknown\n\
+  %:W  Time of file birth, nanoseconds remainder; 0 if unknown\n\
+  %x   Time of last access, human-readable\n\
+  %X   Time of last access, seconds since Epoch\n\
+  %:X  Time of last access, nanoseconds remainder\n\
+  %y   Time of last modification, human-readable\n\
+  %Y   Time of last modification, seconds since Epoch\n\
+  %:Y  Time of last modification, nanoseconds remainder\n\
+  %z   Time of last change, human-readable\n\
+  %Z   Time of last change, seconds since Epoch\n\
+  %:Z  Time of last change, nanoseconds remainder\n\
 \n\
 "), stdout);

diff --git a/tests/touch/60-seconds b/tests/touch/60-seconds
index f98f0c5..d008296 100755
--- a/tests/touch/60-seconds
+++ b/tests/touch/60-seconds
@@ -30,7 +30,7 @@ echo 60.000000000 > exp || framework_failure
 # an `invalid date format'.  Specifying 60 seconds *is* valid.
 TZ=UTC0 touch -t 197001010000.60 f || fail=1

-stat --p='%Y\n' f > out || fail=1
+stat --p='%Y.%:Y\n' f > out || fail=1

 compare out exp || fail=1

--
1.7.3.1.216.g329be



reply via email to

[Prev in Thread] Current Thread [Next in Thread]