xref: /openbsd-src/usr.bin/xargs/xargs.c (revision b2ea75c1b17e1a9a339660e7ed45cd24946b230e)
1 /*	$OpenBSD: xargs.c,v 1.8 1998/06/23 00:22:58 deraadt Exp $	*/
2 /*	$NetBSD: xargs.c,v 1.7 1994/11/14 06:51:41 jtc Exp $	*/
3 
4 /*-
5  * Copyright (c) 1990, 1993
6  *	The Regents of the University of California.  All rights reserved.
7  *
8  * This code is derived from software contributed to Berkeley by
9  * John B. Roll Jr.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice, this list of conditions and the following disclaimer.
16  * 2. Redistributions in binary form must reproduce the above copyright
17  *    notice, this list of conditions and the following disclaimer in the
18  *    documentation and/or other materials provided with the distribution.
19  * 3. All advertising materials mentioning features or use of this software
20  *    must display the following acknowledgement:
21  *	This product includes software developed by the University of
22  *	California, Berkeley and its contributors.
23  * 4. Neither the name of the University nor the names of its contributors
24  *    may be used to endorse or promote products derived from this software
25  *    without specific prior written permission.
26  *
27  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
28  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
31  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37  * SUCH DAMAGE.
38  */
39 
40 #ifndef lint
41 static char copyright[] =
42 "@(#) Copyright (c) 1990, 1993\n\
43 	The Regents of the University of California.  All rights reserved.\n";
44 #endif /* not lint */
45 
46 #ifndef lint
47 #if 0
48 static char sccsid[] = "@(#)xargs.c	8.1 (Berkeley) 6/6/93";
49 #endif
50 static char rcsid[] = "$OpenBSD: xargs.c,v 1.8 1998/06/23 00:22:58 deraadt Exp $";
51 #endif /* not lint */
52 
53 #include <sys/types.h>
54 #include <sys/wait.h>
55 #include <errno.h>
56 #include <stdio.h>
57 #include <stdlib.h>
58 #include <string.h>
59 #include <unistd.h>
60 #include <limits.h>
61 #include <locale.h>
62 #include <signal.h>
63 #include <err.h>
64 #include "pathnames.h"
65 
66 int tflag, rval;
67 int zflag;
68 
69 void run __P((char **));
70 void usage __P((void));
71 
72 int
73 main(argc, argv)
74 	int argc;
75 	char **argv;
76 {
77 	register int ch;
78 	register char *p, *bbp, *ebp, **bxp, **exp, **xp;
79 	int cnt, indouble, insingle, nargs, nflag, nline, xflag;
80 	char **av, *argp;
81 	int arg_max;
82 
83 	setlocale(LC_ALL, "");
84 
85 	/*
86 	 * POSIX.2 limits the exec line length to ARG_MAX - 2K.  Running that
87 	 * caused some E2BIG errors, so it was changed to ARG_MAX - 4K.  Given
88 	 * that the smallest argument is 2 bytes in length, this means that
89 	 * the number of arguments is limited to:
90 	 *
91 	 *	 (ARG_MAX - 4K - LENGTH(utility + arguments)) / 2.
92 	 *
93 	 * We arbitrarily limit the number of arguments to 5000.  This is
94 	 * allowed by POSIX.2 as long as the resulting minimum exec line is
95 	 * at least LINE_MAX.  Realloc'ing as necessary is possible, but
96 	 * probably not worthwhile.
97 	 */
98 	nargs = 5000;
99 	if ((arg_max = sysconf(_SC_ARG_MAX)) == -1)
100 		errx(1, "sysconf(_SC_ARG_MAX) failed");
101 	nline = arg_max - 4 * 1024;
102 	nflag = xflag = 0;
103 	while ((ch = getopt(argc, argv, "0n:s:tx")) != -1)
104 		switch(ch) {
105 		case 'n':
106 			nflag = 1;
107 			if ((nargs = atoi(optarg)) <= 0)
108 				errx(1, "illegal argument count");
109 			break;
110 		case 's':
111 			nline = atoi(optarg);
112 			break;
113 		case 't':
114 			tflag = 1;
115 			break;
116 		case 'x':
117 			xflag = 1;
118 			break;
119 		case '0':
120 			zflag = 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, NULL);
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, NULL);
176 	ebp = (argp = p = bbp) + nline - 1;
177 
178 	for (insingle = indouble = 0;;)
179 		switch(ch = getchar()) {
180 		case EOF:
181 			/* No arguments since last exec. */
182 			if (p == bbp)
183 				exit(rval);
184 
185 			/* Nothing since end of last argument. */
186 			if (argp == p) {
187 				*xp = NULL;
188 				run(av);
189 				exit(rval);
190 			}
191 			goto arg1;
192 		case ' ':
193 		case '\t':
194 			/* Quotes escape tabs and spaces. */
195 			if (insingle || indouble || zflag)
196 				goto addch;
197 			goto arg2;
198 		case '\0':
199 			if (zflag)
200 				goto arg2;
201 			goto addch;
202 		case '\n':
203 			if (zflag)
204 				goto addch;
205 
206 			/* Empty lines are skipped. */
207 			if (argp == p)
208 				continue;
209 
210 			/* Quotes do not escape newlines. */
211 arg1:			if (insingle || indouble)
212 				 errx(1, "unterminated quote");
213 
214 arg2:			*p = '\0';
215 			*xp++ = argp;
216 
217 			/*
218 			 * If max'd out on args or buffer, or reached EOF,
219 			 * run the command.  If xflag and max'd out on buffer
220 			 * but not on args, object.
221 			 */
222 			if (xp == exp || p == ebp || ch == EOF) {
223 				if (xflag && xp != exp && p == ebp)
224 					errx(1, "insufficient space for arguments");
225 				*xp = NULL;
226 				run(av);
227 				if (ch == EOF)
228 					exit(rval);
229 				p = bbp;
230 				xp = bxp;
231 			} else
232 				++p;
233 			argp = p;
234 			break;
235 		case '\'':
236 			if (indouble || zflag)
237 				goto addch;
238 			insingle = !insingle;
239 			break;
240 		case '"':
241 			if (insingle || zflag)
242 				goto addch;
243 			indouble = !indouble;
244 			break;
245 		case '\\':
246 			if (zflag)
247 				goto addch;
248 			/* Backslash escapes anything, is escaped by quotes. */
249 			if (!insingle && !indouble && (ch = getchar()) == EOF)
250 				errx(1, "backslash at EOF");
251 			/* FALLTHROUGH */
252 		default:
253 addch:			if (p < ebp) {
254 				*p++ = ch;
255 				break;
256 			}
257 
258 			/* If only one argument, not enough buffer space. */
259 			if (bxp == xp)
260 				errx(1, "insufficient space for argument");
261 			/* Didn't hit argument limit, so if xflag object. */
262 			if (xflag)
263 				errx(1, "insufficient space for arguments");
264 
265 			*xp = NULL;
266 			run(av);
267 			xp = bxp;
268 			cnt = ebp - argp;
269 			bcopy(argp, bbp, cnt);
270 			p = (argp = bbp) + cnt;
271 			*p++ = ch;
272 			break;
273 		}
274 	/* NOTREACHED */
275 }
276 
277 void
278 run(argv)
279 	char **argv;
280 {
281 	volatile int noinvoke;
282 	register char **p;
283 	pid_t pid;
284 	int status;
285 
286 	if (tflag) {
287 		(void)fprintf(stderr, "%s", *argv);
288 		for (p = argv + 1; *p; ++p)
289 			(void)fprintf(stderr, " %s", *p);
290 		(void)fprintf(stderr, "\n");
291 		(void)fflush(stderr);
292 	}
293 	noinvoke = 0;
294 	switch(pid = vfork()) {
295 	case -1:
296 		err(1, "vfork");
297 	case 0:
298 		execvp(argv[0], argv);
299 		noinvoke = (errno == ENOENT) ? 127 : 126;
300 		warn("%s", argv[0]);
301 		_exit(1);
302 	}
303 	pid = waitpid(pid, &status, 0);
304 	if (pid == -1)
305 		err(1, "waitpid");
306 
307 	/*
308 	 * If we couldn't invoke the utility or the utility didn't exit
309 	 * properly, quit with 127 or 126 respectively.
310 	 */
311 	if (noinvoke)
312 		exit(noinvoke);
313 
314 	/*
315 	 * According to POSIX, we have to exit if the utility exits with
316 	 * a 255 status, or is interrupted by a signal.   xargs is allowed
317 	 * to return any exit status between 1 and 125 in these cases, but
318 	 * we'll use 124 and 125, the same values used by GNU xargs.
319 	 */
320 	if (WIFEXITED(status)) {
321 		if (WEXITSTATUS(status) == 255) {
322 			warnx("%s exited with status 255", argv[0]);
323 			exit(124);
324 		} else if (WEXITSTATUS(status) != 0) {
325 			rval = 123;
326 		}
327 	} else if (WIFSIGNALED(status)) {
328 		if (WTERMSIG(status) != SIGPIPE) {
329 			if (WTERMSIG(status) < NSIG)
330 				warnx("%s terminated by SIG%s", argv[0],
331 				    sys_signame[WTERMSIG(status)]);
332 			else
333 				warnx("%s terminated by signal %d", argv[0],
334 				    WTERMSIG(status));
335 		}
336 		exit(125);
337 	}
338 }
339 
340 void
341 usage()
342 {
343 	(void)fprintf(stderr,
344 "usage: xargs [-0] [-t] [-n number [-x]] [-s size] [utility [argument ...]]\n");
345 	exit(1);
346 }
347