1/* See LICENSE file for copyright and license details. */
2
3#include <sys/wait.h>
4
5#include <errno.h>
6#include <limits.h>
7#include <stdint.h>
8#include <stdio.h>
9#include <stdlib.h>
10#include <string.h>
11#include <unistd.h>
12
13#include "util.h"
14#include "wexec.h"
15
16#define NARGS 10000
17
18static int inputc(void);
19static void fillargbuf(int);
20static int eatspace(void);
21static int parsequote(int);
22static int parseescape(void);
23static char *poparg(void);
24static void waitchld(int);
25static void spawn(void);
26
27static size_t argbsz;
28static size_t argbpos;
29static size_t maxargs;
30static size_t curprocs, maxprocs = 1;
31static int nerrors;
32static int nulflag, nflag, pflag, rflag, tflag, xflag, Iflag, Lflag;
33static size_t maxlines;
34static int newline = 1;
35static int arg_newline;
36static char *argb;
37static char **cmd;
38static char *eofstr;
39
40static int
41inputc(void)
42{
43 int ch;
44
45 ch = getc(stdin);
46 if (ch == EOF && ferror(stdin))
47 eprintf("getc <stdin>:");
48 if (ch == '\n')
49 newline = 1;
50
51 return ch;
52}
53
54static void
55fillargbuf(int ch)
56{
57 if (argbpos >= argbsz) {
58 argbsz = argbpos == 0 ? 1 : argbsz * 2;
59 argb = erealloc(argb, argbsz);
60 }
61 argb[argbpos] = ch;
62}
63
64static int
65eatspace(void)
66{
67 int ch;
68
69 while ((ch = inputc()) != EOF) {
70 if (nulflag || !(ch == ' ' || ch == '\t' || ch == '\n')) {
71 ungetc(ch, stdin);
72 return ch;
73 }
74 }
75 return -1;
76}
77
78static int
79parsequote(int q)
80{
81 int ch;
82
83 while ((ch = inputc()) != EOF) {
84 if (ch == q)
85 return 0;
86 if (ch != '\n') {
87 fillargbuf(ch);
88 argbpos++;
89 }
90 }
91
92 return -1;
93}
94
95static int
96parseescape(void)
97{
98 int ch;
99
100 if ((ch = inputc()) != EOF) {
101 fillargbuf(ch);
102 argbpos++;
103 return ch;
104 }
105
106 return -1;
107}
108
109static char *
110poparg(void)
111{
112 int ch;
113
114 argbpos = 0;
115 if (eatspace() < 0)
116 return NULL;
117
118 arg_newline = newline;
119 newline = 0;
120 while ((ch = inputc()) != EOF) {
121 /* NUL separator: no escaping */
122 if (nulflag) {
123 if (ch == '\0')
124 goto out;
125 else
126 goto fill;
127 }
128
129 switch (ch) {
130 case ' ':
131 case '\t':
132 if (Iflag)
133 goto fill;
134 case '\n':
135 goto out;
136 case '\'':
137 if (parsequote('\'') < 0)
138 eprintf("unterminated single quote\n");
139 break;
140 case '\"':
141 if (parsequote('\"') < 0)
142 eprintf("unterminated double quote\n");
143 break;
144 case '\\':
145 if (parseescape() < 0)
146 eprintf("backslash at EOF\n");
147 break;
148 default:
149 fill:
150 fillargbuf(ch);
151 argbpos++;
152 break;
153 }
154 }
155out:
156 fillargbuf('\0');
157
158 return (eofstr && !strcmp(argb, eofstr)) ? NULL : argb;
159}
160
161static void
162waitchld(int waitall)
163{
164 pid_t pid;
165 int status;
166
167 while ((pid = waitpid(-1, &status, !waitall && curprocs < maxprocs ? WNOHANG : 0)) > 0) {
168 curprocs--;
169
170 if (WIFEXITED(status)) {
171 if (WEXITSTATUS(status) == 255)
172 exit(124);
173 if (WEXITSTATUS(status) == 127 || WEXITSTATUS(status) == 126)
174 exit(WEXITSTATUS(status));
175 if (WEXITSTATUS(status))
176 nerrors++;
177 }
178 if (WIFSIGNALED(status))
179 exit(125);
180 }
181 if (pid == -1 && errno != ECHILD)
182 eprintf("waitpid:");
183}
184
185static int
186prompt(void)
187{
188 FILE *fp;
189 int ch, ret;
190
191 if (!(fp = fopen("/dev/tty", "r")))
192 return -1;
193
194 fputs("?...", stderr);
195 fflush(stderr);
196
197 ch = fgetc(fp);
198 ret = (ch == 'y' || ch == 'Y');
199 if (ch != EOF && ch != '\n') {
200 while ((ch = fgetc(fp)) != EOF) {
201 if (ch == '\n')
202 break;
203 }
204 }
205
206 fclose(fp);
207
208 return ret;
209}
210
211static void
212spawn(void)
213{
214 int savederrno;
215 int first = 1;
216 char **p;
217
218 if (pflag || tflag) {
219 for (p = cmd; *p; p++) {
220 if (!first)
221 fputc(' ', stderr);
222 fputs(*p, stderr);
223 first = 0;
224 }
225 if (pflag) {
226 switch (prompt()) {
227 case -1:
228 break; /* error */
229 case 0:
230 return; /* no */
231 case 1:
232 goto dospawn; /* yes */
233 }
234 }
235 fputc('\n', stderr);
236 fflush(stderr);
237 }
238
239dospawn:
240 switch (fork()) {
241 case -1:
242 eprintf("fork:");
243 /* fallthrough */
244 case 0:
245 wexecvp_self(*cmd, cmd);
246 savederrno = errno;
247 weprintf("wexecvp %s:", *cmd);
248 _exit(126 + (savederrno == ENOENT));
249 }
250 curprocs++;
251 waitchld(0);
252}
253
254static void
255usage(void)
256{
257 eprintf(
258 "usage: %s [-0prtx] [-E eofstr] [-I replstr] [-L maxlines] [-n "
259 "num] [-P maxprocs] [-s num] "
260 "[cmd [arg ...]]\n",
261 argv0
262 );
263}
264
265// ?man xargs: build and run command lines
266// ?man arguments: -n
267// ?man execute commands built from standard input arguments
268int
269main(int argc, char *argv[])
270{
271 int ret = 0, leftover = 0, i, j;
272 size_t argsz, argmaxsz;
273 size_t arglen, a;
274 char *arg = "";
275 char *replstr;
276
277 if ((argmaxsz = sysconf(_SC_ARG_MAX)) == (size_t)-1)
278 argmaxsz = _POSIX_ARG_MAX;
279 /* Leave some room for environment variables */
280 argmaxsz -= 4096;
281 cmd = emalloc(NARGS * sizeof(*cmd));
282
283 ARGBEGIN
284 {
285 // ?man -0: specify option flag
286 case '0':
287 nulflag = 1;
288 break;
289 // ?man -n:num: print line numbers or counts
290 case 'n':
291 nflag = 1;
292 maxargs = estrtonum(
293 EARGF(usage()), 1, MIN((unsigned long long)SIZE_MAX, (unsigned long long)LLONG_MAX)
294 );
295 break;
296 // ?man -p: preserve file attributes
297 case 'p':
298 pflag = 1;
299 break;
300 // ?man -r: operate recursively
301 case 'r':
302 rflag = 1;
303 break;
304 // ?man -s:num: silent mode or print summary
305 case 's':
306 argmaxsz = estrtonum(
307 EARGF(usage()), 1, MIN((unsigned long long)SIZE_MAX, (unsigned long long)LLONG_MAX)
308 );
309 break;
310 // ?man -t: sort or specify timestamp
311 case 't':
312 tflag = 1;
313 break;
314 // ?man -x: hex format or match whole lines
315 case 'x':
316 xflag = 1;
317 break;
318 // ?man -E:str: specify option flag
319 case 'E':
320 eofstr = EARGF(usage());
321 break;
322 // ?man -I:str: specify option flag
323 case 'I':
324 Iflag = 1;
325 xflag = 1;
326 nflag = 1;
327 maxargs = 1;
328 replstr = EARGF(usage());
329 break;
330 // ?man -L:num: specify option flag
331 case 'L':
332 Lflag = 1;
333 maxlines = estrtonum(
334 EARGF(usage()), 1, MIN((unsigned long long)SIZE_MAX, (unsigned long long)LLONG_MAX)
335 );
336 break;
337 // ?man -P:num: specify option flag
338 case 'P':
339 maxprocs = estrtonum(
340 EARGF(usage()), 1, MIN((unsigned long long)SIZE_MAX, (unsigned long long)LLONG_MAX)
341 );
342 break;
343 default:
344 usage();
345 }
346 ARGEND
347
348 do {
349 size_t linecount = 0;
350 argsz = 0;
351 i = 0;
352 a = 0;
353 if (argc) {
354 for (; i < argc; i++) {
355 cmd[i] = estrdup(argv[i]);
356 argsz += strlen(cmd[i]) + 1;
357 }
358 } else {
359 cmd[i] = estrdup("/bin/echo");
360 argsz += strlen("/bin/echo") + 1;
361 i++;
362 }
363 while (leftover || (arg = poparg())) {
364 if (arg) {
365 if (linecount == 0) {
366 linecount = 1;
367 } else if (arg_newline) {
368 linecount++;
369 arg_newline = 0;
370 }
371 if (Lflag && linecount > maxlines) {
372 leftover = 1;
373 break;
374 }
375 }
376 arglen = strlen(arg);
377 if (argsz + arglen >= argmaxsz || i >= NARGS - 1) {
378 if (xflag || arglen >= argmaxsz || leftover)
379 eprintf(
380 "insufficient argument "
381 "space\n"
382 );
383 leftover = 1;
384 break;
385 }
386
387 if (!Iflag) {
388 cmd[i] = estrdup(arg);
389 argsz += arglen + 1;
390 } else {
391 for (j = 1; j < i; j++) {
392 char *p = cmd[j];
393 argsz -= strlen(cmd[j]);
394 strnsubst(&cmd[j], replstr, arg, 255);
395 argsz += strlen(cmd[j]);
396 free(p);
397 }
398 }
399
400 i++;
401 a++;
402 leftover = 0;
403 if (nflag && a >= maxargs)
404 break;
405 }
406 cmd[i] = NULL;
407 if (a >= maxargs && nflag)
408 spawn();
409 else if (Lflag && linecount >= maxlines)
410 spawn();
411 else if (!a || (i == 1 && rflag))
412 ;
413 else
414 spawn();
415 for (; i >= 0; i--)
416 free(cmd[i]);
417 } while (arg);
418
419 free(argb);
420
421 waitchld(1);
422
423 if (nerrors || (fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>")))
424 ret = 123;
425
426 return ret;
427}