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