xargs.c (4546B)
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(void); 23 static void spawn(void); 24 25 static size_t argbsz; 26 static size_t argbpos; 27 static size_t maxargs = 0; 28 static int nerrors = 0; 29 static int rflag = 0, nflag = 0, tflag = 0, xflag = 0; 30 static char *argb; 31 static char *cmd[NARGS]; 32 static char *eofstr; 33 34 static int 35 inputc(void) 36 { 37 int ch; 38 39 ch = getc(stdin); 40 if (ch == EOF && ferror(stdin)) 41 eprintf("getc <stdin>:"); 42 43 return ch; 44 } 45 46 static void 47 fillargbuf(int ch) 48 { 49 if (argbpos >= argbsz) { 50 argbsz = argbpos == 0 ? 1 : argbsz * 2; 51 argb = erealloc(argb, argbsz); 52 } 53 argb[argbpos] = ch; 54 } 55 56 static int 57 eatspace(void) 58 { 59 int ch; 60 61 while ((ch = inputc()) != EOF) { 62 switch (ch) { 63 case ' ': case '\t': case '\n': 64 break; 65 default: 66 ungetc(ch, stdin); 67 return ch; 68 } 69 } 70 return -1; 71 } 72 73 static int 74 parsequote(int q) 75 { 76 int ch; 77 78 while ((ch = inputc()) != EOF) { 79 if (ch == q) 80 return 0; 81 if (ch != '\n') { 82 fillargbuf(ch); 83 argbpos++; 84 } 85 } 86 87 return -1; 88 } 89 90 static int 91 parseescape(void) 92 { 93 int ch; 94 95 if ((ch = inputc()) != EOF) { 96 fillargbuf(ch); 97 argbpos++; 98 return ch; 99 } 100 101 return -1; 102 } 103 104 static char * 105 poparg(void) 106 { 107 int ch; 108 109 argbpos = 0; 110 if (eatspace() < 0) 111 return NULL; 112 while ((ch = inputc()) != EOF) { 113 switch (ch) { 114 case ' ': case '\t': case '\n': 115 goto out; 116 case '\'': 117 if (parsequote('\'') < 0) 118 eprintf("unterminated single quote\n"); 119 break; 120 case '\"': 121 if (parsequote('\"') < 0) 122 eprintf("unterminated double quote\n"); 123 break; 124 case '\\': 125 if (parseescape() < 0) 126 eprintf("backslash at EOF\n"); 127 break; 128 default: 129 fillargbuf(ch); 130 argbpos++; 131 break; 132 } 133 } 134 out: 135 fillargbuf('\0'); 136 137 return (eofstr && !strcmp(argb, eofstr)) ? NULL : argb; 138 } 139 140 static void 141 waitchld(void) 142 { 143 int status; 144 145 wait(&status); 146 if (WIFEXITED(status)) { 147 if (WEXITSTATUS(status) == 255) 148 exit(124); 149 if (WEXITSTATUS(status) == 127 || 150 WEXITSTATUS(status) == 126) 151 exit(WEXITSTATUS(status)); 152 if (status) 153 nerrors++; 154 } 155 if (WIFSIGNALED(status)) 156 exit(125); 157 } 158 159 static void 160 spawn(void) 161 { 162 int savederrno; 163 int first = 1; 164 char **p; 165 166 if (tflag) { 167 for (p = cmd; *p; p++) { 168 if (!first) 169 fputc(' ', stderr); 170 fputs(*p, stderr); 171 first = 0; 172 } 173 fputc('\n', stderr); 174 } 175 176 switch (fork()) { 177 case -1: 178 eprintf("fork:"); 179 case 0: 180 execvp(*cmd, cmd); 181 savederrno = errno; 182 weprintf("execvp %s:", *cmd); 183 _exit(126 + (savederrno == ENOENT)); 184 } 185 waitchld(); 186 } 187 188 static void 189 usage(void) 190 { 191 eprintf("usage: %s [-rtx] [-E eofstr] [-n num] [-s num] " 192 "[cmd [arg ...]]\n", argv0); 193 } 194 195 int 196 main(int argc, char *argv[]) 197 { 198 int ret = 0, leftover = 0, i; 199 size_t argsz, argmaxsz; 200 size_t arglen, a; 201 char *arg = ""; 202 203 if ((argmaxsz = sysconf(_SC_ARG_MAX)) == (size_t)-1) 204 argmaxsz = _POSIX_ARG_MAX; 205 /* Leave some room for environment variables */ 206 argmaxsz -= 4096; 207 208 ARGBEGIN { 209 case 'n': 210 nflag = 1; 211 maxargs = estrtonum(EARGF(usage()), 1, MIN(SIZE_MAX, LLONG_MAX)); 212 break; 213 case 'r': 214 rflag = 1; 215 break; 216 case 's': 217 argmaxsz = estrtonum(EARGF(usage()), 1, MIN(SIZE_MAX, LLONG_MAX)); 218 break; 219 case 't': 220 tflag = 1; 221 break; 222 case 'x': 223 xflag = 1; 224 break; 225 case 'E': 226 eofstr = EARGF(usage()); 227 break; 228 default: 229 usage(); 230 } ARGEND 231 232 do { 233 argsz = 0; i = 0; a = 0; 234 if (argc) { 235 for (; i < argc; i++) { 236 cmd[i] = estrdup(argv[i]); 237 argsz += strlen(cmd[i]) + 1; 238 } 239 } else { 240 cmd[i] = estrdup("/bin/echo"); 241 argsz += strlen("/bin/echo") + 1; 242 i++; 243 } 244 while (leftover || (arg = poparg())) { 245 arglen = strlen(arg); 246 if (argsz + arglen >= argmaxsz || i >= NARGS - 1) { 247 if (arglen >= argmaxsz) { 248 weprintf("insufficient argument space\n"); 249 if (xflag) 250 exit(1); 251 } 252 leftover = 1; 253 break; 254 } 255 cmd[i] = estrdup(arg); 256 argsz += arglen + 1; 257 i++; 258 a++; 259 leftover = 0; 260 if (nflag && a >= maxargs) 261 break; 262 } 263 cmd[i] = NULL; 264 if (a >= maxargs && nflag) 265 spawn(); 266 else if (!a || (i == 1 && rflag)) 267 ; 268 else 269 spawn(); 270 for (; i >= 0; i--) 271 free(cmd[i]); 272 } while (arg); 273 274 free(argb); 275 276 if (nerrors || (fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>"))) 277 ret = 123; 278 279 return ret; 280 }