/* sh.c - toybox shell * * Copyright 2006 Rob Landley * * The POSIX-2008/SUSv4 spec for this is at: * http://opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html * and http://opengroup.org/onlinepubs/9699919799/utilities/sh.html * * The first link describes the following shell builtins: * * break colon continue dot eval exec exit export readonly return set shift * times trap unset * * The second link (the utilities directory) also contains specs for the * following shell builtins: * * alias bg cd command fc fg getopts hash jobs kill read type ulimit * umask unalias wait * * Things like the bash man page are good to read too. USE_SH(NEWTOY(cd, NULL, TOYFLAG_NOFORK)) USE_SH(NEWTOY(exit, NULL, TOYFLAG_NOFORK)) USE_SH(NEWTOY(sh, "c:i", TOYFLAG_BIN)) USE_SH(OLDTOY(toysh, sh, TOYFLAG_BIN)) // Login lies in argv[0], so add some aliases to catch that USE_SH(OLDTOY(-sh, sh, 0)) USE_SH(OLDTOY(-toysh, sh, 0)) config SH bool "sh (toysh)" default n help usage: sh [-c command] [script] Command shell. Runs a shell script, or reads input interactively and responds to it. -c command line to execute -i interactive mode (default when STDIN is a tty) # These are here for the help text, they're not selectable and control nothing config CD bool default n depends on SH help usage: cd [-PL] [path] Change current directory. With no arguments, go $HOME. -P Physical path: resolve symlinks in path -L Local path: .. trims directories off $PWD (default) config EXIT bool default n depends on SH help usage: exit [status] Exit shell. If no return value supplied on command line, use value of most recent command, or 0 if none. */ #define FOR_sh #include "toys.h" GLOBALS( char *command; long lineno; // parse scratch space struct double_list *parse; // Running jobs. struct sh_job { struct sh_job *next, *prev; unsigned jobno; // Every pipeline has at least one set of arguments or it's Not A Thing struct sh_arg { char **v; unsigned long c; } pipeline; // null terminated array of running processes in pipeline struct sh_process { struct string_list *delete; // expanded strings int pid, exit; // status? Stopped? Exited? char *end; struct sh_arg arg; } *procs, *proc; } *jobs, *job; unsigned jobcnt; ) void cd_main(void) { char *dest = *toys.optargs ? *toys.optargs : getenv("HOME"); xchdir(dest ? dest : "/"); } void exit_main(void) { exit(*toys.optargs ? atoi(*toys.optargs) : 0); } // Print prompt, parsing escapes static void do_prompt(char *prompt) { char *s, c, cc; if (!prompt) prompt = "\\$ "; while (*prompt) { c = *(prompt++); if (c=='!') { if (*prompt=='!') prompt++; else { printf("%ld", TT.lineno); continue; } } else if (c=='\\') { int i = 0; cc = *(prompt++); if (!cc) goto down; // \nnn \dD{}hHjlstT@AuvVwW!#$ // Ignore bash's "nonprintable" hack; query our cursor position instead. if (cc=='[' || cc==']') continue; else if (cc=='$') putchar(getuid() ? '$' : '#'); else if (cc=='h' || cc=='H') { *toybuf = 0; gethostname(toybuf, sizeof(toybuf)-1); if (cc=='h' && (s = strchr(toybuf, '.'))) *s = 0; fputs(toybuf, stdout); } else if (cc=='s') fputs(getbasename(*toys.argv), stdout); else { if (!(c = unescape(cc))) { c = '\\'; prompt--; } i++; } if (!i) continue; } down: putchar(c); } fflush(stdout); } // Execute the commands in a pipeline static void run_command(struct sh_process *pp) { struct toy_list *tl = toy_find(*pp->arg.v); // Is this command a builtin that should run in this process? if (tl && (tl->flags & TOYFLAG_NOFORK)) { struct toy_context temp; sigjmp_buf rebound; // This fakes lots of what toybox_main() does. memcpy(&temp, &toys, sizeof(struct toy_context)); memset(&toys, 0, sizeof(struct toy_context)); if (!sigsetjmp(rebound, 1)) { toys.rebound = &rebound; toy_init(tl, pp->arg.v); tl->toy_main(); } pp->exit = toys.exitval; if (toys.optargs != toys.argv+1) free(toys.optargs); if (toys.old_umask) umask(toys.old_umask); memcpy(&toys, &temp, sizeof(struct toy_context)); } else { int pipe[2]; pipe[0] = 0; pipe[1] = 1; if (-1 == (pp->pid = xpopen_both(pp->arg.v, pipe))) perror_msg("%s: vfork", *pp->arg.v); else pp->exit = xpclose_both(pp->pid, 0); } return; } // todo: ${name:?error} causes an error/abort here (syntax_err longjmp?) static void expand_arg(struct sh_arg *arg, char *new) { if (!(arg->c&32)) arg->v = xrealloc(arg->v, sizeof(void *)*(arg->c+33)); arg->v[arg->c++] = new; arg->v[arg->c] = 0; } // like error_msg() but exit from shell scripts void syntax_err(char *msg, ...) { va_list va; va_start(va, msg); verror_msg(msg, 0, va); va_end(va); if (*toys.optargs) xexit(); } // Parse one word from the command line, appending one or more argv[] entries // to struct command. Handles environment variable substitution and // substrings. Returns pointer to next used byte, or NULL if it // hit an ending token. // caller eats leading spaces // parse next word from command line. Returns end, or 0 if need continuation static char *parse_word(char *start) { int i, quote = 0; char *end = start, *s; // find end of string while (*end) { i = 0; // Handle quote contexts if (quote) { // end quote, skip quoted chars if (*end == toybuf[quote-1]) quote--, end++; else if (toybuf[quote-1] == '"' && *end == '`') toybuf[quote++] = *end++; else if (toybuf[quote-1] == '\'' || isspace(*end)) end++; else i++; } else { if (isspace(*end)) break; // start quote if (strchr("\"'`", *end)) toybuf[quote++] = *end++; else if (strstart(&end, "<(") || strstart(&end,">(")) toybuf[quote++]=')'; else if (*end==')') return end+(end==start); else { // control chars for (s = end; strchr(";|&<>(", *s); s++); if (s != end) return (end == start) ? s : end; i++; } } // loop if we already handled a symbol if (!i) continue; // Things the same unquoted or in double quotes // backslash escapes if (*end == '\\') { if (!end[1]) return 0; end += 2; } else if (*end == '$') { // barf if we're near overloading quote stack (nesting ridiculously deep) if (quote>4000) { syntax_err("tilt"); return (void *)1; } end++; if (strstart(&end, "((")) { // all we care about here are parentheses matching and then ending )) for (i = 0;;) { if (!*end) return 0; if (!i && strstart(&end, "))")) break; if (*end == '(') i++; else if (*end == ')') i--; } } else if (-1 != (i = stridx("({[", *end))) { toybuf[quote++] = ")}]"[i]; end++; } } else end++; } return quote ? 0 : end; } // Consume a line of shell script and do what it says. Returns 0 if finished, // pointer to start of unused part of line if it needs another line of input. static char *parse_line(char *line, struct double_list **pipeline) { char *start = line, *end, *s, *ex, *add; struct sh_arg *arg = 0; struct double_list *pl, *expect = 0; unsigned i, paren = 0; // Resume appending to last pipeline's last argument list if (*pipeline) arg = (void *)(*pipeline)->prev->data; if (arg) for (i = 0; ic; i++) { if (!strcmp(arg->v[i], "(")) paren++; else if (!strcmp(arg->v[i], ")")) paren--; } // Loop handling each word for (;;) { // Skip leading whitespace/comment while (isspace(*start)) ++start; if (*start=='#') { while (*start && *start != '\n') start++; continue; } // Parse next word and detect continuation/overflow. if ((end = parse_word(start)) == (void *)1) return 0; if (!end) return start; // Extend pipeline and argv[], handle EOL if (!arg) dlist_add(pipeline, (void *)(arg = xzalloc(sizeof(struct sh_arg)))); if (!(31&arg->c)) arg->v = xrealloc(arg->v, (32+arg->c)*sizeof(void *)); if (end == start) { arg->v[arg->c] = 0; break; } // Save argument (strdup) and check if it's special s = arg->v[arg->c] = xstrndup(start, end-start); if (!strcmp(s, "(")) paren++; else if (!strcmp(s, ")") && !paren--) syntax_err("bad %s", s); if (paren || !strchr(";|&", *start)) arg->c++; else { if (!arg->c) { syntax_err("bad %s", arg->v[arg->c]); goto flush; } arg = 0; } start = end; } // We parsed to the end of the line, which ended a pipeline. // Now handle flow control commands, which can also need more lines. // array of command lines separated by | and such // Note: don't preparse past ; because environment variables differ // Check for flow control continuations end = 0; for (pl = *pipeline; pl ; pl = (pl->next == *pipeline) ? 0 : pl->next) { arg = (void *)pl->data; if (!arg->c) continue; add = 0; // parse flow control statements in this command line for (i = 0; ; i++) { ex = expect ? expect->prev->data : 0; s = arg->v[i]; // push word to expect to end this block, and expect a command first if (add) { dlist_add(&expect, add); dlist_add(&expect, add = 0); } // end of statement? if (i == arg->c) break; // When waiting for { it must be next symbol, but can be on a new line. if (ex && !strcmp(ex, "{") && (strcmp(s, "{") || (!i && end))) { syntax_err("need {"); goto flush; } if (!strcmp(s, "if")) add = "then"; else if (!strcmp(s, "for") || !strcmp(s, "select") || !strcmp(s, "while") || !strcmp(s, "until")) add = "do"; else if (!strcmp(s, "case")) add = "esac"; else if (!strcmp(s, "{")) add = "}"; else if (!strcmp(s, "[[")) add = "]]"; // function NAME () [nl] { [nl] body ; } // Why can you to declare functions inside other functions? else if (arg->c>i+1 && !strcmp(arg->v[i+1], "(")) goto funky; else if (!strcmp(s, "function")) { i++; funky: // At this point we can only have a function: barf if it's invalid if (arg->cv[i+1], "(") || !strcmp(arg->v[i+2], ")")) { syntax_err("bad function ()"); goto flush; } dlist_add(&expect, "}"); dlist_add(&expect, 0); dlist_add(&expect, "{"); // Expecting NULL will take any otherwise unrecognized word } else if (expect && !ex) { free(dlist_pop(&expect)); continue; // If we expect nothing and didn't just start a new flow control block, // rest of statement is a command and arguments, so stop now } else if (!ex) break; if (add) continue; // If we got here we expect a word to end this block: is this it? if (!strcmp(arg->v[i], ex)) { free(dlist_pop(&expect)); // can't "if | then" or "while && do", only ; or newline works if (end && !strcmp(end, ";")) { syntax_err("bad %s", end); goto flush; } // if it's a multipart block, what comes next? if (!strcmp(s, "do")) ex = "done"; else if (!strcmp(s, "then")) add = "fi\0A"; // fi could have elif, which queues a then. } else if (!strcmp(ex, "fi")) { if (!strcmp(s, "elif")) { free(dlist_pop(&expect)); add = "then"; // catch duplicate else while we're here } else if (!strcmp(s, "else")) { if (ex[3] != 'A') { syntax_err("2 else"); goto flush; } free(dlist_pop(&expect)); add = "fi\0B"; } } } // Record how the previous stanza ended: ; | & ;; || && ;& ;;& |& NULL end = arg->v[arg->c]; } // Do we need more lines to finish a flow control statement? if (expect || paren) { llist_traverse(expect, free); return start; } // iterate through the commands running each one for (pl = *pipeline; pl ; pl = (pl->next == *pipeline) ? 0 : pl->next) { struct sh_process *pp = xzalloc(sizeof(struct sh_process)); for (i = 0; i<((struct sh_arg *)pl->data)->c; i++) expand_arg(&pp->arg, ((struct sh_arg *)pl->data)->v[i]); run_command(pp); } flush: while ((pl = dlist_pop(pipeline))) { arg = (void *)pl->data; free(pl); for (i = 0; ic; i++) free(arg->v[i]); free(arg->v); free(arg); } *pipeline = 0; return 0; } void sh_main(void) { FILE *f = 0; char *command = 0, *old = 0; struct double_list *scratch = 0; // Set up signal handlers and grab control of this tty. if (isatty(0)) toys.optflags |= FLAG_i; if (*toys.optargs) f = xfopen(*toys.optargs, "r"); if (TT.command) command = parse_line(TT.command, &scratch); else for (;;) { char *new = 0; size_t linelen = 0; // Prompt and read line if (!f) { char *s = getenv(command ? "PS2" : "PS1"); if (!s) s = command ? "> " : (getpid() ? "\\$ " : "# "); do_prompt(s); } if (1 > getline(&new, &linelen, f ? f : stdin)) break; if (f) TT.lineno++; // Append to unused portion of previous line if any if (command) { command = xmprintf("%s%s", command, new); free(old); free(new); old = command; } else { free(old); old = new; } // returns 0 if line consumed, command if it needs more data command = parse_line(old, &scratch); } if (command) error_exit("unfinished line"); toys.exitval = f && ferror(f); }