futimens, utimensat: work around Linux bug
[gnulib.git] / lib / utimensat.c
1 /* Set the access and modification time of a file relative to directory fd.
2    Copyright (C) 2009 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 <http://www.gnu.org/licenses/>.  */
16
17 /* written by Eric Blake */
18
19 #include <config.h>
20
21 #include <sys/stat.h>
22
23 #include <errno.h>
24 #include <fcntl.h>
25
26 #include "stat-time.h"
27 #include "timespec.h"
28 #include "utimens.h"
29
30 #if HAVE_UTIMENSAT
31
32 # undef utimensat
33
34 /* If we have a native utimensat, but are compiling this file, then
35    utimensat was defined to rpl_utimensat by our replacement
36    sys/stat.h.  We assume the native version might fail with ENOSYS,
37    or succeed without properly affecting ctime (as is the case when
38    using newer glibc but older Linux kernel).  In this scenario,
39    rpl_utimensat checks whether the native version is usable, and
40    local_utimensat provides the fallback manipulation.  */
41
42 static int local_utimensat (int, char const *, struct timespec const[2], int);
43 # define AT_FUNC_NAME local_utimensat
44
45 /* Like utimensat, but work around native bugs.  */
46
47 int
48 rpl_utimensat (int fd, char const *file, struct timespec const times[2],
49                int flag)
50 {
51   /* See comments in utimens.c for details.  */
52   static int utimensat_works_really; /* 0 = unknown, 1 = yes, -1 = no.  */
53   static int utimensat_ctime_really; /* 0 = unknown, 1 = yes, -1 = no.  */
54   if (0 <= utimensat_works_really)
55     {
56       int result;
57       struct stat st1;
58       struct stat st2;
59       struct timespec ts[2];
60       /* Linux kernel 2.6.32 has a bug where mtime of UTIME_OMIT fails
61          to change ctime.  */
62       if (utimensat_ctime_really <= 0 && times
63           && times[0].tv_nsec != UTIME_OMIT && times[1].tv_nsec == UTIME_OMIT)
64         {
65           if (fstatat (fd, file, &st1, flag))
66             return -1;
67           if (utimensat_ctime_really < 0)
68             {
69               ts[0] = times[0];
70               ts[1] = get_stat_mtime (&st1);
71               times = ts;
72             }
73         }
74       result = utimensat (fd, file, times, flag);
75       /* Linux kernel 2.6.25 has a bug where it returns EINVAL for
76          UTIME_NOW or UTIME_OMIT with non-zero tv_sec, which
77          local_utimensat works around.  Meanwhile, EINVAL for a bad
78          flag is indeterminate whether the native utimensat works, but
79          local_utimensat will also reject it.  */
80       if (result == -1 && errno == EINVAL && (flag & ~AT_SYMLINK_NOFOLLOW))
81         return result;
82       if (result == 0 || (errno != ENOSYS && errno != EINVAL))
83         {
84           utimensat_works_really = 1;
85           if (result == 0 && utimensat_ctime_really == 0 && times
86               && times[0].tv_nsec != UTIME_OMIT
87               && times[1].tv_nsec == UTIME_OMIT)
88             {
89               /* Perform a followup [l]stat.  See detect_ctime_bug in
90                  utimens.c for more details.  */
91               struct timespec now;
92               if (fstatat (fd, file, &st2, flag))
93                 return -1;
94               if (st1.st_ctime != st2.st_ctime
95                   || get_stat_ctime_ns (&st1) != get_stat_ctime_ns (&st2))
96                 {
97                   utimensat_ctime_really = 1;
98                   return result;
99                 }
100               if (times[0].tv_nsec == UTIME_NOW)
101                 now = get_stat_atime (&st2);
102               else
103                 gettime (&now);
104               if (now.tv_sec < st2.st_ctime
105                   || 2 < now.tv_sec - st2.st_ctime
106                   || (get_stat_ctime_ns (&st2)
107                       && now.tv_sec - st2.st_ctime < 2
108                       && (20000000 < (1000000000 * (now.tv_sec - st2.st_ctime)
109                                       + now.tv_nsec
110                                       - get_stat_ctime_ns (&st2)))))
111                 utimensat_ctime_really = -1;
112               ts[0] = times[0];
113               ts[1] = get_stat_mtime (&st2);
114               result = utimensat (fd, file, ts, flag);
115             }
116           return result;
117         }
118     }
119   /* No point in trying openat/futimens, since on Linux, futimens is
120      implemented with the same syscall as utimensat.  Only avoid the
121      native utimensat due to an ENOSYS failure; an EINVAL error was
122      data-dependent, and the next caller may pass valid data.  */
123   if (0 <= utimensat_works_really && errno == ENOSYS)
124     utimensat_works_really = -1;
125   return local_utimensat (fd, file, times, flag);
126 }
127
128 #else /* !HAVE_UTIMENSAT */
129
130 # define AT_FUNC_NAME utimensat
131
132 #endif /* !HAVE_UTIMENSAT */
133
134 /* Set the access and modification time stamps of FILE to be
135    TIMESPEC[0] and TIMESPEC[1], respectively; relative to directory
136    FD.  If flag is AT_SYMLINK_NOFOLLOW, change the times of a symlink,
137    or fail with ENOSYS if not possible.  If TIMESPEC is null, set the
138    time stamps to the current time.  If possible, do it without
139    changing the working directory.  Otherwise, resort to using
140    save_cwd/fchdir, then utimens/restore_cwd.  If either the save_cwd
141    or the restore_cwd fails, then give a diagnostic and exit nonzero.
142    Return 0 on success, -1 (setting errno) on failure.  */
143
144 /* AT_FUNC_NAME is now utimensat or local_utimensat.  */
145 #define AT_FUNC_F1 lutimens
146 #define AT_FUNC_F2 utimens
147 #define AT_FUNC_USE_F1_COND AT_SYMLINK_NOFOLLOW
148 #define AT_FUNC_POST_FILE_PARAM_DECLS , struct timespec const ts[2], int flag
149 #define AT_FUNC_POST_FILE_ARGS        , ts
150 #include "at-func.c"
151 #undef AT_FUNC_NAME
152 #undef AT_FUNC_F1
153 #undef AT_FUNC_F2
154 #undef AT_FUNC_USE_F1_COND
155 #undef AT_FUNC_POST_FILE_PARAM_DECLS
156 #undef AT_FUNC_POST_FILE_ARGS