aboutsummaryrefslogtreecommitdiff
path: root/toys/pending/sh.c
diff options
context:
space:
mode:
authorRob Landley <rob@landley.net>2020-11-16 05:37:29 -0600
committerRob Landley <rob@landley.net>2020-11-16 05:37:29 -0600
commit1866f9255eb3b1f3a6f4be1dd60461a050599d98 (patch)
treeac88e331134b3fadebcdb3f7e8e5116b39fed3f8 /toys/pending/sh.c
parenta24b186ea84ca5c65b367a17dfade57e9c6fd704 (diff)
downloadtoybox-1866f9255eb3b1f3a6f4be1dd60461a050599d98.tar.gz
Factor out do_source(), fpathopen(), and prompt_getline().
Use 0 optstr prefix, fix bug initializing toys.envc one too low.
Diffstat (limited to 'toys/pending/sh.c')
-rw-r--r--toys/pending/sh.c254
1 files changed, 126 insertions, 128 deletions
diff --git a/toys/pending/sh.c b/toys/pending/sh.c
index d3a9e59c..21143199 100644
--- a/toys/pending/sh.c
+++ b/toys/pending/sh.c
@@ -46,11 +46,11 @@ USE_SH(NEWTOY(exec, "^cla:", TOYFLAG_NOFORK))
USE_SH(NEWTOY(exit, 0, TOYFLAG_NOFORK))
USE_SH(NEWTOY(export, "np", TOYFLAG_NOFORK))
USE_SH(NEWTOY(shift, ">1", TOYFLAG_NOFORK))
-USE_SH(NEWTOY(source, "<1", TOYFLAG_NOFORK))
+USE_SH(NEWTOY(source, "0<1", TOYFLAG_NOFORK))
USE_SH(OLDTOY(., source, TOYFLAG_NOFORK))
USE_SH(NEWTOY(unset, "fvn", TOYFLAG_NOFORK))
-USE_SH(NEWTOY(sh, "(noediting)(noprofile)(norc)sc:i", TOYFLAG_BIN))
+USE_SH(NEWTOY(sh, "0(noediting)(noprofile)(norc)sc:i", TOYFLAG_BIN))
USE_SH(OLDTOY(toysh, sh, TOYFLAG_BIN))
USE_SH(OLDTOY(bash, sh, TOYFLAG_BIN))
// Login lies in argv[0], so add some aliases to catch that
@@ -237,17 +237,14 @@ static int sh_run(char *new);
static const char *redirectors[] = {"<<<", "<<-", "<<", "<&", "<>", "<", ">>",
">&", ">|", ">", "&>>", "&>", 0};
-#define OPT_I 1
-#define OPT_BRACE 2 // set -B
-#define OPT_NOCLOBBER 4 // set -C
-#define OPT_S 8
-#define OPT_C 16
+#define OPT_BRACE 0x100 // set -B
+#define OPT_NOCLOBBER 0x200 // set -C
static void syntax_err(char *s)
{
error_msg("syntax error: %s", s);
toys.exitval = 2;
- if (!(TT.options&OPT_I)) xexit();
+ if (!(TT.options&FLAG_i)) xexit();
}
// append to array with null terminator and realloc as necessary
@@ -759,10 +756,10 @@ char *getvar_special(char *str, int len, int *used, struct arg_list **delete)
*used = 1;
if (cc == '-') {
s = ss = xmalloc(8);
- if (TT.options&OPT_I) *ss++ = 'i';
+ if (TT.options&FLAG_i) *ss++ = 'i';
if (TT.options&OPT_BRACE) *ss++ = 'B';
- if (TT.options&OPT_S) *ss++ = 's';
- if (TT.options&OPT_C) *ss++ = 'c';
+ 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);
@@ -2286,6 +2283,8 @@ static int parse_line(char *line, struct sh_function *sp)
// Parse next word and detect overflow (too many nested quotes).
if ((end = parse_word(start, 0, 0)) == (void *)1) goto flush;
+// dprintf(2, "word[%ld]=%.*s (%s)\n", end ? end-start : 0, (int)(end ? end-start : 0), start, ex);
+
// Is this a new pipeline segment?
if (!pl) pl = add_pl(sp, &arg);
@@ -2934,7 +2933,7 @@ dprintf(2, "TODO skipped init for((;;)), need math parser\n");
else if (!strcmp(ss, "select")) {
do_prompt(getvar("PS3"));
// TODO: ctrl-c not breaking out of this?
- if (!(ss = xgetline(stdin, 0))) {
+ if (!(ss = xgetline(stdin))) {
pl = pop_block(&blk, pipes);
printf("\n");
} else if (!*ss) {
@@ -3005,6 +3004,7 @@ static int sh_run(char *new)
memset(&scratch, 0, sizeof(struct sh_function));
if (!parse_line(new, &scratch)) run_function(scratch.pipeline);
+// TODO else error?
free_function(&scratch);
return toys.exitval;
@@ -3054,6 +3054,90 @@ static void unexport(char *str)
if (strchr(str, '=')) setvar(str);
}
+FILE *fpathopen(char *name)
+{
+ struct string_list *sl = 0;
+ FILE *f = fopen(name, "r");
+ char *pp = getvar("PATH") ? : _PATH_DEFPATH;
+
+ if (!f) {
+ for (sl = find_in_path(pp, name); sl; free(llist_pop(&sl)))
+ if ((f = fopen(sl->str, "r"))) break;
+ if (sl) llist_traverse(sl, free);
+ }
+
+ return f;
+}
+
+// get line with command history
+char *prompt_getline(FILE *ff, int prompt)
+{
+ char *new, ps[16];
+
+// TODO line editing/history, should set $COLUMNS $LINES and sigwinch update
+ errno = 0;
+ if (!ff && prompt) {
+ sprintf(ps, "PS%d", prompt);
+ do_prompt(getvar(ps));
+ }
+ do if ((new = xgetline(ff ? : stdin))) return new;
+ while (errno == EINTR);
+// TODO: after first EINTR returns closed?
+// TODO: ctrl-z during script read having already read partial line,
+// SIGSTOP and SIGTSTP need need SA_RESTART, but child proc should stop
+// TODO if (!isspace(*new)) add_to_history(line);
+
+ return 0;
+}
+
+// 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;
+ 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));
+
+ // TODO: factor out and combine with sh_main() plumbing?
+ do {
+ new = prompt_getline(ff, more+1);
+ if (!TT.lineno++ && new && !memcmp(new, "\177ELF", 4)) {
+ error_msg("'%s' is ELF", name);
+ free(new);
+
+ break;
+ }
+
+ // TODO: source <(echo 'echo hello\') vs source <(echo -n 'echo hello\')
+ // prints "hello" vs "hello\"
+
+ // returns 0 if line consumed, command if it needs more data
+ more = parse_line(new ? : " ", &scratch);
+ if (more==1) {
+ if (!new && !ff) syntax_err("unexpected end of file");
+ } else {
+ if (!more) run_function(scratch.pipeline);
+ free_function(&scratch);
+ more = 0;
+ }
+ free(new);
+ } while(new);
+
+ if (ff) fclose(ff);
+ TT.lineno = lineno;
+ TT.shift = shift;
+ TT.arg = old;
+
+ return more;
+}
+
// init locals, sanitize environment, handle nommu subshell handoff
static void subshell_setup(void)
{
@@ -3064,7 +3148,6 @@ static void subshell_setup(void)
xmprintf("PPID=%d", myppid)};
struct stat st;
struct utsname uu;
- FILE *fp;
// Initialize magic and read only local variables
srandom(TT.SECONDS = millitime());
@@ -3127,7 +3210,8 @@ static void subshell_setup(void)
}
if (!memcmp(s, "IFS=", 4)) TT.ifs = s+4;
}
- environ[toys.optc = to] = 0;
+ environ[to++] = 0;
+ toys.envc = to;
// set/update PWD
sh_run("cd .");
@@ -3162,52 +3246,21 @@ static void subshell_setup(void)
if (fstat(254, &st) || !S_ISFIFO(st.st_mode)) error_exit(0);
TT.pid = zpid;
fcntl(254, F_SETFD, FD_CLOEXEC);
- fp = fdopen(254, "r");
-
-// TODO This is not efficient, could array_add the local vars.
-// TODO implicit exec when possible
- while ((s = xgetline(fp, 0))) toys.exitval = sh_run(s);
- fclose(fp);
+ do_source("", fdopen(254, "r"));
xexit();
}
-FILE *fpathopen(char *name)
-{
- struct string_list *sl = 0;
- FILE *f = fopen(name, "r");
- char *pp = getvar("PATH") ? : _PATH_DEFPATH;
-
- if (!f) {
- for (sl = find_in_path(pp, *toys.optargs); sl; free(llist_pop(&sl)))
- if ((f = fopen(sl->str, "r"))) break;
- if (sl) llist_traverse(sl, free);
- }
-
- return f;
-}
-
void sh_main(void)
{
- char *new, *cc = 0;
- struct sh_function scratch;
- int prompt = 0;
- struct string_list *sl = 0;
- struct sh_arg arg;
- FILE *f;
+ char *cc = 0;
+ FILE *ff;
signal(SIGPIPE, SIG_IGN);
TT.options = OPT_BRACE;
TT.pid = getpid();
TT.SECONDS = time(0);
- TT.arg = &arg;
- if (!(arg.c = toys.optc)) {
- arg.v = xmalloc(2*sizeof(char *));
- arg.v[arg.c++] = *toys.argv;
- arg.v[arg.c] = 0;
- } else memcpy(arg.v = xmalloc((arg.c+1)*sizeof(char *)), toys.optargs,
- (arg.c+1)*sizeof(char *));
// TODO euid stuff?
// TODO login shell?
@@ -3215,20 +3268,25 @@ void sh_main(void)
// if (!FLAG(noprofile)) { }
- // Is this an interactive shell?
+ // If not reentering, figure out if this is an interactive shell.
if (toys.stacktop) {
cc = TT.sh.c;
- if (FLAG(s) || (!FLAG(c) && !toys.optc)) TT.options |= OPT_S;
- if (FLAG(i) || (!FLAG(c) && (TT.options&OPT_S) && isatty(0)))
- TT.options |= OPT_I;
- if (FLAG(c)) TT.options |= OPT_C;
+ if (!FLAG(c)) {
+ if (toys.optc==1) toys.optflags |= FLAG_s;
+ if (FLAG(s) && isatty(0)) toys.optflags |= FLAG_i;
+ } else if (toys.optc>1) {
+ toys.optargs++;
+ toys.optc--;
+ }
+ TT.options |= toys.optflags&0xff;
}
// Read environment for exports from parent shell. Note, calls run_sh()
// which blanks argument sections of TT and this, so parse everything
// we need from shell command line before that.
subshell_setup();
- if (TT.options&OPT_I) {
+
+ if (TT.options&FLAG_i) {
if (!getvar("PS1")) setvarval("PS1", getpid() ? "\\$ " : "# ");
// TODO Set up signal handlers and grab control of this tty.
// ^C SIGINT ^\ SIGQUIT ^Z SIGTSTP SIGTTIN SIGTTOU SIGCHLD
@@ -3236,50 +3294,17 @@ void sh_main(void)
xsignal(SIGINT, SIG_IGN);
}
- memset(&scratch, 0, sizeof(scratch));
-
// TODO unify fmemopen() here with sh_run
- if (cc) f = fmemopen(cc, strlen(cc), "r");
- else if (TT.options&OPT_S) f = stdin;
- else if (!(f = fpathopen(*toys.optargs))) perror_exit_raw(*toys.optargs);
-
- // Loop prompting and reading lines
- for (;;) {
- TT.lineno++;
- if ((TT.options&(OPT_I|OPT_S|OPT_C)) == (OPT_I|OPT_S))
- do_prompt(getvar(prompt ? "PS2" : "PS1"));
-
-// TODO line editing/history, should set $COLUMNS $LINES and sigwinch update
- if (!(new = xgetline(f, 0))) {
-// TODO: after first EINTR getline returns always closed?
- if (errno != EINTR) break;
- free_function(&scratch);
- prompt = 0;
- if (f != stdin) break;
- continue;
-// TODO: ctrl-z during script read having already read partial line,
-// SIGSTOP and SIGTSTP need need SA_RESTART, but child proc should stop
- }
+ if (cc) ff = fmemopen(cc, strlen(cc), "r");
+ else if (TT.options&FLAG_s) ff = (TT.options&FLAG_i) ? 0 : stdin;
+ else if (!(ff = fpathopen(*toys.optargs))) perror_exit_raw(*toys.optargs);
- if (sl) {
- if (*new == 0x7f) error_exit("'%s' is ELF", sl->str);
- free(sl);
- sl = 0;
- }
-// TODO if (!isspace(*new)) add_to_history(line);
+ // Read and execute lines from file
+ if (do_source(cc ? : *toys.optargs, ff))
+ error_exit("%ld:unfinished line"+4*!TT.lineno, TT.lineno);
+}
- // returns 0 if line consumed, command if it needs more data
- if (1 != (prompt = parse_line(new, &scratch))) {
// TODO: ./blah.sh one two three: put one two three in scratch.arg
- if (!prompt) run_function(scratch.pipeline);
- free_function(&scratch);
- prompt = 0;
- }
- free(new);
- }
-
- if (prompt) error_exit("%ld:unfinished line"+4*!TT.lineno, TT.lineno);
-}
/********************* shell builtin functions *************************/
@@ -3541,41 +3566,14 @@ void shift_main(void)
void source_main(void)
{
- struct sh_function scratch;
- FILE *ff = fpathopen(*toys.optargs);
- long lineno = TT.lineno, shift = TT.shift, prompt;
- struct sh_arg arg, *old = TT.arg;
- char *new;
+ char *name = *toys.optargs;
+ FILE *ff = fpathopen(name);
- if (!ff) return perror_msg_raw(*toys.optargs);
+ if (!ff) return perror_msg_raw(name);
- arg.c = toys.optc;
- arg.v = toys.optargs;
- TT.arg = &arg;
- memset(&scratch, 0, sizeof(scratch));
+ // $0 is shell name, not source file name while running this
+// TODO add tests: sh -c "source input four five" one two three
+ *toys.optargs = *toys.argv;
- // TODO: factor out and combine with sh_main() plumbing?
- for (TT.lineno = TT.shift = 0;;) {
- new = xgetline(ff, 0);
- if (!TT.lineno++ && new && *new == 0x7f) {
- error_msg("'%s' is ELF", *toys.optargs);
- free(new);
-
- break;
- }
- if (1!=(prompt = parse_line(new ? : " ", &scratch))) {
- if (!prompt) run_function(scratch.pipeline);
- free_function(&scratch);
- if (!new) {
- if (prompt) syntax_err("unexpected end of file");
-
- break;
- }
- }
- free(new);
- }
- fclose(ff);
- TT.lineno = lineno;
- TT.shift = shift;
- TT.arg = old;
+ do_source(name, ff);
}