diff options
author | Paul Eggert <eggert@cs.ucla.edu> | 2010-11-05 19:35:12 -0700 |
---|---|---|
committer | Jim Meyering <meyering@redhat.com> | 2010-11-06 15:24:49 +0100 |
commit | c7375c236ca5fa23661388e9f9c41e8312eb0cce (patch) | |
tree | 37fc8fa9c0b9765ddbc491abdffef6146688cfbe /gl | |
parent | 312549b627b9cb634c5bc978bf3def44ea88be21 (diff) | |
download | coreutils-c7375c236ca5fa23661388e9f9c41e8312eb0cce.tar.xz |
stat: use e.g. %.3X instead of %X.%3:X for sub-second precision
* NEWS: Document this.
* doc/coreutils.texi (stat invocation): Likewise.
* gl/lib/fstimeprec.c, gl/lib/fstimeprec.h, gl/modules/fstimeprec:
* gl/modules/fstimeprec-tests, gl/tests/test-fstimeprec.c:
New files.
* bootstrap.conf (gnulib_modules): Add fstimeprec.
* src/stat.c: Include fstimeprec.h. Don't include xstrtol.h.
(decimal_point, decimal_point_len): New static vars.
(main): Initialize them.
(epoch_sec, out_ns): Remove.
(out_int, out_uint): Now returns whatever printf returned.
(out_minus_zero, out_epoch_secs): New functions.
(print_stat): Use out_epoch_sec instead of out_ns and epoch_sec.
(print_stat, print_it, usage): Remove the %:X-style formats.
* tests/misc/stat-nanoseconds: Set TZ=UTC0 to avoid problems
with weird time zones. Use a time stamp near the Epoch so that we
don't have to worry about leap seconds. Redo test cases to match
new behavior.
* tests/touch/60-seconds: Change %Y.%:Y to %.9Y, to adjust to
new behavior.
Diffstat (limited to 'gl')
-rw-r--r-- | gl/lib/fstimeprec.c | 178 | ||||
-rw-r--r-- | gl/lib/fstimeprec.h | 22 | ||||
-rw-r--r-- | gl/modules/fstimeprec | 24 | ||||
-rw-r--r-- | gl/modules/fstimeprec-tests | 10 | ||||
-rw-r--r-- | gl/tests/test-fstimeprec.c | 74 |
5 files changed, 308 insertions, 0 deletions
diff --git a/gl/lib/fstimeprec.c b/gl/lib/fstimeprec.c new file mode 100644 index 000000000..fd6305476 --- /dev/null +++ b/gl/lib/fstimeprec.c @@ -0,0 +1,178 @@ +/* Determine a file system's time stamp precision. + + Copyright 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/>. */ + +/* written by Paul Eggert */ + +#include <config.h> +#include "fstimeprec.h" + +#include "hash.h" +#include "stat-time.h" +#include <limits.h> +#include <stdlib.h> + +/* A hash table that maps device numbers to precisions. */ +struct fstimeprec +{ + /* Map device numbers to precisions. */ + struct hash_table *map; + + /* Cache of the most recently allocated and otherwise-unused storage + for probing this table. */ + struct ent *probe; +}; + +/* A pair that maps a device number to a precision. */ +struct ent +{ + dev_t dev; + int prec; +}; + +/* Hash an entry. */ +static size_t +ent_hash (void const *x, size_t table_size) +{ + struct ent const *p = x; + dev_t dev = p->dev; + + /* FIXME: This code is duplicated from di-set.c. */ + /* When DEV is wider than size_t, exclusive-OR the words of DEV into H. + This avoids loss of info, without applying % to the wider type, + which could be quite slow on some systems. */ + size_t h = dev; + unsigned int i; + unsigned int n_words = sizeof dev / sizeof h + (sizeof dev % sizeof h != 0); + for (i = 1; i < n_words; i++) + h ^= dev >> CHAR_BIT * sizeof h * i; + + return h % table_size; +} + +/* Return true if two entries are the same. */ +static bool +ent_compare (void const *x, void const *y) +{ + struct ent const *a = x; + struct ent const *b = y; + return a->dev == b->dev; +} + +/* Using the SET table, map a device to an entry that represents + the file system precision. Return NULL on error. */ +static struct ent * +map_device (struct fstimeprec *tab, dev_t dev) +{ + /* Find space for the probe, reusing the cache if available. */ + struct ent *ent; + struct ent *probe = tab->probe; + if (probe) + { + /* If repeating a recent query, return the cached result. */ + if (probe->dev == dev) + return probe; + } + else + { + tab->probe = probe = malloc (sizeof *probe); + if (! probe) + return NULL; + probe->prec = 0; + } + + /* Probe for the device. */ + probe->dev = dev; + ent = hash_insert (tab->map, probe); + if (ent == probe) + tab->probe = NULL; + return ent; +} + +/* Return a newly allocated table of file system time stamp + resolutions, or NULL if out of memory. */ +struct fstimeprec * +fstimeprec_alloc (void) +{ + struct fstimeprec *tab = malloc (sizeof *tab); + if (tab) + { + enum { INITIAL_DEV_MAP_SIZE = 11 }; + tab->map = hash_initialize (INITIAL_DEV_MAP_SIZE, NULL, + ent_hash, ent_compare, free); + if (! tab->map) + { + free (tab); + return NULL; + } + tab->probe = NULL; + } + return tab; +} + +/* Free TAB. */ +void +fstimeprec_free (struct fstimeprec *tab) +{ + hash_free (tab->map); + free (tab->probe); + free (tab); +} + +/* Using the cached information in TAB, return the estimated precision + of time stamps for the file system containing the file whose status + info is in ST. The returned value counts the number of decimal + fraction digits. Return 0 on failure. + + If TAB is null, the guess is based purely on ST. */ +int +fstimeprec (struct fstimeprec *tab, struct stat const *st) +{ + /* Map the device number to an entry. */ + struct ent *ent = (tab ? map_device (tab, st->st_dev) : NULL); + + /* Guess the precision based on what has been seen so far. */ + int prec = (ent ? ent->prec : 0); + + /* If the current file's timestamp resolution is higher than the + guess, increase the guess. */ + if (prec < 9) + { + struct timespec ats = get_stat_atime (st); + struct timespec mts = get_stat_mtime (st); + struct timespec cts = get_stat_ctime (st); + struct timespec bts = get_stat_birthtime (st); + unsigned int ans = ats.tv_nsec; + unsigned int mns = mts.tv_nsec; + unsigned int cns = cts.tv_nsec; + unsigned int bns = bts.tv_nsec < 0 ? 0 : bts.tv_nsec; + unsigned int power = 10; + int p; + for (p = prec + 1; p < 9; p++) + power *= 10; + + while (ans % power | mns % power | cns % power | bns % power) + { + power /= 10; + prec++; + } + + if (ent) + ent->prec = prec; + } + + return prec; +} diff --git a/gl/lib/fstimeprec.h b/gl/lib/fstimeprec.h new file mode 100644 index 000000000..8ec24df76 --- /dev/null +++ b/gl/lib/fstimeprec.h @@ -0,0 +1,22 @@ +#include <sys/stat.h> + +#ifndef ATTRIBUTE_MALLOC +# if __GNUC__ >= 3 +# define ATTRIBUTE_MALLOC __attribute__ ((__malloc__)) +# else +# define ATTRIBUTE_MALLOC +# endif +#endif + +#ifndef _GL_ARG_NONNULL +# if (__GNUC__ == 3 && __GNUC_MINOR__ >= 3) || __GNUC__ > 3 +# define _GL_ARG_NONNULL(params) __attribute__ ((__nonnull__ params)) +# else +# define _GL_ARG_NONNULL(params) +# endif +#endif + +struct fstimeprec *fstimeprec_alloc (void) ATTRIBUTE_MALLOC; +void fstimeprec_free (struct fstimeprec *) _GL_ARG_NONNULL ((1)); +int fstimeprec (struct fstimeprec *, struct stat const *) + _GL_ARG_NONNULL ((2)); diff --git a/gl/modules/fstimeprec b/gl/modules/fstimeprec new file mode 100644 index 000000000..787c2e7a0 --- /dev/null +++ b/gl/modules/fstimeprec @@ -0,0 +1,24 @@ +Description: +estimate precision of time stamps in file systems + +Files: +lib/fstimeprec.c +lib/fstimeprec.h + +Depends-on: +hash +stat-time + +configure.ac: + +Makefile.am: +lib_SOURCES += fstimeprec.c fstimeprec.h + +Include: +"fstimeprec.h" + +License +GPL + +Maintainer: +Paul Eggert and Jim Meyering diff --git a/gl/modules/fstimeprec-tests b/gl/modules/fstimeprec-tests new file mode 100644 index 000000000..bf6b49e6b --- /dev/null +++ b/gl/modules/fstimeprec-tests @@ -0,0 +1,10 @@ +Files: +tests/test-fstimeprec.c + +Depends-on: + +configure.ac: + +Makefile.am: +TESTS += test-fstimeprec +check_PROGRAMS += test-fstimeprec diff --git a/gl/tests/test-fstimeprec.c b/gl/tests/test-fstimeprec.c new file mode 100644 index 000000000..f699139df --- /dev/null +++ b/gl/tests/test-fstimeprec.c @@ -0,0 +1,74 @@ +/* Test the fstimeprec module. + 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/>. */ + +/* written by Paul Eggert */ + +#include <config.h> +#include <stdlib.h> +#include <stdio.h> +#include <sys/stat.h> + +#define ASSERT(expr) \ + do \ + { \ + if (!(expr)) \ + { \ + fprintf (stderr, "%s:%d: assertion failed\n", __FILE__, __LINE__); \ + fflush (stderr); \ + abort (); \ + } \ + } \ + while (0) + +#include "fstimeprec.h" + +static int +fstimeprec_file (struct fstimeprec *tab, char const *file) +{ + struct stat st; + if (stat (file, &st) != 0) + return 0; + return fstimeprec (tab, &st); +} + +int +main (void) +{ + struct fstimeprec *tab = fstimeprec_alloc (); + ASSERT (tab); + + int m1 = fstimeprec_file (tab, "/"); + int m2 = fstimeprec_file (tab, "."); + int m3 = fstimeprec_file (tab, ".."); + ASSERT (0 <= m1 && m1 <= 9); + ASSERT (0 <= m2 && m2 <= 9); + ASSERT (0 <= m3 && m3 <= 9); + + int n1 = fstimeprec_file (tab, "/"); + int n2 = fstimeprec_file (tab, "."); + int n3 = fstimeprec_file (tab, ".."); + ASSERT (0 <= n1 && n1 <= 9); + ASSERT (0 <= n2 && n2 <= 9); + ASSERT (0 <= n3 && n3 <= 9); + + ASSERT (m1 <= n1); + ASSERT (m2 <= n2); + ASSERT (m3 <= n3); + + fstimeprec_free (tab); + + return 0; +} |