diff options
author | Rob Landley <rob@landley.net> | 2020-04-02 02:58:42 -0500 |
---|---|---|
committer | Rob Landley <rob@landley.net> | 2020-04-02 02:58:42 -0500 |
commit | e05d620a79575bc96155180e1efccd171452b82f (patch) | |
tree | 8490cfb99ceca6d661687d162da9c59761afe2cd | |
parent | 85b02bddc0389487eaa8936de38441a1f95e08ea (diff) | |
download | toybox-e05d620a79575bc96155180e1efccd171452b82f.tar.gz |
More shell plumbing. Redo of variable storage, add export.
-rw-r--r-- | lib/env.c | 61 | ||||
-rw-r--r-- | lib/lib.h | 5 | ||||
-rw-r--r-- | toys/pending/sh.c | 496 |
3 files changed, 393 insertions, 169 deletions
@@ -34,18 +34,19 @@ void xclearenv(void) // Frees entries we set earlier. Use with libc getenv but not setenv/putenv. // if name has an equals and !val, act like putenv (name=val must be malloced!) // if !val unset name. (Name with = and val is an error) -void xsetmyenv(int *envc, char ***env, char *name, char *val) +// returns pointer to new name=value environment string, NULL if none +char *xsetenv(char *name, char *val) { unsigned i, len, ec; char *new; // If we haven't snapshot initial environment state yet, do so now. - if (!*envc) { + if (!toys.envc) { // envc is size +1 so even if env empty it's nonzero after initialization - while ((*env)[(*envc)++]); - memcpy(new = xmalloc(((*envc|0xff)+1)*sizeof(char *)), *env, - *envc*sizeof(char *)); - *env = (void *)new; + while (environ[toys.envc++]); + memcpy(new = xmalloc(((toys.envc|0xff)+1)*sizeof(char *)), environ, + toys.envc*sizeof(char *)); + environ = (void *)new; } new = strchr(name, '='); @@ -58,36 +59,31 @@ void xsetmyenv(int *envc, char ***env, char *name, char *val) if (val) new = xmprintf("%s=%s", name, val); } - ec = (*envc)-1; // compensate for size +1 above - for (i = 0; (*env)[i]; i++) { + ec = toys.envc-1; // compensate for size +1 above + for (i = 0; environ[i]; i++) { // Drop old entry, freeing as appropriate. Assumes no duplicates. - if (!memcmp(name, (*env)[i], len) && (*env)[i][len]=='=') { - if (i>=ec) free((*env)[i]); + if (!memcmp(name, environ[i], len) && environ[i][len]=='=') { + if (i>=ec) free(environ[i]); else { // move old entries down, add at end of old data - *envc = ec--; - for (; new ? i<ec : !!(*env)[i]; i++) (*env)[i] = (*env)[i+1]; + toys.envc = ec--; + for (; new ? i<ec : !!environ[i]; i++) environ[i] = environ[i+1]; i = ec; } break; } } - if (!new) return; + if (!new) return 0; // resize and null terminate if expanding - if (!(*env)[i]) { + if (!environ[i]) { len = i+1; - if (!(len&255)) *env = xrealloc(*env, (len+256)*sizeof(char *)); - (*env)[len] = 0; + if (!(len&255)) environ = xrealloc(environ, (len+256)*sizeof(char *)); + environ[len] = 0; } - (*env)[i] = new; -} -// xsetenv for normal environment (extern variables). -void xsetenv(char *name, char *val) -{ - return xsetmyenv(&toys.envc, &environ, name, val); + return environ[i] = new; } void xunsetenv(char *name) @@ -96,6 +92,27 @@ void xunsetenv(char *name) xsetenv(name, 0); } +// remove entry and return pointer instead of freeing +char *xpop_env(char *name) +{ + int len, i; + char *s = 0; + + for (len = 0; name[len] && name[len]!='='; len++); + for (i = 0; environ[i]; i++) { + if (!s && !strncmp(name, environ[i], len) && environ[i][len] == '=') { + s = environ[i]; + if (toys.envc-1>i) { + s = xstrdup(s); + toys.envc--; + } + } + if (s) environ[i] = environ[i+1]; + } + + return s; +} + // reset environment for a user, optionally clearing most of it void reset_env(struct passwd *p, int clear) { @@ -269,7 +269,6 @@ char *getgroupname(gid_t gid); void do_lines(int fd, char delim, void (*call)(char **pline, long len)); long long millitime(void); char *format_iso_time(char *buf, size_t len, struct timespec *ts); -void reset_env(struct passwd *p, int clear); void loggit(int priority, char *format, ...); unsigned tar_cksum(void *data); int is_tar_header(void *pkt); @@ -284,9 +283,11 @@ int human_readable(char *buf, unsigned long long num, int style); // env.c long environ_bytes(); -void xsetenv(char *name, char *val); +char *xsetenv(char *name, char *val); void xunsetenv(char *name); +char *xpop_env(char *name); // because xpopenv() looks like xpopen_v() void xclearenv(void); +void reset_env(struct passwd *p, int clear); // linestack.c diff --git a/toys/pending/sh.c b/toys/pending/sh.c index 0b2e85c1..9b116a73 100644 --- a/toys/pending/sh.c +++ b/toys/pending/sh.c @@ -23,7 +23,6 @@ * TODO: test that $PS1 color changes work without stupid \[ \] hack * TODO: Handle embedded NUL bytes in the command line? (When/how?) - * TODO: replace getenv() with faster func: sort env and binary search * builtins: alias bg command fc fg getopts jobs newgrp read umask unalias wait * disown umask suspend source pushd popd dirs logout times trap @@ -48,10 +47,11 @@ * then until while { } time [[ ]] USE_SH(NEWTOY(cd, ">1LP[-LP]", TOYFLAG_NOFORK)) -USE_SH(NEWTOY(exit, 0, TOYFLAG_NOFORK)) -USE_SH(NEWTOY(unset, "fvn", TOYFLAG_NOFORK)) USE_SH(NEWTOY(eval, 0, TOYFLAG_NOFORK)) USE_SH(NEWTOY(exec, "cla:", TOYFLAG_NOFORK)) +USE_SH(NEWTOY(exit, 0, TOYFLAG_NOFORK)) +USE_SH(NEWTOY(export, "np", TOYFLAG_NOFORK)) +USE_SH(NEWTOY(unset, "fvn", TOYFLAG_NOFORK)) USE_SH(NEWTOY(sh, "(noediting)(noprofile)(norc)sc:i", TOYFLAG_BIN)) USE_SH(OLDTOY(toysh, sh, TOYFLAG_BIN)) @@ -126,6 +126,20 @@ config EXEC -a set argv[0] to NAME -c clear environment -l prepend - to argv[0] + +config EXPORT + bool + default n + depends on SH + help + usage: export [-n] [NAME[=VALUE]...] + + Make variables available to child processes. NAME exports existing local + variable(s), NAME=VALUE sets and exports. + + -n Unexport. Turn listed variable(s) into local variables. + + With no arguments list exported variables/attributes as "declare" statements. */ #define FOR_sh @@ -143,10 +157,16 @@ GLOBALS( // keep lineno here, we use it to work around a compiler bug long lineno; - char **locals, *subshell_env, *ifs; + char *ifs; struct double_list functions; - unsigned options, jobcnt, loc_ro, loc_magic; - int hfd, pid; + unsigned options, jobcnt; + int hfd, pid, varlen, cdcount; + unsigned long long SECONDS; + + struct sh_vars { + long flags; + char *str; + } *vars; // Running jobs for job control. struct sh_job { @@ -197,6 +217,7 @@ struct sh_function { #define BUGBUG 0 +// call with NULL to just dump FDs static void dump_state(struct sh_function *sp) { struct sh_pipeline *pl; @@ -205,7 +226,7 @@ static void dump_state(struct sh_function *sp) DIR *dir = fdopendir(fd); char buf[256]; - if (sp->expect) { + if (sp && sp->expect) { struct double_list *dl; for (dl = sp->expect; dl; dl = (dl->next == sp->expect) ? 0 : dl->next) @@ -215,7 +236,7 @@ static void dump_state(struct sh_function *sp) sp->pipeline->prev->here); } - for (pl = sp->pipeline; pl ; pl = (pl->next == sp->pipeline) ? 0 : pl->next) { + if (sp) for (pl = sp->pipeline; pl ; pl = (pl->next == sp->pipeline) ? 0 : pl->next) { for (i = 0; i<pl->arg->c; i++) dprintf(255, "arg[%d][%ld]=%s\n", q, i, pl->arg->v[i]); if (pl->arg->c<0) dprintf(255, "argc=%d\n", pl->arg->c); @@ -275,64 +296,161 @@ static void array_add_del(char ***list, unsigned count, char *data, array_add(list, count, data); } +// return length of valid variable name +static char *varend(char *s) +{ + if (isdigit(*s)) return s; + while (*s>' ' && (*s=='_' || !ispunct(*s))) s++; + + return s; +} + // Return index of variable within this list -static unsigned findvar(char **list, char *name, int len) +static struct sh_vars *findvar(char *name) { - unsigned i; + int len = varend(name)-name; + struct sh_vars *var = TT.vars+TT.varlen; - for (i = 0; list[i]; i++) - if (!strncmp(list[i], name, len) && list[i][len] == '=') break; + if (len) while (var-- != TT.vars) + if (!strncmp(var->str, name, len) && var->str[len] == '=') return var; - return i; + return 0; +} + +// Append variable to TT.vars, returning *struct. Does not check duplicates. +static struct sh_vars *addvar(char *s) +{ + if (!(TT.varlen&31)) + TT.vars = xrealloc(TT.vars, (TT.varlen+32)*sizeof(*TT.vars)); + TT.vars[TT.varlen].flags = 0; + TT.vars[TT.varlen].str = s; + + return TT.vars+TT.varlen++; +} + +// TODO function to resolve a string into a number for $((1+2)) etc +long long do_math(char *s) +{ + return atoll(s); } -// Assign one variable -// s: key=val -// type: 0 = whatever it was before, local otherwise -#define TAKE_MEM 0x80000000 +// Assign one variable from malloced key=val string, returns var struct +// TODO implement remaining types +#define VAR_DICT 256 +#define VAR_ARRAY 128 +#define VAR_INT 64 +#define VAR_TOLOWER 32 +#define VAR_TOUPPER 16 +#define VAR_NAMEREF 8 +#define VAR_GLOBAL 4 +#define VAR_READONLY 2 +#define VAR_MAGIC 1 + // declare -aAilnrux // ft -static void setvar(char *s, unsigned type) +static struct sh_vars *setvar(char *s) { - unsigned uu; - int len = stridx(s, '='); + int len = varend(s)-s; + long flags; + struct sh_vars *var; + + if (s[len] != '=') { + error_msg("bad setvar %s\n", s); + free(s); + return 0; + } + if (len == 3 && !memcmp(s, "IFS", 3)) TT.ifs = s+4; - if (len == -1) return error_msg("no = in setvar %s\n", s); + if (!(var = findvar(s))) return addvar(s); + flags = var->flags; - if (type&TAKE_MEM) type ^= TAKE_MEM; - else s = xstrdup(s); + if (flags&VAR_READONLY) { + error_msg("%.*s: read only", len, s); + free(s); - if (len == 3 && !memcmp(s, "IFS", 3)) TT.ifs = s+4; + return var; + } else if (flags&VAR_MAGIC) { + if (*s == 'S') TT.SECONDS = millitime() - 1000*do_math(s+len-1); + else if (*s == 'R') srandom(do_math(s+len-1)); + } else if (flags&VAR_GLOBAL) xsetenv(var->str = s, 0); + else { + free(var->str); + var->str = s; + } +// TODO if (flags&(VAR_TOUPPER|VAR_TOLOWER)) +// unicode _is stupid enough for upper/lower case to be different utf8 byte +// lengths. example: lowercase of U+0130 (C4 B0) is U+0069 (69) +// TODO VAR_INT +// TODO VAR_ARRAY VAR_DICT - // local, export, readonly, integer... - - // exported variable? - if (environ && environ[uu = findvar(environ, s, len)]) { - if (uu>=toys.envc) free(environ[uu]); - environ[uu] = s; - } else if (TT.locals[uu = findvar(TT.locals, s, len)]) { - if (uu<TT.loc_ro) return error_msg("%.*s: readonly variable", len, s); - free(TT.locals[uu]); - TT.locals[uu] = s; - } else array_add(&TT.locals, uu, s); + return var; } -// get variable of length len starting at s. -static char *getvarbylen(char *s, int len) +static struct sh_vars *setvarval(char *name, char *val) { - int i; + return setvar(xmprintf("%s=%s", name, val)); +} + +// get value of variable starting at s. +static char *getvar(char *s) +{ + struct sh_vars *var = findvar(s); - if (TT.locals && TT.locals[i = findvar(TT.locals, s, len)]) - return TT.locals[i]+len+1; - if (environ && environ[i = findvar(environ, s, len)]) - return environ[i]+len+1; + if (!var) return 0; - return 0; + if (var->flags & VAR_MAGIC) { + char c = *var->str; + + if (c == 'S') sprintf(toybuf, "%lld", (millitime()-TT.SECONDS)/1000); + else if (c == 'R') sprintf(toybuf, "%ld", random()&((1<<16)-1)); + else if (c == 'L') sprintf(toybuf, "%ld", TT.lineno); + else if (c == 'G') sprintf(toybuf, "TODO: GROUPS"); + + return toybuf; + } + + return varend(var->str)+1; } -static char *getvar(char *s) +static void unsetvar(char *name) { - return getvarbylen(s, strlen(s)); + struct sh_vars *var = findvar(name); + int ii; + + if (!var) return; + if (var->flags&VAR_GLOBAL) { + *varend(var->str) = 0; + xunsetenv(var->str); + } else free(var->str); + + ii = var-TT.vars; + memmove(TT.vars+ii, TT.vars+ii+1, TT.varlen-ii); +} + +// malloc declare -x "escaped string" +static char *declarep(struct sh_vars *var) +{ + char *types = "-rgnuliaA", *in = types, flags[16], *out = flags, *ss; + int len; + + while (*++in) if (var->flags&(1<<(in-types))) *out++ = *in; + if (in == types) *out++ = *types; + *out = 0; + len = out-flags; + + for (in = types = varend(var->str); *in; in++) len += !!strchr("$\"\\`", *in); + len += in-types; + ss = xmalloc(len+13); + + out = ss + sprintf(ss, "declare -%s \"", out); + while (types) { + if (strchr("$\"\\`", *in)) *out++ = '\\'; + *out++ = *types++; + } + *out++ = '"'; + *out = 0; + + return ss; } // return length of match found at this point (try is null terminated array) @@ -359,8 +477,7 @@ static int redir_prefix(char *word) char *s = word; if (*s == '{') { - for (s++; isalnum(*s) || *s=='_'; s++); - if (*s == '}' && s != word+1) s++; + if (*(s = varend(s+1)) == '}' && s != word+1) s++; else s = word; } else while (isdigit(*s)) s++; @@ -494,7 +611,7 @@ if (BUGBUG) dprintf(255, "%d redir from=%d to=%d hfd=%d\n", getpid(), from, to, return 1; } } else { -dprintf(255, "%d schedule close %d\n", getpid(), to); +if (BUGBUG) dprintf(255, "%d schedule close %d\n", getpid(), to); hfd = to; to = -1; } @@ -509,12 +626,12 @@ dprintf(255, "%d schedule close %d\n", getpid(), to); } // TODO: waitpid(WNOHANG) to clean up zombies and catch background& ending -// TODO: xunsetenv() after vfork()? static void subshell_callback(void) { - TT.subshell_env = xmprintf("@%d,%d=", getpid(), getppid()); - xsetenv(TT.subshell_env, 0); - TT.subshell_env[strlen(TT.subshell_env)-1] = 0; + char *s; + + xsetenv(s = xmprintf("@%d,%d=", getpid(), getppid()), 0); + s[strlen(s)-1] = 0; xsetenv(xmprintf("$=%d", TT.pid), 0); // TODO: test $$ in (nommu) } @@ -552,10 +669,22 @@ if (BUGBUG) dprintf(255, "run_subshell %.*s\n", len, str); // vfork child pid = xpopen_setup(0, 0, subshell_callback); + // free entries added to end of environment by callback (shared heap) + for (i = 0; environ[i]; i++) { + if (!ispunct(environ[i][0])) continue; + free(environ[i]); + environ[i] = 0; + } + // marshall data to child close(254); - if (TT.locals) - for (i = 0; TT.locals[i]; i++) dprintf(pipes[1], "%s\n", TT.locals[i]); + for (i = 0; i<TT.varlen; i++) { + char *s; + + if (TT.vars[i].flags&VAR_GLOBAL) continue; + dprintf(pipes[1], "%s\n", s = declarep(TT.vars+i)); + free(s); + } dprintf(pipes[1], "%.*s\n", len, str); close(pipes[1]); } @@ -680,7 +809,7 @@ static void expand_arg_nobrace(struct sh_arg *arg, char *str, unsigned flags, struct arg_list **delete) { char cc, qq = 0, *old = str, *new = str, *s, *ss, *ifs = 0, *del = 0; - int at = 0, ii = 0, dd, jj, kk, ll, oo; + int at = 0, ii = 0, dd, jj, kk, ll, oo = 0; if (BUGBUG) dprintf(255, "expand %s\n", str); if (flags&FORCE_KEEP) old = 0; @@ -691,9 +820,8 @@ if (BUGBUG) dprintf(255, "expand %s\n", str); if (!(flags&NO_TILDE) && *str == '~') { struct passwd *pw = 0; - // first expansion so don't need to free previous new ss = 0; - while (str[ii] && str[ii]!=':' && str[ii]!='/') s++; + while (str[ii] && str[ii]!=':' && str[ii]!='/') ii++; if (ii==1) { if (!(ss = getvar("HOME")) || !*ss) pw = bufgetpwuid(getuid()); } else { @@ -701,16 +829,21 @@ if (BUGBUG) dprintf(255, "expand %s\n", str); pw = getpwnam(s = xstrndup(str+1, ii-1)); free(s); } - if (pw && pw->pw_dir) ss = pw->pw_dir; - if (!ss || !*ss) ss = "/"; - s = xmprintf("%s%s", ss, str+ii); - if (old != new) free(new); - new = s; + if (pw) { + ss = pw->pw_dir; + if (!ss || !*ss) ss = "/"; + } + if (ss) { + oo = strlen(ss); + s = xmprintf("%s%s", ss, str+ii); + if (old != new) free(new); + new = s; + } } // parameter/variable expansion, and dequoting - for (oo = 0; (cc = str[ii++]); old!=new && (new[oo] = 0)) { + for (; (cc = str[ii++]); old!=new && (new[oo] = 0)) { // skip literal chars if (!strchr("$'`\\\"", cc)) { @@ -807,11 +940,10 @@ dprintf(2, "TODO: do math for %.*s\n", kk, s); // $VARIABLE } else { - s = str+--ii; - for (jj = 0; s[jj] && (s[jj]=='_' || !ispunct(s[jj])); jj++); - if (!jj) new[oo++] = '$'; + s = varend(ss = str+--ii); + if (!(jj = s-ss)) new[oo++] = '$'; // TODO: $((a=42)) can change var, affect lifetime here - else ifs = getvarbylen(str+ii, jj); + else ifs = getvar(ss); ii += jj; } } @@ -1132,9 +1264,10 @@ static struct sh_process *expand_redir(struct sh_arg *arg, int envlen, int *urd) to = *ss != '<'; if (isdigit(*s)) to = atoi(s); else if (*s == '{') { + if (*varend(s+1) != '}') break; // when we close a filehandle, we _read_ from {var}, not write to it if ((!strcmp(ss, "<&") || !strcmp(ss, ">&")) && !strcmp(sss, "-")) { - if (!(ss = getvarbylen(s+1, ss-s-2))) break; + if (!(ss = getvar(s+1))) break; to = atoi(ss); // TODO trailing garbage? if (save_redirect(&pp->urd, -1, to)) break; close(to); @@ -1144,7 +1277,7 @@ static struct sh_process *expand_redir(struct sh_arg *arg, int envlen, int *urd) } else { // we don't save this, it goes in the env var and user can close it. if (-1 == (to = next_hfd())) break; - cv = xmprintf("%.*s=%d", (int)(ss-s-1), s+1, to); + cv = xmprintf("%.*s=%d", (int)(ss-s-2), s+1, to); } } @@ -1237,7 +1370,7 @@ notfd: // Do we save displaced "to" in env variable instead of undo list? if (cv) { --*pp->urd; - setvar(cv, TAKE_MEM); + setvar(cv); cv = 0; } if ((saveclose&1) && save_redirect(&pp->urd, -1, from)) bad++; @@ -1268,9 +1401,8 @@ if (BUGBUG) dprintf(255, "run_command %s\n", arg->v[0]); // Grab leading variable assignments for (envlen = 0; envlen<arg->c; envlen++) { - s = arg->v[envlen]; - for (j=0; s[j] && (s[j]=='_' || !ispunct(s[j])); j++); - if (!j || s[j] != '=') break; + s = varend(arg->v[envlen]); + if (s == arg->v[envlen] || *s != '=') break; } // expand arguments and perform redirects @@ -1281,7 +1413,8 @@ if (BUGBUG) { int i; dprintf(255, "envlen=%d arg->c=%d run=", envlen, arg->c); f if (envlen == arg->c) { for (j = 0; j<envlen; j++) { s = expand_one_arg(arg->v[j], NO_PATH|NO_SPLIT, 0); - setvar(s, TAKE_MEM*(s!=arg->v[j])); + if (s == arg->v[j]) s = xstrdup(s); + setvar(s); } // Do nothing if nothing to do @@ -1320,16 +1453,21 @@ if (BUGBUG) { int i; dprintf(255, "envlen=%d arg->c=%d run=", envlen, arg->c); f if (toys.old_umask) umask(toys.old_umask); memcpy(&toys, &temp, j); } else { - char **env = 0, **old = environ, *ss, *sss; - int kk = 0, ll; + char **env = 0, **old = environ, *ss = 0, *sss; + int kk = 0, ll, mm = -1; // We don't allocate/free any array members, just the array - if (environ) while (environ[kk]) kk++; + if (environ) while (environ[kk]) { + if (strncmp(environ[kk], "SHLVL=", 6)) mm = kk; + kk++; + } if (kk) { env = xmalloc(sizeof(char *)*(kk+33)); memcpy(env, environ, sizeof(char *)*(kk+1)); + if (mm != -1) env[mm] = xmprintf("SHLVL=%d", atoi(env[mm]+6)+1); environ = env; } + // assign leading environment variables for (j = 0; j<envlen; j++) { sss = expand_one_arg(arg->v[j], NO_PATH|NO_SPLIT, &pp->delete); @@ -1341,15 +1479,13 @@ if (BUGBUG) { int i; dprintf(255, "envlen=%d arg->c=%d run=", envlen, arg->c); f } if (ll == kk) array_add(&environ, kk++, sss); } - ss = getvar("SHLVL"); - sprintf(toybuf, "%d", atoi(ss ? ss : "")+1); - xsetenv("SHLVL", toybuf); if (-1 == (pp->pid = xpopen_both(pp->arg.v, 0))) perror_msg("%s: vfork", *pp->arg.v); // Restore environment variables environ = old; + if (mm != -1) free(env[mm]); free(env); } @@ -2008,8 +2144,7 @@ TODO: a | b | c needs subshell for builtins? continue; } else if (!strncmp(blk->fvar, "((", 2)) { dprintf(2, "TODO skipped running for((;;)), need math parser\n"); - } else setvar(xmprintf("%s=%s", blk->fvar, blk->farg.v[blk->loop++]), - TAKE_MEM); + } else setvarval(blk->fvar, blk->farg.v[blk->loop++]); } // end of block, may have trailing redirections and/or pipe @@ -2131,71 +2266,113 @@ static void do_prompt(char *prompt) writeall(2, toybuf, len); } -// only set local variable when global not present -static void setonlylocal(char ***to, char *name, char *val) +// only set local variable when global not present, does not extend array +static struct sh_vars *initlocal(char *name, char *val) +{ + return addvar(xmprintf("%s=%s", name, val ? val : "")); +} + +// export malloced name=value string +static void export(char *str) +{ + struct sh_vars *shv; + char *s; + + // Make sure variable exists and is updated + if (strchr(str, '=')) shv = setvar(xstrdup(str)); + else if (!findvar(str)) shv = addvar(str = xmprintf("%s=", str)); + if (!shv || (shv->flags&VAR_GLOBAL)) return; + + // Resolve local magic for export + if (shv->flags&VAR_MAGIC) { + s = shv->str; + shv->str = xmprintf("%.*s=%s", (int)(varend(str)-str), str, getvar(str)); + free(s); + } + + xsetenv(shv->str, 0); + shv->flags |= VAR_GLOBAL; +} + +static void unexport(char *str) { - if (getenv(name)) return; - *(*to)++ = xmprintf("%s=%s", name, val ? val : ""); + struct sh_vars *shv = findvar(str); + + if (shv) { + if (shv->flags&VAR_GLOBAL) shv->str = xpop_env(str); + shv->flags &=~VAR_GLOBAL; + } + if (strchr(str, '=')) setvar(str); } // init locals, sanitize environment, handle nommu subshell handoff static void subshell_setup(void) { struct passwd *pw = getpwuid(getuid()); - int to, from, pid = 0, ppid = 0, zpid = 0, mypid, myppid, len; + int ii, to, from, pid, ppid, zpid, myppid = getppid(), len; // TODO: you can unset readonly and these first 4 aren't malloc() - char *s, *ss, **ll, *locals[] = {"GROUPS=", "SECONDS=", "RANDOM=", "LINENO=", - xmprintf("PPID=%d", myppid = getppid()), xmprintf("EUID=%d", geteuid()), - xmprintf("$=%d", mypid = getpid()), xmprintf("UID=%d", getuid())}; + char *s, *ss, *magic[] = {"SECONDS","RANDOM","LINENO","GROUPS"}, + *readonly[] = {xmprintf("EUID=%d", geteuid()), xmprintf("UID=%d", getuid()), + xmprintf("PPID=%d", myppid)}; struct stat st; struct utsname uu; FILE *fp; - // Initialize read only local variables - TT.locals = xmalloc(32*sizeof(char *)); - memcpy(TT.locals, locals, sizeof(locals)); - ll = TT.locals+(TT.loc_ro = ARRAY_LEN(locals)); - TT.loc_magic = 4; + // Initialize magic and read only local variables + srandom(TT.SECONDS = millitime()); + for (ii = 0; ii<ARRAY_LEN(magic); ii++) + initlocal(magic[ii], "")->flags = VAR_MAGIC|(VAR_INT*('G'!=*magic[ii])); + for (ii = 0; ii<ARRAY_LEN(readonly); ii++) + addvar(readonly[ii])->flags = VAR_READONLY|VAR_INT; // Add local variables that can be overwritten - setonlylocal(&ll, "PATH", _PATH_DEFPATH); + initlocal("PATH", _PATH_DEFPATH); if (!pw) pw = (void *)toybuf; // first use, so still zeroed - setonlylocal(&ll, "HOME", *pw->pw_dir ? pw->pw_dir : "/"); - setonlylocal(&ll, "SHELL", pw->pw_shell); - setonlylocal(&ll, "USER", pw->pw_name); - setonlylocal(&ll, "LOGNAME", pw->pw_name); + initlocal("HOME", *pw->pw_dir ? pw->pw_dir : "/"); + initlocal("SHELL", pw->pw_shell); + initlocal("USER", pw->pw_name); + initlocal("LOGNAME", pw->pw_name); gethostname(toybuf, sizeof(toybuf)-1); - *ll++ = xmprintf("HOSTNAME=%s", toybuf); + initlocal("HOSTNAME", toybuf); uname(&uu); - setonlylocal(&ll, "HOSTTYPE", uu.machine); + initlocal("HOSTTYPE", uu.machine); sprintf(toybuf, "%s-unknown-linux", uu.machine); - setonlylocal(&ll, "MACHTYPE", toybuf); - setonlylocal(&ll, "OSTYPE", uu.sysname); + initlocal("MACHTYPE", toybuf); + initlocal("OSTYPE", uu.sysname); // sprintf(toybuf, "%s-toybox", TOYBOX_VERSION); - // setonlylocal(&ll, "BASH_VERSION", toybuf); - *ll++ = xstrdup("OPTERR=1"); - *toybuf = 0; + // initlocal("BASH_VERSION", toybuf); + initlocal("OPTERR", "1"); // TODO: test if already exported? if (readlink0("/proc/self/exe", toybuf, sizeof(toybuf))) - setonlylocal(&ll, "BASH", toybuf); - *ll = 0; + initlocal("BASH", toybuf); // Ensure environ copied and toys.envc set, and clean out illegal entries - xunsetenv(""); TT.ifs = " \t\n"; - for (to = from = 0; (s = environ[from]); from++) { + for (to = from = pid = ppid = zpid = 0; (s = environ[from]); from++) { // If nommu subshell gets handoff if (!CFG_TOYBOX_FORK && !toys.stacktop) { len = 0; sscanf(s, "@%d,%d%n", &pid, &ppid, &len); - if (len && s[len]) pid = ppid = 0; + if (s[len]) pid = ppid = 0; + if (*s == '$' && s[1] == '=') zpid = atoi(s+2); } - // Filter out non-shell variable names - for (len = 0; s[len] && ((s[len] == '_') || !ispunct(s[len])); len++); - if (s[len] == '=') environ[to++] = environ[from]; + // Filter out non-shell variable names from inherited environ. + // (haven't xsetenv() yet so no need to free() or adjust toys.envc) + ss = varend(s); + if (*ss == '=') { + struct sh_vars *shv = findvar(s); + + if (!shv) addvar(environ[from])->flags = VAR_GLOBAL; + else if (shv->flags&VAR_READONLY) continue; + else { + shv->flags |= VAR_GLOBAL; + free(shv->str); + shv->str = s; + } + environ[to++] = s; + } if (!memcmp(s, "IFS=", 4)) TT.ifs = s+4; - if (!CFG_TOYBOX_FORK && *s == '$' && s[1] == '=') zpid = atoi(s+2); } environ[toys.optc = to] = 0; @@ -2206,23 +2383,25 @@ static void subshell_setup(void) s = toys.argv[0]; ss = 0; if (!strchr(s, '/')) { - if (!(ss = getcwd(0, 0))) { - if (*toybuf) s = toybuf; - } else { + if ((ss = getcwd(0, 0))) { s = xmprintf("%s/%s", ss, s); free(ss); ss = s; - } + } else if (*toybuf) s = toybuf; } - xsetenv("_", s); + s = xsetenv("_", s); + if (!findvar(s)) addvar(s)->flags = VAR_GLOBAL; free(ss); - if (!getvar("SHLVL")) xsetenv("SHLVL", "1"); + if (!getvar("SHLVL")) { + s = xsetenv("SHLVL", "1"); + if (!findvar(s)) addvar(s)->flags = VAR_GLOBAL; + } //TODO indexed array,associative array,integer,local,nameref,readonly,uppercase // if (s+1<ss && strchr("aAilnru", *s)) { // sanity check: magic env variable, pipe status - if (CFG_TOYBOX_FORK || toys.stacktop || pid!=mypid || ppid!=myppid) return; + if (CFG_TOYBOX_FORK || toys.stacktop || pid!=getpid() || ppid!=myppid) return; if (fstat(254, &st) || !S_ISFIFO(st.st_mode)) error_exit(0); TT.pid = zpid; fcntl(254, F_SETFD, FD_CLOEXEC); @@ -2248,6 +2427,7 @@ void sh_main(void) signal(SIGPIPE, SIG_IGN); TT.pid = getpid(); + TT.SECONDS = time(0); TT.arg = &arg; if (!(arg.c = toys.optc)) { arg.v = xmalloc(2*sizeof(char *)); @@ -2281,6 +2461,7 @@ if (BUGBUG) { int fd = open("/dev/tty", O_RDWR); dup2(fd, 255); close(fd); } // TODO: syntax_err should exit from shell scripts if (!(f = fopen(*toys.optargs, "r"))) { char *pp = getvar("PATH"); + struct string_list *sl = find_in_path(pp?pp:_PATH_DEFPATH, *toys.optargs); for (;sl; free(llist_pop(&sl))) if ((f = fopen(sl->str, "r"))) break; @@ -2293,7 +2474,7 @@ if (BUGBUG) { int fd = open("/dev/tty", O_RDWR); dup2(fd, 255); close(fd); } // Prompt and read line TT.lineno++; if (ii && f == stdin) { - char *s = getenv(prompt ? "PS2" : "PS1"); + char *s = getvar(prompt ? "PS2" : "PS1"); if (!s) s = prompt ? "> " : (getpid() ? "\\$ " : "# "); do_prompt(s); @@ -2346,7 +2527,7 @@ void cd_main(void) if (*dd != '/') { to = 0; from = pwd ? pwd : (to = getcwd(0, 0)); - if (!from) xsetenv("PWD", "(nowhere)"); + if (!from) setvarval("PWD", "(nowhere)"); else { from = xmprintf("%s/%s", from, dd); free(dd); @@ -2385,8 +2566,18 @@ void cd_main(void) if (bad || chdir(dd)) perror_msg("chdir '%s'", dd); else { - if (pwd) xsetenv("OLDPWD", pwd); - xsetenv("PWD", dd); + if (pwd) { + setvarval("OLDPWD", pwd); + if (TT.cdcount == 1) { + export("OLDPWD"); + TT.cdcount++; + } + } + setvarval("PWD", dd); + if (!TT.cdcount) { + export("PWD"); + TT.cdcount++; + } } free(dd); } @@ -2398,30 +2589,45 @@ void exit_main(void) void unset_main(void) { - char **arg; - unsigned vv, xx; + char **arg, *s; for (arg = toys.optargs; *arg; arg++) { + s = varend(*arg); + if (s == *arg || *s) { + error_msg("bad '%s'", *arg); + continue; + } + + // unset magic variable? if (!strcmp(*arg, "IFS")) TT.ifs = " \t\n"; - if (strchr(*arg, '=')) error_msg("bad '%s'", *arg); - else { + unsetvar(*arg); + } +} - // find and unset local - vv = findvar(TT.locals, *arg, strlen(*arg)); - if (vv<TT.loc_ro || vv<TT.loc_magic) error_msg("nope"); // TODO this - else { - for (xx = vv; TT.locals[xx]; xx++); - if (xx) { - free(TT.locals[vv]); - memmove(TT.locals+vv, TT.locals+xx+1, xx); +#define CLEANUP_cd +#define FOR_export +#include "generated/flags.h" - continue; - } - } +void export_main(void) +{ + char **arg, *eq; - // unset global - xsetenv(*arg, 0); + // list existing variables? + if (!toys.optc) { + for (arg = environ; *arg; arg++) xprintf("declare -x %s\n", *arg); + return; + } + + // set/move variables + for (arg = toys.optargs; *arg; arg++) { + eq = varend(*arg); + if (eq == *arg || (*eq && *eq != '=')) { + error_msg("bad %s", *arg); + continue; } + + if (FLAG(n)) unexport(*arg); + else export(*arg); } } @@ -2434,7 +2640,7 @@ void eval_main(void) free(s); } -#define CLEANUP_cd +#define CLEANUP_export #define FOR_exec #include "generated/flags.h" |