summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NEWS13
-rw-r--r--doc/coreutils.texi27
-rw-r--r--src/stat.c139
-rw-r--r--tests/Makefile.am1
-rwxr-xr-xtests/misc/stat-nanoseconds36
-rwxr-xr-xtests/touch/60-seconds2
6 files changed, 178 insertions, 40 deletions
diff --git a/NEWS b/NEWS
index dcb974528..596d2ec14 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 0b5a3d341..be5999ffe 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 fabbc17e1..d05a93b74 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 84db367b1..dd1c509b9 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 000000000..314f43e2c
--- /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 <http://www.gnu.org/licenses/>.
+
+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 f98f0c5d1..d0082967c 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