mirror of
git://git.sv.gnu.org/coreutils.git
synced 2026-04-20 10:51:48 +02:00
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: 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.
This commit is contained in:
139
src/stat.c
139
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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user