From 4fe9f1e4107986c5897979c0b62c333eaa7da7e9 Mon Sep 17 00:00:00 2001 From: Rob Landley Date: Sat, 11 Jul 2020 00:54:56 -0500 Subject: Chunk of job control plumbing and start of ${x:1:3} variable slicing support. --- toys/pending/sh.c | 539 +++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 376 insertions(+), 163 deletions(-) diff --git a/toys/pending/sh.c b/toys/pending/sh.c index 4e626c75..67b8459e 100644 --- a/toys/pending/sh.c +++ b/toys/pending/sh.c @@ -220,8 +220,7 @@ GLOBALS( // undo redirects, a=b at start, child PID, exit status, has !, job # int *urd, envlen, pid, exit, not, job; long long when; // when job backgrounded/suspended -// TODO struct sh_arg *raw; // for display - struct sh_arg arg; + struct sh_arg *raw, arg; } *pp; // currently running process struct sh_arg jobs, *arg; // job list, command line args for $* etc @@ -348,9 +347,15 @@ static struct sh_vars *addvar(char *s) } // TODO function to resolve a string into a number for $((1+2)) etc -long long do_math(char *s) +long long do_math(char **s) { - return atoll(s); + long long ll; + + while (isspace(**s)) ++*s; + ll = strtoll(*s, s, 0); + while (isspace(**s)) ++*s; + + return ll; } // Assign one variable from malloced key=val string, returns var struct @@ -376,10 +381,10 @@ static struct sh_vars *setvar(char *s) 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 (!strncmp(s, "IFS=", 4)) TT.ifs = s+4; if (!(var = findvar(s))) return addvar(s); flags = var->flags; @@ -388,20 +393,26 @@ static struct sh_vars *setvar(char *s) free(s); return 0; - } 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 + if (flags&VAR_MAGIC) { + char *ss = s+len-1; + +// TODO: trailing garbage after do_math()? + if (*s == 'S') TT.SECONDS = millitime() - 1000*do_math(&ss); + else if (*s == 'R') srandom(do_math(&ss)); + } else if (flags&VAR_GLOBAL) xsetenv(var->str = s, 0); + else { + free(var->str); + var->str = s; + } + return var; } @@ -750,7 +761,7 @@ static int pipe_subshell(char *s, int len, int out) } // utf8 strchr: return wide char matched at wc from chrs, or 0 if not matched -// if len, save length of wc +// if len, save length of next wc (whether or not it's in list) static int utf8chr(char *wc, char *chrs, int *len) { wchar_t wc1, wc2; @@ -772,6 +783,54 @@ static int utf8chr(char *wc, char *chrs, int *len) return 0; } +// grab variable or special param (ala $$) up to len bytes. Return value. +// set *used to length consumed. Does not handle $* and $@ +char *getvar_special(char *str, int len, int *used, struct arg_list **delete) +{ + char *s = 0, *ss, cc = *str; + unsigned uu; + + *used = 1; + if (cc == '-') { + s = ss = xmalloc(8); + if (TT.options&OPT_I) *ss++ = 'i'; + if (TT.options&OPT_BRACE) *ss++ = 'B'; + if (TT.options&OPT_S) *ss++ = 's'; + if (TT.options&OPT_C) *ss++ = 'c'; + *ss = 0; + } else if (cc == '?') s = xmprintf("%d", toys.exitval); + else if (cc == '$') s = xmprintf("%d", TT.pid); + else if (cc == '#') s = xmprintf("%d", TT.arg->c?TT.arg->c-1:0); + else if (cc == '!') s = xmprintf("%d"+2*!TT.bangpid, TT.bangpid); + else { + delete = 0; + for (*used = uu = 0; *usedc) s = TT.arg->v[uu]; + } else if ((*used = varend(str)-str)) return getvar(str); + } + if (s) push_arg(delete, s); + + return s; +} + +// Copy string until } including escaped } +char *slashcopy(char *s, char c) +{ + char *ss; + int ii, jj; + + for (ii = 0; s[ii] != c; ii++) if (s[ii] == '\\') ii++; + ss = xmalloc(ii+1); + for (ii = jj = 0; s[jj] != c; ii++) + if ('\\'==(s[ii] = s[jj++])) s[ii] = s[jj++]; + ss[ii] = 0; + + return ss; +} + #define NO_PATH (1<<0) // path expansion (wildcards) #define NO_SPLIT (1<<1) // word splitting #define NO_BRACE (1<<2) // {brace,expansion} @@ -789,8 +848,8 @@ static int utf8chr(char *wc, char *chrs, int *len) static int 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, **aa; - int ii = 0, dd, jj, kk, ll, oo = 0, nodel; + char cc, qq = 0, sep[6], *old = str, *new = str, *s, *ss, *ifs, *slice; + int ii = 0, oo = 0, xx, yy, dd, jj, kk, ll, mm; if (BUGBUG) dprintf(255, "expand %s\n", str); @@ -822,6 +881,7 @@ if (BUGBUG) dprintf(255, "expand %s\n", str); // parameter/variable expansion, and dequoting for (; (cc = str[ii++]); old!=new && (new[oo] = 0)) { + struct sh_arg aa = {0}; // skip literal chars if (!strchr("$'`\\\"", cc)) { @@ -834,11 +894,9 @@ if (BUGBUG) dprintf(255, "expand %s\n", str); new = xstrdup(new); new[oo = ii-1] = 0; } - ifs = 0; - aa = 0; - nodel = 0; + ifs = slice = 0; - // handle different types of escapes + // handle escapes and quoting if (cc == '\\') new[oo++] = str[ii] ? str[ii++] : cc; else if (cc == '"') qq++; else if (cc == '\'') { @@ -888,50 +946,188 @@ dprintf(2, "TODO: do math for %.*s\n", kk, s); for (kk = strlen(ifs); kk && ifs[kk-1]=='\n'; ifs[--kk] = 0); close(jj); } - } else if (cc == '$') { - // parse $ $'' ${} or $VAR + // $VARIABLE expansions - cc = str[ii++]; + } else if (cc == '$') { + cc = *(ss = str+ii++); if (cc=='\'') { for (s = str+ii; *s != '\''; oo += wcrtomb(new+oo, unescape2(&s, 0),0)); ii = s-str+1; + continue; + } else if (cc=='"' && !(qq&1)) { + qq++; + continue; } else if (cc == '{') { - cc = *(ss = str+ii); - if (!(jj = strchr(ss, '}')-ss)) ifs = (void *)1; - ii += jj+1; - - if (jj>1) { - // handle ${!x} and ${#x} - if (*ss == '!') { - if (!(ss = getvar(ss+1)) || !*ss) continue; - jj = varend(ss)-ss; - if (ss[jj]) ifs = (void *)1; - } else if (*ss == '#') { - if (jj == 2 && (*ss == '@' || *ss == '*')) jj--; - else ifs = xmprintf("%ld", (long)strlen(getvar(ss) ? : "")); + + // Skip escapes to find }, parse_word() guarantees ${} terminates + for (cc = *++ss; str[ii] != '}'; ii++) if (str[ii]=='\\') ii++; + ii++; + + if (cc == '}') ifs = (void *)1; + else if (strchr("#!", cc)) ss++; + jj = varend(ss)-ss; + if (!jj) while (isdigit(ss[jj])) jj++; + if (!jj && strchr("#$!_*", *ss)) jj++; + // parameter or operator? Maybe not a prefix: ${#-} vs ${#-x} + if (!jj && strchr("-?@", *ss)) if (ss[++jj]!='}' && ss[-1]!='{') ss--; + slice = ss+jj; // start of :operation + + if (!jj) { + ifs = (void *)1; + // literal ${#} or ${!} wasn't a prefix + if (strchr("#!", cc)) ifs = getvar_special(--ss, 1, &kk, delete); + } else if (ss[-1]=='{'); // not prefix, fall through + else if (cc == '#') { // TODO ${#x[@]} + dd = !!strchr("@*", *ss); // For ${#@} or ${#*} do normal ${#} + ifs = getvar_special(ss-dd, jj, &kk, delete) ? : ""; + if (!dd) push_arg(delete, ifs = xmprintf("%ld", strlen(ifs))); + // ${!@} ${!@Q} ${!x} ${!x@} ${!x@Q} ${!x#} ${!x[} ${!x[*]} + } else if (cc == '!') { // TODO: ${var[@]} array + + // special case: normal varname followed by @} or *} = prefix list + if (ss[jj] == '*' || (ss[jj] == '@' && !isalpha(ss[jj+1]))) { + for (slice++, kk = 0; kk=la+lb) continue; + } else if (la>=lc || lb<0) ifs = ""; + else if (la+lb>=lc) ifs += la; + else if (!*delete || ifs != (*delete)->arg) + push_arg(delete, ifs = xmprintf("%.*s", (int)lb, ifs+la)); + else { + for (dd = 0; ddc // ${x#y} remove shortest prefix ${x##y} remove longest prefix // x can be @ or * // ${x%y} ${x%%y} suffix @@ -943,130 +1139,60 @@ dprintf(2, "TODO: do math for %.*s\n", kk, s); // A=declare that recreates var a=attribute flags // x can be @* -// TODO: $_ is last arg of last command, and exported as path to exe run - if (ifs); - else if (cc == '-') { - s = ifs = xmalloc(8); - if (TT.options&OPT_I) *s++ = 'i'; - if (TT.options&OPT_BRACE) *s++ = 'B'; - if (TT.options&OPT_S) *s++ = 's'; - if (TT.options&OPT_C) *s++ = 'c'; - *s = 0; - } else if (cc == '?') ifs = xmprintf("%d", toys.exitval); - else if (cc == '$') ifs = xmprintf("%d", TT.pid); - else if (cc == '#') ifs = xmprintf("%d", TT.arg->c?TT.arg->c-1:0); - else if (cc == '!') ifs = xmprintf("%d"+2*!TT.bangpid, TT.bangpid); - else if (cc == '*' || cc == '@') aa = TT.arg->v+1; - else if (isdigit(cc)) { - for (kk = ll = 0; kkc) ifs = TT.arg->v[ll]; - nodel = 1; - - // $VARIABLE - } else { - if (ss == varend(ss)) { - ii--; - if (ss[-1] == '$') new[oo++] = '$'; - else ifs = (void *)1; - } else ifs = getvar(ss); - nodel = 1; - } - } - // TODO: $((a=42)) can change var, affect lifetime // must replace ifs AND any previous output arg[] within pointer strlen() -// TODO ${blah} here - - if (ifs == (void *)1) { - error_msg("%.*s: bad substitution", (int)(s-(str+ii)+3), str+ii-2); - free(new); - - return 1; - } - // combine before/ifs/after sections, splitting words on $IFS in ifs - if (ifs || aa) { - char sep[8]; - - // If not gluing together, nothing to substitute, not quoted: do nothing - if (!aa && !*ifs && !qq) continue; - - // Fetch separator - *sep = 0; - if ((qq&1) && cc=='*') { - wchar_t wc; - - if (flags&SEMI_IFS) strcpy(sep, " "); - else if (0<(dd = utf8towc(&wc, TT.ifs, 4))) - sprintf(sep, "%.*s", dd, TT.ifs); } - // when aa proceed through entries until NULL, else process ifs once + // Nothing left to do? + if (!ifs) break; + if (!*ifs && !qq) continue; + + // loop within current ifs checking region to split words do { - // get next argument, is this last entry, find end of word - if (aa) { - ifs = *aa ? : ""; - if (*aa) aa++; - nodel = 1; - } + // find end of (split) word if (qq&1) ss = ifs+strlen(ifs); - else for (ss = ifs; *ss; ss += kk) - if ((ll = utf8chr(ss, TT.ifs, &kk))) break; - kk = !aa || !*aa; - - // loop within current ifs checking region to split words - do { - // fast path: use existing memory when no prefix, not splitting it, - // and either not last entry or no suffix - if (!oo && !*ss && (!kk || !str[ii]) && !((qq&1) && cc=='*')) { - if (!qq && ss==ifs) break; - arg_add_del(arg, ifs, nodel ? 0 : delete); - nodel = 1; - - continue; - } + else for (ss = ifs; *ss; ss += kk) if (utf8chr(ss, TT.ifs, &kk)) break; - // resize allocation and copy next chunk of IFS-free data - new = xrealloc(new, oo + (ss-ifs) + strlen(sep) + - ((jj = kk && !*ss) ? strlen(str+ii) : 0) + 1); - oo += sprintf(new + oo, "%.*s", (int)(ss-ifs), ifs); - if (!nodel) free(ifs); - nodel = 1; - if (jj) break; + // when no prefix, not splitting, no suffix: use existing memory + if (!oo && !*ss && !((mm==aa.c) ? str[ii] : ((qq&1) && cc=='*'))) { + if (qq || ss!=ifs) arg_add(arg, ifs); + continue; + } - // for single quoted "$*" append IFS - if ((qq&1) && cc == '*') oo += sprintf(new+oo, "%s", sep); + // resize allocation and copy next chunk of IFS-free data + new = xrealloc(new, oo + (ss-ifs) + strlen(sep) + + ((jj = (mm == aa.c) && !*ss) ? strlen(str+ii) : 0) + 1); + oo += sprintf(new + oo, "%.*s", (int)(ss-ifs), ifs); + if (jj) break; - // add argument if quoted, non-blank, or non-whitespace separator - else { - if (qq || *new || *ss) { - arg_add_del(arg, new, nodel ? 0 : delete); - nodel = 1; - } - qq &= 1; - new = xstrdup(str+ii); - oo = 0; - } + // for double quoted "$*" append first separator from IFS + if ((qq&1) && cc == '*') oo += sprintf(new+oo, "%s", sep); - // Skip trailing seperator (combining whitespace) - while ((jj = utf8chr(ss, TT.ifs, &ll)) && iswspace(jj)) ss += ll; + // finished arg: keep if quoted, non-blank, or non-whitespace separator + else { + if (qq || *new || *ss) arg_add(arg, xrealloc(new, strlen(new)+1)); + qq &= 1; + new = xstrdup(str+ii); + oo = 0; + } - } while (*(ifs = ss)); - } while (!kk); - } + // Skip trailing seperator (combining whitespace) + while ((jj = utf8chr(ss, TT.ifs, &ll))) { + ss += ll; + if (!iswspace(jj)) break; + } + } while (*(ifs = ss)); + } while (!(mm == aa.c)); } -// TODO globbing * ? [ +// TODO globbing * ? [] +() happens after variable resolution -// Word splitting completely eliminating argument when no non-$IFS data left +// TODO test word splitting completely eliminating argument when no non-$IFS data left // wordexp keeps pattern when no matches -// TODO NO_SPLIT cares about IFS, see also trailing \n - -// quote removal +// TODO test NO_SPLIT cares about IFS, see also trailing \n // Record result. if (*new || qq) @@ -1268,6 +1394,7 @@ static struct sh_process *expand_redir(struct sh_arg *arg, int envlen, int *urd) pp = xzalloc(sizeof(struct sh_process)); pp->urd = urd; + pp->raw = arg; // When we redirect, we copy each displaced filehandle to restore it later. @@ -1515,8 +1642,7 @@ if (BUGBUG) { int i; dprintf(255, "envlen=%d arg->c=%d run=", envlen, arg->c); f if (envlen == arg->c) { for (jj = 0; jjv[jj], NO_PATH|NO_SPLIT, 0))) break; - if (s == arg->v[jj]) s = xstrdup(s); - setvar(s); + setvar((s == arg->v[jj]) ? xstrdup(s) : s); } goto out; } @@ -1524,7 +1650,7 @@ if (BUGBUG) { int i; dprintf(255, "envlen=%d arg->c=%d run=", envlen, arg->c); f // assign leading environment variables (if any) in temp environ copy jj = 0; env.v = 0; - if (envlen) { + if (!pp->exit && envlen) { for (env.c = 0; environ[env.c]; env.c++); memcpy(env.v = xmalloc(sizeof(char *)*(env.c+33)), environ, sizeof(char *)*(env.c+1)); @@ -2062,7 +2188,6 @@ TODO: a | b | c needs subshell for builtins? when to auto-exec? ps vs sh -c 'ps' vs sh -c '(ps)' */ - TT.hfd = 10; // iterate through pipeline segments @@ -2230,7 +2355,7 @@ dprintf(2, "TODO skipped running for((;;)), need math parser\n"); } // for && and || skip pipeline segment(s) based on return code - if (pl->type == 1 || pl->type == 3) + if (!pl->type || pl->type == 3) while (ctl && !strcmp(ctl, toys.exitval ? "&&" : "||")) ctl = (pl = pl->type ? pl->end : pl->next)?pl->arg->v[pl->arg->c]:0; @@ -2748,6 +2873,94 @@ void exec_main(void) environ = old; } +// Find + and - jobs. Returns index of plus, writes minus to *minus +int find_plus_minus(int *minus) +{ + long long when, then; + int i, plus; + + if (minus) *minus = 0; + for (then = i = plus = 0; iwhen) > then) { + then = when; + if (minus) *minus = plus; + plus = i; + } + } + + return plus; +} + +// Return T.jobs index or -1 from identifier +// Note, we don't return "ambiguous job spec", we return the first hit or -1. +// TODO %% %+ %- %?ab +int find_job(char *s) +{ + char *ss; + long ll = strtol(s, &ss, 10); + int i, j; + + if (!TT.jobs.c) return -1; + if (!*s || (!s[1] && strchr("%+-", *s))) { + int minus, plus = find_plus_minus(&minus); + + return (*s == '-') ? minus : plus; + } + + // Is this a %1 numeric jobspec? + if (s != ss && !*ss) + for (i = 0; ijob == ll) return i; + + // Match start of command or %?abc + for (i = 0; iarg.v)) return i; + if (*s != '?' || !s[1]) continue; + for (j = 0; jarg.c; j++) if (strstr(pp->arg.v[j], s+1)) return i; + } + + return -1; +} + +// We pass in dash to avoid looping over every job each time +void print_job(int i, char dash) +{ + struct sh_process *pp = (void *)TT.jobs.v[i]; + char *s = "Run"; + int j; + +// TODO Terminated (Exited) + if (pp->exit<0) s = "Stop"; + else if (pp->exit>126) s = "Kill"; + else if (pp->exit>0) s = "Done"; + printf("[%d]%c %-6s", pp->job, dash, s); + for (j = 0; jraw->c; j++) printf(" %s"+!j, pp->raw->v[j]); + printf("\n"); +} + +void jobs_main(void) +{ + int i, j, minus, plus = find_plus_minus(&minus); + char *s; + +// TODO -lnprs + + for (i = 0;;i++) { + if (toys.optc) { + if (!(s = toys.optargs[i])) break; + if ((j = find_job(s+('%' == *s))) == -1) { + perror_msg("%s: no such job", s); + + continue; + } + } else if ((j = i) >= TT.jobs.c) break; + + print_job(i, (i == plus) ? '+' : (i == minus) ? '-' : ' '); + } +} + void shift_main(void) { long long by = 1; -- cgit v1.2.3