From 735c00a2f3a5ce7aaec8517f5438ce37b48a936c Mon Sep 17 00:00:00 2001 From: Bruno Haible Date: Tue, 27 Mar 2007 11:01:11 +0000 Subject: [PATCH] 2007-03-27 Bruno Haible * lib/stat-time.h: Include . 2007-03-27 James Youngman * lib/stat-time.h (get_stat_birthtime): New function for retrieving st_birthtime as provided by UFS2 (hence *BSD). * m4/stat-time.m4 (gl_STAT_BIRTHTIME): Probe for st_birthtime and its variants. * modules/stat-time (configure.ac): call gl_STAT_BIRTHTIME. * modules/stat-time-test: New file. * tests/test-stat-time.c: New test, devised by Bruno Haible. --- ChangeLog | 14 ++++ lib/stat-time.h | 95 +++++++++++++++++++++++++++ m4/stat-time.m4 | 29 +++++++- modules/stat-time | 1 + modules/stat-time-tests | 11 ++++ tests/test-stat-time.c | 171 ++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 319 insertions(+), 2 deletions(-) create mode 100644 modules/stat-time-tests create mode 100644 tests/test-stat-time.c diff --git a/ChangeLog b/ChangeLog index 89df5d585..3e7ee2581 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,17 @@ +2007-03-27 Bruno Haible + + * lib/stat-time.h: Include . + +2007-03-27 James Youngman + + * lib/stat-time.h (get_stat_birthtime): New function for + retrieving st_birthtime as provided by UFS2 (hence *BSD). + * m4/stat-time.m4 (gl_STAT_BIRTHTIME): Probe for st_birthtime + and its variants. + * modules/stat-time (configure.ac): call gl_STAT_BIRTHTIME. + * modules/stat-time-test: New file. + * tests/test-stat-time.c: New test, devised by Bruno Haible. + 2007-03-26 Bruno Haible Better support of signalling NaNs. diff --git a/lib/stat-time.h b/lib/stat-time.h index 5fd18c1fa..50e907c5a 100644 --- a/lib/stat-time.h +++ b/lib/stat-time.h @@ -21,6 +21,7 @@ #ifndef STAT_TIME_H #define STAT_TIME_H 1 +#include #include /* STAT_TIMESPEC (ST, ST_XTIM) is the ST_XTIM member for *ST of type @@ -44,6 +45,13 @@ # define STAT_TIMESPEC_NS(st, st_xtim) ((st)->st_xtim.st__tim.tv_nsec) #endif +#if defined HAVE_STRUCT_STAT_ST_BIRTHTIME || defined HAVE_STRUCT_STAT_ST_BIRTHTIMENSEC || defined HAVE_STRUCT_STAT_ST_BIRTHTIMESPEC_TV_NSEC || defined HAVE_STRUCT_STAT_ST_SPARE4 +# define USE_BIRTHTIME 1 +#else +# undef USE_BIRTHTIME +#endif + + /* Return the nanosecond component of *ST's access time. */ static inline long int get_stat_atime_ns (struct stat const *st) @@ -89,6 +97,28 @@ get_stat_mtime_ns (struct stat const *st) # endif } +/* Return the nanosecond component of *ST's birth time. */ +static inline long int +get_stat_birthtime_ns (struct stat const *st) +{ +# if defined USE_BIRTHTIME +# if defined STAT_TIMESPEC && defined HAVE_STRUCT_STAT_ST_BIRTHTIMESPEC_TV_NSEC + return STAT_TIMESPEC (st, st_birthtim).tv_nsec; +# elif defined STAT_TIMESPEC_NS && defined HAVE_STRUCT_STAT_ST_BIRTHTIMESPEC_TV_SEC + return STAT_TIMESPEC_NS (st, st_birthtim); +# elif defined HAVE_STRUCT_STAT_ST_SPARE4 + /* Cygwin, without __CYGWIN_USE_BIG_TYPES__ */ + return st->st_spare4[1] * 1000L; +# else + /* Birthtime is available, but not at nanosecond resolution. */ + return 0; +# endif +# else + /* Birthtime is not available, so indicate this in the returned value. */ + return 0; +# endif +} + /* Return *ST's access time. */ static inline struct timespec get_stat_atime (struct stat const *st) @@ -131,4 +161,69 @@ get_stat_mtime (struct stat const *st) #endif } +/* Return *ST's birth time, if available, in *PTS. A nonzero value is + * returned if the stat structure appears to indicate that the + * timestamp is available. + * + * The return value of this function does not reliably indicate that the + * returned data is valid; see the comments within the body of the + * function for an explanation. + */ +static inline int +get_stat_birthtime (struct stat const *st, + struct timespec *pts) +{ +#if defined USE_BIRTHTIME +# ifdef STAT_TIMESPEC + *pts = STAT_TIMESPEC (st, st_birthtim); +# else + struct timespec t; + pts->tv_sec = st->st_birthtime; + pts->tv_nsec = get_stat_birthtime_ns (st); +# endif + + /* NetBSD sometimes signals the absence of knowledge of the file's + * birth time by using zero. We indicate we don't know, by + * returning 0 from this function when that happens. This is + * slightly problematic since (time_t)0 is otherwise a valid, albeit + * unlikely, timestamp. + * + * NetBSD sometimes returns 0 for unknown values (for example on + * ffs) and sometimes begative values for tv_nsec (for example on + * NFS). For some filesystems (e.g. msdos) NetBSD also appears to + * fail to update the st_birthtime member at all, and just leaves in + * there whatever junk existed int he uninitialised stat structure + * the caller provided. Therefore, callers are advised to initialise + * the tv_nsec number to a negative value before they call stat in + * order to detect this problem. + */ + if (pts->tv_sec == (time_t)0) + { + return 0; /* result probably invalid, see above. */ + } + else + { + /* Sometimes NetBSD returns junk in the birth time fields, so + * do a simple range check on the data, and return 0 to indicate + * that the data is invalid if it just looks wrong. + */ + return (pts->tv_nsec >= 0) && (pts->tv_nsec <= 1000000000); + } +#elif (defined _WIN32 || defined __WIN32__) && !defined __CYGWIN__ + /* Woe32 native platforms (mingw, msvc, but not Cygwin) put the + * "file creation time" in st_ctime (!). See for example the + * article + * + */ + pts->tv_sec = st->st_ctime; + pts->tv_nsec = 0; + return 1; /* result is valid */ +#else + /* Birth time not supported. */ + pts->tv_sec = 0; + pts->tv_nsec = 0; + return 0; /* result is not valid */ +#endif +} + #endif diff --git a/m4/stat-time.m4 b/m4/stat-time.m4 index 353de213f..6b98dd73c 100644 --- a/m4/stat-time.m4 +++ b/m4/stat-time.m4 @@ -10,11 +10,13 @@ dnl From Paul Eggert. # st_atim.tv_nsec - Linux, Solaris -# st_atimespec.tv_nsec - FreeBSD, if ! defined _POSIX_SOURCE -# st_atimensec - FreeBSD, if defined _POSIX_SOURCE +# st_atimespec.tv_nsec - FreeBSD, NetBSD, if ! defined _POSIX_SOURCE +# st_atimensec - FreeBSD, NetBSD, if defined _POSIX_SOURCE # st_atim.st__tim.tv_nsec - UnixWare (at least 2.1.2 through 7.1) # st_spare1 - Cygwin? +# st_birthtimespec present on NetBSD (probably also FreBSD, OpenBSD) + AC_DEFUN([gl_STAT_TIME], [ AC_REQUIRE([AC_C_INLINE]) @@ -61,3 +63,26 @@ AC_DEFUN([gl_STAT_TIME], [#include #include ]) ]) + +# Checks for st_birthtime, which is a feature from UFS2 (FreeBSD, NetBSD, OpenBSD, etc.) +# There was a time when this field was named st_createtime (21 June 2002 to 16 July 2002) +# But that window is very small and applied only to development code, so systems still +# using that configuration are not a realistic development target. +# See revisions 1.10 and 1.11 of FreeBSD's src/sys/ufs/ufs/dinode.h. +# +AC_DEFUN([gl_STAT_BIRTHTIME], +[ + AC_REQUIRE([AC_C_INLINE]) + AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS]) + AC_CHECK_HEADERS_ONCE([sys/time.h]) + AC_CHECK_MEMBERS([struct stat.st_birthtimespec.tv_sec, struct stat.st_birthtimespec.tv_nsec], [], + [AC_CHECK_MEMBERS([struct stat.st_birthtime, struct stat.st_birthtimensec], [], + [AC_CHECK_MEMBERS([struct stat.st_spare4], [], + [], + [#include + #include ])], + [#include + #include ])], + [#include + #include ]) +]) diff --git a/modules/stat-time b/modules/stat-time index 13dc911cb..db4b1aa66 100644 --- a/modules/stat-time +++ b/modules/stat-time @@ -10,6 +10,7 @@ time configure.ac: gl_STAT_TIME +gl_STAT_BIRTHTIME Makefile.am: diff --git a/modules/stat-time-tests b/modules/stat-time-tests new file mode 100644 index 000000000..8c6147bb9 --- /dev/null +++ b/modules/stat-time-tests @@ -0,0 +1,11 @@ +Files: +tests/test-stat-time.c + +Depends-on: +time + +configure.ac: + +Makefile.am: +TESTS += test-stat-time +check_PROGRAMS += test-stat-time diff --git a/tests/test-stat-time.c b/tests/test-stat-time.c new file mode 100644 index 000000000..c3262d0b9 --- /dev/null +++ b/tests/test-stat-time.c @@ -0,0 +1,171 @@ +/* Test of . + Copyright (C) 2007 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 2, 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, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Written by James Youngman , 2007. */ + +#include + +#include "stat-time.h" + +#include +#include +#include +#include +#include +#include + +#define ASSERT(condition) if (!(condition)) abort () + +enum { NFILES = 4 }; + +static void +cleanup (int sig) +{ + /* Remove temporary files. */ + unlink ("t-stt-stamp1"); + unlink ("t-stt-testfile"); + unlink ("t-stt-stamp2"); + unlink ("t-stt-renamed"); + unlink ("t-stt-stamp3"); +} + +static int +open_file (const char *filename, int flags) +{ + int fd = open (filename, flags | O_WRONLY, 0500); + if (fd >= 0) + { + close (fd); + return 1; + } + else + { + return 0; + } +} + +static void +create_file (const char *filename) +{ + ASSERT (open_file (filename, O_CREAT | O_EXCL)); +} + +static void +do_stat (const char *filename, struct stat *p) +{ + ASSERT (stat (filename, p) == 0); +} + +static void +prepare_test (struct stat *statinfo, struct timespec *modtimes) +{ + int i; + + create_file ("t-stt-stamp1"); + sleep (2); + create_file ("t-stt-testfile"); + sleep (2); + create_file ("t-stt-stamp2"); + sleep (2); + ASSERT (rename ("t-stt-testfile", "t-stt-renamed") == 0); + sleep (2); + create_file ("t-stt-stamp3"); + + do_stat ("t-stt-stamp1", &statinfo[0]); + do_stat ("t-stt-renamed", &statinfo[1]); + do_stat ("t-stt-stamp2", &statinfo[2]); + do_stat ("t-stt-stamp3", &statinfo[3]); + + /* Now use our access functions. */ + for (i = 0; i < NFILES; ++i) + { + modtimes[i] = get_stat_mtime (&statinfo[i]); + } +} + +static void +test_mtime (const struct stat *statinfo, struct timespec *modtimes) +{ + int i; + + /* Use the struct stat fields directly. */ + ASSERT (statinfo[0].st_mtime < statinfo[2].st_mtime); /* mtime(stamp1) < mtime(stamp2) */ + ASSERT (statinfo[2].st_mtime < statinfo[3].st_mtime); /* mtime(stamp2) < mtime(stamp3) */ + ASSERT (statinfo[2].st_mtime < statinfo[1].st_ctime); /* mtime(stamp2) < ctime(renamed) */ + + /* Now check the result of the access functions. */ + ASSERT (modtimes[0].tv_sec < modtimes[2].tv_sec); /* mtime(stamp1) < mtime(stamp2) */ + ASSERT (modtimes[2].tv_sec < modtimes[3].tv_sec); /* mtime(stamp2) < mtime(stamp3) */ + + /* verify equivalence */ + for (i = 0; i < NFILES; ++i) + { + struct timespec ts; + ts = get_stat_mtime (&statinfo[i]); + ASSERT (ts.tv_sec == statinfo[i].st_mtime); + } + + ASSERT (statinfo[2].st_mtime < statinfo[1].st_ctime); /* mtime(stamp2) < ctime(renamed) */ +} + +static void +test_birthtime (const struct stat *statinfo, + const struct timespec *modtimes, + struct timespec *birthtimes) +{ + int i; + + /* Collect the birth times.. */ + for (i = 0; i < NFILES; ++i) + { + if (!get_stat_birthtime (&statinfo[i], &birthtimes[i])) + { + return; + } + } + + ASSERT (modtimes[0].tv_sec < birthtimes[1].tv_sec); /* mtime(stamp1) < birthtime(renamed) */ + ASSERT (birthtimes[1].tv_sec < modtimes[2].tv_sec); /* birthtime(renamed) < mtime(stamp2) */ +} + +int +main () +{ + struct stat statinfo[NFILES]; + struct timespec modtimes[NFILES]; + struct timespec birthtimes[NFILES]; + +#ifdef SIGHUP + signal (SIGHUP, cleanup); +#endif +#ifdef SIGINT + signal (SIGINT, cleanup); +#endif +#ifdef SIGQUIT + signal (SIGQUIT, cleanup); +#endif +#ifdef SIGTERM + signal (SIGTERM, cleanup); +#endif + + prepare_test (statinfo, modtimes); + test_mtime (statinfo, modtimes); + test_birthtime (statinfo, modtimes, birthtimes); + + cleanup (0); + return 0; +} -- 2.11.0