master xplshn/aruu / cmd / posix / xargs.c
  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}