aboutsummaryrefslogtreecommitdiff
path: root/toys/posix/xargs.c
blob: f76a5fce415a4dd8ff83c9bfdbaecb9a33d95db4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
/* xargs.c - Run command with arguments taken from stdin.
 *
 * Copyright 2011 Rob Landley <rob@landley.net>
 *
 * See http://opengroup.org/onlinepubs/9699919799/utilities/xargs.html
 *
 * TODO: Rich's whitespace objection, env size isn't fixed anymore.
 * TODO: -I	Insert mode
 * TODO: -L	Max number of lines of input per command
 * TODO: -x	Exit if can't fit everything in one command
 * TODO: -P NUM	Run up to NUM processes at once

USE_XARGS(NEWTOY(xargs, "^E:P#optrn#<1(max-args)s#0[!0E]", TOYFLAG_USR|TOYFLAG_BIN))

config XARGS
  bool "xargs"
  default y
  help
    usage: xargs [-0prt] [-s NUM] [-n NUM] [-E STR] COMMAND...

    Run command line one or more times, appending arguments from stdin.

    If COMMAND exits with 255, don't launch another even if arguments remain.

    -0	Each argument is NULL terminated, no whitespace or quote processing
    -E	Stop at line matching string
    -n	Max number of arguments per command
    -o	Open tty for COMMAND's stdin (default /dev/null)
    -p	Prompt for y/n from tty before running each command
    -r	Don't run command with empty input (otherwise always run command once)
    -s	Size in bytes per command line
    -t	Trace, print command line to stderr
*/

#define FOR_xargs
#include "toys.h"

GLOBALS(
  long s, n, P;
  char *E;

  long entries, bytes;
  char delim;
  FILE *tty;
)

// If !entry count TT.bytes and TT.entries, stopping at max.
// Otherwise, fill out entry[].

// Returning NULL means need more data.
// Returning char * means hit data limits, start of data left over
// Returning 1 means hit data limits, but consumed all data
// Returning 2 means hit -E STR

static char *handle_entries(char *data, char **entry)
{
  if (TT.delim) {
    char *save, *s = data;

    // Chop up whitespace delimited string into args
    while (*s) {
      while (isspace(*s)) {
        if (entry) *s = 0;
        s++;
      }

      if (TT.n && TT.entries >= TT.n)
        return *s ? s : (char *)1;

      if (!*s) break;
      save = s;

      // We ought to add sizeof(char *) to TT.bytes to be correct, but we don't
      // for bug compatibility with busybox 1.30.1 and findutils 4.7.0.

      for (;;) {
        if (++TT.bytes >= TT.s && TT.s) return save;
        if (!*s || isspace(*s)) break;
        s++;
      }
      if (TT.E && strstart(&save, TT.E)) return (char *)2;
      if (entry) entry[TT.entries] = save;
      ++TT.entries;
    }

  // -0 support
  } else {
    TT.bytes += sizeof(char *)+strlen(data)+1;
    if ((TT.s && TT.bytes >= TT.s) || (TT.n && TT.entries >= TT.n)) return data;
    if (entry) entry[TT.entries] = data;
    TT.entries++;
  }

  return 0;
}

void xargs_main(void)
{
  struct double_list *dlist = 0, *dtemp;
  int entries, bytes, done = 0, ran_once = 0, status;
  char *data = 0, **out;
  pid_t pid;

  // POSIX requires that we never hit the ARG_MAX limit, even if we try to
  // with -s. POSIX also says we have to reserve 2048 bytes "to guarantee
  // that the invoked utility has room to modify its environment variables
  // and command line arguments and still be able to invoke another utility",
  // though obviously that's not really something you can guarantee.
  bytes = sysconf(_SC_ARG_MAX) - environ_bytes() - 2048;
  if (!TT.s || TT.s > bytes) TT.s = bytes;

  TT.delim = '\n'*!FLAG(0);

  // If no optargs, call echo.
  if (!toys.optc) {
    free(toys.optargs);
    *(toys.optargs = xzalloc(2*sizeof(char *)))="echo";
    toys.optc = 1;
  }

  // count entries
  for (entries = 0, bytes = -1; entries < toys.optc; entries++, bytes++)
    bytes += strlen(toys.optargs[entries]);
  if (bytes >= TT.s) error_exit("argument too long");

  // Loop through exec chunks.
  while (data || !done) {
    TT.entries = 0;
    TT.bytes = bytes;

    // Loop reading input
    for (;;) {

      // Read line
      if (!data) {
        ssize_t l = 0;
        if (getdelim(&data, (size_t *)&l, TT.delim, stdin)<0) {
          data = 0;
          done++;

          break;
        }
      }
      dlist_add(&dlist, data);

      // Count data used
      if (!(data = handle_entries(data, 0))) continue;
      if (data == (char *)2) done++;
      if ((unsigned long)data <= 2) data = 0;
      else data = xstrdup(data);

      break;
    }

    if (!TT.entries) {
      if (data) error_exit("argument too long");
      else if (ran_once) return;
      else if (FLAG(r)) continue;
    }

    // Fill out command line to exec
    out = xzalloc((entries+TT.entries+1)*sizeof(char *));
    memcpy(out, toys.optargs, entries*sizeof(char *));
    TT.entries = 0;
    TT.bytes = bytes;
    if (dlist) dlist->prev->next = 0;
    for (dtemp = dlist; dtemp; dtemp = dtemp->next)
      handle_entries(dtemp->data, out+entries);

    if (FLAG(p) || FLAG(t)) {
      int i;

      for (i = 0; out[i]; ++i) fprintf(stderr, "%s ", out[i]);
      if (FLAG(p)) {
        fprintf(stderr, "?");
        if (!TT.tty) TT.tty = xfopen("/dev/tty", "re");
        if (!fyesno(TT.tty, 0)) goto skip;
      } else fprintf(stderr, "\n");
    }

    if (!(pid = XVFORK())) {
      close(0);
      xopen_stdio(FLAG(o) ? "/dev/tty" : "/dev/null", O_RDONLY);
      xexec(out);
    }
    waitpid(pid, &status, 0);

    // xargs is yet another weird collection of exit value special cases,
    // different from all the others.
    if (WIFEXITED(status)) {
      if (WEXITSTATUS(status) == 126 || WEXITSTATUS(status) == 127) {
        toys.exitval = WEXITSTATUS(status);
        return;
      } else if (WEXITSTATUS(status) >= 1 && WEXITSTATUS(status) <= 125) {
        toys.exitval = 123;
      } else if (WEXITSTATUS(status) == 255) {
        error_msg("%s: exited with status 255; aborting", out[0]);
        toys.exitval = 124;
        return;
      }
    } else toys.exitval = 127;

    // Abritrary number of execs, can't just leak memory each time...
skip:
    ran_once = 1;
    while (dlist) {
      struct double_list *dtemp = dlist->next;

      free(dlist->data);
      free(dlist);
      dlist = dtemp;
    }
    free(out);
  }
  if (TT.tty) fclose(TT.tty);
}