| /* xargs.c - Run command with arguments taken from stdin. |
| * |
| * Copyright 2011 Rob Landley <rob@landley.net> |
| * |
| * See http://opengroup.org/onlinepubs/9699919799/utilities/xargs.html |
| * |
| * TODO: Rich's whitespace objection, env size isn't fixed anymore. |
| * TODO: -I Insert mode |
| * 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, "^E:P#<0=1optrn#<1(max-args)s#0[!0E]", TOYFLAG_USR|TOYFLAG_BIN)) |
| |
| config XARGS |
| bool "xargs" |
| default y |
| help |
| usage: xargs [-0prt] [-snE STR] COMMAND... |
| |
| Run command line one or more times, appending arguments from stdin. |
| |
| If COMMAND exits with 255, don't launch another even if arguments remain. |
| |
| -0 Each argument is NULL terminated, no whitespace or quote processing |
| -E Stop at line matching string |
| -n Max number of arguments per command |
| -o Open tty for COMMAND's stdin (default /dev/null) |
| -p Prompt for y/n from tty before running each command |
| -P Parallel processes (default 1) |
| -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 |
| */ |
| |
| #define FOR_xargs |
| #include "toys.h" |
| |
| GLOBALS( |
| long s, n, P; |
| char *E; |
| |
| long entries, bytes, np; |
| char delim; |
| FILE *tty; |
| ) |
| |
| // If !entry count TT.bytes and TT.entries, stopping at max. |
| // Otherwise, fill out entry[]. |
| |
| // Returning NULL means need more data. |
| // Returning char * means hit data limits, start of data left over |
| // Returning 1 means hit data limits, but consumed all data |
| // Returning 2 means hit -E STR |
| |
| static char *handle_entries(char *data, char **entry) |
| { |
| if (TT.delim) { |
| char *save, *ss, *s; |
| |
| // Chop up whitespace delimited string into args |
| for (s = data; *s; TT.entries++) { |
| while (isspace(*s)) s++; |
| if (TT.n && TT.entries >= TT.n) return *s ? s : (char *)1; |
| if (!*s) break; |
| save = ss = s; |
| |
| // Specifying -s can cause "argument too long" errors. |
| if (!FLAG(s)) TT.bytes += sizeof(void *)+1; |
| for (;;) { |
| if (++TT.bytes >= TT.s) return save; |
| if (!*s || isspace(*s)) break; |
| s++; |
| } |
| if (TT.E && strstart(&ss, TT.E) && ss == s) return (char *)2; |
| if (entry) { |
| entry[TT.entries] = save; |
| if (*s) *s++ = 0; |
| } |
| } |
| |
| // -0 support |
| } else { |
| long bytes = TT.bytes+sizeof(char *)+strlen(data)+1; |
| |
| if (bytes >= TT.s || (TT.n && TT.entries >= TT.n)) return data; |
| TT.bytes = bytes; |
| if (entry) entry[TT.entries] = data; |
| TT.entries++; |
| } |
| |
| return 0; |
| } |
| |
| // Handle SIGUSR1 and SIGUSR2 for -P |
| static void signal_P(int sig) |
| { |
| if (sig == SIGUSR2 && TT.P>1) TT.P--; |
| else TT.P++; |
| } |
| |
| void xargs_main(void) |
| { |
| struct double_list *dlist = 0, *dtemp; |
| int entries, bytes, done = 0, status; |
| char *data = 0, **out = 0; |
| pid_t pid = 0; |
| |
| xsignal_flags(SIGUSR1, signal_P, SA_RESTART); |
| xsignal_flags(SIGUSR2, signal_P, SA_RESTART); |
| |
| // POSIX requires that we never hit the ARG_MAX limit, even if we try to |
| // with -s. POSIX also says we have to reserve 2048 bytes "to guarantee |
| // that the invoked utility has room to modify its environment variables |
| // and command line arguments and still be able to invoke another utility", |
| // though obviously that's not really something you can guarantee. |
| if (!FLAG(s)) TT.s = sysconf(_SC_ARG_MAX) - environ_bytes() - 4096; |
| |
| TT.delim = '\n'*!FLAG(0); |
| |
| // If no optargs, call echo. |
| if (!toys.optc) { |
| free(toys.optargs); |
| *(toys.optargs = xzalloc(2*sizeof(char *)))="echo"; |
| toys.optc = 1; |
| } |
| |
| // count entries |
| for (entries = 0, bytes = -1; entries < toys.optc; entries++) |
| bytes += strlen(toys.optargs[entries])+1+sizeof(char *)*!FLAG(s); |
| if (bytes >= TT.s) error_exit("command too long"); |
| |
| // Loop through exec chunks. |
| while (data || !done) { |
| TT.entries = 0; |
| TT.bytes = bytes; |
| |
| // Loop reading input |
| for (;;) { |
| |
| // Read line |
| if (!data) { |
| size_t l = 0; |
| |
| if (getdelim(&data, &l, TT.delim, stdin)<0) { |
| data = 0; |
| done++; |
| break; |
| } |
| } |
| dlist_add(&dlist, data); |
| // Count data used |
| if (!(data = handle_entries(data, 0))) continue; |
| if (data == (char *)2) done++; |
| if ((unsigned long)data <= 2) data = 0; |
| else data = xstrdup(data); |
| |
| break; |
| } |
| |
| if (!TT.entries) { |
| if (data) error_exit("argument too long"); |
| if (pid || FLAG(r)) goto reap_children; |
| } |
| |
| // Fill out command line to exec |
| out = xzalloc((entries+TT.entries+1)*sizeof(char *)); |
| memcpy(out, toys.optargs, entries*sizeof(char *)); |
| TT.entries = 0; |
| TT.bytes = bytes; |
| if (dlist) dlist->prev->next = 0; |
| for (dtemp = dlist; dtemp; dtemp = dtemp->next) |
| handle_entries(dtemp->data, out+entries); |
| |
| if (FLAG(p) || FLAG(t)) { |
| int i; |
| |
| for (i = 0; out[i]; ++i) fprintf(stderr, "%s ", out[i]); |
| if (FLAG(p)) { |
| fprintf(stderr, "?"); |
| if (!TT.tty) TT.tty = xfopen("/dev/tty", "re"); |
| if (!fyesno(TT.tty, 0)) goto reap_children; |
| } else fprintf(stderr, "\n"); |
| } |
| |
| if (!(pid = XVFORK())) { |
| close(0); |
| xopen_stdio(FLAG(o) ? "/dev/tty" : "/dev/null", O_RDONLY); |
| xexec(out); |
| } |
| TT.np++; |
| |
| reap_children: |
| while (TT.np) { |
| int xv = (TT.np == TT.P) || (!data && done); |
| |
| if (1>(xv = waitpid(-1, &status, WNOHANG*!xv))) break; |
| TT.np--; |
| xv = WIFEXITED(status) ? WEXITSTATUS(status) : WTERMSIG(status)+128; |
| if (xv == 255) { |
| error_msg("%s: exited with status 255; aborting", *out); |
| toys.exitval = 124; |
| break; |
| } else if ((xv|1)==127) toys.exitval = xv; |
| else if (xv>127) xv = 125; |
| else if (xv) toys.exitval = 123; |
| } |
| |
| // Abritrary number of execs, can't just leak memory each time... |
| llist_traverse(dlist, llist_free_double); |
| dlist = 0; |
| free(out); |
| out = 0; |
| } |
| while (TT.np && -1 != wait(&status)) TT.np--; |
| if (TT.tty) fclose(TT.tty); |
| } |