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