From db42ae787de7165ee3a1bbf8d4988d0698d7c1df Mon Sep 17 00:00:00 2001 From: Jim Meyering 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 MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit 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: Include "xstrtol.h". (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. * tests/misc/stat-nanoseconds: New file. * tests/Makefile.am (TESTS): Add it. * NEWS (Changes in behavior): Mention this. With improvements by Pádraig Brady. Thanks to Andreas Schwab for raising the issue. --- NEWS | 13 +++++ doc/coreutils.texi | 27 ++++++++- src/stat.c | 139 ++++++++++++++++++++++++++++++++------------ tests/Makefile.am | 1 + tests/misc/stat-nanoseconds | 36 ++++++++++++ tests/touch/60-seconds | 2 +- 6 files changed, 178 insertions(+), 40 deletions(-) create mode 100755 tests/misc/stat-nanoseconds diff --git a/NEWS b/NEWS index dcb9745..596d2ec 100644 --- a/NEWS +++ b/NEWS @@ -12,6 +12,19 @@ GNU coreutils NEWS -*- outline -*- cp --attributes-only now completely overrides --reflink. Previously a reflink was needlessly attempted. + stat's %X, %Y, and %Z directives once again print only the integer + part of seconds since the epoch. This reverts a change from + 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 0b5a3d3..be5999f 100644 --- a/doc/coreutils.texi +++ b/doc/coreutils.texi @@ -10706,15 +10706,40 @@ The valid @var{format} directives for files with @option{--format} and @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}. + +Here are some examples: + +@example +zero pad: + $ stat -c '[%015:Y]' /usr + [000000031367045] +space align: + $ stat -c '[%15:Y]' /usr + [ 31367045] + $ stat -c '[%-15:Y]' /usr + [31367045 ] +truncate: + $ stat -c '[%.3:Y]' /usr + [031] +@end example + 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..d05a93b 100644 --- a/src/stat.c +++ b/src/stat.c @@ -70,6 +70,7 @@ #include "stat-time.h" #include "strftime.h" #include "find-mount-point.h" +#include "xstrtol.h" #include "xvasprintf.h" #if USE_STATVFS @@ -462,24 +463,50 @@ 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); + 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. */ +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 be off by a factor of 10 or more. + + If a precision/max width is specified, i.e., a '.' is present + in the modifier, then then treat the modifier as operating + on the default representation, i.e., a zero padded number + of width 9. */ + unsigned long int ns = arg.tv_nsec; + + if (memchr (pformat, '.', prefix_len)) /* precision specified. */ + { + char tmp[INT_BUFSIZE_BOUND (uintmax_t)]; + snprintf (tmp, sizeof tmp, "%09lu", ns); + strcpy (pformat + prefix_len, "s"); + printf (pformat, tmp); + } else - sprintf (str, "%" PRIuMAX ".%09ld", (uintmax_t) t.tv_sec, t.tv_nsec); - return str; + { + char const *fmt = (prefix_len == 1) ? "09lu" : "lu"; + /* Note that pformat is big enough, as %:X -> %09lu + and two extra bytes are already allocated. */ + strcpy (pformat + prefix_len, fmt); + printf (pformat, ns); + } } static void @@ -539,7 +566,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 +739,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 +853,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 +942,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 +968,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 +1000,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 +1274,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/Makefile.am b/tests/Makefile.am index 84db367..dd1c509 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -244,6 +244,7 @@ TESTS = \ misc/stat-fmt \ misc/stat-hyphen \ misc/stat-mount \ + misc/stat-nanoseconds \ misc/stat-printf \ misc/stat-slash \ misc/stdbuf \ diff --git a/tests/misc/stat-nanoseconds b/tests/misc/stat-nanoseconds new file mode 100755 index 0000000..314f43e --- /dev/null +++ b/tests/misc/stat-nanoseconds @@ -0,0 +1,36 @@ +#!/bin/sh +# Exercise format strings involving %:X, %:Y, etc. + +# Copyright (C) 2010 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +test "$VERBOSE" = yes && stat --version +. "${srcdir=.}/init.sh"; path_prepend_ ../src + +touch -d '2010-10-21 18:43:33.023456789' k || framework_failure_ + +ls --full-time | grep 18:43:33.023456789 \ + || skip_ this file system does not support sub-second time stamps + +test "$(stat -c %:X k)" = 023456789 || fail=1 +test "$(stat -c %3:X k)" = 23456789 || fail=1 +test "$(stat -c %3.3:X k)" = 023 || fail=1 +test "$(stat -c %.3:X k)" = 023 || fail=1 +test "$(stat -c %03.3:X k)" = 023 || fail=1 +test "$(stat -c %-5.3:X k)" = '023 ' || fail=1 +test "$(stat -c %05.3:X k)" = ' 023' || fail=1 +test "$(stat -c %010.3:X k)" = ' 023' || fail=1 + +Exit $fail 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 -- 2.7.4