On Sun, 26 Jun 2011 21:12:37 +0200, Pascal Stumpf wrote:
> This is my first try at getting support for terminating '+' in find(1)'s
> -exec statement to work, as required by POSIX.  Code shamelessly
> guttenberged from NetBSD, with some minor modifications.
> 
> 
> 
> Index: extern.h
> ===================================================================
> RCS file: /cvs/src/usr.bin/find/extern.h,v
> retrieving revision 1.16
> diff -u -r1.16 extern.h
> --- extern.h  21 Apr 2011 01:14:21 -0000      1.16
> +++ extern.h  26 Jun 2011 19:07:30 -0000
> @@ -37,10 +37,12 @@
>  PLAN *find_create(char ***);
>  int   find_execute(PLAN *, char **);
>  PLAN *find_formplan(char **);
> +int  find_traverse(PLAN *, int (*)(PLAN *, void *), void *);
>  PLAN *not_squish(PLAN *);
>  OPTION       *option(char *);
>  PLAN *or_squish(PLAN *);
>  PLAN *paren_squish(PLAN *);
> +int  plan_cleanup(PLAN *, void *);
>  struct stat;
>  void  printlong(char *, char *, struct stat *);
>  int   queryuser(char **);
> Index: find.1
> ===================================================================
> RCS file: /cvs/src/usr.bin/find/find.1,v
> retrieving revision 1.82
> diff -u -r1.82 find.1
> --- find.1    13 Feb 2011 12:35:02 -0000      1.82
> +++ find.1    26 Jun 2011 19:07:30 -0000
> @@ -189,28 +189,48 @@
>  .Op argument ...
>  .No ;
>  .Xc
> -True if the program named
> -.Ar utility
> -returns a zero value as its exit status.
> +.It Xo
> +.Ic -exec Ar utility
> +.Op argument ...
> +.No {}
> ++
> +.Xc
> +Execute the specified
> +.Ar utility .
>  Optional arguments may be passed to the utility.
>  The expression must be terminated by a semicolon
> -.Pq Ql \&; .
> +.Pq Ql \&;
> +or a plus sign
> +.Pq Ql \&+ .
> +.Pp
> +If terminated by a semicolon, the
> +.Ar utility
> +is executed once per path.
>  If the string
>  .Qq {}
>  appears anywhere in the utility name or the
>  arguments it is replaced by the pathname of the current file.
> -.Ar utility
> -will be executed from the directory from which
> -.Nm
> -was executed.
>  .Pp
> -Since
> +If terminated by a plus sign
> +.Pq Dq \&+ ,
> +the pathnames for which the
> +primary is evaluated are aggregated into sets, and
>  .Ar utility
> -is executed every time a match is made,
> -it is often more efficient to pipe the output of
> -.Nm
> -to
> +will be invoked once per set, similar to
>  .Xr xargs 1 .
> +If any invocation exits with non-zero exit status, then
> +.Nm
> +will eventually do so as well, but this does not cause
> +.Nm
> +to exit early.
> +The string
> +.Dq {}
> +must appear, and must appear last.
> +Each set is limited to no more than 5,000 pathnames,
> +and is also limited such that the invocation of
> +.Ar utility
> +does not exceed
> +.Dv ARG_MAX .
>  .It Xo
>  .Ic -execdir Ar utility
>  .Op argument ...
> Index: find.c
> ===================================================================
> RCS file: /cvs/src/usr.bin/find/find.c,v
> retrieving revision 1.15
> diff -u -r1.15 find.c
> --- find.c    21 Apr 2011 01:14:21 -0000      1.15
> +++ find.c    26 Jun 2011 19:07:30 -0000
> @@ -148,7 +148,7 @@
>      char **paths)            /* array of pathnames to traverse */
>  {
>       sigset_t fullset, oset;
> -     int rval;
> +     int r, rval;
>       PLAN *p;
>  
>       rval = 0;
> @@ -201,5 +201,45 @@
>                   ;
>       }
>       (void)fts_close(tree);
> +
> +     /*
> +      * Cleanup any plans with leftover state.
> +      * Keep the last non-zero return value.
> +      */
> +     if ((r = find_traverse(plan, plan_cleanup, NULL)) != 0)
> +             rval = r;
>       return (rval);
> +}
> +
> +/*
> + * find_traverse --
> + *   traverse the plan tree and execute func() on all plans.  This
> + *   does not evaluate each plan's eval() function; it is intended
> + *   for operations that must run on all plans, such as state
> + *   cleanup.
> + *
> + *   If any func() returns non-zero, then so will find_traverse().
> + */
> +int
> +find_traverse(PLAN *plan, int (*func)(PLAN *, void *), void *arg)
> +{
> +     PLAN *p;
> +     int r, rval;
> +
> +     rval = 0;
> +     for (p = plan; p; p = p->next) {
> +             if ((r = func(p, arg)) != 0)
> +                     rval = r;
> +             if (p->type == N_EXPR || p->type == N_OR) {
> +                     if (p->p_data[0])
> +                             if ((r = find_traverse(p->p_data[0],
> +                                         func, arg)) != 0)
> +                                     rval = r;
> +                     if (p->p_data[1])
> +                             if ((r = find_traverse(p->p_data[1],
> +                                         func, arg)) != 0)
> +                                     rval = r;
> +             }
> +     }
> +     return rval;
>  }
> Index: find.h
> ===================================================================
> RCS file: /cvs/src/usr.bin/find/find.h,v
> retrieving revision 1.14
> diff -u -r1.14 find.h
> --- find.h    15 Sep 2004 18:43:25 -0000      1.14
> +++ find.h    26 Jun 2011 19:07:30 -0000
> @@ -54,6 +54,7 @@
>  #define      F_LESSTHAN      2
>  #define      F_GREATER       3
>  #define      F_NEEDOK        1                       /* exec ok */
> +#define F_PLUSSET    2                       /* -exec ... {} + */
>  #define      F_MTFLAG        1                       /* fstype */
>  #define      F_MTTYPE        2
>  #define      F_ATLEAST       1                       /* perm */
> @@ -77,6 +78,13 @@
>                       char **_e_argv;         /* argv array */
>                       char **_e_orig;         /* original strings */
>                       int *_e_len;            /* allocated length */
> +                     char **_ep_bxp;         /* ptr to 1st addt'l arg */
> +                     char *_ep_p;            /* current buffer pointer */
> +                     char *_ep_bbp;          /* begin buffer pointer */
> +                     char *_ep_ebp;          /* end buffer pointer */
> +                     int _ep_maxargs;        /* max #args */
> +                     int _ep_narg;           /* # addt'l args */
> +                     int _ep_rval;           /* return value */
>               } ex;
>               char *_a_data[2];               /* array of char pointers */
>               char *_c_data;                  /* char pointer */
> @@ -104,6 +112,15 @@
>  #define      e_argv          p_un.ex._e_argv
>  #define      e_orig          p_un.ex._e_orig
>  #define      e_len           p_un.ex._e_len
> +#define      ep_p            p_un.ex._ep_p
> +#define      ep_bbp          p_un.ex._ep_bbp
> +#define      ep_ebp          p_un.ex._ep_ebp
> +#define      ep_bxp          p_un.ex._ep_bxp
> +#define      ep_cnt          p_un.ex._ep_cnt
> +#define      ep_maxargs      p_un.ex._ep_maxargs
> +#define      ep_nline        p_un.ex._ep_nline
> +#define      ep_narg         p_un.ex._ep_narg
> +#define      ep_rval         p_un.ex._ep_rval
>  
>  typedef struct _option {
>       char *name;                             /* option name */
> Index: function.c
> ===================================================================
> RCS file: /cvs/src/usr.bin/find/function.c,v
> retrieving revision 1.36
> diff -u -r1.36 function.c
> --- function.c        1 Dec 2010 01:20:29 -0000       1.36
> +++ function.c        26 Jun 2011 19:07:30 -0000
> @@ -46,6 +46,7 @@
>  #include <fts.h>
>  #include <grp.h>
>  #include <libgen.h>
> +#include <limits.h>
>  #include <pwd.h>
>  #include <stdio.h>
>  #include <stdlib.h>
> @@ -71,6 +72,7 @@
>  
>  static PLAN *palloc(enum ntype, int (*)(PLAN *, FTSENT *));
>  static long find_parsenum(PLAN *plan, char *option, char *vp, char *endch);
> +static void run_f_exec(PLAN *plan);
>  static PLAN *palloc(enum ntype t, int (*f)(PLAN *, FTSENT *));
>  
>  int  f_amin(PLAN *, FTSENT *);
> @@ -339,38 +341,108 @@
>  
>  /*
>   * [-exec | -ok] utility [arg ... ] ; functions --
> + * [-exec | -ok] utility [arg ... ] {} + functions --
>   *
> - *   True if the executed utility returns a zero value as exit status.
> - *   The end of the primary expression is delimited by a semicolon.  If
> - *   "{}" occurs anywhere, it gets replaced by the current pathname.
> - *   The current directory for the execution of utility is the same as
> - *   the current directory when the find utility was started.
> + *   If the end of the primary expression is delimited by a
> + *   semicolon: true if the executed utility returns a zero value
> + *   as exit status.  If "{}" occurs anywhere, it gets replaced by
> + *   the current pathname.
> + *
> + *   If the end of the primary expression is delimited by a plus
> + *   sign: always true. Pathnames for which the primary is
> + *   evaluated shall be aggregated into sets. The utility will be
> + *   executed once per set, with "{}" replaced by the entire set of
> + *   pathnames (as if xargs). "{}" must appear last.
>   *
> - *   The primary -ok is different in that it requests affirmation of the
> - *   user before executing the utility.
> + *   The current directory for the execution of utility is the same
> + *   as the current directory when the find utility was started.
> + *
> + *   The primary -ok is different in that it requests affirmation
> + *   of the user before executing the utility.
>   */
>  int
>  f_exec(PLAN *plan, FTSENT *entry)
>  {
> -     int cnt;
> +     int cnt, l;
>       pid_t pid;
>       int status;
>  
> -     for (cnt = 0; plan->e_argv[cnt]; ++cnt)
> -             if (plan->e_len[cnt])
> -                     brace_subst(plan->e_orig[cnt], &plan->e_argv[cnt],
> -                         entry->fts_path, plan->e_len[cnt]);
> +     if (plan->flags & F_PLUSSET) {
> +             /*
> +              * Confirm sufficient buffer space, then copy the path
> +              * to the buffer.
> +              */
> +             l = strlen(entry->fts_path);
> +             if (plan->ep_p + l < plan->ep_ebp) {
> +                     plan->ep_bxp[plan->ep_narg++] = plan->ep_p;
> +                     strlcpy(plan->ep_p, entry->fts_path, l + 1);
> +                     plan->ep_p += l + 1;
> +
> +                     if (plan->ep_narg == plan->ep_maxargs)
> +                             run_f_exec(plan);
> +             } else {
> +                     /*
> +                      * Without sufficient space to copy in the next
> +                      * argument, run the command to empty out the
> +                      * buffer before re-attepting the copy.
> +                      */
> +                     run_f_exec(plan);
> +                     if (plan->ep_p + l < plan->ep_ebp) {
> +                             plan->ep_bxp[plan->ep_narg++] = plan->ep_p;
> +                             strlcpy(plan->ep_p, entry->fts_path, l + 1);
> +                             plan->ep_p += l + 1;
> +                     } else
> +                             errx(1, "insufficient space for argument");
> +             }
> +             return (1);
> +     } else {
> +             for (cnt = 0; plan->e_argv[cnt]; ++cnt)
> +                     if (plan->e_len[cnt])
> +                             brace_subst(plan->e_orig[cnt],
> +                                 &plan->e_argv[cnt],
> +                                 entry->fts_path,
> +                                 plan->e_len[cnt]);
> +             if (plan->flags & F_NEEDOK && !queryuser(plan->e_argv))
> +                     return (0);
>  
> -     if (plan->flags == F_NEEDOK && !queryuser(plan->e_argv))
> -             return (0);
> +             /* don't mix output of command with find output */
> +             fflush(stdout);
> +             fflush(stderr);
> +
> +             switch (pid = vfork()) {
> +             case -1:
> +                     err(1, "fork");
> +                     /* NOTREACHED */
> +             case 0:
> +                     if (fchdir(dotfd)) {
> +                             warn("chdir");
> +                             _exit(1);
> +                     }
> +                     execvp(plan->e_argv[0], plan->e_argv);
> +                     warn("%s", plan->e_argv[0]);
> +                     _exit(1);
> +             }
> +             pid = waitpid(pid, &status, 0);
> +             return (pid != -1 && WIFEXITED(status) && !WEXITSTATUS(status))
> ;
> +     }
> +}
>  
> -     /* don't mix output of command with find output */
> -     fflush(stdout);
> -     fflush(stderr);
> +static void
> +run_f_exec(PLAN *plan)
> +{
> +     pid_t pid;
> +     int rval, status;
> +
> +     /* Ensure arg list is null terminated. */
> +     plan->ep_bxp[plan->ep_narg] = NULL;
> +
> +     /* Don't mix output of command with find output. */
> +     fflush(stdout);
> +     fflush(stderr);
>  
>       switch (pid = vfork()) {
>       case -1:
> -             err(1, "fork");
> +             err(1, "vfork");
>               /* NOTREACHED */
>       case 0:
>               if (fchdir(dotfd)) {
> @@ -381,8 +453,26 @@
>               warn("%s", plan->e_argv[0]);
>               _exit(1);
>       }
> +
> +     /* Clear out the argument list. */
> +     plan->ep_narg = 0;
> +     plan->ep_bxp[plan->ep_narg] = NULL;
> +     /* As well as the argument buffer. */
> +     plan->ep_p = plan->ep_bbp;
> +     *plan->ep_p = '\0';
> +
>       pid = waitpid(pid, &status, 0);
> -     return (pid != -1 && WIFEXITED(status) && !WEXITSTATUS(status));
> +     if (WIFEXITED(status))
> +             rval = WEXITSTATUS(status);
> +     else
> +             rval = -1;
> +
> +     /*
> +      * If we have a non-zero exit status, preserve it so find(1) can
> +      * later exit with it.
> +      */
> +     if (rval)
> +             plan->ep_rval = rval;
>  }
>   
>  /*
> @@ -391,12 +481,16 @@
>   *   on the command line, one with (possibly duplicated) pointers to the
>   *   argv array, and one with integer values that are lengths of the
>   *   strings, but also flags meaning that the string has to be massaged.
> + *
> + *   If -exec ... {} +, use only the first array, but make it large
> + *   enough to hold 5000 args (cf. src/usr.bin/xargs/xargs.c for a
> + *   discussion), and then allocate ARG_MAX - 4K of space for args.
>   */
>  PLAN *
>  c_exec(char *unused, char ***argvp, int isok)
>  {
>       PLAN *new;                      /* node returned */
> -     int cnt;
> +     int cnt, brace, lastbrace;
>       char **argv, **ap, *p;
>  
>       /* make sure the current directory is readable */
> @@ -407,36 +501,93 @@
>      
>       new = palloc(N_EXEC, f_exec);
>       if (isok)
> -             new->flags = F_NEEDOK;
> +             new->flags |= F_NEEDOK;
>  
> -     for (ap = argv = *argvp;; ++ap) {
> +     /*
> +      * Terminate if we encounter an arg exacty equal to ";", or an
> +      * arg exacty equal to "+" following an arg exacty equal to
> +      * "{}".
> +      */
> +     for (ap = argv = *argvp, brace = 0;; ++ap) {
>               if (!*ap)
> -                     errx(1,
> -                         "%s: no terminating \";\"", isok ? "-ok" : "-exec")
> ;
> -             if (**ap == ';')
> +                     errx(1, "%s: no terminating \";\" or \"+\"",
> +                         isok ? "-ok" : "-exec");
> +             lastbrace = brace;
> +             brace = 0;
> +             if (strcmp(*ap, "{}") == 0)
> +                     brace = 1;
> +             if (strcmp(*ap, ";") == 0)
>                       break;
> +             if (strcmp(*ap, "+") == 0 && lastbrace) {
> +                     new->flags |= F_PLUSSET;
> +                     break;
> +             }
>       }
>  
> -     cnt = ap - *argvp + 1;
> -     new->e_argv = (char **)emalloc((u_int)cnt * sizeof(char *));
> -     new->e_orig = (char **)emalloc((u_int)cnt * sizeof(char *));
> -     new->e_len = (int *)emalloc((u_int)cnt * sizeof(int));
>  
> -     for (argv = *argvp, cnt = 0; argv < ap; ++argv, ++cnt) {
> -             new->e_orig[cnt] = *argv;
> -             for (p = *argv; *p; ++p)
> -                     if (p[0] == '{' && p[1] == '}') {
> -                             new->e_argv[cnt] = emalloc((u_int)MAXPATHLEN);
> -                             new->e_len[cnt] = MAXPATHLEN;
> -                             break;
> +     /*
> +      * POSIX says -ok ... {} + "need not be supported," and it does
> +      * not make much sense anyway.
> +      */
> +     if (new->flags & F_NEEDOK && new->flags & F_PLUSSET)
> +             errx(1, "-ok: terminating \"+\" not permitted.");
> +
> +     if (new->flags & F_PLUSSET) {
> +             u_int c, bufsize;
> +
> +             cnt = ap - *argvp - 1;                  /* units are words */
> +             new->ep_maxargs = 5000;
> +             new->e_argv = (char **)emalloc((u_int)(cnt + new->ep_maxargs)
> +                                             * sizeof(char **));
> +
> +             /* We start stuffing arguments after the user's last one. */
> +             new->ep_bxp = &new->e_argv[cnt];
> +             new->ep_narg = 0;
> +
> +             /*
> +              * Count up the space of the user's arguments, and
> +              * subtract that from what we allocate.
> +              */
> +             for (argv = *argvp, c = 0, cnt = 0;
> +                  argv < ap;
> +                  ++argv, ++cnt) {
> +                     c += strlen(*argv) + 1;
> +                     new->e_argv[cnt] = *argv;
> +             }
> +             bufsize = ARG_MAX - 4 * 1024 - c;
> +
> +
> +             /*
> +              * Allocate, and then initialize current, base, and
> +              * end pointers.
> +              */
> +             new->ep_p = new->ep_bbp = malloc(bufsize + 1);
> +             new->ep_ebp = new->ep_bbp + bufsize - 1;
> +             new->ep_rval = 0;
> +     } else { /* !F_PLUSSET */
> +             cnt = ap - *argvp + 1;
> +             new->e_argv = (char **)emalloc((u_int)cnt * sizeof(char *));
> +             new->e_orig = (char **)emalloc((u_int)cnt * sizeof(char *));
> +             new->e_len = (int *)emalloc((u_int)cnt * sizeof(int));
> +
> +             for (argv = *argvp, cnt = 0; argv < ap; ++argv, ++cnt) {
> +                     new->e_orig[cnt] = *argv;
> +                     for (p = *argv; *p; ++p)
> +                             if (p[0] == '{' && p[1] == '}') {
> +                                     new->e_argv[cnt] =
> +                                             emalloc((u_int)MAXPATHLEN);
> +                                     new->e_len[cnt] = MAXPATHLEN;
> +                                     break;
> +                             }
> +                     if (!*p) {
> +                             new->e_argv[cnt] = *argv;
> +                             new->e_len[cnt] = 0;
>                       }
> -             if (!*p) {
> -                     new->e_argv[cnt] = *argv;
> -                     new->e_len[cnt] = 0;
>               }
> -     }
> -     new->e_argv[cnt] = new->e_orig[cnt] = NULL;
> +             new->e_orig[cnt] = NULL;
> +     }
>  
> +     new->e_argv[cnt] = NULL;
>       *argvp = argv + 1;
>       return (new);
>  }
> @@ -1440,6 +1591,27 @@
>  {
>       return (palloc(N_OR, f_or));
>  }
> +
> +
> +/*
> + * plan_cleanup --
> + *   Check and see if the specified plan has any residual state,
> + *   and if so, clean it up as appropriate.
> + *
> + *   At the moment, only N_EXEC has state. Two kinds: 1)
> + *   lists of files to feed to subprocesses 2) State on exit
> + *   statusses of past subprocesses.
> + */
> +/* ARGSUSED1 */
> +int
> +plan_cleanup(PLAN *plan, void *arg)
> +{
> +     if (plan->type==N_EXEC && plan->ep_narg)
> +             run_f_exec(plan);
> +
> +     return plan->ep_rval;           /* Passed save exit-status up chain */
> +}
> +
>  
>  static PLAN *
>  palloc(enum ntype t, int (*f)(PLAN *, FTSENT *))
> 
> 

ping?

Reply via email to