Mercurial > hg > octave-shane > gnulib-hg
view lib/utimens.c @ 12317:407921985bc0
utimens: fix regression on Solaris
Revert commit 26c5fd742f. Solaris lacks futimens and futimes, so
futimesat is the only way to change fd timestamps. But since
FreeBSD futimesat can't change fd timestamps, we need a configure
check to avoid the crash there.
* m4/utimens.m4 (gl_UTIMENS): Check for BSD bug.
* lib/utimens.c (fdutimens): Revert 2009-11-08 change; Solaris 10
can only change fd timestamps via futimesat. Instead, use an
additional witness macro to avoid BSD bug.
Reported by Jim Meyering.
Signed-off-by: Eric Blake <ebb9@byu.net>
author | Eric Blake <ebb9@byu.net> |
---|---|
date | Wed, 18 Nov 2009 06:59:44 -0700 |
parents | f704a98798c2 |
children | fd066c132b7d |
line wrap: on
line source
/* Set file access and modification times. Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009 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 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. */ /* derived from a function in touch.c */ #include <config.h> #include "utimens.h" #include <assert.h> #include <errno.h> #include <fcntl.h> #include <stdbool.h> #include <sys/stat.h> #include <sys/time.h> #include <unistd.h> #include "stat-time.h" #include "timespec.h" #if HAVE_UTIME_H # include <utime.h> #endif /* Some systems (even some that do have <utime.h>) don't declare this structure anywhere. */ #ifndef HAVE_STRUCT_UTIMBUF struct utimbuf { long actime; long modtime; }; #endif /* Avoid recursion with rpl_futimens or rpl_utimensat. */ #undef futimens #undef utimensat #if HAVE_UTIMENSAT || HAVE_FUTIMENS /* Cache variable for whether syscall works; used to avoid calling the syscall if we know it will just fail with ENOSYS. 0 = unknown, 1 = yes, -1 = no. */ static int utimensat_works_really; #endif /* HAVE_UTIMENSAT || HAVE_UTIMENSAT */ /* Solaris 9 mistakenly succeeds when given a non-directory with a trailing slash. Force the use of rpl_stat for a fix. */ #ifndef REPLACE_FUNC_STAT_FILE # define REPLACE_FUNC_STAT_FILE 0 #endif /* Validate the requested timestamps. Return 0 if the resulting timespec can be used for utimensat (after possibly modifying it to work around bugs in utimensat). Return 1 if the timespec needs further adjustment based on stat results for utimes or other less powerful interfaces. Return -1, with errno set to EINVAL, if timespec is out of range. */ static int validate_timespec (struct timespec timespec[2]) { int result = 0; assert (timespec); if ((timespec[0].tv_nsec != UTIME_NOW && timespec[0].tv_nsec != UTIME_OMIT && (timespec[0].tv_nsec < 0 || 1000000000 <= timespec[0].tv_nsec)) || (timespec[1].tv_nsec != UTIME_NOW && timespec[1].tv_nsec != UTIME_OMIT && (timespec[1].tv_nsec < 0 || 1000000000 <= timespec[1].tv_nsec))) { errno = EINVAL; return -1; } /* Work around Linux kernel 2.6.25 bug, where utimensat fails with EINVAL if tv_sec is not 0 when using the flag values of tv_nsec. */ if (timespec[0].tv_nsec == UTIME_NOW || timespec[0].tv_nsec == UTIME_OMIT) { timespec[0].tv_sec = 0; result = 1; } if (timespec[1].tv_nsec == UTIME_NOW || timespec[1].tv_nsec == UTIME_OMIT) { timespec[1].tv_sec = 0; result = 1; } return result; } /* Normalize any UTIME_NOW or UTIME_OMIT values in *TS, using stat buffer STATBUF to obtain the current timestamps of the file. If both times are UTIME_NOW, set *TS to NULL (as this can avoid some permissions issues). If both times are UTIME_OMIT, return true (nothing further beyond the prior collection of STATBUF is necessary); otherwise return false. */ static bool update_timespec (struct stat const *statbuf, struct timespec *ts[2]) { struct timespec *timespec = *ts; if (timespec[0].tv_nsec == UTIME_OMIT && timespec[1].tv_nsec == UTIME_OMIT) return true; if (timespec[0].tv_nsec == UTIME_NOW && timespec[1].tv_nsec == UTIME_NOW) { *ts = NULL; return false; } if (timespec[0].tv_nsec == UTIME_OMIT) timespec[0] = get_stat_atime (statbuf); else if (timespec[0].tv_nsec == UTIME_NOW) gettime (×pec[0]); if (timespec[1].tv_nsec == UTIME_OMIT) timespec[1] = get_stat_mtime (statbuf); else if (timespec[1].tv_nsec == UTIME_NOW) gettime (×pec[1]); return false; } /* Set the access and modification time stamps of FD (a.k.a. FILE) to be TIMESPEC[0] and TIMESPEC[1], respectively. FD must be either negative -- in which case it is ignored -- or a file descriptor that is open on FILE. If FD is nonnegative, then FILE can be NULL, which means use just futimes (or equivalent) instead of utimes (or equivalent), and fail if on an old system without futimes (or equivalent). If TIMESPEC is null, set the time stamps to the current time. Return 0 on success, -1 (setting errno) on failure. */ int fdutimens (char const *file, int fd, struct timespec const timespec[2]) { struct timespec adjusted_timespec[2]; struct timespec *ts = timespec ? adjusted_timespec : NULL; int adjustment_needed = 0; if (ts) { adjusted_timespec[0] = timespec[0]; adjusted_timespec[1] = timespec[1]; adjustment_needed = validate_timespec (ts); } if (adjustment_needed < 0) return -1; /* Require that at least one of FD or FILE are valid. Works around a Linux bug where futimens (AT_FDCWD, NULL) changes "." rather than failing. */ if (!file) { if (fd < 0) { errno = EBADF; return -1; } if (dup2 (fd, fd) != fd) return -1; } /* Some Linux-based NFS clients are buggy, and mishandle time stamps of files in NFS file systems in some cases. We have no configure-time test for this, but please see <http://bugs.gentoo.org/show_bug.cgi?id=132673> for references to some of the problems with Linux 2.6.16. If this affects you, compile with -DHAVE_BUGGY_NFS_TIME_STAMPS; this is reported to help in some cases, albeit at a cost in performance. But you really should upgrade your kernel to a fixed version, since the problem affects many applications. */ #if HAVE_BUGGY_NFS_TIME_STAMPS if (fd < 0) sync (); else fsync (fd); #endif /* POSIX 2008 added two interfaces to set file timestamps with nanosecond resolution; newer Linux implements both functions via a single syscall. We provide a fallback for ENOSYS (for example, compiling against Linux 2.6.25 kernel headers and glibc 2.7, but running on Linux 2.6.18 kernel). */ #if HAVE_UTIMENSAT || HAVE_FUTIMENS if (0 <= utimensat_works_really) { # if HAVE_UTIMENSAT if (fd < 0) { int result = utimensat (AT_FDCWD, file, ts, 0); # ifdef __linux__ /* Work around a kernel bug: http://bugzilla.redhat.com/442352 http://bugzilla.redhat.com/449910 It appears that utimensat can mistakenly return 280 rather than -1 upon ENOSYS failure. FIXME: remove in 2010 or whenever the offending kernels are no longer in common use. */ if (0 < result) errno = ENOSYS; # endif /* __linux__ */ if (result == 0 || errno != ENOSYS) { utimensat_works_really = 1; return result; } } # endif /* HAVE_UTIMENSAT */ # if HAVE_FUTIMENS { int result = futimens (fd, ts); # ifdef __linux__ /* Work around the same bug as above. */ if (0 < result) errno = ENOSYS; # endif /* __linux__ */ if (result == 0 || errno != ENOSYS) { utimensat_works_really = 1; return result; } } # endif /* HAVE_FUTIMENS */ } utimensat_works_really = -1; #endif /* HAVE_UTIMENSAT || HAVE_FUTIMENS */ /* The platform lacks an interface to set file timestamps with nanosecond resolution, so do the best we can, discarding any fractional part of the timestamp. */ if (adjustment_needed || (REPLACE_FUNC_STAT_FILE && fd < 0)) { struct stat st; if (fd < 0 ? stat (file, &st) : fstat (fd, &st)) return -1; if (ts && update_timespec (&st, &ts)) return 0; } { #if HAVE_FUTIMESAT || HAVE_WORKING_UTIMES struct timeval timeval[2]; struct timeval const *t; if (ts) { timeval[0].tv_sec = ts[0].tv_sec; timeval[0].tv_usec = ts[0].tv_nsec / 1000; timeval[1].tv_sec = ts[1].tv_sec; timeval[1].tv_usec = ts[1].tv_nsec / 1000; t = timeval; } else t = NULL; if (fd < 0) { # if HAVE_FUTIMESAT return futimesat (AT_FDCWD, file, t); # endif } else { /* If futimesat or futimes fails here, don't try to speed things up by returning right away. glibc can incorrectly fail with errno == ENOENT if /proc isn't mounted. Also, Mandrake 10.0 in high security mode doesn't allow ordinary users to read /proc/self, so glibc incorrectly fails with errno == EACCES. If errno == EIO, EPERM, or EROFS, it's probably safe to fail right away, but these cases are rare enough that they're not worth optimizing, and who knows what other messed-up systems are out there? So play it safe and fall back on the code below. */ # if HAVE_FUTIMESAT && !FUTIMESAT_NULL_BUG if (futimesat (fd, NULL, t) == 0) return 0; # elif HAVE_FUTIMES if (futimes (fd, t) == 0) return 0; # endif } #endif /* HAVE_FUTIMESAT || HAVE_WORKING_UTIMES */ if (!file) { #if ! ((HAVE_FUTIMESAT && !FUTIMESAT_NULL_BUG) \ || (HAVE_WORKING_UTIMES && HAVE_FUTIMES)) errno = ENOSYS; #endif return -1; } #if HAVE_WORKING_UTIMES return utimes (file, t); #else { struct utimbuf utimbuf; struct utimbuf *ut; if (ts) { utimbuf.actime = ts[0].tv_sec; utimbuf.modtime = ts[1].tv_sec; ut = &utimbuf; } else ut = NULL; return utime (file, ut); } #endif /* !HAVE_WORKING_UTIMES */ } } /* Set the access and modification time stamps of FD (a.k.a. FILE) to be TIMESPEC[0] and TIMESPEC[1], respectively. FD must be either negative -- in which case it is ignored -- or a file descriptor that is open on FILE. If FD is nonnegative, then FILE can be NULL, which means use just futimes (or equivalent) instead of utimes (or equivalent), and fail if on an old system without futimes (or equivalent). If TIMESPEC is null, set the time stamps to the current time. Return 0 on success, -1 (setting errno) on failure. */ int gl_futimens (int fd, char const *file, struct timespec const timespec[2]) { return fdutimens (file, fd, timespec); } /* Set the access and modification time stamps of FILE to be TIMESPEC[0] and TIMESPEC[1], respectively. */ int utimens (char const *file, struct timespec const timespec[2]) { return fdutimens (file, -1, timespec); } /* Set the access and modification time stamps of FILE to be TIMESPEC[0] and TIMESPEC[1], respectively, without dereferencing symlinks. Fail with ENOSYS if the platform does not support changing symlink timestamps, but FILE was a symlink. */ int lutimens (char const *file, struct timespec const timespec[2]) { struct timespec adjusted_timespec[2]; struct timespec *ts = timespec ? adjusted_timespec : NULL; int adjustment_needed = 0; struct stat st; if (ts) { adjusted_timespec[0] = timespec[0]; adjusted_timespec[1] = timespec[1]; adjustment_needed = validate_timespec (ts); } if (adjustment_needed < 0) return -1; /* The Linux kernel did not support symlink timestamps until utimensat, in version 2.6.22, so we don't need to mimic gl_futimens' worry about buggy NFS clients. But we do have to worry about bogus return values. */ #if HAVE_UTIMENSAT if (0 <= utimensat_works_really) { int result = utimensat (AT_FDCWD, file, ts, AT_SYMLINK_NOFOLLOW); # ifdef __linux__ /* Work around a kernel bug: http://bugzilla.redhat.com/442352 http://bugzilla.redhat.com/449910 It appears that utimensat can mistakenly return 280 rather than -1 upon ENOSYS failure. FIXME: remove in 2010 or whenever the offending kernels are no longer in common use. */ if (0 < result) errno = ENOSYS; # endif if (result == 0 || errno != ENOSYS) { utimensat_works_really = 1; return result; } } utimensat_works_really = -1; #endif /* HAVE_UTIMENSAT */ /* The platform lacks an interface to set file timestamps with nanosecond resolution, so do the best we can, discarding any fractional part of the timestamp. */ if (adjustment_needed || REPLACE_FUNC_STAT_FILE) { if (lstat (file, &st)) return -1; if (ts && update_timespec (&st, &ts)) return 0; } #if HAVE_LUTIMES { struct timeval timeval[2]; struct timeval const *t; if (ts) { timeval[0].tv_sec = ts[0].tv_sec; timeval[0].tv_usec = ts[0].tv_nsec / 1000; timeval[1].tv_sec = ts[1].tv_sec; timeval[1].tv_usec = ts[1].tv_nsec / 1000; t = timeval; } else t = NULL; return lutimes (file, t); } #endif /* HAVE_LUTIMES */ /* Out of luck for symlinks, but we still handle regular files. */ if (!(adjustment_needed || REPLACE_FUNC_STAT_FILE) && lstat (file, &st)) return -1; if (!S_ISLNK (st.st_mode)) return fdutimens (file, -1, ts); errno = ENOSYS; return -1; }