/* See LICENSE file for copyright and license details. */ #include #include #include #include #include #include #include #include #include "util.h" #define NARGS 10000 static int inputc(void); static void fillargbuf(int); static int eatspace(void); static int parsequote(int); static int parseescape(void); static char *poparg(void); static void waitchld(void); static void spawn(void); static size_t argbsz; static size_t argbpos; static size_t maxargs = 0; static int nerrors = 0; static int rflag = 0, nflag = 0, tflag = 0, xflag = 0; static char *argb; static char *cmd[NARGS]; static char *eofstr; static int inputc(void) { int ch; ch = getc(stdin); if (ch == EOF && ferror(stdin)) eprintf("getc :"); return ch; } static void fillargbuf(int ch) { if (argbpos >= argbsz) { argbsz = argbpos == 0 ? 1 : argbsz * 2; argb = erealloc(argb, argbsz); } argb[argbpos] = ch; } static int eatspace(void) { int ch; while ((ch = inputc()) != EOF) { switch (ch) { case ' ': case '\t': case '\n': break; default: ungetc(ch, stdin); return ch; } } return -1; } static int parsequote(int q) { int ch; while ((ch = inputc()) != EOF) { if (ch == q) return 0; if (ch != '\n') { fillargbuf(ch); argbpos++; } } return -1; } static int parseescape(void) { int ch; if ((ch = inputc()) != EOF) { fillargbuf(ch); argbpos++; return ch; } return -1; } static char * poparg(void) { int ch; argbpos = 0; if (eatspace() < 0) return NULL; while ((ch = inputc()) != EOF) { switch (ch) { case ' ': case '\t': case '\n': goto out; case '\'': if (parsequote('\'') < 0) eprintf("unterminated single quote\n"); break; case '\"': if (parsequote('\"') < 0) eprintf("unterminated double quote\n"); break; case '\\': if (parseescape() < 0) eprintf("backslash at EOF\n"); break; default: fillargbuf(ch); argbpos++; break; } } out: fillargbuf('\0'); return (eofstr && !strcmp(argb, eofstr)) ? NULL : argb; } static void waitchld(void) { int status; wait(&status); if (WIFEXITED(status)) { if (WEXITSTATUS(status) == 255) exit(124); if (WEXITSTATUS(status) == 127 || WEXITSTATUS(status) == 126) exit(WEXITSTATUS(status)); if (status) nerrors++; } if (WIFSIGNALED(status)) exit(125); } static void spawn(void) { int savederrno; int first = 1; char **p; if (tflag) { for (p = cmd; *p; p++) { if (!first) fputc(' ', stderr); fputs(*p, stderr); first = 0; } fputc('\n', stderr); } switch (fork()) { case -1: eprintf("fork:"); case 0: execvp(*cmd, cmd); savederrno = errno; weprintf("execvp %s:", *cmd); _exit(126 + (savederrno == ENOENT)); } waitchld(); } static void usage(void) { eprintf("usage: %s [-rtx] [-E eofstr] [-n num] [-s num] " "[cmd [arg ...]]\n", argv0); } int main(int argc, char *argv[]) { int ret = 0, leftover = 0, i; size_t argsz, argmaxsz; size_t arglen, a; char *arg = ""; if ((argmaxsz = sysconf(_SC_ARG_MAX)) == (size_t)-1) argmaxsz = _POSIX_ARG_MAX; /* Leave some room for environment variables */ argmaxsz -= 4096; ARGBEGIN { case 'n': nflag = 1; maxargs = estrtonum(EARGF(usage()), 1, MIN(SIZE_MAX, LLONG_MAX)); break; case 'r': rflag = 1; break; case 's': argmaxsz = estrtonum(EARGF(usage()), 1, MIN(SIZE_MAX, LLONG_MAX)); break; case 't': tflag = 1; break; case 'x': xflag = 1; break; case 'E': eofstr = EARGF(usage()); break; default: usage(); } ARGEND do { argsz = 0; i = 0; a = 0; if (argc) { for (; i < argc; i++) { cmd[i] = estrdup(argv[i]); argsz += strlen(cmd[i]) + 1; } } else { cmd[i] = estrdup("/bin/echo"); argsz += strlen("/bin/echo") + 1; i++; } while (leftover || (arg = poparg())) { arglen = strlen(arg); if (argsz + arglen >= argmaxsz || i >= NARGS - 1) { if (arglen >= argmaxsz) { weprintf("insufficient argument space\n"); if (xflag) exit(1); } leftover = 1; break; } cmd[i] = estrdup(arg); argsz += arglen + 1; i++; a++; leftover = 0; if (nflag && a >= maxargs) break; } cmd[i] = NULL; if (a >= maxargs && nflag) spawn(); else if (!a || (i == 1 && rflag)) ; else spawn(); for (; i >= 0; i--) free(cmd[i]); } while (arg); free(argb); if (nerrors || (fshut(stdin, "") | fshut(stdout, ""))) ret = 123; return ret; }