blob: e8a11ce7aff679c079187ef23e4e62a70f611d20 [file] [log] [blame]
/* sh.c - toybox shell
*
* Copyright 2006 Rob Landley <rob@landley.net>
*
* 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; i<arg->c; 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->c<i+3 || !strcmp(arg->v[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; i<arg->c; 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);
}