From e18fd827840acbc6770d8300ef0b187ed9adf119 Mon Sep 17 00:00:00 2001 From: Elliott Hughes Date: Wed, 5 Nov 2025 11:50:17 -0500 Subject: [PATCH] xargs: add --process-slot-var. This also requires that we properly track child pids, so we can map between pids and slots. --- tests/xargs.test | 2 ++ toys/posix/xargs.c | 40 +++++++++++++++++++++++++++++----------- 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/tests/xargs.test b/tests/xargs.test index f4875997..c5f4edad 100644 --- a/tests/xargs.test +++ b/tests/xargs.test @@ -82,6 +82,8 @@ testing "parallel sleep" " B=\`xargs_sleep -P 2\` && C=\`xargs_sleep -P 0\` && [ \${A} -gt \${B} -a \${B} -gt \${C} ] && echo OK || echo FAIL" "OK\n" "" "" +testing "--process-slot-var" "xargs -n 1 -P 1 --process-slot-var=V printenv V"\ + "0\n0\n0\n" "" "one\ntwo\nthree\n" # TODO: what exactly is -x supposed to do? why does coreutils output "one"? #testing "-x" "xargs -x -s 9 || echo expected" "one\nexpected\n" "" "one\ntwo\nthree" diff --git a/toys/posix/xargs.c b/toys/posix/xargs.c index 6b65c744..ac8aa5b4 100644 --- a/toys/posix/xargs.c +++ b/toys/posix/xargs.c @@ -9,7 +9,7 @@ * TODO: -L Max number of lines of input per command * TODO: -x Exit if can't fit everything in one command -USE_XARGS(NEWTOY(xargs, "^a:E:P#<0(null)=1optr(no-run-if-empty)n#<1(max-args)s#0[!0E]", TOYFLAG_USR|TOYFLAG_BIN)) +USE_XARGS(NEWTOY(xargs, "^(process-slot-var):a:E:P#<0(null)=1optr(no-run-if-empty)n#<1(max-args)s#0[!0E]", TOYFLAG_USR|TOYFLAG_BIN)) config XARGS bool "xargs" @@ -31,6 +31,8 @@ config XARGS -r Don't run with empty input (otherwise always run command once) -s Size in bytes per command line -t Trace, print command line to stderr + + --process-slot-var NAME Set environment variable NAME in children */ #define FOR_xargs @@ -38,11 +40,12 @@ config XARGS GLOBALS( long s, n, P; - char *E, *a; + char *E, *a, *process_slot_var; long entries, bytes, np; char delim; FILE *tty; + pid_t *pids; ) // If !entry count TT.bytes and TT.entries, stopping at max. @@ -101,23 +104,31 @@ static void signal_P(int sig) static void waitchild(int options) { - int ii, status; + int pid, i, status; + + if ((pid = waitpid(-1, &status, options)) <= 0) return; - if (1>waitpid(-1, &status, options)) return; TT.np--; - ii = WIFEXITED(status) ? WEXITSTATUS(status) : WTERMSIG(status)+128; - if (ii == 255) { + for (i = 0; i < TT.P; ++i) { + if (TT.pids[i] == pid) { + TT.pids[i] = 0; + break; + } + } + + i = WIFEXITED(status) ? WEXITSTATUS(status) : WTERMSIG(status)+128; + if (i == 255) { error_msg("%s: exited with status 255; aborting", *toys.optargs); toys.exitval = 124; - } else if ((ii|1)==127) toys.exitval = ii; - else if (ii>127) toys.exitval = 125; - else if (ii) toys.exitval = 123; + } else if ((i|1)==127) toys.exitval = i; + else if (i>127) toys.exitval = 125; + else if (i) toys.exitval = 123; } void xargs_main(void) { struct double_list *dlist = 0, *dtemp; - int entries, bytes, done = 0; + int entries, bytes, slot, done = 0; char *data = 0, **out = 0; pid_t pid = 0; FILE *args_fp = TT.a ? xfopen(TT.a, "re") : stdin; @@ -146,6 +157,8 @@ void xargs_main(void) bytes += strlen(toys.optargs[entries])+1+sizeof(char *)*!FLAG(s); if (bytes >= TT.s) error_exit("command too long"); + if (TT.P) TT.pids = xzalloc(sizeof(pid_t) * TT.P); + // Loop through exec chunks. while (data || !done) { TT.entries = 0; @@ -206,12 +219,17 @@ void xargs_main(void) } else fprintf(stderr, "\n"); } + TT.np++; + for (slot = 0; slot < TT.P && TT.pids[slot]; ++slot); + if (!(pid = XVFORK())) { if (!TT.a) close(0); + if (TT.process_slot_var) + xsetenv(xmprintf("%s=%d", TT.process_slot_var, slot), 0); xopen_stdio(FLAG(o) ? "/dev/tty" : "/dev/null", O_RDONLY|O_CLOEXEC); xexec(out); } - TT.np++; + if (TT.pids) TT.pids[slot] = pid; } while (TT.np) waitchild(0); if (TT.tty) fclose(TT.tty); -- 2.39.5