/* 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 "jbwrap.h" #include #include "rc.h" #ifndef NOLIMITS #include #include #endif #include "utils.h" #include "walk.h" #include "input.h" #include "builtins.h" #include "hash.h" #include "nalloc.h" #include "status.h" #include "footobar.h" #include "lex.h" #include "open.h" #include "except.h" #include "redir.h" #include "glom.h" #include "tree.h" #include "sigmsgs.h" #include "getopt.h" #include "wait.h" #include "addon.h" extern int umask(int); static void b_break(char **), b_cd(char **), b_echo(char **), b_eval(char **), b_exit(char **), b_limit(char **), b_return(char **), b_shift(char **), b_umask(char **), b_wait(char **), b_whatis(char **); static builtin_t *const builtins[] = { b_break, b_builtin, b_cd, b_echo, b_eval, b_exec, b_exit, b_limit, b_return, b_shift, b_umask, b_wait, b_whatis, b_dot, ADDON_FUN }; static char *const builtins_str[] = { "break", "builtin", "cd", "echo", "eval", "exec", "exit", "limit", "return", "shift", "umask", "wait", "whatis", ".", ADDON_STR }; builtin_t *isbuiltin(char *s) { int i; for (i = 0; i < arraysize(builtins_str); i++) if (streq(builtins_str[i], s)) return builtins[i]; return NULL; } /* funcall() is the wrapper used to invoke shell functions. pushes $*, and "return" returns here. */ void funcall(char **av) { jbwrap j; Estack e1, e2; if (setjmp(j.j)) return; starassign(*av, av+1, TRUE); except(RETURN, &j, &e1); except(VARSTACK, "*", &e2); walk(treecpy(fnlookup(*av),nalloc), TRUE); varrm("*", TRUE); unexcept(); /* VARSTACK */ unexcept(); /* RETURN */ } 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", num); set(FALSE); } /* a dummy command. (exec() performs "exec" simply by not forking) */ void b_exec(char **av) { } /* echo -n omits a newline. echo -- -n echos '-n' */ static void b_echo(char **av) { SIZE_T i; char *format = "%a\n"; if (*++av != NULL) { if (streq(*av, "-n")) { format = "%a"; av++; } else if (streq(*av, "--")) { av++; } } i = strarraylen(av) + 1; /* one for the null terminator */ if (i < FPRINT_SIZE) fprint(1, format, av); else writeall(1, sprint(nalloc(i), format, av), i-1); 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)) { /* 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); 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) { int s; if (av[1] == NULL) rc_exit(getstatus()); if (av[2] != NULL) { fprint(2, "exit: too many arguments\n"); rc_exit(1); } if ((s = a2u(av[1])) >= 0) rc_exit(s); badnum(av[1]); rc_exit(1); } /* 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[1] != NULL) ssetstatus(av + 1); rc_raise(RETURN); } /* 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(BREAK); } /* shift $* n places (default 1) */ static void b_shift(char **av) { int shift; List *s, *dollarzero; shift = (av[1] == NULL ? 1 : a2u(av[1])); 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 */ 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(&stat); setstatus(stat); return; } if (av[2] != NULL) { arg_count("wait"); return; } if ((pid = a2u(av[1])) < 0) { badnum(av[1]); return; } if (rc_wait4(pid, &stat) > 0) setstatus(stat); else set(FALSE); } /* 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) { enum bool f,found; int i,j,ac,c; List *s; Node *n; char *e; boolean ess = FALSE; optind = 0; for (ac = 0; av[ac] != NULL; ac++) ; /* count the arguments for getopt */ while ((c = getopt(ac, av, "s")) != -1) switch (c) { case 's': ess = TRUE; break; case '?': set(FALSE); return; } av += 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; for (j = 0; j < arraysize(builtins_str); j++) if (streq(av[i], builtins_str[j])) break; fprint(1, "builtin %s\n", builtins_str[j]); } 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) { boolean i = interactive; if (av[1] == NULL) return; interactive = FALSE; pushinput(STRING, 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. */ void b_dot(char **av) { int fd; boolean old_i = interactive, i = FALSE; Estack e; 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, FROM); 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); pushinput(FD, fd); interactive = i; except(VARSTACK, "*", &e); doit(TRUE); varrm("*", TRUE); unexcept(); /* VARSTACK */ interactive = old_i; } /* Berkeley limit support was cleaned up by Paul Haahr. */ #ifdef NOLIMITS static void b_limit(char **av) { rc_error("rc was compiled without berkeley limits"); } #else 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, boolean 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 /= suf->amount; break; } fprint(1, "%s \t%d%s\n", limit->name, lim, suf == NULL ? "" : suf->name); } } static long parselimit(const Limit *limit, char *s) { int len = strlen(s); long lim = 1; const Suffix *suf = limit->suffix; if (streq(s, "unlimited")) return RLIM_INFINITY; if (suf == TIMESUF && strchr(s, ':') != NULL) { char *t = strchr(s, ':'); *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; boolean 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