aboutsummaryrefslogtreecommitdiff
path: root/usr.bin/xargs
diff options
context:
space:
mode:
Diffstat (limited to 'usr.bin/xargs')
-rw-r--r--usr.bin/xargs/Makefile11
-rw-r--r--usr.bin/xargs/Makefile.depend15
-rw-r--r--usr.bin/xargs/Makefile.depend.host10
-rw-r--r--usr.bin/xargs/Makefile.depend.options6
-rw-r--r--usr.bin/xargs/pathnames.h32
-rw-r--r--usr.bin/xargs/strnsubst.c112
-rw-r--r--usr.bin/xargs/tests/Makefile27
-rw-r--r--usr.bin/xargs/tests/Makefile.depend10
-rw-r--r--usr.bin/xargs/tests/regress.0.inbin0 -> 86 bytes
-rw-r--r--usr.bin/xargs/tests/regress.0.out8
-rw-r--r--usr.bin/xargs/tests/regress.0I.out18
-rw-r--r--usr.bin/xargs/tests/regress.0J.out4
-rw-r--r--usr.bin/xargs/tests/regress.0L.out6
-rw-r--r--usr.bin/xargs/tests/regress.0P1.out4
-rw-r--r--usr.bin/xargs/tests/regress.I.out4
-rw-r--r--usr.bin/xargs/tests/regress.J.out1
-rw-r--r--usr.bin/xargs/tests/regress.L.out2
-rw-r--r--usr.bin/xargs/tests/regress.P1.out1
-rw-r--r--usr.bin/xargs/tests/regress.R-1.out4
-rw-r--r--usr.bin/xargs/tests/regress.R.out4
-rw-r--r--usr.bin/xargs/tests/regress.in4
-rw-r--r--usr.bin/xargs/tests/regress.n1.out8
-rw-r--r--usr.bin/xargs/tests/regress.n2.out4
-rw-r--r--usr.bin/xargs/tests/regress.n2P0.out4
-rw-r--r--usr.bin/xargs/tests/regress.n3.out3
-rw-r--r--usr.bin/xargs/tests/regress.nargmax.out1
-rw-r--r--usr.bin/xargs/tests/regress.normal.out1
-rw-r--r--usr.bin/xargs/tests/regress.quotes.in4
-rw-r--r--usr.bin/xargs/tests/regress.quotes.out7
-rwxr-xr-xusr.bin/xargs/tests/xargs_test.sh193
-rw-r--r--usr.bin/xargs/xargs.1388
-rw-r--r--usr.bin/xargs/xargs.c803
32 files changed, 1699 insertions, 0 deletions
diff --git a/usr.bin/xargs/Makefile b/usr.bin/xargs/Makefile
new file mode 100644
index 000000000000..3202d0e7322a
--- /dev/null
+++ b/usr.bin/xargs/Makefile
@@ -0,0 +1,11 @@
+.include <src.opts.mk>
+
+PACKAGE= runtime
+
+PROG= xargs
+SRCS= xargs.c strnsubst.c
+
+HAS_TESTS=
+SUBDIR.${MK_TESTS}+= tests
+
+.include <bsd.prog.mk>
diff --git a/usr.bin/xargs/Makefile.depend b/usr.bin/xargs/Makefile.depend
new file mode 100644
index 000000000000..6ef78fac5cbf
--- /dev/null
+++ b/usr.bin/xargs/Makefile.depend
@@ -0,0 +1,15 @@
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.bin/xargs/Makefile.depend.host b/usr.bin/xargs/Makefile.depend.host
new file mode 100644
index 000000000000..11aba52f82cf
--- /dev/null
+++ b/usr.bin/xargs/Makefile.depend.host
@@ -0,0 +1,10 @@
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.bin/xargs/Makefile.depend.options b/usr.bin/xargs/Makefile.depend.options
new file mode 100644
index 000000000000..4dcc6c412671
--- /dev/null
+++ b/usr.bin/xargs/Makefile.depend.options
@@ -0,0 +1,6 @@
+DIRDEPS_OPTIONS = host_egacy
+
+DIRDEPS.host_egacy.yes = tools/build
+
+.include <dirdeps-options.mk>
+
diff --git a/usr.bin/xargs/pathnames.h b/usr.bin/xargs/pathnames.h
new file mode 100644
index 000000000000..b804276da175
--- /dev/null
+++ b/usr.bin/xargs/pathnames.h
@@ -0,0 +1,32 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * 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. 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.
+ */
+
+#define _PATH_ECHO "/bin/echo"
diff --git a/usr.bin/xargs/strnsubst.c b/usr.bin/xargs/strnsubst.c
new file mode 100644
index 000000000000..1d4eaac77727
--- /dev/null
+++ b/usr.bin/xargs/strnsubst.c
@@ -0,0 +1,112 @@
+/* $xMach: strnsubst.c,v 1.3 2002/02/23 02:10:24 jmallett Exp $ */
+
+/*
+ * Copyright (c) 2002 J. Mallett. All rights reserved.
+ * You may do whatever you want with this file as long as
+ * the above copyright and this notice remain intact, along
+ * with the following statement:
+ * For the man who taught me vi, and who got too old, too young.
+ */
+
+#include <sys/cdefs.h>
+#include <err.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+bool strnsubst(char **, const char *, const char *, size_t);
+
+/*
+ * Replaces str with a string consisting of str with match replaced with
+ * replstr as many times as can be done before the constructed string is
+ * maxsize bytes large. It does not free the string pointed to by str, it
+ * is up to the calling program to be sure that the original contents of
+ * str as well as the new contents are handled in an appropriate manner.
+ * If replstr is NULL, then that internally is changed to a nil-string, so
+ * that we can still pretend to do somewhat meaningful substitution.
+ *
+ * Returns true if truncation was needed to do the replacement, false if
+ * truncation was not required.
+ */
+bool
+strnsubst(char **str, const char *match, const char *replstr, size_t maxsize)
+{
+ char *s1, *s2, *this;
+ bool error = false;
+
+ s1 = *str;
+ if (s1 == NULL)
+ return false;
+ /*
+ * 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");
+
+ if (replstr == NULL)
+ replstr = "";
+
+ if (match == NULL || replstr == NULL || maxsize == strlen(s1)) {
+ strlcpy(s2, s1, maxsize);
+ goto done;
+ }
+
+ for (;;) {
+ this = strstr(s1, match);
+ if (this == NULL)
+ break;
+ if ((strlen(s2) + strlen(s1) + strlen(replstr) -
+ strlen(match) + 1) > maxsize) {
+ strlcat(s2, s1, maxsize);
+ error = true;
+ goto done;
+ }
+ strncat(s2, s1, (uintptr_t)this - (uintptr_t)s1);
+ strcat(s2, replstr);
+ s1 = this + strlen(match);
+ }
+ strcat(s2, s1);
+done:
+ *str = s2;
+ return error;
+}
+
+#ifdef TEST
+#include <stdio.h>
+
+int
+main(void)
+{
+ char *x, *y, *z, *za;
+
+ 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
diff --git a/usr.bin/xargs/tests/Makefile b/usr.bin/xargs/tests/Makefile
new file mode 100644
index 000000000000..9fa8ff11fac2
--- /dev/null
+++ b/usr.bin/xargs/tests/Makefile
@@ -0,0 +1,27 @@
+PACKAGE= tests
+
+ATF_TESTS_SH= xargs_test
+
+${PACKAGE}FILES+= regress.0.in
+${PACKAGE}FILES+= regress.0.out
+${PACKAGE}FILES+= regress.0I.out
+${PACKAGE}FILES+= regress.0J.out
+${PACKAGE}FILES+= regress.0L.out
+${PACKAGE}FILES+= regress.0P1.out
+${PACKAGE}FILES+= regress.I.out
+${PACKAGE}FILES+= regress.J.out
+${PACKAGE}FILES+= regress.L.out
+${PACKAGE}FILES+= regress.P1.out
+${PACKAGE}FILES+= regress.R.out
+${PACKAGE}FILES+= regress.R-1.out
+${PACKAGE}FILES+= regress.in
+${PACKAGE}FILES+= regress.n1.out
+${PACKAGE}FILES+= regress.n2.out
+${PACKAGE}FILES+= regress.nargmax.out
+${PACKAGE}FILES+= regress.n2P0.out
+${PACKAGE}FILES+= regress.n3.out
+${PACKAGE}FILES+= regress.normal.out
+${PACKAGE}FILES+= regress.quotes.in
+${PACKAGE}FILES+= regress.quotes.out
+
+.include <bsd.test.mk>
diff --git a/usr.bin/xargs/tests/Makefile.depend b/usr.bin/xargs/tests/Makefile.depend
new file mode 100644
index 000000000000..11aba52f82cf
--- /dev/null
+++ b/usr.bin/xargs/tests/Makefile.depend
@@ -0,0 +1,10 @@
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.bin/xargs/tests/regress.0.in b/usr.bin/xargs/tests/regress.0.in
new file mode 100644
index 000000000000..448ba5367d4e
--- /dev/null
+++ b/usr.bin/xargs/tests/regress.0.in
Binary files differ
diff --git a/usr.bin/xargs/tests/regress.0.out b/usr.bin/xargs/tests/regress.0.out
new file mode 100644
index 000000000000..2bc97257af69
--- /dev/null
+++ b/usr.bin/xargs/tests/regress.0.out
@@ -0,0 +1,8 @@
+quick ' brown
+fox jumped
+over "the lazy
+dog
+quick brown fox
+jumped over the
+lazy dog
+
diff --git a/usr.bin/xargs/tests/regress.0I.out b/usr.bin/xargs/tests/regress.0I.out
new file mode 100644
index 000000000000..16009c092b01
--- /dev/null
+++ b/usr.bin/xargs/tests/regress.0I.out
@@ -0,0 +1,18 @@
+The quick ' brown quick ' brownquick ' brown quick ' brown
+The fox jumped
+over "the lazy fox jumped
+over "the lazyfox jumped
+over "the lazy fox jumped
+over "the lazy
+The
+The dog
+quick brown fox dog
+quick brown foxdog
+quick brown fox dog
+quick brown fox
+The jumped over the jumped over thejumped over the jumped over the
+The lazy dog
+ lazy dog
+lazy dog
+ lazy dog
+
diff --git a/usr.bin/xargs/tests/regress.0J.out b/usr.bin/xargs/tests/regress.0J.out
new file mode 100644
index 000000000000..69c87f9fa52e
--- /dev/null
+++ b/usr.bin/xargs/tests/regress.0J.out
@@ -0,0 +1,4 @@
+The quick ' brown fox jumped
+over "the lazy dog
+quick brown fox jumped over the lazy dog
+ again.
diff --git a/usr.bin/xargs/tests/regress.0L.out b/usr.bin/xargs/tests/regress.0L.out
new file mode 100644
index 000000000000..2d13fcc0d388
--- /dev/null
+++ b/usr.bin/xargs/tests/regress.0L.out
@@ -0,0 +1,6 @@
+quick ' brown fox jumped
+over "the lazy
+dog
+quick brown fox
+jumped over the lazy dog
+
diff --git a/usr.bin/xargs/tests/regress.0P1.out b/usr.bin/xargs/tests/regress.0P1.out
new file mode 100644
index 000000000000..a95e9a252575
--- /dev/null
+++ b/usr.bin/xargs/tests/regress.0P1.out
@@ -0,0 +1,4 @@
+quick ' brown fox jumped
+over "the lazy dog
+quick brown fox jumped over the lazy dog
+
diff --git a/usr.bin/xargs/tests/regress.I.out b/usr.bin/xargs/tests/regress.I.out
new file mode 100644
index 000000000000..f2a6a1e82e22
--- /dev/null
+++ b/usr.bin/xargs/tests/regress.I.out
@@ -0,0 +1,4 @@
+The quick brown quick brown quick brown quick brownquick brown quick brown %
+The fox jumped fox jumped fox jumped fox jumpedfox jumped fox jumped %
+The over the lazy over the lazy over the lazy over the lazyover the lazy over the lazy %
+The dog dog dog dogdog dog %
diff --git a/usr.bin/xargs/tests/regress.J.out b/usr.bin/xargs/tests/regress.J.out
new file mode 100644
index 000000000000..b88a41e5076b
--- /dev/null
+++ b/usr.bin/xargs/tests/regress.J.out
@@ -0,0 +1 @@
+The quick brown fox jumped over the lazy dog again.
diff --git a/usr.bin/xargs/tests/regress.L.out b/usr.bin/xargs/tests/regress.L.out
new file mode 100644
index 000000000000..7359dee8f366
--- /dev/null
+++ b/usr.bin/xargs/tests/regress.L.out
@@ -0,0 +1,2 @@
+quick brown fox jumped over the lazy
+dog
diff --git a/usr.bin/xargs/tests/regress.P1.out b/usr.bin/xargs/tests/regress.P1.out
new file mode 100644
index 000000000000..cc32a92a2199
--- /dev/null
+++ b/usr.bin/xargs/tests/regress.P1.out
@@ -0,0 +1 @@
+quick brown fox jumped over the lazy dog
diff --git a/usr.bin/xargs/tests/regress.R-1.out b/usr.bin/xargs/tests/regress.R-1.out
new file mode 100644
index 000000000000..7bdf58090c60
--- /dev/null
+++ b/usr.bin/xargs/tests/regress.R-1.out
@@ -0,0 +1,4 @@
+The quick brown quick brown quick brown quick brownquick brown quick brown quick brown
+The fox jumped fox jumped fox jumped fox jumpedfox jumped fox jumped fox jumped
+The over the lazy over the lazy over the lazy over the lazyover the lazy over the lazy over the lazy
+The dog dog dog dogdog dog dog
diff --git a/usr.bin/xargs/tests/regress.R.out b/usr.bin/xargs/tests/regress.R.out
new file mode 100644
index 000000000000..6fe947f0d9e4
--- /dev/null
+++ b/usr.bin/xargs/tests/regress.R.out
@@ -0,0 +1,4 @@
+The quick brown % % %% % %
+The fox jumped % % %% % %
+The over the lazy % % %% % %
+The dog % % %% % %
diff --git a/usr.bin/xargs/tests/regress.in b/usr.bin/xargs/tests/regress.in
new file mode 100644
index 000000000000..5252b480a520
--- /dev/null
+++ b/usr.bin/xargs/tests/regress.in
@@ -0,0 +1,4 @@
+quick brown
+fox jumped
+over the lazy
+dog
diff --git a/usr.bin/xargs/tests/regress.n1.out b/usr.bin/xargs/tests/regress.n1.out
new file mode 100644
index 000000000000..77ef6c555c2d
--- /dev/null
+++ b/usr.bin/xargs/tests/regress.n1.out
@@ -0,0 +1,8 @@
+quick
+brown
+fox
+jumped
+over
+the
+lazy
+dog
diff --git a/usr.bin/xargs/tests/regress.n2.out b/usr.bin/xargs/tests/regress.n2.out
new file mode 100644
index 000000000000..4fa3f55161d6
--- /dev/null
+++ b/usr.bin/xargs/tests/regress.n2.out
@@ -0,0 +1,4 @@
+quick brown
+fox jumped
+over the
+lazy dog
diff --git a/usr.bin/xargs/tests/regress.n2P0.out b/usr.bin/xargs/tests/regress.n2P0.out
new file mode 100644
index 000000000000..de062b213dd5
--- /dev/null
+++ b/usr.bin/xargs/tests/regress.n2P0.out
@@ -0,0 +1,4 @@
+fox jumped
+lazy dog
+over the
+quick brown
diff --git a/usr.bin/xargs/tests/regress.n3.out b/usr.bin/xargs/tests/regress.n3.out
new file mode 100644
index 000000000000..21b2c1e97416
--- /dev/null
+++ b/usr.bin/xargs/tests/regress.n3.out
@@ -0,0 +1,3 @@
+quick brown fox
+jumped over the
+lazy dog
diff --git a/usr.bin/xargs/tests/regress.nargmax.out b/usr.bin/xargs/tests/regress.nargmax.out
new file mode 100644
index 000000000000..cc32a92a2199
--- /dev/null
+++ b/usr.bin/xargs/tests/regress.nargmax.out
@@ -0,0 +1 @@
+quick brown fox jumped over the lazy dog
diff --git a/usr.bin/xargs/tests/regress.normal.out b/usr.bin/xargs/tests/regress.normal.out
new file mode 100644
index 000000000000..458d9cb930c4
--- /dev/null
+++ b/usr.bin/xargs/tests/regress.normal.out
@@ -0,0 +1 @@
+The quick brown fox jumped over the lazy dog
diff --git a/usr.bin/xargs/tests/regress.quotes.in b/usr.bin/xargs/tests/regress.quotes.in
new file mode 100644
index 000000000000..11388a05b046
--- /dev/null
+++ b/usr.bin/xargs/tests/regress.quotes.in
@@ -0,0 +1,4 @@
+a 'b "c' \'d
+e\ f "g ' h"
+i\
+j
diff --git a/usr.bin/xargs/tests/regress.quotes.out b/usr.bin/xargs/tests/regress.quotes.out
new file mode 100644
index 000000000000..f79ad41ad0fd
--- /dev/null
+++ b/usr.bin/xargs/tests/regress.quotes.out
@@ -0,0 +1,7 @@
+a
+b "c
+'d
+e f
+g ' h
+i
+j
diff --git a/usr.bin/xargs/tests/xargs_test.sh b/usr.bin/xargs/tests/xargs_test.sh
new file mode 100755
index 000000000000..12c9407a7e45
--- /dev/null
+++ b/usr.bin/xargs/tests/xargs_test.sh
@@ -0,0 +1,193 @@
+#
+# Copyright (c) 2002 Juli Mallett <jmallett@FreeBSD.org>
+# Copyright (c) 2025 Dag-Erling Smørgrav <des@FreeBSD.org>
+#
+# SPDX-License-Identifier: BSD-2-Clause
+#
+
+SRCDIR=$(atf_get_srcdir)
+
+atf_test_case xargs_normal
+xargs_normal_body()
+{
+ atf_check -o file:${SRCDIR}/regress.normal.out \
+ xargs echo The <${SRCDIR}/regress.in
+}
+
+atf_test_case xargs_I
+xargs_I_body()
+{
+ atf_check -o file:${SRCDIR}/regress.I.out \
+ xargs -I% echo The % % % %% % % <${SRCDIR}/regress.in
+}
+
+atf_test_case xargs_J
+xargs_J_body()
+{
+ atf_check -o file:${SRCDIR}/regress.J.out \
+ xargs -J% echo The % again. <${SRCDIR}/regress.in
+}
+
+atf_test_case xargs_L
+xargs_L_body()
+{
+ atf_check -o file:${SRCDIR}/regress.L.out \
+ xargs -L3 echo <${SRCDIR}/regress.in
+}
+
+atf_test_case xargs_P1
+xargs_P1_body()
+{
+ atf_check -o file:${SRCDIR}/regress.P1.out \
+ xargs -P1 echo <${SRCDIR}/regress.in
+}
+
+atf_test_case xargs_R
+xargs_R_body()
+{
+ atf_check -o file:${SRCDIR}/regress.R.out \
+ xargs -I% -R1 echo The % % % %% % % <${SRCDIR}/regress.in
+}
+
+atf_test_case xargs_R_1
+xargs_R_1_body()
+{
+ atf_check -o file:${SRCDIR}/regress.R-1.out \
+ xargs -I% -R-1 echo The % % % %% % % <${SRCDIR}/regress.in
+}
+
+atf_test_case xargs_n1
+xargs_n1_body()
+{
+ atf_check -o file:${SRCDIR}/regress.n1.out \
+ xargs -n1 echo <${SRCDIR}/regress.in
+}
+
+atf_test_case xargs_n2
+xargs_n2_body()
+{
+ atf_check -o file:${SRCDIR}/regress.n2.out \
+ xargs -n2 echo <${SRCDIR}/regress.in
+}
+
+atf_test_case xargs_nargmax
+xargs_nargmax_body()
+{
+ argmax=$(sysctl -n kern.argmax)
+ atf_check -o file:${SRCDIR}/regress.nargmax.out \
+ xargs -n$((argmax)) <${SRCDIR}/regress.in
+ atf_check -s exit:1 -e match:"too large" \
+ xargs -n$((argmax+1)) <${SRCDIR}/regress.in
+}
+
+atf_test_case xargs_n2P0
+xargs_n2P0_body()
+{
+ atf_check -o save:regress.out \
+ xargs -n2 -P0 echo <${SRCDIR}/regress.in
+ atf_check -o file:${SRCDIR}/regress.n2P0.out \
+ sort regress.out
+}
+
+atf_test_case xargs_n3
+xargs_n3_body()
+{
+ atf_check -o file:${SRCDIR}/regress.n3.out \
+ xargs -n3 echo <${SRCDIR}/regress.in
+}
+
+atf_test_case xargs_0
+xargs_0_body()
+{
+ atf_check -o file:${SRCDIR}/regress.0.out \
+ xargs -0 -n1 echo <${SRCDIR}/regress.0.in
+}
+
+atf_test_case xargs_0I
+xargs_0I_body()
+{
+ atf_check -o file:${SRCDIR}/regress.0I.out \
+ xargs -0 -I% echo The % %% % <${SRCDIR}/regress.0.in
+}
+
+atf_test_case xargs_0J
+xargs_0J_body()
+{
+ atf_check -o file:${SRCDIR}/regress.0J.out \
+ xargs -0 -J% echo The % again. <${SRCDIR}/regress.0.in
+}
+
+atf_test_case xargs_0L
+xargs_0L_body()
+{
+ atf_check -o file:${SRCDIR}/regress.0L.out \
+ xargs -0 -L2 echo <${SRCDIR}/regress.0.in
+}
+
+atf_test_case xargs_0P1
+xargs_0P1_body()
+{
+ atf_check -o file:${SRCDIR}/regress.0P1.out \
+ xargs -0 -P1 echo <${SRCDIR}/regress.0.in
+}
+
+atf_test_case xargs_quotes
+xargs_quotes_body()
+{
+ atf_check -o file:${SRCDIR}/regress.quotes.out \
+ xargs -n1 echo <${SRCDIR}/regress.quotes.in
+}
+
+atf_test_case xargs_parallel1
+xargs_parallel1_body()
+{
+ echo /var/empty /var/empty >input
+ atf_check xargs -n1 -P2 test -d <input
+}
+
+atf_test_case xargs_parallel2
+xargs_parallel2_body()
+{
+ echo /var/empty /var/empty/nodir >input
+ atf_check -s exit:1 xargs -n1 -P2 test -d <input
+}
+
+atf_test_case xargs_parallel3
+xargs_parallel3_body()
+{
+ echo /var/empty/nodir /var/empty >input
+ atf_check -s exit:1 xargs -n1 -P2 test -d <input
+}
+
+atf_test_case xargs_parallel4
+xargs_parallel4_body()
+{
+ echo /var/empty/nodir /var/empty/nodir >input
+ atf_check -s exit:1 xargs -n1 -P2 test -d <input
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case xargs_normal
+ atf_add_test_case xargs_I
+ atf_add_test_case xargs_J
+ atf_add_test_case xargs_L
+ atf_add_test_case xargs_P1
+ atf_add_test_case xargs_R
+ atf_add_test_case xargs_R_1
+ atf_add_test_case xargs_n1
+ atf_add_test_case xargs_n2
+ atf_add_test_case xargs_nargmax
+ atf_add_test_case xargs_n2P0
+ atf_add_test_case xargs_n3
+ atf_add_test_case xargs_0
+ atf_add_test_case xargs_0I
+ atf_add_test_case xargs_0J
+ atf_add_test_case xargs_0L
+ atf_add_test_case xargs_0P1
+ atf_add_test_case xargs_quotes
+ atf_add_test_case xargs_parallel1
+ atf_add_test_case xargs_parallel2
+ atf_add_test_case xargs_parallel3
+ atf_add_test_case xargs_parallel4
+}
diff --git a/usr.bin/xargs/xargs.1 b/usr.bin/xargs/xargs.1
new file mode 100644
index 000000000000..f72ca911037e
--- /dev/null
+++ b/usr.bin/xargs/xargs.1
@@ -0,0 +1,388 @@
+.\" Copyright (c) 1990, 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" John B. Roll Jr. and the Institute of Electrical and Electronics
+.\" Engineers, Inc.
+.\"
+.\" 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. 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.
+.\" $xMach: xargs.1,v 1.2 2002/02/23 05:23:37 tim Exp $
+.\"
+.Dd September 21, 2020
+.Dt XARGS 1
+.Os
+.Sh NAME
+.Nm xargs
+.Nd "construct argument list(s) and execute utility"
+.Sh SYNOPSIS
+.Nm
+.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
+.Oo
+.Fl n Ar number
+.Op Fl x
+.Oc
+.Op Fl P Ar maxprocs
+.Op Fl s Ar size
+.Op Ar utility Op Ar argument ...
+.Sh DESCRIPTION
+The
+.Nm
+utility reads space, tab, newline and end-of-file delimited strings
+from the standard input and executes
+.Ar utility
+with the strings as
+arguments.
+.Pp
+Any arguments specified on the command line are given to
+.Ar utility
+upon each invocation, followed by some number of the arguments read
+from the standard input of
+.Nm .
+This is repeated until standard input is exhausted.
+.Pp
+Spaces, tabs and newlines may be embedded in arguments using single
+(``\ '\ '')
+or double (``"'') quotes or backslashes (``\e'').
+Single quotes escape all non-single quote characters, excluding newlines,
+up to the matching single quote.
+Double quotes escape all non-double quote characters, excluding newlines,
+up to the matching double quote.
+Any single character, including newlines, may be escaped by a backslash.
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl 0 , Fl -null
+Change
+.Nm
+to expect NUL
+(``\\0'')
+characters as separators, instead of spaces and newlines.
+This is expected to be used in concert with the
+.Fl print0
+function in
+.Xr find 1 .
+.It Fl E Ar eofstr
+Use
+.Ar eofstr
+as a logical EOF marker.
+.It Fl I Ar replstr
+Execute
+.Ar utility
+for each input line, replacing one or more occurrences of
+.Ar replstr
+in up to
+.Ar replacements
+(or 5 if no
+.Fl R
+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
+.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
+.Ar replsize
+bytes.
+The size limit does not apply to arguments to
+.Ar utility
+which do not contain
+.Ar replstr ,
+and furthermore, no replacement will be done on
+.Ar utility
+itself.
+Implies
+.Fl x .
+.It Fl J Ar replstr
+If this option is specified,
+.Nm
+will use the data read from standard input to replace the first occurrence of
+.Ar replstr
+instead of appending that data after all other arguments.
+This option will not affect how many arguments will be read from input
+.Pq Fl n ,
+or the size of the command(s)
+.Nm
+will generate
+.Pq Fl s .
+The option just moves where those arguments will be placed in the command(s)
+that are executed.
+The
+.Ar replstr
+must show up as a distinct
+.Ar argument
+to
+.Nm .
+It will not be recognized if, for instance, it is in the middle of a
+quoted string.
+Furthermore, only the first occurrence of the
+.Ar replstr
+will be replaced.
+For example, the following command will copy the list of files and
+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
+.It Fl L Ar number
+Call
+.Ar utility
+for every
+.Ar number
+lines read.
+If EOF is reached and fewer lines have been read than
+.Ar number
+then
+.Ar utility
+will be called with the available lines.
+.It Fl n Ar number , Fl -max-args= Ns Ar number
+Set the maximum number of arguments taken from standard input for each
+invocation of
+.Ar utility .
+An invocation of
+.Ar utility
+will use less than
+.Ar number
+standard input arguments if the number of bytes accumulated (see the
+.Fl s
+option) exceeds the specified
+.Ar size
+or there are fewer than
+.Ar number
+arguments remaining for the last invocation of
+.Ar utility .
+The current default value for
+.Ar number
+is 5000.
+.It Fl o
+Reopen stdin as
+.Pa /dev/tty
+in the child process before executing the command.
+This is useful if you want
+.Nm
+to run an interactive application.
+.It Fl P Ar maxprocs , Fl -max-procs= Ns Ar maxprocs
+Parallel mode: run at most
+.Ar maxprocs
+invocations of
+.Ar utility
+at once.
+If
+.Ar maxprocs
+is set to 0,
+.Nm
+will run as many processes as possible.
+.It Fl p , Fl -interactive
+Echo each command to be executed and ask the user whether it should be
+executed.
+An affirmative response,
+.Ql y
+in the POSIX locale,
+causes the command to be executed, any other response causes it to be
+skipped.
+No commands are executed if the process is not attached to a terminal.
+.It Fl r , Fl -no-run-if-empty
+Compatibility with GNU
+.Nm .
+The GNU version of
+.Nm
+runs the
+.Ar utility
+argument at least once, even if
+.Nm
+input is empty, and it supports a
+.Fl r
+option to inhibit this behavior.
+The
+.Fx
+version of
+.Nm
+does not run the
+.Ar utility
+argument on empty input, but it supports the
+.Fl r
+option for command-line compatibility with GNU
+.Nm ,
+but the
+.Fl r
+option does nothing in the
+.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 , Fl -max-chars= Ns Ar size
+Set the maximum number of bytes for the command line length provided to
+.Ar utility .
+The sum of the length of the utility name, the arguments passed to
+.Ar utility
+(including
+.Dv NULL
+terminators) and the current environment will be less than or equal to
+this number.
+The current default value for
+.Ar size
+is
+.Dv ARG_MAX
+- 4096.
+.It Fl t , Fl -verbose
+Echo the command to be executed to standard error immediately before it
+is executed.
+.It Fl x , Fl -exit
+Force
+.Nm
+to terminate immediately if a command line containing
+.Ar number
+arguments will not fit in the specified (or default) command line length.
+.El
+.Pp
+If
+.Ar utility
+is omitted,
+.Xr echo 1
+is used.
+.Pp
+Undefined behavior may occur if
+.Ar utility
+reads from the standard input.
+.Pp
+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, the
+.Nm
+utility stops processing input and exits after all invocations of
+.Ar utility
+finish processing.
+.Sh EXIT STATUS
+The
+.Nm
+utility exits with a value of 0 if no error occurs.
+If
+.Ar utility
+cannot be found,
+.Nm
+exits with a value of 127, otherwise if
+.Ar utility
+cannot be executed,
+.Nm
+exits with a value of 126.
+If any other error occurs,
+.Nm
+exits with a value of 1.
+.Sh EXAMPLES
+Create a 3x3 matrix with numbers from 1 to 9.
+Every
+.Xr echo 1
+instance receives three lines as arguments:
+.Bd -literal -offset indent
+$ seq 1 9 | xargs -L3 echo
+1 2 3
+4 5 6
+7 8 9
+.Ed
+.Pp
+Duplicate every line from standard input:
+.Bd -literal -offset indent
+$ echo -e "one\\ntwo\\nthree" | xargs -I % echo % %
+one one
+two two
+three three
+.Ed
+.Pp
+Execute at most 2 concurrent instances of
+.Xr find 1
+every one of them using one of the directories from the standard input:
+.Bd -literal -offset indent
+echo -e "/usr/ports\\n/etc\\n/usr/local" | xargs -J % -P2 -n1 find % -name file
+.Ed
+.Sh SEE ALSO
+.Xr echo 1 ,
+.Xr find 1 ,
+.Xr execvp 3
+.Sh STANDARDS
+The
+.Nm
+utility is expected to be
+.St -p1003.2
+compliant.
+The
+.Fl J , o , P , R
+and
+.Fl S
+options are non-standard
+.Fx
+extensions which may not be available on other operating systems.
+.Sh HISTORY
+The
+.Nm
+utility appeared in PWB UNIX.
+.Sh BUGS
+If
+.Ar utility
+attempts to invoke another command such that the number of arguments or the
+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.
diff --git a/usr.bin/xargs/xargs.c b/usr.bin/xargs/xargs.c
new file mode 100644
index 000000000000..2a7f026e5066
--- /dev/null
+++ b/usr.bin/xargs/xargs.c
@@ -0,0 +1,803 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * John B. Roll Jr.
+ *
+ * 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. 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.
+ *
+ * $xMach: xargs.c,v 1.6 2002/02/23 05:27:47 tim Exp $
+ */
+
+#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>
+#include <getopt.h>
+#include <langinfo.h>
+#include <locale.h>
+#include <paths.h>
+#include <regex.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "pathnames.h"
+
+static void parse_input(int, char *[]);
+static void prerun(int, char *[]);
+static int prompt(void);
+static void run(char **);
+static void usage(void);
+bool 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, **endxp, **xp;
+static char *argp, *bbp, *ebp, *inpline, *p, *replstr;
+static const char *eofstr;
+static long eoflen;
+static int count, insingle, indouble, oflag, pflag, tflag, Rflag, rval, zflag;
+static int cnt, Iflag, jfound, Lflag, Sflag, wasquoted, xflag;
+static int curprocs, maxprocs;
+static pid_t *childpids;
+
+static volatile int childerr;
+
+extern char **environ;
+
+static const char *optstr = "+0E:I:J:L:n:oP:pR:S:s:rtx";
+
+static const struct option long_options[] =
+{
+ {"exit", no_argument, NULL, 'x'},
+ {"interactive", no_argument, NULL, 'p'},
+ {"max-args", required_argument, NULL, 'n'},
+ {"max-chars", required_argument, NULL, 's'},
+ {"max-procs", required_argument, NULL, 'P'},
+ {"no-run-if-empty", no_argument, NULL, 'r'},
+ {"null", no_argument, NULL, '0'},
+ {"verbose", no_argument, NULL, 't'},
+
+ {NULL, no_argument, NULL, 0},
+};
+
+int
+main(int argc, char *argv[])
+{
+ long arg_max;
+ int ch, Jflag, nargs, nflag, nline;
+ size_t linelen;
+ struct rlimit rl;
+ const char *errstr;
+
+ inpline = replstr = NULL;
+ ep = environ;
+ eofstr = "";
+ eoflen = 0;
+ Jflag = nflag = 0;
+
+ (void)setlocale(LC_ALL, "");
+
+ /*
+ * 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;
+ 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);
+ }
+ maxprocs = 1;
+ while ((ch = getopt_long(argc, argv, optstr, long_options, NULL)) != -1)
+ switch (ch) {
+ case 'E':
+ eofstr = optarg;
+ eoflen = strlen(eofstr);
+ break;
+ case 'I':
+ Jflag = 0;
+ Iflag = 1;
+ Lflag = 1;
+ replstr = optarg;
+ break;
+ case 'J':
+ Iflag = 0;
+ Jflag = 1;
+ replstr = optarg;
+ break;
+ case 'L':
+ Lflag = (int)strtonum(optarg, 1, INT_MAX, &errstr);
+ if (errstr)
+ errx(1, "-%c %s: %s", ch, optarg, errstr);
+ break;
+ case 'n':
+ nflag = 1;
+ nargs = (int)strtonum(optarg, 1, arg_max, &errstr);
+ if (errstr)
+ errx(1, "-%c %s: %s", ch, optarg, errstr);
+ break;
+ case 'o':
+ oflag = 1;
+ break;
+ case 'P':
+ maxprocs = (int)strtonum(optarg, 0, INT_MAX, &errstr);
+ if (errstr)
+ errx(1, "-%c %s: %s", ch, 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':
+ pflag = 1;
+ break;
+ case 'R':
+ Rflag = (int)strtonum(optarg, INT_MIN, INT_MAX, &errstr);
+ if (errstr)
+ errx(1, "-%c %s: %s", ch, optarg, errstr);
+ if (!Rflag)
+ errx(1, "-%c %s: %s", ch, optarg, "must be non-zero");
+ break;
+ case 'r':
+ /* GNU compatibility */
+ break;
+ case 'S':
+ Sflag = (int)strtonum(optarg, 0, INT_MAX, &errstr);
+ if (errstr)
+ errx(1, "-%c %s: %s", ch, optarg, errstr);
+ break;
+ case 's':
+ nline = (int)strtonum(optarg, 0, INT_MAX, &errstr);
+ if (errstr)
+ errx(1, "-%c %s: %s", ch, optarg, errstr);
+ break;
+ case 't':
+ tflag = 1;
+ break;
+ case 'x':
+ xflag = 1;
+ break;
+ case '0':
+ zflag = 1;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ argc -= optind;
+ argv += optind;
+
+ 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)
+ xflag = 1;
+ 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 + (size_t)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.
+ */
+ if (*argv == NULL)
+ cnt = strlen(*bxp++ = echo);
+ else {
+ 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;
+ } while (*++argv != NULL);
+ }
+
+ /*
+ * 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.
+ */
+ endxp = (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)
+ errx(1, "insufficient space for command");
+
+ if ((bbp = malloc((size_t)(nline + 1))) == NULL)
+ errx(1, "malloc failed");
+ ebp = (argp = p = bbp) + nline - 1;
+ for (;;)
+ parse_input(argc, argv);
+}
+
+static void
+parse_input(int argc, char *argv[])
+{
+ int ch, foundeof;
+ char **avj;
+
+ foundeof = 0;
+
+ switch (ch = getchar()) {
+ case EOF:
+ /* No arguments since last exec. */
+ if (p == bbp) {
+ waitchildren(*av, 1);
+ exit(rval);
+ }
+ goto arg1;
+ case ' ':
+ case '\t':
+ /* Quotes escape tabs and spaces. */
+ if (insingle || indouble || zflag)
+ goto addch;
+ goto arg2;
+ case '\0':
+ 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':
+ if (zflag)
+ goto addch;
+ count++; /* Indicate end-of-line (used by -L) */
+
+ /* Quotes do not escape newlines. */
+arg1: if (insingle || indouble) {
+ warnx("unterminated quote");
+ xexit(*av, 1);
+ }
+arg2:
+ foundeof = eoflen != 0 && p - argp == eoflen &&
+ strncmp(argp, eofstr, eoflen) == 0;
+
+ /* Do not make empty args unless they are quoted */
+ if ((argp != p || wasquoted) && !foundeof) {
+ *p++ = '\0';
+ *xp++ = argp;
+ if (Iflag) {
+ size_t curlen;
+
+ if (inpline == NULL)
+ curlen = 0;
+ else {
+ /*
+ * If this string is not zero
+ * length, append a space for
+ * separation 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) {
+ warnx("realloc failed");
+ xexit(*av, 1);
+ }
+ if (curlen == 1)
+ strcpy(inpline, argp);
+ else
+ strcat(inpline, 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. Having reached the limit
+ * of input lines, as specified by -L is the same as
+ * maxing out on arguments.
+ */
+ if (xp == endxp || p > ebp || ch == EOF ||
+ (Lflag <= count && xflag) || foundeof) {
+ 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(*av, 1);
+ exit(rval);
+ }
+ p = bbp;
+ xp = bxp;
+ 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 '\\':
+ if (zflag)
+ goto addch;
+ /* Backslash escapes anything, is escaped by quotes. */
+ if (!insingle && !indouble && (ch = getchar()) == EOF) {
+ warnx("backslash at EOF");
+ xexit(*av, 1);
+ }
+ /* FALLTHROUGH */
+ default:
+addch: if (p < ebp) {
+ *p++ = ch;
+ break;
+ }
+
+ /* If only one argument, not enough buffer space. */
+ if (bxp == xp) {
+ warnx("insufficient space for argument");
+ xexit(*av, 1);
+ }
+ /* Didn't hit argument limit, so if xflag object. */
+ if (xflag) {
+ warnx("insufficient space for arguments");
+ xexit(*av, 1);
+ }
+
+ 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;
+ }
+}
+
+/*
+ * Do things necessary before run()'ing, such as -I substitution,
+ * and then call run().
+ */
+static void
+prerun(int argc, char *argv[])
+{
+ 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) {
+ 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) {
+ warnx("strdup failed");
+ xexit(*argv, 1);
+ }
+
+ /*
+ * 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 occurrence 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) {
+ if (strnsubst(tmp++, replstr, inpline, (size_t)Sflag)) {
+ warnx("command line cannot be assembled, too long");
+ xexit(*argv, 1);
+ }
+ if (repls > 0)
+ repls--;
+ } else {
+ if ((*tmp = strdup(*tmp)) == NULL) {
+ warnx("strdup failed");
+ xexit(*argv, 1);
+ }
+ 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)
+{
+ pid_t pid;
+ int fd;
+ char **avec;
+
+ /*
+ * 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);
+ 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);
+ }
+exec:
+ childerr = 0;
+ switch (pid = vfork()) {
+ case -1:
+ warn("vfork");
+ xexit(*argv, 1);
+ case 0:
+ if (oflag) {
+ if ((fd = open(_PATH_TTY, O_RDONLY)) == -1)
+ err(1, "can't open /dev/tty");
+ } else {
+ 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);
+ }
+ pids_add(pid);
+ waitchildren(*argv, 0);
+}
+
+/*
+ * 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 = xwait(waitall || pids_full(), &status)) > 0) {
+ /*
+ * 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 (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, "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;
+}
+
+/*
+ * Prompt the user about running a command.
+ */
+static int
+prompt(void)
+{
+ regex_t cre;
+ size_t rsize = 0;
+ int match;
+ char *response = NULL;
+ FILE *ttyfp;
+
+ if ((ttyfp = fopen(_PATH_TTY, "r")) == NULL)
+ return (2); /* Indicate that the TTY failed to open. */
+ (void)fprintf(stderr, "?...");
+ (void)fflush(stderr);
+ if (getline(&response, &rsize, ttyfp) < 0 ||
+ regcomp(&cre, nl_langinfo(YESEXPR), REG_EXTENDED) != 0) {
+ (void)fclose(ttyfp);
+ return (0);
+ }
+ match = regexec(&cre, response, 0, NULL, 0);
+ free(response);
+ (void)fclose(ttyfp);
+ regfree(&cre);
+ return (match == 0);
+}
+
+static void
+usage(void)
+{
+
+ fprintf(stderr,
+"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);
+}