/* 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; }
static void test_mtime (const struct stat *statinfo, struct timespec *modtimes) { int i; /* Use the struct stat fields directly. */ /* mtime(stamp1) < mtime(stamp2) */ ASSERT (statinfo[0].st_mtime < statinfo[2].st_mtime || (statinfo[0].st_mtime == statinfo[2].st_mtime && (get_stat_mtime_ns (&statinfo[0]) < get_stat_mtime_ns (&statinfo[2])))); /* mtime(stamp2) < mtime(stamp3) */ ASSERT (statinfo[2].st_mtime < statinfo[3].st_mtime || (statinfo[2].st_mtime == statinfo[3].st_mtime && (get_stat_mtime_ns (&statinfo[2]) < get_stat_mtime_ns (&statinfo[3])))); /* Now check the result of the access functions. */ /* mtime(stamp1) < mtime(stamp2) */ ASSERT (modtimes[0].tv_sec < modtimes[2].tv_sec || (modtimes[0].tv_sec == modtimes[2].tv_sec && modtimes[0].tv_nsec < modtimes[2].tv_nsec)); /* mtime(stamp2) < mtime(stamp3) */ ASSERT (modtimes[2].tv_sec < modtimes[3].tv_sec || (modtimes[2].tv_sec == modtimes[3].tv_sec && modtimes[2].tv_nsec < modtimes[3].tv_nsec)); /* verify equivalence */ for (i = 0; i < NFILES; ++i) { struct timespec ts; ts = get_stat_mtime (&statinfo[i]); ASSERT (ts.tv_sec == statinfo[i].st_mtime); } }
static void prepare_test (struct stat *statinfo, struct timespec *modtimes) { int i; create_file ("t-stt-stamp1"); nap (); create_file ("t-stt-testfile"); nap (); create_file ("t-stt-stamp2"); nap (); ASSERT (chmod ("t-stt-testfile", 0400) == 0); nap (); create_file ("t-stt-stamp3"); do_stat ("t-stt-stamp1", &statinfo[0]); do_stat ("t-stt-testfile", &statinfo[1]); do_stat ("t-stt-stamp2", &statinfo[2]); do_stat ("t-stt-stamp3", &statinfo[3]); /* Now use our access functions. */ for (i = 0; i < NFILES; ++i) { modtimes[i] = get_stat_mtime (&statinfo[i]); } }
/* Some filesystems can slightly offset the timestamps of newly created files. To compensate for it, tar testsuite waits at least 1 second before creating next level of incremental backups. However, NFS mounts can offset the timestamps by bigger amounts. This program returns with success (0) if a newly created file is assigned mtime matching the system time to the nearest second. */ int main (int argc, char **argv) { int fd; char name[sizeof(TEMPLATE)]; struct stat st; struct timespec ts, td; double diff; gettime (&ts); strcpy (name, TEMPLATE); umask (077); fd = mkstemp (name); assert (fd != -1); unlink (name); assert (fstat (fd, &st) == 0); close (fd); td = timespec_sub (get_stat_mtime (&st), ts); diff = td.tv_sec * BILLION + td.tv_nsec; if (diff < 0) diff = - diff; if (diff / BILLION >= 1) { fprintf (stderr, "file timestamp unreliable\n"); return 1; } return 0; }
void update_parent_directory (struct tar_stat_info *parent) { struct directory *directory = find_directory (parent->orig_file_name); if (directory) { struct stat st; if (fstat (parent->fd, &st) != 0) stat_diag (directory->name); else directory->mtime = get_stat_mtime (&st); } }
void update_parent_directory (const char *name) { struct directory *directory; char *p; p = dir_name (name); directory = find_directory (p); if (directory) { struct stat st; if (deref_stat (dereference_option, p, &st) != 0) stat_diag (name); else directory->mtime = get_stat_mtime (&st); } free (p); }
void update_parent_directory (const char *name) { struct directory *directory; char *p; p = dir_name (name); directory = find_directory (p); if (directory) { struct stat st; if (deref_stat (dereference_option, p, &st) != 0) { if (errno != ENOENT) stat_diag (directory->name); /* else: should have been already reported */ } else directory->mtime = get_stat_mtime (&st); } free (p); }
void set_file_attributes (char const *to, enum file_attributes attr, char const *from, const struct stat *st, mode_t mode, struct timespec *new_time) { if (attr & FA_TIMES) { struct timespec times[2]; if (new_time) times[0] = times[1] = *new_time; else { times[0] = get_stat_atime (st); times[1] = get_stat_mtime (st); } if (lutimens (to, times) != 0) pfatal ("Failed to set the timestamps of %s %s", S_ISLNK (mode) ? "symbolic link" : "file", quotearg (to)); } if (attr & FA_IDS) { static uid_t euid = -1; static gid_t egid = -1; uid_t uid; uid_t gid; if (euid == -1) { euid = geteuid (); egid = getegid (); } uid = (euid == st->st_uid) ? -1 : st->st_uid; gid = (egid == st->st_gid) ? -1 : st->st_gid; /* May fail if we are not privileged to set the file owner, or we are not in group instat.st_gid. Ignore those errors. */ if ((uid != -1 || gid != -1) && lchown (to, uid, gid) != 0 && (errno != EPERM || (uid != -1 && lchown (to, (uid = -1), gid) != 0 && errno != EPERM))) pfatal ("Failed to set the %s of %s %s", (uid == -1) ? "owner" : "owning group", S_ISLNK (mode) ? "symbolic link" : "file", quotearg (to)); } if (attr & FA_XATTRS) if (copy_attr (from, to) != 0 && errno != ENOSYS && errno != ENOTSUP && errno != EPERM) fatal_exit (0); if (attr & FA_MODE) { #if 0 && defined HAVE_LCHMOD /* The "diff --git" format does not store the file permissions of symlinks, so don't try to set symlink file permissions even on systems where we could. */ if (lchmod (to, mode)) #else if (! S_ISLNK (mode) && chmod (to, mode) != 0) #endif pfatal ("Failed to set the permissions of %s %s", S_ISLNK (mode) ? "symbolic link" : "file", quotearg (to)); } }
/* 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 fdutimens' worry about buggy NFS clients. But we do have to worry about bogus return values. */ #if HAVE_UTIMENSAT if (0 <= lutimensat_works_really) { int result; # if __linux__ /* As recently as Linux kernel 2.6.32 (Dec 2009), several file systems (xfs, ntfs-3g) have bugs with a single UTIME_OMIT, but work if both times are either explicitly specified or UTIME_NOW. Work around it with a preparatory lstat prior to calling utimensat; fortunately, there is not much timing impact due to the extra syscall even on file systems where UTIME_OMIT would have worked. FIXME: Simplify this in 2012, when file system bugs are no longer common. */ if (adjustment_needed == 2) { if (lstat (file, &st)) return -1; if (ts[0].tv_nsec == UTIME_OMIT) ts[0] = get_stat_atime (&st); else if (ts[1].tv_nsec == UTIME_OMIT) ts[1] = get_stat_mtime (&st); /* Note that st is good, in case utimensat gives ENOSYS. */ adjustment_needed++; } # endif /* __linux__ */ 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; lutimensat_works_really = 1; return result; } } lutimensat_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 || REPLACE_FUNC_STAT_FILE) { if (adjustment_needed != 3 && lstat (file, &st)) return -1; if (ts && update_timespec (&st, &ts)) return 0; } /* On Linux, lutimes is a thin wrapper around utimensat, so there is no point trying lutimes if utimensat failed with ENOSYS. */ #if HAVE_LUTIMES && !HAVE_UTIMENSAT { struct timeval timeval[2]; struct timeval *t; int result; 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; result = lutimes (file, t); if (result == 0 || errno != ENOSYS) return result; } #endif /* HAVE_LUTIMES && !HAVE_UTIMENSAT */ /* Out of luck for symlinks, but we still handle regular files. */ if (!(adjustment_needed || REPLACE_FUNC_STAT_FILE) && lstat (file, &st)) return -1; if (!S_ISLNK (st.st_mode)) return fdutimens (-1, file, ts); errno = ENOSYS; return -1; }
int fdutimens (int fd, 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; /* Require that at least one of FD or FILE are potentially valid, to avoid a Linux bug where futimens (AT_FDCWD, NULL) changes "." rather than failing. */ if (fd < 0 && !file) { errno = EBADF; 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) { int result; # if __linux__ /* As recently as Linux kernel 2.6.32 (Dec 2009), several file systems (xfs, ntfs-3g) have bugs with a single UTIME_OMIT, but work if both times are either explicitly specified or UTIME_NOW. Work around it with a preparatory [f]stat prior to calling futimens/utimensat; fortunately, there is not much timing impact due to the extra syscall even on file systems where UTIME_OMIT would have worked. FIXME: Simplify this in 2012, when file system bugs are no longer common. */ if (adjustment_needed == 2) { if (fd < 0 ? stat (file, &st) : fstat (fd, &st)) return -1; if (ts[0].tv_nsec == UTIME_OMIT) ts[0] = get_stat_atime (&st); else if (ts[1].tv_nsec == UTIME_OMIT) ts[1] = get_stat_mtime (&st); /* Note that st is good, in case utimensat gives ENOSYS. */ adjustment_needed++; } # endif /* __linux__ */ # if HAVE_UTIMENSAT if (fd < 0) { 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 if (0 <= fd) { result = futimens (fd, ts); # 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; lutimensat_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 || (REPLACE_FUNC_STAT_FILE && fd < 0)) { if (adjustment_needed != 3 && (fd < 0 ? stat (file, &st) : fstat (fd, &st))) return -1; if (ts && update_timespec (&st, &ts)) return 0; } { #if HAVE_FUTIMESAT || HAVE_WORKING_UTIMES struct timeval timeval[2]; struct timeval *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 && !FUTIMESAT_NULL_BUG) || HAVE_FUTIMES # if HAVE_FUTIMESAT && !FUTIMESAT_NULL_BUG # undef futimes # define futimes(fd, t) futimesat (fd, NULL, t) # endif if (futimes (fd, t) == 0) { # if __linux__ && __GLIBC__ /* Work around a longstanding glibc bug, still present as of 2010-12-27. On older Linux kernels that lack both utimensat and utimes, glibc's futimes rounds instead of truncating when falling back on utime. The same bug occurs in futimesat with a null 2nd arg. */ if (t) { bool abig = 500000 <= t[0].tv_usec; bool mbig = 500000 <= t[1].tv_usec; if ((abig | mbig) && fstat (fd, &st) == 0) { /* If these two subtractions overflow, they'll track the overflows inside the buggy glibc. */ time_t adiff = st.st_atime - t[0].tv_sec; time_t mdiff = st.st_mtime - t[1].tv_sec; struct timeval *tt = NULL; struct timeval truncated_timeval[2]; truncated_timeval[0] = t[0]; truncated_timeval[1] = t[1]; if (abig && adiff == 1 && get_stat_atime_ns (&st) == 0) { tt = truncated_timeval; tt[0].tv_usec = 0; } if (mbig && mdiff == 1 && get_stat_mtime_ns (&st) == 0) { tt = truncated_timeval; tt[1].tv_usec = 0; } if (tt) futimes (fd, tt); } } # endif return 0; } # endif } #endif /* HAVE_FUTIMESAT || HAVE_WORKING_UTIMES */ if (!file) { #if ! ((HAVE_FUTIMESAT && !FUTIMESAT_NULL_BUG) \ || (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 */ } }
static struct directory * procdir (const char *name_buffer, struct stat *stat_data, dev_t device, int flag, char *entry) { struct directory *directory; bool nfs = NFS_FILE_STAT (*stat_data); if ((directory = find_directory (name_buffer)) != NULL) { if (DIR_IS_INITED (directory)) { if (flag & PD_FORCE_INIT) { assign_string (&directory->name, name_buffer); } else { *entry = 'N'; /* Avoid duplicating this directory */ return directory; } } if (strcmp (directory->name, name_buffer)) { *entry = 'N'; return directory; } /* With NFS, the same file can have two different devices if an NFS directory is mounted in multiple locations, which is relatively common when automounting. To avoid spurious incremental redumping of directories, consider all NFS devices as equal, relying on the i-node to establish differences. */ if (! ((!check_device_option || (DIR_IS_NFS (directory) && nfs) || directory->device_number == stat_data->st_dev) && directory->inode_number == stat_data->st_ino)) { /* FIXME: find_directory_meta ignores nfs */ struct directory *d = find_directory_meta (stat_data->st_dev, stat_data->st_ino); if (d) { if (strcmp (d->name, name_buffer)) { WARNOPT (WARN_RENAME_DIRECTORY, (0, 0, _("%s: Directory has been renamed from %s"), quotearg_colon (name_buffer), quote_n (1, d->name))); directory->orig = d; DIR_SET_FLAG (directory, DIRF_RENAMED); dirlist_replace_prefix (d->name, name_buffer); } directory->children = CHANGED_CHILDREN; } else { WARNOPT (WARN_RENAME_DIRECTORY, (0, 0, _("%s: Directory has been renamed"), quotearg_colon (name_buffer))); directory->children = ALL_CHILDREN; directory->device_number = stat_data->st_dev; directory->inode_number = stat_data->st_ino; } if (nfs) DIR_SET_FLAG (directory, DIRF_NFS); } else directory->children = CHANGED_CHILDREN; DIR_SET_FLAG (directory, DIRF_FOUND); } else { struct directory *d = find_directory_meta (stat_data->st_dev, stat_data->st_ino); directory = note_directory (name_buffer, get_stat_mtime(stat_data), stat_data->st_dev, stat_data->st_ino, nfs, true, NULL); if (d) { if (strcmp (d->name, name_buffer)) { WARNOPT (WARN_RENAME_DIRECTORY, (0, 0, _("%s: Directory has been renamed from %s"), quotearg_colon (name_buffer), quote_n (1, d->name))); directory->orig = d; DIR_SET_FLAG (directory, DIRF_RENAMED); dirlist_replace_prefix (d->name, name_buffer); } directory->children = CHANGED_CHILDREN; } else { DIR_SET_FLAG (directory, DIRF_NEW); WARNOPT (WARN_NEW_DIRECTORY, (0, 0, _("%s: Directory is new"), quotearg_colon (name_buffer))); directory->children = (listed_incremental_option || (OLDER_STAT_TIME (*stat_data, m) || (after_date_option && OLDER_STAT_TIME (*stat_data, c)))) ? ALL_CHILDREN : CHANGED_CHILDREN; } } /* If the directory is on another device and --one-file-system was given, omit it... */ if (one_file_system_option && device != stat_data->st_dev /* ... except if it was explicitely given in the command line */ && !is_individual_file (name_buffer)) /* FIXME: WARNOPT (WARN_XDEV, (0, 0, _("%s: directory is on a different filesystem; not dumped"), quotearg_colon (directory->name))); */ directory->children = NO_CHILDREN; else if (flag & PD_FORCE_CHILDREN) { directory->children = PD_CHILDREN(flag); if (directory->children == NO_CHILDREN) *entry = 'N'; } DIR_SET_FLAG (directory, DIRF_INIT); if (directory->children != NO_CHILDREN) { const char *tag_file_name; switch (check_exclusion_tags (name_buffer, &tag_file_name)) { case exclusion_tag_all: /* This warning can be duplicated by code in dump_file0, but only in case when the topmost directory being archived contains an exclusion tag. */ exclusion_tag_warning (name_buffer, tag_file_name, _("directory not dumped")); *entry = 'N'; directory->children = NO_CHILDREN; break; case exclusion_tag_contents: exclusion_tag_warning (name_buffer, tag_file_name, _("contents not dumped")); directory->children = NO_CHILDREN; break; case exclusion_tag_under: exclusion_tag_warning (name_buffer, tag_file_name, _("contents not dumped")); directory->tagfile = tag_file_name; break; case exclusion_tag_none: break; } } return directory; }
int fdutimens (int fd, 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; /* Require that at least one of FD or FILE are potentially valid, to avoid a Linux bug where futimens (AT_FDCWD, NULL) changes "." rather than failing. */ if (fd < 0 && !file) { errno = EBADF; return -1; } /* Some Linux-based NFS clients are buggy, and mishandle timestamps of files in NFS file systems in some cases. We have no configure-time test for this, but please see <https://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) { int result; # if __linux__ || __sun /* As recently as Linux kernel 2.6.32 (Dec 2009), several file systems (xfs, ntfs-3g) have bugs with a single UTIME_OMIT, but work if both times are either explicitly specified or UTIME_NOW. Work around it with a preparatory [f]stat prior to calling futimens/utimensat; fortunately, there is not much timing impact due to the extra syscall even on file systems where UTIME_OMIT would have worked. The same bug occurs in Solaris 11.1 (Apr 2013). FIXME: Simplify this for Linux in 2016 and for Solaris in 2024, when file system bugs are no longer common. */ if (adjustment_needed == 2) { if (fd < 0 ? stat (file, &st) : fstat (fd, &st)) return -1; if (ts[0].tv_nsec == UTIME_OMIT) ts[0] = get_stat_atime (&st); else if (ts[1].tv_nsec == UTIME_OMIT) ts[1] = get_stat_mtime (&st); /* Note that st is good, in case utimensat gives ENOSYS. */ adjustment_needed++; } # endif # if HAVE_UTIMENSAT if (fd < 0) { result = utimensat (AT_FDCWD, file, ts, 0); # ifdef __linux__ /* Work around a kernel bug: https://bugzilla.redhat.com/show_bug.cgi?id=442352 https://bugzilla.redhat.com/show_bug.cgi?id=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 if (0 <= fd) { result = futimens (fd, ts); # 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; lutimensat_works_really = -1; #endif /* HAVE_UTIMENSAT || HAVE_FUTIMENS */ #ifdef USE_SETFILETIME /* On native Windows, use SetFileTime(). See <https://msdn.microsoft.com/en-us/library/ms724933.aspx> <https://msdn.microsoft.com/en-us/library/ms724284.aspx> */ if (0 <= fd) { HANDLE handle; FILETIME current_time; FILETIME last_access_time; FILETIME last_write_time; handle = (HANDLE) _get_osfhandle (fd); if (handle == INVALID_HANDLE_VALUE) { errno = EBADF; return -1; } if (ts == NULL || ts[0].tv_nsec == UTIME_NOW || ts[1].tv_nsec == UTIME_NOW) { /* GetSystemTimeAsFileTime <https://msdn.microsoft.com/en-us/library/ms724397.aspx>. It would be overkill to use GetSystemTimePreciseAsFileTime <https://msdn.microsoft.com/en-us/library/hh706895.aspx>. */ GetSystemTimeAsFileTime (¤t_time); } if (ts == NULL || ts[0].tv_nsec == UTIME_NOW) { last_access_time = current_time; } else if (ts[0].tv_nsec == UTIME_OMIT) { last_access_time.dwLowDateTime = 0; last_access_time.dwHighDateTime = 0; } else { ULONGLONG time_since_16010101 = (ULONGLONG) ts[0].tv_sec * 10000000 + ts[0].tv_nsec / 100 + 116444736000000000LL; last_access_time.dwLowDateTime = (DWORD) time_since_16010101; last_access_time.dwHighDateTime = time_since_16010101 >> 32; } if (ts == NULL || ts[1].tv_nsec == UTIME_NOW) { last_write_time = current_time; } else if (ts[1].tv_nsec == UTIME_OMIT) { last_write_time.dwLowDateTime = 0; last_write_time.dwHighDateTime = 0; } else { ULONGLONG time_since_16010101 = (ULONGLONG) ts[1].tv_sec * 10000000 + ts[1].tv_nsec / 100 + 116444736000000000LL; last_write_time.dwLowDateTime = (DWORD) time_since_16010101; last_write_time.dwHighDateTime = time_since_16010101 >> 32; } if (SetFileTime (handle, NULL, &last_access_time, &last_write_time)) return 0; else { DWORD sft_error = GetLastError (); #if 0 fprintf (stderr, "fdutimens SetFileTime error 0x%x\n", (unsigned int) sft_error); #endif switch (sft_error) { case ERROR_ACCESS_DENIED: /* fd was opened without O_RDWR */ errno = EACCES; /* not specified by POSIX */ break; default: errno = EINVAL; break; } return -1; } }
int rpl_utimensat (int fd, char const *file, struct timespec const times[2], int flag) { # if defined __linux__ || defined __sun struct timespec ts[2]; # endif /* See comments in utimens.c for details. */ static int utimensat_works_really; /* 0 = unknown, 1 = yes, -1 = no. */ if (0 <= utimensat_works_really) { int result; # if defined __linux__ || defined __sun struct stat st; /* As recently as Linux kernel 2.6.32 (Dec 2009), several file systems (xfs, ntfs-3g) have bugs with a single UTIME_OMIT, but work if both times are either explicitly specified or UTIME_NOW. Work around it with a preparatory [l]stat prior to calling utimensat; fortunately, there is not much timing impact due to the extra syscall even on file systems where UTIME_OMIT would have worked. The same bug occurs in Solaris 11.1 (Apr 2013). FIXME: Simplify this for Linux in 2016 and for Solaris in 2024, when file system bugs are no longer common. */ if (times && (times[0].tv_nsec == UTIME_OMIT || times[1].tv_nsec == UTIME_OMIT)) { if (fstatat (fd, file, &st, flag)) return -1; if (times[0].tv_nsec == UTIME_OMIT && times[1].tv_nsec == UTIME_OMIT) return 0; if (times[0].tv_nsec == UTIME_OMIT) ts[0] = get_stat_atime (&st); else ts[0] = times[0]; if (times[1].tv_nsec == UTIME_OMIT) ts[1] = get_stat_mtime (&st); else ts[1] = times[1]; times = ts; } # ifdef __hppa__ /* Linux kernel 2.6.22.19 on hppa does not reject invalid tv_nsec values. */ else if (times && ((times[0].tv_nsec != UTIME_NOW && (times[0].tv_nsec < 0 || times[0].tv_nsec >= 1000000000)) || (times[1].tv_nsec != UTIME_NOW && (times[1].tv_nsec < 0 || times[1].tv_nsec >= 1000000000)))) { errno = EINVAL; return -1; } # endif # endif result = utimensat (fd, file, times, flag); /* Linux kernel 2.6.25 has a bug where it returns EINVAL for UTIME_NOW or UTIME_OMIT with non-zero tv_sec, which local_utimensat works around. Meanwhile, EINVAL for a bad flag is indeterminate whether the native utimensat works, but local_utimensat will also reject it. */ if (result == -1 && errno == EINVAL && (flag & ~AT_SYMLINK_NOFOLLOW)) return result; if (result == 0 || (errno != ENOSYS && errno != EINVAL)) { utimensat_works_really = 1; return result; } } /* No point in trying openat/futimens, since on Linux, futimens is implemented with the same syscall as utimensat. Only avoid the native utimensat due to an ENOSYS failure; an EINVAL error was data-dependent, and the next caller may pass valid data. */ if (0 <= utimensat_works_really && errno == ENOSYS) utimensat_works_really = -1; return local_utimensat (fd, file, times, flag); }
static void do_fprintf (struct format_val *dest, struct segment *segment, const char *pathname, const struct stat *stat_buf) { char hbuf[LONGEST_HUMAN_READABLE + 1]; const char *cp; switch (segment->segkind) { case KIND_PLAIN: /* Plain text string (no % conversion). */ /* trusted */ checked_fwrite(segment->text, 1, segment->text_len, dest); break; case KIND_STOP: /* Terminate argument and flush output. */ /* trusted */ checked_fwrite (segment->text, 1, segment->text_len, dest); checked_fflush (dest); break; case KIND_FORMAT: switch (segment->format_char[0]) { case 'a': /* atime in `ctime' format. */ /* UNTRUSTED, probably unexploitable */ checked_fprintf (dest, segment->text, ctime_format (get_stat_atime (stat_buf))); break; case 'b': /* size in 512-byte blocks */ /* UNTRUSTED, probably unexploitable */ checked_fprintf (dest, segment->text, human_readable ((uintmax_t) ST_NBLOCKS (*stat_buf), hbuf, human_ceiling, ST_NBLOCKSIZE, 512)); break; case 'c': /* ctime in `ctime' format */ /* UNTRUSTED, probably unexploitable */ checked_fprintf (dest, segment->text, ctime_format (get_stat_ctime (stat_buf))); break; case 'd': /* depth in search tree */ /* UNTRUSTED, probably unexploitable */ checked_fprintf (dest, segment->text, state.curdepth); break; case 'D': /* Device on which file exists (stat.st_dev) */ /* trusted */ checked_fprintf (dest, segment->text, human_readable ((uintmax_t) stat_buf->st_dev, hbuf, human_ceiling, 1, 1)); break; case 'f': /* base name of path */ /* sanitised */ { char *base = base_name (pathname); checked_print_quoted (dest, segment->text, base); free (base); } break; case 'F': /* file system type */ /* trusted */ checked_print_quoted (dest, segment->text, filesystem_type (stat_buf, pathname)); break; case 'g': /* group name */ /* trusted */ /* (well, the actual group is selected by the user but * its name was selected by the system administrator) */ { struct group *g; g = getgrgid (stat_buf->st_gid); if (g) { segment->text[segment->text_len] = 's'; checked_fprintf (dest, segment->text, g->gr_name); break; } else { /* Do nothing. */ /*FALLTHROUGH*/ } } /*FALLTHROUGH*/ /*...sometimes, so 'G' case.*/ case 'G': /* GID number */ /* UNTRUSTED, probably unexploitable */ checked_fprintf (dest, segment->text, human_readable ((uintmax_t) stat_buf->st_gid, hbuf, human_ceiling, 1, 1)); break; case 'h': /* leading directories part of path */ /* sanitised */ { cp = strrchr (pathname, '/'); if (cp == NULL) /* No leading directories. */ { /* If there is no slash in the pathname, we still * print the string because it contains characters * other than just '%s'. The %h expands to ".". */ checked_print_quoted (dest, segment->text, "."); } else { char *s = strdup (pathname); s[cp - pathname] = 0; checked_print_quoted (dest, segment->text, s); free (s); } } break; case 'H': /* ARGV element file was found under */ /* trusted */ { char *s = xmalloc (state.starting_path_length+1); memcpy (s, pathname, state.starting_path_length); s[state.starting_path_length] = 0; checked_fprintf (dest, segment->text, s); free (s); } break; case 'i': /* inode number */ /* UNTRUSTED, but not exploitable I think */ /* POSIX does not guarantee that ino_t is unsigned or even * integral (except as an XSI extension), but we'll work on * fixing that if we ever get a report of a system where * ino_t is indeed a signed integral type or a non-integral * arithmetic type. */ checked_fprintf (dest, segment->text, human_readable ((uintmax_t) stat_buf->st_ino, hbuf, human_ceiling, 1, 1)); break; case 'k': /* size in 1K blocks */ /* UNTRUSTED, but not exploitable I think */ checked_fprintf (dest, segment->text, human_readable ((uintmax_t) ST_NBLOCKS (*stat_buf), hbuf, human_ceiling, ST_NBLOCKSIZE, 1024)); break; case 'l': /* object of symlink */ /* sanitised */ #ifdef S_ISLNK { char *linkname = 0; if (S_ISLNK (stat_buf->st_mode)) { linkname = areadlinkat (state.cwd_dir_fd, state.rel_pathname); if (linkname == NULL) { nonfatal_target_file_error (errno, pathname); state.exit_status = 1; } } if (linkname) { checked_print_quoted (dest, segment->text, linkname); } else { /* We still need to honour the field width etc., so this is * not a no-op. */ checked_print_quoted (dest, segment->text, ""); } free (linkname); } #endif /* S_ISLNK */ break; case 'M': /* mode as 10 chars (eg., "-rwxr-x--x" */ /* UNTRUSTED, probably unexploitable */ { char modestring[16] ; filemodestring (stat_buf, modestring); modestring[10] = '\0'; checked_fprintf (dest, segment->text, modestring); } break; case 'm': /* mode as octal number (perms only) */ /* UNTRUSTED, probably unexploitable */ { /* Output the mode portably using the traditional numbers, even if the host unwisely uses some other numbering scheme. But help the compiler in the common case where the host uses the traditional numbering scheme. */ mode_t m = stat_buf->st_mode; bool traditional_numbering_scheme = (S_ISUID == 04000 && S_ISGID == 02000 && S_ISVTX == 01000 && S_IRUSR == 00400 && S_IWUSR == 00200 && S_IXUSR == 00100 && S_IRGRP == 00040 && S_IWGRP == 00020 && S_IXGRP == 00010 && S_IROTH == 00004 && S_IWOTH == 00002 && S_IXOTH == 00001); checked_fprintf (dest, segment->text, (traditional_numbering_scheme ? m & MODE_ALL : ((m & S_ISUID ? 04000 : 0) | (m & S_ISGID ? 02000 : 0) | (m & S_ISVTX ? 01000 : 0) | (m & S_IRUSR ? 00400 : 0) | (m & S_IWUSR ? 00200 : 0) | (m & S_IXUSR ? 00100 : 0) | (m & S_IRGRP ? 00040 : 0) | (m & S_IWGRP ? 00020 : 0) | (m & S_IXGRP ? 00010 : 0) | (m & S_IROTH ? 00004 : 0) | (m & S_IWOTH ? 00002 : 0) | (m & S_IXOTH ? 00001 : 0)))); } break; case 'n': /* number of links */ /* UNTRUSTED, probably unexploitable */ checked_fprintf (dest, segment->text, human_readable ((uintmax_t) stat_buf->st_nlink, hbuf, human_ceiling, 1, 1)); break; case 'p': /* pathname */ /* sanitised */ checked_print_quoted (dest, segment->text, pathname); break; case 'P': /* pathname with ARGV element stripped */ /* sanitised */ if (state.curdepth > 0) { cp = pathname + state.starting_path_length; if (*cp == '/') /* Move past the slash between the ARGV element and the rest of the pathname. But if the ARGV element ends in a slash, we didn't add another, so we've already skipped past it. */ cp++; } else { cp = ""; } checked_print_quoted (dest, segment->text, cp); break; case 's': /* size in bytes */ /* UNTRUSTED, probably unexploitable */ checked_fprintf (dest, segment->text, human_readable ((uintmax_t) stat_buf->st_size, hbuf, human_ceiling, 1, 1)); break; case 'S': /* sparseness */ /* UNTRUSTED, probably unexploitable */ checked_fprintf (dest, segment->text, file_sparseness (stat_buf)); break; case 't': /* mtime in `ctime' format */ /* UNTRUSTED, probably unexploitable */ checked_fprintf (dest, segment->text, ctime_format (get_stat_mtime (stat_buf))); break; case 'u': /* user name */ /* trusted */ /* (well, the actual user is selected by the user on systems * where chown is not restricted, but the user name was * selected by the system administrator) */ { struct passwd *p; p = getpwuid (stat_buf->st_uid); if (p) { segment->text[segment->text_len] = 's'; checked_fprintf (dest, segment->text, p->pw_name); break; } /* else fallthru */ } /* FALLTHROUGH*/ /* .. to case U */ case 'U': /* UID number */ /* UNTRUSTED, probably unexploitable */ checked_fprintf (dest, segment->text, human_readable ((uintmax_t) stat_buf->st_uid, hbuf, human_ceiling, 1, 1)); break; /* %Y: type of file system entry like `ls -l`: * (d,-,l,s,p,b,c,n) n=nonexistent (symlink) */ case 'Y': /* in case of symlink */ /* trusted */ { #ifdef S_ISLNK if (S_ISLNK (stat_buf->st_mode)) { struct stat sbuf; /* If we would normally follow links, do not do so. * If we would normally not follow links, do so. */ if ((following_links () ? optionp_stat : optionl_stat) (state.rel_pathname, &sbuf) != 0) { if ( errno == ENOENT ) { checked_fprintf (dest, segment->text, "N"); break; } else if ( errno == ELOOP ) { checked_fprintf (dest, segment->text, "L"); break; } else { checked_fprintf (dest, segment->text, "?"); error (0, errno, "%s", safely_quote_err_filename (0, pathname)); /* exit_status = 1; return ; */ break; } } checked_fprintf (dest, segment->text, mode_to_filetype (sbuf.st_mode & S_IFMT)); } #endif /* S_ISLNK */ else { checked_fprintf (dest, segment->text, mode_to_filetype (stat_buf->st_mode & S_IFMT)); } } break; case 'y': /* trusted */ { checked_fprintf (dest, segment->text, mode_to_filetype (stat_buf->st_mode & S_IFMT)); } break; case 'Z': /* SELinux security context */ { security_context_t scontext; int rv = (*options.x_getfilecon) (state.cwd_dir_fd, state.rel_pathname, &scontext); if (rv < 0) { /* If getfilecon fails, there will in the general case still be some text to print. We just make %Z expand to an empty string. */ checked_fprintf (dest, segment->text, ""); error (0, errno, _("getfilecon failed: %s"), safely_quote_err_filename (0, pathname)); state.exit_status = 1; } else { checked_fprintf (dest, segment->text, scontext); freecon (scontext); } } break; case 0: case '%': checked_fprintf (dest, segment->text); break; } /* end of KIND_FORMAT case */ break; } }
bool pred_fprintf (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) { struct format_val *dest = &pred_ptr->args.printf_vec; struct segment *segment; for (segment = dest->segment; segment; segment = segment->next) { if ( (KIND_FORMAT == segment->segkind) && segment->format_char[1]) /* Component of date. */ { struct timespec ts; int valid = 0; switch (segment->format_char[0]) { case 'A': ts = get_stat_atime (stat_buf); valid = 1; break; case 'B': ts = get_stat_birthtime (stat_buf); if ('@' == segment->format_char[1]) valid = 1; else valid = (ts.tv_nsec >= 0); break; case 'C': ts = get_stat_ctime (stat_buf); valid = 1; break; case 'T': ts = get_stat_mtime (stat_buf); valid = 1; break; default: assert (0); abort (); } /* We trust the output of format_date not to contain * nasty characters, though the value of the date * is itself untrusted data. */ if (valid) { /* trusted */ checked_fprintf (dest, segment->text, format_date (ts, segment->format_char[1])); } else { /* The specified timestamp is not available, output * nothing for the timestamp, but use the rest (so that * for example find foo -printf '[%Bs] %p\n' can print * "[] foo"). */ /* trusted */ checked_fprintf (dest, segment->text, ""); } } else { /* Print a segment which is not a date. */ do_fprintf (dest, segment, pathname, stat_buf); } } return true; }