From 8cfb3b8dbc3c9998c4cfe7aad12cb72d2524df92 Mon Sep 17 00:00:00 2001 From: James Youngman Date: Sat, 18 Jun 2011 13:53:39 +0100 Subject: Move the printing code into print.c. * find/print.c (scan_for_digit_differences): Move to this file from pred.c. (do_time_format): Move to this file from pred.c. (format_date): Likewise. (weekdays): Likewise. (months): Likewise. (ctime_format): Likewise. (file_sparseness): Likewise. (checked_fprintf): Likewise. (checked_print_quoted): Likewise. (checked_fwrite): Likewise. (checked_fflush): Likewise. (HANDLE_TYPE): Likewise. (do_fprintf): Likewise. (pred_fprintf): Likewise. * find/pred.c: Don't include human.h, filemode.h, verify.h or xalloc.h, we don't need them. Don't define MAX. Don't declare ctime_format or format_date. Each of the functions moved into print.c were moved out of this file. * find/print.h: Declare pred_fprintf. --- ChangeLog | 21 ++ find/pred.c | 888 ----------------------------------------------------------- find/print.c | 888 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 909 insertions(+), 888 deletions(-) diff --git a/ChangeLog b/ChangeLog index 7a0af57c..e6d9fef9 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,26 @@ 2011-06-18 James Youngman + Move the printing code into print.c. + * find/print.c (scan_for_digit_differences): Move to this file + from pred.c. + (do_time_format): Move to this file from pred.c. + (format_date): Likewise. + (weekdays): Likewise. + (months): Likewise. + (ctime_format): Likewise. + (file_sparseness): Likewise. + (checked_fprintf): Likewise. + (checked_print_quoted): Likewise. + (checked_fwrite): Likewise. + (checked_fflush): Likewise. + (HANDLE_TYPE): Likewise. + (do_fprintf): Likewise. + (pred_fprintf): Likewise. + * find/pred.c: Don't include human.h, filemode.h, verify.h or + xalloc.h, we don't need them. Don't define MAX. Don't declare + ctime_format or format_date. Each of the functions moved into + print.c were moved out of this file. + Reserve format specifiers %(, %{ and %[ for future use. * find/print.c (insert_fprintf): Reject %(, %{ and %[. (make_segment): Remove code which previously supposedly rejected diff --git a/find/pred.c b/find/pred.c index eadd9636..b17c4f95 100644 --- a/find/pred.c +++ b/find/pred.c @@ -34,10 +34,7 @@ #include /* for unlinkat() */ #include #include -#include "xalloc.h" #include "dirname.h" -#include "human.h" -#include "filemode.h" #include "printquoted.h" #include "yesno.h" #include "listfile.h" @@ -45,7 +42,6 @@ #include "stat-size.h" #include "dircallback.h" #include "error.h" -#include "verify.h" #include "areadlink.h" #include @@ -70,14 +66,8 @@ #define CLOSEDIR(d) closedir (d) #endif -#undef MAX -#define MAX(a, b) ((a) > (b) ? (a) : (b)) - static bool match_lname (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr, bool ignore_case); -static char *format_date (struct timespec ts, int kind); -static char *ctime_format (struct timespec ts); - #ifdef DEBUG struct pred_assoc { @@ -478,538 +468,6 @@ pred_fprint0 (const char *pathname, struct stat *stat_buf, struct predicate *pre -static const char* -mode_to_filetype (mode_t m) -{ -#define HANDLE_TYPE(t,letter) if (m==t) { return letter; } -#ifdef S_IFREG - HANDLE_TYPE(S_IFREG, "f"); /* regular file */ -#endif -#ifdef S_IFDIR - HANDLE_TYPE(S_IFDIR, "d"); /* directory */ -#endif -#ifdef S_IFLNK - HANDLE_TYPE(S_IFLNK, "l"); /* symbolic link */ -#endif -#ifdef S_IFSOCK - HANDLE_TYPE(S_IFSOCK, "s"); /* Unix domain socket */ -#endif -#ifdef S_IFBLK - HANDLE_TYPE(S_IFBLK, "b"); /* block device */ -#endif -#ifdef S_IFCHR - HANDLE_TYPE(S_IFCHR, "c"); /* character device */ -#endif -#ifdef S_IFIFO - HANDLE_TYPE(S_IFIFO, "p"); /* FIFO */ -#endif -#ifdef S_IFDOOR - HANDLE_TYPE(S_IFDOOR, "D"); /* Door (e.g. on Solaris) */ -#endif - return "U"; /* Unknown */ -} - -static double -file_sparseness (const struct stat *p) -{ - if (0 == p->st_size) - { - if (0 == ST_NBLOCKS(*p)) - return 1.0; - else - return ST_NBLOCKS(*p) < 0 ? -HUGE_VAL : HUGE_VAL; - } - else - { - double blklen = ST_NBLOCKSIZE * (double)ST_NBLOCKS(*p); - return blklen / p->st_size; - } -} - - - -static void -checked_fprintf (struct format_val *dest, const char *fmt, ...) -{ - int rv; - va_list ap; - - va_start (ap, fmt); - rv = vfprintf (dest->stream, fmt, ap); - if (rv < 0) - nonfatal_nontarget_file_error (errno, dest->filename); -} - - -static void -checked_print_quoted (struct format_val *dest, - const char *format, const char *s) -{ - int rv = print_quoted (dest->stream, dest->quote_opts, dest->dest_is_tty, - format, s); - if (rv < 0) - nonfatal_nontarget_file_error (errno, dest->filename); -} - - -static void -checked_fwrite (void *p, size_t siz, size_t nmemb, struct format_val *dest) -{ - const size_t items_written = fwrite (p, siz, nmemb, dest->stream); - if (items_written < nmemb) - nonfatal_nontarget_file_error (errno, dest->filename); -} - -static void -checked_fflush (struct format_val *dest) -{ - if (0 != fflush (dest->stream)) - { - nonfatal_nontarget_file_error (errno, dest->filename); - } -} - -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 */ - 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; - } - /* 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; -} - bool pred_fstype (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) { @@ -1746,352 +1204,6 @@ pred_context (const char *pathname, struct stat *stat_buf, return rv; } - - - - -static bool -scan_for_digit_differences (const char *p, const char *q, - size_t *first, size_t *n) -{ - bool seen = false; - size_t i; - - for (i=0; p[i] && q[i]; i++) - { - if (p[i] != q[i]) - { - if (!isdigit ((unsigned char)q[i]) || !isdigit ((unsigned char)q[i])) - return false; - - if (!seen) - { - *first = i; - *n = 1; - seen = 1; - } - else - { - if (i-*first == *n) - { - /* Still in the first sequence of differing digits. */ - ++*n; - } - else - { - /* More than one differing contiguous character sequence. */ - return false; - } - } - } - } - if (p[i] || q[i]) - { - /* strings are different lengths. */ - return false; - } - return true; -} - - -static char* -do_time_format (const char *fmt, const struct tm *p, const char *ns, size_t ns_size) -{ - static char *buf = NULL; - static size_t buf_size; - char *timefmt = NULL; - struct tm altered_time; - - - /* If the format expands to nothing (%p in some locales, for - * example), strftime can return 0. We actually want to distinguish - * the error case where the buffer is too short, so we just prepend - * an otherwise uninteresting character to prevent the no-output - * case. - */ - timefmt = xmalloc (strlen (fmt) + 2u); - sprintf (timefmt, "_%s", fmt); - - /* altered_time is a similar time, but in which both - * digits of the seconds field are different. - */ - altered_time = *p; - if (altered_time.tm_sec >= 11) - altered_time.tm_sec -= 11; - else - altered_time.tm_sec += 11; - - /* If we call strftime() with buf_size=0, the program will coredump - * on Solaris, since it unconditionally writes the terminating null - * character. - */ - buf_size = 1u; - buf = xmalloc (buf_size); - while (true) - { - /* I'm not sure that Solaris will return 0 when the buffer is too small. - * Therefore we do not check for (buf_used != 0) as the termination - * condition. - */ - size_t buf_used = strftime (buf, buf_size, timefmt, p); - if (buf_used /* Conforming POSIX system */ - && (buf_used < buf_size)) /* Solaris workaround */ - { - char *altbuf; - size_t i = 0, n = 0; - size_t final_len = (buf_used - + 1u /* for \0 */ - + ns_size); - buf = xrealloc (buf, final_len); - altbuf = xmalloc (final_len); - strftime (altbuf, buf_size, timefmt, &altered_time); - - /* Find the seconds digits; they should be the only changed part. - * In theory the result of the two formatting operations could differ in - * more than just one sequence of decimal digits (for example %X might - * in theory return a spelled-out time like "thirty seconds past noon"). - * When that happens, we just avoid inserting the nanoseconds field. - */ - if (scan_for_digit_differences (buf, altbuf, &i, &n) - && (2==n) && !isdigit ((unsigned char)buf[i+n])) - { - const size_t end_of_seconds = i + n; - const size_t suffix_len = buf_used-(end_of_seconds)+1; - - /* Move the tail (including the \0). Note that this - * is a move of an overlapping memory block, so we - * must use memmove instead of memcpy. Then insert - * the nanoseconds (but not its trailing \0). - */ - assert (end_of_seconds + ns_size + suffix_len == final_len); - memmove (buf+end_of_seconds+ns_size, - buf+end_of_seconds, - suffix_len); - memcpy (buf+i+n, ns, ns_size); - } - else - { - /* No seconds digits. No need to insert anything. */ - } - /* The first character of buf is the underscore, which we actually - * don't want. - */ - free (timefmt); - return buf+1; - } - else - { - buf = x2nrealloc (buf, &buf_size, 2u); - } - } -} - - - -/* Return a static string formatting the time WHEN according to the - * strftime format character KIND. - * - * This function contains a number of assertions. These look like - * runtime checks of the results of computations, which would be a - * problem since external events should not be tested for with - * "assert" (instead you should use "if"). However, they are not - * really runtime checks. The assertions actually exist to verify - * that the various buffers are correctly sized. - */ -static char * -format_date (struct timespec ts, int kind) -{ - /* In theory, we use an extra 10 characters for 9 digits of - * nanoseconds and 1 for the decimal point. However, the real - * world is more complex than that. - * - * For example, some systems return junk in the tv_nsec part of - * st_birthtime. An example of this is the NetBSD-4.0-RELENG kernel - * (at Sat Mar 24 18:46:46 2007) running a NetBSD-3.1-RELEASE - * runtime and examining files on an msdos filesytem. So for that - * reason we set NS_BUF_LEN to 32, which is simply "long enough" as - * opposed to "exactly the right size". Note that the behaviour of - * NetBSD appears to be a result of the use of uninitialised data, - * as it's not 100% reproducible (more like 25%). - */ - enum { - NS_BUF_LEN = 32, - DATE_LEN_PERCENT_APLUS=21 /* length of result of %A+ (it's longer than %c)*/ - }; - static char buf[128u+10u + MAX(DATE_LEN_PERCENT_APLUS, - MAX (LONGEST_HUMAN_READABLE + 2, NS_BUF_LEN+64+200))]; - char ns_buf[NS_BUF_LEN]; /* -.9999999990 (- sign can happen!)*/ - int charsprinted, need_ns_suffix; - struct tm *tm; - char fmt[6]; - - /* human_readable() assumes we pass a buffer which is at least as - * long as LONGEST_HUMAN_READABLE. We use an assertion here to - * ensure that no nasty unsigned overflow happend in our calculation - * of the size of buf. Do the assertion here rather than in the - * code for %@ so that we find the problem quickly if it exists. If - * you want to submit a patch to move this into the if statement, go - * ahead, I'll apply it. But include performance timings - * demonstrating that the performance difference is actually - * measurable. - */ - verify (sizeof (buf) >= LONGEST_HUMAN_READABLE); - - charsprinted = 0; - need_ns_suffix = 0; - - /* Format the main part of the time. */ - if (kind == '+') - { - strcpy (fmt, "%F+%T"); - need_ns_suffix = 1; - } - else - { - fmt[0] = '%'; - fmt[1] = kind; - fmt[2] = '\0'; - - /* %a, %c, and %t are handled in ctime_format() */ - switch (kind) - { - case 'S': - case 'T': - case 'X': - case '@': - need_ns_suffix = 1; - break; - default: - need_ns_suffix = 0; - break; - } - } - - if (need_ns_suffix) - { - /* Format the nanoseconds part. Leave a trailing zero to - * discourage people from writing scripts which extract the - * fractional part of the timestamp by using column offsets. - * The reason for discouraging this is that in the future, the - * granularity may not be nanoseconds. - */ - charsprinted = snprintf (ns_buf, NS_BUF_LEN, ".%09ld0", (long int)ts.tv_nsec); - assert (charsprinted < NS_BUF_LEN); - } - else - { - charsprinted = 0; - ns_buf[0] = 0; - } - - if (kind != '@') - { - tm = localtime (&ts.tv_sec); - if (tm) - { - char *s = do_time_format (fmt, tm, ns_buf, charsprinted); - if (s) - return s; - } - } - - /* If we get to here, either the format was %@, or we have fallen back to it - * because strftime failed. - */ - if (1) - { - uintmax_t w = ts.tv_sec; - size_t used, len, remaining; - - /* XXX: note that we are negating an unsigned type which is the - * widest possible unsigned type. - */ - char *p = human_readable (ts.tv_sec < 0 ? -w : w, buf + 1, - human_ceiling, 1, 1); - assert (p > buf); - assert (p < (buf + (sizeof buf))); - if (ts.tv_sec < 0) - *--p = '-'; /* XXX: Ugh, relying on internal details of human_readable(). */ - - /* Add the nanoseconds part. Because we cannot enforce a - * particlar implementation of human_readable, we cannot assume - * any particular value for (p-buf). So we need to be careful - * that there is enough space remaining in the buffer. - */ - if (need_ns_suffix) - { - len = strlen (p); - used = (p-buf) + len; /* Offset into buf of current end */ - assert (sizeof buf > used); /* Ensure we can perform subtraction safely. */ - remaining = sizeof buf - used - 1u; /* allow space for NUL */ - - if (strlen (ns_buf) >= remaining) - { - error (0, 0, - "charsprinted=%ld but remaining=%lu: ns_buf=%s", - (long)charsprinted, (unsigned long)remaining, ns_buf); - } - assert (strlen (ns_buf) < remaining); - strcat (p, ns_buf); - } - return p; - } -} - -static const char *weekdays[] = - { - "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" - }; -static const char * months[] = - { - "Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" - }; - - -static char * -ctime_format (struct timespec ts) -{ - const struct tm * ptm; -#define TIME_BUF_LEN 1024 - static char resultbuf[TIME_BUF_LEN]; - int nout; - - ptm = localtime (&ts.tv_sec); - if (ptm) - { - assert (ptm->tm_wday >= 0); - assert (ptm->tm_wday < 7); - assert (ptm->tm_mon >= 0); - assert (ptm->tm_mon < 12); - assert (ptm->tm_hour >= 0); - assert (ptm->tm_hour < 24); - assert (ptm->tm_min < 60); - assert (ptm->tm_sec <= 61); /* allows 2 leap seconds. */ - - /* wkday mon mday hh:mm:ss.nnnnnnnnn yyyy */ - nout = snprintf (resultbuf, TIME_BUF_LEN, - "%3s %3s %2d %02d:%02d:%02d.%010ld %04d", - weekdays[ptm->tm_wday], - months[ptm->tm_mon], - ptm->tm_mday, - ptm->tm_hour, - ptm->tm_min, - ptm->tm_sec, - (long int)ts.tv_nsec, - 1900 + ptm->tm_year); - - assert (nout < TIME_BUF_LEN); - return resultbuf; - } - else - { - /* The time cannot be represented as a struct tm. - Output it as an integer. */ - return format_date (ts, '@'); - } -} - /* Copy STR into BUF and trim blanks from the end of BUF. Return BUF. */ diff --git a/find/print.c b/find/print.c index 177bf913..b1f5d68a 100644 --- a/find/print.c +++ b/find/print.c @@ -23,10 +23,25 @@ /* system headers go here. */ #include #include +#include +#include #include +#include +#include +#include +#include +#include /* gnulib headers. */ +#include "areadlink.h" +#include "dirname.h" #include "error.h" +#include "filemode.h" +#include "human.h" +#include "printquoted.h" +#include "stat-size.h" +#include "stat-time.h" +#include "verify.h" #include "xalloc.h" /* find-specific headers. */ @@ -51,6 +66,9 @@ #else # define ISDIGIT(c) (isascii ((unsigned char)c) && isdigit ((unsigned char)c)) #endif +#undef MAX +#define MAX(a, b) ((a) > (b) ? (a) : (b)) + /* Create a new fprintf segment in *SEGMENT, with type KIND, from the text in FORMAT, which has length LEN. @@ -352,3 +370,873 @@ insert_fprintf (struct format_val *vec, our_pred); return true; } + +static bool +scan_for_digit_differences (const char *p, const char *q, + size_t *first, size_t *n) +{ + bool seen = false; + size_t i; + + for (i=0; p[i] && q[i]; i++) + { + if (p[i] != q[i]) + { + if (!isdigit ((unsigned char)q[i]) || !isdigit ((unsigned char)q[i])) + return false; + + if (!seen) + { + *first = i; + *n = 1; + seen = 1; + } + else + { + if (i-*first == *n) + { + /* Still in the first sequence of differing digits. */ + ++*n; + } + else + { + /* More than one differing contiguous character sequence. */ + return false; + } + } + } + } + if (p[i] || q[i]) + { + /* strings are different lengths. */ + return false; + } + return true; +} + +static char* +do_time_format (const char *fmt, const struct tm *p, const char *ns, size_t ns_size) +{ + static char *buf = NULL; + static size_t buf_size; + char *timefmt = NULL; + struct tm altered_time; + + + /* If the format expands to nothing (%p in some locales, for + * example), strftime can return 0. We actually want to distinguish + * the error case where the buffer is too short, so we just prepend + * an otherwise uninteresting character to prevent the no-output + * case. + */ + timefmt = xmalloc (strlen (fmt) + 2u); + sprintf (timefmt, "_%s", fmt); + + /* altered_time is a similar time, but in which both + * digits of the seconds field are different. + */ + altered_time = *p; + if (altered_time.tm_sec >= 11) + altered_time.tm_sec -= 11; + else + altered_time.tm_sec += 11; + + /* If we call strftime() with buf_size=0, the program will coredump + * on Solaris, since it unconditionally writes the terminating null + * character. + */ + buf_size = 1u; + buf = xmalloc (buf_size); + while (true) + { + /* I'm not sure that Solaris will return 0 when the buffer is too small. + * Therefore we do not check for (buf_used != 0) as the termination + * condition. + */ + size_t buf_used = strftime (buf, buf_size, timefmt, p); + if (buf_used /* Conforming POSIX system */ + && (buf_used < buf_size)) /* Solaris workaround */ + { + char *altbuf; + size_t i = 0, n = 0; + size_t final_len = (buf_used + + 1u /* for \0 */ + + ns_size); + buf = xrealloc (buf, final_len); + altbuf = xmalloc (final_len); + strftime (altbuf, buf_size, timefmt, &altered_time); + + /* Find the seconds digits; they should be the only changed part. + * In theory the result of the two formatting operations could differ in + * more than just one sequence of decimal digits (for example %X might + * in theory return a spelled-out time like "thirty seconds past noon"). + * When that happens, we just avoid inserting the nanoseconds field. + */ + if (scan_for_digit_differences (buf, altbuf, &i, &n) + && (2==n) && !isdigit ((unsigned char)buf[i+n])) + { + const size_t end_of_seconds = i + n; + const size_t suffix_len = buf_used-(end_of_seconds)+1; + + /* Move the tail (including the \0). Note that this + * is a move of an overlapping memory block, so we + * must use memmove instead of memcpy. Then insert + * the nanoseconds (but not its trailing \0). + */ + assert (end_of_seconds + ns_size + suffix_len == final_len); + memmove (buf+end_of_seconds+ns_size, + buf+end_of_seconds, + suffix_len); + memcpy (buf+i+n, ns, ns_size); + } + else + { + /* No seconds digits. No need to insert anything. */ + } + /* The first character of buf is the underscore, which we actually + * don't want. + */ + free (timefmt); + return buf+1; + } + else + { + buf = x2nrealloc (buf, &buf_size, 2u); + } + } +} + +/* Return a static string formatting the time WHEN according to the + * strftime format character KIND. + * + * This function contains a number of assertions. These look like + * runtime checks of the results of computations, which would be a + * problem since external events should not be tested for with + * "assert" (instead you should use "if"). However, they are not + * really runtime checks. The assertions actually exist to verify + * that the various buffers are correctly sized. + */ +static char * +format_date (struct timespec ts, int kind) +{ + /* In theory, we use an extra 10 characters for 9 digits of + * nanoseconds and 1 for the decimal point. However, the real + * world is more complex than that. + * + * For example, some systems return junk in the tv_nsec part of + * st_birthtime. An example of this is the NetBSD-4.0-RELENG kernel + * (at Sat Mar 24 18:46:46 2007) running a NetBSD-3.1-RELEASE + * runtime and examining files on an msdos filesytem. So for that + * reason we set NS_BUF_LEN to 32, which is simply "long enough" as + * opposed to "exactly the right size". Note that the behaviour of + * NetBSD appears to be a result of the use of uninitialised data, + * as it's not 100% reproducible (more like 25%). + */ + enum { + NS_BUF_LEN = 32, + DATE_LEN_PERCENT_APLUS=21 /* length of result of %A+ (it's longer than %c)*/ + }; + static char buf[128u+10u + MAX(DATE_LEN_PERCENT_APLUS, + MAX (LONGEST_HUMAN_READABLE + 2, NS_BUF_LEN+64+200))]; + char ns_buf[NS_BUF_LEN]; /* -.9999999990 (- sign can happen!)*/ + int charsprinted, need_ns_suffix; + struct tm *tm; + char fmt[6]; + + /* human_readable() assumes we pass a buffer which is at least as + * long as LONGEST_HUMAN_READABLE. We use an assertion here to + * ensure that no nasty unsigned overflow happend in our calculation + * of the size of buf. Do the assertion here rather than in the + * code for %@ so that we find the problem quickly if it exists. If + * you want to submit a patch to move this into the if statement, go + * ahead, I'll apply it. But include performance timings + * demonstrating that the performance difference is actually + * measurable. + */ + verify (sizeof (buf) >= LONGEST_HUMAN_READABLE); + + charsprinted = 0; + need_ns_suffix = 0; + + /* Format the main part of the time. */ + if (kind == '+') + { + strcpy (fmt, "%F+%T"); + need_ns_suffix = 1; + } + else + { + fmt[0] = '%'; + fmt[1] = kind; + fmt[2] = '\0'; + + /* %a, %c, and %t are handled in ctime_format() */ + switch (kind) + { + case 'S': + case 'T': + case 'X': + case '@': + need_ns_suffix = 1; + break; + default: + need_ns_suffix = 0; + break; + } + } + + if (need_ns_suffix) + { + /* Format the nanoseconds part. Leave a trailing zero to + * discourage people from writing scripts which extract the + * fractional part of the timestamp by using column offsets. + * The reason for discouraging this is that in the future, the + * granularity may not be nanoseconds. + */ + charsprinted = snprintf (ns_buf, NS_BUF_LEN, ".%09ld0", (long int)ts.tv_nsec); + assert (charsprinted < NS_BUF_LEN); + } + else + { + charsprinted = 0; + ns_buf[0] = 0; + } + + if (kind != '@') + { + tm = localtime (&ts.tv_sec); + if (tm) + { + char *s = do_time_format (fmt, tm, ns_buf, charsprinted); + if (s) + return s; + } + } + + /* If we get to here, either the format was %@, or we have fallen back to it + * because strftime failed. + */ + if (1) + { + uintmax_t w = ts.tv_sec; + size_t used, len, remaining; + + /* XXX: note that we are negating an unsigned type which is the + * widest possible unsigned type. + */ + char *p = human_readable (ts.tv_sec < 0 ? -w : w, buf + 1, + human_ceiling, 1, 1); + assert (p > buf); + assert (p < (buf + (sizeof buf))); + if (ts.tv_sec < 0) + *--p = '-'; /* XXX: Ugh, relying on internal details of human_readable(). */ + + /* Add the nanoseconds part. Because we cannot enforce a + * particlar implementation of human_readable, we cannot assume + * any particular value for (p-buf). So we need to be careful + * that there is enough space remaining in the buffer. + */ + if (need_ns_suffix) + { + len = strlen (p); + used = (p-buf) + len; /* Offset into buf of current end */ + assert (sizeof buf > used); /* Ensure we can perform subtraction safely. */ + remaining = sizeof buf - used - 1u; /* allow space for NUL */ + + if (strlen (ns_buf) >= remaining) + { + error (0, 0, + "charsprinted=%ld but remaining=%lu: ns_buf=%s", + (long)charsprinted, (unsigned long)remaining, ns_buf); + } + assert (strlen (ns_buf) < remaining); + strcat (p, ns_buf); + } + return p; + } +} + +static const char *weekdays[] = + { + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" + }; +static const char * months[] = + { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" + }; + + +static char * +ctime_format (struct timespec ts) +{ + const struct tm * ptm; +#define TIME_BUF_LEN 1024 + static char resultbuf[TIME_BUF_LEN]; + int nout; + + ptm = localtime (&ts.tv_sec); + if (ptm) + { + assert (ptm->tm_wday >= 0); + assert (ptm->tm_wday < 7); + assert (ptm->tm_mon >= 0); + assert (ptm->tm_mon < 12); + assert (ptm->tm_hour >= 0); + assert (ptm->tm_hour < 24); + assert (ptm->tm_min < 60); + assert (ptm->tm_sec <= 61); /* allows 2 leap seconds. */ + + /* wkday mon mday hh:mm:ss.nnnnnnnnn yyyy */ + nout = snprintf (resultbuf, TIME_BUF_LEN, + "%3s %3s %2d %02d:%02d:%02d.%010ld %04d", + weekdays[ptm->tm_wday], + months[ptm->tm_mon], + ptm->tm_mday, + ptm->tm_hour, + ptm->tm_min, + ptm->tm_sec, + (long int)ts.tv_nsec, + 1900 + ptm->tm_year); + + assert (nout < TIME_BUF_LEN); + return resultbuf; + } + else + { + /* The time cannot be represented as a struct tm. + Output it as an integer. */ + return format_date (ts, '@'); + } +} + +static double +file_sparseness (const struct stat *p) +{ + if (0 == p->st_size) + { + if (0 == ST_NBLOCKS(*p)) + return 1.0; + else + return ST_NBLOCKS(*p) < 0 ? -HUGE_VAL : HUGE_VAL; + } + else + { + double blklen = ST_NBLOCKSIZE * (double)ST_NBLOCKS(*p); + return blklen / p->st_size; + } +} + +static void +checked_fprintf (struct format_val *dest, const char *fmt, ...) +{ + int rv; + va_list ap; + + va_start (ap, fmt); + rv = vfprintf (dest->stream, fmt, ap); + if (rv < 0) + nonfatal_nontarget_file_error (errno, dest->filename); +} + +static void +checked_print_quoted (struct format_val *dest, + const char *format, const char *s) +{ + int rv = print_quoted (dest->stream, dest->quote_opts, dest->dest_is_tty, + format, s); + if (rv < 0) + nonfatal_nontarget_file_error (errno, dest->filename); +} + + +static void +checked_fwrite (void *p, size_t siz, size_t nmemb, struct format_val *dest) +{ + const size_t items_written = fwrite (p, siz, nmemb, dest->stream); + if (items_written < nmemb) + nonfatal_nontarget_file_error (errno, dest->filename); +} + +static void +checked_fflush (struct format_val *dest) +{ + if (0 != fflush (dest->stream)) + { + nonfatal_nontarget_file_error (errno, dest->filename); + } +} + +static const char* +mode_to_filetype (mode_t m) +{ +#define HANDLE_TYPE(t,letter) if (m==t) { return letter; } +#ifdef S_IFREG + HANDLE_TYPE(S_IFREG, "f"); /* regular file */ +#endif +#ifdef S_IFDIR + HANDLE_TYPE(S_IFDIR, "d"); /* directory */ +#endif +#ifdef S_IFLNK + HANDLE_TYPE(S_IFLNK, "l"); /* symbolic link */ +#endif +#ifdef S_IFSOCK + HANDLE_TYPE(S_IFSOCK, "s"); /* Unix domain socket */ +#endif +#ifdef S_IFBLK + HANDLE_TYPE(S_IFBLK, "b"); /* block device */ +#endif +#ifdef S_IFCHR + HANDLE_TYPE(S_IFCHR, "c"); /* character device */ +#endif +#ifdef S_IFIFO + HANDLE_TYPE(S_IFIFO, "p"); /* FIFO */ +#endif +#ifdef S_IFDOOR + HANDLE_TYPE(S_IFDOOR, "D"); /* Door (e.g. on Solaris) */ +#endif + return "U"; /* Unknown */ +} + + + +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 */ + 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; + } + /* 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; +} -- cgit v1.2.1