From 80c6b26efd4eaf091c44c0c4b5dbaa778cfdfa8e Mon Sep 17 00:00:00 2001
From: Rob Landley <rob@landley.net>
Date: Tue, 5 Jan 2016 12:14:24 -0600
Subject: Work towards making "find . -execdir echo {} + -execdir ls {} +"
 work, (not finished yet) plus some error message improvements.

---
 toys/posix/find.c | 113 +++++++++++++++++++++++++++++++++++++-----------------
 1 file changed, 77 insertions(+), 36 deletions(-)

(limited to 'toys')

diff --git a/toys/posix/find.c b/toys/posix/find.c
index 94179cb3..4f8db5af 100644
--- a/toys/posix/find.c
+++ b/toys/posix/find.c
@@ -7,6 +7,8 @@
  * Our "unspecified" behavior for no paths is to use "."
  * Parentheses can only stack 4096 deep
  * Not treating two {} as an error, but only using last
+ *
+ * TODO: -empty (dirs too!) -delete -execdir +
 
 USE_FIND(NEWTOY(find, "?^HL[-HL]", TOYFLAG_USR|TOYFLAG_BIN))
 
@@ -32,7 +34,7 @@ config FIND
     -ctime N        created N days ago        -mtime N    modified N days ago
     -newer FILE     newer mtime than FILE     -mindepth # at least # dirs down
     -depth          ignore contents of dir    -maxdepth # at most # dirs down
-    -inum  N        inode number N
+    -inum  N        inode number N            -empty      empty files and dirs
     -type [bcdflps] (block, char, dir, file, symlink, pipe, socket)
 
     Numbers N may be prefixed by a - (less than) or + (greater than):
@@ -59,32 +61,37 @@ GLOBALS(
   time_t now;
 )
 
+struct execdir_data {
+  struct execdir_data *next;
+
+  int namecount;
+  struct double_list *names;
+};
+
 // None of this can go in TT because you can have more than one -exec
 struct exec_range {
   char *next, *prev;  // layout compatible with struct double_list
 
-  int dir, plus, arglen, argsize, curly, namecount;
+  int dir, plus, arglen, argsize, curly;
   char **argstart;
-  struct double_list *names;
+  struct execdir_data exec, *execdir;
 };
 
 // Perform pending -exec (if any)
 static int flush_exec(struct dirtree *new, struct exec_range *aa)
 {
-  struct double_list **dl;
+  struct execdir_data *bb = aa->execdir ? aa->execdir : &aa->exec;
   char **newargs;
-  int rc = 0;
+  int rc;
 
-  if (!aa->namecount) return 0;
+  if (!bb->namecount) return 0;
 
-  if (aa->dir && new->parent) dl = (void *)&new->parent->extra;
-  else dl = &aa->names;
-  dlist_terminate(*dl);
+  dlist_terminate(bb->names);
 
   // switch to directory for -execdir, or back to top if we have an -execdir
   // _and_ a normal -exec, or are at top of tree in -execdir
   if (aa->dir && new->parent) rc = fchdir(new->parent->dirfd);
-  else if (TT.topdir != -1) rc = fchdir(TT.topdir);
+  else rc = fchdir(TT.topdir);
   if (rc) {
     perror_msg("%s", new->name);
 
@@ -92,17 +99,17 @@ static int flush_exec(struct dirtree *new, struct exec_range *aa)
   }
 
   // execdir: accumulated execs in this directory's children.
-  newargs = xmalloc(sizeof(char *)*(aa->arglen+aa->namecount+1));
+  newargs = xmalloc(sizeof(char *)*(aa->arglen+bb->namecount+1));
   if (aa->curly < 0) {
     memcpy(newargs, aa->argstart, sizeof(char *)*aa->arglen);
     newargs[aa->arglen] = 0;
   } else {
-    struct double_list *dl2 = *dl;
     int pos = aa->curly, rest = aa->arglen - aa->curly;
+    struct double_list *dl;
 
     // Collate argument list
     memcpy(newargs, aa->argstart, sizeof(char *)*pos);
-    for (dl2 = *dl; dl2; dl2 = dl2->next) newargs[pos++] = dl2->data;
+    for (dl = bb->names; dl; dl = dl->next) newargs[pos++] = dl->data;
     rest = aa->arglen - aa->curly - 1;
     memcpy(newargs+pos, aa->argstart+aa->curly+1, sizeof(char *)*rest);
     newargs[pos+rest] = 0;
@@ -110,9 +117,9 @@ static int flush_exec(struct dirtree *new, struct exec_range *aa)
 
   rc = xrun(newargs);
 
-  llist_traverse(*dl, llist_free_double);
-  *dl = 0;
-  aa->namecount = 0;
+  llist_traverse(bb->names, llist_free_double);
+  bb->names = 0;
+  bb->namecount = 0;
 
   return rc;
 }
@@ -159,10 +166,13 @@ static int do_find(struct dirtree *new)
       if (TT.xdev && new->st.st_dev != new->parent->st.st_dev) recurse = 0;
     }
     if (S_ISDIR(new->st.st_mode)) {
+      struct double_list *dl;
+      struct exec_range *aa;
+      struct execdir_data *bb;
+
       if (!new->again) {
         struct dirtree *n;
 
-        if (TT.depth) return recurse;
         for (n = new->parent; n; n = n->parent) {
           if (n->st.st_ino==new->st.st_ino && n->st.st_dev==new->st.st_dev) {
             error_msg("'%s': loop detected", s = dirtree_path(new, 0));
@@ -171,15 +181,37 @@ static int do_find(struct dirtree *new)
             return 0;
           }
         }
+        // Push new per-directory struct for -execdir/okdir + codepath. (Can't
+        // use new->extra because command line may have multiple -execdir)
+        if (TT.topdir != -1) for (dl = TT.argdata; dl; dl = dl->next) {
+          if (dl->prev != (void *)1) continue;
+          aa = (void *)dl;
+          if (!aa->plus || !aa->dir) continue;
+          bb = xzalloc(sizeof(struct execdir_data));
+          bb->next = aa->execdir;
+          aa->execdir = bb;
+        }
+        if (TT.depth) return recurse;
+      // On COMEAGAIN call flush pending "-execdir +" instances for this dir
+      // or flush everything for -exec at top
       } else {
         struct double_list *dl;
 
-        if (TT.topdir != -1)
-          for (dl = TT.argdata; dl; dl = dl->next)
-            if (dl->prev == (void *)1 || !new->parent)
-              toys.exitval |= flush_exec(new, (void *)dl);
+        if (TT.topdir != -1) for (dl = TT.argdata; dl; dl = dl->next) {
+          if (dl->prev != (void *)1) continue;
+          aa = (void *)dl;
+          if (!aa->plus) continue;
+          if (!new->parent || aa->dir) toys.exitval |= flush_exec(new, aa);
+
+          // pop per-directory struct
+          if ((bb = aa->execdir)) {
+            aa->execdir = bb->next;
+            free(bb);
+          }
+        }
 
-        return 0;
+        recurse = 0;
+        if (!TT.depth) return 0;
       }
     }
   }
@@ -394,16 +426,17 @@ static int do_find(struct dirtree *new)
               }
             } else aa->argsize += sizeof(char *) + strlen(ss[len]) + 1;
           }
-          if (!ss[len]) error_exit("-exec without \\;");
+          if (!ss[len]) error_exit("-exec without %s",
+            aa->curly!=-1 ? "\\;" : "{}");
           ss += len;
           aa->arglen = len;
           aa->dir = !!strchr(s, 'd');
-          if (aa->dir && TT.topdir == -1) TT.topdir = xopen(".", 0);
+          if (TT.topdir == -1) TT.topdir = xopen(".", 0);
 
         // collect names and execute commands
         } else {
           char *name, *ss1 = ss[1];
-          struct double_list **ddl;
+          struct execdir_data *bb;
 
           // Grab command line exec argument list
           aa = (void *)llist_pop(&argdata);
@@ -413,10 +446,6 @@ static int do_find(struct dirtree *new)
           // name is always a new malloc, so we can always free it.
           name = aa->dir ? xstrdup(new->name) : dirtree_path(new, 0);
 
-          // Mark entry so COMEAGAIN can call flush_exec() in parent.
-          // This is never a valid pointer value for prev to have otherwise
-          if (aa->dir) aa->prev = (void *)1;
-
           if (*s == 'o') {
             fprintf(stderr, "[%s] %s", ss1, name);
             if (!(test = yesno(0))) {
@@ -426,13 +455,25 @@ static int do_find(struct dirtree *new)
           }
 
           // Add next name to list (global list without -dir, local with)
-          if (aa->dir && new->parent)
-            ddl = (struct double_list **)&new->parent->extra;
-          else ddl = &aa->names;
-
-          dlist_add(ddl, name);
-          aa->namecount++;
-          if (!aa->plus) test = flush_exec(new, aa);
+          bb = aa->execdir ? aa->execdir : &aa->exec;
+          dlist_add(&bb->names, name);
+          bb->namecount++;
+
+          // -exec + collates and saves result in exitval
+          if (aa->plus) {
+            // Mark entry so COMEAGAIN can call flush_exec() in parent.
+            // This is never a valid pointer value for prev to have otherwise
+            // Done here vs argument parsing pass so it's after dlist_terminate
+            aa->prev = (void *)1;
+
+            // Flush if we pass 16 megs of environment space.
+            // An insanely long path (>2 gigs) could wrap the counter and
+            // defeat this test, which could potentially trigger OOM killer.
+            if ((aa->plus += sizeof(char *)+strlen(name)+1) > 1<<24) {
+              aa->plus = 1;
+              toys.exitval |= flush_exec(new, aa);
+            }
+          } else test = flush_exec(new, aa);
         }
 
         // Argument consumed, skip the check.
-- 
cgit v1.2.3