src.hhvn.uk > rc > file > walk.c

Byron Rakitzis' rc(1) port, with a few modifications
Log | Files | Refs | README | LICENSE

walk.c (10018B)


      1 /* walk.c: walks the parse tree. */
      2 
      3 #include "rc.h"
      4 
      5 #include <sys/stat.h>
      6 #include <signal.h>
      7 #include <setjmp.h>
      8 #include <termios.h>
      9 #include <unistd.h>
     10 
     11 #ifdef __OpenBSD__
     12 #include <sys/wait.h>
     13 #endif
     14 
     15 #include "jbwrap.h"
     16 
     17 /*
     18    global which indicates whether rc is executing a test;
     19    used by rc -e so that if (false) does not exit.
     20 */
     21 bool cond = FALSE;
     22 
     23 static bool haspreredir(Node *);
     24 static bool isallpre(Node *);
     25 static bool dofork(bool);
     26 static void dopipe(Node *);
     27 static void loop_body(Node* n);
     28 
     29 enum if_state { if_false, if_true, if_nothing };
     30 enum if_state if_last = if_nothing;
     31 
     32 /* Tail-recursive version of walk() */
     33 
     34 #define WALK(x, y) { n = x; parent = y; goto top; }
     35 
     36 /* walk the parse-tree. "obvious". */
     37 
     38 extern bool walk(Node *n, bool parent) {
     39 top:	sigchk();
     40 	if (n == NULL) {
     41 		if (!parent)
     42 			exit(0);
     43 		set(TRUE);
     44 		return TRUE;
     45 	}
     46 	switch (n->type) {
     47 	case nArgs: case nBackq: case nConcat: case nCount:
     48 	case nFlat: case nLappend: case nRedir: case nVar:
     49 	case nVarsub: case nWord:
     50 		exec(glob(glom(n)), parent);	/* simple command */
     51 		break;
     52 	case nBody:
     53 		walk(n->u[0].p, TRUE);
     54 		WALK(n->u[1].p, parent);
     55 		/* WALK doesn't fall through */
     56 	case nNowait: {
     57 		int pid;
     58 		if ((pid = rc_fork()) == 0) {
     59 #if defined(RC_JOB) && defined(SIGTTOU) && defined(SIGTTIN) && defined(SIGTSTP)
     60 			setsigdefaults(FALSE);
     61 			rc_signal(SIGTTOU, SIG_IGN);	/* Berkeleyized version: put it in a new pgroup. */
     62 			rc_signal(SIGTTIN, SIG_IGN);
     63 			rc_signal(SIGTSTP, SIG_IGN);
     64 			setpgid(0, getpid());
     65 #else
     66 			setsigdefaults(TRUE);		/* ignore SIGINT, SIGQUIT, SIGTERM */
     67 #endif
     68 			mvfd(rc_open("/dev/null", rFrom), 0);
     69 			walk(n->u[0].p, FALSE);
     70 			exit(getstatus());
     71 		}
     72 		if (interactive)
     73 			fprint(2, "%d\n", pid);
     74 		varassign("apid", word(nprint("%d", pid), NULL), FALSE);
     75 		redirq = NULL; /* kill pre-redir queue */
     76 		break;
     77 	}
     78 	case nAndalso: {
     79 		bool oldcond = cond;
     80 		cond = TRUE;
     81 		if (walk(n->u[0].p, TRUE)) {
     82 			cond = oldcond;
     83 			WALK(n->u[1].p, parent);
     84 		} else
     85 			cond = oldcond;
     86 		break;
     87 	}
     88 	case nOrelse: {
     89 		bool oldcond = cond;
     90 		cond = TRUE;
     91 		if (!walk(n->u[0].p, TRUE)) {
     92 			cond = oldcond;
     93 			WALK(n->u[1].p, parent);
     94 		} else
     95 			cond = oldcond;
     96 		break;
     97 	}
     98 	case nBang:
     99 		set(!walk(n->u[0].p, TRUE));
    100 		break;
    101 	case nIf: {
    102 		bool oldcond = cond;
    103 		enum if_state if_this;
    104 		Node *true_cmd = n->u[1].p, *false_cmd = NULL;
    105 		if (true_cmd != NULL && true_cmd->type == nElse) {
    106 			false_cmd = true_cmd->u[1].p;
    107 			true_cmd = true_cmd->u[0].p;
    108 		}
    109 		cond = TRUE;
    110 		if_this = (enum if_state)walk(n->u[0].p, TRUE);
    111 		cond = oldcond;
    112 		if (if_last == if_nothing) if_last = if_this;
    113 		walk(if_this ? true_cmd : false_cmd, parent);
    114 		break;
    115 	}
    116 	case nIfnot: {
    117 		if (if_last == if_nothing)
    118 			rc_error("`if not' must follow `if'");
    119 		if (if_last == if_false)
    120 			walk(n->u[0].p, TRUE);
    121 		if_last = if_nothing;
    122 		break;
    123 	}
    124 	case nWhile: {
    125 		Jbwrap break_jb;
    126 		Edata  break_data;
    127 		Estack break_stack;
    128 		bool testtrue;
    129 		const bool oldcond = cond;
    130 		cond = TRUE;
    131 		if (!walk(n->u[0].p, TRUE)) { /* prevent spurious breaks inside test */
    132 			cond = oldcond;
    133 			break;
    134 		}
    135 		cond = oldcond;
    136 		if (sigsetjmp(break_jb.j, 1))
    137 			break;
    138 		break_data.jb = &break_jb;
    139 		except(eBreak, break_data, &break_stack);
    140 
    141 		cond = oldcond;
    142 		do {
    143 			Edata  iter_data;
    144 			Estack iter_stack;
    145 			iter_data.b = newblock();
    146 			except(eArena, iter_data, &iter_stack);
    147 			loop_body(n->u[1].p);
    148 			cond = TRUE;
    149 			testtrue = walk(n->u[0].p, TRUE);
    150 			cond = oldcond;
    151 			unexcept(eArena);
    152 		} while (testtrue);
    153 		cond = oldcond;
    154 		unexcept(eBreak);
    155 		break;
    156 	}
    157 	case nForin: {
    158 		List *l, *var = glom(n->u[0].p);
    159 		Jbwrap break_jb;
    160 		Edata  break_data;
    161 		Estack break_stack;
    162 		if (sigsetjmp(break_jb.j, 1))
    163 			break;
    164 		break_data.jb = &break_jb;
    165 		except(eBreak, break_data, &break_stack);
    166 
    167 		for (l = listcpy(glob(glom(n->u[1].p)), nalloc); l != NULL; l = l->n) {
    168 			Edata  iter_data;
    169 			Estack iter_stack;
    170 			assign(var, word(l->w, NULL), FALSE);
    171 			iter_data.b = newblock();
    172 			except(eArena, iter_data, &iter_stack);
    173 			loop_body(n->u[2].p);
    174 			unexcept(eArena);
    175 		}
    176 		unexcept(eBreak);
    177 		break;
    178 	}
    179 	case nSubshell:
    180 		if (dofork(TRUE)) {
    181 			setsigdefaults(FALSE);
    182 			walk(n->u[0].p, FALSE);
    183 			rc_exit(getstatus());
    184 		}
    185 		break;
    186 	case nAssign:
    187 		if (n->u[0].p == NULL)
    188 			rc_error("null variable name");
    189 		assign(glom(n->u[0].p), glob(glom(n->u[1].p)), FALSE);
    190 		set(TRUE);
    191 		break;
    192 	case nPipe:
    193 		dopipe(n);
    194 		break;
    195 	case nNewfn: {
    196 		List *l = glom(n->u[0].p);
    197 		if (l == NULL)
    198 			rc_error("null function name");
    199 		while (l != NULL) {
    200 			if (dashex)
    201 				prettyprint_fn(2, l->w, n->u[1].p);
    202 			fnassign(l->w, n->u[1].p);
    203 			l = l->n;
    204 		}
    205 		set(TRUE);
    206 		break;
    207 	}
    208 	case nRmfn: {
    209 		List *l = glom(n->u[0].p);
    210 		while (l != NULL) {
    211 			if (dashex)
    212 				fprint(2, "fn %S\n", l->w);
    213 			fnrm(l->w);
    214 			l = l->n;
    215 		}
    216 		set(TRUE);
    217 		break;
    218 	}
    219 	case nDup:
    220 		redirq = NULL;
    221 		break; /* Null command */
    222 	case nMatch: {
    223 		List *a = glob(glom(n->u[0].p)), *b = glom(n->u[1].p);
    224 		if (dashex)
    225 			fprint(2, (a != NULL && a->n != NULL) ? "~ (%L) %L\n" : "~ %L %L\n", a, " ", b, " ");
    226 		set(lmatch(a, b));
    227 		break;
    228 	}
    229 	case nSwitch: {
    230 		List *v = glom(n->u[0].p);
    231 		while (1) {
    232 			do {
    233 				n = n->u[1].p;
    234 				if (n == NULL)
    235 					return istrue();
    236 			} while (n->u[0].p == NULL || n->u[0].p->type != nCase);
    237 			if (lmatch(v, glom(n->u[0].p->u[0].p))) {
    238 				for (n = n->u[1].p; n != NULL && (n->u[0].p == NULL || n->u[0].p->type != nCase); n = n->u[1].p)
    239 					walk(n->u[0].p, TRUE);
    240 				break;
    241 			}
    242 		}
    243 		break;
    244 	}
    245 	case nPre: {
    246 		List *v;
    247 		if (n->u[0].p->type == nRedir || n->u[0].p->type == nDup) {
    248 			if (redirq == NULL && !dofork(parent)) /* subshell on first preredir */
    249 				break;
    250 			setsigdefaults(FALSE);
    251 			qredir(n->u[0].p);
    252 			if (!haspreredir(n->u[1].p))
    253 				doredirs(); /* no more preredirs, empty queue */
    254 			walk(n->u[1].p, FALSE);
    255 			rc_exit(getstatus());
    256 			/* NOTREACHED */
    257 		} else if (n->u[0].p->type == nAssign) {
    258 			if (isallpre(n->u[1].p)) {
    259 				walk(n->u[0].p, TRUE);
    260 				WALK(n->u[1].p, parent);
    261 			} else {
    262 				Estack e;
    263 				Edata var;
    264 				v = glom(n->u[0].p->u[0].p);
    265 				assign(v, glob(glom(n->u[0].p->u[1].p)), TRUE);
    266 				var.name = v->w;
    267 				except(eVarstack, var, &e);
    268 				walk(n->u[1].p, parent);
    269 				varrm(v->w, TRUE);
    270 				unexcept(eVarstack);
    271 			}
    272 		} else
    273 			panic("unexpected node in preredir section of walk");
    274 		break;
    275 	}
    276 	case nBrace:
    277 		if (n->u[1].p == NULL) {
    278 			WALK(n->u[0].p, parent);
    279 		} else if (dofork(parent)) {
    280 			setsigdefaults(FALSE);
    281 			walk(n->u[1].p, TRUE); /* Do redirections */
    282 			redirq = NULL;   /* Reset redirection queue */
    283 			walk(n->u[0].p, FALSE); /* Do commands */
    284 			rc_exit(getstatus());
    285 			/* NOTREACHED */
    286 		}
    287 		break;
    288 	case nEpilog:
    289 		qredir(n->u[0].p);
    290 		if (n->u[1].p != NULL) {
    291 			WALK(n->u[1].p, parent); /* Do more redirections. */
    292 		} else {
    293 			doredirs();	/* Okay, we hit the bottom. */
    294 		}
    295 		break;
    296 	case nNmpipe:
    297 		rc_error("named pipes cannot be executed as commands");
    298 		/* NOTREACHED */
    299 	default:
    300 		panic("unknown node in walk");
    301 		/* NOTREACHED */
    302 	}
    303 	return istrue();
    304 }
    305 
    306 /* checks to see whether there are any pre-redirections left in the tree */
    307 
    308 static bool haspreredir(Node *n) {
    309 	while (n != NULL && n->type == nPre) {
    310 		if (n->u[0].p->type == nDup || n->u[0].p->type == nRedir)
    311 			return TRUE;
    312 		n = n->u[1].p;
    313 	}
    314 	return FALSE;
    315 }
    316 
    317 /* checks to see whether a subtree is all pre-command directives, i.e., assignments and redirs only */
    318 
    319 static bool isallpre(Node *n) {
    320 	while (n != NULL && n->type == nPre)
    321 		n = n->u[1].p;
    322 	return n == NULL || n->type == nRedir || n->type == nAssign || n->type == nDup;
    323 }
    324 
    325 /*
    326    A code-saver. Forks, child returns (for further processing in walk()), and the parent
    327    waits for the child to finish, setting $status appropriately.
    328 */
    329 
    330 static bool dofork(bool parent) {
    331 	int pid, sp;
    332 	struct termios t;
    333 
    334 	if (interactive)
    335 		tcgetattr(0, &t);
    336 	if (!parent || (pid = rc_fork()) == 0)
    337 		return TRUE;
    338 	redirq = NULL; /* clear out the pre-redirection queue in the parent */
    339 	rc_wait4(pid, &sp, TRUE);
    340 	if (interactive && WIFSIGNALED(sp))
    341 		tcsetattr(0, TCSANOW, &t);
    342 	setstatus(-1, sp);
    343 	sigchk();
    344 	return FALSE;
    345 }
    346 
    347 static void dopipe(Node *n) {
    348 	int i, j, sp, pid, fd_prev, fd_out, pids[512], stats[512], p[2];
    349 	bool intr;
    350 	Node *r;
    351 	struct termios t;
    352 
    353 	if (interactive)
    354 		tcgetattr(0, &t);
    355 	fd_prev = fd_out = 1;
    356 	for (r = n, i = 0; r != NULL && r->type == nPipe; r = r->u[2].p, i++) {
    357 		if (i > 500) /* the only hard-wired limit in rc? */
    358 			rc_error("pipe too long");
    359 		if (pipe(p) < 0) {
    360 			uerror("pipe");
    361 			rc_error(NULL);
    362 		}
    363 		if ((pid = rc_fork()) == 0) {
    364 			setsigdefaults(FALSE);
    365 			redirq = NULL; /* clear preredir queue */
    366 			mvfd(p[0], r->u[1].i);
    367 			if (fd_prev != 1)
    368 				mvfd(fd_prev, fd_out);
    369 			close(p[1]);
    370 			walk(r->u[3].p, FALSE);
    371 			exit(getstatus());
    372 		}
    373 		if (fd_prev != 1)
    374 			close(fd_prev); /* parent must close all pipe fd's */
    375 		pids[i] = pid;
    376 		fd_prev = p[1];
    377 		fd_out = r->u[0].i;
    378 		close(p[0]);
    379 	}
    380 	if ((pid = rc_fork()) == 0) {
    381 		setsigdefaults(FALSE);
    382 		mvfd(fd_prev, fd_out);
    383 		walk(r, FALSE);
    384 		exit(getstatus());
    385 		/* NOTREACHED */
    386 	}
    387 	redirq = NULL; /* clear preredir queue */
    388 	close(fd_prev);
    389 	pids[i++] = pid;
    390 
    391 	/* collect statuses */
    392 
    393 	intr = FALSE;
    394 	for (j = 0; j < i; j++) {
    395 		rc_wait4(pids[j], &sp, TRUE);
    396 		stats[j] = sp;
    397 		intr |= WIFSIGNALED(sp);
    398 	}
    399 	if (interactive && intr)
    400 		tcsetattr(0, TCSANOW, &t);
    401 	setpipestatus(stats, i);
    402 	sigchk();
    403 }
    404 
    405 /* From http://en.cppreference.com/w/c/program/setjmp
    406  * According to the C standard setjmp() must appear only in the following 4 constructs:
    407  *   1. switch (setjmp(args)) {statements}
    408  *   2. if (setjmp(args) == Const) {statements} with any of
    409  *             operators: ==, !=, <, >, <=, >=
    410  *   3. while (! setjmp(args)) {statements}
    411  *   4. setjmp(args);
    412 */
    413 static void loop_body(Node* nd)
    414 {
    415 	Node *volatile n = nd;
    416 	Jbwrap cont_jb;
    417 	Edata  cont_data;
    418 	Estack cont_stack;
    419 
    420 	if (sigsetjmp(cont_jb.j, 1) == 0) {
    421 		cont_data.jb = &cont_jb;
    422 		except(eContinue, cont_data, &cont_stack);
    423 		walk(n, TRUE);
    424 		unexcept(eContinue);
    425 	}
    426 }