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?