Discussion:
[PATCH] stat: work around Solaris bug with tv_nsec < 0
Add Reply
Paul Eggert
2017-11-23 08:07:10 UTC
Reply
Permalink
Raw Message
* doc/posix-functions/fstat.texi (fstat):
* doc/posix-functions/fstatat.texi (fstatat):
* doc/posix-functions/lstat.texi (lstat):
* doc/posix-functions/stat.texi (stat):
Mention Solaris 11 bug.
* lib/fstat.c, lib/fstatat.c, lib/lstat.c: Include stat-time.h.
* lib/fstat.c (rpl_fstat) [!WINDOWS_NATIVE]:
* lib/lstat.c (rpl_lstat):
* lib/stat.c (rpl_stat):
Normalize resulting timestamps.
* lib/fstatat.c (normal_fstatat): New function.
(rpl_fstatat): Use it.
* lib/stat-time.h: Include intprops.h, errno.h, stddef.h.
(stat_time_normalize): New function.
* m4/fstat.m4 (gl_FUNC_FSTAT):
* m4/fstatat.m4 (gl_FUNC_FSTATAT):
* m4/lstat.m4 (gl_FUNC_LSTAT):
* m4/stat.m4 (gl_FUNC_STAT):
Replace on Solaris.
* modules/fstat (Depends-on):
* modules/fstatat (Depends-on):
Add stat-time.
* modules/stat-time (Depends-on): Add errno, intprops.
---
ChangeLog | 27 ++++++++++++++++++++++++
doc/posix-functions/fstat.texi | 5 +++++
doc/posix-functions/fstatat.texi | 5 +++++
doc/posix-functions/lstat.texi | 5 +++++
doc/posix-functions/stat.texi | 5 +++++
lib/fstat.c | 4 +++-
lib/fstatat.c | 12 +++++++++--
lib/lstat.c | 39 ++++++++++++++++++----------------
lib/stat-time.h | 45 ++++++++++++++++++++++++++++++++++++++++
lib/stat.c | 22 ++++++++++++--------
m4/fstat.m4 | 7 ++++---
m4/fstatat.m4 | 19 ++++++++++-------
m4/lstat.m4 | 7 ++++---
m4/stat.m4 | 7 ++++++-
modules/fstat | 1 +
modules/fstatat | 1 +
modules/stat-time | 2 ++
17 files changed, 169 insertions(+), 44 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index edcb62d08..700ee09c5 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,30 @@
+2017-11-23 Paul Eggert <***@cs.ucla.edu>
+
+ stat: work around Solaris bug with tv_nsec < 0
+ * doc/posix-functions/fstat.texi (fstat):
+ * doc/posix-functions/fstatat.texi (fstatat):
+ * doc/posix-functions/lstat.texi (lstat):
+ * doc/posix-functions/stat.texi (stat):
+ Mention Solaris 11 bug.
+ * lib/fstat.c, lib/fstatat.c, lib/lstat.c: Include stat-time.h.
+ * lib/fstat.c (rpl_fstat) [!WINDOWS_NATIVE]:
+ * lib/lstat.c (rpl_lstat):
+ * lib/stat.c (rpl_stat):
+ Normalize resulting timestamps.
+ * lib/fstatat.c (normal_fstatat): New function.
+ (rpl_fstatat): Use it.
+ * lib/stat-time.h: Include intprops.h, errno.h, stddef.h.
+ (stat_time_normalize): New function.
+ * m4/fstat.m4 (gl_FUNC_FSTAT):
+ * m4/fstatat.m4 (gl_FUNC_FSTATAT):
+ * m4/lstat.m4 (gl_FUNC_LSTAT):
+ * m4/stat.m4 (gl_FUNC_STAT):
+ Replace on Solaris.
+ * modules/fstat (Depends-on):
+ * modules/fstatat (Depends-on):
+ Add stat-time.
+ * modules/stat-time (Depends-on): Add errno, intprops.
+
2017-11-22 Paul Eggert <***@cs.ucla.edu>

regex: merge from glibc
diff --git a/doc/posix-functions/fstat.texi b/doc/posix-functions/fstat.texi
index 494889cb5..65dfd2182 100644
--- a/doc/posix-functions/fstat.texi
+++ b/doc/posix-functions/fstat.texi
@@ -16,6 +16,11 @@ On platforms where @code{off_t} is a 32-bit type, @code{fstat} may not correctly
report the size of files or block devices larger than 2 GB.
(Cf. @code{AC_SYS_LARGEFILE}.)
@item
+On Solaris 11.3, when this function yields a timestamp with a
+nonpositive @code{tv_sec} value, @code{tv_nsec} might in the range
+@minus{}***@minus{}1, representing a negative nanoseconds
+offset from @code{tv_sec}.
+@item
The @code{st_atime}, @code{st_ctime}, @code{st_mtime} fields are affected by
the current time zone and by the DST flag of the current time zone on some
platforms:
diff --git a/doc/posix-functions/fstatat.texi b/doc/posix-functions/fstatat.texi
index 1eaadcb81..69cd62b47 100644
--- a/doc/posix-functions/fstatat.texi
+++ b/doc/posix-functions/fstatat.texi
@@ -25,6 +25,11 @@ Solaris 9.
For symlinks, when the argument ends in a slash, some platforms don't
dereference the argument:
Solaris 9.
+@item
+On Solaris 11.3, when this function yields a timestamp with a
+nonpositive @code{tv_sec} value, @code{tv_nsec} might in the range
+@minus{}***@minus{}1, representing a negative nanoseconds
+offset from @code{tv_sec}.
@end itemize

Portability problems not fixed by Gnulib:
diff --git a/doc/posix-functions/lstat.texi b/doc/posix-functions/lstat.texi
index 6f5cfc25c..9c7e85ec6 100644
--- a/doc/posix-functions/lstat.texi
+++ b/doc/posix-functions/lstat.texi
@@ -21,6 +21,11 @@ On some platforms, @code{lstat("file/",buf)} succeeds instead of
failing with @code{ENOTDIR}.
Solaris 9.
@item
+On Solaris 11.3, when this function yields a timestamp with a
+nonpositive @code{tv_sec} value, @code{tv_nsec} might in the range
+@minus{}***@minus{}1, representing a negative nanoseconds
+offset from @code{tv_sec}.
+@item
On Windows platforms (excluding Cygwin), symlinks are not supported, so
@code{lstat} does not exist.
@end itemize
diff --git a/doc/posix-functions/stat.texi b/doc/posix-functions/stat.texi
index d1e0f38c9..d5f73ef91 100644
--- a/doc/posix-functions/stat.texi
+++ b/doc/posix-functions/stat.texi
@@ -29,6 +29,11 @@ FreeBSD 7.2, AIX 7.1, Solaris 9, mingw64.
On some platforms, @code{stat(".",buf)} and @code{stat("./",buf)} give
different results:
mingw, MSVC 14.
+@item
+On Solaris 11.3, when this function yields a timestamp with a
+nonpositive @code{tv_sec} value, @code{tv_nsec} might in the range
+@minus{}***@minus{}1, representing a negative nanoseconds
+offset from @code{tv_sec}.
@end itemize

Portability problems not fixed by Gnulib:
diff --git a/lib/fstat.c b/lib/fstat.c
index 7ab2cdcb9..3a60ebf12 100644
--- a/lib/fstat.c
+++ b/lib/fstat.c
@@ -45,6 +45,8 @@ orig_fstat (int fd, struct stat *buf)
above. */
#include "sys/stat.h"

+#include "stat-time.h"
+
#include <errno.h>
#include <unistd.h>
#ifdef WINDOWS_NATIVE
@@ -83,6 +85,6 @@ rpl_fstat (int fd, struct stat *buf)
}
return _gl_fstat_by_handle (h, NULL, buf);
#else
- return orig_fstat (fd, buf);
+ return stat_time_normalize (orig_fstat (fd, buf), buf);
#endif
}
diff --git a/lib/fstatat.c b/lib/fstatat.c
index 294861f51..237e68c5d 100644
--- a/lib/fstatat.c
+++ b/lib/fstatat.c
@@ -41,6 +41,8 @@ orig_fstatat (int fd, char const *filename, struct stat *buf, int flags)
above. */
#include "sys/stat.h"

+#include "stat-time.h"
+
#include <errno.h>
#include <fcntl.h>
#include <string.h>
@@ -51,6 +53,12 @@ orig_fstatat (int fd, char const *filename, struct stat *buf, int flags)
# define LSTAT_FOLLOWS_SLASHED_SYMLINK 0
# endif

+static int
+normal_fstatat (int fd, char const *file, struct stat *st, int flag)
+{
+ return stat_time_normalize (orig_fstatat (fd, file, st, flag), st);
+}
+
/* fstatat should always follow symbolic links that end in /, but on
Solaris 9 it doesn't if AT_SYMLINK_NOFOLLOW is specified.
Likewise, trailing slash on a non-directory should be an error.
@@ -63,7 +71,7 @@ orig_fstatat (int fd, char const *filename, struct stat *buf, int flags)
int
rpl_fstatat (int fd, char const *file, struct stat *st, int flag)
{
- int result = orig_fstatat (fd, file, st, flag);
+ int result = normal_fstatat (fd, file, st, flag);
size_t len;

if (LSTAT_FOLLOWS_SLASHED_SYMLINK || result != 0)
@@ -79,7 +87,7 @@ rpl_fstatat (int fd, char const *file, struct stat *st, int flag)
errno = ENOTDIR;
return -1;
}
- result = orig_fstatat (fd, file, st, flag & ~AT_SYMLINK_NOFOLLOW);
+ result = normal_fstatat (fd, file, st, flag & ~AT_SYMLINK_NOFOLLOW);
}
/* Fix stat behavior. */
if (result == 0 && !S_ISDIR (st->st_mode) && file[len - 1] == '/')
diff --git a/lib/lstat.c b/lib/lstat.c
index c721a4e64..f3c617795 100644
--- a/lib/lstat.c
+++ b/lib/lstat.c
@@ -47,6 +47,8 @@ orig_lstat (const char *filename, struct stat *buf)
above. */
# include "sys/stat.h"

+# include "stat-time.h"
+
# include <string.h>
# include <errno.h>

@@ -66,32 +68,33 @@ orig_lstat (const char *filename, struct stat *buf)
int
rpl_lstat (const char *file, struct stat *sbuf)
{
- size_t len;
- int lstat_result = orig_lstat (file, sbuf);
-
- if (lstat_result != 0)
- return lstat_result;
+ int result = orig_lstat (file, sbuf);

/* This replacement file can blindly check against '/' rather than
using the ISSLASH macro, because all platforms with '\\' either
lack symlinks (mingw) or have working lstat (cygwin) and thus do
not compile this file. 0 len should have already been filtered
out above, with a failure return of ENOENT. */
- len = strlen (file);
- if (file[len - 1] != '/' || S_ISDIR (sbuf->st_mode))
- return 0;
-
- /* At this point, a trailing slash is only permitted on
- symlink-to-dir; but it should have found information on the
- directory, not the symlink. Call stat() to get info about the
- link's referent. Our replacement stat guarantees valid results,
- even if the symlink is not pointing to a directory. */
- if (!S_ISLNK (sbuf->st_mode))
+ if (result == 0)
{
- errno = ENOTDIR;
- return -1;
+ if (S_ISDIR (sbuf->st_mode) || file[strlen (file) - 1] != '/')
+ result = stat_time_normalize (result, sbuf);
+ else
+ {
+ /* At this point, a trailing slash is permitted only on
+ symlink-to-dir; but it should have found information on the
+ directory, not the symlink. Call 'stat' to get info about the
+ link's referent. Our replacement stat guarantees valid results,
+ even if the symlink is not pointing to a directory. */
+ if (!S_ISLNK (sbuf->st_mode))
+ {
+ errno = ENOTDIR;
+ return -1;
+ }
+ result = stat (file, sbuf);
+ }
}
- return stat (file, sbuf);
+ return result;
}

#endif /* HAVE_LSTAT */
diff --git a/lib/stat-time.h b/lib/stat-time.h
index 47a3bf8f2..1cf821992 100644
--- a/lib/stat-time.h
+++ b/lib/stat-time.h
@@ -20,6 +20,10 @@
#ifndef STAT_TIME_H
#define STAT_TIME_H 1

+#include "intprops.h"
+
+#include <errno.h>
+#include <stddef.h>
#include <sys/stat.h>
#include <time.h>

@@ -202,6 +206,47 @@ get_stat_birthtime (struct stat const *st)
return t;
}

+/* If a stat-like function returned RESULT, normalize the timestamps
+ in *ST, in case this platform suffers from the Solaris 11 bug where
+ tv_nsec might be negative. Return the adjusted RESULT, setting
+ errno to EOVERFLOW if normalization overflowed. This function
+ is intended to be private to this .h file. */
+_GL_STAT_TIME_INLINE int
+stat_time_normalize (int result, struct stat *st)
+{
+#if defined __sun && defined STAT_TIMESPEC
+ if (result == 0)
+ {
+ long int timespec_resolution = 1000000000;
+ short int const ts_off[] = { offsetof (struct stat, st_atim),
+ offsetof (struct stat, st_mtim),
+ offsetof (struct stat, st_ctim) };
+ int i;
+ for (i = 0; i < sizeof ts_off / sizeof *ts_off; i++)
+ {
+ struct timespec *ts = (struct timespec *) ((char *) st + ts_off[i]);
+ long int q = ts->tv_nsec / timespec_resolution;
+ long int r = ts->tv_nsec % timespec_resolution;
+ if (r < 0)
+ {
+ r += timespec_resolution;
+ q--;
+ }
+ ts->tv_nsec = r;
+ /* Overflow is possible, as Solaris 11 stat can yield
+ tv_sec == TYPE_MINIMUM (time_t) && tv_nsec == -1000000000.
+ INT_ADD_WRAPV is OK, since time_t is signed on Solaris. */
+ if (INT_ADD_WRAPV (q, ts->tv_sec, &ts->tv_sec))
+ {
+ errno = EOVERFLOW;
+ return -1;
+ }
+ }
+ }
+#endif
+ return result;
+}
+
#ifdef __cplusplus
}
#endif
diff --git a/lib/stat.c b/lib/stat.c
index 061496322..30ba67efc 100644
--- a/lib/stat.c
+++ b/lib/stat.c
@@ -405,19 +405,23 @@ rpl_stat (char const *name, struct stat *buf)
}
#else
int result = orig_stat (name, buf);
-# if REPLACE_FUNC_STAT_FILE
- /* Solaris 9 mistakenly succeeds when given a non-directory with a
- trailing slash. */
- if (result == 0 && !S_ISDIR (buf->st_mode))
+ if (result == 0)
{
- size_t len = strlen (name);
- if (ISSLASH (name[len - 1]))
+# if REPLACE_FUNC_STAT_FILE
+ /* Solaris 9 mistakenly succeeds when given a non-directory with a
+ trailing slash. */
+ if (!S_ISDIR (buf->st_mode))
{
- errno = ENOTDIR;
- return -1;
+ size_t len = strlen (name);
+ if (ISSLASH (name[len - 1]))
+ {
+ errno = ENOTDIR;
+ return -1;
+ }
}
- }
# endif /* REPLACE_FUNC_STAT_FILE */
+ result = stat_time_normalize (result, buf);
+ }
return result;
#endif
}
diff --git a/m4/fstat.m4 b/m4/fstat.m4
index e70e533e7..256218935 100644
--- a/m4/fstat.m4
+++ b/m4/fstat.m4
@@ -1,4 +1,4 @@
-# fstat.m4 serial 5
+# fstat.m4 serial 6
dnl Copyright (C) 2011-2017 Free Software Foundation, Inc.
dnl This file is free software; the Free Software Foundation
dnl gives unlimited permission to copy and/or distribute it,
@@ -10,9 +10,10 @@ AC_DEFUN([gl_FUNC_FSTAT],
AC_REQUIRE([gl_SYS_STAT_H_DEFAULTS])

case "$host_os" in
- mingw*)
- dnl On this platform, the original stat() returns st_atime, st_mtime,
+ mingw* | solaris*)
+ dnl On MinGW, the original stat() returns st_atime, st_mtime,
dnl st_ctime values that are affected by the time zone.
+ dnl Solaris stat can return a negative tv_nsec.
REPLACE_FSTAT=1
;;
esac
diff --git a/m4/fstatat.m4 b/m4/fstatat.m4
index 7dba52796..767eb83db 100644
--- a/m4/fstatat.m4
+++ b/m4/fstatat.m4
@@ -1,4 +1,4 @@
-# fstatat.m4 serial 3
+# fstatat.m4 serial 4
dnl Copyright (C) 2004-2017 Free Software Foundation, Inc.
dnl This file is free software; the Free Software Foundation
dnl gives unlimited permission to copy and/or distribute it,
@@ -13,7 +13,7 @@ AC_DEFUN([gl_FUNC_FSTATAT],
AC_REQUIRE([gl_SYS_STAT_H_DEFAULTS])
AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS])
AC_REQUIRE([gl_FUNC_LSTAT_FOLLOWS_SLASHED_SYMLINK])
- AC_REQUIRE([AC_CANONICAL_HOST]) dnl for cross-compiles
+ AC_REQUIRE([AC_CANONICAL_HOST])
AC_CHECK_FUNCS_ONCE([fstatat])

if test $ac_cv_func_fstatat = no; then
@@ -46,15 +46,20 @@ AC_DEFUN([gl_FUNC_FSTATAT],

case $gl_cv_func_fstatat_zero_flag+$gl_cv_func_lstat_dereferences_slashed_symlink in
*yes+*yes) ;;
- *) REPLACE_FSTATAT=1
- case $gl_cv_func_fstatat_zero_flag in
- *yes)
+ *) REPLACE_FSTATAT=1 ;;
+ esac
+
+ case $host_os in
+ solaris*)
+ REPLACE_FSTATAT=1 ;;
+ esac
+
+ case $REPLACE_FSTATAT,$gl_cv_func_fstatat_zero_flag in
+ 1,*yes)
AC_DEFINE([HAVE_WORKING_FSTATAT_ZERO_FLAG], [1],
[Define to 1 if fstatat (..., 0) works.
For example, it does not work in AIX 7.1.])
;;
- esac
- ;;
esac
fi
])
diff --git a/m4/lstat.m4 b/m4/lstat.m4
index 0b6e5d70c..6ba18cec5 100644
--- a/m4/lstat.m4
+++ b/m4/lstat.m4
@@ -1,4 +1,4 @@
-# serial 29
+# serial 30

# Copyright (C) 1997-2001, 2003-2017 Free Software Foundation, Inc.
#
@@ -10,14 +10,15 @@ dnl From Jim Meyering.

AC_DEFUN([gl_FUNC_LSTAT],
[
+ AC_REQUIRE([AC_CANONICAL_HOST])
AC_REQUIRE([gl_SYS_STAT_H_DEFAULTS])
dnl If lstat does not exist, the replacement <sys/stat.h> does
dnl "#define lstat stat", and lstat.c is a no-op.
AC_CHECK_FUNCS_ONCE([lstat])
if test $ac_cv_func_lstat = yes; then
AC_REQUIRE([gl_FUNC_LSTAT_FOLLOWS_SLASHED_SYMLINK])
- case "$gl_cv_func_lstat_dereferences_slashed_symlink" in
- *no)
+ case $host_os,$gl_cv_func_lstat_dereferences_slashed_symlink in
+ solaris* | *no)
REPLACE_LSTAT=1
;;
esac
diff --git a/m4/stat.m4 b/m4/stat.m4
index 286fcba84..183a70ffe 100644
--- a/m4/stat.m4
+++ b/m4/stat.m4
@@ -1,4 +1,4 @@
-# serial 12
+# serial 13

# Copyright (C) 2009-2017 Free Software Foundation, Inc.
#
@@ -56,6 +56,11 @@ AC_DEFUN([gl_FUNC_STAT],
AC_DEFINE([REPLACE_FUNC_STAT_FILE], [1], [Define to 1 if stat needs
help when passed a file name with a trailing slash]);;
esac
+ case $host_os in
+ dnl Solaris stat can return a negative tv_nsec.
+ solaris*)
+ REPLACE_FSTAT=1 ;;
+ esac
;;
esac
])
diff --git a/modules/fstat b/modules/fstat
index e49894184..b557494cf 100644
--- a/modules/fstat
+++ b/modules/fstat
@@ -11,6 +11,7 @@ Depends-on:
sys_stat
largefile
pathmax [test $REPLACE_FSTAT = 1]
+stat-time [test $REPLACE_FSTAT = 1]
unistd [test $REPLACE_FSTAT = 1]
verify [test $REPLACE_FSTAT = 1]
msvc-nothrow [test $REPLACE_FSTAT = 1]
diff --git a/modules/fstatat b/modules/fstatat
index 3d25cc8ec..c31184675 100644
--- a/modules/fstatat
+++ b/modules/fstatat
@@ -20,6 +20,7 @@ lstat [test $HAVE_FSTATAT = 0 || test $REPLACE_FSTATAT = 1]
openat-die [test $HAVE_FSTATAT = 0 || test $REPLACE_FSTATAT = 1]
openat-h [test $HAVE_FSTATAT = 0 || test $REPLACE_FSTATAT = 1]
save-cwd [test $HAVE_FSTATAT = 0 || test $REPLACE_FSTATAT = 1]
+stat-time [test $HAVE_FSTATAT = 0 || test $REPLACE_FSTATAT = 1]

configure.ac:
gl_FUNC_FSTATAT
diff --git a/modules/stat-time b/modules/stat-time
index feeb5106c..2f137fc25 100644
--- a/modules/stat-time
+++ b/modules/stat-time
@@ -8,8 +8,10 @@ m4/stat-time.m4

Depends-on:
time
+errno
extensions
extern-inline
+intprops

configure.ac:
gl_STAT_TIME
--
2.14.3
Loading...