rc

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

builtins.c (14089B)


      1 /* builtins.c: the collection of rc's builtin commands */
      2 
      3 /*
      4 	NOTE: rc's builtins do not call "rc_error" because they are
      5 	commands, and rc errors usually arise from syntax errors. e.g.,
      6 	you probably don't want interpretation of a shell script to stop
      7 	because of a bad umask.
      8 */
      9 
     10 #include "rc.h"
     11 
     12 #include <sys/ioctl.h>
     13 #include <sys/stat.h>
     14 #include <setjmp.h>
     15 #include <errno.h>
     16 
     17 #include "addon.h"
     18 #include "input.h"
     19 #include "jbwrap.h"
     20 #include "rlimit.h"
     21 #include "sigmsgs.h"
     22 
     23 static void b_break(char **), b_cd(char **), b_continue(char **), b_eval(char **), b_flag(char **),
     24 	b_exit(char **), b_newpgrp(char **), b_return(char **), b_shift(char **), b_umask(char **),
     25 	b_wait(char **), b_whatis(char **);
     26 
     27 #if HAVE_SETRLIMIT
     28 static void b_limit(char **);
     29 #endif
     30 
     31 #if RC_ECHO
     32 static void b_echo(char **);
     33 #endif
     34 
     35 static struct {
     36 	builtin_t *p;
     37 	char *name;
     38 } builtins[] = {
     39 	{ b_break,	"break" },
     40 	{ b_builtin,	"builtin" },
     41 	{ b_cd,		"cd" },
     42 	{ b_continue,	"continue" },
     43 #if RC_ECHO
     44 	{ b_echo,	"echo" },
     45 #endif
     46 	{ b_eval,	"eval" },
     47 	{ b_exec,	"exec" },
     48 	{ b_exit,	"exit" },
     49 	{ b_flag,	"flag" },
     50 #if HAVE_SETRLIMIT
     51 	{ b_limit,	"limit" },
     52 #endif
     53 	{ b_newpgrp,	"newpgrp" },
     54 	{ b_return,	"return" },
     55 	{ b_shift,	"shift" },
     56 	{ b_umask,	"umask" },
     57 	{ b_wait,	"wait" },
     58 	{ b_whatis,	"whatis" },
     59 	{ b_dot,	"." },
     60 #ifdef ADDONS
     61 	ADDONS
     62 #endif
     63 };
     64 
     65 extern builtin_t *isbuiltin(char *s) {
     66     int i;
     67     for (i = 0; i < arraysize(builtins); i++)
     68 	if (streq(builtins[i].name, s))
     69 	    return builtins[i].p;
     70     return NULL;
     71 }
     72 
     73 /* funcall() is the wrapper used to invoke shell functions. pushes $*, and "return" returns here. */
     74 
     75 extern void funcall(char **av) {
     76 	Jbwrap j;
     77 	Estack e1, e2;
     78 	Edata jreturn, star;
     79 	if (sigsetjmp(j.j, 1))
     80 		return;
     81 	starassign(*av, av+1, TRUE);
     82 	jreturn.jb = &j;
     83 	star.name = "*";
     84 	except(eReturn, jreturn, &e1);
     85 	except(eVarstack, star, &e2);
     86 	walk(treecpy(fnlookup(*av), nalloc), TRUE);
     87 	varrm("*", TRUE);
     88 	unexcept(eVarstack);
     89 	unexcept(eReturn);
     90 }
     91 
     92 static void arg_count(char *name) {
     93     fprint(2, RC "too many arguments to %s\n", name);
     94     set(FALSE);
     95 }
     96 
     97 static void badnum(char *num) {
     98     fprint(2, RC "`%s' is a bad number\n", num);
     99     set(FALSE);
    100 }
    101 
    102 /* a dummy command. (exec() performs "exec" simply by not forking) */
    103 
    104 extern void b_exec(char **ignore) {
    105 }
    106 
    107 #if RC_ECHO
    108 /* echo -n omits a newline. echo -- -n echos '-n' */
    109 
    110 static void b_echo(char **av) {
    111     char *format = "%A\n";
    112     if (*++av != NULL) {
    113 	if (streq(*av, "-n"))
    114 	    format = "%A", av++;
    115 	else if (streq(*av, "--"))
    116 	    av++;
    117     }
    118     fprint(1, format, av);
    119     set(TRUE);
    120 }
    121 #endif
    122 
    123 /* cd. traverse $cdpath if the directory given is not an absolute pathname */
    124 
    125 static void b_cd(char **av) {
    126     List *s, nil;
    127     char *path = NULL;
    128     size_t t, pathlen = 0;
    129     if (*++av == NULL) {
    130 	s = varlookup("home");
    131 	*av = (s == NULL) ? "/" : s->w;
    132     } else if (av[1] != NULL) {
    133 	arg_count("cd");
    134 	return;
    135     }
    136     if (isabsolute(*av) || streq(*av, ".") || streq(*av, "..")) { /* absolute pathname? */
    137 	if (chdir(*av) < 0) {
    138 	    set(FALSE);
    139 	    uerror(*av);
    140 	} else
    141 	    set(TRUE);
    142     } else {
    143 	s = varlookup("cdpath");
    144 	if (s == NULL) {
    145 	    s = &nil;
    146 	    nil.w = "";
    147 	    nil.n = NULL;
    148 	}
    149 	do {
    150 	    if (s != &nil && *s->w != '\0') {
    151 		t = strlen(*av) + strlen(s->w) + 2;
    152 		if (t > pathlen)
    153 		    path = nalloc(pathlen = t);
    154 		strcpy(path, s->w);
    155 		if (!streq(s->w, "/")) /* "//" is special to POSIX */
    156 		    strcat(path, "/");
    157 		strcat(path, *av);
    158 	    } else {
    159 		pathlen = 0;
    160 		path = *av;
    161 	    }
    162 	    if (chdir(path) >= 0) {
    163 		set(TRUE);
    164 		if (interactive && *s->w != '\0' && !streq(s->w, "."))
    165 		    fprint(1, "%s\n", path);
    166 		return;
    167 	    }
    168 	    s = s->n;
    169 	} while (s != NULL);
    170 	fprint(2, "couldn't cd to %s\n", *av);
    171 	set(FALSE);
    172     }
    173 }
    174 
    175 static void b_umask(char **av) {
    176     int i;
    177     if (*++av == NULL) {
    178 	set(TRUE);
    179 	i = umask(0);
    180 	umask(i);
    181 	fprint(1, "0%o\n", i);
    182     } else if (av[1] == NULL) {
    183 	i = o2u(*av);
    184 	if ((unsigned int) i > 0777) {
    185 	    fprint(2, "bad umask\n");
    186 	    set(FALSE);
    187 	} else {
    188 	    umask(i);
    189 	    set(TRUE);
    190 	}
    191     } else {
    192 	arg_count("umask");
    193 	return;
    194     }
    195 }
    196 
    197 static void b_exit(char **av) {
    198 	if (*++av != NULL)
    199 		ssetstatus(av);
    200 	rc_exit(getstatus());
    201 }
    202 
    203 static void b_flag(char **av) {
    204 	bool *flagp = NULL;
    205 	char f;
    206 	int mode = 3; /* 0 = reset (-), 1 = set (+), 2 = test */
    207 	const char *usage = "usage: flag f [ + | - ]\n";
    208 
    209 	if (*++av == NULL) {
    210 		fprint(2, RC "not enough arguments to flag\n");
    211 		set(FALSE);
    212 		return;
    213 	}
    214 	f = av[0][0];
    215 	if (f == '\0' || av[0][1] != '\0') goto flag_usage;
    216 	if (*++av == NULL) {
    217 		mode = 2;
    218 	} else if (av[0][0] == '+' && av[0][1] == '\0') {
    219 		mode = 1;
    220 	} else if (av[0][0] == '-' && av[0][1] == '\0') {
    221 		mode = 0;
    222 	}
    223 	if (mode == 3) goto flag_usage;
    224 	switch (f) {
    225 		case 'c':
    226 			if (mode != 2) goto flag_immutable;
    227 			set(dashsee[0] != NULL);
    228 			return;
    229 		case 'd':
    230 			if (mode != 2) goto flag_immutable;
    231 			flagp = &dashdee; break;
    232 		case 'e': flagp = &dashee; break;
    233 		case 'i': flagp = &interactive; break;
    234 		case 'l':
    235 			  if (mode != 2) goto flag_immutable;
    236 			  flagp = &dashell; break;
    237 		case 'n': flagp = &dashen; break;
    238 		case 'o':
    239 			  if (mode != 2) goto flag_immutable;
    240 			  flagp = &dashoh; break;
    241 		case 'p':
    242 			  if (mode != 2) goto flag_immutable;
    243 			  flagp = &dashpee; break;
    244 		case 's':
    245 			  if (mode != 2) goto flag_immutable;
    246 			  flagp = &dashess; break;
    247 		case 'v': flagp = &dashvee; break;
    248 		case 'x': flagp = &dashex; break;
    249 	}
    250 	if (flagp != NULL) {
    251 		if (mode == 2)
    252 			set(*flagp);
    253 		else {
    254 			*flagp = mode;
    255 			set(TRUE);
    256 		}
    257 	} else {
    258 		fprint(2, RC "unknown flag");
    259 		set(FALSE);
    260 	}
    261 	return;
    262 flag_immutable:
    263 	fprint(2, RC "flag immutable\n");
    264 	set(FALSE);
    265 	return;
    266 flag_usage:
    267 	fprint(2, usage);
    268 	set(FALSE);
    269 }
    270 
    271 /* raise a "return" exception, i.e., return from a function. if an integer argument is present, set $status to it */
    272 
    273 static void b_return(char **av) {
    274 	if (*++av != NULL)
    275 		ssetstatus(av);
    276 	rc_raise(eReturn);
    277 }
    278 
    279 /* raise a "break" exception for breaking out of for and while loops */
    280 
    281 static void b_break(char **av) {
    282 	if (av[1] != NULL) {
    283 		arg_count("break");
    284 		return;
    285 	}
    286 	rc_raise(eBreak);
    287 }
    288 
    289 /* raise a "continue" exception to finish early an iteration of 'for' and 'while' loops */
    290 
    291 static void b_continue(char **av) {
    292 	if (av[1] != NULL) {
    293 		arg_count("continue");
    294 		return;
    295 	}
    296 	rc_raise(eContinue);
    297 }
    298 
    299 /* shift $* n places (default 1) */
    300 
    301 static void b_shift(char **av) {
    302 	int shift = (av[1] == NULL ? 1 : a2u(av[1]));
    303 	List *s, *dollarzero;
    304 	if (av[1] != NULL && av[2] != NULL) {
    305 		arg_count("shift");
    306 		return;
    307 	}
    308 	if (shift < 0) {
    309 		badnum(av[1]);
    310 		return;
    311 	}
    312 	s = varlookup("*")->n;
    313 	dollarzero = varlookup("0");
    314 	while (s != NULL && shift != 0) {
    315 		s = s->n;
    316 		--shift;
    317 	}
    318 	if (s == NULL && shift != 0) {
    319 		fprint(2, "rc: cannot shift\n");
    320 		set(FALSE);
    321 	} else {
    322 		varassign("*", append(dollarzero, s), FALSE);
    323 		set(TRUE);
    324 	}
    325 }
    326 
    327 /* dud function */
    328 
    329 extern void b_builtin(char **ignore) {
    330 }
    331 
    332 /* wait for a given process, or all outstanding processes */
    333 
    334 static void b_wait(char **av) {
    335 	int status;
    336 	pid_t pid;
    337 	if (av[1] == NULL) {
    338 		waitforall();
    339 		return;
    340 	}
    341 	if (av[2] != NULL) {
    342 		arg_count("wait");
    343 		return;
    344 	}
    345 	if ((pid = a2u(av[1])) < 0) {
    346 		badnum(av[1]);
    347 		return;
    348 	}
    349 	if (rc_wait4(pid, &status, FALSE) > 0)
    350 		setstatus(pid, status);
    351 	else
    352 		set(FALSE);
    353 	sigchk();
    354 }
    355 
    356 /*
    357    whatis without arguments prints all variables and functions. Otherwise, check to see if a name
    358    is defined as a variable, function or pathname.
    359 */
    360 
    361 #define not(b)	((b)^TRUE)
    362 #define show(b)	(not(eff|vee|pee|bee|ess)|(b))
    363 
    364 static bool issig(char *s) {
    365 	int i;
    366 	for (i = 0; i < NUMOFSIGNALS; i++)
    367 		if (streq(s, signals[i].name))
    368 			return TRUE;
    369 	return FALSE;
    370 }
    371 
    372 static void b_whatis(char **av) {
    373 	bool ess, eff, vee, pee, bee;
    374 	bool f, found;
    375 	int i, ac, c;
    376 	List *s;
    377 	Node *n;
    378 	char *e;
    379 	for (rc_optind = ac = 0; av[ac] != NULL; ac++)
    380 		; /* count the arguments for getopt */
    381 	ess = eff = vee = pee = bee = FALSE;
    382 	while ((c = rc_getopt(ac, av, "sfvpb")) != -1)
    383 		switch (c) {
    384 		default: set(FALSE); return;
    385 		case 's': ess = TRUE; break;
    386 		case 'f': eff = TRUE; break;
    387 		case 'v': vee = TRUE; break;
    388 		case 'p': pee = TRUE; break;
    389 		case 'b': bee = TRUE; break;
    390 		}
    391 	av += rc_optind;
    392 	if (*av == NULL) {
    393 		if (vee|eff)
    394 			whatare_all_vars(eff, vee);
    395 		if (ess)
    396 			whatare_all_signals();
    397 		if (bee)
    398 			for (i = 0; i < arraysize(builtins); i++)
    399 				fprint(1, "builtin %s\n", builtins[i].name);
    400 		if (pee)
    401 			fprint(2, "whatis -p: must specify argument\n");
    402 		if (show(FALSE)) /* no options? */
    403 			whatare_all_vars(TRUE, TRUE);
    404 		set(TRUE);
    405 		return;
    406 	}
    407 	found = TRUE;
    408 	for (i = 0; av[i] != NULL; i++) {
    409 		f = FALSE;
    410 		errno = ENOENT;
    411 		if (show(vee) && (s = varlookup(av[i])) != NULL) {
    412 			f = TRUE;
    413 			prettyprint_var(1, av[i], s);
    414 		}
    415 		if (((show(ess)&&issig(av[i])) || show(eff)) && (n = fnlookup(av[i])) != NULL) {
    416 			f = TRUE;
    417 			prettyprint_fn(1, av[i], n);
    418 		} else if (show(bee) && isbuiltin(av[i]) != NULL) {
    419 			f = TRUE;
    420 			fprint(1, "builtin %s\n", av[i]);
    421 		} else if (show(pee) && (e = which(av[i], FALSE)) != NULL) {
    422 			f = TRUE;
    423 			fprint(1, "%S\n", e);
    424 		}
    425 		if (!f) {
    426 			found = FALSE;
    427 			if (errno != ENOENT)
    428 				uerror(av[i]);
    429 			else
    430 				fprint(2, "%s not found\n", av[i]);
    431 		}
    432 	}
    433 	set(found);
    434 }
    435 
    436 /* push a string to be eval'ed onto the input stack. evaluate it */
    437 
    438 static void b_eval(char **av) {
    439 	bool i = interactive;
    440 	if (av[1] == NULL)
    441 		return;
    442 	interactive = FALSE;
    443 	pushstring(av + 1, i); /* don't reset line numbers on noninteractive eval */
    444 	doit(TRUE);
    445 	interactive = i;
    446 }
    447 
    448 /*
    449    push a file to be interpreted onto the input stack. with "-i" treat this as an interactive
    450    input source.
    451 */
    452 
    453 extern void b_dot(char **av) {
    454 	int fd;
    455 	bool old_i = interactive, i = FALSE;
    456 	Estack e;
    457 	Edata star;
    458 	av++;
    459 	if (*av == NULL)
    460 		return;
    461 	if (streq(*av, "-i")) {
    462 		av++;
    463 		i = TRUE;
    464 	}
    465 	if (dasheye) { /* rc -i file has to do the right thing. reset the dasheye state to FALSE, though. */
    466 		dasheye = FALSE;
    467 		i = TRUE;
    468 	}
    469 	if (*av == NULL)
    470 		return;
    471 	fd = rc_open(*av, rFrom);
    472 	if (fd < 0) {
    473 		uerror(*av);
    474 		set(FALSE);
    475 		return;
    476 	}
    477 	starassign(*av, av+1, TRUE);
    478 	interactive = i;
    479 	pushfd(fd);
    480 	star.name = "*";
    481 	except(eVarstack, star, &e);
    482 	doit(TRUE);
    483 	varrm("*", TRUE);
    484 	unexcept(eVarstack);
    485 	interactive = old_i;
    486 }
    487 
    488 /* put rc into a new pgrp. Used on the NeXT where the Terminal program is broken (sigh) */
    489 
    490 static void b_newpgrp(char **av) {
    491 	if (av[1] != NULL) {
    492 		arg_count("newpgrp");
    493 		return;
    494 	}
    495 	setpgid(rc_pid, rc_pid); /* XXX check return value */
    496 	tcsetpgrp(2, rc_pid); /* XXX check return value */
    497 }
    498 
    499 /* Berkeley limit support was cleaned up by Paul Haahr. */
    500 
    501 #if HAVE_SETRLIMIT
    502 static const struct Suffix
    503 	kbsuf = { NULL, 1024, "k" },
    504 	mbsuf = { &kbsuf, 1024*1024, "m" },
    505 	gbsuf = { &mbsuf, 1024*1024*1024, "g" },
    506 	stsuf = { NULL, 1, "s" },
    507 	mtsuf = { &stsuf, 60, "m" },
    508 	htsuf = { &mtsuf, 60*60, "h" };
    509 #define	SIZESUF &gbsuf
    510 #define	TIMESUF &htsuf
    511 #define	NOSUF ((struct Suffix *) NULL)  /* for RLIMIT_NOFILE on SunOS 4.1 */
    512 
    513 static const struct Limit limits[] = {
    514 	{ "cputime",		RLIMIT_CPU,	TIMESUF },
    515 	{ "filesize",		RLIMIT_FSIZE,	SIZESUF },
    516 	{ "datasize",		RLIMIT_DATA,	SIZESUF },
    517 	{ "stacksize",		RLIMIT_STACK,	SIZESUF },
    518 	{ "coredumpsize",	RLIMIT_CORE,	SIZESUF },
    519 #ifdef RLIMIT_NOFILE  /* SUSv2, but not universal */
    520 	{ "descriptors",	RLIMIT_NOFILE,	NOSUF },
    521 #endif
    522 #ifdef RLIMIT_AS /* SUSv2, but not universal */
    523 	{ "memoryuse",		RLIMIT_AS,	SIZESUF },
    524 #endif
    525 #if defined(RLIMIT_VMEM) && !defined(RLIMIT_AS) /* old name for AS */
    526 	{ "memoryuse",		RLIMIT_VMEM,	SIZESUF },
    527 #endif
    528 #ifdef RLIMIT_RSS
    529 	{ "memoryrss",		RLIMIT_RSS,	SIZESUF },
    530 #endif
    531 #ifdef RLIMIT_NPROC
    532 	{ "maxproc",		RLIMIT_NPROC,	NOSUF },
    533 #endif
    534 #ifdef RLIMIT_MEMLOCK
    535 	{ "memorylocked",	RLIMIT_MEMLOCK,	SIZESUF },
    536 #endif
    537 #ifdef RLIMIT_LOCKS
    538 	{ "filelocks",		RLIMIT_LOCKS,	NOSUF },
    539 #endif
    540 	{ NULL, 0, NULL }
    541 };
    542 
    543 static void printlimit(const struct Limit *limit, bool hard) {
    544 	struct rlimit rlim;
    545 	rlim_t lim;
    546 	getrlimit(limit->flag, &rlim);
    547 	if (hard)
    548 		lim = rlim.rlim_max;
    549 	else
    550 		lim = rlim.rlim_cur;
    551 	if (lim == RLIM_INFINITY)
    552 		fprint(1, "%s \tunlimited\n", limit->name);
    553 	else {
    554 		const struct Suffix *suf;
    555 		for (suf = limit->suffix; suf != NULL; suf = suf->next)
    556 			if (lim % suf->amount == 0 && (lim != 0 || suf->amount > 1)) {
    557 				lim /= suf->amount;
    558 				break;
    559 			}
    560 		fprint(1, RLIM_FMT, limit->name, (RLIM_CONV)lim, (suf == NULL || lim == 0) ? "" : suf->name);
    561 	}
    562 }
    563 
    564 static bool parselimit(const struct Limit *resource, rlim_t *limit, char *s) {
    565 	char *t;
    566 	int len = strlen(s);
    567 	const struct Suffix *suf = resource->suffix;
    568 
    569 	*limit = 1;
    570 	if (streq(s, "unlimited")) {
    571 		*limit = RLIM_INFINITY;
    572 		return TRUE;
    573 	}
    574 	if (suf == TIMESUF && (t = strchr(s, ':')) != NULL) {
    575 		int min, sec;
    576 		*t++ = '\0';
    577 		min = a2u(s); sec = a2u(t);
    578 		if (min == -1 || sec == -1) return FALSE;
    579 		*limit = 60 * min + sec;
    580 	} else {
    581 		int n;
    582 		for (; suf != NULL; suf = suf->next)
    583 			if (streq(suf->name, s + len - strlen(suf->name))) {
    584 				s[len - strlen(suf->name)] = '\0';
    585 				*limit *= suf->amount;
    586 				break;
    587 			}
    588 		n = a2u(s);
    589 		if (n == -1) return FALSE;
    590 		*limit *= n;
    591 	}
    592 	return TRUE;
    593 }
    594 
    595 static void b_limit(char **av) {
    596 	const struct Limit *lp = limits;
    597 	bool hard = FALSE;
    598 	if (*++av != NULL && streq(*av, "-h")) {
    599 		av++;
    600 		hard = TRUE;
    601 	}
    602 	if (*av == NULL) {
    603 		for (; lp->name != NULL; lp++)
    604 			printlimit(lp, hard);
    605 		return;
    606 	}
    607 	for (;; lp++) {
    608 		if (lp->name == NULL) {
    609 			fprint(2, "no such limit\n");
    610 			set(FALSE);
    611 			return;
    612 		}
    613 		if (streq(*av, lp->name))
    614 			break;
    615 	}
    616 	if (*++av == NULL)
    617 		printlimit(lp, hard);
    618 	else {
    619 		struct rlimit rlim;
    620 		rlim_t pl;
    621 		getrlimit(lp->flag, &rlim);
    622 		if (!parselimit(lp, &pl, *av)) {
    623 			fprint(2, "bad limit\n");
    624 			set(FALSE);
    625 			return;
    626 		}
    627 		if (hard)
    628 			rlim.rlim_max = pl;
    629 		else
    630 			rlim.rlim_cur = pl;
    631 		if (setrlimit(lp->flag, &rlim) == -1) {
    632 			uerror("setrlimit");
    633 			set(FALSE);
    634 		} else
    635 			set(TRUE);
    636 	}
    637 }
    638 #endif
    639 
    640 extern char *compl_builtin(const char *text, int state) {
    641 	return compl_name(text, state, &builtins[0].name, arraysize(builtins), &builtins[1].name - &builtins[0].name);
    642 }