From e05d620a79575bc96155180e1efccd171452b82f Mon Sep 17 00:00:00 2001 From: Rob Landley Date: Thu, 2 Apr 2020 02:58:42 -0500 Subject: More shell plumbing. Redo of variable storage, add export. --- lib/env.c | 61 ++++--- lib/lib.h | 5 +- toys/pending/sh.c | 496 ++++++++++++++++++++++++++++++++++++++---------------- 3 files changed, 393 insertions(+), 169 deletions(-) diff --git a/lib/env.c b/lib/env.c index 614a504c..3017c40a 100644 --- a/lib/env.c +++ b/lib/env.c @@ -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 ? iflags; - 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 (uuflags & 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; ipw_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; envlenc; 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; jv[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; jv[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; iiflags = VAR_MAGIC|(VAR_INT*('G'!=*magic[ii])); + for (ii = 0; iiflags = 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+1str, "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