xargs: do not decrease orig_arg_max to negative values
[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 #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 #if !defined(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         const char *eof_str = NULL;
382         unsigned opt;
383         size_t n_max_chars;
384 #if ENABLE_FEATURE_XARGS_SUPPORT_ZERO_TERM
385         xlist_t* (*read_args)(xlist_t*, const char*, size_t, char*) = process_stdin;
386 #else
387 #define read_args process_stdin
388 #endif
389
390         opt = getopt32(argv, OPTION_STR, &max_args, &max_chars, &eof_str, &eof_str);
391
392         /* -E ""? You may wonder why not just omit -E?
393          * This is used for portability:
394          * old xargs was using "_" as default for -E / -e */
395         if ((opt & OPT_EOF_STRING1) && eof_str[0] == '\0')
396                 eof_str = NULL;
397
398         if (opt & OPT_ZEROTERM)
399                 IF_FEATURE_XARGS_SUPPORT_ZERO_TERM(read_args = process0_stdin);
400
401         argv += optind;
402         argc -= optind;
403         if (!argc) {
404                 /* default behavior is to echo all the filenames */
405                 *argv = (char*)"echo";
406                 argc++;
407         }
408
409         n_max_chars = ARG_MAX; /* might be calling sysconf(_SC_ARG_MAX) */
410         if (n_max_chars < 4*1024); /* paranoia */
411                 n_max_chars = LONG_MAX;
412         /* The Open Group Base Specifications Issue 6:
413          * "The xargs utility shall limit the command line length such that
414          * when the command line is invoked, the combined argument
415          * and environment lists (see the exec family of functions
416          * in the System Interfaces volume of IEEE Std 1003.1-2001)
417          * shall not exceed {ARG_MAX}-2048 bytes".
418          */
419         n_max_chars -= 2048;
420         /* Sanity check for systems with huge ARG_MAX defines (e.g., Suns which
421          * have it at 1 meg).  Things will work fine with a large ARG_MAX but it
422          * will probably hurt the system more than it needs to; an array of this
423          * size is allocated.
424          */
425         if (n_max_chars > 20 * 1024)
426                 n_max_chars = 20 * 1024;
427
428         if (opt & OPT_UPTO_SIZE) {
429                 size_t n_chars = 0;
430                 n_max_chars = xatoul_range(max_chars, 1, n_max_chars);
431                 for (i = 0; i < argc; i++) {
432                         n_chars += strlen(*argv) + 1;
433                 }
434                 if (n_max_chars <= n_chars) {
435                         bb_error_msg_and_die("can't fit single argument within argument list size limit");
436                 }
437                 n_max_chars -= n_chars;
438         }
439         max_chars = xmalloc(n_max_chars);
440
441         if (opt & OPT_UPTO_NUMBER) {
442                 n_max_arg = xatoul_range(max_args, 1, INT_MAX);
443         } else {
444                 n_max_arg = n_max_chars;
445         }
446
447         while ((list = read_args(list, eof_str, n_max_chars, max_chars)) != NULL ||
448                 !(opt & OPT_NO_EMPTY))
449         {
450                 size_t n_chars = 0;
451                 opt |= OPT_NO_EMPTY;
452                 n = 0;
453 #if ENABLE_FEATURE_XARGS_SUPPORT_TERMOPT
454                 for (cur = list; cur;) {
455                         n_chars += cur->length;
456                         n++;
457                         cur = cur->link;
458                         if (n_chars > n_max_chars || (n == n_max_arg && cur)) {
459                                 if (opt & OPT_TERMINATE)
460                                         bb_error_msg_and_die("argument list too long");
461                                 break;
462                         }
463                 }
464 #else
465                 for (cur = list; cur; cur = cur->link) {
466                         n_chars += cur->length;
467                         n++;
468                         if (n_chars > n_max_chars || n == n_max_arg) {
469                                 break;
470                         }
471                 }
472 #endif /* FEATURE_XARGS_SUPPORT_TERMOPT */
473
474                 /* allocate pointers for execvp:
475                    argc*arg, n*arg from stdin, NULL */
476                 args = xzalloc((n + argc + 1) * sizeof(char *));
477
478                 /* store the command to be executed
479                    (taken from the command line) */
480                 for (i = 0; i < argc; i++)
481                         args[i] = argv[i];
482                 /* (taken from stdin) */
483                 for (cur = list; n; cur = cur->link) {
484                         args[i++] = cur->xstr;
485                         n--;
486                 }
487
488                 if (opt & (OPT_INTERACTIVE | OPT_VERBOSE)) {
489                         for (i = 0; args[i]; i++) {
490                                 if (i)
491                                         fputc(' ', stderr);
492                                 fputs(args[i], stderr);
493                         }
494                         if (!(opt & OPT_INTERACTIVE))
495                                 fputc('\n', stderr);
496                 }
497                 if (!(opt & OPT_INTERACTIVE) || xargs_ask_confirmation()) {
498                         child_error = xargs_exec(args);
499                 }
500
501                 /* clean up */
502                 for (i = argc; args[i]; i++) {
503                         cur = list;
504                         list = list->link;
505                         free(cur);
506                 }
507                 free(args);
508                 if (child_error > 0 && child_error != 123) {
509                         break;
510                 }
511         } /* while */
512         if (ENABLE_FEATURE_CLEAN_UP)
513                 free(max_chars);
514         return child_error;
515 }
516
517
518 #ifdef TEST
519
520 const char *applet_name = "debug stuff usage";
521
522 void bb_show_usage(void)
523 {
524         fprintf(stderr, "Usage: %s [-p] [-r] [-t] -[x] [-n max_arg] [-s max_chars]\n",
525                 applet_name);
526         exit(EXIT_FAILURE);
527 }
528
529 int main(int argc, char **argv)
530 {
531         return xargs_main(argc, argv);
532 }
533 #endif /* TEST */