xargs: error out if argument to -L is no valid number
[dragonfly.git] / usr.bin / xargs / xargs.c
blobf48b975336974f9e82fd9940f04c8475119e9e9a
1 /*-
2 * Copyright (c) 1990, 1993
3 * The Regents of the University of California. All rights reserved.
5 * This code is derived from software contributed to Berkeley by
6 * John B. Roll Jr.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 * 3. All advertising materials mentioning features or use of this software
17 * must display the following acknowledgement:
18 * This product includes software developed by the University of
19 * California, Berkeley and its contributors.
20 * 4. Neither the name of the University nor the names of its contributors
21 * may be used to endorse or promote products derived from this software
22 * without specific prior written permission.
24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 * SUCH DAMAGE.
36 * $xMach: xargs.c,v 1.6 2002/02/23 05:27:47 tim Exp $
38 * @(#) Copyright (c) 1990, 1993 The Regents of the University of California. All rights reserved.
39 * @(#)xargs.c 8.1 (Berkeley) 6/6/93
40 * $FreeBSD: src/usr.bin/xargs/xargs.c,v 1.9.2.6 2003/06/01 21:40:35 mux Exp $
41 * $DragonFly: src/usr.bin/xargs/xargs.c,v 1.3 2004/07/27 21:42:48 dillon Exp $
44 #include <sys/types.h>
45 #include <sys/wait.h>
47 #include <err.h>
48 #include <errno.h>
49 #include <fcntl.h>
50 #ifndef BOOTSTRAPPING
51 #include <langinfo.h>
52 #endif
53 #include <locale.h>
54 #include <paths.h>
55 #include <regex.h>
56 #include <stdio.h>
57 #include <stdlib.h>
58 #include <string.h>
59 #include <unistd.h>
61 #include "pathnames.h"
63 static void parse_input(int, char *[]);
64 static void prerun(int, char *[]);
65 static int prompt(void);
66 static void run(char **);
67 static void usage(void);
68 void strnsubst(char **, const char *, const char *, size_t);
70 static char echo[] = _PATH_ECHO;
71 static char **av, **bxp, **ep, **expx, **xp;
72 static char *argp, *bbp, *ebp, *inpline, *p, *replstr;
73 static const char *eofstr;
74 static int count, insingle, indouble, oflag, pflag, tflag, Rflag, rval, zflag;
75 static int cnt, Iflag, jfound, Lflag, wasquoted, xflag;
77 extern char **environ;
79 int
80 main(int argc, char *argv[])
82 char *tmp;
83 long arg_max;
84 int ch, Jflag, nargs, nflag, nline;
85 size_t linelen;
87 inpline = replstr = NULL;
88 ep = environ;
89 eofstr = "";
90 Jflag = nflag = 0;
92 (void)setlocale(LC_MESSAGES, "");
95 * POSIX.2 limits the exec line length to ARG_MAX - 2K. Running that
96 * caused some E2BIG errors, so it was changed to ARG_MAX - 4K. Given
97 * that the smallest argument is 2 bytes in length, this means that
98 * the number of arguments is limited to:
100 * (ARG_MAX - 4K - LENGTH(utility + arguments)) / 2.
102 * We arbitrarily limit the number of arguments to 5000. This is
103 * allowed by POSIX.2 as long as the resulting minimum exec line is
104 * at least LINE_MAX. Realloc'ing as necessary is possible, but
105 * probably not worthwhile.
107 nargs = 5000;
108 if ((arg_max = sysconf(_SC_ARG_MAX)) == -1)
109 errx(1, "sysconf(_SC_ARG_MAX) failed");
110 nline = arg_max - 4 * 1024;
111 while (*ep != NULL) {
112 /* 1 byte for each '\0' */
113 nline -= strlen(*ep++) + 1 + sizeof(*ep);
115 while ((ch = getopt(argc, argv, "0E:I:J:L:n:opR:s:tx")) != -1)
116 switch(ch) {
117 case 'E':
118 eofstr = optarg;
119 break;
120 case 'I':
121 Jflag = 0;
122 Iflag = 1;
123 Lflag = 1;
124 replstr = optarg;
125 break;
126 case 'J':
127 Iflag = 0;
128 Jflag = 1;
129 replstr = optarg;
130 break;
131 case 'L':
132 Lflag = strtol(optarg, &tmp, 10);
133 if (*tmp != 0 || *optarg == 0)
134 errx(1, "illegal argument count");
135 break;
136 case 'n':
137 nflag = 1;
138 if ((nargs = atoi(optarg)) <= 0)
139 errx(1, "illegal argument count");
140 break;
141 case 'o':
142 oflag = 1;
143 break;
144 case 'p':
145 pflag = 1;
146 break;
147 case 'R':
148 if ((Rflag = atoi(optarg)) <= 0)
149 errx(1, "illegal number of replacements");
150 break;
151 case 's':
152 nline = atoi(optarg);
153 break;
154 case 't':
155 tflag = 1;
156 break;
157 case 'x':
158 xflag = 1;
159 break;
160 case '0':
161 zflag = 1;
162 break;
163 case '?':
164 default:
165 usage();
167 argc -= optind;
168 argv += optind;
170 if (!Iflag && Rflag)
171 usage();
172 if (Iflag && !Rflag)
173 Rflag = 5;
174 if (xflag && !nflag)
175 usage();
176 if (Iflag || Lflag)
177 xflag = 1;
178 if (replstr != NULL && *replstr == '\0')
179 errx(1, "replstr may not be empty");
182 * Allocate pointers for the utility name, the utility arguments,
183 * the maximum arguments to be read from stdin and the trailing
184 * NULL.
186 linelen = 1 + argc + nargs + 1;
187 if ((av = bxp = malloc(linelen * sizeof(char **))) == NULL)
188 errx(1, "malloc failed");
191 * Use the user's name for the utility as argv[0], just like the
192 * shell. Echo is the default. Set up pointers for the user's
193 * arguments.
195 if (*argv == NULL)
196 cnt = strlen(*bxp++ = echo);
197 else {
198 do {
199 if (Jflag && strcmp(*argv, replstr) == 0) {
200 char **avj;
201 jfound = 1;
202 argv++;
203 for (avj = argv; *avj; avj++)
204 cnt += strlen(*avj) + 1;
205 break;
207 cnt += strlen(*bxp++ = *argv) + 1;
208 } while (*++argv != NULL);
212 * Set up begin/end/traversing pointers into the array. The -n
213 * count doesn't include the trailing NULL pointer, so the malloc
214 * added in an extra slot.
216 expx = (xp = bxp) + nargs;
219 * Allocate buffer space for the arguments read from stdin and the
220 * trailing NULL. Buffer space is defined as the default or specified
221 * space, minus the length of the utility name and arguments. Set up
222 * begin/end/traversing pointers into the array. The -s count does
223 * include the trailing NULL, so the malloc didn't add in an extra
224 * slot.
226 nline -= cnt;
227 if (nline <= 0)
228 errx(1, "insufficient space for command");
230 if ((bbp = malloc((size_t)(nline + 1))) == NULL)
231 errx(1, "malloc failed");
232 ebp = (argp = p = bbp) + nline - 1;
233 for (;;)
234 parse_input(argc, argv);
237 static void
238 parse_input(int argc, char *argv[])
240 int ch, foundeof;
241 char **avj;
243 foundeof = 0;
245 switch(ch = getchar()) {
246 case EOF:
247 /* No arguments since last exec. */
248 if (p == bbp)
249 exit(rval);
250 goto arg1;
251 case ' ':
252 case '\t':
253 /* Quotes escape tabs and spaces. */
254 if (insingle || indouble || zflag)
255 goto addch;
256 goto arg2;
257 case '\0':
258 if (zflag)
259 goto arg2;
260 goto addch;
261 case '\n':
262 count++;
263 if (zflag)
264 goto addch;
266 /* Quotes do not escape newlines. */
267 arg1: if (insingle || indouble)
268 errx(1, "unterminated quote");
269 arg2:
270 foundeof = *eofstr != '\0' &&
271 strcmp(argp, eofstr) == 0;
273 /* Do not make empty args unless they are quoted */
274 if ((argp != p || wasquoted) && !foundeof) {
275 *p++ = '\0';
276 *xp++ = argp;
277 if (Iflag) {
278 size_t curlen;
280 if (inpline == NULL)
281 curlen = 0;
282 else {
284 * If this string is not zero
285 * length, append a space for
286 * separation before the next
287 * argument.
289 if ((curlen = strlen(inpline)))
290 strcat(inpline, " ");
292 curlen++;
294 * Allocate enough to hold what we will
295 * be holding in a second, and to append
296 * a space next time through, if we have
297 * to.
299 inpline = realloc(inpline, curlen + 2 +
300 strlen(argp));
301 if (inpline == NULL)
302 errx(1, "realloc failed");
303 if (curlen == 1)
304 strcpy(inpline, argp);
305 else
306 strcat(inpline, argp);
311 * If max'd out on args or buffer, or reached EOF,
312 * run the command. If xflag and max'd out on buffer
313 * but not on args, object. Having reached the limit
314 * of input lines, as specified by -L is the same as
315 * maxing out on arguments.
317 if (xp == expx || p > ebp || ch == EOF ||
318 (Lflag <= count && xflag) || foundeof) {
319 if (xflag && xp != expx && p > ebp)
320 errx(1, "insufficient space for arguments");
321 if (jfound) {
322 for (avj = argv; *avj; avj++)
323 *xp++ = *avj;
325 prerun(argc, av);
326 if (ch == EOF || foundeof)
327 exit(rval);
328 p = bbp;
329 xp = bxp;
330 count = 0;
332 argp = p;
333 wasquoted = 0;
334 break;
335 case '\'':
336 if (indouble || zflag)
337 goto addch;
338 insingle = !insingle;
339 wasquoted = 1;
340 break;
341 case '"':
342 if (insingle || zflag)
343 goto addch;
344 indouble = !indouble;
345 wasquoted = 1;
346 break;
347 case '\\':
348 if (zflag)
349 goto addch;
350 /* Backslash escapes anything, is escaped by quotes. */
351 if (!insingle && !indouble && (ch = getchar()) == EOF)
352 errx(1, "backslash at EOF");
353 /* FALLTHROUGH */
354 default:
355 addch: if (p < ebp) {
356 *p++ = ch;
357 break;
360 /* If only one argument, not enough buffer space. */
361 if (bxp == xp)
362 errx(1, "insufficient space for argument");
363 /* Didn't hit argument limit, so if xflag object. */
364 if (xflag)
365 errx(1, "insufficient space for arguments");
367 if (jfound) {
368 for (avj = argv; *avj; avj++)
369 *xp++ = *avj;
371 prerun(argc, av);
372 xp = bxp;
373 cnt = ebp - argp;
374 memcpy(bbp, argp, (size_t)cnt);
375 p = (argp = bbp) + cnt;
376 *p++ = ch;
377 break;
379 return;
383 * Do things necessary before run()'ing, such as -I substitution,
384 * and then call run().
386 static void
387 prerun(int argc, char *argv[])
389 char **tmp, **tmp2, **avj;
390 int repls;
392 repls = Rflag;
394 if (argc == 0 || repls == 0) {
395 *xp = NULL;
396 run(argv);
397 return;
400 avj = argv;
403 * Allocate memory to hold the argument list, and
404 * a NULL at the tail.
406 tmp = malloc((argc + 1) * sizeof(char**));
407 if (tmp == NULL)
408 errx(1, "malloc failed");
409 tmp2 = tmp;
412 * Save the first argument and iterate over it, we
413 * cannot do strnsubst() to it.
415 if ((*tmp++ = strdup(*avj++)) == NULL)
416 errx(1, "strdup failed");
419 * For each argument to utility, if we have not used up
420 * the number of replacements we are allowed to do, and
421 * if the argument contains at least one occurrence of
422 * replstr, call strnsubst(), else just save the string.
423 * Iterations over elements of avj and tmp are done
424 * where appropriate.
426 while (--argc) {
427 *tmp = *avj++;
428 if (repls && strstr(*tmp, replstr) != NULL) {
429 strnsubst(tmp++, replstr, inpline, (size_t)255);
430 repls--;
431 } else {
432 if ((*tmp = strdup(*tmp)) == NULL)
433 errx(1, "strdup failed");
434 tmp++;
439 * Run it.
441 *tmp = NULL;
442 run(tmp2);
445 * Walk from the tail to the head, free along the way.
447 for (; tmp2 != tmp; tmp--)
448 free(*tmp);
450 * Now free the list itself.
452 free(tmp2);
455 * Free the input line buffer, if we have one.
457 if (inpline != NULL) {
458 free(inpline);
459 inpline = NULL;
463 static void
464 run(char **argv)
466 volatile int childerr;
467 char **avec;
468 pid_t pid;
469 int status;
472 * If the user wants to be notified of each command before it is
473 * executed, notify them. If they want the notification to be
474 * followed by a prompt, then prompt them.
476 if (tflag || pflag) {
477 (void)fprintf(stderr, "%s", *argv);
478 for (avec = argv + 1; *avec != NULL; ++avec)
479 (void)fprintf(stderr, " %s", *avec);
481 * If the user has asked to be prompted, do so.
483 if (pflag)
485 * If they asked not to exec, return without execution
486 * but if they asked to, go to the execution. If we
487 * could not open their tty, break the switch and drop
488 * back to -t behaviour.
490 switch (prompt()) {
491 case 0:
492 return;
493 case 1:
494 goto exec;
495 case 2:
496 break;
498 (void)fprintf(stderr, "\n");
499 (void)fflush(stderr);
501 exec:
502 childerr = 0;
503 switch(pid = vfork()) {
504 case -1:
505 err(1, "vfork");
506 case 0:
507 close(0);
508 if (oflag) {
509 if (open("/dev/tty", O_RDONLY) == -1)
510 err(1, "open /dev/tty");
511 } else {
512 if (open("/dev/null", O_RDONLY) == -1)
513 err(1, "open /dev/null");
515 execvp(argv[0], argv);
516 childerr = errno;
517 _exit(1);
519 pid = waitpid(pid, &status, 0);
520 if (pid == -1)
521 err(1, "waitpid");
522 /* If we couldn't invoke the utility, exit. */
523 if (childerr != 0)
524 err(childerr == ENOENT ? 127 : 126, "%s", *argv);
525 /* If utility signaled or exited with a value of 255, exit 1-125. */
526 if (WIFSIGNALED(status) || WEXITSTATUS(status) == 255)
527 exit(1);
528 if (WEXITSTATUS(status))
529 rval = 1;
533 * Prompt the user about running a command.
535 static int
536 prompt(void)
538 regex_t cre;
539 size_t rsize;
540 int match;
541 char *response;
542 FILE *ttyfp;
544 if ((ttyfp = fopen(_PATH_TTY, "r")) == NULL)
545 return (2); /* Indicate that the TTY failed to open. */
546 (void)fprintf(stderr, "?...");
547 (void)fflush(stderr);
548 if ((response = fgetln(ttyfp, &rsize)) == NULL ||
549 regcomp(&cre,
550 #ifdef BOOTSTRAPPING
551 "^[yY]",
552 #else
553 nl_langinfo(YESEXPR),
554 #endif
555 REG_BASIC) != 0) {
556 (void)fclose(ttyfp);
557 return (0);
559 match = regexec(&cre, response, 0, NULL, 0);
560 (void)fclose(ttyfp);
561 regfree(&cre);
562 return (match == 0);
565 static void
566 usage(void)
568 fprintf(stderr,
569 "usage: xargs [-0opt] [-E eofstr] [-I replstr [-R replacements]] [-J replstr]\n"
570 " [-L number] [-n number [-x] [-s size] [utility [argument ...]]\n");
571 exit(1);