]> git.saurik.com Git - apple/shell_cmds.git/blobdiff - xargs/xargs.c
shell_cmds-81.1.tar.gz
[apple/shell_cmds.git] / xargs / xargs.c
index 81a0d9af63cd30cc0044c3ce49209b924643c349..a54326152b2965bcc5a8c21efbbd377aa09497bb 100644 (file)
@@ -1,5 +1,3 @@
-/*     $NetBSD: xargs.c,v 1.10 1998/04/14 09:26:33 fair Exp $  */
-
 /*-
  * Copyright (c) 1990, 1993
  *     The Regents of the University of California.  All rights reserved.
 /*-
  * Copyright (c) 1990, 1993
  *     The Regents of the University of California.  All rights reserved.
  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
+ *
+ * $xMach: xargs.c,v 1.6 2002/02/23 05:27:47 tim Exp $
  */
 
  */
 
-#include <sys/cdefs.h>
 #ifndef lint
 #ifndef lint
-__COPYRIGHT("@(#) Copyright (c) 1990, 1993\n\
-       The Regents of the University of California.  All rights reserved.\n");
+static const char copyright[] =
+"@(#) Copyright (c) 1990, 1993\n\
+       The Regents of the University of California.  All rights reserved.\n";
 #endif /* not lint */
 
 #endif /* not lint */
 
-#ifndef lint
 #if 0
 #if 0
+#ifndef lint
 static char sccsid[] = "@(#)xargs.c    8.1 (Berkeley) 6/6/93";
 static char sccsid[] = "@(#)xargs.c    8.1 (Berkeley) 6/6/93";
-#endif
-__RCSID("$NetBSD: xargs.c,v 1.10 1998/04/14 09:26:33 fair Exp $");
 #endif /* not lint */
 #endif /* not lint */
+#endif
+
+#include <sys/cdefs.h>
+__RCSID("$FreeBSD: src/usr.bin/xargs/xargs.c,v 1.41 2002/07/01 03:21:05 tjr Exp $");
 
 #include <sys/types.h>
 #include <sys/wait.h>
 
 #include <sys/types.h>
 #include <sys/wait.h>
+
+#include <err.h>
 #include <errno.h>
 #include <errno.h>
+#include <locale.h>
+#include <paths.h>
+#include <regex.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
-#include <limits.h>
-#include <locale.h>
-#include <signal.h>
-#include <err.h>
+
 #include "pathnames.h"
 
 #include "pathnames.h"
 
-int tflag, zflag, rval;
+#ifdef __APPLE__
+#include <get_compat.h>
+#else
+#define COMPAT_MODE(a,b) (1)
+#endif /* __APPLE__ */
+
+static void    parse_input(int, char *[]);
+static void    prerun(int, char *[]);
+static int     prompt(void);
+static void    run(char **);
+static void    usage(void);
+void           strnsubst(char **, const char *, const char *, size_t);
+
+static char echo[] = _PATH_ECHO;
+static char **av, **bxp, **ep, **exp, **xp;
+static char *argp, *bbp, *ebp, *inpline, *p, *replstr;
+static const char *eofstr;
+static int count, insingle, indouble, pflag, tflag, Rflag, rval, zflag;
+static int cnt, Iflag, jfound, Lflag, wasquoted, xflag;
+static int last_was_newline = 1;
+static int last_was_blank = 0;
 
 
-void   run __P((char **));
-int    main __P((int, char **));
-void   usage __P((void));
+extern char **environ;
 
 int
 
 int
-main(argc, argv)
-       int argc;
-       char **argv;
+main(int argc, char *argv[])
 {
 {
-       int ch;
-       char *p, *bbp, *ebp, **bxp, **exp, **xp;
-       int cnt, indouble, insingle, nargs, nflag, nline, xflag;
-       char **av, *argp;
+       long arg_max;
+       int ch, Jflag, nargs, nflag, nline;
+       size_t linelen;
 
 
-       setlocale(LC_ALL, "");
+       inpline = replstr = NULL;
+       ep = environ;
+       eofstr = "";
+       Jflag = nflag = 0;
 
        /*
         * POSIX.2 limits the exec line length to ARG_MAX - 2K.  Running that
 
        /*
         * POSIX.2 limits the exec line length to ARG_MAX - 2K.  Running that
@@ -94,18 +116,51 @@ main(argc, argv)
         * probably not worthwhile.
         */
        nargs = 5000;
         * probably not worthwhile.
         */
        nargs = 5000;
-       nline = ARG_MAX - 4 * 1024;
-       nflag = xflag = 0;
-       while ((ch = getopt(argc, argv, "0n:s:tx")) != -1)
+       if ((arg_max = sysconf(_SC_ARG_MAX)) == -1)
+               errx(1, "sysconf(_SC_ARG_MAX) failed");
+       nline = arg_max - 4 * 1024;
+       while (*ep != NULL) {
+               /* 1 byte for each '\0' */
+               nline -= strlen(*ep++) + 1 + sizeof(*ep);
+       }
+       while ((ch = getopt(argc, argv, "0E:I:J:L:n:pR:s:tx")) != -1)
                switch(ch) {
                switch(ch) {
-               case '0':
-                       zflag = 1;
+               case 'E':
+                       eofstr = optarg;
+                       break;
+               case 'I':
+                       Jflag = 0;
+                       Iflag = 1;
+                       Lflag = 1;
+                       replstr = optarg;
+                       break;
+               case 'J':
+                       Iflag = 0;
+                       Jflag = 1;
+                       replstr = optarg;
+                       break;
+               case 'L':
+                       Lflag = atoi(optarg);
+                       if (COMPAT_MODE("bin/xargs", "Unix2003")) {
+                               nflag = 0; /* Override */
+                               nargs = 5000;
+                       }
                        break;
                case 'n':
                        nflag = 1;
                        break;
                case 'n':
                        nflag = 1;
+                       if (COMPAT_MODE("bin/xargs", "Unix2003")) {
+                               Lflag = 0; /* Override */
+                       }
                        if ((nargs = atoi(optarg)) <= 0)
                                errx(1, "illegal argument count");
                        break;
                        if ((nargs = atoi(optarg)) <= 0)
                                errx(1, "illegal argument count");
                        break;
+               case 'p':
+                       pflag = 1;
+                       break;
+               case 'R':
+                       if ((Rflag = atoi(optarg)) <= 0)
+                               errx(1, "illegal number of replacements");
+                       break;
                case 's':
                        nline = atoi(optarg);
                        break;
                case 's':
                        nline = atoi(optarg);
                        break;
@@ -115,6 +170,9 @@ main(argc, argv)
                case 'x':
                        xflag = 1;
                        break;
                case 'x':
                        xflag = 1;
                        break;
+               case '0':
+                       zflag = 1;
+                       break;
                case '?':
                default:
                        usage();
                case '?':
                default:
                        usage();
@@ -122,30 +180,45 @@ main(argc, argv)
        argc -= optind;
        argv += optind;
 
        argc -= optind;
        argv += optind;
 
+       if (!Iflag && Rflag)
+               usage();
+       if (Iflag && !Rflag)
+               Rflag = 5;
        if (xflag && !nflag)
                usage();
        if (xflag && !nflag)
                usage();
+       if (Iflag || Lflag)
+               xflag = 1;
+       if (replstr != NULL && *replstr == '\0')
+               errx(1, "replstr may not be empty");
 
        /*
         * Allocate pointers for the utility name, the utility arguments,
         * the maximum arguments to be read from stdin and the trailing
         * NULL.
         */
 
        /*
         * Allocate pointers for the utility name, the utility arguments,
         * the maximum arguments to be read from stdin and the trailing
         * NULL.
         */
-       if (!(av = bxp =
-           malloc((u_int)(1 + argc + nargs + 1) * sizeof(char **))))
-               err(1, "malloc");
+       linelen = 1 + argc + nargs + 1;
+       if ((av = bxp = malloc(linelen * sizeof(char **))) == NULL)
+               errx(1, "malloc failed");
 
        /*
         * Use the user's name for the utility as argv[0], just like the
         * shell.  Echo is the default.  Set up pointers for the user's
         * arguments.
         */
 
        /*
         * Use the user's name for the utility as argv[0], just like the
         * shell.  Echo is the default.  Set up pointers for the user's
         * arguments.
         */
-       if (!*argv)
-               cnt = strlen(*bxp++ = _PATH_ECHO);
+       if (*argv == NULL)
+               cnt = strlen(*bxp++ = echo);
        else {
        else {
-               cnt = 0;
                do {
                do {
+                       if (Jflag && strcmp(*argv, replstr) == 0) {
+                               char **avj;
+                               jfound = 1;
+                               argv++;
+                               for (avj = argv; *avj; avj++)
+                                       cnt += strlen(*avj) + 1;
+                               break;
+                       }
                        cnt += strlen(*bxp++ = *argv) + 1;
                        cnt += strlen(*bxp++ = *argv) + 1;
-               } while (*++argv);
+               } while (*++argv != NULL);
        }
 
        /*
        }
 
        /*
@@ -167,178 +240,353 @@ main(argc, argv)
        if (nline <= 0)
                errx(1, "insufficient space for command");
 
        if (nline <= 0)
                errx(1, "insufficient space for command");
 
-       if (!(bbp = malloc((u_int)nline + 1)))
-               err(1, "malloc");
+       if ((bbp = malloc((size_t)(nline + 1))) == NULL)
+               errx(1, "malloc failed");
        ebp = (argp = p = bbp) + nline - 1;
        ebp = (argp = p = bbp) + nline - 1;
+       for (;;)
+               parse_input(argc, argv);
+}
 
 
-       for (insingle = indouble = 0;;)
-               switch(ch = getchar()) {
-               case EOF:
-                       /* No arguments since last exec. */
-                       if (p == bbp)
-                               exit(rval);
+static void
+parse_input(int argc, char *argv[])
+{
+       int ch, foundeof;
+       char **avj;
+       int last_was_backslashed = 0;
 
 
-                       /* Nothing since end of last argument. */
-                       if (argp == p) {
-                               *xp = NULL;
-                               run(av);
-                               exit(rval);
-                       }
-                       goto arg1;
-               case ' ':
-               case '\t':
-                       /* Quotes escape tabs and spaces. */
-                       if (insingle || indouble || zflag)
-                               goto addch;
+       foundeof = 0;
+
+       switch(ch = getchar()) {
+       case EOF:
+               /* No arguments since last exec. */
+               if (p == bbp)
+                       exit(rval);
+               goto arg1;
+       case ' ':
+               last_was_blank = 1;
+       case '\t':
+               /* Quotes escape tabs and spaces. */
+               if (insingle || indouble || zflag)
+                       goto addch;
+               goto arg2;
+       case '\0':
+               if (zflag)
                        goto arg2;
                        goto arg2;
-               case '\0':
-                       if (zflag)
-                               goto arg2;
+               goto addch;
+       case '\n':
+               if (COMPAT_MODE("bin/xargs", "Unix2003")) {
+                       if (last_was_newline) {
+                               /* don't count empty line */
+                               break;
+                       }
+                       if (!last_was_blank ) {
+                               /* only count if NOT continuation line */
+                               count++;
+                       } 
+               } else {
+                       count++;
+               }
+               last_was_newline = 1;
+               if (zflag)
                        goto addch;
                        goto addch;
-               case '\n':
-                       if (zflag)
-                               goto addch;
-                       /* Empty lines are skipped. */
-                       if (argp == p)
-                               continue;
-
-                       /* Quotes do not escape newlines. */
-arg1:                  if (insingle || indouble)
-                                errx(1, "unterminated quote");
-
-arg2:                  *p = '\0';
+
+               /* Quotes do not escape newlines. */
+arg1:          if (insingle || indouble)
+                       errx(1, "unterminated quote");
+arg2:
+               foundeof = *eofstr != '\0' &&
+                   strcmp(argp, eofstr) == 0;
+
+               /* Do not make empty args unless they are quoted */
+               if ((argp != p || wasquoted) && !foundeof) {
+                       *p++ = '\0';
                        *xp++ = argp;
                        *xp++ = argp;
+                       if (Iflag) {
+                               size_t curlen;
 
 
-                       /*
-                        * If max'd out on args or buffer, or reached EOF,
-                        * run the command.  If xflag and max'd out on buffer
-                        * but not on args, object.
-                        */
-                       if (xp == exp || p == ebp || ch == EOF) {
-                               if (xflag && xp != exp && p == ebp)
-                                       errx(1, "insufficient space for arguments");
-                               *xp = NULL;
-                               run(av);
-                               if (ch == EOF)
-                                       exit(rval);
-                               p = bbp;
-                               xp = bxp;
-                       } else
-                               ++p;
-                       argp = p;
-                       break;
-               case '\'':
-                       if (indouble || zflag)
-                               goto addch;
-                       insingle = !insingle;
-                       break;
-               case '"':
-                       if (insingle || zflag)
-                               goto addch;
-                       indouble = !indouble;
-                       break;
-               case '\\':
-                       if (zflag)
-                               goto addch;
-                       /* Backslash escapes anything, is escaped by quotes. */
-                       if (!insingle && !indouble && (ch = getchar()) == EOF)
-                               errx(1, "backslash at EOF");
-                       /* FALLTHROUGH */
-               default:
-addch:                 if (p < ebp) {
-                               *p++ = ch;
-                               break;
+                               if (inpline == NULL)
+                                       curlen = 0;
+                               else {
+                                       /*
+                                        * If this string is not zero
+                                        * length, append a space for
+                                        * seperation before the next
+                                        * argument.
+                                        */
+                                       if ((curlen = strlen(inpline)))
+                                               strcat(inpline, " ");
+                               }
+                               curlen++;
+                               /*
+                                * Allocate enough to hold what we will
+                                * be holding in a second, and to append
+                                * a space next time through, if we have
+                                * to.
+                                */
+                               inpline = realloc(inpline, curlen + 2 +
+                                   strlen(argp));
+                               if (inpline == NULL)
+                                       errx(1, "realloc failed");
+                               if (curlen == 1)
+                                       strcpy(inpline, argp);
+                               else
+                                       strcat(inpline, argp);
                        }
                        }
+               }
 
 
-                       /* If only one argument, not enough buffer space. */
-                       if (bxp == xp)
-                               errx(1, "insufficient space for argument");
-                       /* Didn't hit argument limit, so if xflag object. */
-                       if (xflag)
+               /*
+                * If max'd out on args or buffer, or reached EOF,
+                * run the command.  If xflag and max'd out on buffer
+                * but not on args, object.  Having reached the limit
+                * of input lines, as specified by -L is the same as
+                * maxing out on arguments.
+                */
+               if (xp == exp || p > ebp || ch == EOF ||
+                   (Lflag <= count && xflag) || foundeof) {
+                       if (xflag && xp != exp && p > ebp)
                                errx(1, "insufficient space for arguments");
                                errx(1, "insufficient space for arguments");
-
-                       *xp = NULL;
-                       run(av);
+                       if (jfound) {
+                               for (avj = argv; *avj; avj++)
+                                       *xp++ = *avj;
+                       }
+                       prerun(argc, av);
+                       if (ch == EOF || foundeof)
+                               exit(rval);
+                       p = bbp;
                        xp = bxp;
                        xp = bxp;
-                       cnt = ebp - argp;
-                       memmove(bbp, argp, cnt);
-                       p = (argp = bbp) + cnt;
+                       count = 0;
+               }
+               argp = p;
+               wasquoted = 0;
+               break;
+       case '\'':
+               if (indouble || zflag)
+                       goto addch;
+               insingle = !insingle;
+               wasquoted = 1;
+               break;
+       case '"':
+               if (insingle || zflag)
+                       goto addch;
+               indouble = !indouble;
+               wasquoted = 1;
+               break;
+       case '\\':
+               last_was_backslashed = 1;
+               if (zflag)
+                       goto addch;
+               /* Backslash escapes anything, is escaped by quotes. */
+               if (!insingle && !indouble && (ch = getchar()) == EOF)
+                       errx(1, "backslash at EOF");
+               /* FALLTHROUGH */
+       default:
+addch:         if (p < ebp) {
                        *p++ = ch;
                        break;
                }
                        *p++ = ch;
                        break;
                }
-       /* NOTREACHED */
+
+               /* If only one argument, not enough buffer space. */
+               if (bxp == xp)
+                       errx(1, "insufficient space for argument");
+               /* Didn't hit argument limit, so if xflag object. */
+               if (xflag)
+                       errx(1, "insufficient space for arguments");
+
+               if (jfound) {
+                       for (avj = argv; *avj; avj++)
+                               *xp++ = *avj;
+               }
+               prerun(argc, av);
+               xp = bxp;
+               cnt = ebp - argp;
+               memcpy(bbp, argp, (size_t)cnt);
+               p = (argp = bbp) + cnt;
+               *p++ = ch;
+               break;
+       }
+       if (ch != ' ')
+               last_was_blank = 0;
+       if (ch != '\n' || last_was_backslashed)
+               last_was_newline = 0;
+       return;
 }
 
 }
 
-void
-run(argv)
-       char **argv;
+/*
+ * Do things necessary before run()'ing, such as -I substitution,
+ * and then call run().
+ */
+static void
+prerun(int argc, char *argv[])
 {
 {
-       volatile int noinvoke;
-       char **p;
+       char **tmp, **tmp2, **avj;
+       int repls;
+
+       repls = Rflag;
+
+       if (argc == 0 || repls == 0) {
+               *xp = NULL;
+               run(argv);
+               return;
+       }
+
+       avj = argv;
+
+       /*
+        * Allocate memory to hold the argument list, and
+        * a NULL at the tail.
+        */
+       tmp = malloc((argc + 1) * sizeof(char**));
+       if (tmp == NULL)
+               errx(1, "malloc failed");
+       tmp2 = tmp;
+
+       /*
+        * Save the first argument and iterate over it, we
+        * cannot do strnsubst() to it.
+        */
+       if ((*tmp++ = strdup(*avj++)) == NULL)
+               errx(1, "strdup failed");
+
+       /*
+        * For each argument to utility, if we have not used up
+        * the number of replacements we are allowed to do, and
+        * if the argument contains at least one occurance of
+        * replstr, call strnsubst(), else just save the string.
+        * Iterations over elements of avj and tmp are done
+        * where appropriate.
+        */
+       while (--argc) {
+               *tmp = *avj++;
+               if (repls && strstr(*tmp, replstr) != NULL) {
+                       strnsubst(tmp++, replstr, inpline, (size_t)255);
+                       repls--;
+               } else {
+                       if ((*tmp = strdup(*tmp)) == NULL)
+                               errx(1, "strdup failed");
+                       tmp++;
+               }
+       }
+
+       /*
+        * Run it.
+        */
+       *tmp = NULL;
+       run(tmp2);
+
+       /*
+        * Walk from the tail to the head, free along the way.
+        */
+       for (; tmp2 != tmp; tmp--)
+               free(*tmp);
+       /*
+        * Now free the list itself.
+        */
+       free(tmp2);
+
+       /*
+        * Free the input line buffer, if we have one.
+        */
+       if (inpline != NULL) {
+               free(inpline);
+               inpline = NULL;
+       }
+}
+
+static void
+run(char **argv)
+{
+       volatile int childerr;
+       char **avec;
        pid_t pid;
        int status;
 
        pid_t pid;
        int status;
 
-       if (tflag) {
+       /*
+        * If the user wants to be notified of each command before it is
+        * executed, notify them.  If they want the notification to be
+        * followed by a prompt, then prompt them.
+        */
+       if (tflag || pflag) {
                (void)fprintf(stderr, "%s", *argv);
                (void)fprintf(stderr, "%s", *argv);
-               for (p = argv + 1; *p; ++p)
-                       (void)fprintf(stderr, " %s", *p);
+               for (avec = argv + 1; *avec != NULL; ++avec)
+                       (void)fprintf(stderr, " %s", *avec);
+               /*
+                * If the user has asked to be prompted, do so.
+                */
+               if (pflag)
+                       /*
+                        * If they asked not to exec, return without execution
+                        * but if they asked to, go to the execution.  If we
+                        * could not open their tty, break the switch and drop
+                        * back to -t behaviour.
+                        */
+                       switch (prompt()) {
+                       case 0:
+                               return;
+                       case 1:
+                               goto exec;
+                       case 2:
+                               break;
+                       }
                (void)fprintf(stderr, "\n");
                (void)fflush(stderr);
        }
                (void)fprintf(stderr, "\n");
                (void)fflush(stderr);
        }
-       noinvoke = 0;
+exec:
+       childerr = 0;
        switch(pid = vfork()) {
        case -1:
                err(1, "vfork");
        case 0:
                execvp(argv[0], argv);
        switch(pid = vfork()) {
        case -1:
                err(1, "vfork");
        case 0:
                execvp(argv[0], argv);
-               noinvoke = (errno == ENOENT) ? 127 : 126;
-               warn("%s", argv[0]);;
+               childerr = errno;
                _exit(1);
        }
        pid = waitpid(pid, &status, 0);
        if (pid == -1)
                err(1, "waitpid");
                _exit(1);
        }
        pid = waitpid(pid, &status, 0);
        if (pid == -1)
                err(1, "waitpid");
+       /* If we couldn't invoke the utility, exit. */
+       if (childerr != 0)
+               err(childerr == ENOENT ? 127 : 126, "%s", *argv);
+       /* If utility signaled or exited with a value of 255, exit 1-125. */
+       if (WIFSIGNALED(status) || WEXITSTATUS(status) == 255)
+               exit(1);
+       if (WEXITSTATUS(status))
+               rval = 1;
+}
 
 
-       /*
-        * If we couldn't invoke the utility or the utility didn't exit
-        * properly, quit with 127 or 126 respectively.
-        */
-       if (noinvoke)
-               exit(noinvoke);
+/*
+ * Prompt the user about running a command.
+ */
+static int
+prompt(void)
+{
+       regex_t cre;
+       size_t rsize;
+       int match;
+       char *response;
+       FILE *ttyfp;
 
 
-       /*
-        * According to POSIX, we have to exit if the utility exits with
-        * a 255 status, or is interrupted by a signal.   xargs is allowed
-        * to return any exit status between 1 and 125 in these cases, but
-        * we'll use 124 and 125, the same values used by GNU xargs.
-        */
-       if (WIFEXITED(status)) {
-               if (WEXITSTATUS (status) == 255) {
-                       warnx ("%s exited with status 255", argv[0]);
-                       exit(124);
-               } else if (WEXITSTATUS (status) != 0) {
-                       rval = 123;
-               }
-       } else if (WIFSIGNALED (status)) {
-#ifdef __APPLE__
-               if (WTERMSIG(status) < NSIG) {
-#else
-               if (WTERMSIG(status) < _NSIG) {
-#endif
-                       warnx("%s terminated by SIG%s", argv[0],
-                               sys_signame[WTERMSIG(status)]);
-               } else {
-                       warnx("%s terminated by signal %d", argv[0],
-                               WTERMSIG(status));
-               }
-               exit(125);
+       if ((ttyfp = fopen(_PATH_TTY, "r")) == NULL)
+               return (2);     /* Indicate that the TTY failed to open. */
+       (void)fprintf(stderr, "?...");
+       (void)fflush(stderr);
+       if ((response = fgetln(ttyfp, &rsize)) == NULL ||
+           regcomp(&cre,
+               "^[yY]",
+               REG_BASIC) != 0) {
+               (void)fclose(ttyfp);
+               return (0);
        }
        }
+       match = regexec(&cre, response, 0, NULL, 0);
+       (void)fclose(ttyfp);
+       regfree(&cre);
+       return (match == 0);
 }
 
 }
 
-void
-usage()
+static void
+usage(void)
 {
 {
-       (void)fprintf(stderr,
-"usage: xargs [-0t] [-n number [-x]] [-s size] [utility [argument ...]]\n");
+       fprintf(stderr,
+"usage: xargs [-0pt] [-E eofstr] [-I replstr [-R replacements]] [-J replstr]\n"
+"             [-L number] [-n number [-x] [-s size] [utility [argument ...]]\n");
        exit(1);
 }
        exit(1);
 }