#include <sys/wait.h>
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <paths.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <limits.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);
void strnsubst(char **, const char *, const char *, size_t);
static void waitchildren(const char *, int);
static char **av, **bxp, **ep, **endxp, **xp;
static char *argp, *bbp, *ebp, *inpline, *p, *replstr;
static const char *eofstr;
static int count, insingle, indouble, oflag, pflag, tflag, Rflag, rval, zflag;
static int cnt, Iflag, jfound, Lflag, wasquoted, xflag, runeof = 1;
static int curprocs, maxprocs;
static size_t inpsize;
extern char **environ;
int
main(int argc, char *argv[])
{
long arg_max;
int ch, Jflag, nargs, nflag, nline;
size_t linelen;
char *endptr;
const char *errstr;
inpline = replstr = NULL;
ep = environ;
eofstr = "";
Jflag = nflag = 0;
nargs = 5000;
if ((arg_max = sysconf(_SC_ARG_MAX)) == -1)
errx(1, "sysconf(_SC_ARG_MAX) failed");
if (pledge("stdio rpath proc exec", NULL) == -1)
err(1, "pledge");
nline = arg_max - 4 * 1024;
while (*ep != NULL) {
nline -= strlen(*ep++) + 1 + sizeof(*ep);
}
maxprocs = 1;
while ((ch = getopt(argc, argv, "0E:I:J:L:n:oP:pR:rs:tx")) != -1)
switch (ch) {
case 'E':
eofstr = optarg;
break;
case 'I':
Jflag = 0;
Iflag = 1;
Lflag = 1;
replstr = optarg;
break;
case 'J':
Iflag = 0;
Jflag = 1;
replstr = optarg;
break;
case 'L':
Lflag = strtonum(optarg, 0, INT_MAX, &errstr);
if (errstr)
errx(1, "-L %s: %s", optarg, errstr);
break;
case 'n':
nflag = 1;
nargs = strtonum(optarg, 1, INT_MAX, &errstr);
if (errstr)
errx(1, "-n %s: %s", optarg, errstr);
break;
case 'o':
oflag = 1;
break;
case 'P':
maxprocs = strtonum(optarg, 1, INT_MAX, &errstr);
if (errstr)
errx(1, "-P %s: %s", optarg, errstr);
break;
case 'p':
pflag = 1;
break;
case 'r':
runeof = 0;
break;
case 'R':
Rflag = strtol(optarg, &endptr, 10);
if (*endptr != '\0')
errx(1, "replacements must be a number");
break;
case 's':
nline = strtonum(optarg, 0, INT_MAX, &errstr);
if (errstr)
errx(1, "-s %s: %s", optarg, errstr);
break;
case 't':
tflag = 1;
break;
case 'x':
xflag = 1;
break;
case '0':
zflag = 1;
break;
default:
usage();
}
argc -= optind;
argv += optind;
if (!Iflag && Rflag)
usage();
if (Iflag && !Rflag)
Rflag = 5;
if (xflag && !nflag)
usage();
if (Iflag || Lflag)
xflag = 1;
if (replstr != NULL && *replstr == '\0')
errx(1, "replstr may not be empty");
linelen = 1 + argc + nargs + 1;
if ((av = bxp = calloc(linelen, sizeof(char *))) == NULL)
err(1, NULL);
if (*argv == NULL)
cnt = strlen(*bxp++ = _PATH_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)
err(1, NULL);
ebp = (argp = p = bbp) + nline - 1;
for (;;)
parse_input(argc, argv);
}
static void
parse_input(int argc, char *argv[])
{
int hasblank = 0;
static int hadblank = 0;
int ch, foundeof = 0;
char **avj;
ch = getchar();
if (isblank(ch)) {
if (insingle || indouble)
goto addch;
hasblank = 1;
if (zflag)
goto addch;
goto arg2;
}
switch (ch) {
case EOF:
if (p == bbp) {
if (runeof)
prerun(0, av);
waitchildren(*argv, 1);
exit(rval);
}
goto arg1;
case '\0':
if (zflag) {
count++;
goto arg3;
}
goto addch;
case '\n':
if (zflag)
goto addch;
hasblank = 1;
if (hadblank == 0)
count++;
arg1: if (insingle || indouble)
errx(1, "unterminated quote");
arg2:
foundeof = *eofstr != '\0' &&
strcmp(argp, eofstr) == 0;
arg3:
if (((zflag && ch != EOF) || argp != p || wasquoted) &&
!foundeof) {
*p++ = '\0';
*xp++ = argp;
if (Iflag) {
size_t curlen;
if (inpline == NULL)
curlen = 0;
else {
if ((curlen = strlen(inpline)))
strlcat(inpline, " ", inpsize);
}
curlen++;
inpsize = curlen + 2 + strlen(argp);
inpline = realloc(inpline, inpsize);
if (inpline == NULL)
errx(1, "realloc failed");
if (curlen == 1)
strlcpy(inpline, argp, inpsize);
else
strlcat(inpline, argp, inpsize);
}
}
if (xp == endxp || p > ebp || ch == EOF ||
(Lflag <= count && xflag) || foundeof) {
if (xflag && xp != endxp && p > ebp)
errx(1, "insufficient space for arguments");
if (jfound) {
for (avj = argv; *avj; avj++)
*xp++ = *avj;
}
prerun(argc, av);
if (ch == EOF || foundeof) {
waitchildren(*argv, 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)
errx(1, "backslash at EOF");
default:
addch: if (p < ebp) {
*p++ = ch;
break;
}
if (bxp == xp)
errx(1, "insufficient space for argument");
if (xflag)
errx(1, "insufficient space for arguments");
if (jfound) {
for (avj = argv; *avj; avj++)
*xp++ = *avj;
}
prerun(argc, av);
xp = bxp;
cnt = ebp - argp;
memmove(bbp, argp, (size_t)cnt);
p = (argp = bbp) + cnt;
*p++ = ch;
break;
}
hadblank = hasblank;
}
static void
prerun(int argc, char *argv[])
{
char **tmp, **tmp2, **avj;
int repls;
repls = Rflag;
runeof = 0;
if (argc == 0 || repls == 0) {
*xp = NULL;
run(argv);
return;
}
avj = argv;
tmp = calloc(argc + 1, sizeof(char *));
if (tmp == NULL)
err(1, NULL);
tmp2 = tmp;
if ((*tmp++ = strdup(*avj++)) == NULL)
err(1, NULL);
while (--argc) {
*tmp = *avj++;
if (repls && strstr(*tmp, replstr) != NULL) {
strnsubst(tmp++, replstr, inpline, (size_t)255);
if (repls > 0)
repls--;
} else {
if ((*tmp = strdup(*tmp)) == NULL)
err(1, NULL);
tmp++;
}
}
*tmp = NULL;
run(tmp2);
for (; tmp2 != tmp; tmp--)
free(*tmp);
free(tmp2);
free(inpline);
inpline = NULL;
}
static void
run(char **argv)
{
pid_t pid;
int fd;
char **avec;
if (tflag || pflag) {
fprintf(stderr, "%s", *argv);
for (avec = argv + 1; *avec != NULL; ++avec)
fprintf(stderr, " %s", *avec);
if (pflag)
switch (prompt()) {
case 0:
return;
case 1:
goto exec;
case 2:
break;
}
fprintf(stderr, "\n");
fflush(stderr);
}
exec:
switch (pid = vfork()) {
case -1:
err(1, "vfork");
case 0:
if (oflag) {
if ((fd = open(_PATH_TTY, O_RDONLY)) == -1) {
warn("can't open /dev/tty");
_exit(1);
}
} else {
fd = open(_PATH_DEVNULL, O_RDONLY);
}
if (fd > STDIN_FILENO) {
if (dup2(fd, STDIN_FILENO) != 0) {
warn("can't dup2 to stdin");
_exit(1);
}
close(fd);
}
execvp(argv[0], argv);
warn("%s", argv[0]);
_exit(errno == ENOENT ? 127 : 126);
}
curprocs++;
waitchildren(*argv, 0);
}
static void
waitchildren(const char *name, int waitall)
{
pid_t pid;
int status;
while ((pid = waitpid(-1, &status, !waitall && curprocs < maxprocs ?
WNOHANG : 0)) > 0) {
curprocs--;
if (WIFEXITED(status)) {
if (WEXITSTATUS(status) == 255) {
warnx("%s exited with status 255", name);
exit(124);
} else if (WEXITSTATUS(status) == 127 ||
WEXITSTATUS(status) == 126) {
exit(WEXITSTATUS(status));
} else if (WEXITSTATUS(status) != 0) {
rval = 123;
}
} else if (WIFSIGNALED(status)) {
if (WTERMSIG(status) != SIGPIPE) {
if (WTERMSIG(status) < NSIG)
warnx("%s terminated by SIG%s", name,
sys_signame[WTERMSIG(status)]);
else
warnx("%s terminated by signal %d",
name, WTERMSIG(status));
}
exit(125);
}
}
if (pid == -1 && errno != ECHILD)
err(1, "waitpid");
}
static int
prompt(void)
{
size_t rsize;
char *response;
FILE *ttyfp;
int doit = 0;
if ((ttyfp = fopen(_PATH_TTY, "r")) == NULL)
return (2);
fprintf(stderr, "?...");
fflush(stderr);
response = fgetln(ttyfp, &rsize);
doit = response != NULL && (*response == 'y' || *response == 'Y');
fclose(ttyfp);
return (doit);
}
static void
usage(void)
{
fprintf(stderr,
"usage: xargs [-0oprt] [-E eofstr] [-I replstr [-R replacements]] [-J replstr]\n"
" [-L number] [-n number [-x]] [-P maxprocs] [-s size]\n"
" [utility [argument ...]]\n");
exit(1);
}