xref: /netbsd-src/usr.bin/xargs/xargs.c (revision 23c8222edbfb0f0932d88a8351d3a0cf817dfb9e)
1 /*	$NetBSD: xargs.c,v 1.14 2003/08/07 11:17:49 agc Exp $	*/
2 
3 /*-
4  * Copyright (c) 1990, 1993
5  *	The Regents of the University of California.  All rights reserved.
6  *
7  * This code is derived from software contributed to Berkeley by
8  * John B. Roll Jr.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  * 3. Neither the name of the University nor the names of its contributors
19  *    may be used to endorse or promote products derived from this software
20  *    without specific prior written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  */
34 
35 #include <sys/cdefs.h>
36 #ifndef lint
37 __COPYRIGHT("@(#) Copyright (c) 1990, 1993\n\
38 	The Regents of the University of California.  All rights reserved.\n");
39 #endif /* not lint */
40 
41 #ifndef lint
42 #if 0
43 static char sccsid[] = "@(#)xargs.c	8.1 (Berkeley) 6/6/93";
44 #endif
45 __RCSID("$NetBSD: xargs.c,v 1.14 2003/08/07 11:17:49 agc Exp $");
46 #endif /* not lint */
47 
48 #include <sys/types.h>
49 #include <sys/wait.h>
50 #include <err.h>
51 #include <errno.h>
52 #include <langinfo.h>
53 #include <limits.h>
54 #include <locale.h>
55 #include <paths.h>
56 #include <regex.h>
57 #include <signal.h>
58 #include <stdio.h>
59 #include <stdlib.h>
60 #include <string.h>
61 #include <unistd.h>
62 #include "pathnames.h"
63 
64 static int pflag, tflag, zflag, rval;
65 static FILE *promptfile;
66 static regex_t yesexpr;
67 
68 static void	run __P((char **));
69 int		main __P((int, char **));
70 static void	usage __P((void));
71 
72 int
73 main(argc, argv)
74 	int argc;
75 	char **argv;
76 {
77 	int ch;
78 	char *p, *bbp, *ebp, **bxp, **exp, **xp;
79 	int cnt, indouble, insingle, nargs, nflag, nline, xflag;
80 	char **av, *argp;
81 
82 	setlocale(LC_ALL, "");
83 
84 	/*
85 	 * POSIX.2 limits the exec line length to ARG_MAX - 2K.  Running that
86 	 * caused some E2BIG errors, so it was changed to ARG_MAX - 4K.  Given
87 	 * that the smallest argument is 2 bytes in length, this means that
88 	 * the number of arguments is limited to:
89 	 *
90 	 *	 (ARG_MAX - 4K - LENGTH(utility + arguments)) / 2.
91 	 *
92 	 * We arbitrarily limit the number of arguments to 5000.  This is
93 	 * allowed by POSIX.2 as long as the resulting minimum exec line is
94 	 * at least LINE_MAX.  Realloc'ing as necessary is possible, but
95 	 * probably not worthwhile.
96 	 */
97 	nargs = 5000;
98 	nline = ARG_MAX - 4 * 1024;
99 	nflag = xflag = 0;
100 	while ((ch = getopt(argc, argv, "0n:ps:tx")) != -1)
101 		switch(ch) {
102 		case '0':
103 			zflag = 1;
104 			break;
105 		case 'n':
106 			nflag = 1;
107 			if ((nargs = atoi(optarg)) <= 0)
108 				errx(1, "illegal argument count");
109 			break;
110 		case 'p':
111 			pflag = tflag = 1;
112 			break;
113 		case 's':
114 			nline = atoi(optarg);
115 			break;
116 		case 't':
117 			tflag = 1;
118 			break;
119 		case 'x':
120 			xflag = 1;
121 			break;
122 		case '?':
123 		default:
124 			usage();
125 	}
126 	argc -= optind;
127 	argv += optind;
128 
129 	if (xflag && !nflag)
130 		usage();
131 
132 	/*
133 	 * Allocate pointers for the utility name, the utility arguments,
134 	 * the maximum arguments to be read from stdin and the trailing
135 	 * NULL.
136 	 */
137 	if (!(av = bxp =
138 	    malloc((u_int)(1 + argc + nargs + 1) * sizeof(char **))))
139 		err(1, "malloc");
140 
141 	/*
142 	 * Use the user's name for the utility as argv[0], just like the
143 	 * shell.  Echo is the default.  Set up pointers for the user's
144 	 * arguments.
145 	 */
146 	if (!*argv)
147 		cnt = strlen(*bxp++ = _PATH_ECHO);
148 	else {
149 		cnt = 0;
150 		do {
151 			cnt += strlen(*bxp++ = *argv) + 1;
152 		} while (*++argv);
153 	}
154 
155 	/*
156 	 * Set up begin/end/traversing pointers into the array.  The -n
157 	 * count doesn't include the trailing NULL pointer, so the malloc
158 	 * added in an extra slot.
159 	 */
160 	exp = (xp = bxp) + nargs;
161 
162 	/*
163 	 * Allocate buffer space for the arguments read from stdin and the
164 	 * trailing NULL.  Buffer space is defined as the default or specified
165 	 * space, minus the length of the utility name and arguments.  Set up
166 	 * begin/end/traversing pointers into the array.  The -s count does
167 	 * include the trailing NULL, so the malloc didn't add in an extra
168 	 * slot.
169 	 */
170 	nline -= cnt;
171 	if (nline <= 0)
172 		errx(1, "insufficient space for command");
173 
174 	if (!(bbp = malloc((u_int)nline + 1)))
175 		err(1, "malloc");
176 	ebp = (argp = p = bbp) + nline - 1;
177 
178 	if (pflag) {
179 		int error;
180 
181 		if ((promptfile = fopen(_PATH_TTY, "r")) == NULL)
182 			err(1, "prompt mode: cannot open input");
183 		if ((error = regcomp(&yesexpr, nl_langinfo(YESEXPR), REG_NOSUB))
184 		    != 0) {
185 			char msg[NL_TEXTMAX];
186 
187 			(void)regerror(error, NULL, msg, sizeof (msg));
188 			err(1, "cannot compile yesexpr: %s", msg);
189 		}
190 	}
191 
192 	for (insingle = indouble = 0;;)
193 		switch(ch = getchar()) {
194 		case EOF:
195 			/* No arguments since last exec. */
196 			if (p == bbp)
197 				exit(rval);
198 
199 			/* Nothing since end of last argument. */
200 			if (argp == p) {
201 				*xp = NULL;
202 				run(av);
203 				exit(rval);
204 			}
205 			goto arg1;
206 		case ' ':
207 		case '\t':
208 			/* Quotes escape tabs and spaces. */
209 			if (insingle || indouble || zflag)
210 				goto addch;
211 			goto arg2;
212 		case '\0':
213 			if (zflag)
214 				goto arg2;
215 			goto addch;
216 		case '\n':
217 			if (zflag)
218 				goto addch;
219 			/* Empty lines are skipped. */
220 			if (argp == p)
221 				continue;
222 
223 			/* Quotes do not escape newlines. */
224 arg1:			if (insingle || indouble)
225 				 errx(1, "unterminated quote");
226 
227 arg2:			*p = '\0';
228 			*xp++ = argp;
229 
230 			/*
231 			 * If max'd out on args or buffer, or reached EOF,
232 			 * run the command.  If xflag and max'd out on buffer
233 			 * but not on args, object.
234 			 */
235 			if (xp == exp || p == ebp || ch == EOF) {
236 				if (xflag && xp != exp && p == ebp)
237 					errx(1, "insufficient space for arguments");
238 				*xp = NULL;
239 				run(av);
240 				if (ch == EOF)
241 					exit(rval);
242 				p = bbp;
243 				xp = bxp;
244 			} else
245 				++p;
246 			argp = p;
247 			break;
248 		case '\'':
249 			if (indouble || zflag)
250 				goto addch;
251 			insingle = !insingle;
252 			break;
253 		case '"':
254 			if (insingle || zflag)
255 				goto addch;
256 			indouble = !indouble;
257 			break;
258 		case '\\':
259 			if (zflag)
260 				goto addch;
261 			/* Backslash escapes anything, is escaped by quotes. */
262 			if (!insingle && !indouble && (ch = getchar()) == EOF)
263 				errx(1, "backslash at EOF");
264 			/* FALLTHROUGH */
265 		default:
266 addch:			if (p < ebp) {
267 				*p++ = ch;
268 				break;
269 			}
270 
271 			/* If only one argument, not enough buffer space. */
272 			if (bxp == xp)
273 				errx(1, "insufficient space for argument");
274 			/* Didn't hit argument limit, so if xflag object. */
275 			if (xflag)
276 				errx(1, "insufficient space for arguments");
277 
278 			*xp = NULL;
279 			run(av);
280 			xp = bxp;
281 			cnt = ebp - argp;
282 			memmove(bbp, argp, cnt);
283 			p = (argp = bbp) + cnt;
284 			*p++ = ch;
285 			break;
286 		}
287 	/* NOTREACHED */
288 }
289 
290 static void
291 run(argv)
292 	char **argv;
293 {
294 	volatile int noinvoke;
295 	char **p;
296 	pid_t pid;
297 	int status;
298 
299 	if (tflag) {
300 		(void)fprintf(stderr, "%s", *argv);
301 		for (p = argv + 1; *p; ++p)
302 			(void)fprintf(stderr, " %s", *p);
303 		if (pflag) {
304 			char buf[LINE_MAX + 1];
305 
306 			(void)fprintf(stderr, "?...");
307 			fflush(stderr);
308 			if (fgets(buf, sizeof (buf), promptfile) == NULL) {
309 				rval = 1;
310 				return;
311 			}
312 			if (regexec(&yesexpr, buf, 0, NULL, 0) != 0)
313 				return;
314 		} else {
315 			(void)fprintf(stderr, "\n");
316 		}
317 	}
318 	noinvoke = 0;
319 	switch(pid = vfork()) {
320 	case -1:
321 		err(1, "vfork");
322 	case 0:
323 		execvp(argv[0], argv);
324 		noinvoke = (errno == ENOENT) ? 127 : 126;
325 		warn("%s", argv[0]);
326 		_exit(1);
327 	}
328 	pid = waitpid(pid, &status, 0);
329 	if (pid == -1)
330 		err(1, "waitpid");
331 
332 	/*
333 	 * If we couldn't invoke the utility or the utility didn't exit
334 	 * properly, quit with 127 or 126 respectively.
335 	 */
336 	if (noinvoke)
337 		exit(noinvoke);
338 
339 	/*
340 	 * According to POSIX, we have to exit if the utility exits with
341 	 * a 255 status, or is interrupted by a signal.   xargs is allowed
342 	 * to return any exit status between 1 and 125 in these cases, but
343 	 * we'll use 124 and 125, the same values used by GNU xargs.
344 	 */
345 	if (WIFEXITED(status)) {
346 		if (WEXITSTATUS (status) == 255) {
347 			warnx ("%s exited with status 255", argv[0]);
348 			exit(124);
349 		} else if (WEXITSTATUS (status) != 0) {
350 			rval = 123;
351 		}
352 	} else if (WIFSIGNALED (status)) {
353 		if (WTERMSIG(status) < NSIG) {
354 			warnx("%s terminated by SIG%s", argv[0],
355 				sys_signame[WTERMSIG(status)]);
356 		} else {
357 			warnx("%s terminated by signal %d", argv[0],
358 				WTERMSIG(status));
359 		}
360 		exit(125);
361 	}
362 }
363 
364 static void
365 usage()
366 {
367 	(void)fprintf(stderr,
368 "usage: xargs [-0pt] [-n number [-x]] [-s size] [utility [argument ...]]\n");
369 	exit(1);
370 }
371