summaryrefslogtreecommitdiff
path: root/builtins.c
diff options
context:
space:
mode:
authorByron Rakitzis <byron@archone.tamu.edu>1992-05-27 00:00:01 +0100
committerToby Goodwin <toby@paccrat.org>2015-05-16 21:22:56 +0100
commit26bb34cf036027dfa22dfae8d8fc2f6bfd6acb75 (patch)
tree589705b2492ce4681760846d9ca45600f0af298d /builtins.c
release: rc-1.4
Diffstat (limited to 'builtins.c')
-rw-r--r--builtins.c532
1 files changed, 532 insertions, 0 deletions
diff --git a/builtins.c b/builtins.c
new file mode 100644
index 0000000..d9b2e2e
--- /dev/null
+++ b/builtins.c
@@ -0,0 +1,532 @@
+/* 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"
+#ifndef NOLIMITS
+#include <sys/time.h>
+#include <sys/resource.h>
+#endif
+#include "addon.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 **);
+
+#ifndef NOLIMITS
+static void b_limit(char **);
+#endif
+#ifndef NOECHO
+static void b_echo(char **);
+#endif
+
+static struct {
+ builtin_t *p;
+ char *name;
+} builtins[] = {
+ { b_break, "break" },
+ { b_builtin, "builtin" },
+ { b_cd, "cd" },
+#ifndef NOECHO
+ { b_echo, "echo" },
+#endif
+ { b_eval, "eval" },
+ { b_exec, "exec" },
+ { b_exit, "exit" },
+#ifndef NOLIMITS
+ { b_limit, "limit" },
+#endif
+ { b_newpgrp, "newpgrp" },
+ { b_return, "return" },
+ { b_shift, "shift" },
+ { b_umask, "umask" },
+ { b_wait, "wait" },
+ { b_whatis, "whatis" },
+ { b_dot, "." },
+ ADDONS
+};
+
+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) {
+}
+
+#ifndef NOECHO
+/* 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);
+}
+#endif
+
+/* 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);
+#ifdef TIOCSPGRP
+ ioctl(2, TIOCSPGRP, &rc_pid);
+#endif
+}
+
+/* Berkeley limit support was cleaned up by Paul Haahr. */
+
+#ifndef NOLIMITS
+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 },
+#ifdef RLIMIT_RSS /* SysVr4 does not have this */
+ { "memoryuse", RLIMIT_RSS, SIZESUF },
+#endif
+#ifdef RLIMIT_VMEM /* instead, they have this! */
+ { "vmemory", RLIMIT_VMEM, SIZESUF },
+#endif
+#ifdef RLIMIT_NOFILE /* SunOS 4.1 adds a limit on file descriptors */
+ { "descriptors", RLIMIT_NOFILE, NOSUF },
+#endif
+ { NULL, 0, NULL }
+};
+
+extern int getrlimit(int, struct rlimit *);
+extern int setrlimit(int, struct rlimit *);
+
+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 (lim == 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);
+ }
+}
+#endif