bug-findutils
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH 8/8] Move the printing code into print.c.


From: James Youngman
Subject: [PATCH 8/8] Move the printing code into print.c.
Date: Sat, 18 Jun 2011 15:51:01 +0100

* 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 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 find/print.h |    1 +
 4 files changed, 910 insertions(+), 888 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 2be5757..e2d9bd6 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,26 @@
 2011-06-18  James Youngman  <address@hidden>
 
+       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 eadd963..b17c4f9 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 177bf91..b1f5d68 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;
+}
diff --git a/find/print.h b/find/print.h
index 4ae0307..1bad825 100644
--- a/find/print.h
+++ b/find/print.h
@@ -14,3 +14,4 @@ bool
 insert_fprintf (struct format_val *vec,
                const struct parser_table *entry,
                const char *format_const);
+
-- 
1.7.2.5




reply via email to

[Prev in Thread] Current Thread [Next in Thread]