root/maint/gnulib/lib/utimensat.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. rpl_utimensat
  2. rpl_utimensat

   1 /* Set the access and modification time of a file relative to directory fd.
   2    Copyright (C) 2009-2021 Free Software Foundation, Inc.
   3 
   4    This program is free software: you can redistribute it and/or modify
   5    it under the terms of the GNU General Public License as published by
   6    the Free Software Foundation; either version 3 of the License, or
   7    (at your option) any later version.
   8 
   9    This program is distributed in the hope that it will be useful,
  10    but WITHOUT ANY WARRANTY; without even the implied warranty of
  11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12    GNU General Public License for more details.
  13 
  14    You should have received a copy of the GNU General Public License
  15    along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
  16 
  17 /* written by Eric Blake */
  18 
  19 #include <config.h>
  20 
  21 /* Specification.  */
  22 #include <sys/stat.h>
  23 
  24 #include <errno.h>
  25 #include <fcntl.h>
  26 #include <stdlib.h>
  27 #include <string.h>
  28 #include <sys/stat.h>
  29 
  30 #include "stat-time.h"
  31 #include "timespec.h"
  32 #include "utimens.h"
  33 
  34 #if HAVE_NEARLY_WORKING_UTIMENSAT
  35 
  36 /* Use the original utimensat(), but correct the trailing slash handling.  */
  37 int
  38 rpl_utimensat (int fd, char const *file, struct timespec const times[2],
     /* [previous][next][first][last][top][bottom][index][help] */
  39                int flag)
  40 # undef utimensat
  41 {
  42   size_t len = strlen (file);
  43   if (len && file[len - 1] == '/')
  44     {
  45       struct stat st;
  46       if (fstatat (fd, file, &st, flag & AT_SYMLINK_NOFOLLOW) < 0)
  47         return -1;
  48       if (!S_ISDIR (st.st_mode))
  49         {
  50           errno = ENOTDIR;
  51           return -1;
  52         }
  53     }
  54 
  55   return utimensat (fd, file, times, flag);
  56 }
  57 
  58 #else
  59 
  60 # if HAVE_UTIMENSAT
  61 
  62 /* If we have a native utimensat, but are compiling this file, then
  63    utimensat was defined to rpl_utimensat by our replacement
  64    sys/stat.h.  We assume the native version might fail with ENOSYS,
  65    or succeed without properly affecting ctime (as is the case when
  66    using newer glibc but older Linux kernel).  In this scenario,
  67    rpl_utimensat checks whether the native version is usable, and
  68    local_utimensat provides the fallback manipulation.  */
  69 
  70 static int local_utimensat (int, char const *, struct timespec const[2], int);
  71 #  define AT_FUNC_NAME local_utimensat
  72 
  73 /* Like utimensat, but work around native bugs.  */
  74 
  75 int
  76 rpl_utimensat (int fd, char const *file, struct timespec const times[2],
     /* [previous][next][first][last][top][bottom][index][help] */
  77                int flag)
  78 #  undef utimensat
  79 {
  80 #  if defined __linux__ || defined __sun
  81   struct timespec ts[2];
  82 #  endif
  83 
  84   /* See comments in utimens.c for details.  */
  85   static int utimensat_works_really; /* 0 = unknown, 1 = yes, -1 = no.  */
  86   if (0 <= utimensat_works_really)
  87     {
  88       int result;
  89 #  if defined __linux__ || defined __sun
  90       struct stat st;
  91       /* As recently as Linux kernel 2.6.32 (Dec 2009), several file
  92          systems (xfs, ntfs-3g) have bugs with a single UTIME_OMIT,
  93          but work if both times are either explicitly specified or
  94          UTIME_NOW.  Work around it with a preparatory [l]stat prior
  95          to calling utimensat; fortunately, there is not much timing
  96          impact due to the extra syscall even on file systems where
  97          UTIME_OMIT would have worked.
  98 
  99          The same bug occurs in Solaris 11.1 (Apr 2013).
 100 
 101          FIXME: Simplify this in 2024, when these file system bugs are
 102          no longer common on Gnulib target platforms.  */
 103       if (times && (times[0].tv_nsec == UTIME_OMIT
 104                     || times[1].tv_nsec == UTIME_OMIT))
 105         {
 106           if (fstatat (fd, file, &st, flag))
 107             return -1;
 108           if (times[0].tv_nsec == UTIME_OMIT && times[1].tv_nsec == UTIME_OMIT)
 109             return 0;
 110           if (times[0].tv_nsec == UTIME_OMIT)
 111             ts[0] = get_stat_atime (&st);
 112           else
 113             ts[0] = times[0];
 114           if (times[1].tv_nsec == UTIME_OMIT)
 115             ts[1] = get_stat_mtime (&st);
 116           else
 117             ts[1] = times[1];
 118           times = ts;
 119         }
 120 #   ifdef __hppa__
 121       /* Linux kernel 2.6.22.19 on hppa does not reject invalid tv_nsec
 122          values.  */
 123       else if (times
 124                && ((times[0].tv_nsec != UTIME_NOW
 125                     && ! (0 <= times[0].tv_nsec
 126                           && times[0].tv_nsec < TIMESPEC_HZ))
 127                    || (times[1].tv_nsec != UTIME_NOW
 128                        && ! (0 <= times[1].tv_nsec
 129                              && times[1].tv_nsec < TIMESPEC_HZ))))
 130         {
 131           errno = EINVAL;
 132           return -1;
 133         }
 134 #   endif
 135 #  endif
 136 #  if defined __APPLE__ && defined __MACH__
 137       /* macOS 10.13 does not reject invalid tv_nsec values either.  */
 138       if (times
 139           && ((times[0].tv_nsec != UTIME_OMIT
 140                && times[0].tv_nsec != UTIME_NOW
 141                && ! (0 <= times[0].tv_nsec
 142                      && times[0].tv_nsec < TIMESPEC_HZ))
 143               || (times[1].tv_nsec != UTIME_OMIT
 144                   && times[1].tv_nsec != UTIME_NOW
 145                   && ! (0 <= times[1].tv_nsec
 146                         && times[1].tv_nsec < TIMESPEC_HZ))))
 147         {
 148           errno = EINVAL;
 149           return -1;
 150         }
 151       size_t len = strlen (file);
 152       if (len > 0 && file[len - 1] == '/')
 153         {
 154           struct stat statbuf;
 155           if (fstatat (fd, file, &statbuf, 0) < 0)
 156             return -1;
 157           if (!S_ISDIR (statbuf.st_mode))
 158             {
 159               errno = ENOTDIR;
 160               return -1;
 161             }
 162         }
 163 #  endif
 164       result = utimensat (fd, file, times, flag);
 165       /* Linux kernel 2.6.25 has a bug where it returns EINVAL for
 166          UTIME_NOW or UTIME_OMIT with non-zero tv_sec, which
 167          local_utimensat works around.  Meanwhile, EINVAL for a bad
 168          flag is indeterminate whether the native utimensat works, but
 169          local_utimensat will also reject it.  */
 170       if (result == -1 && errno == EINVAL && (flag & ~AT_SYMLINK_NOFOLLOW))
 171         return result;
 172       if (result == 0 || (errno != ENOSYS && errno != EINVAL))
 173         {
 174           utimensat_works_really = 1;
 175           return result;
 176         }
 177     }
 178   /* No point in trying openat/futimens, since on Linux, futimens is
 179      implemented with the same syscall as utimensat.  Only avoid the
 180      native utimensat due to an ENOSYS failure; an EINVAL error was
 181      data-dependent, and the next caller may pass valid data.  */
 182   if (0 <= utimensat_works_really && errno == ENOSYS)
 183     utimensat_works_really = -1;
 184   return local_utimensat (fd, file, times, flag);
 185 }
 186 
 187 # else /* !HAVE_UTIMENSAT */
 188 
 189 #  define AT_FUNC_NAME utimensat
 190 
 191 # endif /* !HAVE_UTIMENSAT */
 192 
 193 /* Set the access and modification timestamps of FILE to be
 194    TIMESPEC[0] and TIMESPEC[1], respectively; relative to directory
 195    FD.  If flag is AT_SYMLINK_NOFOLLOW, change the times of a symlink,
 196    or fail with ENOSYS if not possible.  If TIMESPEC is null, set the
 197    timestamps to the current time.  If possible, do it without
 198    changing the working directory.  Otherwise, resort to using
 199    save_cwd/fchdir, then utimens/restore_cwd.  If either the save_cwd
 200    or the restore_cwd fails, then give a diagnostic and exit nonzero.
 201    Return 0 on success, -1 (setting errno) on failure.  */
 202 
 203 /* AT_FUNC_NAME is now utimensat or local_utimensat.  */
 204 # define AT_FUNC_F1 lutimens
 205 # define AT_FUNC_F2 utimens
 206 # define AT_FUNC_USE_F1_COND AT_SYMLINK_NOFOLLOW
 207 # define AT_FUNC_POST_FILE_PARAM_DECLS , struct timespec const ts[2], int flag
 208 # define AT_FUNC_POST_FILE_ARGS        , ts
 209 # include "at-func.c"
 210 # undef AT_FUNC_NAME
 211 # undef AT_FUNC_F1
 212 # undef AT_FUNC_F2
 213 # undef AT_FUNC_USE_F1_COND
 214 # undef AT_FUNC_POST_FILE_PARAM_DECLS
 215 # undef AT_FUNC_POST_FILE_ARGS
 216 
 217 #endif /* !HAVE_NEARLY_WORKING_UTIMENSAT */

/* [previous][next][first][last][top][bottom][index][help] */