c55ac5724e795665c277da8abe9c3dc0df4fd64b
[platform/upstream/busybox.git] / findutils / xargs.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * Mini xargs implementation for busybox
4  * Options are supported: "-prtx -n max_arg -s max_chars -e[ouf_str]"
5  *
6  * (C) 2002,2003 by Vladimir Oleynik <dzo@simtreas.ru>
7  *
8  * Special thanks
9  * - Mark Whitley and Glenn McGrath for stimulus to rewrite :)
10  * - Mike Rendell <michael@cs.mun.ca>
11  * and David MacKenzie <djm@gnu.ai.mit.edu>.
12  *
13  * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
14  *
15  * xargs is described in the Single Unix Specification v3 at
16  * http://www.opengroup.org/onlinepubs/007904975/utilities/xargs.html
17  *
18  */
19
20 //kbuild:lib-$(CONFIG_XARGS) += xargs.o
21 //config:
22 //config:config XARGS
23 //config:       bool "xargs"
24 //config:       default n
25 //config:       help
26 //config:         xargs is used to execute a specified command for
27 //config:         every item from standard input.
28 //config:
29 //config:config FEATURE_XARGS_SUPPORT_CONFIRMATION
30 //config:       bool "Enable -p: prompt and confirmation"
31 //config:       default n
32 //config:       depends on XARGS
33 //config:       help
34 //config:         Support -p: prompt the user whether to run each command
35 //config:         line and read a line from the terminal.
36 //config:
37 //config:config FEATURE_XARGS_SUPPORT_QUOTES
38 //config:       bool "Enable single and double quotes and backslash"
39 //config:       default n
40 //config:       depends on XARGS
41 //config:       help
42 //config:         Support quoting in the input.
43 //config:
44 //config:config FEATURE_XARGS_SUPPORT_TERMOPT
45 //config:       bool "Enable -x: exit if -s or -n is exceeded"
46 //config:       default n
47 //config:       depends on XARGS
48 //config:       help
49 //config:         Support -x: exit if the command size (see the -s or -n option)
50 //config:         is exceeded.
51 //config:
52 //config:config FEATURE_XARGS_SUPPORT_ZERO_TERM
53 //config:       bool "Enable -0: NUL-terminated input"
54 //config:       default n
55 //config:       depends on XARGS
56 //config:       help
57 //config:         Support -0: input items are terminated by a NUL character
58 //config:         instead of whitespace, and the quotes and backslash
59 //config:         are not special.
60
61 #include "libbb.h"
62
63 /* This is a NOEXEC applet. Be very careful! */
64
65
66 /* COMPAT:  SYSV version defaults size (and has a max value of) to 470.
67    We try to make it as large as possible. */
68 #if !defined(ARG_MAX) && defined(_SC_ARG_MAX)
69 # define ARG_MAX sysconf(_SC_ARG_MAX)
70 #endif
71 #if !defined(ARG_MAX)
72 # define ARG_MAX 470
73 #endif
74
75
76 #ifdef TEST
77 # ifndef ENABLE_FEATURE_XARGS_SUPPORT_CONFIRMATION
78 #  define ENABLE_FEATURE_XARGS_SUPPORT_CONFIRMATION 1
79 # endif
80 # ifndef ENABLE_FEATURE_XARGS_SUPPORT_QUOTES
81 #  define ENABLE_FEATURE_XARGS_SUPPORT_QUOTES 1
82 # endif
83 # ifndef ENABLE_FEATURE_XARGS_SUPPORT_TERMOPT
84 #  define ENABLE_FEATURE_XARGS_SUPPORT_TERMOPT 1
85 # endif
86 # ifndef ENABLE_FEATURE_XARGS_SUPPORT_ZERO_TERM
87 #  define ENABLE_FEATURE_XARGS_SUPPORT_ZERO_TERM 1
88 # endif
89 #endif
90
91 /*
92    This function has special algorithm.
93    Don't use fork and include to main!
94 */
95 static int xargs_exec(char **args)
96 {
97         int status;
98
99         status = spawn_and_wait(args);
100         if (status < 0) {
101                 bb_simple_perror_msg(args[0]);
102                 return errno == ENOENT ? 127 : 126;
103         }
104         if (status == 255) {
105                 bb_error_msg("%s: exited with status 255; aborting", args[0]);
106                 return 124;
107         }
108         if (status >= 0x180) {
109                 bb_error_msg("%s: terminated by signal %d",
110                         args[0], status - 0x180);
111                 return 125;
112         }
113         if (status)
114                 return 123;
115         return 0;
116 }
117
118
119 typedef struct xlist_t {
120         struct xlist_t *link;
121         size_t length;
122         char xstr[1];
123 } xlist_t;
124
125 static smallint eof_stdin_detected;
126
127 #define ISBLANK(c) ((c) == ' ' || (c) == '\t')
128 #define ISSPACE(c) (ISBLANK(c) || (c) == '\n' || (c) == '\r' \
129                     || (c) == '\f' || (c) == '\v')
130
131 #if ENABLE_FEATURE_XARGS_SUPPORT_QUOTES
132 static xlist_t *process_stdin(xlist_t *list_arg,
133         const char *eof_str, size_t mc, char *buf)
134 {
135 #define NORM      0
136 #define QUOTE     1
137 #define BACKSLASH 2
138 #define SPACE     4
139
140         char *s = NULL;         /* start word */
141         char *p = NULL;         /* pointer to end word */
142         char q = '\0';          /* quote char */
143         char state = NORM;
144         char eof_str_detected = 0;
145         size_t line_l = 0;      /* size loaded args line */
146         int c;                  /* current char */
147         xlist_t *cur;
148         xlist_t *prev;
149
150         prev = cur = list_arg;
151         while (1) {
152                 if (!cur) break;
153                 prev = cur;
154                 line_l += cur->length;
155                 cur = cur->link;
156         }
157
158         while (!eof_stdin_detected) {
159                 c = getchar();
160                 if (c == EOF) {
161                         eof_stdin_detected = 1;
162                         if (s)
163                                 goto unexpected_eof;
164                         break;
165                 }
166                 if (eof_str_detected)
167                         continue;
168                 if (state == BACKSLASH) {
169                         state = NORM;
170                         goto set;
171                 } else if (state == QUOTE) {
172                         if (c != q)
173                                 goto set;
174                         q = '\0';
175                         state = NORM;
176                 } else { /* if (state == NORM) */
177                         if (ISSPACE(c)) {
178                                 if (s) {
179  unexpected_eof:
180                                         state = SPACE;
181                                         c = '\0';
182                                         goto set;
183                                 }
184                         } else {
185                                 if (s == NULL)
186                                         s = p = buf;
187                                 if (c == '\\') {
188                                         state = BACKSLASH;
189                                 } else if (c == '\'' || c == '"') {
190                                         q = c;
191                                         state = QUOTE;
192                                 } else {
193  set:
194                                         if ((size_t)(p - buf) >= mc)
195                                                 bb_error_msg_and_die("argument line too long");
196                                         *p++ = c;
197                                 }
198                         }
199                 }
200                 if (state == SPACE) {   /* word's delimiter or EOF detected */
201                         if (q) {
202                                 bb_error_msg_and_die("unmatched %s quote",
203                                         q == '\'' ? "single" : "double");
204                         }
205                         /* word loaded */
206                         if (eof_str) {
207                                 eof_str_detected = (strcmp(s, eof_str) == 0);
208                         }
209                         if (!eof_str_detected) {
210                                 size_t length = (p - buf);
211                                 /* Dont xzalloc - it can be quite big */
212                                 cur = xmalloc(offsetof(xlist_t, xstr) + length);
213                                 cur->link = NULL;
214                                 cur->length = length;
215                                 memcpy(cur->xstr, s, length);
216                                 if (prev == NULL) {
217                                         list_arg = cur;
218                                 } else {
219                                         prev->link = cur;
220                                 }
221                                 prev = cur;
222                                 line_l += length;
223                                 if (line_l > mc) {
224                                         /* stop memory usage :-) */
225                                         break;
226                                 }
227                         }
228                         s = NULL;
229                         state = NORM;
230                 }
231         }
232         return list_arg;
233 }
234 #else
235 /* The variant does not support single quotes, double quotes or backslash */
236 static xlist_t *process_stdin(xlist_t *list_arg,
237                 const char *eof_str, size_t mc, char *buf)
238 {
239
240         int c;                  /* current char */
241         char eof_str_detected = 0;
242         char *s = NULL;         /* start word */
243         char *p = NULL;         /* pointer to end word */
244         size_t line_l = 0;      /* size loaded args line */
245         xlist_t *cur;
246         xlist_t *prev;
247
248         prev = cur = list_arg;
249         while (1) {
250                 if (!cur) break;
251                 prev = cur;
252                 line_l += cur->length;
253                 cur = cur->link;
254         }
255
256         while (!eof_stdin_detected) {
257                 c = getchar();
258                 if (c == EOF) {
259                         eof_stdin_detected = 1;
260                 }
261                 if (eof_str_detected)
262                         continue;
263                 if (c == EOF || ISSPACE(c)) {
264                         if (s == NULL)
265                                 continue;
266                         c = EOF;
267                 }
268                 if (s == NULL)
269                         s = p = buf;
270                 if ((size_t)(p - buf) >= mc)
271                         bb_error_msg_and_die("argument line too long");
272                 *p++ = (c == EOF ? '\0' : c);
273                 if (c == EOF) { /* word's delimiter or EOF detected */
274                         /* word loaded */
275                         if (eof_str) {
276                                 eof_str_detected = (strcmp(s, eof_str) == 0);
277                         }
278                         if (!eof_str_detected) {
279                                 size_t length = (p - buf);
280                                 /* Dont xzalloc - it can be quite big */
281                                 cur = xmalloc(offsetof(xlist_t, xstr) + length);
282                                 cur->link = NULL;
283                                 cur->length = length;
284                                 memcpy(cur->xstr, s, length);
285                                 if (prev == NULL) {
286                                         list_arg = cur;
287                                 } else {
288                                         prev->link = cur;
289                                 }
290                                 prev = cur;
291                                 line_l += length;
292                                 if (line_l > mc) {
293                                         /* stop memory usage :-) */
294                                         break;
295                                 }
296                                 s = NULL;
297                         }
298                 }
299         }
300         return list_arg;
301 }
302 #endif /* FEATURE_XARGS_SUPPORT_QUOTES */
303
304
305 #if ENABLE_FEATURE_XARGS_SUPPORT_CONFIRMATION
306 /* Prompt the user for a response, and
307    if the user responds affirmatively, return true;
308    otherwise, return false. Uses "/dev/tty", not stdin. */
309 static int xargs_ask_confirmation(void)
310 {
311         FILE *tty_stream;
312         int c, savec;
313
314         tty_stream = xfopen_for_read(CURRENT_TTY);
315         fputs(" ?...", stderr);
316         fflush_all();
317         c = savec = getc(tty_stream);
318         while (c != EOF && c != '\n')
319                 c = getc(tty_stream);
320         fclose(tty_stream);
321         return (savec == 'y' || savec == 'Y');
322 }
323 #else
324 # define xargs_ask_confirmation() 1
325 #endif /* FEATURE_XARGS_SUPPORT_CONFIRMATION */
326
327 #if ENABLE_FEATURE_XARGS_SUPPORT_ZERO_TERM
328 static xlist_t *process0_stdin(xlist_t *list_arg,
329                 const char *eof_str UNUSED_PARAM, size_t mc, char *buf)
330 {
331         int c;                  /* current char */
332         char *s = NULL;         /* start word */
333         char *p = NULL;         /* pointer to end word */
334         size_t line_l = 0;      /* size loaded args line */
335         xlist_t *cur;
336         xlist_t *prev;
337
338         prev = cur = list_arg;
339         while (1) {
340                 if (!cur) break;
341                 prev = cur;
342                 line_l += cur->length;
343                 cur = cur->link;
344         }
345
346         while (!eof_stdin_detected) {
347                 c = getchar();
348                 if (c == EOF) {
349                         eof_stdin_detected = 1;
350                         if (s == NULL)
351                                 break;
352                         c = '\0';
353                 }
354                 if (s == NULL)
355                         s = p = buf;
356                 if ((size_t)(p - buf) >= mc)
357                         bb_error_msg_and_die("argument line too long");
358                 *p++ = c;
359                 if (c == '\0') {   /* word's delimiter or EOF detected */
360                         /* word loaded */
361                         size_t length = (p - buf);
362                         /* Dont xzalloc - it can be quite big */
363                         cur = xmalloc(offsetof(xlist_t, xstr) + length);
364                         cur->link = NULL;
365                         cur->length = length;
366                         memcpy(cur->xstr, s, length);
367                         if (prev == NULL) {
368                                 list_arg = cur;
369                         } else {
370                                 prev->link = cur;
371                         }
372                         prev = cur;
373                         line_l += length;
374                         if (line_l > mc) {
375                                 /* stop memory usage :-) */
376                                 break;
377                         }
378                         s = NULL;
379                 }
380         }
381         return list_arg;
382 }
383 #endif /* FEATURE_XARGS_SUPPORT_ZERO_TERM */
384
385 /* Correct regardless of combination of CONFIG_xxx */
386 enum {
387         OPTBIT_VERBOSE = 0,
388         OPTBIT_NO_EMPTY,
389         OPTBIT_UPTO_NUMBER,
390         OPTBIT_UPTO_SIZE,
391         OPTBIT_EOF_STRING,
392         OPTBIT_EOF_STRING1,
393         IF_FEATURE_XARGS_SUPPORT_CONFIRMATION(OPTBIT_INTERACTIVE,)
394         IF_FEATURE_XARGS_SUPPORT_TERMOPT(     OPTBIT_TERMINATE  ,)
395         IF_FEATURE_XARGS_SUPPORT_ZERO_TERM(   OPTBIT_ZEROTERM   ,)
396
397         OPT_VERBOSE     = 1 << OPTBIT_VERBOSE    ,
398         OPT_NO_EMPTY    = 1 << OPTBIT_NO_EMPTY   ,
399         OPT_UPTO_NUMBER = 1 << OPTBIT_UPTO_NUMBER,
400         OPT_UPTO_SIZE   = 1 << OPTBIT_UPTO_SIZE  ,
401         OPT_EOF_STRING  = 1 << OPTBIT_EOF_STRING , /* GNU: -e[<param>] */
402         OPT_EOF_STRING1 = 1 << OPTBIT_EOF_STRING1, /* SUS: -E<param> */
403         OPT_INTERACTIVE = IF_FEATURE_XARGS_SUPPORT_CONFIRMATION((1 << OPTBIT_INTERACTIVE)) + 0,
404         OPT_TERMINATE   = IF_FEATURE_XARGS_SUPPORT_TERMOPT(     (1 << OPTBIT_TERMINATE  )) + 0,
405         OPT_ZEROTERM    = IF_FEATURE_XARGS_SUPPORT_ZERO_TERM(   (1 << OPTBIT_ZEROTERM   )) + 0,
406 };
407 #define OPTION_STR "+trn:s:e::E:" \
408         IF_FEATURE_XARGS_SUPPORT_CONFIRMATION("p") \
409         IF_FEATURE_XARGS_SUPPORT_TERMOPT(     "x") \
410         IF_FEATURE_XARGS_SUPPORT_ZERO_TERM(   "0")
411
412 int xargs_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
413 int xargs_main(int argc, char **argv)
414 {
415         char **args;
416         int i, n;
417         xlist_t *list = NULL;
418         xlist_t *cur;
419         int child_error = 0;
420         char *max_args, *max_chars;
421         int n_max_arg;
422         const char *eof_str = NULL;
423         unsigned opt;
424         size_t n_max_chars;
425 #if ENABLE_FEATURE_XARGS_SUPPORT_ZERO_TERM
426         xlist_t* (*read_args)(xlist_t*, const char*, size_t, char*) = process_stdin;
427 #else
428 #define read_args process_stdin
429 #endif
430
431         opt = getopt32(argv, OPTION_STR, &max_args, &max_chars, &eof_str, &eof_str);
432
433         /* -E ""? You may wonder why not just omit -E?
434          * This is used for portability:
435          * old xargs was using "_" as default for -E / -e */
436         if ((opt & OPT_EOF_STRING1) && eof_str[0] == '\0')
437                 eof_str = NULL;
438
439         if (opt & OPT_ZEROTERM)
440                 IF_FEATURE_XARGS_SUPPORT_ZERO_TERM(read_args = process0_stdin);
441
442         argv += optind;
443         argc -= optind;
444         if (!argc) {
445                 /* default behavior is to echo all the filenames */
446                 *argv = (char*)"echo";
447                 argc++;
448         }
449
450         n_max_chars = ARG_MAX; /* might be calling sysconf(_SC_ARG_MAX) */
451         if (n_max_chars < 4*1024); /* paranoia */
452                 n_max_chars = LONG_MAX;
453         /* The Open Group Base Specifications Issue 6:
454          * "The xargs utility shall limit the command line length such that
455          * when the command line is invoked, the combined argument
456          * and environment lists (see the exec family of functions
457          * in the System Interfaces volume of IEEE Std 1003.1-2001)
458          * shall not exceed {ARG_MAX}-2048 bytes".
459          */
460         n_max_chars -= 2048;
461         /* Sanity check for systems with huge ARG_MAX defines (e.g., Suns which
462          * have it at 1 meg).  Things will work fine with a large ARG_MAX but it
463          * will probably hurt the system more than it needs to; an array of this
464          * size is allocated.
465          */
466         if (n_max_chars > 20 * 1024)
467                 n_max_chars = 20 * 1024;
468
469         if (opt & OPT_UPTO_SIZE) {
470                 size_t n_chars = 0;
471                 n_max_chars = xatoul_range(max_chars, 1, n_max_chars);
472                 for (i = 0; i < argc; i++) {
473                         n_chars += strlen(*argv) + 1;
474                 }
475                 if (n_max_chars <= n_chars) {
476                         bb_error_msg_and_die("can't fit single argument within argument list size limit");
477                 }
478                 n_max_chars -= n_chars;
479         }
480         max_chars = xmalloc(n_max_chars);
481
482         if (opt & OPT_UPTO_NUMBER) {
483                 n_max_arg = xatoul_range(max_args, 1, INT_MAX);
484         } else {
485                 n_max_arg = n_max_chars;
486         }
487
488         while ((list = read_args(list, eof_str, n_max_chars, max_chars)) != NULL ||
489                 !(opt & OPT_NO_EMPTY))
490         {
491                 size_t n_chars = 0;
492                 opt |= OPT_NO_EMPTY;
493                 n = 0;
494 #if ENABLE_FEATURE_XARGS_SUPPORT_TERMOPT
495                 for (cur = list; cur;) {
496                         n_chars += cur->length;
497                         n++;
498                         cur = cur->link;
499                         if (n_chars > n_max_chars || (n == n_max_arg && cur)) {
500                                 if (opt & OPT_TERMINATE)
501                                         bb_error_msg_and_die("argument list too long");
502                                 break;
503                         }
504                 }
505 #else
506                 for (cur = list; cur; cur = cur->link) {
507                         n_chars += cur->length;
508                         n++;
509                         if (n_chars > n_max_chars || n == n_max_arg) {
510                                 break;
511                         }
512                 }
513 #endif /* FEATURE_XARGS_SUPPORT_TERMOPT */
514
515                 /* allocate pointers for execvp:
516                    argc*arg, n*arg from stdin, NULL */
517                 args = xzalloc((n + argc + 1) * sizeof(char *));
518
519                 /* store the command to be executed
520                    (taken from the command line) */
521                 for (i = 0; i < argc; i++)
522                         args[i] = argv[i];
523                 /* (taken from stdin) */
524                 for (cur = list; n; cur = cur->link) {
525                         args[i++] = cur->xstr;
526                         n--;
527                 }
528
529                 if (opt & (OPT_INTERACTIVE | OPT_VERBOSE)) {
530                         for (i = 0; args[i]; i++) {
531                                 if (i)
532                                         fputc(' ', stderr);
533                                 fputs(args[i], stderr);
534                         }
535                         if (!(opt & OPT_INTERACTIVE))
536                                 fputc('\n', stderr);
537                 }
538                 if (!(opt & OPT_INTERACTIVE) || xargs_ask_confirmation()) {
539                         child_error = xargs_exec(args);
540                 }
541
542                 /* clean up */
543                 for (i = argc; args[i]; i++) {
544                         cur = list;
545                         list = list->link;
546                         free(cur);
547                 }
548                 free(args);
549                 if (child_error > 0 && child_error != 123) {
550                         break;
551                 }
552         } /* while */
553         if (ENABLE_FEATURE_CLEAN_UP)
554                 free(max_chars);
555         return child_error;
556 }
557
558
559 #ifdef TEST
560
561 const char *applet_name = "debug stuff usage";
562
563 void bb_show_usage(void)
564 {
565         fprintf(stderr, "Usage: %s [-p] [-r] [-t] -[x] [-n max_arg] [-s max_chars]\n",
566                 applet_name);
567         exit(EXIT_FAILURE);
568 }
569
570 int main(int argc, char **argv)
571 {
572         return xargs_main(argc, argv);
573 }
574 #endif /* TEST */