BSD 4_3_Net_2 release
[unix-history] / usr / src / usr.bin / xargs / xargs.c
index a51cbcc..e4d3a23 100644 (file)
@@ -5,19 +5,33 @@
  * This code is derived from software contributed to Berkeley by
  * John B. Roll Jr.
  *
  * This code is derived from software contributed to Berkeley by
  * John B. Roll Jr.
  *
- * Redistribution and use in source and binary forms are permitted provided
- * that: (1) source distributions retain this entire copyright notice and
- * comment, and (2) distributions including binaries display the following
- * acknowledgement:  ``This product includes software developed by the
- * University of California, Berkeley and its contributors'' in the
- * documentation or other materials provided with the distribution and in
- * all advertising materials mentioning features or use of this software.
- * Neither the name of the University nor the names of its contributors may
- * be used to endorse or promote products derived from this software without
- * specific prior written permission.
- * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
- * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * 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.
  */
 
 #ifndef lint
  */
 
 #ifndef lint
@@ -27,54 +41,69 @@ char copyright[] =
 #endif /* not lint */
 
 #ifndef lint
 #endif /* not lint */
 
 #ifndef lint
-static char sccsid[] = "@(#)xargs.c    5.4 (Berkeley) 6/24/90";
+static char sccsid[] = "@(#)xargs.c    5.11 (Berkeley) 6/19/91";
 #endif /* not lint */
 
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <errno.h>
 #include <stdio.h>
 #endif /* not lint */
 
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <errno.h>
 #include <stdio.h>
+#include <stdlib.h>
 #include <string.h>
 #include <string.h>
+#include <unistd.h>
 #include <limits.h>
 #include "pathnames.h"
 
 #include <limits.h>
 #include "pathnames.h"
 
-#define        DEF_ARGC        255
-
-int tflag;
+int fflag, tflag;
+void err __P((const char *, ...));
+void run(), usage();
 
 main(argc, argv)
        int argc;
        char **argv;
 {
 
 main(argc, argv)
        int argc;
        char **argv;
 {
-       extern int errno, optind;
+       extern int optind;
        extern char *optarg;
        register int ch;
        extern char *optarg;
        register int ch;
-       register char *p, *bp, *endbp, **bxp, **endxp, **xp;
-       int cnt, indouble, insingle, nargs, nline;
-       char *mark, *prog, **xargs, *malloc();
+       register char *p, *bbp, *ebp, **bxp, **exp, **xp;
+       int cnt, indouble, insingle, nargs, nflag, nline, xflag;
+       char **av, *argp;
 
 
-       nargs = DEF_ARGC;
-       nline = _BSD_LINE_MAX;
-
-       while ((ch = getopt(argc, argv, "n:s:t")) != EOF)
+       /*
+        * POSIX.2 limits the exec line length to ARG_MAX - 2K.  Running that
+        * caused some E2BIG errors, so it was changed to ARG_MAX - 4K.  Given
+        * that the smallest argument is 2 bytes in length, this means that
+        * the number of arguments is limited to:
+        *
+        *       (ARG_MAX - 4K - LENGTH(utility + arguments)) / 2.
+        *
+        * We arbitrarily limit the number of arguments to 5000.  This is
+        * allowed by POSIX.2 as long as the resulting minimum exec line is
+        * at least LINE_MAX.  Realloc'ing as necessary is possible, but
+        * probably not worthwhile.
+        */
+       nargs = 5000;
+       nline = ARG_MAX - 4 * 1024;
+       nflag = xflag = 0;
+       while ((ch = getopt(argc, argv, "fn:s:tx")) != EOF)
                switch(ch) {
                switch(ch) {
+               case 'f':
+                       fflag = 1;
+                       break;
                case 'n':
                case 'n':
-                       if ((nargs = atoi(optarg)) <= 0) {
-                               (void)fprintf(stderr,
-                                   "xargs: bad argument count.\n");
-                               exit(1);
-                       }
+                       nflag = 1;
+                       if ((nargs = atoi(optarg)) <= 0)
+                               err("illegal argument count");
                        break;
                case 's':
                        break;
                case 's':
-                       if ((nline = atoi(optarg)) <= 0) {
-                               (void)fprintf(stderr,
-                                   "xargs: bad command length.\n");
-                               exit(1);
-                       }
+                       nline = atoi(optarg);
                        break;
                case 't':
                        tflag = 1;
                        break;
                        break;
                case 't':
                        tflag = 1;
                        break;
+               case 'x':
+                       xflag = 1;
+                       break;
                case '?':
                default:
                        usage();
                case '?':
                default:
                        usage();
@@ -82,66 +111,104 @@ main(argc, argv)
        argc -= optind;
        argv += optind;
 
        argc -= optind;
        argv += optind;
 
-       /* room for the command, leftover arguments and trailing NULL */
-       if (!(xargs =
-           (char **)malloc((u_int)(nargs + argc + 2) * sizeof(char **))))
-               enomem();
+       if (xflag && !nflag)
+               usage();
 
 
-       if (!(bp = malloc((u_int)nline + 1)))
-               enomem();
+       /*
+        * 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("%s", strerror(errno));
 
 
-       xp = xargs + 1;
+       /*
+        * 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)
        if (!*argv)
-               prog = _PATH_ECHO;
+               cnt = strlen(*bxp++ = _PATH_ECHO);
        else {
        else {
-               prog = *argv;
-               while (*++argv)
-                       *xp++ = *argv;
+               cnt = 0;
+               do {
+                       cnt += strlen(*bxp++ = *argv) + 1;
+               } while (*++argv);
        }
 
        }
 
-       if (xargs[0] = rindex(prog, '/'))
-               ++xargs[0];
-       else
-               xargs[0] = prog;
+       /*
+        * Set up begin/end/traversing pointers into the array.  The -n
+        * count doesn't include the trailing NULL pointer, so the malloc
+        * added in an extra slot.
+        */
+       exp = (xp = bxp) + nargs;
+
+       /*
+        * Allocate buffer space for the arguments read from stdin and the
+        * trailing NULL.  Buffer space is defined as the default or specified
+        * space, minus the length of the utility name and arguments.  Set up
+        * begin/end/traversing pointers into the array.  The -s count does
+        * include the trailing NULL, so the malloc didn't add in an extra
+        * slot.
+        */
+       nline -= cnt;
+       if (nline <= 0)
+               err("insufficient space for command");
 
 
-       /* set up the pointers into the buffer and the arguments */
-       *(endxp = (bxp = xp) + nargs) = NULL;
-       endbp = (mark = p = bp) + nline;
+       if (!(bbp = malloc((u_int)nline + 1)))
+               err("%s", strerror(errno));
+       ebp = (argp = p = bbp) + nline - 1;
 
 
-       insingle = indouble = 0;
-       for (;;)
+       for (insingle = indouble = 0;;)
                switch(ch = getchar()) {
                case EOF:
                switch(ch = getchar()) {
                case EOF:
-                       if (p == bp)            /* nothing to display */
+                       /* No arguments since last exec. */
+                       if (p == bbp)
                                exit(0);
                                exit(0);
-                       if (mark == p) {        /* nothing since last arg end */
-                               run(prog, xargs);
+
+                       /* Nothing since end of last argument. */
+                       if (argp == p) {
+                               *xp = NULL;
+                               run(av);
                                exit(0);
                        }
                                exit(0);
                        }
-                       goto addarg;
+                       goto arg1;
                case ' ':
                case '\t':
                case ' ':
                case '\t':
+                       /* Quotes escape tabs and spaces. */
                        if (insingle || indouble)
                                goto addch;
                        if (insingle || indouble)
                                goto addch;
-                       goto addarg;
+                       goto arg2;
                case '\n':
                case '\n':
-                       if (mark == p)                  /* empty line */
+                       /* Empty lines are skipped. */
+                       if (argp == p)
                                continue;
                                continue;
-addarg:                        *xp++ = mark;
-                       *p++ = '\0';
-                       if (xp == endxp || p >= endbp || ch == EOF) {
-                               if (insingle || indouble) {
-                                       (void)fprintf(stderr,
-                                          "xargs: unterminated quote.\n");
-                                       exit(1);
-                               }
-                               run(prog, xargs);
+
+                       /* Quotes do not escape newlines. */
+arg1:                  if (insingle || indouble)
+                                err("unterminated quote");
+
+arg2:                  *p = '\0';
+                       *xp++ = argp;
+
+                       /*
+                        * 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)
+                                       err("insufficient space for arguments");
+                               *xp = NULL;
+                               run(av);
                                if (ch == EOF)
                                        exit(0);
                                if (ch == EOF)
                                        exit(0);
-                               p = bp;
+                               p = bbp;
                                xp = bxp;
                                xp = bxp;
-                       }
-                       mark = p;
+                       } else
+                               ++p;
+                       argp = p;
                        break;
                case '\'':
                        if (indouble)
                        break;
                case '\'':
                        if (indouble)
@@ -154,42 +221,43 @@ addarg:                   *xp++ = mark;
                        indouble = !indouble;
                        break;
                case '\\':
                        indouble = !indouble;
                        break;
                case '\\':
-                       if ((ch = getchar()) == EOF)
-                               ch = '\\';
-                       if (ch == '\n') {
-                               (void)fprintf(stderr,
-                                   "xargs: newline may not be escaped.\n");
-                               exit(1);
-                       }
+                       /* Backslash escapes anything, is escaped by quotes. */
+                       if (!insingle && !indouble && (ch = getchar()) == EOF)
+                               err("backslash at EOF");
                        /* FALLTHROUGH */
                default:
                        /* FALLTHROUGH */
                default:
-addch:                 if (p != endbp) {
+addch:                 if (p < ebp) {
                                *p++ = ch;
                                *p++ = ch;
-                               continue;
-                       }
-                       if (xp == bxp) {
-                               (void)fprintf(stderr,
-                                   "xargs: argument too large.\n");
-                               exit(1);
+                               break;
                        }
                        }
+
+                       /* If only one argument, not enough buffer space. */
+                       if (bxp == xp)
+                               err("insufficient space for argument");
+                       /* Didn't hit argument limit, so if xflag object. */
+                       if (xflag)
+                               err("insufficient space for arguments");
+
                        *xp = NULL;
                        *xp = NULL;
-                       run(prog, xargs);
-                       cnt = endbp - mark;
-                       bcopy(mark, bp, cnt);
-                       p = (mark = bp) + cnt;
-                       *p++ = ch;
+                       run(av);
                        xp = bxp;
                        xp = bxp;
+                       cnt = ebp - argp;
+                       bcopy(argp, bbp, cnt);
+                       p = (argp = bbp) + cnt;
+                       *p++ = ch;
                        break;
                }
        /* NOTREACHED */
 }
 
                        break;
                }
        /* NOTREACHED */
 }
 
-run(prog, argv)
-       char *prog, **argv;
+void
+run(argv)
+       char **argv;
 {
 {
-       union wait pstat;
-       pid_t pid, waitpid();
-       char **p;
+       register char **p;
+       pid_t pid;
+       volatile int noinvoke;
+       int status;
 
        if (tflag) {
                (void)fprintf(stderr, "%s", *argv);
 
        if (tflag) {
                (void)fprintf(stderr, "%s", *argv);
@@ -198,36 +266,65 @@ run(prog, argv)
                (void)fprintf(stderr, "\n");
                (void)fflush(stderr);
        }
                (void)fprintf(stderr, "\n");
                (void)fflush(stderr);
        }
+       noinvoke = 0;
        switch(pid = vfork()) {
        case -1:
        switch(pid = vfork()) {
        case -1:
-               (void)fprintf(stderr,
-                  "xargs: vfork: %s.\n", strerror(errno));
-               exit(1);
+               err("vfork: %s", strerror(errno));
        case 0:
        case 0:
-               execvp(prog, argv);
+               execvp(argv[0], argv);
                (void)fprintf(stderr,
                (void)fprintf(stderr,
-                  "xargs: %s: %s.\n", prog, strerror(errno));
+                   "xargs: %s: %s.\n", argv[0], strerror(errno));
+               noinvoke = 1;
                _exit(1);
        }
                _exit(1);
        }
-       pid = waitpid(pid, &pstat, 0);
-       if (pid == -1) {
-               (void)fprintf(stderr,
-                  "xargs: waitpid: %s.\n", strerror(errno));
-               exit(1);
-       }
-       if (pstat.w_status)
-               exit(1);
+       pid = waitpid(pid, &status, 0);
+       if (pid == -1)
+               err("waitpid: %s", strerror(errno));
+       /*
+        * If we couldn't invoke the utility or the utility didn't exit
+        * properly, quit with 127.
+        * Otherwise, if not specified otherwise, and the utility exits
+        * non-zero, exit with that value.
+        */
+       if (noinvoke || !WIFEXITED(status) || WIFSIGNALED(status))
+               exit(127);
+       if (!fflag && WEXITSTATUS(status))
+               exit(WEXITSTATUS(status));
 }
 
 }
 
-enomem()
+void
+usage()
 {
 {
-       (void)fprintf(stderr, "xargs: %s.\n", strerror(ENOMEM));
+       (void)fprintf(stderr,
+"usage: xargs [-ft] [[-x] -n number] [-s size] [utility [argument ...]]\n");
        exit(1);
 }
 
        exit(1);
 }
 
-usage()
+#if __STDC__
+#include <stdarg.h>
+#else
+#include <varargs.h>
+#endif
+
+void
+#if __STDC__
+err(const char *fmt, ...)
+#else
+err(fmt, va_alist)
+       char *fmt;
+        va_dcl
+#endif
 {
 {
-       (void)fprintf(stderr,
-           "xargs: [-t] [-n number] [-s size] [utility [argument ...]]\n");
+       va_list ap;
+#if __STDC__
+       va_start(ap, fmt);
+#else
+       va_start(ap);
+#endif
+       (void)fprintf(stderr, "xargs: ");
+       (void)vfprintf(stderr, fmt, ap);
+       va_end(ap);
+       (void)fprintf(stderr, "\n");
        exit(1);
        exit(1);
+       /* NOTREACHED */
 }
 }