/* 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; }
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 */ } }
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; }