diff options
Diffstat (limited to 'toys/pending/sh.c')
-rw-r--r-- | toys/pending/sh.c | 993 |
1 files changed, 644 insertions, 349 deletions
diff --git a/toys/pending/sh.c b/toys/pending/sh.c index 8f45c172..2bdf64a3 100644 --- a/toys/pending/sh.c +++ b/toys/pending/sh.c @@ -10,6 +10,9 @@ * and http://opengroup.org/onlinepubs/9699919799/utilities/sh.html * * deviations from posix: don't care about $LANG or $LC_ALL + * deviations from bash: + * redirect+expansion in one pass so we can't report errors between them. + * Trailing redirects error at runtime, not parse time. * builtins: alias bg command fc fg getopts jobs newgrp read umask unalias wait * disown suspend source pushd popd dirs logout times trap cd hash exit @@ -70,10 +73,62 @@ config SH usage: sh [-c command] [script] Command shell. Runs a shell script, or reads input interactively - and responds to it. + and responds to it. Roughly compatible with "bash". Run "help" for + list of built-in commands. -c command line to execute -i interactive mode (default when STDIN is a tty) + -s don't run script (args set $* parameters but read commands from stdin) + + Command shells parse each line of input (prompting when interactive), perform + variable expansion and redirection, execute commands (spawning child processes + and background jobs), and perform flow control based on the return code. + + Parsing: + syntax errors + + Interactive prompts: + line continuation + + Variable expansion: + Note: can cause syntax errors at runtime + + Redirection: + HERE documents (parsing) + Pipelines (flow control and job control) + + Running commands: + process state + builtins + cd [[ ]] (( )) + ! : [ # TODO: help for these? + true false help echo kill printf pwd test + child processes + + Job control: + & Background process + Ctrl-C kill process + Ctrl-Z suspend process + bg fg jobs kill + + Flow control: + ; End statement (same as newline) + & Background process (returns true unless syntax error) + && If this fails, next command fails without running + || If this succeeds, next command succeeds without running + | Pipelines! (Can of worms...) + for {name [in...]}|((;;)) do; BODY; done + if TEST; then BODY; fi + while TEST; do BODY; done + case a in X);; esac + [[ TEST ]] + ((MATH)) + + Job control: + & Background process + Ctrl-C kill process + Ctrl-Z suspend process + bg fg jobs kill # These are here for the help text, they're not selectable and control nothing config CD @@ -256,7 +311,7 @@ GLOBALS( long long SECONDS; char *isexec, *wcpat; unsigned options, jobcnt, LINENO; - int hfd, pid, bangpid, varslen, srclvl, recursion; + int hfd, pid, bangpid, srclvl, recursion; // Callable function array struct sh_function { @@ -282,11 +337,11 @@ GLOBALS( long flags; char *str; } *vars; - long varslen, shift; + long varslen, varscap, shift, oldlineno; struct sh_function *func; // TODO wire this up struct sh_pipeline *pl; - char *ifs; + char *ifs, *omnom; struct sh_arg arg; struct arg_list *delete; @@ -318,6 +373,10 @@ GLOBALS( // Prototype because $($($(blah))) nests, leading to run->parse->run loop int do_source(char *name, FILE *ff); +// functions contain pipelines contain functions: prototype because loop +static void free_pipeline(void *pipeline); +// recalculate needs to get/set variables, but setvar_found calls recalculate +static struct sh_vars *setvar(char *str); // ordered for greedy matching, so >&; becomes >& ; not > &; // making these const means I need to typecast the const away later to @@ -338,7 +397,10 @@ static const char *redirectors[] = {"<<<", "<<-", "<<", "<&", "<>", "<", ">>", static void syntax_err(char *s) { - error_msg("syntax error: %s", s); + struct sh_fcall *ff = TT.ff; +// TODO: script@line only for script not interactive. + for (ff = TT.ff; ff != TT.ff->prev; ff = ff->next) if (ff->omnom) break; + error_msg("syntax error '%s'@%u: %s", ff->omnom ? : "-c", TT.LINENO, s); toys.exitval = 2; if (!(TT.options&FLAG_i)) xexit(); } @@ -362,6 +424,13 @@ void debug_show_fds() closedir(X); } +static char **nospace(char **ss) +{ + while (isspace(**ss)) ++*ss; + + return ss; +} + // append to array with null terminator and realloc as necessary static void arg_add(struct sh_arg *arg, char *data) { @@ -440,11 +509,42 @@ static struct sh_vars *findvar(char *name, struct sh_fcall **pff) return 0; } +// get value of variable starting at s. +static char *getvar(char *s) +{ + struct sh_vars *var = findvar(s, 0); + + if (!var) 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, "%u", TT.ff->pl->lineno); + else if (c == 'G') sprintf(toybuf, "TODO: GROUPS"); + else if (c == 'B') sprintf(toybuf, "%d", getpid()); + else if (c == 'E') { + struct timespec ts; + + clock_gettime(CLOCK_REALTIME, &ts); + sprintf(toybuf, "%lld%c%06ld", (long long)ts.tv_sec, (s[5]=='R')*'.', + ts.tv_nsec/1000); + } + + return toybuf; + } + + return varend(var->str)+1; +} + // Append variable to ff->vars, returning *struct. Does not check duplicates. static struct sh_vars *addvar(char *s, struct sh_fcall *ff) { - if (!(ff->varslen&31)) - ff->vars = xrealloc(ff->vars, (ff->varslen+32)*sizeof(*ff->vars)); + if (ff->varslen == ff->varscap && !(ff->varslen&31)) { + ff->varscap += 32; + ff->vars = xrealloc(ff->vars, (ff->varscap)*sizeof(*ff->vars)); + } if (!s) return ff->vars; ff->vars[ff->varslen].flags = 0; ff->vars[ff->varslen].str = s; @@ -452,107 +552,222 @@ static struct sh_vars *addvar(char *s, struct sh_fcall *ff) return ff->vars+ff->varslen++; } -static char **nospace(char **ss) -{ - while (isspace(**ss)) ++*ss; - - return ss; -} - -/* -15L ( [ . -> ++ -- -14R (all prefix operators) -++ -- + - ! ~ (typecast) * & sizeof -13L * / % -12L + - -11L << >> -10L < <= > >= -9L == != -8L & (bitwise) -7L ^ (xor) -6L | -5L && -4L || -3R ? : -2R (assignments) = += -= *= /= %= &= ^= |= <<= >>= -1L , - -*/ - // Recursively calculate string into dd, returns 0 if failed, ss = error point +// Recursion resolves operators of lower priority level to a value +// Loops through operators at same priority +#define NO_ASSIGN 128 static int recalculate(long long *dd, char **ss, int lvl) { long long ee, ff; - char cc = **nospace(ss); + char *var = 0, *val, cc = **nospace(ss); + int ii, noa = lvl&NO_ASSIGN; + lvl &= NO_ASSIGN-1; - // TODO: assignable (variable) - // Always start handling unary prefixes, parenthetical blocks, and constants - if (cc=='+' || cc=='-') { + // Unary prefixes can only occur at the start of a parse context + if (cc=='!' || cc=='~') { ++*ss; - if (!recalculate(dd, ss, 1)) return 0; - if (cc=='-') *dd = -*dd; + if (!recalculate(dd, ss, noa|15)) return 0; + *dd = (cc=='!') ? !*dd : ~*dd; + } else if (cc=='+' || cc=='-') { + // Is this actually preincrement/decrement? (Requires assignable var.) + if (*++*ss==cc) { + val = (*ss)++; + nospace(ss); + if (*ss==(var = varend(*ss))) { + *ss = val; + var = 0; + } + } + if (!var) { + if (!recalculate(dd, ss, noa|15)) return 0; + if (cc=='-') *dd = -*dd; + } } else if (cc=='(') { ++*ss; - if (!recalculate(dd, ss, 1)) return 0; + if (!recalculate(dd, ss, noa|1)) return 0; if (**ss!=')') return 0; else ++*ss; - } else if (isdigit(cc)) *dd = strtoll(*ss, ss, 0); //TODO overflow? - else if (!lvl && (!cc || cc==')')) { + } else if (isdigit(cc)) { + *dd = strtoll(*ss, ss, 0); + if (**ss=='#') { + if (!*++*ss || isspace(**ss) || ispunct(**ss)) return 0; + *dd = strtoll(val = *ss, ss, *dd); + if (val == *ss) return 0; + } + } else if ((var = varend(*ss))==*ss) { + // At lvl 0 "" is ok, anything higher needs a non-empty equation + if (lvl || (cc && cc!=')')) return 0; *dd = 0; + return 1; - } else return 0; + } + + // If we got a variable, evaluate its contents to set *dd + if (var) { + // Recursively evaluate, catching x=y; y=x; echo $((x)) + if (TT.recursion++ == 50+200*CFG_TOYBOX_FORK) { + perror_msg("recursive occlusion"); + --TT.recursion; + + return 0; + } + val = getvar(var = *ss) ? : ""; + ii = recalculate(dd, &val, noa); + TT.recursion--; + if (!ii) return 0; + if (*val) { + perror_msg("bad math: %s @ %d", var, (int)(val-var)); - // x^y binds first - if (lvl<4) while (strstart(nospace(ss), "**")) { - if (!recalculate(&ee, ss, 4)) return 0; + return 0; + } + val = *ss = varend(var); + + // Operators that assign to a varible must be adjacent to one: + + // Handle preincrement/predecrement (only gets here if var set before else) + if (cc=='+' || cc=='-') { + if (cc=='+') ee = ++*dd; + else ee = --*dd; + } else cc = 0; + + // handle postinc/postdec + if ((**nospace(ss)=='+' || **ss=='-') && (*ss)[1]==**ss) { + ee = ((cc = **ss)=='+') ? 1+*dd : -1+*dd; + *ss += 2; + + // Assignment operators: = *= /= %= += -= <<= >>= &= ^= |= + } else if (lvl<=2 && (*ss)[ii = (-1 != stridx("*/%+-", **ss)) + +2*!smemcmp(*ss, "<<", 2)+2*!smemcmp(*ss, ">>", 2)]=='=') + { + // TODO: assignments are lower priority BUT must go after variable, + // come up with precedence checking tests? + cc = **ss; + *ss += ii+1; + if (!recalculate(&ee, ss, noa|1)) return 0; // TODO lvl instead of 1? + if (cc=='*') *dd *= ee; + else if (cc=='+') *dd += ee; + else if (cc=='-') *dd -= ee; + else if (cc=='<') *dd <<= ee; + else if (cc=='>') *dd >>= ee; + else if (cc=='&') *dd &= ee; + else if (cc=='^') *dd ^= ee; + else if (cc=='|') *dd |= ee; + else if (!cc) *dd = ee; + else if (!ee) { + perror_msg("%c0", cc); + + return 0; + } else if (cc=='/') *dd /= ee; + else if (cc=='%') *dd %= ee; + ee = *dd; + } + if (cc && !noa) setvar(xmprintf("%.*s=%lld", (int)(val-var), var, ee)); + } + + // x**y binds first + if (lvl<=14) while (strstart(nospace(ss), "**")) { + if (!recalculate(&ee, ss, noa|15)) return 0; if (ee<0) perror_msg("** < 0"); for (ff = *dd, *dd = 1; ee; ee--) *dd *= ff; } // w*x/y%z bind next - if (lvl<3) while ((cc = **nospace(ss))) { - if (cc=='*' || cc=='/' || cc=='%') { - ++*ss; - if (!recalculate(&ee, ss, 3)) return 0; - if (cc=='*') *dd *= ee; - else if (cc=='%') *dd %= ee; - else if (!ee) { - perror_msg("/0"); - return 0; - } else *dd /= ee; - } else break; + if (lvl<=13) while ((cc = **nospace(ss)) && strchr("*/%", cc)) { + ++*ss; + if (!recalculate(&ee, ss, noa|14)) return 0; + if (cc=='*') *dd *= ee; + else if (!ee) { + perror_msg("%c0", cc); + + return 0; + } else if (cc=='%') *dd %= ee; + else *dd /= ee; } // x+y-z - if (lvl<2) while ((cc = **nospace(ss))) { - if (cc=='+' || cc=='-') { - ++*ss; - if (!recalculate(&ee, ss, 2)) return 0; - if (cc=='+') *dd += ee; - else *dd -= ee; - } else break; + if (lvl<=12) while ((cc = **nospace(ss)) && strchr("+-", cc)) { + ++*ss; + if (!recalculate(&ee, ss, noa|13)) return 0; + if (cc=='+') *dd += ee; + else *dd -= ee; } - nospace(ss); - return 1; -} + // x<<y >> -static int calculate(long long *ll, char *equation) -{ - char *ss = equation; + if (lvl<=11) while ((cc = **nospace(ss)) && strchr("<>", cc) && cc==(*ss)[1]){ + *ss += 2; + if (!recalculate(&ee, ss, noa|12)) return 0; + if (cc == '<') *dd <<= ee; + else *dd >>= ee; + } - // TODO: error_msg->sherror_msg() with LINENO for scripts - if (!recalculate(ll, &ss, 0) || *ss) { - perror_msg("bad math: %s @ %d", equation, (int)(ss-equation)); + // x<y <= > >= + if (lvl<=10) while ((cc = **nospace(ss)) && strchr("<>", cc)) { + if ((ii = *++*ss=='=')) ++*ss; + if (!recalculate(&ee, ss, noa|11)) return 0; + if (cc=='<') *dd = ii ? (*dd<=ee) : (*dd<ee); + else *dd = ii ? (*dd>=ee) : (*dd>ee); + } - return 0; + if (lvl<=9) while ((cc = **nospace(ss)) && strchr("=!", cc) && (*ss)[1]=='='){ + *ss += 2; + if (!recalculate(&ee, ss, noa|10)) return 0; + *dd = (cc=='!') ? *dd != ee : *dd == ee; + } + + if (lvl<=8) while (**nospace(ss)=='&' && (*ss)[1]!='&') { + ++*ss; + if (!recalculate(&ee, ss, noa|9)) return 0; + *dd &= ee; + } + + if (lvl<=7) while (**nospace(ss)=='^') { + ++*ss; + if (!recalculate(&ee, ss, noa|8)) return 0; + *dd ^= ee; + } + + if (lvl<=6) while (**nospace(ss)=='|' && (*ss)[1]!='|') { + ++*ss; + if (!recalculate(&ee, ss, noa|7)) return 0; + *dd |= ee; + } + + if (lvl<=5) while (strstart(nospace(ss), "&&")) { + if (!recalculate(&ee, ss, noa|6|NO_ASSIGN*!*dd)) return 0; + *dd = *dd && ee; + } + + if (lvl<=4) while (strstart(nospace(ss), "||")) { + if (!recalculate(&ee, ss, noa|5|NO_ASSIGN*!!*dd)) return 0; + *dd = *dd || ee; + } + + // ? : slightly weird: recurses with lower priority instead of looping + // because a ? b ? c : d ? e : f : g == a ? (b ? c : (d ? e : f) : g) + if (lvl<=3) if (**nospace(ss)=='?') { + ++*ss; + if (**nospace(ss)==':' && *dd) ee = *dd; + else if (!recalculate(&ee, ss, noa|1|NO_ASSIGN*!*dd) || **nospace(ss)!=':') + return 0; + ++*ss; + if (!recalculate(&ff, ss, noa|1|NO_ASSIGN*!!*dd)) return 0; + *dd = *dd ? ee : ff; + } + + // lvl<=2 assignment would go here, but handled above because variable + + // , (slightly weird, replaces dd instead of modifying it via ee/ff) + if (lvl<=1) while (**nospace(ss)==',') { + ++*ss; + if (!recalculate(dd, ss, noa|2)) return 0; } return 1; } // Return length of utf8 char @s fitting in len, writing value into *cc -int getutf8(char *s, int len, int *cc) +static int getutf8(char *s, int len, int *cc) { unsigned wc; @@ -563,33 +778,45 @@ int getutf8(char *s, int len, int *cc) return len; } -// get value of variable starting at s. -static char *getvar(char *s) +// utf8 strchr: return wide char matched at wc from chrs, or 0 if not matched +// if len, save length of next wc (whether or not it's in list) +static int utf8chr(char *wc, char *chrs, int *len) { - struct sh_vars *var = findvar(s, 0); + unsigned wc1, wc2; + int ll; - if (!var) return 0; + if (len) *len = 1; + if (!*wc) return 0; + if (0<(ll = utf8towc(&wc1, wc, 99))) { + if (len) *len = ll; + while (*chrs) { + if(1>(ll = utf8towc(&wc2, chrs, 99))) chrs++; + else { + if (wc1 == wc2) return wc1; + chrs += ll; + } + } + } - if (var->flags & VAR_MAGIC) { - char c = *var->str; + return 0; +} - 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, "%u", TT.ff->pl->lineno); - else if (c == 'G') sprintf(toybuf, "TODO: GROUPS"); - else if (c == 'B') sprintf(toybuf, "%d", getpid()); - else if (c == 'E') { - struct timespec ts; +// return length of match found at this point (try is null terminated array) +static int anystart(char *s, char **try) +{ + char *ss = s; - clock_gettime(CLOCK_REALTIME, &ts); - sprintf(toybuf, "%lld%c%06ld", (long long)ts.tv_sec, (s[5]=='R')*'.', - ts.tv_nsec/1000); - } + while (*try) if (strstart(&s, *try++)) return s-ss; - return toybuf; - } + return 0; +} - return varend(var->str)+1; +// does this entire string match one of the strings in try[] +static int anystr(char *s, char **try) +{ + while (*try) if (!strcmp(s, *try++)) return 1; + + return 0; } // Update $IFS cache in function call stack after variable assignment @@ -655,7 +882,13 @@ static struct sh_vars *setvar_found(char *s, int freeable, struct sh_vars *var) // integer variables treat += differently ss = s+vlen+(s[vlen]=='+')+1; if (flags&VAR_INT) { - if (!calculate(&ll, ss)) goto bad; + sd = ss; + if (!recalculate(&ll, &sd, 0) || *sd) { + perror_msg("bad math: %s @ %d", ss, (int)(sd-ss)); + + goto bad; + } + sprintf(buf, "%lld", ll); if (flags&VAR_MAGIC) { if (*s == 'S') { @@ -704,16 +937,18 @@ static struct sh_vars *setvar_long(char *s, int freeable, struct sh_fcall *ff) if (ss[*ss=='+']!='=') { error_msg("bad setvar %s\n", s); if (freeable) free(s); + return 0; } // Add if necessary, set value, and remove again if we added but set failed if (!(was = vv = findvar(s, &ff))) (vv = addvar(s, ff))->flags = VAR_NOFREE; - if (!(vv = setvar_found(s, freeable, vv))) { - int ii = vv-ff->vars; + if (!setvar_found(s, freeable, vv)) { + if (!was) memmove(vv, vv+1, sizeof(ff->vars)*(--ff->varslen-(vv-ff->vars))); - if (!was) memmove(ff->vars+ii, ff->vars+ii+1, (ff->varslen--)-ii); - } else cache_ifs(vv->str, ff); + return 0; + } + cache_ifs(vv->str, ff); return vv; } @@ -731,7 +966,7 @@ static int unsetvar(char *name) { struct sh_fcall *ff; struct sh_vars *var = findvar(name, &ff); - int ii = var-ff->vars, len = varend(name)-name; + int len = varend(name)-name; if (!var || (var->flags&VAR_WHITEOUT)) return 0; if (var->flags&VAR_READONLY) error_msg("readonly %.*s", len, name); @@ -744,7 +979,7 @@ static int unsetvar(char *name) // free from global context } else { if (!(var->flags&VAR_NOFREE)) free(var->str); - memmove(ff->vars+ii, ff->vars+ii+1, (ff->varslen--)-ii); + memmove(var, var+1, sizeof(ff->vars)*(ff->varslen-(var-ff->vars))); } if (!strcmp(name, "IFS")) do ff->ifs = " \t\n"; while ((ff = ff->next) != TT.ff->prev); @@ -817,26 +1052,8 @@ static char *declarep(struct sh_vars *var) return ss; } -// return length of match found at this point (try is null terminated array) -static int anystart(char *s, char **try) -{ - char *ss = s; - - while (*try) if (strstart(&s, *try++)) return s-ss; - - return 0; -} - -// does this entire string match one of the strings in try[] -static int anystr(char *s, char **try) -{ - while (*try) if (!strcmp(s, *try++)) return 1; - - return 0; -} - -// return length of valid prefix that could go before redirect -static int redir_prefix(char *word) +// Skip past valid prefix that could go before redirect +static char *skip_redir_prefix(char *word) { char *s = word; @@ -845,7 +1062,7 @@ static int redir_prefix(char *word) else s = word; } else while (isdigit(*s)) s++; - return s-word; + return s; } // parse next word from command line. Returns end, or 0 if need continuation @@ -857,7 +1074,7 @@ static char *parse_word(char *start, int early, int quote) char *end = start, *ss; // Handle redirections, <(), (( )) that only count at the start of word - ss = end + redir_prefix(end); // 123<<file- parses as 2 args: "123<<" "file-" + ss = skip_redir_prefix(end); // 123<<file- parses as 2 args: "123<<" "file-" if (strstart(&ss, "<(") || strstart(&ss, ">(")) { toybuf[quote++]=')'; end = ss; @@ -975,6 +1192,21 @@ static int save_redirect(int **rd, int from, int to) return 0; } +// restore displaced filehandles, closing high filehandles they were copied to +static void unredirect(int *urd) +{ + int *rr = urd+1, i; + + if (!urd) return; + + for (i = 0; i<*urd; i++, rr += 2) if (rr[0] != -1) { + // No idea what to do about fd exhaustion here, so Steinbach's Guideline. + dup2(rr[0], rr[1]); + close(rr[0]); + } + free(urd); +} + // TODO: waitpid(WNOHANG) to clean up zombies and catch background& ending static void subshell_callback(char **argv) { @@ -1014,21 +1246,6 @@ static char *pl2str(struct sh_pipeline *pl, int one) // TODO handle functions } -// restore displaced filehandles, closing high filehandles they were copied to -static void unredirect(int *urd) -{ - int *rr = urd+1, i; - - if (!urd) return; - - for (i = 0; i<*urd; i++, rr += 2) if (rr[0] != -1) { - // No idea what to do about fd exhaustion here, so Steinbach's Guideline. - dup2(rr[0], rr[1]); - close(rr[0]); - } - free(urd); -} - static struct sh_blockstack *clear_block(struct sh_blockstack *blk) { memset(blk, 0, sizeof(*blk)); @@ -1082,9 +1299,6 @@ static void call_function(void) TT.ff->ifs = TT.ff->next->ifs; } -// functions contain pipelines contain functions: prototype because loop -static void free_pipeline(void *pipeline); - static void free_function(struct sh_function *funky) { if (--funky->refcount) return; @@ -1206,29 +1420,6 @@ static int pipe_subshell(char *s, int len, int out) return pipes[out]; } -// utf8 strchr: return wide char matched at wc from chrs, or 0 if not matched -// if len, save length of next wc (whether or not it's in list) -static int utf8chr(char *wc, char *chrs, int *len) -{ - unsigned wc1, wc2; - int ll; - - if (len) *len = 1; - if (!*wc) return 0; - if (0<(ll = utf8towc(&wc1, wc, 99))) { - if (len) *len = ll; - while (*chrs) { - if(1>(ll = utf8towc(&wc2, chrs, 99))) chrs++; - else { - if (wc1 == wc2) return wc1; - chrs += ll; - } - } - } - - 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) @@ -1384,7 +1575,7 @@ char *wildcard_path(char *pattern, int off, struct sh_arg *deck, int *idx, j = 0; } - // Got wildcard? Return if start of name if out of count, else skip [] () + // Got wildcard? Return start of name if out of count, else skip [] () if (*idx<deck->c && p-pattern == (long)deck->v[*idx]) { if (!j++ && !count--) return old; ++*idx; @@ -1589,9 +1780,10 @@ char *slashcopy(char *s, char *c, struct sh_arg *deck) #define SEMI_IFS (1<<6) // Use ' ' instead of IFS to combine $* // expand str appending to arg using above flag defines, add mallocs to delete // if ant not null, save wildcard deck there instead of expanding vs filesystem -// returns 0 for success, 1 for error +// returns 0 for success, 1 for error. +// If measure stop at *measure and return input bytes consumed in *measure static int expand_arg_nobrace(struct sh_arg *arg, char *str, unsigned flags, - struct arg_list **delete, struct sh_arg *ant) + struct arg_list **delete, struct sh_arg *ant, long *measure) { char cc, qq = flags&NO_QUOTE, sep[6], *new = str, *s, *ss = ss, *ifs, *slice; int ii = 0, oo = 0, xx, yy, dd, jj, kk, ll, mm; @@ -1628,6 +1820,8 @@ static int expand_arg_nobrace(struct sh_arg *arg, char *str, unsigned flags, struct sh_arg aa = {0}; int nosplit = 0; + if (measure && cc==*measure) break; + // skip literal chars if (!strchr("'\"\\$`"+2*(flags&NO_QUOTE), cc)) { if (str != new) new[oo] = cc; @@ -1644,10 +1838,7 @@ static int expand_arg_nobrace(struct sh_arg *arg, char *str, unsigned flags, ifs = slice = 0; // handle escapes and quoting - if (cc == '\\') { - if (!(qq&1) || (str[ii] && strchr("\"\\$`", str[ii]))) - new[oo++] = str[ii] ? str[ii++] : cc; - } else if (cc == '"') qq++; + if (cc == '"') qq++; else if (cc == '\'') { if (qq&1) new[oo++] = cc; else { @@ -1657,25 +1848,25 @@ static int expand_arg_nobrace(struct sh_arg *arg, char *str, unsigned flags, // both types of subshell work the same, so do $( here not in '$' below // TODO $((echo hello) | cat) ala $(( becomes $( ( retroactively - } else if (cc == '`' || (cc == '$' && str[ii] && strchr("([", str[ii]))) { + } else if (cc == '`' || (cc == '$' && (str[ii]=='(' || str[ii]=='['))) { off_t pp = 0; s = str+ii-1; kk = parse_word(s, 1, 0)-s; - if (str[ii] == '[' || *toybuf == 255) { + if (str[ii] == '[' || *toybuf == 255) { // (( parsed together, not (( ) ) struct sh_arg aa = {0}; long long ll; // Expand $VARS in math string ss = str+ii+1+(str[ii]=='('); push_arg(delete, ss = xstrndup(ss, kk - (3+2*(str[ii]!='[')))); - expand_arg_nobrace(&aa, ss, NO_PATH|NO_SPLIT, delete, 0); + expand_arg_nobrace(&aa, ss, NO_PATH|NO_SPLIT, delete, 0, 0); s = ss = (aa.v && *aa.v) ? *aa.v : ""; free(aa.v); // Recursively calculate result if (!recalculate(&ll, &s, 0) || *s) { - error_msg("bad math: %s @ %ld", ss, (s-ss)+1); + error_msg("bad math: %s @ %ld", ss, (long)(s-ss)+1); goto fail; } ii += kk-1; @@ -1705,15 +1896,18 @@ static int expand_arg_nobrace(struct sh_arg *arg, char *str, unsigned flags, // TODO what does \ in `` mean? What is echo `printf %s \$x` supposed to do? // This has to be async so pipe buffer doesn't fill up - if (!ss) jj = pipe_subshell(s, kk, 0); + if (!ss) jj = pipe_subshell(s, kk, 0); // TODO $(true &&) syntax_err() if ((ifs = readfd(jj, 0, &pp))) for (kk = strlen(ifs); kk && ifs[kk-1]=='\n'; ifs[--kk] = 0); close(jj); } + } else if (cc=='\\' || !str[ii]) { + if (!(qq&1) || (str[ii] && strchr("\"\\$`", str[ii]))) + new[oo++] = str[ii] ? str[ii++] : cc; // $VARIABLE expansions - } else if (cc == '$') { + } else if (cc == '$' && str[ii]) { cc = *(ss = str+ii++); if (cc=='\'') { for (s = str+ii; *s != '\''; oo += wcrtomb(new+oo, unescape2(&s, 0),0)); @@ -1766,7 +1960,7 @@ static int expand_arg_nobrace(struct sh_arg *arg, char *str, unsigned flags, } else { // First expansion if (strchr("@*", *ss)) { // special case ${!*}/${!@} - expand_arg_nobrace(&aa, "\"$*\"", NO_PATH|NO_SPLIT, delete, 0); + expand_arg_nobrace(&aa, "\"$*\"", NO_PATH|NO_SPLIT, delete, 0, 0); ifs = *aa.v; free(aa.v); memset(&aa, 0, sizeof(aa)); @@ -1863,7 +2057,7 @@ barf: } if (!lc || *ss != '}') { for (s = ss; *s != '}' && *s != ':'; s++); - error_msg("bad %.*s @ %ld", (int)(s-slice), slice, ss-slice); + error_msg("bad %.*s @ %ld", (int)(s-slice), slice,(long)(ss-slice)); //TODO fix error message goto fail; } @@ -1932,7 +2126,7 @@ barf: } if (yy != -1) { - if (*delete && (*delete)->arg==ifs) ifs[yy] = 0; + if (delete && *delete && (*delete)->arg==ifs) ifs[yy] = 0; else push_arg(delete, ifs = xstrndup(ifs, yy)); } } @@ -1959,7 +2153,7 @@ barf: ll++; continue; } - if (*delete && (*delete)->arg==ifs) { + if (delete && *delete && (*delete)->arg==ifs) { if (jj==dd) memcpy(ifs+ll, ss, jj); else if (jj<dd) sprintf(ifs+ll, "%s%s", ss, ifs+ll+dd); else bird = ifs; @@ -2067,6 +2261,7 @@ fail: if (str != new) free(new); free(deck.v); if (ant!=&deck && ant->v) collect_wildcards("", 0, ant); + if (measure) *measure = --ii; return !!arg; } @@ -2093,50 +2288,60 @@ static int expand_arg(struct sh_arg *arg, char *old, unsigned flags, if ((TT.options&OPT_B) && !(flags&NO_BRACE)) for (i = 0; ; i++) { // skip quoted/escaped text while ((s = parse_word(old+i, 1, 0)) != old+i) i += s-(old+i); - // stop at end of string if we haven't got any more open braces - if (!bb && !old[i]) break; - // end a brace? - if (bb && (!old[i] || old[i] == '}')) { + + // start a new span + if (old[i] == '{') { + dlist_add_nomalloc((void *)&blist, + (void *)(bb = xzalloc(sizeof(struct sh_brace)+34*4))); + bb->commas[0] = i; + // end of string: abort unfinished spans and end loop + } else if (!old[i]) { + for (bb = blist; bb;) { + if (!bb->active) { + if (bb==blist) { + dlist_pop(&blist); + bb = blist; + } else dlist_pop(&bb); + } else bb = (bb->next==blist) ? 0 : bb->next; + } + break; + // no active span? + } else if (!bb) continue; + // end current span + else if (old[i] == '}') { bb->active = bb->commas[bb->cnt+1] = i; - // pop brace from bb into bnext - for (bnext = bb; bb && bb->active; bb = (bb==blist) ? 0 : bb->prev); // Is this a .. span? - j = 1+*bnext->commas; - if (old[i] && !bnext->cnt && i-j>=4) { + j = 1+*bb->commas; + if (!bb->cnt && i-j>=4) { // a..z span? Single digit numbers handled here too. TODO: utf8 if (old[j+1]=='.' && old[j+2]=='.') { - bnext->commas[2] = old[j]; - bnext->commas[3] = old[j+3]; + bb->commas[2] = old[j]; + bb->commas[3] = old[j+3]; k = 0; if (old[j+4]=='}' || - (sscanf(old+j+4, "..%u}%n", bnext->commas+4, &k) && k)) - bnext->cnt = -1; + (sscanf(old+j+4, "..%u}%n", bb->commas+4, &k) && k)) + bb->cnt = -1; } // 3..11 numeric span? - if (!bnext->cnt) { - for (k=0, j = 1+*bnext->commas; k<3; k++, j += x) - if (!sscanf(old+j, "..%u%n"+2*!k, bnext->commas+2+k, &x)) break; - if (old[j] == '}') bnext->cnt = -2; + if (!bb->cnt) { + for (k=0, j = 1+*bb->commas; k<3; k++, j += x) + if (!sscanf(old+j, "..%u%n"+2*!k, bb->commas+2+k, &x)) break; + if (old[j]=='}') bb->cnt = -2; } // Increment goes in the right direction by at least 1 - if (bnext->cnt) { - if (!bnext->commas[4]) bnext->commas[4] = 1; - if ((bnext->commas[3]-bnext->commas[2]>0) != (bnext->commas[4]>0)) - bnext->commas[4] *= -1; + if (bb->cnt) { + if (!bb->commas[4]) bb->commas[4] = 1; + if ((bb->commas[3]-bb->commas[2]>0) != (bb->commas[4]>0)) + bb->commas[4] *= -1; } } - // discard unterminated span, or commaless span that wasn't x..y - if (!old[i] || !bnext->cnt) - free(dlist_pop((blist == bnext) ? &blist : &bnext)); - // starting brace - } else if (old[i] == '{') { - dlist_add_nomalloc((void *)&blist, - (void *)(bb = xzalloc(sizeof(struct sh_brace)+34*4))); - bb->commas[0] = i; - // no active span? - } else if (!bb) continue; + // discard commaless span that wasn't x..y + if (!bb->cnt) free(dlist_pop((blist==bb) ? &blist : &bb)); + // Set bb to last unfinished brace (if any) + for (bb = blist ? blist->prev : 0; bb && bb->active; + bb = (bb==blist) ? 0 : bb->prev); // add a comma to current span - else if (bb && old[i] == ',') { + } else if (old[i] == ',') { if (bb->cnt && !(bb->cnt&31)) { dlist_lpop(&blist); dlist_add_nomalloc((void *)&blist, @@ -2148,7 +2353,7 @@ static int expand_arg(struct sh_arg *arg, char *old, unsigned flags, // TODO NO_SPLIT with braces? (Collate with spaces?) // If none, pass on verbatim - if (!blist) return expand_arg_nobrace(arg, old, flags, delete, 0); + if (!blist) return expand_arg_nobrace(arg, old, flags, delete, 0, 0); // enclose entire range in top level brace. (bstk = xzalloc(sizeof(struct sh_brace)+8))->commas[1] = strlen(old)+1; @@ -2227,7 +2432,7 @@ static int expand_arg(struct sh_arg *arg, char *old, unsigned flags, } // Save result, aborting on expand error - if (expand_arg_nobrace(arg, push_arg(delete, ss), flags, delete, 0)) { + if (expand_arg_nobrace(arg, push_arg(delete, ss), flags, delete, 0, 0)) { llist_traverse(blist, free); return 1; @@ -2258,6 +2463,7 @@ static char *expand_one_arg(char *new, unsigned flags, struct arg_list **del) struct sh_arg arg = {0}; char *s = 0; + // TODO: ${var:?error} here? if (!expand_arg(&arg, new, flags|NO_PATH|NO_SPLIT, del)) if (!(s = *arg.v) && (flags&(SEMI_IFS|NO_NULL))) s = ""; free(arg.v); @@ -2280,7 +2486,7 @@ static struct sh_process *expand_redir(struct sh_arg *arg, int skip, int *urd) pp->urd = urd; pp->raw = arg; - // When we redirect, we copy each displaced filehandle to restore it later. + // When redirecting, copy each displaced filehandle to restore it later. // Expand arguments and perform redirections for (j = skip; j<arg->c; j++) { @@ -2312,7 +2518,7 @@ static struct sh_process *expand_redir(struct sh_arg *arg, int skip, int *urd) } // Is this a redirect? s = prefix, ss = operator - ss = s + redir_prefix(arg->v[j]); + ss = skip_redir_prefix(s); sss = ss + anystart(ss, (void *)redirectors); if (ss == sss) { // Nope: save/expand argument and loop @@ -2332,8 +2538,8 @@ static struct sh_process *expand_redir(struct sh_arg *arg, int skip, int *urd) // It's a redirect: for [to]<from s = start of [to], ss = <, sss = from if (isdigit(*s) && ss-s>5) break; - // expand arguments for everything but << and <<- - if (strncmp(ss, "<<", 2) && ss[2] != '<') { + // expand arguments for everything but HERE docs + if (strncmp(ss, "<<", 2)) { struct sh_arg tmp = {0}; if (!expand_arg(&tmp, sss, 0, &pp->delete) && tmp.c == 1) sss = *tmp.v; @@ -2367,17 +2573,14 @@ static struct sh_process *expand_redir(struct sh_arg *arg, int skip, int *urd) } // HERE documents? - if (!strcmp(ss, "<<<") || !strcmp(ss, "<<-") || !strcmp(ss, "<<")) { - char *tmp = getvar("TMPDIR"); + if (!strncmp(ss, "<<", 2)) { + char *tmp = xmprintf("%s/sh-XXXXXX", getvar("TMPDIR") ? : "/tmp"); int i, len, zap = (ss[2] == '-'), x = !ss[strcspn(ss, "\"'")]; - // store contents in open-but-deleted /tmp file. - tmp = xmprintf("%s/sh-XXXXXX", tmp ? tmp : "/tmp"); + // store contents in open-but-deleted /tmp file: write then lseek(start) if ((from = mkstemp(tmp))>=0) { if (unlink(tmp)) bad++; - - // write contents to file (if <<< else <<) then lseek back to start - else if (ss[2] == '<') { + else if (ss[2] == '<') { // not stored in arg[here] if (!(ss = expand_one_arg(sss, 0, 0))) { s = 0; break; @@ -2386,21 +2589,21 @@ static struct sh_process *expand_redir(struct sh_arg *arg, int skip, int *urd) if (len != writeall(from, ss, len)) bad++; if (ss != sss) free(ss); } else { - struct sh_arg *hh = arg+here++; + struct sh_arg *hh = arg+ ++here; for (i = 0; i<hh->c; i++) { ss = hh->v[i]; sss = 0; // TODO audit this ala man page // expand_parameter, commands, and arithmetic - if (x && !(ss = sss = expand_one_arg(ss, ~SEMI_IFS, 0))) { + if (x && !(sss = expand_one_arg(ss, ~SEMI_IFS, 0))) { s = 0; break; } while (zap && *ss == '\t') ss++; x = writeall(from, ss, len = strlen(ss)); - free(sss); + if (ss != sss) free(sss); if (len != x) break; } if (i != hh->c) bad++; @@ -2541,19 +2744,43 @@ static struct sh_process *run_command(void) { char *s, *ss, *sss; struct sh_arg *arg = TT.ff->pl->arg; - int envlen, funk = TT.funcslen, jj = 0, prefix = 0; + int envlen, skiplen, funk = TT.funcslen, ii, jj = 0, prefix = 0; struct sh_process *pp; // Count leading variable assignments - for (envlen = 0; envlen<arg->c; envlen++) + for (envlen = skiplen = 0; envlen<arg->c; envlen++) if ((ss = varend(arg->v[envlen]))==arg->v[envlen] || ss[*ss=='+']!='=') break; - pp = expand_redir(arg, envlen, 0); + + // Skip [[ ]] and (( )) contents for now + if ((s = arg->v[envlen])) { + if (!smemcmp(s, "((", 2)) skiplen = 1; + else if (!strcmp(s, "[[")) while (strcmp(arg->v[envlen+skiplen++], "]]")); + } + pp = expand_redir(arg, envlen+skiplen, 0); + +// TODO: if error stops redir, expansion assignments, prefix assignments, +// what sequence do they occur in? + if (skiplen) { + // Trailing redirects can't expand to any contents + if (pp->arg.c) { + syntax_err(*pp->arg.v); + pp->exit = 1; + } + if (!pp->exit) { + for (ii = 0; ii<skiplen; ii++) +// TODO: [[ ~ ] expands but ((~)) doesn't, what else? + if (expand_arg(&pp->arg, arg->v[envlen+ii], NO_PATH|NO_SPLIT, &pp->delete)) + break; + if (ii != skiplen) pp->exit = toys.exitval = 1; + } + if (pp->exit) return pp; + } // Are we calling a shell function? TODO binary search - if (pp->arg.c && !strchr(*pp->arg.v, '/')) - for (funk = 0; funk<TT.funcslen; funk++) - if (!strcmp(*pp->arg.v, TT.functions[funk]->name)) break; + if (pp->arg.c) + if (!strchr(s, '/')) for (funk = 0; funk<TT.funcslen; funk++) + if (!strcmp(s, TT.functions[funk]->name)) break; // Create new function context to hold local vars? if (funk != TT.funcslen || (envlen && pp->arg.c) || TT.ff->blk->pipe) { @@ -2590,8 +2817,21 @@ static struct sh_process *run_command(void) // TODO what about "echo | x=1 | export fruit", must subshell? Test this. // Several NOFORK can just NOP in a pipeline? Except ${a?b} still errors + // ((math)) + else if (!smemcmp(s = *pp->arg.v, "((", 2)) { + char *ss = s+2; + long long ll; + + funk = TT.funcslen; + ii = strlen(s)-2; + if (!recalculate(&ll, &ss, 0) || ss!=s+ii) + perror_msg("bad math: %.*s @ %ld", ii-2, s+2, (long)(ss-s)-2); + else toys.exitval = !ll; + pp->exit = toys.exitval; + s = 0; // Really! + // call shell function - else if (funk != TT.funcslen) { + } else if (funk != TT.funcslen) { s = 0; // $_ set on return, not here (TT.ff->func = TT.functions[funk])->refcount++; TT.ff->pl = TT.ff->func->pipeline; @@ -2605,7 +2845,6 @@ static struct sh_process *run_command(void) s = pp->arg.v[pp->arg.c-1]; sss = pp->arg.v[pp->arg.c]; //dprintf(2, "%d run command %p %s\n", getpid(), TT.ff, *pp->arg.v); debug_show_fds(); -// TODO handle ((math)): else if (!strcmp(*pp->arg.v, "((")) // TODO: figure out when can exec instead of forking, ala sh -c blah // Is this command a builtin that should run in this process? @@ -2630,6 +2869,7 @@ static struct sh_process *run_command(void) } toys.rebound = 0; pp->exit = toys.exitval; + clearerr(stdout); if (toys.optargs != toys.argv+1) free(toys.optargs); if (toys.old_umask) umask(toys.old_umask); memcpy(&toys, &temp, jj); @@ -2716,22 +2956,28 @@ static int parse_line(char *line, struct sh_pipeline **ppl, // is a HERE document in progress? } else if (pl->count != pl->here) { - arg += 1+pl->here; + // Back up to oldest unfinished pipeline segment. + while (pl != *ppl && pl->prev->count != pl->prev->here) pl = pl->prev; + arg = pl->arg+1+pl->here; // Match unquoted EOF. - for (s = line, end = arg->v[arg->c]; *s && *end; s++) { + for (s = line, end = arg->v[arg->c]; *end; s++, end++) { s += strspn(s, "\\\"'"); - if (*s != *end) break; + if (!*s || *s != *end) break; } // Add this line, else EOF hit so end HERE document - if (!*s && !*end) { + if (*s || *end) { end = arg->v[arg->c]; arg_add(arg, xstrdup(line)); arg->v[arg->c] = end; } else { + // End segment and advance/consume bridge segments arg->v[arg->c] = 0; - pl->here++; + if (pl->count == ++pl->here) + while (pl->next != *ppl && (pl = pl->next)->here == -1) + pl->here = pl->count; } + if (pl->here != pl->count) return 1; start = 0; // Nope, new segment if not self-managing type @@ -2744,63 +2990,30 @@ static int parse_line(char *line, struct sh_pipeline **ppl, // Look for << HERE redirections in completed pipeline segment if (pl && pl->count == -1) { - pl->count = 0; - arg = pl->arg; - // find arguments of the form [{n}]<<[-] with another one after it - for (i = 0; i<arg->c; i++) { - s = arg->v[i] + redir_prefix(arg->v[i]); -// TODO <<< is funky -// argc[] entries removed from main list? Can have more than one? - if (strcmp(s, "<<") && strcmp(s, "<<-") && strcmp(s, "<<<")) continue; + for (arg = pl->arg, pl->count = i = 0; i<arg->c; i++) { + s = skip_redir_prefix(arg->v[i]); + if (strncmp(s, "<<", 2) || s[2]=='<') continue; if (i+1 == arg->c) goto flush; - // Add another arg[] to the pipeline segment (removing/readding to list - // because realloc can move pointer) + // Add another arg[] to the pipeline segment (removing/re-adding + // to list because realloc can move pointer, and adjusing end pointers) dlist_lpop(ppl); - pl = xrealloc(pl, sizeof(*pl) + ++pl->count*sizeof(struct sh_arg)); + pl2 = pl; + pl = xrealloc(pl, sizeof(*pl)+(++pl->count+1)*sizeof(struct sh_arg)); + arg = pl->arg; dlist_add_nomalloc((void *)ppl, (void *)pl); + for (pl3 = *ppl;;) { + if (pl3->end == pl2) pl3->end = pl; + if ((pl3 = pl3->next) == *ppl) break; + } // queue up HERE EOF so input loop asks for more lines. - arg[pl->count].v = xzalloc(2*sizeof(void *)); - arg[pl->count].v[0] = arg->v[++i]; - arg[pl->count].v[1] = 0; + *(arg[pl->count].v = xzalloc(2*sizeof(void *))) = arg->v[++i]; arg[pl->count].c = 0; - if (s[2] == '<') pl->here++; // <<< doesn't load more data - } - - // Did we just end a function? - if (ex == (void *)1) { - struct sh_function *funky; - - // function must be followed by a compound statement for some reason - if ((*ppl)->prev->type != 3) { - s = *(*ppl)->prev->arg->v; - goto flush; - } - - // Back up to saved function() statement and create sh_function - free(dlist_lpop(expect)); - pl = (void *)(*expect)->data; - funky = xmalloc(sizeof(struct sh_function)); - funky->refcount = 1; - funky->name = *pl->arg->v; - *pl->arg->v = (void *)funky; - - // Chop out pipeline segments added since saved function - funky->pipeline = pl->next; - pl->next->prev = (*ppl)->prev; - (*ppl)->prev->next = pl->next; - pl->next = *ppl; - (*ppl)->prev = pl; - dlist_terminate(funky->pipeline = add_pl(&funky->pipeline, 0)); - funky->pipeline->type = 'f'; - - // Immature function has matured (meaning cleanup is different) - pl->type = 'F'; - free(dlist_lpop(expect)); - ex = *expect ? (*expect)->prev->data : 0; } + // Mark "bridge" segment when previous pl had HERE but this doesn't + if (!pl->count && pl->prev->count != pl->prev->here) pl->prev->here = -1; pl = 0; } if (done) break; @@ -2812,15 +3025,13 @@ static int parse_line(char *line, struct sh_pipeline **ppl, // Parse next word and detect overflow (too many nested quotes). if ((end = parse_word(start, 0, 0)) == (void *)1) goto flush; -//dprintf(2, "%d %p %s word=%.*s\n", getpid(), pl, (ex != (void *)1) ? ex : "function", (int)(end-start), end ? start : ""); +//dprintf(2, "%d %p(%d) %s word=%.*s\n", getpid(), pl, pl ? pl->type : -1, ex, (int)(end-start), end ? start : ""); + // End function declaration? if (pl && pl->type == 'f' && arg->c == 1 && (end-start!=1 || *start!='(')) { -funky: - // end function segment, expect function body - dlist_add(expect, (void *)pl); - pl = 0; - dlist_add(expect, (void *)1); + // end (possibly multiline) function segment, expect function body next dlist_add(expect, 0); + pl = 0; continue; } @@ -2841,7 +3052,7 @@ funky: // Ok, we have a word. What does it _mean_? // case/esac parsing is weird (unbalanced parentheses!), handle first - i = (unsigned long)ex>1 && !strcmp(ex, "esac") && + i = ex && !strcmp(ex, "esac") && ((pl->type && pl->type != 3) || (*start==';' && end-start>1)); if (i) { @@ -2854,7 +3065,7 @@ funky: } // type 0 means just got ;; so start new type 2 - if (!pl->type) { + if (!pl->type || pl->type==3) { // catch "echo | ;;" errors if (arg->v && arg->v[arg->c] && strcmp(arg->v[arg->c], "&")) goto flush; if (!arg->c) { @@ -2874,6 +3085,7 @@ funky: // Did we hit end of line or ) outside a function declaration? // ) is only saved at start of a statement, ends current statement } else if (end == start || (arg->c && *start == ')' && pl->type!='f')) { +//TODO: test ) within ]] // function () needs both parentheses or neither if (pl->type == 'f' && arg->c != 1 && arg->c != 3) { s = "function("; @@ -2881,7 +3093,7 @@ funky: } // "for" on its own line is an error. - if (arg->c == 1 && (unsigned long)ex>1 && !memcmp(ex, "do\0A", 4)) { + if (arg->c == 1 && !smemcmp(ex, "do\0A", 4)) { s = "newline"; goto flush; } @@ -2959,7 +3171,8 @@ funky: if (arg->c == 2 && strcmp(s, "(")) goto flush; if (arg->c == 3) { if (strcmp(s, ")")) goto flush; - goto funky; + dlist_add(expect, 0); + pl = 0; } continue; @@ -2968,26 +3181,25 @@ funky: } else if (strchr(";|&", *s) && strncmp(s, "&>", 2)) { arg->c--; + // Connecting nonexistent statements is an error + if (!arg->c || !smemcmp(ex, "do\0A", 4)) goto flush; + // treat ; as newline so we don't have to check both elsewhere. if (!strcmp(s, ";")) { arg->v[arg->c] = 0; free(s); s = 0; // TODO can't have ; between "for i" and in or do. (Newline yes, ; no. Why?) - if (!arg->c && (unsigned long)ex>1 && !memcmp(ex, "do\0C", 4)) continue; + if (!arg->c && !smemcmp(ex, "do\0C", 4)) continue; // ;; and friends only allowed in case statements } else if (*s == ';') goto flush; - - // flow control without a statement is an error - if (!arg->c) goto flush; pl->count = -1; continue; // a for/select must have at least one additional argument on same line - } else if ((unsigned long)ex>1 && !memcmp(ex, "do\0A", 4)) { - + } else if (!smemcmp(ex, "do\0A", 4)) { // Sanity check and break the segment if (strncmp(s, "((", 2) && *varend(s)) goto flush; pl->count = -1; @@ -2996,22 +3208,24 @@ funky: continue; // flow control is the first word of a pipeline segment - } else if (arg->c>1) continue; - - // Do we expect something that _must_ come next? (no multiple statements) - if ((unsigned long)ex>1) { - // The "test" part of for/select loops can have (at most) one "in" line, - // for {((;;))|name [in...]} do - if (!memcmp(ex, "do\0C", 4)) { - if (strcmp(s, "do")) { - // can only have one "in" line between for/do, but not with for(()) - if (pl->prev->type == 's') goto flush; - if (!strncmp(pl->prev->arg->v[1], "((", 2)) goto flush; - else if (strcmp(s, "in")) goto flush; - pl->type = 's'; + } else if (arg->c>1) { + // Except that [[ ]] is a type 0 segment + if (ex && *ex==']' && !strcmp(s, ex)) free(dlist_lpop(expect)); - continue; - } + continue; + } + + // The "test" part of for/select loops can have (at most) one "in" line, + // for {((;;))|name [in...]} do + if (!smemcmp(ex, "do\0C", 4)) { + if (strcmp(s, "do")) { + // can only have one "in" line between for/do, but not with for(()) + if (pl->prev->type == 's') goto flush; + if (!strncmp(pl->prev->arg->v[1], "((", 2)) goto flush; + else if (strcmp(s, "in")) goto flush; + pl->type = 's'; + + continue; } } @@ -3030,28 +3244,36 @@ funky: if (!strcmp(s, "if")) end = "then"; else if (!strcmp(s, "while") || !strcmp(s, "until")) end = "do\0B"; else if (!strcmp(s, "{")) end = "}"; - else if (!strcmp(s, "[[")) end = "]]"; else if (!strcmp(s, "(")) end = ")"; + else if (!strcmp(s, "[[")) end = "]]"; - // Expecting NULL means a statement: I.E. any otherwise unrecognized word - if (!ex && *expect) free(dlist_lpop(expect)); + // Expecting NULL means any statement (don't care which). + if (!ex && *expect) { + if (pl->prev->type == 'f' && !end && smemcmp(s, "((", 2)) goto flush; + free(dlist_lpop(expect)); + } // Did we start a new statement if (end) { - pl->type = 1; + if (*end!=']') pl->type = 1; + else { + // [[ ]] is a type 0 segment, not a flow control block + dlist_add(expect, end); + continue; + } // Only innermost statement needed in { { { echo ;} ;} ;} and such if (*expect && !(*expect)->prev->data) free(dlist_lpop(expect)); - // if can't end a statement here skip next few tests - } else if ((unsigned long)ex<2); + // if not looking for end of statement skip next few tests + } else if (!ex); // If we got here we expect a specific word to end this block: is this it? else if (!strcmp(s, ex)) { // can't "if | then" or "while && do", only ; & or newline works if (strcmp(pl->prev->arg->v[pl->prev->arg->c] ? : "&", "&")) goto flush; - // consume word, record block end location in earlier !0 type blocks + // consume word, record block end in earlier !0 type (non-nested) blocks free(dlist_lpop(expect)); if (3 == (pl->type = anystr(s, tails) ? 3 : 2)) { for (i = 0, pl2 = pl3 = pl; (pl2 = pl2->prev);) { @@ -3060,7 +3282,7 @@ funky: if (!i) { if (pl2->type == 2) { pl2->end = pl3; - pl3 = pl2; + pl3 = pl2; // chain multiple gearshifts for case/esac } else pl2->end = pl; } if (pl2->type == 1 && --i<0) break; @@ -3105,18 +3327,44 @@ funky: // ignore blank and comment lines if (!*ppl) return 0; - -// TODO <<< has no parsing impact, why play with it here at all? - // advance past <<< arguments (stored as here documents, but no new input) pl = (*ppl)->prev; - while (pl->count<pl->here && pl->arg[pl->count].c<0) - pl->arg[pl->count++].c = 0; // return if HERE document pending or more flow control needed to complete + if (pl->count != pl->here) return 1; if (*expect) return 1; - if (*ppl && pl->count != pl->here) return 1; if (pl->arg->v[pl->arg->c] && strcmp(pl->arg->v[pl->arg->c], "&")) return 1; + // Transplant completed function bodies into reference counted structures + for (;;) { + if (pl->type=='f') { + struct sh_function *funky; + + // Create sh_function struct, attach to declaration's pipeline segment + funky = xmalloc(sizeof(struct sh_function)); + funky->refcount = 1; + funky->name = *pl->arg->v; + *pl->arg->v = (void *)funky; + pl->type = 'F'; // different cleanup + + // Transplant function body into new struct, re-circling both lists + pl2 = pl->next; + // Add NOP 'f' segment (TODO: remove need for this?) + (funky->pipeline = add_pl(&pl2, 0))->type = 'f'; + // Find end of block + for (i = 0, pl3 = pl2->next;;pl3 = pl3->next) + if (pl3->type == 1) i++; + else if (pl3->type == 3 && --i<0) break; + // Chop removed segment out of old list. + pl3->next->prev = pl; + pl->next = pl3->next; + // Terminate removed segment. + pl2->prev = 0; + pl3->next = 0; + } + if (pl == *ppl) break; + pl = pl->prev; + } + // Don't need more input, can start executing. dlist_terminate(*ppl); @@ -3310,6 +3558,7 @@ static char *get_next_line(FILE *ff, int prompt) // TODO: ctrl-z during script read having already read partial line, // SIGSTOP and SIGTSTP need SA_RESTART, but child proc should stop // TODO if (!isspace(*new)) add_to_history(line); +// TODO: embedded nul bytes need signaling for the "tried to run binary" test. for (new = 0, len = 0;;) { errno = 0; @@ -3457,8 +3706,7 @@ static void run_lines(void) break; } // Parse and run next command, saving resulting process - } else if ((pp = run_command())) - dlist_add_nomalloc((void *)&pplist, (void *)pp); + } else dlist_add_nomalloc((void *)&pplist, (void *)run_command()); // Start of flow control block? } else if (TT.ff->pl->type == 1) { @@ -3483,7 +3731,7 @@ static void run_lines(void) // TODO test background a block: { abc; } & // If we spawn a subshell, pass data off to child process - if (TT.ff->blk->pipe || !strcmp(s, "(") || (ctl && !strcmp(ctl, "&"))) { + if (TT.ff->blk->next->pipe || !strcmp(s, "(") || (ctl && !strcmp(ctl, "&"))) { if (!(pp->pid = run_subshell(0, -1))) { // zap forked child's cleanup context and advance to next statement @@ -3509,10 +3757,39 @@ static void run_lines(void) // for/select/do/done: populate blk->farg with expanded args (if any) if (!strcmp(s, "for") || !strcmp(s, "select")) { - if (TT.ff->blk->loop); + if (TT.ff->blk->loop); // skip init, not first time through loop + + // in (;;) else if (!strncmp(TT.ff->blk->fvar = ss, "((", 2)) { + char *in = ss+2, *out; + long long ll; + TT.ff->blk->loop = 1; -dprintf(2, "TODO skipped init for((;;)), need math parser\n"); + for (i = 0; i<3; i++) { + if (i==2) k = strlen(in)-2; + else { + // perform expansion but immediately discard it to find ; + k = ';'; + pp = xzalloc(sizeof(*pp)); + if (expand_arg_nobrace(&pp->arg, ss+2, NO_PATH|NO_SPLIT, + &pp->delete, 0, &k)) break; + free_process(pp); + if (in[k] != ';') break; + } + (out = xmalloc(k+1))[k] = 0; + memcpy(out, in, k); + arg_add(&TT.ff->blk->farg, push_arg(&TT.ff->blk->fdelete, out)); + in += k+1; + } + if (i!=3) { + syntax_err(ss); + break; + } + in = out = *TT.ff->blk->farg.v; + if (!recalculate(&ll, &in, 0) || *in) { + perror_msg("bad math: %s @ %ld", in, (long)(in-out)); + break; + } // in LIST } else if (TT.ff->pl->next->type == 's') { @@ -3563,7 +3840,7 @@ dprintf(2, "TODO skipped init for((;;)), need math parser\n"); } arg.c = arg2.c = 0; if ((err = expand_arg_nobrace(&arg, *vv++, NO_SPLIT, - &TT.ff->blk->fdelete, &arg2))) break; + &TT.ff->blk->fdelete, &arg2, 0))) break; s = arg.c ? *arg.v : ""; match = wildcard_match(TT.ff->blk->fvar, s, &arg2, 0); if (match>=0 && !s[match]) break; @@ -3606,7 +3883,21 @@ dprintf(2, "TODO skipped init for((;;)), need math parser\n"); } } else if (blk->loop >= blk->farg.c) TT.ff->pl = pop_block(); else if (!strncmp(blk->fvar, "((", 2)) { -dprintf(2, "TODO skipped running for((;;)), need math parser\n"); + char *aa, *bb; + long long ll; + + for (i = 2; i; i--) { + if (TT.ff->blk->loop == 1) { + TT.ff->blk->loop++; + i--; + } + aa = bb = TT.ff->blk->farg.v[i]; + if (!recalculate(&ll, &aa, 0) || *aa) { + perror_msg("bad math: %s @ %ld", aa, (long)(aa-bb)); + break; + } + if (i==1 && !ll) TT.ff->pl = pop_block(); + } } else setvarval(blk->fvar, blk->farg.v[blk->loop++]); } @@ -3753,6 +4044,8 @@ int do_source(char *name, FILE *ff) goto end; } + if (name) TT.ff->omnom = name; + // TODO fix/catch NONBLOCK on input? // TODO when DO we reset lineno? (!LINENO means \0 returns 1) // when do we NOT reset lineno? Inherit but preserve perhaps? newline in $()? @@ -3764,7 +4057,7 @@ int do_source(char *name, FILE *ff) // did we exec an ELF file or something? if (!TT.LINENO++ && name && new) { // A shell script's first line has no high bytes that aren't valid utf-8. - for (ii = 0; new[ii] && 0<(cc = utf8towc(&wc, new+ii, 4)); ii += cc); + for (ii = 0; new[ii]>6 && 0<(cc = utf8towc(&wc, new+ii, 4)); ii += cc); if (new[ii]) { is_binary: if (name) error_msg("'%s' is binary", name); // TODO syntax_err() exit? @@ -3780,9 +4073,8 @@ is_binary: more = parse_line(new ? : " ", &pl, &expect); free(new); if (more==1) { - if (!new) { - if (!ff) syntax_err("unexpected end of file"); - } else continue; + if (!new) syntax_err("unexpected end of file"); + else continue; } else if (!more && pl) { TT.ff->pl = pl; run_lines(); @@ -3832,7 +4124,7 @@ static void nommu_reentry(void) // Read named variables: type, len, var=value\0 for (;;) { len = ll = 0; - fscanf(fp, "%u %lu%*[^\n]", &len, &ll); + (void)fscanf(fp, "%u %lu%*[^\n]", &len, &ll); fgetc(fp); // Discard the newline fscanf didn't eat. if (!len) break; (s = xmalloc(len+1))[len] = 0; @@ -3901,7 +4193,7 @@ static void subshell_setup(void) shv->flags |= VAR_EXPORT; shv->str = s; } - cache_ifs(s, TT.ff); + cache_ifs(s, TT.ff); // TODO: replace with set(get("IFS")) after loop } // set/update PWD @@ -4394,7 +4686,10 @@ void source_main(void) call_function(); TT.ff->arg.v = toys.optargs; TT.ff->arg.c = toys.optc; + TT.ff->oldlineno = TT.LINENO; + TT.LINENO = 0; do_source(name, ff); + TT.LINENO = TT.ff->oldlineno; free(dlist_pop(&TT.ff)); --TT.srclvl; } |