utimens: introduce fdutimens
[pspp] / lib / utimens.c
1 /* Set file access and modification times.
2
3    Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free
4    Software Foundation, Inc.
5
6    This program is free software: you can redistribute it and/or modify it
7    under the terms of the GNU General Public License as published by the
8    Free Software Foundation; either version 3 of the License, or any
9    later version.
10
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15
16    You should have received a copy of the GNU General Public License
17    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
18
19 /* Written by Paul Eggert.  */
20
21 /* derived from a function in touch.c */
22
23 #include <config.h>
24
25 #include "utimens.h"
26
27 #include <assert.h>
28 #include <errno.h>
29 #include <fcntl.h>
30 #include <stdbool.h>
31 #include <sys/stat.h>
32 #include <sys/time.h>
33 #include <unistd.h>
34
35 #include "stat-time.h"
36 #include "timespec.h"
37
38 #if HAVE_UTIME_H
39 # include <utime.h>
40 #endif
41
42 /* Some systems (even some that do have <utime.h>) don't declare this
43    structure anywhere.  */
44 #ifndef HAVE_STRUCT_UTIMBUF
45 struct utimbuf
46 {
47   long actime;
48   long modtime;
49 };
50 #endif
51
52 /* Validate the requested timestamps.  Return 0 if the resulting
53    timespec can be used for utimensat (after possibly modifying it to
54    work around bugs in utimensat).  Return 1 if the timespec needs
55    further adjustment based on stat results for utimes or other less
56    powerful interfaces.  Return -1, with errno set to EINVAL, if
57    timespec is out of range.  */
58 static int
59 validate_timespec (struct timespec timespec[2])
60 {
61   int result = 0;
62   assert (timespec);
63   if ((timespec[0].tv_nsec != UTIME_NOW
64        && timespec[0].tv_nsec != UTIME_OMIT
65        && (timespec[0].tv_nsec < 0 || 1000000000 <= timespec[0].tv_nsec))
66       || (timespec[1].tv_nsec != UTIME_NOW
67           && timespec[1].tv_nsec != UTIME_OMIT
68           && (timespec[1].tv_nsec < 0 || 1000000000 <= timespec[1].tv_nsec)))
69     {
70       errno = EINVAL;
71       return -1;
72     }
73   /* Work around Linux kernel 2.6.25 bug, where utimensat fails with
74      EINVAL if tv_sec is not 0 when using the flag values of
75      tv_nsec.  */
76   if (timespec[0].tv_nsec == UTIME_NOW
77       || timespec[0].tv_nsec == UTIME_OMIT)
78     {
79       timespec[0].tv_sec = 0;
80       result = 1;
81     }
82   if (timespec[1].tv_nsec == UTIME_NOW
83       || timespec[1].tv_nsec == UTIME_OMIT)
84     {
85       timespec[1].tv_sec = 0;
86       result = 1;
87     }
88   return result;
89 }
90
91 /* Normalize any UTIME_NOW or UTIME_OMIT values in *TS, using stat
92    buffer STATBUF to obtain the current timestamps of the file.  If
93    both times are UTIME_NOW, set *TS to NULL (as this can avoid some
94    permissions issues).  If both times are UTIME_OMIT, return true
95    (nothing further beyond the prior collection of STATBUF is
96    necessary); otherwise return false.  */
97 static bool
98 update_timespec (struct stat const *statbuf, struct timespec *ts[2])
99 {
100   struct timespec *timespec = *ts;
101   if (timespec[0].tv_nsec == UTIME_OMIT
102       && timespec[1].tv_nsec == UTIME_OMIT)
103     return true;
104   if (timespec[0].tv_nsec == UTIME_NOW
105       && timespec[1].tv_nsec == UTIME_NOW)
106     {
107       *ts = NULL;
108       return false;
109     }
110
111   if (timespec[0].tv_nsec == UTIME_OMIT)
112     timespec[0] = get_stat_atime (statbuf);
113   else if (timespec[0].tv_nsec == UTIME_NOW)
114     gettime (&timespec[0]);
115
116   if (timespec[1].tv_nsec == UTIME_OMIT)
117     timespec[1] = get_stat_mtime (statbuf);
118   else if (timespec[1].tv_nsec == UTIME_NOW)
119     gettime (&timespec[1]);
120
121   return false;
122 }
123
124 /* Set the access and modification time stamps of FD (a.k.a. FILE) to be
125    TIMESPEC[0] and TIMESPEC[1], respectively.
126    FD must be either negative -- in which case it is ignored --
127    or a file descriptor that is open on FILE.
128    If FD is nonnegative, then FILE can be NULL, which means
129    use just futimes (or equivalent) instead of utimes (or equivalent),
130    and fail if on an old system without futimes (or equivalent).
131    If TIMESPEC is null, set the time stamps to the current time.
132    Return 0 on success, -1 (setting errno) on failure.  */
133
134 int
135 fdutimens (char const *file, int fd, struct timespec const timespec[2])
136 {
137   struct timespec adjusted_timespec[2];
138   struct timespec *ts = timespec ? adjusted_timespec : NULL;
139   int adjustment_needed = 0;
140
141   if (ts)
142     {
143       adjusted_timespec[0] = timespec[0];
144       adjusted_timespec[1] = timespec[1];
145       adjustment_needed = validate_timespec (ts);
146     }
147   if (adjustment_needed < 0)
148     return -1;
149
150   /* Require that at least one of FD or FILE are valid.  Works around
151      a Linux bug where futimens (AT_FDCWD, NULL) changes "." rather
152      than failing.  */
153   if (!file)
154     {
155       if (fd < 0)
156         {
157           errno = EBADF;
158           return -1;
159         }
160       if (dup2 (fd, fd) != fd)
161         return -1;
162     }
163
164   /* Some Linux-based NFS clients are buggy, and mishandle time stamps
165      of files in NFS file systems in some cases.  We have no
166      configure-time test for this, but please see
167      <http://bugs.gentoo.org/show_bug.cgi?id=132673> for references to
168      some of the problems with Linux 2.6.16.  If this affects you,
169      compile with -DHAVE_BUGGY_NFS_TIME_STAMPS; this is reported to
170      help in some cases, albeit at a cost in performance.  But you
171      really should upgrade your kernel to a fixed version, since the
172      problem affects many applications.  */
173
174 #if HAVE_BUGGY_NFS_TIME_STAMPS
175   if (fd < 0)
176     sync ();
177   else
178     fsync (fd);
179 #endif
180
181   /* POSIX 2008 added two interfaces to set file timestamps with
182      nanosecond resolution.  We provide a fallback for ENOSYS (for
183      example, compiling against Linux 2.6.25 kernel headers and glibc
184      2.7, but running on Linux 2.6.18 kernel).  */
185 #if HAVE_UTIMENSAT
186   if (fd < 0)
187     {
188       int result = utimensat (AT_FDCWD, file, ts, 0);
189 # ifdef __linux__
190       /* Work around a kernel bug:
191          http://bugzilla.redhat.com/442352
192          http://bugzilla.redhat.com/449910
193          It appears that utimensat can mistakenly return 280 rather
194          than -1 upon failure.
195          FIXME: remove in 2010 or whenever the offending kernels
196          are no longer in common use.  */
197       if (0 < result)
198         errno = ENOSYS;
199 # endif
200
201       if (result == 0 || errno != ENOSYS)
202         return result;
203     }
204 #endif
205 #if HAVE_FUTIMENS
206   {
207     int result = futimens (fd, timespec);
208 # ifdef __linux__
209     /* Work around the same bug as above.  */
210     if (0 < result)
211       errno = ENOSYS;
212 # endif
213     if (result == 0 || errno != ENOSYS)
214       return result;
215   }
216 #endif
217
218   /* The platform lacks an interface to set file timestamps with
219      nanosecond resolution, so do the best we can, discarding any
220      fractional part of the timestamp.  */
221
222   if (adjustment_needed)
223     {
224       struct stat st;
225       if (fd < 0 ? stat (file, &st) : fstat (fd, &st))
226         return -1;
227       if (update_timespec (&st, &ts))
228         return 0;
229     }
230
231   {
232 #if HAVE_FUTIMESAT || HAVE_WORKING_UTIMES
233     struct timeval timeval[2];
234     struct timeval const *t;
235     if (ts)
236       {
237         timeval[0].tv_sec = ts[0].tv_sec;
238         timeval[0].tv_usec = ts[0].tv_nsec / 1000;
239         timeval[1].tv_sec = ts[1].tv_sec;
240         timeval[1].tv_usec = ts[1].tv_nsec / 1000;
241         t = timeval;
242       }
243     else
244       t = NULL;
245
246     if (fd < 0)
247       {
248 # if HAVE_FUTIMESAT
249         return futimesat (AT_FDCWD, file, t);
250 # endif
251       }
252     else
253       {
254         /* If futimesat or futimes fails here, don't try to speed things
255            up by returning right away.  glibc can incorrectly fail with
256            errno == ENOENT if /proc isn't mounted.  Also, Mandrake 10.0
257            in high security mode doesn't allow ordinary users to read
258            /proc/self, so glibc incorrectly fails with errno == EACCES.
259            If errno == EIO, EPERM, or EROFS, it's probably safe to fail
260            right away, but these cases are rare enough that they're not
261            worth optimizing, and who knows what other messed-up systems
262            are out there?  So play it safe and fall back on the code
263            below.  */
264 # if HAVE_FUTIMESAT
265         if (futimesat (fd, NULL, t) == 0)
266           return 0;
267 # elif HAVE_FUTIMES
268         if (futimes (fd, t) == 0)
269           return 0;
270 # endif
271       }
272 #endif /* HAVE_FUTIMESAT || HAVE_WORKING_UTIMES */
273
274     if (!file)
275       {
276 #if ! (HAVE_FUTIMESAT || (HAVE_WORKING_UTIMES && HAVE_FUTIMES))
277         errno = ENOSYS;
278 #endif
279         return -1;
280       }
281
282 #if HAVE_WORKING_UTIMES
283     return utimes (file, t);
284 #else
285     {
286       struct utimbuf utimbuf;
287       struct utimbuf *ut;
288       if (ts)
289         {
290           utimbuf.actime = ts[0].tv_sec;
291           utimbuf.modtime = ts[1].tv_sec;
292           ut = &utimbuf;
293         }
294       else
295         ut = NULL;
296
297       return utime (file, ut);
298     }
299 #endif /* !HAVE_WORKING_UTIMES */
300   }
301 }
302
303 /* Set the access and modification time stamps of FD (a.k.a. FILE) to be
304    TIMESPEC[0] and TIMESPEC[1], respectively.
305    FD must be either negative -- in which case it is ignored --
306    or a file descriptor that is open on FILE.
307    If FD is nonnegative, then FILE can be NULL, which means
308    use just futimes (or equivalent) instead of utimes (or equivalent),
309    and fail if on an old system without futimes (or equivalent).
310    If TIMESPEC is null, set the time stamps to the current time.
311    Return 0 on success, -1 (setting errno) on failure.  */
312
313 int
314 gl_futimens (int fd, char const *file, struct timespec const timespec[2])
315 {
316   return fdutimens (file, fd, timespec);
317 }
318
319 /* Set the access and modification time stamps of FILE to be
320    TIMESPEC[0] and TIMESPEC[1], respectively.  */
321 int
322 utimens (char const *file, struct timespec const timespec[2])
323 {
324   return gl_futimens (-1, file, timespec);
325 }
326
327 /* Set the access and modification time stamps of the symlink FILE to
328    be TIMESPEC[0] and TIMESPEC[1], respectively.  Fail with ENOSYS if
329    the platform does not support changing symlink timestamps.  */
330 int
331 lutimens (char const *file, struct timespec const timespec[2])
332 {
333   struct timespec adjusted_timespec[2];
334   struct timespec *ts = timespec ? adjusted_timespec : NULL;
335   int adjustment_needed = 0;
336
337   if (ts)
338     {
339       adjusted_timespec[0] = timespec[0];
340       adjusted_timespec[1] = timespec[1];
341       adjustment_needed = validate_timespec (ts);
342     }
343   if (adjustment_needed < 0)
344     return -1;
345
346   /* The Linux kernel did not support symlink timestamps until
347      utimensat, in version 2.6.22, so we don't need to mimic
348      gl_futimens' worry about buggy NFS clients.  But we do have to
349      worry about bogus return values.  */
350
351 #if HAVE_UTIMENSAT
352   {
353     int result = utimensat (AT_FDCWD, file, ts, AT_SYMLINK_NOFOLLOW);
354 # ifdef __linux__
355     /* Work around a kernel bug:
356        http://bugzilla.redhat.com/442352
357        http://bugzilla.redhat.com/449910
358        It appears that utimensat can mistakenly return 280 rather
359        than -1 upon ENOSYS failure.
360        FIXME: remove in 2010 or whenever the offending kernels
361        are no longer in common use.  */
362     if (0 < result)
363       errno = ENOSYS;
364 # endif
365
366     if (result == 0 || errno != ENOSYS)
367       return result;
368   }
369 #endif /* HAVE_UTIMENSAT */
370
371   /* The platform lacks an interface to set file timestamps with
372      nanosecond resolution, so do the best we can, discarding any
373      fractional part of the timestamp.  */
374
375   if (adjustment_needed)
376     {
377       struct stat st;
378       if (lstat (file, &st))
379         return -1;
380       if (update_timespec (&st, &ts))
381         return 0;
382     }
383
384 #if HAVE_LUTIMES
385   {
386     struct timeval timeval[2];
387     struct timeval const *t;
388     if (ts)
389       {
390         timeval[0].tv_sec = ts[0].tv_sec;
391         timeval[0].tv_usec = ts[0].tv_nsec / 1000;
392         timeval[1].tv_sec = ts[1].tv_sec;
393         timeval[1].tv_usec = ts[1].tv_nsec / 1000;
394         t = timeval;
395       }
396     else
397       t = NULL;
398
399     return lutimes (file, t);
400   }
401 #endif /* HAVE_LUTIMES */
402
403   /* Out of luck.  Symlink timestamps can't be changed.  We won't
404      bother changing the timestamps if FILE was not a symlink.  */
405   errno = ENOSYS;
406   return -1;
407 }