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