master xplshn/aruu / cmd / posix / xargs.c
  1/* See LICENSE file for copyright and license details. */
  2
  3#include <sys/wait.h>
  4
  5#include <errno.h>
  6#include <limits.h>
  7#include <stdint.h>
  8#include <stdio.h>
  9#include <stdlib.h>
 10#include <string.h>
 11#include <unistd.h>
 12
 13#include "util.h"
 14#include "wexec.h"
 15
 16#define NARGS 10000
 17
 18static int   inputc(void);
 19static void  fillargbuf(int);
 20static int   eatspace(void);
 21static int   parsequote(int);
 22static int   parseescape(void);
 23static char *poparg(void);
 24static void  waitchld(int);
 25static void  spawn(void);
 26
 27static size_t argbsz;
 28static size_t argbpos;
 29static size_t maxargs;
 30static size_t curprocs, maxprocs = 1;
 31static int    nerrors;
 32static int    nulflag, nflag, pflag, rflag, tflag, xflag, Iflag, Lflag;
 33static size_t maxlines;
 34static int    newline = 1;
 35static int    arg_newline;
 36static char  *argb;
 37static char **cmd;
 38static char  *eofstr;
 39
 40static int
 41inputc(void)
 42{
 43  int ch;
 44
 45  ch = getc(stdin);
 46  if (ch == EOF && ferror(stdin))
 47    eprintf("getc <stdin>:");
 48  if (ch == '\n')
 49    newline = 1;
 50
 51  return ch;
 52}
 53
 54static void
 55fillargbuf(int ch)
 56{
 57  if (argbpos >= argbsz) {
 58    argbsz = argbpos == 0 ? 1 : argbsz * 2;
 59    argb   = erealloc(argb, argbsz);
 60  }
 61  argb[argbpos] = ch;
 62}
 63
 64static int
 65eatspace(void)
 66{
 67  int ch;
 68
 69  while ((ch = inputc()) != EOF) {
 70    if (nulflag || !(ch == ' ' || ch == '\t' || ch == '\n')) {
 71      ungetc(ch, stdin);
 72      return ch;
 73    }
 74  }
 75  return -1;
 76}
 77
 78static int
 79parsequote(int q)
 80{
 81  int ch;
 82
 83  while ((ch = inputc()) != EOF) {
 84    if (ch == q)
 85      return 0;
 86    if (ch != '\n') {
 87      fillargbuf(ch);
 88      argbpos++;
 89    }
 90  }
 91
 92  return -1;
 93}
 94
 95static int
 96parseescape(void)
 97{
 98  int ch;
 99
100  if ((ch = inputc()) != EOF) {
101    fillargbuf(ch);
102    argbpos++;
103    return ch;
104  }
105
106  return -1;
107}
108
109static char *
110poparg(void)
111{
112  int ch;
113
114  argbpos = 0;
115  if (eatspace() < 0)
116    return NULL;
117
118  arg_newline = newline;
119  newline     = 0;
120  while ((ch = inputc()) != EOF) {
121    /* NUL separator: no escaping */
122    if (nulflag) {
123      if (ch == '\0')
124        goto out;
125      else
126        goto fill;
127    }
128
129    switch (ch) {
130      case ' ':
131      case '\t':
132        if (Iflag)
133          goto fill;
134      case '\n':
135        goto out;
136      case '\'':
137        if (parsequote('\'') < 0)
138          eprintf("unterminated single quote\n");
139        break;
140      case '\"':
141        if (parsequote('\"') < 0)
142          eprintf("unterminated double quote\n");
143        break;
144      case '\\':
145        if (parseescape() < 0)
146          eprintf("backslash at EOF\n");
147        break;
148      default:
149      fill:
150        fillargbuf(ch);
151        argbpos++;
152        break;
153    }
154  }
155out:
156  fillargbuf('\0');
157
158  return (eofstr && !strcmp(argb, eofstr)) ? NULL : argb;
159}
160
161static void
162waitchld(int waitall)
163{
164  pid_t pid;
165  int   status;
166
167  while ((pid = waitpid(-1, &status, !waitall && curprocs < maxprocs ? WNOHANG : 0)) > 0) {
168    curprocs--;
169
170    if (WIFEXITED(status)) {
171      if (WEXITSTATUS(status) == 255)
172        exit(124);
173      if (WEXITSTATUS(status) == 127 || WEXITSTATUS(status) == 126)
174        exit(WEXITSTATUS(status));
175      if (WEXITSTATUS(status))
176        nerrors++;
177    }
178    if (WIFSIGNALED(status))
179      exit(125);
180  }
181  if (pid == -1 && errno != ECHILD)
182    eprintf("waitpid:");
183}
184
185static int
186prompt(void)
187{
188  FILE *fp;
189  int   ch, ret;
190
191  if (!(fp = fopen("/dev/tty", "r")))
192    return -1;
193
194  fputs("?...", stderr);
195  fflush(stderr);
196
197  ch  = fgetc(fp);
198  ret = (ch == 'y' || ch == 'Y');
199  if (ch != EOF && ch != '\n') {
200    while ((ch = fgetc(fp)) != EOF) {
201      if (ch == '\n')
202        break;
203    }
204  }
205
206  fclose(fp);
207
208  return ret;
209}
210
211static void
212spawn(void)
213{
214  int    savederrno;
215  int    first = 1;
216  char **p;
217
218  if (pflag || tflag) {
219    for (p = cmd; *p; p++) {
220      if (!first)
221        fputc(' ', stderr);
222      fputs(*p, stderr);
223      first = 0;
224    }
225    if (pflag) {
226      switch (prompt()) {
227        case -1:
228          break; /* error */
229        case 0:
230          return; /* no */
231        case 1:
232          goto dospawn; /* yes */
233      }
234    }
235    fputc('\n', stderr);
236    fflush(stderr);
237  }
238
239dospawn:
240  switch (fork()) {
241    case -1:
242      eprintf("fork:");
243      /* fallthrough */
244    case 0:
245      wexecvp_self(*cmd, cmd);
246      savederrno = errno;
247      weprintf("wexecvp %s:", *cmd);
248      _exit(126 + (savederrno == ENOENT));
249  }
250  curprocs++;
251  waitchld(0);
252}
253
254static void
255usage(void)
256{
257  eprintf(
258      "usage: %s [-0prtx] [-E eofstr] [-I replstr] [-L maxlines] [-n "
259      "num] [-P maxprocs] [-s num] "
260      "[cmd [arg ...]]\n",
261      argv0
262  );
263}
264
265// ?man xargs: build and run command lines
266// ?man arguments: -n
267// ?man execute commands built from standard input arguments
268int
269main(int argc, char *argv[])
270{
271  int    ret = 0, leftover = 0, i, j;
272  size_t argsz, argmaxsz;
273  size_t arglen, a;
274  char  *arg = "";
275  char  *replstr;
276
277  if ((argmaxsz = sysconf(_SC_ARG_MAX)) == (size_t)-1)
278    argmaxsz = _POSIX_ARG_MAX;
279  /* Leave some room for environment variables */
280  argmaxsz -= 4096;
281  cmd = emalloc(NARGS * sizeof(*cmd));
282
283  ARGBEGIN
284  {
285    // ?man -0: specify option flag
286    case '0':
287      nulflag = 1;
288      break;
289    // ?man -n:num: print line numbers or counts
290    case 'n':
291      nflag   = 1;
292      maxargs = estrtonum(
293          EARGF(usage()), 1, MIN((unsigned long long)SIZE_MAX, (unsigned long long)LLONG_MAX)
294      );
295      break;
296    // ?man -p: preserve file attributes
297    case 'p':
298      pflag = 1;
299      break;
300    // ?man -r: operate recursively
301    case 'r':
302      rflag = 1;
303      break;
304    // ?man -s:num: silent mode or print summary
305    case 's':
306      argmaxsz = estrtonum(
307          EARGF(usage()), 1, MIN((unsigned long long)SIZE_MAX, (unsigned long long)LLONG_MAX)
308      );
309      break;
310    // ?man -t: sort or specify timestamp
311    case 't':
312      tflag = 1;
313      break;
314    // ?man -x: hex format or match whole lines
315    case 'x':
316      xflag = 1;
317      break;
318    // ?man -E:str: specify option flag
319    case 'E':
320      eofstr = EARGF(usage());
321      break;
322    // ?man -I:str: specify option flag
323    case 'I':
324      Iflag   = 1;
325      xflag   = 1;
326      nflag   = 1;
327      maxargs = 1;
328      replstr = EARGF(usage());
329      break;
330    // ?man -L:num: specify option flag
331    case 'L':
332      Lflag    = 1;
333      maxlines = estrtonum(
334          EARGF(usage()), 1, MIN((unsigned long long)SIZE_MAX, (unsigned long long)LLONG_MAX)
335      );
336      break;
337    // ?man -P:num: specify option flag
338    case 'P':
339      maxprocs = estrtonum(
340          EARGF(usage()), 1, MIN((unsigned long long)SIZE_MAX, (unsigned long long)LLONG_MAX)
341      );
342      break;
343    default:
344      usage();
345  }
346  ARGEND
347
348  do {
349    size_t linecount = 0;
350    argsz            = 0;
351    i                = 0;
352    a                = 0;
353    if (argc) {
354      for (; i < argc; i++) {
355        cmd[i] = estrdup(argv[i]);
356        argsz += strlen(cmd[i]) + 1;
357      }
358    } else {
359      cmd[i] = estrdup("/bin/echo");
360      argsz += strlen("/bin/echo") + 1;
361      i++;
362    }
363    while (leftover || (arg = poparg())) {
364      if (arg) {
365        if (linecount == 0) {
366          linecount = 1;
367        } else if (arg_newline) {
368          linecount++;
369          arg_newline = 0;
370        }
371        if (Lflag && linecount > maxlines) {
372          leftover = 1;
373          break;
374        }
375      }
376      arglen = strlen(arg);
377      if (argsz + arglen >= argmaxsz || i >= NARGS - 1) {
378        if (xflag || arglen >= argmaxsz || leftover)
379          eprintf(
380              "insufficient argument "
381              "space\n"
382          );
383        leftover = 1;
384        break;
385      }
386
387      if (!Iflag) {
388        cmd[i] = estrdup(arg);
389        argsz += arglen + 1;
390      } else {
391        for (j = 1; j < i; j++) {
392          char *p = cmd[j];
393          argsz -= strlen(cmd[j]);
394          strnsubst(&cmd[j], replstr, arg, 255);
395          argsz += strlen(cmd[j]);
396          free(p);
397        }
398      }
399
400      i++;
401      a++;
402      leftover = 0;
403      if (nflag && a >= maxargs)
404        break;
405    }
406    cmd[i] = NULL;
407    if (a >= maxargs && nflag)
408      spawn();
409    else if (Lflag && linecount >= maxlines)
410      spawn();
411    else if (!a || (i == 1 && rflag))
412      ;
413    else
414      spawn();
415    for (; i >= 0; i--)
416      free(cmd[i]);
417  } while (arg);
418
419  free(argb);
420
421  waitchld(1);
422
423  if (nerrors || (fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>")))
424    ret = 123;
425
426  return ret;
427}