rc

[fork] interactive rc shell
git clone https://hhvn.uk/rc
git clone git://hhvn.uk/rc
Log | Files | Refs | README | LICENSE

walk.c (9932B)


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