From 80c6b26efd4eaf091c44c0c4b5dbaa778cfdfa8e Mon Sep 17 00:00:00 2001 From: Rob Landley 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/posix/find.c') 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