summaryrefslogtreecommitdiff
path: root/gl/lib
diff options
context:
space:
mode:
authorPaul Eggert <eggert@cs.ucla.edu>2010-11-05 19:35:12 -0700
committerJim Meyering <meyering@redhat.com>2010-11-06 15:24:49 +0100
commitc7375c236ca5fa23661388e9f9c41e8312eb0cce (patch)
tree37fc8fa9c0b9765ddbc491abdffef6146688cfbe /gl/lib
parent312549b627b9cb634c5bc978bf3def44ea88be21 (diff)
downloadcoreutils-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/lib')
-rw-r--r--gl/lib/fstimeprec.c178
-rw-r--r--gl/lib/fstimeprec.h22
2 files changed, 200 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));