#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, "");
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) {
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':
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();
linelen = 1 + argc + (size_t)nargs + 1;
if ((av = bxp = malloc(linelen * sizeof(char *))) == NULL)
errx(1, "malloc failed");
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);
}
endxp = (xp = bxp) + nargs;
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:
if (p == bbp) {
waitchildren(*av, 1);
exit(rval);
}
goto arg1;
case ' ':
case '\t':
if (insingle || indouble || zflag)
goto addch;
goto arg2;
case '\0':
if (zflag) {
count++;
goto arg2;
}
goto addch;
case '\n':
if (zflag)
goto addch;
count++;
arg1: if (insingle || indouble) {
warnx("unterminated quote");
xexit(*av, 1);
}
arg2:
foundeof = eoflen != 0 && p - argp == eoflen &&
strncmp(argp, eofstr, eoflen) == 0;
if ((argp != p || wasquoted) && !foundeof) {
*p++ = '\0';
*xp++ = argp;
if (Iflag) {
size_t curlen;
if (inpline == NULL)
curlen = 0;
else {
if ((curlen = strlen(inpline)))
strcat(inpline, " ");
}
curlen++;
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 (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;
if (!insingle && !indouble && (ch = getchar()) == EOF) {
warnx("backslash at EOF");
xexit(*av, 1);
}
default:
addch: if (p < ebp) {
*p++ = ch;
break;
}
if (bxp == xp) {
warnx("insufficient space for argument");
xexit(*av, 1);
}
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;
}
}
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;
tmp = malloc((argc + 1) * sizeof(char *));
if (tmp == NULL) {
warnx("malloc failed");
xexit(*argv, 1);
}
tmp2 = tmp;
if ((*tmp++ = strdup(*avj++)) == NULL) {
warnx("strdup failed");
xexit(*argv, 1);
}
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++;
}
}
*tmp = NULL;
run(tmp2);
for (; tmp2 != tmp; tmp--)
free(*tmp);
free(tmp2);
if (inpline != NULL) {
free(inpline);
inpline = NULL;
}
}
static void
run(char **argv)
{
pid_t pid;
int fd;
char **avec;
if (tflag || pflag) {
(void)fprintf(stderr, "%s", *argv);
for (avec = argv + 1; *avec != NULL; ++avec)
(void)fprintf(stderr, " %s", *avec);
if (pflag)
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);
}
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 (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;
}
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);
(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);
}