Hash :
791cc509
Author :
Date :
2009-10-12T16:48:57
utimensat: new module Provide utimensat where it is missing, and rpl_utimensat to work around ENOSYS and EINVAL bugs in older Linux kernels. * modules/utimensat: New file. * lib/utimensat.c (utimensat): Likewise. * m4/utimensat.m4 (gl_FUNC_UTIMENSAT): Likewise. * lib/utimens.c (utimensat): Avoid recursion into rpl_utimensat, so we can work around Linux bugs. * m4/sys_stat_h.m4 (gl_SYS_STAT_H_DEFAULTS): Add witnesses. * modules/sys_stat (Makefile.am): Substitute them. * lib/sys_stat.in.h (utimensat): Declare it. * MODULES.html.sh (systems lacking POSIX:2008): Mention module. * doc/posix-functions/utimensat.texi (utimensat): Likewise. * modules/utimensat-tests: New test. * tests/test-utimensat.c: Likewise. Signed-off-by: Eric Blake <ebb9@byu.net>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438
/* 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 */
/* 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, timespec);
# 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)
{
struct stat st;
if (fd < 0 ? stat (file, &st) : fstat (fd, &st))
return -1;
if (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
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 || (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)
{
if (lstat (file, &st))
return -1;
if (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 && lstat (file, &st))
return -1;
if (!S_ISLNK (st.st_mode))
return fdutimens (file, -1, ts);
errno = ENOSYS;
return -1;
}