xargs(1): Sync with FreeBSD
authorAntonio Huete Jimenez <tuxillo@quantumachine.net>
Sat, 23 May 2020 01:22:51 +0000 (03:22 +0200)
committerAntonio Huete Jimenez <tuxillo@quantumachine.net>
Sat, 23 May 2020 01:22:51 +0000 (03:22 +0200)
  Changes:
    - Use waitpid(2) instead of wait3(2), for portability reasons.
    - Call setlocale(3) with LC_ALL category instead of LC_MESSAGES.
    - Fix -0 vs -L/-I processing.
    - Add -S option to control the maximum size of an argument.
    - Make xargs(1) keep track of its children.
    - Wait for all invocations upon exit.
    - calloc(3) usage fixes.
    - Replace atoi(3) calls with stronum(3) ones.
    - Now -P0 creates as many concurrent processes as possible.
    - Fix exit status expression when a child fails to exec.

The -0 fix solves a problem with DPorts' graphics/libprojectm during stage phase.

usr.bin/xargs/pathnames.h
usr.bin/xargs/strnsubst.c
usr.bin/xargs/xargs.1
usr.bin/xargs/xargs.c

index 940977f..497b4f9 100644 (file)
@@ -1,4 +1,6 @@
 /*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
  * Copyright (c) 1990, 1993
  *     The Regents of the University of California.  All rights reserved.
  *
@@ -26,6 +28,8 @@
  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  *
+ * $FreeBSD: head/usr.bin/xargs/pathnames.h 326025 2017-11-20 19:49:47Z pfg $
+ *
  *     @(#)pathnames.h 8.1 (Berkeley) 6/6/93
  */
 
index 376068a..a597235 100644 (file)
@@ -7,8 +7,7 @@
  * with the following statement:
  *     For the man who taught me vi, and who got too old, too young.
  *
- * $FreeBSD: src/usr.bin/xargs/strnsubst.c,v 1.5.2.1 2002/06/17 04:44:46 jmallett Exp $
- * $DragonFly: src/usr.bin/xargs/strnsubst.c,v 1.2 2003/06/17 04:29:34 dillon Exp $
+ * $FreeBSD: head/usr.bin/xargs/strnsubst.c 327230 2017-12-27 03:23:01Z eadler $
  */
 
 #include <err.h>
@@ -37,7 +36,19 @@ strnsubst(char **str, const char *match, const char *replstr, size_t maxsize)
        s1 = *str;
        if (s1 == NULL)
                return;
-       s2 = calloc(maxsize, 1);
+       /*
+        * If maxsize is 0 then set it to the length of s1, because we have
+        * to duplicate s1.  XXX we maybe should double-check whether the match
+        * appears in s1.  If it doesn't, then we also have to set the length
+        * to the length of s1, to avoid modifying the argument.  It may make
+        * sense to check if maxsize is <= strlen(s1), because in that case we
+        * want to return the unmodified string, too.
+        */
+       if (maxsize == 0) {
+               match = NULL;
+               maxsize = strlen(s1) + 1;
+       }
+       s2 = calloc(1, maxsize);
        if (s2 == NULL)
                err(1, "calloc");
 
@@ -53,8 +64,8 @@ strnsubst(char **str, const char *match, const char *replstr, size_t maxsize)
                this = strstr(s1, match);
                if (this == NULL)
                        break;
-               if ((strlen(s2) + ((uintptr_t)this - (uintptr_t)s1) +
-                   (strlen(replstr) - 1)) > maxsize && *replstr != '\0') {
+               if ((strlen(s2) + strlen(s1) + strlen(replstr) -
+                   strlen(match) + 1) > maxsize) {
                        strlcat(s2, s1, maxsize);
                        goto done;
                }
@@ -74,14 +85,25 @@ done:
 int 
 main(void)
 {
-       char *x, *y;
+       char *x, *y, *z, *za;
 
-       y = x = "{}{}{}";
-       strnsubst(&x, "{}", "v ybir whyv! ", 12);
-       if (strcmp(x, "v ybir whyv! ") == 0)
-               printf("strnsubst() seems to work as expected.\n");
-       printf("x: %s\ny: %s\n", x, y);
+       x = "{}%$";
+       strnsubst(&x, "%$", "{} enpury!", 255);
+       y = x;
+       strnsubst(&y, "}{}", "ybir", 255);
+       z = y;
+       strnsubst(&z, "{", "v ", 255);
+       za = z;
+       strnsubst(&z, NULL, za, 255);
+       if (strcmp(z, "v ybir enpury!") == 0)
+               printf("strnsubst() seems to work!\n");
+       else
+               printf("strnsubst() is broken.\n");
+       printf("%s\n", z);
        free(x);
+       free(y);
+       free(z);
+       free(za);
        return 0;
 }
 #endif
index 8b35905..3ae3576 100644 (file)
 .\" SUCH DAMAGE.
 .\"
 .\"    @(#)xargs.1     8.1 (Berkeley) 6/6/93
-.\" $FreeBSD: src/usr.bin/xargs/xargs.1,v 1.6.2.12 2003/06/01 21:40:35 mux Exp $
+.\" $FreeBSD: head/usr.bin/xargs/xargs.1 314436 2017-02-28 23:42:47Z imp $
 .\" $xMach: xargs.1,v 1.2 2002/02/23 05:23:37 tim Exp $
 .\"
-.Dd March 1, 2017
+.Dd May 23, 2020
 .Dt XARGS 1
 .Os
 .Sh NAME
 .Nd "construct argument list(s) and execute utility"
 .Sh SYNOPSIS
 .Nm
-.Op Fl 0opt
+.Op Fl 0oprt
 .Op Fl E Ar eofstr
 .Oo
 .Fl I Ar replstr
 .Op Fl R Ar replacements
+.Op Fl S Ar replsize
 .Oc
 .Op Fl J Ar replstr
 .Op Fl L Ar number
@@ -53,7 +54,7 @@
 .Fl n Ar number
 .Op Fl x
 .Oc
-.Op Fl P Ar maxjobs
+.Op Fl P Ar maxprocs
 .Op Fl s Ar size
 .Op Ar utility Op Ar argument ...
 .Sh DESCRIPTION
@@ -70,8 +71,7 @@ Any arguments specified on the command line are given to
 upon each invocation, followed by some number of the arguments read
 from the standard input of
 .Nm .
-The utility
-is repeatedly executed until standard input is exhausted.
+This is repeated until standard input is exhausted.
 .Pp
 Spaces, tabs and newlines may be embedded in arguments using single
 (``\ '\ '')
@@ -111,13 +111,20 @@ flag is specified) arguments to
 .Ar utility
 with the entire line of input.
 The resulting arguments, after replacement is done, will not be allowed to grow
-beyond 255 bytes; this is implemented by concatenating as much of the argument
+beyond
+.Ar replsize
+(or 255 if no
+.Fl S
+flag is specified)
+bytes; this is implemented by concatenating as much of the argument
 containing
 .Ar replstr
 as possible, to the constructed arguments to
 .Ar utility ,
-up to 255 bytes.
-The 255 byte limit does not apply to arguments to
+up to
+.Ar replsize
+bytes.
+The size limit does not apply to arguments to
 .Ar utility
 which do not contain
 .Ar replstr ,
@@ -156,7 +163,7 @@ directories which start with an uppercase letter in the current
 directory to
 .Pa destdir :
 .Pp
-.Dl /bin/ls -1d [A-Z]* | xargs -J % cp -rp % destdir
+.Dl /bin/ls -1d [A-Z]* | xargs -J % cp -Rp % destdir
 .It Fl L Ar number
 Call
 .Ar utility
@@ -200,6 +207,11 @@ Parallel mode: run at most
 invocations of
 .Ar utility
 at once.
+If
+.Ar maxprocs
+is set to 0,
+.Nm
+will run as many processes as possible.
 .It Fl p
 Echo each command to be executed and ask the user whether it should be
 executed.
@@ -222,7 +234,7 @@ input is empty, and it supports a
 .Fl r
 option to inhibit this behavior.
 The
-.Dx
+.Fx
 version of
 .Nm
 does not run the
@@ -234,13 +246,23 @@ option for command-line compatibility with GNU
 but the
 .Fl r
 option does nothing in the
-.Dx
+.Fx
 version of
 .Nm .
 .It Fl R Ar replacements
 Specify the maximum number of arguments that
 .Fl I
 will do replacement in.
+If
+.Ar replacements
+is negative, the number of arguments in which to replace is unbounded.
+.It Fl S Ar replsize
+Specify the amount of space (in bytes) that
+.Fl I
+can use for replacements.
+The default for
+.Ar replsize
+is 255.
 .It Fl s Ar size
 Set the maximum number of bytes for the command line length provided to
 .Ar utility .
@@ -276,17 +298,17 @@ Undefined behavior may occur if
 .Ar utility
 reads from the standard input.
 .Pp
-The
-.Nm
-utility exits immediately (without processing any further input) if a
-command line cannot be assembled,
-.Ar utility
-cannot be invoked, an invocation of
+If a command line cannot be assembled, or
+cannot be invoked, or if an invocation of
 .Ar utility
 is terminated by a signal,
 or an invocation of
 .Ar utility
-exits with a value of 255.
+exits with a value of 255, the
+.Nm
+utility stops processing input and exits after all invocations of
+.Ar utility
+finish processing.
 .Sh EXIT STATUS
 The
 .Nm
@@ -314,16 +336,16 @@ utility is expected to be
 .St -p1003.2
 compliant.
 The
-.Fl J , o , P
+.Fl J , o , P, R
 and
-.Fl R
+.Fl S
 options are non-standard
 .Dx
 extensions which may not be available on other operating systems.
 .Sh HISTORY
 The
 .Nm
-command appeared in PWB
+utility appeared in PWB
 .Ux .
 .Sh BUGS
 If
@@ -333,3 +355,12 @@ size of the environment is increased, it risks
 .Xr execvp 3
 failing with
 .Er E2BIG .
+.Pp
+The
+.Nm
+utility does not take multibyte characters into account when performing
+string comparisons for the
+.Fl I
+and
+.Fl J
+options, which may lead to incorrect results in some locales.
index 198f1cc..0963b01 100644 (file)
@@ -1,4 +1,6 @@
 /*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
  * 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.
  * @(#)xargs.c 8.1 (Berkeley) 6/6/93
- * $FreeBSD: src/usr.bin/xargs/xargs.c,v 1.9.2.6 2003/06/01 21:40:35 mux Exp $
+ * $FreeBSD: head/usr.bin/xargs/xargs.c 359596 2020-04-03 14:03:58Z markj $
  */
 
 #include <sys/types.h>
 #include <sys/wait.h>
-
+#include <sys/time.h>
+#include <sys/limits.h>
+#include <sys/resource.h>
 #include <err.h>
 #include <errno.h>
 #include <fcntl.h>
@@ -63,15 +67,27 @@ static int  prompt(void);
 static void    run(char **);
 static void    usage(void);
 void           strnsubst(char **, const char *, const char *, size_t);
+static pid_t   xwait(int block, int *status);
+static void    xexit(const char *, const int);
 static void    waitchildren(const char *, int);
+static void    pids_init(void);
+static int     pids_empty(void);
+static int     pids_full(void);
+static void    pids_add(pid_t pid);
+static int     pids_remove(pid_t pid);
+static int     findslot(pid_t pid);
+static int     findfreeslot(void);
+static void    clearslot(int slot);
 
 static char echo[] = _PATH_ECHO;
-static char **av, **bxp, **ep, **expx, **xp;
+static char **av, **bxp, **ep, **endxp, **xp;
 static char *argp, *bbp, *ebp, *inpline, *p, *replstr;
 static const char *eofstr;
 static int count, insingle, indouble, oflag, pflag, tflag, Rflag, rval, zflag;
-static int cnt, Iflag, jfound, Lflag, wasquoted, xflag;
+static int cnt, Iflag, jfound, Lflag, Sflag, wasquoted, xflag;
 static int curprocs, maxprocs;
+static pid_t *childpids;
+
 static volatile int childerr;
 
 extern char **environ;
@@ -79,17 +95,19 @@ extern char **environ;
 int
 main(int argc, char *argv[])
 {
-       char *tmp;
        long arg_max;
        int ch, Jflag, nargs, nflag, nline;
        size_t linelen;
+       struct rlimit rl;
+       char *endptr;
+       const char *errstr;
 
        inpline = replstr = NULL;
        ep = environ;
        eofstr = "";
        Jflag = nflag = 0;
 
-       (void)setlocale(LC_MESSAGES, "");
+       (void)setlocale(LC_ALL, "");
 
        /*
         * POSIX.2 limits the exec line length to ARG_MAX - 2K.  Running that
@@ -116,10 +134,9 @@ main(int argc, char *argv[])
                /* 1 byte for each '\0' */
                nline -= strlen(*ep++) + 1 + sizeof(*ep);
        }
-
        maxprocs = 1;
-       while ((ch = getopt(argc, argv, "0E:I:J:L:n:oprP:R:s:tx")) != -1)
-               switch(ch) {
+       while ((ch = getopt(argc, argv, "0E:I:J:L:n:oP:pR:S:s:rtx")) != -1)
+               switch (ch) {
                case 'E':
                        eofstr = optarg;
                        break;
@@ -135,37 +152,50 @@ main(int argc, char *argv[])
                        replstr = optarg;
                        break;
                case 'L':
-                       Lflag = strtol(optarg, &tmp, 10);
-                       if (*tmp != 0 || *optarg == 0)
-                               errx(1, "illegal argument count");
+                       Lflag = strtonum(optarg, 0, INT_MAX, &errstr);
+                       if (errstr)
+                               errx(1, "-L %s: %s", optarg, errstr);
                        break;
                case 'n':
                        nflag = 1;
-                       if ((nargs = atoi(optarg)) <= 0)
-                               errx(1, "illegal argument count");
+                       nargs = strtonum(optarg, 1, INT_MAX, &errstr);
+                       if (errstr)
+                               errx(1, "-n %s: %s", optarg, errstr);
                        break;
                case 'o':
                        oflag = 1;
                        break;
+               case 'P':
+                       maxprocs = strtonum(optarg, 0, INT_MAX, &errstr);
+                       if (errstr)
+                               errx(1, "-P %s: %s", optarg, errstr);
+                       if (getrlimit(RLIMIT_NPROC, &rl) != 0)
+                               errx(1, "getrlimit failed");
+                       if (maxprocs == 0 || maxprocs > rl.rlim_cur)
+                               maxprocs = rl.rlim_cur;
+                       break;
                case 'p':
 #ifndef BOOTSTRAPPING
                        pflag = 1;
 #endif
                        break;
-               case 'P':
-                       if ((maxprocs = atoi(optarg)) <= 0)
-                               errx(1, "max.processes must be >0");
-                       break;
                case 'R':
-                       if ((Rflag = atoi(optarg)) <= 0)
-                               errx(1, "illegal number of replacements");
+                       Rflag = strtol(optarg, &endptr, 10);
+                       if (*endptr != '\0')
+                               errx(1, "replacements must be a number");
                        break;
                case 'r':
                        /* GNU compatibility */
-                       /* --no-run-if-empty (this is our default) */
+                       break;
+               case 'S':
+                       Sflag = strtoul(optarg, &endptr, 10);
+                       if (*endptr != '\0')
+                               errx(1, "replsize must be a number");
                        break;
                case 's':
-                       nline = atoi(optarg);
+                       nline = strtonum(optarg, 0, INT_MAX, &errstr);
+                       if (errstr)
+                               errx(1, "-s %s: %s", optarg, errstr);
                        break;
                case 't':
                        tflag = 1;
@@ -185,8 +215,12 @@ main(int argc, char *argv[])
 
        if (!Iflag && Rflag)
                usage();
+       if (!Iflag && Sflag)
+               usage();
        if (Iflag && !Rflag)
                Rflag = 5;
+       if (Iflag && !Sflag)
+               Sflag = 255;
        if (xflag && !nflag)
                usage();
        if (Iflag || Lflag)
@@ -194,13 +228,15 @@ main(int argc, char *argv[])
        if (replstr != NULL && *replstr == '\0')
                errx(1, "replstr may not be empty");
 
+       pids_init();
+
        /*
         * Allocate pointers for the utility name, the utility arguments,
         * the maximum arguments to be read from stdin and the trailing
         * NULL.
         */
        linelen = 1 + argc + nargs + 1;
-       if ((av = bxp = malloc(linelen * sizeof(char **))) == NULL)
+       if ((av = bxp = malloc(linelen * sizeof(char *))) == NULL)
                errx(1, "malloc failed");
 
        /*
@@ -229,7 +265,7 @@ main(int argc, char *argv[])
         * count doesn't include the trailing NULL pointer, so the malloc
         * added in an extra slot.
         */
-       expx = (xp = bxp) + nargs;
+       endxp = (xp = bxp) + nargs;
 
        /*
         * Allocate buffer space for the arguments read from stdin and the
@@ -258,13 +294,11 @@ parse_input(int argc, char *argv[])
 
        foundeof = 0;
 
-       switch(ch = getchar()) {
+       switch (ch = getchar()) {
        case EOF:
                /* No arguments since last exec. */
-               if (p == bbp) {
-                       waitchildren(*argv, 1);
-                       exit(rval);
-               }
+               if (p == bbp)
+                       xexit(*av, rval);
                goto arg1;
        case ' ':
        case '\t':
@@ -273,20 +307,29 @@ parse_input(int argc, char *argv[])
                        goto addch;
                goto arg2;
        case '\0':
-               if (zflag)
+               if (zflag) {
+                       /*
+                        * Increment 'count', so that nulls will be treated
+                        * as end-of-line, as well as end-of-argument.  This
+                        * is needed so -0 works properly with -I and -L.
+                        */
+                       count++;
                        goto arg2;
+               }
                goto addch;
        case '\n':
-               count++;
                if (zflag)
                        goto addch;
+               count++;            /* Indicate end-of-line (used by -L) */
 
                /* Quotes do not escape newlines. */
-arg1:          if (insingle || indouble)
-                       errx(1, "unterminated quote");
+arg1:          if (insingle || indouble) {
+                       warnx("unterminated quote");
+                       xexit(*av, 1);
+               }
 arg2:
                foundeof = *eofstr != '\0' &&
-                   strcmp(argp, eofstr) == 0;
+                   strncmp(argp, eofstr, p - argp) == 0;
 
                /* Do not make empty args unless they are quoted */
                if ((argp != p || wasquoted) && !foundeof) {
@@ -316,8 +359,10 @@ arg2:
                                 */
                                inpline = realloc(inpline, curlen + 2 +
                                    strlen(argp));
-                               if (inpline == NULL)
-                                       errx(1, "realloc failed");
+                               if (inpline == NULL) {
+                                       warnx("realloc failed");
+                                       xexit(*av, 1);
+                               }
                                if (curlen == 1)
                                        strcpy(inpline, argp);
                                else
@@ -332,19 +377,19 @@ arg2:
                 * of input lines, as specified by -L is the same as
                 * maxing out on arguments.
                 */
-               if (xp == expx || p > ebp || ch == EOF ||
+               if (xp == endxp || p > ebp || ch == EOF ||
                    (Lflag <= count && xflag) || foundeof) {
-                       if (xflag && xp != expx && p > ebp)
-                               errx(1, "insufficient space for arguments");
+                       if (xflag && xp != endxp && p > ebp) {
+                               warnx("insufficient space for arguments");
+                               xexit(*av, 1);
+                       }
                        if (jfound) {
                                for (avj = argv; *avj; avj++)
                                        *xp++ = *avj;
                        }
                        prerun(argc, av);
-                       if (ch == EOF || foundeof) {
-                               waitchildren(*argv, 1);
-                               exit(rval);
-                       }
+                       if (ch == EOF || foundeof)
+                               xexit(*av, rval);
                        p = bbp;
                        xp = bxp;
                        count = 0;
@@ -368,8 +413,10 @@ arg2:
                if (zflag)
                        goto addch;
                /* Backslash escapes anything, is escaped by quotes. */
-               if (!insingle && !indouble && (ch = getchar()) == EOF)
-                       errx(1, "backslash at EOF");
+               if (!insingle && !indouble && (ch = getchar()) == EOF) {
+                       warnx("backslash at EOF");
+                       xexit(*av, 1);
+               }
                /* FALLTHROUGH */
        default:
 addch:         if (p < ebp) {
@@ -378,11 +425,15 @@ addch:            if (p < ebp) {
                }
 
                /* If only one argument, not enough buffer space. */
-               if (bxp == xp)
-                       errx(1, "insufficient space for argument");
+               if (bxp == xp) {
+                       warnx("insufficient space for argument");
+                       xexit(*av, 1);
+               }
                /* Didn't hit argument limit, so if xflag object. */
-               if (xflag)
-                       errx(1, "insufficient space for arguments");
+               if (xflag) {
+                       warnx("insufficient space for arguments");
+                       xexit(*av, 1);
+               }
 
                if (jfound) {
                        for (avj = argv; *avj; avj++)
@@ -396,7 +447,6 @@ addch:              if (p < ebp) {
                *p++ = ch;
                break;
        }
-       return;
 }
 
 /*
@@ -423,17 +473,21 @@ prerun(int argc, char *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");
+       tmp = malloc((argc + 1) * sizeof(char *));
+       if (tmp == NULL) {
+               warnx("malloc failed");
+               xexit(*argv, 1);
+       }
        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");
+       if ((*tmp++ = strdup(*avj++)) == NULL) {
+               warnx("strdup failed");
+               xexit(*argv, 1);
+       }
 
        /*
         * For each argument to utility, if we have not used up
@@ -446,11 +500,14 @@ prerun(int argc, char *argv[])
        while (--argc) {
                *tmp = *avj++;
                if (repls && strstr(*tmp, replstr) != NULL) {
-                       strnsubst(tmp++, replstr, inpline, (size_t)255);
-                       repls--;
+                       strnsubst(tmp++, replstr, inpline, (size_t)Sflag);
+                       if (repls > 0)
+                               repls--;
                } else {
-                       if ((*tmp = strdup(*tmp)) == NULL)
-                               errx(1, "strdup failed");
+                       if ((*tmp = strdup(*tmp)) == NULL) {
+                               warnx("strdup failed");
+                               xexit(*argv, 1);
+                       }
                        tmp++;
                }
        }
@@ -483,8 +540,9 @@ prerun(int argc, char *argv[])
 static void
 run(char **argv)
 {
-       char **avec;
        pid_t pid;
+       int fd;
+       char **avec;
 
        /*
         * If the user wants to be notified of each command before it is
@@ -522,56 +580,177 @@ run(char **argv)
 exec:
 #endif
        childerr = 0;
-       switch(pid = vfork()) {
+       switch (pid = vfork()) {
        case -1:
-               err(1, "vfork");
+               warn("vfork");
+               xexit(*argv, 1);
+               break;
        case 0:
-               close(0);
                if (oflag) {
-                       if (open("/dev/tty", O_RDONLY) == -1)
-                               err(1, "open /dev/tty");
+                       if ((fd = open(_PATH_TTY, O_RDONLY)) == -1)
+                               err(1, "can't open /dev/tty");
                } else {
-                       if (open("/dev/null", O_RDONLY) == -1)
-                               err(1, "open /dev/null");
+                       fd = open(_PATH_DEVNULL, O_RDONLY);
+               }
+               if (fd > STDIN_FILENO) {
+                       if (dup2(fd, STDIN_FILENO) != 0)
+                               err(1, "can't dup2 to stdin");
+                       close(fd);
                }
                execvp(argv[0], argv);
                childerr = errno;
                _exit(1);
        }
-       curprocs++;
+       pids_add(pid);
        waitchildren(*argv, 0);
 }
 
 /*
- * Handle child processes.
+ * Wait for a tracked child to exit and return its pid and exit status.
+ *
+ * Ignores (discards) all untracked child processes.
+ * Returns -1 and sets errno to ECHILD if no tracked children exist.
+ * If block is set, waits indefinitely for a child process to exit.
+ * If block is not set and no children have exited, returns 0 immediately.
  */
+static pid_t
+xwait(int block, int *status) {
+       pid_t pid;
+
+       if (pids_empty()) {
+               errno = ECHILD;
+               return (-1);
+       }
+
+       while ((pid = waitpid(-1, status, block ? 0 : WNOHANG)) > 0)
+               if (pids_remove(pid))
+                       break;
+
+       return (pid);
+}
+
+static void
+xexit(const char *name, const int exit_code) {
+       waitchildren(name, 1);
+       exit(exit_code);
+}
+
 static void
 waitchildren(const char *name, int waitall)
 {
        pid_t pid;
        int status;
+       int cause_exit = 0;
 
-       while ((pid = wait3(&status, !waitall && curprocs < maxprocs ?
-               WNOHANG : 0, NULL)) > 0) {
-               curprocs--;
-
-               /* If we couldn't invoke the utility, exit. */
-               if (childerr != 0) {
-                       errno = childerr;
-                       err(errno == ENOENT ? 127 : 126, "%s", name);
-               }
-
+       while ((pid = xwait(waitall || pids_full(), &status)) > 0) {
                /*
-                * If utility signaled or exited with a value of 255,
-                * exit 1-125.
+                * If we couldn't invoke the utility or if utility exited
+                * because of a signal or with a value of 255, warn (per
+                * POSIX), and then wait until all other children have
+                * exited before exiting 1-125. POSIX requires us to stop
+                * reading if child exits because of a signal or with 255,
+                * but it does not require us to exit immediately; waiting
+                * is preferable to orphaning.
                 */
-               if (WIFSIGNALED(status) || WEXITSTATUS(status) == 255)
-                       exit(1);
-               if (WEXITSTATUS(status))
-                       rval = 1;
-        }
+               if (childerr != 0 && cause_exit == 0) {
+                       errno = childerr;
+                       waitall = 1;
+                       cause_exit = errno == ENOENT ? 127 : 126;
+                       warn("%s", name);
+               } else if (WIFSIGNALED(status)) {
+                       waitall = cause_exit = 1;
+                       warnx("%s: terminated with signal %d; aborting",
+                           name, WTERMSIG(status));
+               } else if (WEXITSTATUS(status) == 255) {
+                       waitall = cause_exit = 1;
+                       warnx("%s: exited with status 255; aborting", name);
+               } else if (WEXITSTATUS(status))
+                       rval = 1;
+       }
+
+       if (cause_exit)
+               exit(cause_exit);
        if (pid == -1 && errno != ECHILD)
-               err(1, "wait3");
+               err(1, "waitpid");
+}
+
+#define        NOPID   (0)
+
+static void
+pids_init(void)
+{
+       int i;
+
+       if ((childpids = malloc(maxprocs * sizeof(*childpids))) == NULL)
+               errx(1, "malloc failed");
+
+       for (i = 0; i < maxprocs; i++)
+               clearslot(i);
+}
+
+static int
+pids_empty(void)
+{
+
+       return (curprocs == 0);
+}
+
+static int
+pids_full(void)
+{
+
+       return (curprocs >= maxprocs);
+}
+
+static void
+pids_add(pid_t pid)
+{
+       int slot;
+
+       slot = findfreeslot();
+       childpids[slot] = pid;
+       curprocs++;
+}
+
+static int
+pids_remove(pid_t pid)
+{
+       int slot;
+
+       if ((slot = findslot(pid)) < 0)
+               return (0);
+
+       clearslot(slot);
+       curprocs--;
+       return (1);
+}
+
+static int
+findfreeslot(void)
+{
+       int slot;
+
+       if ((slot = findslot(NOPID)) < 0)
+               errx(1, "internal error: no free pid slot");
+       return (slot);
+}
+
+static int
+findslot(pid_t pid)
+{
+       int slot;
+
+       for (slot = 0; slot < maxprocs; slot++)
+               if (childpids[slot] == pid)
+                       return (slot);
+       return (-1);
+}
+
+static void
+clearslot(int slot)
+{
+
+       childpids[slot] = NOPID;
 }
 
 /*
@@ -592,12 +771,11 @@ prompt(void)
        (void)fprintf(stderr, "?...");
        (void)fflush(stderr);
        if ((response = fgetln(ttyfp, &rsize)) == NULL ||
-           regcomp(&cre,
-               nl_langinfo(YESEXPR),
-               REG_BASIC) != 0) {
+           regcomp(&cre, nl_langinfo(YESEXPR), REG_BASIC) != 0) {
                (void)fclose(ttyfp);
                return (0);
        }
+       response[rsize - 1] = '\0';
        match = regexec(&cre, response, 0, NULL, 0);
        (void)fclose(ttyfp);
        regfree(&cre);
@@ -608,9 +786,10 @@ prompt(void)
 static void
 usage(void)
 {
+
        fprintf(stderr,
-"usage: xargs [-0opt] [-E eofstr] [-I replstr [-R replacements]] [-J replstr]\n"
-"             [-L number] [-n number [-x] [-P maxprocs] [-s size]\n"
-"             [utility [argument ...]]\n");
+"usage: xargs [-0opt] [-E eofstr] [-I replstr [-R replacements] [-S replsize]]\n"
+"             [-J replstr] [-L number] [-n number [-x]] [-P maxprocs]\n"
+"             [-s size] [utility [argument ...]]\n");
        exit(1);
 }