sbase

suckless unix tools
git clone git://git.suckless.org/sbase
Log | Files | Refs | README | LICENSE

xargs.c (5961B)


      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[NARGS];
     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 
    263 	ARGBEGIN {
    264 	case '0':
    265 		nulflag = 1;
    266 		break;
    267 	case 'n':
    268 		nflag = 1;
    269 		maxargs = estrtonum(EARGF(usage()), 1, MIN(SIZE_MAX, LLONG_MAX));
    270 		break;
    271 	case 'p':
    272 		pflag = 1;
    273 		break;
    274 	case 'r':
    275 		rflag = 1;
    276 		break;
    277 	case 's':
    278 		argmaxsz = estrtonum(EARGF(usage()), 1, MIN(SIZE_MAX, LLONG_MAX));
    279 		break;
    280 	case 't':
    281 		tflag = 1;
    282 		break;
    283 	case 'x':
    284 		xflag = 1;
    285 		break;
    286 	case 'E':
    287 		eofstr = EARGF(usage());
    288 		break;
    289 	case 'I':
    290 		Iflag = 1;
    291 		xflag = 1;
    292 		nflag = 1;
    293 		maxargs = 1;
    294 		replstr = EARGF(usage());
    295 		break;
    296 	case 'P':
    297 		maxprocs = estrtonum(EARGF(usage()), 1, MIN(SIZE_MAX, LLONG_MAX));
    298 		break;
    299 	default:
    300 		usage();
    301 	} ARGEND
    302 
    303 	do {
    304 		argsz = 0; i = 0; a = 0;
    305 		if (argc) {
    306 			for (; i < argc; i++) {
    307 				cmd[i] = estrdup(argv[i]);
    308 				argsz += strlen(cmd[i]) + 1;
    309 			}
    310 		} else {
    311 			cmd[i] = estrdup("/bin/echo");
    312 			argsz += strlen("/bin/echo") + 1;
    313 			i++;
    314 		}
    315 		while (leftover || (arg = poparg())) {
    316 			arglen = strlen(arg);
    317 			if (argsz + arglen >= argmaxsz || i >= NARGS - 1) {
    318 				if (xflag || arglen >= argmaxsz || leftover)
    319 					eprintf("insufficient argument space\n");
    320 				leftover = 1;
    321 				break;
    322 			}
    323 
    324 			if (!Iflag) {
    325 				cmd[i] = estrdup(arg);
    326 				argsz += arglen + 1;
    327 			} else {
    328 				for (j = 1; j < i; j++) {
    329 					char *p = cmd[j];
    330 					argsz -= strlen(cmd[j]);
    331 					strnsubst(&cmd[j], replstr, arg, 255);
    332 					argsz += strlen(cmd[j]);
    333 					free(p);
    334 				}
    335 			}
    336 
    337 			i++;
    338 			a++;
    339 			leftover = 0;
    340 			if (nflag && a >= maxargs)
    341 				break;
    342 		}
    343 		cmd[i] = NULL;
    344 		if (a >= maxargs && nflag)
    345 			spawn();
    346 		else if (!a || (i == 1 && rflag))
    347 			;
    348 		else
    349 			spawn();
    350 		for (; i >= 0; i--)
    351 			free(cmd[i]);
    352 	} while (arg);
    353 
    354 	free(argb);
    355 
    356 	waitchld(1);
    357 
    358 	if (nerrors || (fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>")))
    359 		ret = 123;
    360 
    361 	return ret;
    362 }