summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJames Youngman <jay@gnu.org>2011-06-18 13:53:39 +0100
committerJames Youngman <jay@gnu.org>2011-06-19 11:49:34 +0100
commit8cfb3b8dbc3c9998c4cfe7aad12cb72d2524df92 (patch)
treed16c201ce0c8d3b549d9dc753995db0ef34febcb
parent9cb1805e619b0f3aebd0984856de24964218bce2 (diff)
downloadfindutils-8cfb3b8dbc3c9998c4cfe7aad12cb72d2524df92.tar.gz
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.
-rw-r--r--ChangeLog21
-rw-r--r--find/pred.c888
-rw-r--r--find/print.c888
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 <jay@gnu.org>
+ 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 <unistd.h> /* for unlinkat() */
#include <sys/wait.h>
#include <dirent.h>
-#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 <selinux/selinux.h>
@@ -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 <assert.h>
#include <ctype.h>
+#include <errno.h>
+#include <grp.h>
#include <locale.h>
+#include <math.h>
+#include <pwd.h>
+#include <stdarg.h>
+#include <sys/stat.h>
+#include <sys/types.h>
/* 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;
+}