4.4BSD/usr/src/contrib/rc-1.4/builtins.c
/* builtins.c: the collection of rc's builtin commands */
/*
NOTE: rc's builtins do not call "rc_error" because they are
commands, and rc errors usually arise from syntax errors. e.g.,
you probably don't want interpretation of a shell script to stop
because of a bad umask.
*/
#include <sys/ioctl.h>
#include <setjmp.h>
#include <errno.h>
#include "rc.h"
#include "jbwrap.h"
#include <sys/time.h>
#include <sys/resource.h>
extern int umask(int);
static void b_break(char **), b_cd(char **), b_eval(char **), b_exit(char **),
b_newpgrp(char **), b_return(char **), b_shift(char **), b_umask(char **),
b_wait(char **), b_whatis(char **);
static void b_limit(char **);
static void b_echo(char **);
static struct {
builtin_t *p;
char *name;
} builtins[] = {
{ b_break, "break" },
{ b_builtin, "builtin" },
{ b_cd, "cd" },
{ b_echo, "echo" },
{ b_eval, "eval" },
{ b_exec, "exec" },
{ b_exit, "exit" },
{ b_limit, "limit" },
{ b_newpgrp, "newpgrp" },
{ b_return, "return" },
{ b_shift, "shift" },
{ b_umask, "umask" },
{ b_wait, "wait" },
{ b_whatis, "whatis" },
{ b_dot, "." },
};
extern builtin_t *isbuiltin(char *s) {
int i;
for (i = 0; i < arraysize(builtins); i++)
if (streq(builtins[i].name, s))
return builtins[i].p;
return NULL;
}
/* funcall() is the wrapper used to invoke shell functions. pushes $*, and "return" returns here. */
extern void funcall(char **av) {
Jbwrap j;
Estack e1, e2;
Edata jreturn, star;
if (setjmp(j.j))
return;
starassign(*av, av+1, TRUE);
jreturn.jb = &j;
star.name = "*";
except(eReturn, jreturn, &e1);
except(eVarstack, star, &e2);
walk(treecpy(fnlookup(*av), nalloc), TRUE);
varrm("*", TRUE);
unexcept(); /* eVarstack */
unexcept(); /* eReturn */
}
static void arg_count(char *name) {
fprint(2, "too many arguments to %s\n", name);
set(FALSE);
}
static void badnum(char *num) {
fprint(2, "%s is a bad number\n", num);
set(FALSE);
}
/* a dummy command. (exec() performs "exec" simply by not forking) */
extern void b_exec(char **av) {
}
/* echo -n omits a newline. echo -- -n echos '-n' */
static void b_echo(char **av) {
char *format = "%A\n";
if (*++av != NULL) {
if (streq(*av, "-n"))
format = "%A", av++;
else if (streq(*av, "--"))
av++;
}
fprint(1, format, av);
set(TRUE);
}
/* cd. traverse $cdpath if the directory given is not an absolute pathname */
static void b_cd(char **av) {
List *s, nil;
char *path = NULL;
size_t t, pathlen = 0;
if (*++av == NULL) {
s = varlookup("home");
*av = (s == NULL) ? "/" : s->w;
} else if (av[1] != NULL) {
arg_count("cd");
return;
}
if (isabsolute(*av) || streq(*av, ".") || streq(*av, "..")) { /* absolute pathname? */
if (chdir(*av) < 0) {
set(FALSE);
uerror(*av);
} else
set(TRUE);
} else {
s = varlookup("cdpath");
if (s == NULL) {
s = &nil;
nil.w = "";
nil.n = NULL;
}
do {
if (s != &nil && *s->w != '\0') {
t = strlen(*av) + strlen(s->w) + 2;
if (t > pathlen)
path = nalloc(pathlen = t);
strcpy(path, s->w);
if (!streq(s->w, "/")) /* "//" is special to POSIX */
strcat(path, "/");
strcat(path, *av);
} else {
pathlen = 0;
path = *av;
}
if (chdir(path) >= 0) {
set(TRUE);
if (interactive && *s->w != '\0' && !streq(s->w, "."))
fprint(1, "%s\n", path);
return;
}
s = s->n;
} while (s != NULL);
fprint(2, "couldn't cd to %s\n", *av);
set(FALSE);
}
}
static void b_umask(char **av) {
int i;
if (*++av == NULL) {
set(TRUE);
i = umask(0);
umask(i);
fprint(1, "0%o\n", i);
} else if (av[1] == NULL) {
i = o2u(*av);
if ((unsigned int) i > 0777) {
fprint(2, "bad umask\n");
set(FALSE);
} else {
umask(i);
set(TRUE);
}
} else {
arg_count("umask");
return;
}
}
static void b_exit(char **av) {
if (*++av != NULL)
ssetstatus(av);
rc_exit(getstatus());
}
/* raise a "return" exception, i.e., return from a function. if an integer argument is present, set $status to it */
static void b_return(char **av) {
if (*++av != NULL)
ssetstatus(av);
rc_raise(eReturn);
}
/* raise a "break" exception for breaking out of for and while loops */
static void b_break(char **av) {
if (av[1] != NULL) {
arg_count("break");
return;
}
rc_raise(eBreak);
}
/* shift $* n places (default 1) */
static void b_shift(char **av) {
int shift = (av[1] == NULL ? 1 : a2u(av[1]));
List *s, *dollarzero;
if (av[1] != NULL && av[2] != NULL) {
arg_count("shift");
return;
}
if (shift < 0) {
badnum(av[1]);
return;
}
s = varlookup("*")->n;
dollarzero = varlookup("0");
while (s != NULL && shift != 0) {
s = s->n;
--shift;
}
if (s == NULL && shift != 0) {
fprint(2, "cannot shift\n");
set(FALSE);
} else {
varassign("*", append(dollarzero, s), FALSE);
set(TRUE);
}
}
/* dud function */
extern void b_builtin(char **av) {
}
/* wait for a given process, or all outstanding processes */
static void b_wait(char **av) {
int stat, pid;
if (av[1] == NULL) {
waitforall();
return;
}
if (av[2] != NULL) {
arg_count("wait");
return;
}
if ((pid = a2u(av[1])) < 0) {
badnum(av[1]);
return;
}
if (rc_wait4(pid, &stat, FALSE) > 0)
setstatus(pid, stat);
else
set(FALSE);
SIGCHK;
}
/*
whatis without arguments prints all variables and functions. Otherwise, check to see if a name
is defined as a variable, function or pathname.
*/
static void b_whatis(char **av) {
bool f, found;
bool ess = FALSE;
int i, ac, c;
List *s;
Node *n;
char *e;
for (rc_optind = ac = 0; av[ac] != NULL; ac++)
; /* count the arguments for getopt */
while ((c = rc_getopt(ac, av, "s")) == 's')
ess = TRUE;
if (c != -1) {
set(FALSE);
return;
}
av += rc_optind;
if (*av == NULL && !ess) {
whatare_all_vars();
set(TRUE);
return;
}
if (ess)
whatare_all_signals();
found = TRUE;
for (i = 0; av[i] != NULL; i++) {
f = FALSE;
errno = ENOENT;
if ((s = varlookup(av[i])) != NULL) {
f = TRUE;
prettyprint_var(1, av[i], s);
}
if ((n = fnlookup(av[i])) != NULL) {
f = TRUE;
prettyprint_fn(1, av[i], n);
} else if (isbuiltin(av[i]) != NULL) {
f = TRUE;
fprint(1, "builtin %s\n", av[i]);
} else if ((e = which(av[i], FALSE)) != NULL) {
f = TRUE;
fprint(1, "%s\n", e);
}
if (!f) {
found = FALSE;
if (errno != ENOENT)
uerror(av[i]);
else
fprint(2, "%s not found\n", av[i]);
}
}
set(found);
}
/* push a string to be eval'ed onto the input stack. evaluate it */
static void b_eval(char **av) {
bool i = interactive;
if (av[1] == NULL)
return;
interactive = FALSE;
pushstring(av + 1, i); /* don't reset line numbers on noninteractive eval */
doit(TRUE);
interactive = i;
}
/*
push a file to be interpreted onto the input stack. with "-i" treat this as an interactive
input source.
*/
extern void b_dot(char **av) {
int fd;
bool old_i = interactive, i = FALSE;
Estack e;
Edata star;
av++;
if (*av == NULL)
return;
if (streq(*av, "-i")) {
av++;
i = TRUE;
}
if (dasheye) { /* rc -i file has to do the right thing. reset the dasheye state to FALSE, though. */
dasheye = FALSE;
i = TRUE;
}
if (*av == NULL)
return;
fd = rc_open(*av, rFrom);
if (fd < 0) {
if (rcrc) /* on rc -l, don't flag nonexistence of .rcrc */
rcrc = FALSE;
else {
uerror(*av);
set(FALSE);
}
return;
}
rcrc = FALSE;
starassign(*av, av+1, TRUE);
pushfd(fd);
interactive = i;
star.name = "*";
except(eVarstack, star, &e);
doit(TRUE);
varrm("*", TRUE);
unexcept(); /* eVarstack */
interactive = old_i;
}
/* put rc into a new pgrp. Used on the NeXT where the Terminal program is broken (sigh) */
static void b_newpgrp(char **av) {
if (av[1] != NULL) {
arg_count("newpgrp");
return;
}
setpgrp(rc_pid, rc_pid);
ioctl(2, TIOCSPGRP, &rc_pid);
}
/* Berkeley limit support was cleaned up by Paul Haahr. */
typedef struct Suffix Suffix;
struct Suffix {
const Suffix *next;
long amount;
char *name;
};
static const Suffix
kbsuf = { NULL, 1024, "k" },
mbsuf = { &kbsuf, 1024*1024, "m" },
gbsuf = { &mbsuf, 1024*1024*1024, "g" },
stsuf = { NULL, 1, "s" },
mtsuf = { &stsuf, 60, "m" },
htsuf = { &mtsuf, 60*60, "h" };
#define SIZESUF &gbsuf
#define TIMESUF &htsuf
#define NOSUF ((Suffix *) NULL) /* for RLIMIT_NOFILE on SunOS 4.1 */
typedef struct {
char *name;
int flag;
const Suffix *suffix;
} Limit;
static const Limit limits[] = {
{ "cputime", RLIMIT_CPU, TIMESUF },
{ "filesize", RLIMIT_FSIZE, SIZESUF },
{ "datasize", RLIMIT_DATA, SIZESUF },
{ "stacksize", RLIMIT_STACK, SIZESUF },
{ "coredumpsize", RLIMIT_CORE, SIZESUF },
{ "memoryuse", RLIMIT_RSS, SIZESUF },
{ "descriptors", RLIMIT_NOFILE, NOSUF },
{ NULL, 0, NULL }
};
static void printlimit(const Limit *limit, bool hard) {
struct rlimit rlim;
long lim;
getrlimit(limit->flag, &rlim);
if (hard)
lim = rlim.rlim_max;
else
lim = rlim.rlim_cur;
if ((unsigned) lim == (unsigned) RLIM_INFINITY)
fprint(1, "%s \tunlimited\n", limit->name);
else {
const Suffix *suf;
for (suf = limit->suffix; suf != NULL; suf = suf->next)
if (lim % suf->amount == 0 && (lim != 0 || suf->amount > 1)) {
lim /= suf->amount;
break;
}
fprint(1, "%s \t%d%s\n", limit->name, lim, (suf == NULL || lim == 0) ? "" : suf->name);
}
}
static long parselimit(const Limit *limit, char *s) {
char *t;
int len = strlen(s);
long lim = 1;
const Suffix *suf = limit->suffix;
if (streq(s, "unlimited"))
return RLIM_INFINITY;
if (suf == TIMESUF && (t = strchr(s, ':')) != NULL) {
*t++ = '\0';
lim = 60 * a2u(s) + a2u(t);
} else {
for (; suf != NULL; suf = suf->next)
if (streq(suf->name, s + len - strlen(suf->name))) {
s[len - strlen(suf->name)] = '\0';
lim *= suf->amount;
break;
}
lim *= a2u(s);
}
return lim;
}
static void b_limit(char **av) {
const Limit *lp = limits;
bool hard = FALSE;
if (*++av != NULL && streq(*av, "-h")) {
av++;
hard = TRUE;
}
if (*av == NULL) {
for (; lp->name != NULL; lp++)
printlimit(lp, hard);
return;
}
for (;; lp++) {
if (lp->name == NULL) {
fprint(2, "no such limit\n");
set(FALSE);
return;
}
if (streq(*av, lp->name))
break;
}
if (*++av == NULL)
printlimit(lp, hard);
else {
struct rlimit rlim;
long pl;
getrlimit(lp->flag, &rlim);
if ((pl = parselimit(lp, *av)) < 0) {
fprint(2, "bad limit\n");
set(FALSE);
return;
}
if (hard)
rlim.rlim_max = pl;
else
rlim.rlim_cur = pl;
if (setrlimit(lp->flag, &rlim) == -1) {
uerror("setrlimit");
set(FALSE);
} else
set(TRUE);
}
}