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