root/lib/utimensat.c

/* [<][>][^][v][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-2023 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],
    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],
    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 */

/* [<][>][^][v][top][bottom][index][help] */