1/* See LICENSE file for copyright and license details. */
2
3
4#include <sys/wait.h>
5
6#include <errno.h>
7#include <limits.h>
8#include <stdint.h>
9#include <stdio.h>
10#include <stdlib.h>
11#include <string.h>
12#include <unistd.h>
13
14#include "util.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,
168 !waitall && curprocs < maxprocs ? WNOHANG : 0)) >
169 0) {
170 curprocs--;
171
172 if (WIFEXITED(status)) {
173 if (WEXITSTATUS(status) == 255)
174 exit(124);
175 if (WEXITSTATUS(status) == 127 ||
176 WEXITSTATUS(status) == 126)
177 exit(WEXITSTATUS(status));
178 if (WEXITSTATUS(status))
179 nerrors++;
180 }
181 if (WIFSIGNALED(status))
182 exit(125);
183 }
184 if (pid == -1 && errno != ECHILD)
185 eprintf("waitpid:");
186}
187
188static int
189prompt(void)
190{
191 FILE *fp;
192 int ch, ret;
193
194 if (!(fp = fopen("/dev/tty", "r")))
195 return -1;
196
197 fputs("?...", stderr);
198 fflush(stderr);
199
200 ch = fgetc(fp);
201 ret = (ch == 'y' || ch == 'Y');
202 if (ch != EOF && ch != '\n') {
203 while ((ch = fgetc(fp)) != EOF) {
204 if (ch == '\n')
205 break;
206 }
207 }
208
209 fclose(fp);
210
211 return ret;
212}
213
214static void
215spawn(void)
216{
217 int savederrno;
218 int first = 1;
219 char **p;
220
221 if (pflag || tflag) {
222 for (p = cmd; *p; p++) {
223 if (!first)
224 fputc(' ', stderr);
225 fputs(*p, stderr);
226 first = 0;
227 }
228 if (pflag) {
229 switch (prompt()) {
230 case -1:
231 break; /* error */
232 case 0:
233 return; /* no */
234 case 1:
235 goto dospawn; /* yes */
236 }
237 }
238 fputc('\n', stderr);
239 fflush(stderr);
240 }
241
242dospawn:
243 switch (fork()) {
244 case -1:
245 eprintf("fork:");
246 /* fallthrough */
247 case 0:
248 execvp(*cmd, cmd);
249 savederrno = errno;
250 weprintf("execvp %s:", *cmd);
251 _exit(126 + (savederrno == ENOENT));
252 }
253 curprocs++;
254 waitchld(0);
255}
256
257static void
258usage(void)
259{
260 eprintf("usage: %s [-0prtx] [-E eofstr] [-I replstr] [-L maxlines] [-n "
261 "num] [-P maxprocs] [-s num] "
262 "[cmd [arg ...]]\n",
263 argv0);
264}
265
266// ?man xargs: build and run command lines
267// ?man arguments: -n
268// ?man execute commands built from standard input arguments
269int
270main(int argc, char *argv[])
271{
272 int ret = 0, leftover = 0, i, j;
273 size_t argsz, argmaxsz;
274 size_t arglen, a;
275 char *arg = "";
276 char *replstr;
277
278 if ((argmaxsz = sysconf(_SC_ARG_MAX)) == (size_t)-1)
279 argmaxsz = _POSIX_ARG_MAX;
280 /* Leave some room for environment variables */
281 argmaxsz -= 4096;
282 cmd = emalloc(NARGS * sizeof(*cmd));
283
284 ARGBEGIN
285 {
286 // ?man -0: specify option flag
287 case '0':
288 nulflag = 1;
289 break;
290 // ?man -n:num: print line numbers or counts
291 case 'n':
292 nflag = 1;
293 maxargs =
294 estrtonum(EARGF(usage()), 1, MIN((unsigned long long)SIZE_MAX, (unsigned long long)LLONG_MAX));
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 =
307 estrtonum(EARGF(usage()), 1, MIN((unsigned long long)SIZE_MAX, (unsigned long long)LLONG_MAX));
308 break;
309 // ?man -t: sort or specify timestamp
310 case 't':
311 tflag = 1;
312 break;
313 // ?man -x: hex format or match whole lines
314 case 'x':
315 xflag = 1;
316 break;
317 // ?man -E:str: specify option flag
318 case 'E':
319 eofstr = EARGF(usage());
320 break;
321 // ?man -I:str: specify option flag
322 case 'I':
323 Iflag = 1;
324 xflag = 1;
325 nflag = 1;
326 maxargs = 1;
327 replstr = EARGF(usage());
328 break;
329 // ?man -L:num: specify option flag
330 case 'L':
331 Lflag = 1;
332 maxlines =
333 estrtonum(EARGF(usage()), 1, MIN((unsigned long long)SIZE_MAX, (unsigned long long)LLONG_MAX));
334 break;
335 // ?man -P:num: specify option flag
336 case 'P':
337 maxprocs =
338 estrtonum(EARGF(usage()), 1, MIN((unsigned long long)SIZE_MAX, (unsigned long long)LLONG_MAX));
339 break;
340 default:
341 usage();
342 }
343 ARGEND
344
345 do {
346 size_t linecount = 0;
347 argsz = 0;
348 i = 0;
349 a = 0;
350 if (argc) {
351 for (; i < argc; i++) {
352 cmd[i] = estrdup(argv[i]);
353 argsz += strlen(cmd[i]) + 1;
354 }
355 } else {
356 cmd[i] = estrdup("/bin/echo");
357 argsz += strlen("/bin/echo") + 1;
358 i++;
359 }
360 while (leftover || (arg = poparg())) {
361 if (arg) {
362 if (linecount == 0) {
363 linecount = 1;
364 } else if (arg_newline) {
365 linecount++;
366 arg_newline = 0;
367 }
368 if (Lflag && linecount > maxlines) {
369 leftover = 1;
370 break;
371 }
372 }
373 arglen = strlen(arg);
374 if (argsz + arglen >= argmaxsz || i >= NARGS - 1) {
375 if (xflag || arglen >= argmaxsz || leftover)
376 eprintf("insufficient argument "
377 "space\n");
378 leftover = 1;
379 break;
380 }
381
382 if (!Iflag) {
383 cmd[i] = estrdup(arg);
384 argsz += arglen + 1;
385 } else {
386 for (j = 1; j < i; j++) {
387 char *p = cmd[j];
388 argsz -= strlen(cmd[j]);
389 strnsubst(&cmd[j], replstr, arg, 255);
390 argsz += strlen(cmd[j]);
391 free(p);
392 }
393 }
394
395 i++;
396 a++;
397 leftover = 0;
398 if (nflag && a >= maxargs)
399 break;
400 }
401 cmd[i] = NULL;
402 if (a >= maxargs && nflag)
403 spawn();
404 else if (Lflag && linecount >= maxlines)
405 spawn();
406 else if (!a || (i == 1 && rflag))
407 ;
408 else
409 spawn();
410 for (; i >= 0; i--)
411 free(cmd[i]);
412 } while (arg);
413
414 free(argb);
415
416 waitchld(1);
417
418 if (nerrors || (fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>")))
419 ret = 123;
420
421 return ret;
422}