xargs.c (4934B)
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, Iflag = 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 ' ': 115 case '\t': 116 if (Iflag) 117 goto fill; 118 case '\n': 119 goto out; 120 case '\'': 121 if (parsequote('\'') < 0) 122 eprintf("unterminated single quote\n"); 123 break; 124 case '\"': 125 if (parsequote('\"') < 0) 126 eprintf("unterminated double quote\n"); 127 break; 128 case '\\': 129 if (parseescape() < 0) 130 eprintf("backslash at EOF\n"); 131 break; 132 default: 133 fill: 134 fillargbuf(ch); 135 argbpos++; 136 break; 137 } 138 } 139 out: 140 fillargbuf('\0'); 141 142 return (eofstr && !strcmp(argb, eofstr)) ? NULL : argb; 143 } 144 145 static void 146 waitchld(void) 147 { 148 int status; 149 150 wait(&status); 151 if (WIFEXITED(status)) { 152 if (WEXITSTATUS(status) == 255) 153 exit(124); 154 if (WEXITSTATUS(status) == 127 || 155 WEXITSTATUS(status) == 126) 156 exit(WEXITSTATUS(status)); 157 if (status) 158 nerrors++; 159 } 160 if (WIFSIGNALED(status)) 161 exit(125); 162 } 163 164 static void 165 spawn(void) 166 { 167 int savederrno; 168 int first = 1; 169 char **p; 170 171 if (tflag) { 172 for (p = cmd; *p; p++) { 173 if (!first) 174 fputc(' ', stderr); 175 fputs(*p, stderr); 176 first = 0; 177 } 178 fputc('\n', stderr); 179 } 180 181 switch (fork()) { 182 case -1: 183 eprintf("fork:"); 184 case 0: 185 execvp(*cmd, cmd); 186 savederrno = errno; 187 weprintf("execvp %s:", *cmd); 188 _exit(126 + (savederrno == ENOENT)); 189 } 190 waitchld(); 191 } 192 193 static void 194 usage(void) 195 { 196 eprintf("usage: %s [-rtx] [-E eofstr] [-n num] [-s num] " 197 "[cmd [arg ...]]\n", argv0); 198 } 199 200 int 201 main(int argc, char *argv[]) 202 { 203 int ret = 0, leftover = 0, i, j; 204 size_t argsz, argmaxsz; 205 size_t arglen, a; 206 char *arg = ""; 207 char *replstr; 208 209 if ((argmaxsz = sysconf(_SC_ARG_MAX)) == (size_t)-1) 210 argmaxsz = _POSIX_ARG_MAX; 211 /* Leave some room for environment variables */ 212 argmaxsz -= 4096; 213 214 ARGBEGIN { 215 case 'n': 216 nflag = 1; 217 maxargs = estrtonum(EARGF(usage()), 1, MIN(SIZE_MAX, LLONG_MAX)); 218 break; 219 case 'r': 220 rflag = 1; 221 break; 222 case 's': 223 argmaxsz = estrtonum(EARGF(usage()), 1, MIN(SIZE_MAX, LLONG_MAX)); 224 break; 225 case 't': 226 tflag = 1; 227 break; 228 case 'x': 229 xflag = 1; 230 break; 231 case 'E': 232 eofstr = EARGF(usage()); 233 break; 234 case 'I': 235 Iflag = 1; 236 xflag = 1; 237 nflag = 1; 238 maxargs = 1; 239 replstr = EARGF(usage()); 240 break; 241 default: 242 usage(); 243 } ARGEND 244 245 do { 246 argsz = 0; i = 0; a = 0; 247 if (argc) { 248 for (; i < argc; i++) { 249 cmd[i] = estrdup(argv[i]); 250 argsz += strlen(cmd[i]) + 1; 251 } 252 } else { 253 cmd[i] = estrdup("/bin/echo"); 254 argsz += strlen("/bin/echo") + 1; 255 i++; 256 } 257 while (leftover || (arg = poparg())) { 258 arglen = strlen(arg); 259 if (argsz + arglen >= argmaxsz || i >= NARGS - 1) { 260 if (arglen >= argmaxsz) { 261 weprintf("insufficient argument space\n"); 262 if (xflag) 263 exit(1); 264 } 265 leftover = 1; 266 break; 267 } 268 269 if (!Iflag) { 270 cmd[i] = estrdup(arg); 271 argsz += arglen + 1; 272 } else { 273 for (j = 1; j < i; j++) { 274 char *p = cmd[j]; 275 argsz -= strlen(cmd[j]); 276 strnsubst(&cmd[j], replstr, arg, 255); 277 argsz += strlen(cmd[j]); 278 free(p); 279 } 280 } 281 282 i++; 283 a++; 284 leftover = 0; 285 if (nflag && a >= maxargs) 286 break; 287 } 288 cmd[i] = NULL; 289 if (a >= maxargs && nflag) 290 spawn(); 291 else if (!a || (i == 1 && rflag)) 292 ; 293 else 294 spawn(); 295 for (; i >= 0; i--) 296 free(cmd[i]); 297 } while (arg); 298 299 free(argb); 300 301 if (nerrors || (fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>"))) 302 ret = 123; 303 304 return ret; 305 }