From b25f04996cfd4700fd3ee164168d9737c010908c Mon Sep 17 00:00:00 2001 From: Rob Landley Date: Tue, 15 Dec 2020 02:45:59 -0600 Subject: Implement set, trace support, and function call context stack. --- toys/pending/sh.c | 190 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 142 insertions(+), 48 deletions(-) (limited to 'toys/pending') diff --git a/toys/pending/sh.c b/toys/pending/sh.c index 82d9ad32..4a96a939 100644 --- a/toys/pending/sh.c +++ b/toys/pending/sh.c @@ -45,6 +45,7 @@ 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(set, 0, TOYFLAG_NOFORK)) USE_SH(NEWTOY(shift, ">1", TOYFLAG_NOFORK)) USE_SH(NEWTOY(source, "0<1", TOYFLAG_NOFORK)) USE_SH(OLDTOY(., source, TOYFLAG_NOFORK)) @@ -93,6 +94,24 @@ config EXIT Exit shell. If no return value supplied on command line, use value of most recent command, or 0 if none. +config SET + bool + default n + depends on SH + help + usage: set [+a] [+o OPTION] [VAR...] + + Set variables and shell attributes. Use + to disable and - to enable. + NAME=VALUE arguments assign to the variable, any leftovers set $1, $2... + With no arguments, prints current variables. + + -f NAME is a function + -v NAME is a variable + -n dereference NAME and unset that + + OPTIONs: + history - enable command history + config UNSET bool default n @@ -186,11 +205,10 @@ GLOBALS( } exec; }; - // keep lineno here: used to work around compiler limitation in run_command() - long lineno; + // keep ifs here: used to work around compiler limitation in run_command() char *ifs, *isexec, *wcpat; unsigned options, jobcnt; - int hfd, pid, bangpid, varslen, shift, cdcount; + int hfd, pid, bangpid, varslen, cdcount; long long SECONDS; // global and local variables @@ -202,7 +220,7 @@ GLOBALS( // Parsed functions struct sh_function { char *name; - struct sh_pipeline { // pipeline segments + struct sh_pipeline { // pipeline segments: linked list of arg w/metadata struct sh_pipeline *next, *prev, *end; int count, here, type; // TODO abuse type to replace count during parsing struct sh_arg { @@ -223,8 +241,16 @@ GLOBALS( struct sh_arg *raw, arg; } *pp; // currently running process + struct sh_callstack { + struct sh_callstack *next; + struct sh_function scratch; + struct sh_arg arg; + struct arg_list *delete; + long lineno, shift; + } *cc; + // job list, command line for $*, scratch space for do_wildcard_files() - struct sh_arg jobs, *arg, *wcdeck; + struct sh_arg jobs, *wcdeck; ) // Can't yet avoid this prototype. Fundamental problem is $($($(blah))) nests, @@ -237,8 +263,10 @@ static int sh_run(char *new); static const char *redirectors[] = {"<<<", "<<-", "<<", "<&", "<>", "<", ">>", ">&", ">|", ">", "&>>", "&>", 0}; -#define OPT_BRACE 0x100 // set -B -#define OPT_NOCLOBBER 0x200 // set -C +// The order of these has to match the string in set_main() +#define OPT_B 0x100 +#define OPT_C 0x200 +#define OPT_x 0x400 static void syntax_err(char *s) { @@ -258,7 +286,7 @@ static void arg_add(struct sh_arg *arg, char *data) } // add argument to an arg_list -static char *push_arg(struct arg_list **list, char *arg) +static void *push_arg(struct arg_list **list, void *arg) { struct arg_list *al; @@ -409,7 +437,7 @@ static char *getvar(char *s) 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 == 'L') sprintf(toybuf, "%ld", TT.cc->lineno); else if (c == 'G') sprintf(toybuf, "TODO: GROUPS"); return toybuf; @@ -757,21 +785,21 @@ char *getvar_special(char *str, int len, int *used, struct arg_list **delete) if (cc == '-') { s = ss = xmalloc(8); if (TT.options&FLAG_i) *ss++ = 'i'; - if (TT.options&OPT_BRACE) *ss++ = 'B'; + if (TT.options&OPT_B) *ss++ = 'B'; if (TT.options&FLAG_s) *ss++ = 's'; if (TT.options&FLAG_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", TT.cc->arg.c?TT.cc->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]; + if (uu) uu += TT.cc->shift; + if (uuarg.c) s = TT.cc->arg.v[uu]; } else if ((*used = varend(str)-str)) return getvar(str); } if (s) push_arg(delete, s); @@ -1270,7 +1298,7 @@ dprintf(2, "TODO: do math for %.*s\n", kk, s); for (slice++, kk = 0; kkarg->v[i]); else len += strlen(pl->arg->v[i])+1; - sss = pl->arg->v[pl->arg->c]; - if (!sss) sss = ";"; + if (!(sss = pl->arg->v[pl->arg->c])) sss = ";"+!end->next; if (j) ss = stpcpy(ss, sss); else len += strlen(sss); @@ -1977,7 +2004,7 @@ notfd: else if (strstr(ss, ">>")) from = O_CREAT|O_APPEND|O_WRONLY; else { from = (*ss == '<') ? O_RDONLY : O_CREAT|O_WRONLY|O_TRUNC; - if (!strcmp(ss, ">") && (TT.options&OPT_NOCLOBBER)) { + if (!strcmp(ss, ">") && (TT.options&OPT_C)) { struct stat st; // Not _just_ O_EXCL: > /dev/null allowed @@ -2117,7 +2144,7 @@ static struct sh_process *run_command(struct sh_arg *arg) // "declaration does not declare anything", but if we DON'T give it a name // it accepts it. So we can't use the union's type name here, and have // to offsetof() the first thing _after_ the union to get the size. - memset(&TT, 0, offsetof(struct sh_data, lineno)); + memset(&TT, 0, offsetof(struct sh_data, ifs)); TT.pp = pp; if (!sigsetjmp(rebound, 1)) { @@ -2187,6 +2214,7 @@ dprintf(2, "stub add_function"); return pl->end; } +// Append a new pipeline to function, returning pipeline and pipeline's arg static struct sh_pipeline *add_pl(struct sh_function *sp, struct sh_arg **arg) { struct sh_pipeline *pl = xzalloc(sizeof(struct sh_pipeline)); @@ -2642,6 +2670,7 @@ static int pipe_segments(char *ctl, int *pipes, int **urd) return 0; } +// Stack of nested if/else/fi and for/do/done contexts. struct blockstack { struct blockstack *next; struct sh_pipeline *start, *middle; @@ -2683,7 +2712,7 @@ static void do_prompt(char *prompt) if (c=='!') { if (*prompt=='!') prompt++; else { - pp += snprintf(pp, len, "%ld", TT.lineno); + pp += snprintf(pp, len, "%ld", TT.cc->lineno); continue; } } else if (c=='\\') { @@ -2756,6 +2785,14 @@ TODO: a | b | c needs subshell for builtins? TT.hfd = 10; + if (TT.options&OPT_x) { + char *s = pl2str(pl); + + do_prompt(getvar("PS4")); + dprintf(2, "%s\n", s); + free(s); + } + // iterate through pipeline segments while (pl) { char *ctl = pl->end->arg->v[pl->end->arg->c], **vv, @@ -3010,7 +3047,7 @@ static int sh_run(char *new) return toys.exitval; } -// only set local variable when global not present, does not extend array +// set variable static struct sh_vars *initlocal(char *name, char *val) { return addvar(xmprintf("%s=%s", name, val ? val : "")); @@ -3093,21 +3130,18 @@ char *prompt_getline(FILE *ff, int prompt) // Read script input and execute lines, with or without prompts int do_source(char *name, FILE *ff) { - struct sh_function scratch; - long lineno = TT.lineno, shift = TT.shift; - struct sh_arg arg, *old = TT.arg; + struct sh_callstack *cc = xzalloc(sizeof(struct sh_callstack)); int more = 0; char *new; - arg.c = toys.optc; - arg.v = toys.optargs; - TT.arg = &arg; - TT.lineno = TT.shift = 0; - memset(&scratch, 0, sizeof(scratch)); + cc->next = TT.cc; + cc->arg.v = toys.optargs; + cc->arg.c = toys.optc; + TT.cc = cc; do { new = prompt_getline(ff, more+1); - if (!TT.lineno++ && new && !memcmp(new, "\177ELF", 4)) { + if (!TT.cc->lineno++ && new && !memcmp(new, "\177ELF", 4)) { error_msg("'%s' is ELF", name); free(new); @@ -3118,21 +3152,22 @@ int do_source(char *name, FILE *ff) // prints "hello" vs "hello\" // returns 0 if line consumed, command if it needs more data - more = parse_line(new ? : " ", &scratch); + more = parse_line(new ? : " ", &cc->scratch); if (more==1) { if (!new && !ff) syntax_err("unexpected end of file"); } else { - if (!more) run_function(scratch.pipeline); - free_function(&scratch); + if (!more) run_function(cc->scratch.pipeline); + free_function(&cc->scratch); more = 0; } free(new); } while(new); if (ff) fclose(ff); - TT.lineno = lineno; - TT.shift = shift; - TT.arg = old; + TT.cc = TT.cc->next; + free_function(&cc->scratch); + llist_traverse(cc->delete, llist_free_arg); + free(cc); return more; } @@ -3177,6 +3212,7 @@ static void subshell_setup(void) initlocal("BASH", s); initlocal("PS2", "> "); initlocal("PS3", "#? "); + initlocal("PS4", "+ "); // Ensure environ copied and toys.envc set, and clean out illegal entries TT.ifs = " \t\n"; @@ -3256,7 +3292,7 @@ void sh_main(void) FILE *ff; signal(SIGPIPE, SIG_IGN); - TT.options = OPT_BRACE; + TT.options = OPT_B; TT.pid = getpid(); TT.SECONDS = time(0); @@ -3301,7 +3337,7 @@ void sh_main(void) // Read and execute lines from file if (do_source(cc ? : *toys.optargs, ff)) - error_exit("%ld:unfinished line"+4*!TT.lineno, TT.lineno); + error_exit("%ld:unfinished line"+4*!TT.cc->lineno, TT.cc->lineno); } // TODO: ./blah.sh one two three: put one two three in scratch.arg @@ -3382,6 +3418,58 @@ void exit_main(void) exit(*toys.optargs ? atoi(*toys.optargs) : 0); } +// lib/args.c can't +prefix & "+o history" needs space so parse cmdline here +void set_main(void) +{ + char *cc, *ostr[] = {"braceexpand", "noclobber", "xtrace"}; + int ii, jj, kk, oo = 0, dd = 0; + + // Handle options + for (ii = 0;; ii++) { + if ((cc = toys.optargs[ii]) && !(dd = stridx("-+", *cc)+1) && oo--) { + for (jj = 0; jj0) for (jj = 0; jjarg; + + for (al = *(head = &TT.cc->delete); al; al = *(head = &al->next)) + if (al->arg == (void *)arg->v) break; + + // free last set's memory (if any) so it doesn't accumulate in loop + if (al) for (jj = arg->c+1; jj; jj--) { + *head = al->next; + free(al->arg); + free(al); + } + + while (toys.optargs[ii]) + arg_add(arg, push_arg(&TT.cc->delete, strdup(toys.optargs[ii++]))); + push_arg(&TT.cc->delete, arg->v); + } +} + void unset_main(void) { char **arg, *s; @@ -3428,12 +3516,18 @@ void export_main(void) void eval_main(void) { - struct sh_arg *old = TT.arg, new = {toys.argv, toys.optc+1}; + struct sh_arg old = TT.cc->arg, *volatile arg = &TT.cc->arg; char *s; - TT.arg = &new; + // borrow the $* expand infrastructure (avoiding $* from trap handler race). + arg->c = 0; + arg->v = toys.argv; + arg->c = toys.optc+1; s = expand_one_arg("\"$*\"", SEMI_IFS, 0); - TT.arg = old; + arg->c = 0; + arg->v = old.v; + arg->c = old.c; + sh_run(s); free(s); } @@ -3559,9 +3653,9 @@ void shift_main(void) long long by = 1; if (toys.optc) by = atolx(*toys.optargs); - by += TT.shift; - if (by<0 || by>=TT.arg->c) toys.exitval++; - else TT.shift = by; + by += TT.cc->shift; + if (by<0 || by>=TT.cc->arg.c) toys.exitval++; + else TT.cc->shift = by; } void source_main(void) -- cgit v1.2.3