From 1dbd86ec825ba92be12e4aafad49804faaa86ec6 Mon Sep 17 00:00:00 2001
From: Elliott Hughes <enh@google.com>
Date: Fri, 24 May 2019 16:01:47 -0700
Subject: find: add -printf support.

This only implements the format specifiers that I've seen used in the
wild (which is actually a significant fraction of the total supported by
findutils' find). The most obvious gap is in the time support. I'm happy
to add more, but didn't want to add stuff "just because".

I'd say %A@, %C@, and -- for SELinux users -- %Z are probably the most
plausibly useful formats still missing. I don't think the human-readable
date formatting is particularly useful unless someone's seen it actually
used in the wild. The %T+ "full ISO" format being the most likely
exception to that.

Anyway, this is enough for me get started building AOSP with toybox find.
---
 lib/lib.c         |  2 +-
 toys/posix/find.c | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++++---
 2 files changed, 80 insertions(+), 5 deletions(-)

diff --git a/lib/lib.c b/lib/lib.c
index fe15c990..b5825fd2 100644
--- a/lib/lib.c
+++ b/lib/lib.c
@@ -1046,7 +1046,7 @@ char *getdirname(char *name)
 {
   char *s = xstrdup(name), *ss = strrchr(s, '/');
 
-  while (*ss && *ss == '/' && s != ss) *ss-- = 0;
+  while (ss && *ss && *ss == '/' && s != ss) *ss-- = 0;
 
   return s;
 }
diff --git a/toys/posix/find.c b/toys/posix/find.c
index 5cefbf15..8b757b8e 100644
--- a/toys/posix/find.c
+++ b/toys/posix/find.c
@@ -46,10 +46,23 @@ config FIND
     -print   Print match with newline  -print0    Print match with null
     -exec    Run command with path     -execdir   Run command in file's dir
     -ok      Ask before exec           -okdir     Ask before execdir
-    -delete  Remove matching file/dir
+    -delete         Remove matching file/dir
+    -printf FORMAT  Print using format string
 
     Commands substitute "{}" with matched file. End with ";" to run each file,
     or "+" (next argument after "{}") to collect and run with multiple files.
+
+    FORMAT characters are \ escapes and:
+    %f  basename
+    %g  textual gid                    %G  numeric gid
+    %i  decimal inode
+    %l  target of symlink (or empty)
+    %m  octal mode, no leading 0       %M  type and mode in ls format
+    %p  filename                       %P  filename without root
+    %s  size in bytes
+    %T@ last modification unix time with fraction
+    %u  textual uid                    %U  numeric uid
+    %Z  security context
 */
 
 #define FOR_find
@@ -61,6 +74,7 @@ GLOBALS(
   int topdir, xdev, depth;
   time_t now;
   long max_bytes;
+  char *start;
 )
 
 struct execdir_data {
@@ -535,6 +549,65 @@ static int do_find(struct dirtree *new)
 
         // Argument consumed, skip the check.
         goto cont;
+      } else if (!strcmp(s, "printf")) {
+        char *fmt = ss[1], *path, *lnk, *start, mode_str[11], ch;
+
+        print++;
+        if (check) {
+          for (; *fmt; fmt++) {
+            if (*fmt == '\\') {
+              if (!(ch = unescape(fmt[1])))
+                error_exit("bad \\ escape: %c", fmt[1]);
+              fmt++;
+              putchar(ch);
+            } else if (*fmt == '%') {
+              switch (*++fmt) {
+                case '%': putchar('%'); break;
+                case 'f': printf("%s", new->name); break;
+                case 'G': printf("%d", new->st.st_gid); break;
+                case 'g': printf("%s", getgroupname(new->st.st_gid)); break;
+                case 'i': printf("%lld", (long long) new->st.st_ino); break;
+                case 'l':
+                  path = dirtree_path(new, 0);
+                  lnk = xreadlink(path);
+                  printf("%s", lnk ? lnk : "");
+                  free(lnk);
+                  free(path);
+                  break;
+                case 'M':
+                  mode_to_string(new->st.st_mode, mode_str);
+                  printf("%s", mode_str);
+                  break;
+                case 'm': printf("%o", new->st.st_mode & ~S_IFMT); break;
+                case 'P':
+                  start = getdirname(TT.start);
+                  path = dirtree_path(new, 0);
+                  printf("%s", path + 1+strlen(start));
+                  free(path);
+                  free(start);
+                  break;
+                case 'p':
+                  path = dirtree_path(new, 0);
+                  printf("%s", path);
+                  free(path);
+                  break;
+                case 's': printf("%lld", (long long) new->st.st_size); break;
+                case 'T':
+                  switch (*++fmt) {
+                    case '@':
+                      printf("%ld.%ld", new->st.st_mtim.tv_sec,
+                             new->st.st_mtim.tv_nsec);
+                      break;
+                    default: error_exit("bad %%T variant: %%T%c", *fmt);
+                  }
+                  break;
+                case 'U': printf("%d", new->st.st_uid); break;
+                case 'u': printf("%s", getusername(new->st.st_uid)); break;
+                default: error_exit("bad %% specifier: %c", *fmt);
+              }
+            } else putchar(*fmt);
+          }
+        }
       } else goto error;
 
       // This test can go at the end because we do a syntax checking
@@ -586,9 +659,11 @@ void find_main(void)
   do_find(0);
 
   // Loop through paths
-  for (i = 0; i < len; i++)
-    dirtree_flagread(ss[i], DIRTREE_SYMFOLLOW*!!(toys.optflags&(FLAG_H|FLAG_L)),
-      do_find);
+  for (i = 0; i < len; i++) {
+    TT.start = ss[i];
+    dirtree_flagread(TT.start,
+      DIRTREE_SYMFOLLOW*!!(toys.optflags&(FLAG_H|FLAG_L)), do_find);
+  }
 
   execdir(0, 1);
 
-- 
cgit v1.2.3