Tested on: Linux (ext3) (no st_birthtime support at all) NetBSD-3.1 (UFS1) (st_birthtime support in kernel but not filesystem) NetBSD-3.1 (UFS2) (st_birthtime support both kernel and filesystem) NetBSD-3.1 (msdos) (st_birthtime support in kernel but not in filesystem) Needs testing on everything else, notably including: Cygwin CVS (i.e. version 1.7 or later) mingw Systems completely lacking nanosecond timestamps
Style issue: The test case perhaps should not ASSERT that the test files can be created. It should arguably skip the test withough failing if that happens. 2007-03-27 James Youngman <[EMAIL PROTECTED]> * 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 test * tests/test-stat-time.c: New test, devised by Bruno Haible. Index: lib/stat-time.h =================================================================== RCS file: /sources/gnulib/gnulib/lib/stat-time.h,v retrieving revision 1.4 diff -u -p -r1.4 stat-time.h --- lib/stat-time.h 23 Feb 2007 18:25:21 -0000 1.4 +++ lib/stat-time.h 27 Mar 2007 08:06:22 -0000 @@ -44,6 +44,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 +96,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 +160,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 + * http://msdn2.microsoft.com/de-de/library/14h5k7ff(VS.80).aspx + */ + 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 Index: m4/stat-time.m4 =================================================================== RCS file: /sources/gnulib/gnulib/m4/stat-time.m4,v retrieving revision 1.4 diff -u -p -r1.4 stat-time.m4 --- m4/stat-time.m4 18 Jan 2007 08:33:35 -0000 1.4 +++ m4/stat-time.m4 27 Mar 2007 08:06:22 -0000 @@ -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 <sys/types.h> #include <sys/stat.h>]) ]) + +# 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 <sys/types.h> + #include <sys/stat.h>])], + [#include <sys/types.h> + #include <sys/stat.h>])], + [#include <sys/types.h> + #include <sys/stat.h>]) +]) Index: modules/stat-time =================================================================== RCS file: /sources/gnulib/gnulib/modules/stat-time,v retrieving revision 1.4 diff -u -p -r1.4 stat-time --- modules/stat-time 12 Feb 2007 18:49:19 -0000 1.4 +++ modules/stat-time 27 Mar 2007 08:06:22 -0000 @@ -10,6 +10,7 @@ time configure.ac: gl_STAT_TIME +gl_STAT_BIRTHTIME Makefile.am: =================================================================== --- /dev/null 2007-03-26 11:36:11.944059000 +0100 +++ modules/stat-time-tests 2007-03-26 00:49:57.000000000 +0100 @@ -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 =================================================================== --- /dev/null 2007-03-26 11:36:11.944059000 +0100 +++ tests/test-stat-time.c 2007-03-26 02:02:39.000000000 +0100 @@ -0,0 +1,160 @@ +/* Test of <stat-time.h>. + 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 <[EMAIL PROTECTED]>, 2007. */ + +#include <config.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <sys/fcntl.h> + +#include "stat-time.h" + +enum { NFILES = 5 }; + + + +#define ASSERT(condition) if (!(condition)) abort () + +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 int +touch_file(const char *filename) +{ + ASSERT(open_file(filename, O_CREAT)); +} + +static void +do_stat(const char *filename, struct stat *p) +{ + ASSERT(0 == stat(filename, p)); +} + + +static int +prepare_test(struct stat *statinfo, struct timespec *modtimes) +{ + int i; + + create_file("stamp1"); + sleep(2); + create_file("testfile"); + sleep(2); + create_file("stamp2"); + sleep(2); + ASSERT(0 == rename("testfile", "renamed")); + sleep(2); + create_file("stamp3"); + + do_stat("stamp1", &statinfo[0]); + do_stat("renamed", &statinfo[1]); + do_stat("stamp2", &statinfo[2]); + do_stat("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 int +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 (1 || !get_stat_birthtime(&statinfo[i], &birthtimes[i])) + { + return 0; + } + } + + 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 () +{ + int skipflag = 0; + struct stat statinfo[NFILES]; + struct timespec modtimes[NFILES]; + struct timespec birthtimes[NFILES]; + + prepare_test(statinfo, modtimes); + test_mtime (statinfo, modtimes); + if (!test_birthtime(statinfo, modtimes, birthtimes)) + skipflag = 1; + + unlink("stamp1"); + unlink("renamed"); + unlink("stamp2"); + unlink("stamp3"); + return 0; +} -- James Youngman Dublin, Ireland