+ INTOFF;
+ do {
+ setvareq(lp->text, flags);
+ } while ((lp = lp->next));
+ INTON;
+}
+
+
+/*
+ * Find the value of a variable. Returns NULL if not set.
+ */
+
+static char *
+lookupvar(const char *name)
+{
+ struct var *v;
+
+ if ((v = *findvar(hashvar(name), name))) {
+#ifdef DYNAMIC_VAR
+ /*
+ * Dynamic variables are implemented roughly the same way they are
+ * in bash. Namely, they're "special" so long as they aren't unset.
+ * As soon as they're unset, they're no longer dynamic, and dynamic
+ * lookup will no longer happen at that point. -- PFM.
+ */
+ if((v->flags & VDYNAMIC))
+ (*v->func)(NULL);
+#endif
+ if(!(v->flags & VUNSET))
+ return strchrnul(v->text, '=') + 1;
+ }
+
+ return NULL;
+}
+
+
+/*
+ * Search the environment of a builtin command.
+ */
+
+static char *
+bltinlookup(const char *name)
+{
+ struct strlist *sp;
+
+ for (sp = cmdenviron ; sp ; sp = sp->next) {
+ if (varequal(sp->text, name))
+ return strchrnul(sp->text, '=') + 1;
+ }
+ return lookupvar(name);
+}
+
+
+/*
+ * Generate a list of variables satisfying the given conditions.
+ */
+
+static char **
+listvars(int on, int off, char ***end)
+{
+ struct var **vpp;
+ struct var *vp;
+ char **ep;
+ int mask;
+
+ STARTSTACKSTR(ep);
+ vpp = vartab;
+ mask = on | off;
+ do {
+ for (vp = *vpp ; vp ; vp = vp->next)
+ if ((vp->flags & mask) == on) {
+ if (ep == stackstrend())
+ ep = growstackstr();
+ *ep++ = (char *) vp->text;
+ }
+ } while (++vpp < vartab + VTABSIZE);
+ if (ep == stackstrend())
+ ep = growstackstr();
+ if (end)
+ *end = ep;
+ *ep++ = NULL;
+ return grabstackstr(ep);
+}
+
+
+/*
+ * POSIX requires that 'set' (but not export or readonly) output the
+ * variables in lexicographic order - by the locale's collating order (sigh).
+ * Maybe we could keep them in an ordered balanced binary tree
+ * instead of hashed lists.
+ * For now just roll 'em through qsort for printing...
+ */
+
+static int
+showvars(const char *sep_prefix, int on, int off)
+{
+ const char *sep;
+ char **ep, **epend;
+
+ ep = listvars(on, off, &epend);
+ qsort(ep, epend - ep, sizeof(char *), vpcmp);
+
+ sep = *sep_prefix ? spcstr : sep_prefix;
+
+ for (; ep < epend; ep++) {
+ const char *p;
+ const char *q;
+
+ p = strchrnul(*ep, '=');
+ q = nullstr;
+ if (*p)
+ q = single_quote(++p);
+
+ out1fmt("%s%s%.*s%s\n", sep_prefix, sep, (int)(p - *ep), *ep, q);
+ }
+
+ return 0;
+}
+
+
+
+/*
+ * The export and readonly commands.
+ */
+
+static int
+exportcmd(int argc, char **argv)
+{
+ struct var *vp;
+ char *name;
+ const char *p;
+ char **aptr;
+ int flag = argv[0][0] == 'r'? VREADONLY : VEXPORT;
+ int notp;
+
+ notp = nextopt("p") - 'p';
+ if (notp && ((name = *(aptr = argptr)))) {
+ do {
+ if ((p = strchr(name, '=')) != NULL) {
+ p++;
+ } else {
+ if ((vp = *findvar(hashvar(name), name))) {
+ vp->flags |= flag;
+ continue;
+ }
+ }
+ setvar(name, p, flag);
+ } while ((name = *++aptr) != NULL);
+ } else {
+ showvars(argv[0], flag, 0);
+ }
+ return 0;
+}
+
+
+/*
+ * Make a variable a local variable. When a variable is made local, it's
+ * value and flags are saved in a localvar structure. The saved values
+ * will be restored when the shell function returns. We handle the name
+ * "-" as a special case.
+ */
+
+static void mklocal(char *name)
+{
+ struct localvar *lvp;
+ struct var **vpp;
+ struct var *vp;
+
+ INTOFF;
+ lvp = ckmalloc(sizeof (struct localvar));
+ if (LONE_DASH(name)) {
+ char *p;
+ p = ckmalloc(sizeof(optlist));
+ lvp->text = memcpy(p, optlist, sizeof(optlist));
+ vp = NULL;
+ } else {
+ char *eq;
+
+ vpp = hashvar(name);
+ vp = *findvar(vpp, name);
+ eq = strchr(name, '=');
+ if (vp == NULL) {
+ if (eq)
+ setvareq(name, VSTRFIXED);
+ else
+ setvar(name, NULL, VSTRFIXED);
+ vp = *vpp; /* the new variable */
+ lvp->flags = VUNSET;
+ } else {
+ lvp->text = vp->text;
+ lvp->flags = vp->flags;
+ vp->flags |= VSTRFIXED|VTEXTFIXED;
+ if (eq)
+ setvareq(name, 0);
+ }
+ }
+ lvp->vp = vp;
+ lvp->next = localvars;
+ localvars = lvp;
+ INTON;
+}
+
+/*
+ * The "local" command.
+ */
+
+static int
+localcmd(int argc, char **argv)
+{
+ char *name;
+
+ argv = argptr;
+ while ((name = *argv++) != NULL) {
+ mklocal(name);
+ }
+ return 0;
+}
+
+
+/*
+ * Called after a function returns.
+ * Interrupts must be off.
+ */
+
+static void
+poplocalvars(void)
+{
+ struct localvar *lvp;
+ struct var *vp;
+
+ while ((lvp = localvars) != NULL) {
+ localvars = lvp->next;
+ vp = lvp->vp;
+ TRACE(("poplocalvar %s", vp ? vp->text : "-"));
+ if (vp == NULL) { /* $- saved */
+ memcpy(optlist, lvp->text, sizeof(optlist));
+ ckfree(lvp->text);
+ optschanged();
+ } else if ((lvp->flags & (VUNSET|VSTRFIXED)) == VUNSET) {
+ unsetvar(vp->text);
+ } else {
+ if (vp->func)
+ (*vp->func)(strchrnul(lvp->text, '=') + 1);
+ if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0)
+ ckfree(vp->text);
+ vp->flags = lvp->flags;
+ vp->text = lvp->text;
+ }
+ ckfree(lvp);
+ }
+}
+
+
+/*
+ * The unset builtin command. We unset the function before we unset the
+ * variable to allow a function to be unset when there is a readonly variable
+ * with the same name.
+ */
+
+int
+unsetcmd(int argc, char **argv)
+{
+ char **ap;
+ int i;
+ int flag = 0;
+ int ret = 0;
+
+ while ((i = nextopt("vf")) != '\0') {
+ flag = i;
+ }
+
+ for (ap = argptr; *ap ; ap++) {
+ if (flag != 'f') {
+ i = unsetvar(*ap);
+ ret |= i;
+ if (!(i & 2))
+ continue;
+ }
+ if (flag != 'v')
+ unsetfunc(*ap);
+ }
+ return ret & 1;
+}
+
+
+/*
+ * Unset the specified variable.
+ */
+
+int
+unsetvar(const char *s)
+{
+ struct var **vpp;
+ struct var *vp;
+ int retval;
+
+ vpp = findvar(hashvar(s), s);
+ vp = *vpp;
+ retval = 2;
+ if (vp) {
+ int flags = vp->flags;
+
+ retval = 1;
+ if (flags & VREADONLY)
+ goto out;
+#ifdef DYNAMIC_VAR
+ vp->flags &= ~VDYNAMIC;
+#endif
+ if (flags & VUNSET)
+ goto ok;
+ if ((flags & VSTRFIXED) == 0) {
+ INTOFF;
+ if ((flags & (VTEXTFIXED|VSTACK)) == 0)
+ ckfree(vp->text);
+ *vpp = vp->next;
+ ckfree(vp);
+ INTON;
+ } else {
+ setvar(s, 0, 0);
+ vp->flags &= ~VEXPORT;
+ }
+ok:
+ retval = 0;
+ }
+
+out:
+ return retval;
+}
+
+
+
+/*
+ * Find the appropriate entry in the hash table from the name.
+ */
+
+static struct var **
+hashvar(const char *p)
+{
+ unsigned int hashval;
+
+ hashval = ((unsigned char) *p) << 4;
+ while (*p && *p != '=')
+ hashval += (unsigned char) *p++;
+ return &vartab[hashval % VTABSIZE];
+}
+
+
+
+/*
+ * Compares two strings up to the first = or '\0'. The first
+ * string must be terminated by '='; the second may be terminated by
+ * either '=' or '\0'.
+ */
+
+int
+varcmp(const char *p, const char *q)
+{
+ int c, d;
+
+ while ((c = *p) == (d = *q)) {
+ if (!c || c == '=')
+ goto out;
+ p++;
+ q++;
+ }
+ if (c == '=')
+ c = 0;
+ if (d == '=')
+ d = 0;
+out:
+ return c - d;
+}
+
+static int
+vpcmp(const void *a, const void *b)
+{
+ return varcmp(*(const char **)a, *(const char **)b);
+}
+
+static struct var **
+findvar(struct var **vpp, const char *name)
+{
+ for (; *vpp; vpp = &(*vpp)->next) {
+ if (varequal((*vpp)->text, name)) {
+ break;
+ }
+ }
+ return vpp;
+}
+/* setmode.c */
+
+#include <sys/times.h>
+
+static const unsigned char timescmd_str[] = {
+ ' ', offsetof(struct tms, tms_utime),
+ '\n', offsetof(struct tms, tms_stime),
+ ' ', offsetof(struct tms, tms_cutime),
+ '\n', offsetof(struct tms, tms_cstime),
+ 0
+};
+
+static int timescmd(int ac, char **av)
+{
+ long int clk_tck, s, t;
+ const unsigned char *p;
+ struct tms buf;
+
+ clk_tck = sysconf(_SC_CLK_TCK);
+ times(&buf);
+
+ p = timescmd_str;
+ do {
+ t = *(clock_t *)(((char *) &buf) + p[1]);
+ s = t / clk_tck;
+ out1fmt("%ldm%ld.%.3lds%c",
+ s/60, s%60,
+ ((t - s * clk_tck) * 1000) / clk_tck,
+ p[0]);
+ } while (*(p += 2));
+
+ return 0;
+}
+
+#ifdef CONFIG_ASH_MATH_SUPPORT
+static arith_t
+dash_arith(const char *s)
+{
+ arith_t result;
+ int errcode = 0;
+
+ INTOFF;
+ result = arith(s, &errcode);
+ if (errcode < 0) {
+ if (errcode == -3)
+ sh_error("exponent less than 0");
+ else if (errcode == -2)
+ sh_error("divide by zero");
+ else if (errcode == -5)
+ sh_error("expression recursion loop detected");
+ else
+ synerror(s);
+ }
+ INTON;
+
+ return result;
+}
+
+
+/*
+ * The let builtin. partial stolen from GNU Bash, the Bourne Again SHell.
+ * Copyright (C) 1987, 1989, 1991 Free Software Foundation, Inc.
+ *
+ * Copyright (C) 2003 Vladimir Oleynik <dzo@simtreas.ru>
+ */
+
+static int
+letcmd(int argc, char **argv)
+{
+ char **ap;
+ arith_t i = 0;
+
+ ap = argv + 1;
+ if(!*ap)
+ sh_error("expression expected");
+ for (ap = argv + 1; *ap; ap++) {
+ i = dash_arith(*ap);
+ }
+
+ return !i;
+}
+#endif /* CONFIG_ASH_MATH_SUPPORT */
+
+/* miscbltin.c */
+
+/*
+ * Miscellaneous builtins.
+ */
+
+#undef rflag
+
+#if defined(__GLIBC__) && __GLIBC__ == 2 && __GLIBC_MINOR__ < 1
+typedef enum __rlimit_resource rlim_t;
+#endif
+
+
+/*
+ * The read builtin. The -e option causes backslashes to escape the
+ * following character.
+ *
+ * This uses unbuffered input, which may be avoidable in some cases.
+ */
+
+static int
+readcmd(int argc, char **argv)
+{
+ char **ap;
+ int backslash;
+ char c;
+ int rflag;
+ char *prompt;
+ const char *ifs;
+ char *p;
+ int startword;
+ int status;
+ int i;
+#if defined(CONFIG_ASH_READ_NCHARS)
+ int nch_flag = 0;
+ int nchars = 0;
+ int silent = 0;
+ struct termios tty, old_tty;
+#endif
+#if defined(CONFIG_ASH_READ_TIMEOUT)
+ fd_set set;
+ struct timeval ts;
+
+ ts.tv_sec = ts.tv_usec = 0;
+#endif
+
+ rflag = 0;
+ prompt = NULL;
+#if defined(CONFIG_ASH_READ_NCHARS) && defined(CONFIG_ASH_READ_TIMEOUT)
+ while ((i = nextopt("p:rt:n:s")) != '\0')
+#elif defined(CONFIG_ASH_READ_NCHARS)
+ while ((i = nextopt("p:rn:s")) != '\0')
+#elif defined(CONFIG_ASH_READ_TIMEOUT)
+ while ((i = nextopt("p:rt:")) != '\0')
+#else
+ while ((i = nextopt("p:r")) != '\0')
+#endif
+ {
+ switch (i) {
+ case 'p':
+ prompt = optionarg;
+ break;
+#if defined(CONFIG_ASH_READ_NCHARS)
+ case 'n':
+ nchars = strtol(optionarg, &p, 10);
+ if (*p)
+ sh_error("invalid count");
+ nch_flag = (nchars > 0);
+ break;
+ case 's':
+ silent = 1;
+ break;
+#endif
+#if defined(CONFIG_ASH_READ_TIMEOUT)
+ case 't':
+ ts.tv_sec = strtol(optionarg, &p, 10);
+ ts.tv_usec = 0;
+ if (*p == '.') {
+ char *p2;
+ if (*++p) {
+ int scale;
+ ts.tv_usec = strtol(p, &p2, 10);
+ if (*p2)
+ sh_error("invalid timeout");
+ scale = p2 - p;
+ /* normalize to usec */
+ if (scale > 6)
+ sh_error("invalid timeout");
+ while (scale++ < 6)
+ ts.tv_usec *= 10;
+ }
+ } else if (*p) {
+ sh_error("invalid timeout");
+ }
+ if ( ! ts.tv_sec && ! ts.tv_usec)
+ sh_error("invalid timeout");
+ break;
+#endif
+ case 'r':
+ rflag = 1;
+ break;
+ default:
+ break;
+ }
+ }
+ if (prompt && isatty(0)) {
+ out2str(prompt);
+ }
+ if (*(ap = argptr) == NULL)
+ sh_error("arg count");
+ if ((ifs = bltinlookup("IFS")) == NULL)
+ ifs = defifs;
+#if defined(CONFIG_ASH_READ_NCHARS)
+ if (nch_flag || silent) {
+ tcgetattr(0, &tty);
+ old_tty = tty;
+ if (nch_flag) {
+ tty.c_lflag &= ~ICANON;
+ tty.c_cc[VMIN] = nchars;
+ }
+ if (silent) {
+ tty.c_lflag &= ~(ECHO|ECHOK|ECHONL);
+
+ }
+ tcsetattr(0, TCSANOW, &tty);
+ }
+#endif
+#if defined(CONFIG_ASH_READ_TIMEOUT)
+ if (ts.tv_sec || ts.tv_usec) {
+ FD_ZERO (&set);
+ FD_SET (0, &set);
+
+ i = select(FD_SETSIZE, &set, NULL, NULL, &ts);
+ if (!i) {
+#if defined(CONFIG_ASH_READ_NCHARS)
+ if (nch_flag)
+ tcsetattr(0, TCSANOW, &old_tty);
+#endif
+ return 1;
+ }
+ }
+#endif
+ status = 0;
+ startword = 1;
+ backslash = 0;
+ STARTSTACKSTR(p);
+#if defined(CONFIG_ASH_READ_NCHARS)
+ while (!nch_flag || nchars--)
+#else
+ for (;;)
+#endif
+ {
+ if (read(0, &c, 1) != 1) {
+ status = 1;
+ break;
+ }
+ if (c == '\0')
+ continue;
+ if (backslash) {
+ backslash = 0;
+ if (c != '\n')
+ goto put;
+ continue;
+ }
+ if (!rflag && c == '\\') {
+ backslash++;
+ continue;
+ }
+ if (c == '\n')
+ break;
+ if (startword && *ifs == ' ' && strchr(ifs, c)) {
+ continue;
+ }
+ startword = 0;
+ if (ap[1] != NULL && strchr(ifs, c) != NULL) {
+ STACKSTRNUL(p);
+ setvar(*ap, stackblock(), 0);
+ ap++;
+ startword = 1;
+ STARTSTACKSTR(p);
+ } else {
+put:
+ STPUTC(c, p);
+ }
+ }
+#if defined(CONFIG_ASH_READ_NCHARS)
+ if (nch_flag || silent)
+ tcsetattr(0, TCSANOW, &old_tty);
+#endif
+
+ STACKSTRNUL(p);
+ /* Remove trailing blanks */
+ while ((char *)stackblock() <= --p && strchr(ifs, *p) != NULL)
+ *p = '\0';
+ setvar(*ap, stackblock(), 0);
+ while (*++ap != NULL)
+ setvar(*ap, nullstr, 0);
+ return status;
+}
+
+
+static int umaskcmd(int argc, char **argv)
+{
+ static const char permuser[3] = "ugo";
+ static const char permmode[3] = "rwx";
+ static const short int permmask[] = {
+ S_IRUSR, S_IWUSR, S_IXUSR,
+ S_IRGRP, S_IWGRP, S_IXGRP,
+ S_IROTH, S_IWOTH, S_IXOTH
+ };
+
+ char *ap;
+ mode_t mask;
+ int i;
+ int symbolic_mode = 0;
+
+ while (nextopt("S") != '\0') {
+ symbolic_mode = 1;
+ }
+
+ INTOFF;
+ mask = umask(0);
+ umask(mask);
+ INTON;
+
+ if ((ap = *argptr) == NULL) {
+ if (symbolic_mode) {
+ char buf[18];
+ char *p = buf;
+
+ for (i = 0; i < 3; i++) {
+ int j;
+
+ *p++ = permuser[i];
+ *p++ = '=';
+ for (j = 0; j < 3; j++) {
+ if ((mask & permmask[3 * i + j]) == 0) {
+ *p++ = permmode[j];
+ }
+ }
+ *p++ = ',';
+ }
+ *--p = 0;
+ puts(buf);
+ } else {
+ out1fmt("%.4o\n", mask);
+ }
+ } else {
+ if (is_digit((unsigned char) *ap)) {
+ mask = 0;
+ do {
+ if (*ap >= '8' || *ap < '0')
+ sh_error(illnum, argv[1]);
+ mask = (mask << 3) + (*ap - '0');
+ } while (*++ap != '\0');
+ umask(mask);
+ } else {
+ mask = ~mask & 0777;
+ if (!bb_parse_mode(ap, &mask)) {
+ sh_error("Illegal mode: %s", ap);
+ }
+ umask(~mask & 0777);
+ }
+ }
+ return 0;
+}
+
+/*
+ * ulimit builtin
+ *
+ * This code, originally by Doug Gwyn, Doug Kingston, Eric Gisin, and
+ * Michael Rendell was ripped from pdksh 5.0.8 and hacked for use with
+ * ash by J.T. Conklin.
+ *
+ * Public domain.
+ */
+
+struct limits {
+ const char *name;
+ int cmd;
+ int factor; /* multiply by to get rlim_{cur,max} values */
+ char option;
+};
+
+static const struct limits limits[] = {
+#ifdef RLIMIT_CPU
+ { "time(seconds)", RLIMIT_CPU, 1, 't' },
+#endif
+#ifdef RLIMIT_FSIZE
+ { "file(blocks)", RLIMIT_FSIZE, 512, 'f' },
+#endif
+#ifdef RLIMIT_DATA
+ { "data(kbytes)", RLIMIT_DATA, 1024, 'd' },
+#endif
+#ifdef RLIMIT_STACK
+ { "stack(kbytes)", RLIMIT_STACK, 1024, 's' },
+#endif
+#ifdef RLIMIT_CORE
+ { "coredump(blocks)", RLIMIT_CORE, 512, 'c' },
+#endif
+#ifdef RLIMIT_RSS
+ { "memory(kbytes)", RLIMIT_RSS, 1024, 'm' },
+#endif
+#ifdef RLIMIT_MEMLOCK
+ { "locked memory(kbytes)", RLIMIT_MEMLOCK, 1024, 'l' },
+#endif
+#ifdef RLIMIT_NPROC
+ { "process", RLIMIT_NPROC, 1, 'p' },
+#endif
+#ifdef RLIMIT_NOFILE
+ { "nofiles", RLIMIT_NOFILE, 1, 'n' },
+#endif
+#ifdef RLIMIT_AS
+ { "vmemory(kbytes)", RLIMIT_AS, 1024, 'v' },
+#endif
+#ifdef RLIMIT_LOCKS
+ { "locks", RLIMIT_LOCKS, 1, 'w' },
+#endif
+ { (char *) 0, 0, 0, '\0' }
+};
+
+enum limtype { SOFT = 0x1, HARD = 0x2 };
+
+static void printlim(enum limtype how, const struct rlimit *limit,
+ const struct limits *l)
+{
+ rlim_t val;
+
+ val = limit->rlim_max;
+ if (how & SOFT)
+ val = limit->rlim_cur;
+
+ if (val == RLIM_INFINITY)
+ out1fmt("unlimited\n");
+ else {
+ val /= l->factor;
+ out1fmt("%lld\n", (long long) val);
+ }
+}
+
+int
+ulimitcmd(int argc, char **argv)
+{
+ int c;
+ rlim_t val = 0;
+ enum limtype how = SOFT | HARD;
+ const struct limits *l;
+ int set, all = 0;
+ int optc, what;
+ struct rlimit limit;
+
+ what = 'f';
+ while ((optc = nextopt("HSa"
+#ifdef RLIMIT_CPU
+ "t"
+#endif
+#ifdef RLIMIT_FSIZE
+ "f"
+#endif
+#ifdef RLIMIT_DATA
+ "d"
+#endif
+#ifdef RLIMIT_STACK
+ "s"
+#endif
+#ifdef RLIMIT_CORE
+ "c"
+#endif
+#ifdef RLIMIT_RSS
+ "m"
+#endif
+#ifdef RLIMIT_MEMLOCK
+ "l"
+#endif
+#ifdef RLIMIT_NPROC
+ "p"
+#endif
+#ifdef RLIMIT_NOFILE
+ "n"
+#endif
+#ifdef RLIMIT_AS
+ "v"
+#endif
+#ifdef RLIMIT_LOCKS
+ "w"
+#endif
+ )) != '\0')
+ switch (optc) {
+ case 'H':
+ how = HARD;
+ break;
+ case 'S':
+ how = SOFT;
+ break;
+ case 'a':
+ all = 1;
+ break;
+ default:
+ what = optc;
+ }
+
+ for (l = limits; l->option != what; l++)
+ ;
+
+ set = *argptr ? 1 : 0;
+ if (set) {
+ char *p = *argptr;
+
+ if (all || argptr[1])
+ sh_error("too many arguments");
+ if (strncmp(p, "unlimited\n", 9) == 0)
+ val = RLIM_INFINITY;
+ else {
+ val = (rlim_t) 0;
+
+ while ((c = *p++) >= '0' && c <= '9')
+ {
+ val = (val * 10) + (long)(c - '0');
+ if (val < (rlim_t) 0)
+ break;
+ }
+ if (c)
+ sh_error("bad number");
+ val *= l->factor;
+ }
+ }
+ if (all) {
+ for (l = limits; l->name; l++) {
+ getrlimit(l->cmd, &limit);
+ out1fmt("%-20s ", l->name);
+ printlim(how, &limit, l);
+ }
+ return 0;
+ }
+
+ getrlimit(l->cmd, &limit);
+ if (set) {
+ if (how & HARD)
+ limit.rlim_max = val;
+ if (how & SOFT)
+ limit.rlim_cur = val;
+ if (setrlimit(l->cmd, &limit) < 0)
+ sh_error("error setting limit (%m)");
+ } else {
+ printlim(how, &limit, l);