From 73b16af8feec390afbabd9356d6e5e83c0390838 Mon Sep 17 00:00:00 2001 From: Bjørn Mork Date: Fri, 15 May 2015 10:20:47 +0200 Subject: busybox: imported from http://www.busybox.net/downloads/busybox-1.13.3.tar.bz2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Bjørn Mork --- shell/Config.in | 344 + shell/Kbuild | 11 + shell/README | 108 + shell/README.job | 304 + shell/ash.c | 13762 +++++++++++++++++++ shell/ash_doc.txt | 31 + shell/ash_ptr_hack.c | 29 + shell/ash_test/ash-alias/alias.right | 4 + shell/ash_test/ash-alias/alias.tests | 37 + shell/ash_test/ash-arith/README.ash | 1 + shell/ash_test/ash-arith/arith-bash1.right | 2 + shell/ash_test/ash-arith/arith-bash1.tests | 5 + shell/ash_test/ash-arith/arith-for.right | 74 + shell/ash_test/ash-arith/arith-for.testsx | 94 + shell/ash_test/ash-arith/arith.right | 138 + shell/ash_test/ash-arith/arith.tests | 302 + shell/ash_test/ash-arith/arith1.sub | 40 + shell/ash_test/ash-arith/arith2.sub | 57 + shell/ash_test/ash-heredoc/heredoc.right | 21 + shell/ash_test/ash-heredoc/heredoc.tests | 94 + shell/ash_test/ash-invert/invert.right | 10 + shell/ash_test/ash-invert/invert.tests | 19 + shell/ash_test/ash-misc/shift1.right | 9 + shell/ash_test/ash-misc/shift1.tests | 10 + .../ash_test/ash-quoting/dollar_squote_bash1.right | 9 + .../ash_test/ash-quoting/dollar_squote_bash1.tests | 7 + shell/ash_test/ash-read/read_n.right | 3 + shell/ash_test/ash-read/read_n.tests | 3 + shell/ash_test/ash-read/read_r.right | 2 + shell/ash_test/ash-read/read_r.tests | 2 + shell/ash_test/ash-read/read_t.right | 4 + shell/ash_test/ash-read/read_t.tests | 10 + shell/ash_test/ash-redir/redir.right | 1 + shell/ash_test/ash-redir/redir.tests | 6 + shell/ash_test/ash-redir/redir2.right | 1 + shell/ash_test/ash-redir/redir2.tests | 5 + shell/ash_test/ash-redir/redir3.right | 3 + shell/ash_test/ash-redir/redir3.tests | 5 + shell/ash_test/ash-redir/redir4.right | 1 + shell/ash_test/ash-redir/redir4.tests | 72 + shell/ash_test/ash-redir/redir5.right | 2 + shell/ash_test/ash-redir/redir5.tests | 3 + shell/ash_test/ash-redir/redir6.right | 2 + shell/ash_test/ash-redir/redir6.tests | 3 + shell/ash_test/ash-signals/reap1.right | 1 + shell/ash_test/ash-signals/reap1.tests | 14 + shell/ash_test/ash-signals/signal1.right | 20 + shell/ash_test/ash-signals/signal1.tests | 23 + shell/ash_test/ash-signals/signal2.right | 3 + shell/ash_test/ash-signals/signal2.tests | 18 + shell/ash_test/ash-signals/signal3.right | 4 + shell/ash_test/ash-signals/signal3.tests | 17 + .../ash-standalone/noexec_gets_no_env.right | 4 + .../ash-standalone/noexec_gets_no_env.tests | 5 + .../ash-standalone/nofork_trashes_getopt.right | 1 + .../ash-standalone/nofork_trashes_getopt.tests | 6 + shell/ash_test/ash-vars/var1.right | 6 + shell/ash_test/ash-vars/var1.tests | 14 + shell/ash_test/ash-vars/var2.right | 1 + shell/ash_test/ash-vars/var2.tests | 1 + shell/ash_test/ash-vars/var_bash1.right | 14 + shell/ash_test/ash-vars/var_bash1.tests | 18 + shell/ash_test/ash-vars/var_bash2.right | 10 + shell/ash_test/ash-vars/var_bash2.tests | 24 + shell/ash_test/ash-vars/var_bash3.right | 20 + shell/ash_test/ash-vars/var_bash3.tests | 41 + shell/ash_test/ash-vars/var_leak.right | 2 + shell/ash_test/ash-vars/var_leak.tests | 9 + shell/ash_test/ash-vars/var_posix1.right | 17 + shell/ash_test/ash-vars/var_posix1.tests | 21 + shell/ash_test/printenv.c | 67 + shell/ash_test/recho.c | 63 + shell/ash_test/run-all | 74 + shell/ash_test/zecho.c | 39 + shell/bbsh.c | 223 + shell/cttyhack.c | 77 + shell/hush.c | 4749 +++++++ shell/hush_doc.txt | 143 + shell/hush_leaktool.sh | 13 + shell/hush_test/hush-glob/glob1.right | 2 + shell/hush_test/hush-glob/glob1.tests | 2 + shell/hush_test/hush-glob/glob_and_assign.right | 6 + shell/hush_test/hush-glob/glob_and_assign.tests | 10 + shell/hush_test/hush-glob/glob_redir.right | 2 + shell/hush_test/hush-glob/glob_redir.tests | 9 + shell/hush_test/hush-misc/assignment1.right | 9 + shell/hush_test/hush-misc/assignment1.tests | 42 + shell/hush_test/hush-misc/assignment2.rigth | 2 + shell/hush_test/hush-misc/assignment2.tests | 4 + shell/hush_test/hush-misc/break1.right | 2 + shell/hush_test/hush-misc/break1.tests | 3 + shell/hush_test/hush-misc/break2.right | 3 + shell/hush_test/hush-misc/break2.tests | 6 + shell/hush_test/hush-misc/break3.right | 2 + shell/hush_test/hush-misc/break3.tests | 2 + shell/hush_test/hush-misc/break4.right | 6 + shell/hush_test/hush-misc/break4.tests | 12 + shell/hush_test/hush-misc/break5.right | 13 + shell/hush_test/hush-misc/break5.tests | 4 + shell/hush_test/hush-misc/builtin1.right | 2 + shell/hush_test/hush-misc/builtin1.tests | 6 + shell/hush_test/hush-misc/case1.right | 14 + shell/hush_test/hush-misc/case1.tests | 25 + shell/hush_test/hush-misc/colon.right | 2 + shell/hush_test/hush-misc/colon.tests | 5 + shell/hush_test/hush-misc/continue1.right | 8 + shell/hush_test/hush-misc/continue1.tests | 4 + shell/hush_test/hush-misc/empty_for.right | 1 + shell/hush_test/hush-misc/empty_for.tests | 3 + shell/hush_test/hush-misc/empty_for2.right | 4 + shell/hush_test/hush-misc/empty_for2.tests | 6 + shell/hush_test/hush-misc/for_with_keywords.right | 4 + shell/hush_test/hush-misc/for_with_keywords.tests | 2 + shell/hush_test/hush-misc/pid.right | 1 + shell/hush_test/hush-misc/pid.tests | 1 + shell/hush_test/hush-misc/read.right | 4 + shell/hush_test/hush-misc/read.tests | 4 + shell/hush_test/hush-misc/shift.right | 6 + shell/hush_test/hush-misc/shift.tests | 14 + shell/hush_test/hush-misc/syntax_err.right | 2 + shell/hush_test/hush-misc/syntax_err.tests | 3 + shell/hush_test/hush-misc/syntax_err_negate.right | 2 + shell/hush_test/hush-misc/syntax_err_negate.tests | 2 + shell/hush_test/hush-misc/while1.right | 1 + shell/hush_test/hush-misc/while1.tests | 2 + shell/hush_test/hush-misc/while_in_subshell.right | 1 + shell/hush_test/hush-misc/while_in_subshell.tests | 2 + shell/hush_test/hush-parsing/argv0.right | 1 + shell/hush_test/hush-parsing/argv0.tests | 4 + shell/hush_test/hush-parsing/escape1.right | 4 + shell/hush_test/hush-parsing/escape1.tests | 4 + shell/hush_test/hush-parsing/escape2.right | 4 + shell/hush_test/hush-parsing/escape2.tests | 4 + shell/hush_test/hush-parsing/escape3.right | 23 + shell/hush_test/hush-parsing/escape3.tests | 8 + shell/hush_test/hush-parsing/negate.right | 35 + shell/hush_test/hush-parsing/negate.tests | 16 + shell/hush_test/hush-parsing/noeol.right | 1 + shell/hush_test/hush-parsing/noeol.tests | 2 + shell/hush_test/hush-parsing/noeol2.right | 1 + shell/hush_test/hush-parsing/noeol2.tests | 7 + shell/hush_test/hush-parsing/noeol3.right | 1 + shell/hush_test/hush-parsing/noeol3.tests | 2 + shell/hush_test/hush-parsing/process_subst.right | 3 + shell/hush_test/hush-parsing/process_subst.tests | 3 + shell/hush_test/hush-parsing/quote1.right | 1 + shell/hush_test/hush-parsing/quote1.tests | 2 + shell/hush_test/hush-parsing/quote2.right | 1 + shell/hush_test/hush-parsing/quote2.tests | 2 + shell/hush_test/hush-parsing/quote3.right | 12 + shell/hush_test/hush-parsing/quote3.tests | 21 + shell/hush_test/hush-parsing/quote4.right | 1 + shell/hush_test/hush-parsing/quote4.tests | 2 + shell/hush_test/hush-parsing/redir_space.right | 3 + shell/hush_test/hush-parsing/redir_space.tests | 6 + shell/hush_test/hush-parsing/starquoted.right | 8 + shell/hush_test/hush-parsing/starquoted.tests | 8 + shell/hush_test/hush-parsing/starquoted2.right | 2 + shell/hush_test/hush-parsing/starquoted2.tests | 14 + shell/hush_test/hush-psubst/tick.right | 2 + shell/hush_test/hush-psubst/tick.tests | 4 + shell/hush_test/hush-psubst/tick2.right | 1 + shell/hush_test/hush-psubst/tick2.tests | 5 + shell/hush_test/hush-psubst/tick3.right | 6 + shell/hush_test/hush-psubst/tick3.tests | 10 + shell/hush_test/hush-psubst/tick4.right | 7 + shell/hush_test/hush-psubst/tick4.tests | 7 + shell/hush_test/hush-vars/empty.right | 3 + shell/hush_test/hush-vars/empty.tests | 5 + shell/hush_test/hush-vars/glob_and_vars.right | 1 + shell/hush_test/hush-vars/glob_and_vars.tests | 2 + shell/hush_test/hush-vars/param_glob.right | 4 + shell/hush_test/hush-vars/param_glob.tests | 10 + shell/hush_test/hush-vars/star.right | 6 + shell/hush_test/hush-vars/star.tests | 8 + shell/hush_test/hush-vars/var1.right | 4 + shell/hush_test/hush-vars/var1.tests | 9 + shell/hush_test/hush-vars/var2.right | 2 + shell/hush_test/hush-vars/var2.tests | 4 + .../hush_test/hush-vars/var_expand_in_assign.right | 5 + .../hush_test/hush-vars/var_expand_in_assign.tests | 15 + .../hush_test/hush-vars/var_expand_in_redir.right | 3 + .../hush_test/hush-vars/var_expand_in_redir.tests | 13 + shell/hush_test/hush-vars/var_leaks.right | 1 + shell/hush_test/hush-vars/var_leaks.tests | 14 + shell/hush_test/hush-vars/var_preserved.right | 4 + shell/hush_test/hush-vars/var_preserved.tests | 16 + shell/hush_test/hush-vars/var_subst_in_for.right | 40 + shell/hush_test/hush-vars/var_subst_in_for.tests | 40 + shell/hush_test/hush-z_slow/leak_var.right | 2 + shell/hush_test/hush-z_slow/leak_var.tests | 138 + shell/hush_test/hush-z_slow/leak_var2.right | 2 + shell/hush_test/hush-z_slow/leak_var2.tests | 63 + shell/hush_test/run-all | 73 + shell/lash_unused.c | 1570 +++ shell/msh.c | 5336 +++++++ shell/msh_function.patch | 350 + shell/msh_test/msh-bugs/noeol3.right | 1 + shell/msh_test/msh-bugs/noeol3.tests | 2 + shell/msh_test/msh-bugs/process_subst.right | 3 + shell/msh_test/msh-bugs/process_subst.tests | 3 + shell/msh_test/msh-bugs/read.right | 4 + shell/msh_test/msh-bugs/read.tests | 4 + shell/msh_test/msh-bugs/shift.right | 6 + shell/msh_test/msh-bugs/shift.tests | 14 + shell/msh_test/msh-bugs/starquoted.right | 8 + shell/msh_test/msh-bugs/starquoted.tests | 8 + shell/msh_test/msh-bugs/syntax_err.right | 2 + shell/msh_test/msh-bugs/syntax_err.tests | 3 + shell/msh_test/msh-bugs/var_expand_in_assign.right | 5 + shell/msh_test/msh-bugs/var_expand_in_assign.tests | 15 + shell/msh_test/msh-bugs/var_expand_in_redir.right | 3 + shell/msh_test/msh-bugs/var_expand_in_redir.tests | 13 + shell/msh_test/msh-execution/exitcode_EACCES.right | 2 + shell/msh_test/msh-execution/exitcode_EACCES.tests | 2 + shell/msh_test/msh-execution/exitcode_ENOENT.right | 2 + shell/msh_test/msh-execution/exitcode_ENOENT.tests | 2 + shell/msh_test/msh-execution/many_continues.right | 1 + shell/msh_test/msh-execution/many_continues.tests | 15 + shell/msh_test/msh-execution/nested_break.right | 8 + shell/msh_test/msh-execution/nested_break.tests | 17 + shell/msh_test/msh-misc/tick.right | 2 + shell/msh_test/msh-misc/tick.tests | 4 + shell/msh_test/msh-parsing/argv0.right | 1 + shell/msh_test/msh-parsing/argv0.tests | 4 + shell/msh_test/msh-parsing/noeol.right | 1 + shell/msh_test/msh-parsing/noeol.tests | 2 + shell/msh_test/msh-parsing/noeol2.right | 1 + shell/msh_test/msh-parsing/noeol2.tests | 7 + shell/msh_test/msh-parsing/quote1.right | 1 + shell/msh_test/msh-parsing/quote1.tests | 2 + shell/msh_test/msh-parsing/quote2.right | 1 + shell/msh_test/msh-parsing/quote2.tests | 2 + shell/msh_test/msh-parsing/quote3.right | 3 + shell/msh_test/msh-parsing/quote3.tests | 8 + shell/msh_test/msh-parsing/quote4.right | 1 + shell/msh_test/msh-parsing/quote4.tests | 2 + shell/msh_test/msh-vars/star.right | 6 + shell/msh_test/msh-vars/star.tests | 8 + shell/msh_test/msh-vars/var.right | 4 + shell/msh_test/msh-vars/var.tests | 9 + shell/msh_test/msh-vars/var_subst_in_for.right | 40 + shell/msh_test/msh-vars/var_subst_in_for.tests | 40 + shell/msh_test/run-all | 64 + shell/susv3_doc.tar.bz2 | Bin 0 -> 71444 bytes 245 files changed, 30031 insertions(+) create mode 100644 shell/Config.in create mode 100644 shell/Kbuild create mode 100644 shell/README create mode 100644 shell/README.job create mode 100644 shell/ash.c create mode 100644 shell/ash_doc.txt create mode 100644 shell/ash_ptr_hack.c create mode 100644 shell/ash_test/ash-alias/alias.right create mode 100755 shell/ash_test/ash-alias/alias.tests create mode 100644 shell/ash_test/ash-arith/README.ash create mode 100644 shell/ash_test/ash-arith/arith-bash1.right create mode 100755 shell/ash_test/ash-arith/arith-bash1.tests create mode 100644 shell/ash_test/ash-arith/arith-for.right create mode 100755 shell/ash_test/ash-arith/arith-for.testsx create mode 100644 shell/ash_test/ash-arith/arith.right create mode 100755 shell/ash_test/ash-arith/arith.tests create mode 100755 shell/ash_test/ash-arith/arith1.sub create mode 100755 shell/ash_test/ash-arith/arith2.sub create mode 100644 shell/ash_test/ash-heredoc/heredoc.right create mode 100755 shell/ash_test/ash-heredoc/heredoc.tests create mode 100644 shell/ash_test/ash-invert/invert.right create mode 100755 shell/ash_test/ash-invert/invert.tests create mode 100644 shell/ash_test/ash-misc/shift1.right create mode 100755 shell/ash_test/ash-misc/shift1.tests create mode 100644 shell/ash_test/ash-quoting/dollar_squote_bash1.right create mode 100755 shell/ash_test/ash-quoting/dollar_squote_bash1.tests create mode 100644 shell/ash_test/ash-read/read_n.right create mode 100755 shell/ash_test/ash-read/read_n.tests create mode 100644 shell/ash_test/ash-read/read_r.right create mode 100755 shell/ash_test/ash-read/read_r.tests create mode 100644 shell/ash_test/ash-read/read_t.right create mode 100755 shell/ash_test/ash-read/read_t.tests create mode 100644 shell/ash_test/ash-redir/redir.right create mode 100755 shell/ash_test/ash-redir/redir.tests create mode 100644 shell/ash_test/ash-redir/redir2.right create mode 100755 shell/ash_test/ash-redir/redir2.tests create mode 100644 shell/ash_test/ash-redir/redir3.right create mode 100755 shell/ash_test/ash-redir/redir3.tests create mode 100644 shell/ash_test/ash-redir/redir4.right create mode 100755 shell/ash_test/ash-redir/redir4.tests create mode 100644 shell/ash_test/ash-redir/redir5.right create mode 100755 shell/ash_test/ash-redir/redir5.tests create mode 100644 shell/ash_test/ash-redir/redir6.right create mode 100755 shell/ash_test/ash-redir/redir6.tests create mode 100644 shell/ash_test/ash-signals/reap1.right create mode 100755 shell/ash_test/ash-signals/reap1.tests create mode 100644 shell/ash_test/ash-signals/signal1.right create mode 100755 shell/ash_test/ash-signals/signal1.tests create mode 100644 shell/ash_test/ash-signals/signal2.right create mode 100755 shell/ash_test/ash-signals/signal2.tests create mode 100644 shell/ash_test/ash-signals/signal3.right create mode 100755 shell/ash_test/ash-signals/signal3.tests create mode 100644 shell/ash_test/ash-standalone/noexec_gets_no_env.right create mode 100755 shell/ash_test/ash-standalone/noexec_gets_no_env.tests create mode 100644 shell/ash_test/ash-standalone/nofork_trashes_getopt.right create mode 100755 shell/ash_test/ash-standalone/nofork_trashes_getopt.tests create mode 100644 shell/ash_test/ash-vars/var1.right create mode 100755 shell/ash_test/ash-vars/var1.tests create mode 100644 shell/ash_test/ash-vars/var2.right create mode 100755 shell/ash_test/ash-vars/var2.tests create mode 100644 shell/ash_test/ash-vars/var_bash1.right create mode 100755 shell/ash_test/ash-vars/var_bash1.tests create mode 100644 shell/ash_test/ash-vars/var_bash2.right create mode 100755 shell/ash_test/ash-vars/var_bash2.tests create mode 100644 shell/ash_test/ash-vars/var_bash3.right create mode 100755 shell/ash_test/ash-vars/var_bash3.tests create mode 100644 shell/ash_test/ash-vars/var_leak.right create mode 100755 shell/ash_test/ash-vars/var_leak.tests create mode 100644 shell/ash_test/ash-vars/var_posix1.right create mode 100755 shell/ash_test/ash-vars/var_posix1.tests create mode 100644 shell/ash_test/printenv.c create mode 100644 shell/ash_test/recho.c create mode 100755 shell/ash_test/run-all create mode 100644 shell/ash_test/zecho.c create mode 100644 shell/bbsh.c create mode 100644 shell/cttyhack.c create mode 100644 shell/hush.c create mode 100644 shell/hush_doc.txt create mode 100644 shell/hush_leaktool.sh create mode 100644 shell/hush_test/hush-glob/glob1.right create mode 100755 shell/hush_test/hush-glob/glob1.tests create mode 100644 shell/hush_test/hush-glob/glob_and_assign.right create mode 100755 shell/hush_test/hush-glob/glob_and_assign.tests create mode 100644 shell/hush_test/hush-glob/glob_redir.right create mode 100755 shell/hush_test/hush-glob/glob_redir.tests create mode 100644 shell/hush_test/hush-misc/assignment1.right create mode 100755 shell/hush_test/hush-misc/assignment1.tests create mode 100644 shell/hush_test/hush-misc/assignment2.rigth create mode 100755 shell/hush_test/hush-misc/assignment2.tests create mode 100644 shell/hush_test/hush-misc/break1.right create mode 100755 shell/hush_test/hush-misc/break1.tests create mode 100644 shell/hush_test/hush-misc/break2.right create mode 100755 shell/hush_test/hush-misc/break2.tests create mode 100644 shell/hush_test/hush-misc/break3.right create mode 100755 shell/hush_test/hush-misc/break3.tests create mode 100644 shell/hush_test/hush-misc/break4.right create mode 100755 shell/hush_test/hush-misc/break4.tests create mode 100644 shell/hush_test/hush-misc/break5.right create mode 100755 shell/hush_test/hush-misc/break5.tests create mode 100644 shell/hush_test/hush-misc/builtin1.right create mode 100755 shell/hush_test/hush-misc/builtin1.tests create mode 100644 shell/hush_test/hush-misc/case1.right create mode 100755 shell/hush_test/hush-misc/case1.tests create mode 100644 shell/hush_test/hush-misc/colon.right create mode 100755 shell/hush_test/hush-misc/colon.tests create mode 100644 shell/hush_test/hush-misc/continue1.right create mode 100755 shell/hush_test/hush-misc/continue1.tests create mode 100644 shell/hush_test/hush-misc/empty_for.right create mode 100755 shell/hush_test/hush-misc/empty_for.tests create mode 100644 shell/hush_test/hush-misc/empty_for2.right create mode 100755 shell/hush_test/hush-misc/empty_for2.tests create mode 100644 shell/hush_test/hush-misc/for_with_keywords.right create mode 100755 shell/hush_test/hush-misc/for_with_keywords.tests create mode 100644 shell/hush_test/hush-misc/pid.right create mode 100755 shell/hush_test/hush-misc/pid.tests create mode 100644 shell/hush_test/hush-misc/read.right create mode 100755 shell/hush_test/hush-misc/read.tests create mode 100644 shell/hush_test/hush-misc/shift.right create mode 100755 shell/hush_test/hush-misc/shift.tests create mode 100644 shell/hush_test/hush-misc/syntax_err.right create mode 100755 shell/hush_test/hush-misc/syntax_err.tests create mode 100644 shell/hush_test/hush-misc/syntax_err_negate.right create mode 100755 shell/hush_test/hush-misc/syntax_err_negate.tests create mode 100644 shell/hush_test/hush-misc/while1.right create mode 100755 shell/hush_test/hush-misc/while1.tests create mode 100644 shell/hush_test/hush-misc/while_in_subshell.right create mode 100755 shell/hush_test/hush-misc/while_in_subshell.tests create mode 100644 shell/hush_test/hush-parsing/argv0.right create mode 100755 shell/hush_test/hush-parsing/argv0.tests create mode 100644 shell/hush_test/hush-parsing/escape1.right create mode 100755 shell/hush_test/hush-parsing/escape1.tests create mode 100644 shell/hush_test/hush-parsing/escape2.right create mode 100755 shell/hush_test/hush-parsing/escape2.tests create mode 100644 shell/hush_test/hush-parsing/escape3.right create mode 100755 shell/hush_test/hush-parsing/escape3.tests create mode 100644 shell/hush_test/hush-parsing/negate.right create mode 100755 shell/hush_test/hush-parsing/negate.tests create mode 100644 shell/hush_test/hush-parsing/noeol.right create mode 100755 shell/hush_test/hush-parsing/noeol.tests create mode 100644 shell/hush_test/hush-parsing/noeol2.right create mode 100755 shell/hush_test/hush-parsing/noeol2.tests create mode 100644 shell/hush_test/hush-parsing/noeol3.right create mode 100755 shell/hush_test/hush-parsing/noeol3.tests create mode 100644 shell/hush_test/hush-parsing/process_subst.right create mode 100755 shell/hush_test/hush-parsing/process_subst.tests create mode 100644 shell/hush_test/hush-parsing/quote1.right create mode 100755 shell/hush_test/hush-parsing/quote1.tests create mode 100644 shell/hush_test/hush-parsing/quote2.right create mode 100755 shell/hush_test/hush-parsing/quote2.tests create mode 100644 shell/hush_test/hush-parsing/quote3.right create mode 100755 shell/hush_test/hush-parsing/quote3.tests create mode 100644 shell/hush_test/hush-parsing/quote4.right create mode 100755 shell/hush_test/hush-parsing/quote4.tests create mode 100644 shell/hush_test/hush-parsing/redir_space.right create mode 100755 shell/hush_test/hush-parsing/redir_space.tests create mode 100644 shell/hush_test/hush-parsing/starquoted.right create mode 100755 shell/hush_test/hush-parsing/starquoted.tests create mode 100644 shell/hush_test/hush-parsing/starquoted2.right create mode 100755 shell/hush_test/hush-parsing/starquoted2.tests create mode 100644 shell/hush_test/hush-psubst/tick.right create mode 100755 shell/hush_test/hush-psubst/tick.tests create mode 100644 shell/hush_test/hush-psubst/tick2.right create mode 100755 shell/hush_test/hush-psubst/tick2.tests create mode 100644 shell/hush_test/hush-psubst/tick3.right create mode 100755 shell/hush_test/hush-psubst/tick3.tests create mode 100644 shell/hush_test/hush-psubst/tick4.right create mode 100755 shell/hush_test/hush-psubst/tick4.tests create mode 100644 shell/hush_test/hush-vars/empty.right create mode 100755 shell/hush_test/hush-vars/empty.tests create mode 100644 shell/hush_test/hush-vars/glob_and_vars.right create mode 100755 shell/hush_test/hush-vars/glob_and_vars.tests create mode 100644 shell/hush_test/hush-vars/param_glob.right create mode 100755 shell/hush_test/hush-vars/param_glob.tests create mode 100644 shell/hush_test/hush-vars/star.right create mode 100755 shell/hush_test/hush-vars/star.tests create mode 100644 shell/hush_test/hush-vars/var1.right create mode 100755 shell/hush_test/hush-vars/var1.tests create mode 100644 shell/hush_test/hush-vars/var2.right create mode 100755 shell/hush_test/hush-vars/var2.tests create mode 100644 shell/hush_test/hush-vars/var_expand_in_assign.right create mode 100755 shell/hush_test/hush-vars/var_expand_in_assign.tests create mode 100644 shell/hush_test/hush-vars/var_expand_in_redir.right create mode 100755 shell/hush_test/hush-vars/var_expand_in_redir.tests create mode 100644 shell/hush_test/hush-vars/var_leaks.right create mode 100755 shell/hush_test/hush-vars/var_leaks.tests create mode 100644 shell/hush_test/hush-vars/var_preserved.right create mode 100755 shell/hush_test/hush-vars/var_preserved.tests create mode 100644 shell/hush_test/hush-vars/var_subst_in_for.right create mode 100755 shell/hush_test/hush-vars/var_subst_in_for.tests create mode 100644 shell/hush_test/hush-z_slow/leak_var.right create mode 100755 shell/hush_test/hush-z_slow/leak_var.tests create mode 100644 shell/hush_test/hush-z_slow/leak_var2.right create mode 100755 shell/hush_test/hush-z_slow/leak_var2.tests create mode 100755 shell/hush_test/run-all create mode 100644 shell/lash_unused.c create mode 100644 shell/msh.c create mode 100644 shell/msh_function.patch create mode 100644 shell/msh_test/msh-bugs/noeol3.right create mode 100755 shell/msh_test/msh-bugs/noeol3.tests create mode 100644 shell/msh_test/msh-bugs/process_subst.right create mode 100755 shell/msh_test/msh-bugs/process_subst.tests create mode 100644 shell/msh_test/msh-bugs/read.right create mode 100755 shell/msh_test/msh-bugs/read.tests create mode 100644 shell/msh_test/msh-bugs/shift.right create mode 100755 shell/msh_test/msh-bugs/shift.tests create mode 100644 shell/msh_test/msh-bugs/starquoted.right create mode 100755 shell/msh_test/msh-bugs/starquoted.tests create mode 100644 shell/msh_test/msh-bugs/syntax_err.right create mode 100755 shell/msh_test/msh-bugs/syntax_err.tests create mode 100644 shell/msh_test/msh-bugs/var_expand_in_assign.right create mode 100755 shell/msh_test/msh-bugs/var_expand_in_assign.tests create mode 100644 shell/msh_test/msh-bugs/var_expand_in_redir.right create mode 100755 shell/msh_test/msh-bugs/var_expand_in_redir.tests create mode 100644 shell/msh_test/msh-execution/exitcode_EACCES.right create mode 100755 shell/msh_test/msh-execution/exitcode_EACCES.tests create mode 100644 shell/msh_test/msh-execution/exitcode_ENOENT.right create mode 100755 shell/msh_test/msh-execution/exitcode_ENOENT.tests create mode 100644 shell/msh_test/msh-execution/many_continues.right create mode 100755 shell/msh_test/msh-execution/many_continues.tests create mode 100644 shell/msh_test/msh-execution/nested_break.right create mode 100755 shell/msh_test/msh-execution/nested_break.tests create mode 100644 shell/msh_test/msh-misc/tick.right create mode 100755 shell/msh_test/msh-misc/tick.tests create mode 100644 shell/msh_test/msh-parsing/argv0.right create mode 100755 shell/msh_test/msh-parsing/argv0.tests create mode 100644 shell/msh_test/msh-parsing/noeol.right create mode 100755 shell/msh_test/msh-parsing/noeol.tests create mode 100644 shell/msh_test/msh-parsing/noeol2.right create mode 100755 shell/msh_test/msh-parsing/noeol2.tests create mode 100644 shell/msh_test/msh-parsing/quote1.right create mode 100755 shell/msh_test/msh-parsing/quote1.tests create mode 100644 shell/msh_test/msh-parsing/quote2.right create mode 100755 shell/msh_test/msh-parsing/quote2.tests create mode 100644 shell/msh_test/msh-parsing/quote3.right create mode 100755 shell/msh_test/msh-parsing/quote3.tests create mode 100644 shell/msh_test/msh-parsing/quote4.right create mode 100755 shell/msh_test/msh-parsing/quote4.tests create mode 100644 shell/msh_test/msh-vars/star.right create mode 100755 shell/msh_test/msh-vars/star.tests create mode 100644 shell/msh_test/msh-vars/var.right create mode 100755 shell/msh_test/msh-vars/var.tests create mode 100644 shell/msh_test/msh-vars/var_subst_in_for.right create mode 100755 shell/msh_test/msh-vars/var_subst_in_for.tests create mode 100755 shell/msh_test/run-all create mode 100644 shell/susv3_doc.tar.bz2 (limited to 'shell') diff --git a/shell/Config.in b/shell/Config.in new file mode 100644 index 0000000..e064450 --- /dev/null +++ b/shell/Config.in @@ -0,0 +1,344 @@ +# +# For a description of the syntax of this configuration file, +# see scripts/kbuild/config-language.txt. +# + +menu "Shells" + +choice + prompt "Choose your default shell" + default FEATURE_SH_IS_NONE + help + Choose a shell. The ash shell is the most bash compatible + and full featured one. + +config FEATURE_SH_IS_ASH + select ASH + bool "ash" + +config FEATURE_SH_IS_HUSH + select HUSH + bool "hush" + +####config FEATURE_SH_IS_LASH +#### select LASH +#### bool "lash" + +config FEATURE_SH_IS_MSH + select MSH + bool "msh" + +config FEATURE_SH_IS_NONE + bool "none" + +endchoice + +config ASH + bool "ash" + default n + help + Tha 'ash' shell adds about 60k in the default configuration and is + the most complete and most pedantically correct shell included with + busybox. This shell is actually a derivative of the Debian 'dash' + shell (by Herbert Xu), which was created by porting the 'ash' shell + (written by Kenneth Almquist) from NetBSD. + +comment "Ash Shell Options" + depends on ASH + +config ASH_BASH_COMPAT + bool "bash-compatible extensions" + default y + depends on ASH + help + Enable bash-compatible extensions. + +config ASH_JOB_CONTROL + bool "Job control" + default y + depends on ASH + help + Enable job control in the ash shell. + +config ASH_READ_NCHARS + bool "'read -n N' and 'read -s' support" + default n + depends on ASH + help + 'read -n N' will return a value after N characters have been read. + 'read -s' will read without echoing the user's input. + +config ASH_READ_TIMEOUT + bool "'read -t S' support" + default n + depends on ASH + help + 'read -t S' will return a value after S seconds have passed. + This implementation will allow fractional seconds, expressed + as a decimal fraction, e.g. 'read -t 2.5 foo'. + +config ASH_ALIAS + bool "alias support" + default y + depends on ASH + help + Enable alias support in the ash shell. + +config ASH_MATH_SUPPORT + bool "Posix math support" + default y + depends on ASH + help + Enable math support in the ash shell. + +config ASH_MATH_SUPPORT_64 + bool "Extend Posix math support to 64 bit" + default n + depends on ASH_MATH_SUPPORT + help + Enable 64-bit math support in the ash shell. This will make + the shell slightly larger, but will allow computation with very + large numbers. + +config ASH_GETOPTS + bool "Builtin getopt to parse positional parameters" + default n + depends on ASH + help + Enable getopts builtin in the ash shell. + +config ASH_BUILTIN_ECHO + bool "Builtin version of 'echo'" + default y + depends on ASH + help + Enable support for echo, builtin to ash. + +config ASH_BUILTIN_PRINTF + bool "Builtin version of 'printf'" + default y + depends on ASH + help + Enable support for printf, builtin to ash. + +config ASH_BUILTIN_TEST + bool "Builtin version of 'test'" + default y + depends on ASH + help + Enable support for test, builtin to ash. + +config ASH_CMDCMD + bool "'command' command to override shell builtins" + default n + depends on ASH + help + Enable support for the ash 'command' builtin, which allows + you to run the specified command with the specified arguments, + even when there is an ash builtin command with the same name. + +config ASH_MAIL + bool "Check for new mail on interactive shells" + default y + depends on ASH + help + Enable "check for new mail" in the ash shell. + +config ASH_OPTIMIZE_FOR_SIZE + bool "Optimize for size instead of speed" + default y + depends on ASH + help + Compile ash for reduced size at the price of speed. + +config ASH_RANDOM_SUPPORT + bool "Pseudorandom generator and variable $RANDOM" + default n + depends on ASH + help + Enable pseudorandom generator and dynamic variable "$RANDOM". + Each read of "$RANDOM" will generate a new pseudorandom value. + You can reset the generator by using a specified start value. + After "unset RANDOM" then generator will switch off and this + variable will no longer have special treatment. + +config ASH_EXPAND_PRMT + bool "Expand prompt string" + default n + depends on ASH + help + "PS#" may be contain volatile content, such as backquote commands. + This option recreates the prompt string from the environment + variable each time it is displayed. + +config HUSH + bool "hush" + default n + help + hush is a very small shell (just 18k) and it has fairly complete + Bourne shell grammar. It even handles all the normal flow control + options such as if/then/elif/else/fi, for/in/do/done, while loops, + case/esac. + + It uses only vfork, so it can be used on uClinux systems. + + It does not handle select, functions, here documents ( << + word ), arithmetic expansion, aliases, brace expansion, tilde + expansion, &> and >& redirection of stdout+stderr, etc. + +config HUSH_HELP + bool "help builtin" + default n + depends on HUSH + help + Enable help builtin in hush. Code size + ~1 kbyte. + +config HUSH_INTERACTIVE + bool "Interactive mode" + default y + depends on HUSH + help + Enable interactive mode (prompt and command editing). + Without this, hush simply reads and executes commands + from stdin just like a shell script from the file. + No prompt, no PS1/PS2 magic shell variables. + +config HUSH_JOB + bool "Job control" + default n + depends on HUSH_INTERACTIVE + help + Enable job control: Ctrl-Z backgrounds, Ctrl-C interrupts current + command (not entire shell), fg/bg builtins work. Without this option, + "cmd &" still works by simply spawning a process and immediately + prompting for next command (or executing next command in a script), + but no separate process group is formed. + +config HUSH_TICK + bool "Process substitution" + default n + depends on HUSH + help + Enable process substitution `command` and $(command) in hush. + +config HUSH_IF + bool "Support if/then/elif/else/fi" + default n + depends on HUSH + help + Enable if/then/elif/else/fi in hush. + +config HUSH_LOOPS + bool "Support for, while and until loops" + default n + depends on HUSH + help + Enable for, while and until loops in hush. + As of 2008-07, break and continue statements are not supported. + +config HUSH_CASE + bool "Support case ... esac statement" + default n + depends on HUSH + help + Enable case ... esac statement in hush. +400 bytes. + +config LASH + bool "lash" + default n + select HUSH + help + lash is deprecated and will be removed, please migrate to hush. + +config MSH + bool "msh" + default n + help + The minix shell (adds just 30k) is quite complete and handles things + like for/do/done, case/esac and all the things you expect a Bourne + shell to do. It is not always pedantically correct about Bourne + shell grammar (try running the shell testscript "tests/sh.testcases" + on it and compare vs bash) but for most things it works quite well. + It uses only vfork, so it can be used on uClinux systems. + +comment "Bourne Shell Options" + depends on MSH || LASH || HUSH || ASH + +config FEATURE_SH_EXTRA_QUIET + bool "Hide message on interactive shell startup" + default n + depends on MSH || LASH || HUSH || ASH + help + Remove the busybox introduction when starting a shell. + +config FEATURE_SH_STANDALONE + bool "Standalone shell" + default n + depends on (MSH || LASH || HUSH || ASH) && FEATURE_PREFER_APPLETS + help + This option causes busybox shells to use busybox applets + in preference to executables in the PATH whenever possible. For + example, entering the command 'ifconfig' into the shell would cause + busybox to use the ifconfig busybox applet. Specifying the fully + qualified executable name, such as '/sbin/ifconfig' will still + execute the /sbin/ifconfig executable on the filesystem. This option + is generally used when creating a statically linked version of busybox + for use as a rescue shell, in the event that you screw up your system. + + This is implemented by re-execing /proc/self/exe (typically) + with right parameters. Some selected applets ("NOFORK" applets) + can even be executed without creating new process. + Instead, busybox will call _main() internally. + + However, this causes problems in chroot jails without mounted /proc + and with ps/top (command name can be shown as 'exe' for applets + started this way). +# untrue? +# Note that this will *also* cause applets to take precedence +# over shell builtins of the same name. So turning this on will +# eliminate any performance gained by turning on the builtin "echo" +# and "test" commands in ash. +# untrue? +# Note that when using this option, the shell will attempt to directly +# run '/bin/busybox'. If you do not have the busybox binary sitting in +# that exact location with that exact name, this option will not work at +# all. + +config FEATURE_SH_NOFORK + bool "Run 'nofork' applets directly" + default n + depends on (MSH || LASH || HUSH || ASH) && FEATURE_PREFER_APPLETS + help + This option causes busybox shells [currently only ash] + to not execute typical fork/exec/wait sequence, but call _main + directly, if possible. (Sometimes it is not possible: for example, + this is not possible in pipes). + + This will be done only for some applets (those which are marked + NOFORK in include/applets.h). + + This may significantly speed up some shell scripts. + + This feature is relatively new. Use with care. + +config CTTYHACK + bool "cttyhack" + default n + help + One common problem reported on the mailing list is "can't access tty; + job control turned off" error message which typically appears when + one tries to use shell with stdin/stdout opened to /dev/console. + This device is special - it cannot be a controlling tty. + + Proper solution is to use correct device instead of /dev/console. + + cttyhack provides "quick and dirty" solution to this problem. + It analyzes stdin with various ioctls, trying to determine whether + it is a /dev/ttyN or /dev/ttySN (virtual terminal or serial line). + If it detects one, it closes stdin/out/err and reopens that device. + Then it executes given program. Usage example for /etc/inittab + (for busybox init): + + ::respawn:/bin/cttyhack /bin/sh + +endmenu diff --git a/shell/Kbuild b/shell/Kbuild new file mode 100644 index 0000000..deedc24 --- /dev/null +++ b/shell/Kbuild @@ -0,0 +1,11 @@ +# Makefile for busybox +# +# Copyright (C) 1999-2005 by Erik Andersen +# +# Licensed under the GPL v2, see the file LICENSE in this tarball. + +lib-y:= +lib-$(CONFIG_ASH) += ash.o ash_ptr_hack.o +lib-$(CONFIG_HUSH) += hush.o +lib-$(CONFIG_MSH) += msh.o +lib-$(CONFIG_CTTYHACK) += cttyhack.o diff --git a/shell/README b/shell/README new file mode 100644 index 0000000..59efe49 --- /dev/null +++ b/shell/README @@ -0,0 +1,108 @@ +Various bits of what is known about busybox shells, in no particular order. + +2008-02-14 +ash: does not restore tty pgrp if killed by HUP. Symptom: Midnight Commander +is backgrounded if you started ash under it, and then killed it with HUP. + +2007-11-23 +hush: fixed bogus glob handling; fixed exec <"$1"; added test and echo builtins + +2007-06-13 +hush: exec <"$1" doesn't do parameter subst + +2007-05-24 +hush: environment-related memory leak plugged, with net code size +decrease. + +2007-05-24 +hush: '( echo ${name )' will show syntax error message, but prompt +doesn't return (need to press ). Pressing Ctrl-C, , +'( echo ${name )' again, Ctrl-C segfaults. + +2007-05-21 +hush: environment cannot be handled by libc routines as they are leaky +(by API design and thus unfixable): hush will leak memory in this script, +bash does not: +pid=$$ +while true; do + unset t; + t=111111111111111111111111111111111111111111111111111111111111111111111111 + export t + ps -o vsz,pid,comm | grep " $pid " +done +The fix is to not use setenv/putenv/unsetenv but manipulate env ourself. TODO. +hush: meanwhile, first three command subst bugs mentioned below are fixed. :) + +2007-05-06 +hush: more bugs spotted. Comparison with bash: +bash-3.2# echo "TEST`date;echo;echo`BEST" +TESTSun May 6 09:21:05 CEST 2007BEST [we dont strip eols] +bash-3.2# echo "TEST`echo '$(echo ZZ)'`BEST" +TEST$(echo ZZ)BEST [we execute inner echo] +bash-3.2# echo "TEST`echo "'"`BEST" +TEST'BEST [we totally mess up this one] +bash-3.2# echo `sleep 5` +[Ctrl-C should work, Ctrl-Z should do nothing][we totally mess up this one] +bash-3.2# if true; then +> [Ctrl-C] +bash-3.2# [we re-issue "> "] +bash-3.2# if echo `sleep 5`; then +> true; fi [we execute sleep before "> "] + +2007-05-04 +hush: made ctrl-Z/C work correctly for "while true; do true; done" +(namely, it backgrounds/interrupts entire "while") + +2007-05-03 +hush: new bug spotted: Ctrl-C on "while true; do true; done" doesn't +work right: +# while true; do true; done +[1] 0 true <-- pressing Ctrl-C several times... +[2] 0 true +[3] 0 true +Segmentation fault + +2007-05-03 +hush: update on "sleep 1 | exit 3; echo $?" bug. +parse_stream_outer() repeatedly calls parse_stream(). +parse_stream() is now fixed to stop on ';' in this example, +fixing it (parse_stream_outer() will call parse_stream() 1st time, +execute the parse tree, call parse_stream() 2nd time and execute the tree). +But it's not the end of story. +In more complex situations we _must_ parse way farther before executing. +Example #2: "{ sleep 1 | exit 3; echo $?; ...few_lines... } >file". +Because of redirection, we cannot execute 1st pipe before we parse it all. +We probably need to learn to store $var expressions in parse tree. +Debug printing of parse tree would be nice too. + +2007-04-28 +hush: Ctrl-C and Ctrl-Z for single NOFORK commands are working. +Memory and other resource leaks (opendir) are not addressed +(testcase is "rm -i" interrupted by ctrl-c). + +2007-04-21 +hush: "sleep 5 | sleep 6" + Ctrl-Z + fg seems to work. +"rm -i" + Ctrl-C, "sleep 5" + Ctrl-Z still doesn't work +for SH_STANDALONE case :( + +2007-04-21 +hush: fixed non-backgrounding of "sleep 1 &" and totally broken +"sleep 1 | sleep 2 &". Noticed a bug where successive jobs +get numbers 1,2,3 even when job #1 has exited before job# 2 is started. +(bash reuses #1 in this case) + +2007-04-21 +hush: "sleep 1 | exit 3; echo $?" prints 0 because $? is substituted +_before_ pipe gets executed!! run_list_real() already has "pipe;echo" +parsed and handed to it for execution, so it sees "pipe"; "echo 0". + +2007-04-21 +hush: removed setsid() and made job control sort-of-sometimes-work. +Ctrl-C in "rm -i" works now except for SH_STANDALONE case. +"sleep 1 | exit 3" + "echo $?" works, "sleep 1 | exit 3; echo $?" +shows exitcode 0 (should be 3). "sleep 1 | sleep 2 &" fails horribly. + +2007-04-14 +lash, hush: both do setsid() and as a result don't have ctty! +Ctrl-C doesn't work for any child (try rm -i), etc... +lash: bare ">file" doesn't create a file (hush works) diff --git a/shell/README.job b/shell/README.job new file mode 100644 index 0000000..d5ba965 --- /dev/null +++ b/shell/README.job @@ -0,0 +1,304 @@ +strace of "sleep 1 | sleep 2" being run from interactive bash 3.0 + + +Synopsis: +open /dev/tty [, if fails, open ttyname(0)]; close /* helps re-establish ctty */ +get current signal mask +TCGETS on fd# 0 +TCGETS on fd# 2 /* NB: if returns ENOTTY (2>/dev/null), sh seems to disable job control, + does not show prompt, but still executes cmds from fd# 0 */ +install default handlers for CHLD QUIT TERM +install common handler for HUP INT ILL TRAP ABRT FPE BUS SEGV SYS PIPE ALRM TERM XCPU XFSZ VTALRM USR1 USR2 +ignore QUIT +install handler for INT +ignore TERM +install handler for INT +ignore TSTP TTOU TTIN +install handler for WINCH +get pid, ppid +block all signals +unblock all signals +get our pprocess group + minidoc: + Each process group is a member of a session and each process is a member + of the session of which its process group is a member. + Process groups are used for distribution of signals, and by terminals + to arbitrate requests for their input: processes that have the same + process group as the terminal are foreground and may read, while others + will block with a signal if they attempt to read. These calls are thus used + by programs (shells) to create process groups in implementing job control. + The TIOCGPGRP and TIOCSPGRP calls described in termios(3) are used to get/set + the process group of the control terminal. + If a session has a controlling terminal, CLOCAL is not set and a hangup occurs, + then the session leader is sent a SIGHUP. If the session leader exits, + the SIGHUP signal will be sent to each process in the foreground process + group of the controlling terminal. + If the exit of the process causes a process group to become orphaned, + and if any member of the newly-orphaned process group is stopped, then a SIGHUP + signal followed by a SIGCONT signal will be sent to each process + in the newly-orphaned process group. +... +dup stderr to fd# 255 +move ourself to our own process group +block CHLD TSTP TTIN TTOU +set tty's (255, stderr's) foreground process group to our group +allow all signals +mark 255 CLOEXEC +set CHLD handler +get signal mask +get fd#0 flags +get signal mask +set INT handler +block CHLD TSTP TTIN TTOU +set fd #255 foreground process group to our group +allow all signals +set INT handler +block all signals +allow all signals +block INT +allow all signals +lotsa sigactions: set INT,ALRM,WINCH handlers, ignore TERM,QUIT,TSTP,TTOU,TTIN +block all signals +allow all signals +block all signals +allow all signals +block all signals +allow all signals +read "sleep 1 | sleep 2\n" +block INT +TCSETSW on fd# 0 +allow all signals +lotsa sigactions: set INT,ALRM,WINCH handlers, ignore TERM,QUIT,TSTP,TTOU,TTIN +block CHLD +pipe([4, 5]) /* oops seems I lost another pipe() in editing... */ +fork child #1 +put child in it's own process group +block only CHLD +close(5) +block only INT CHLD +fork child #2 +put child in the same process group as first one +block only CHLD +close(4) +block only CHLD +block only CHLD TSTP TTIN TTOU +set fd# 255 foreground process group to first child's one +block only CHLD +block only CHLD +block only CHLD +/* note: because shell is not in foreground now, e.g. Ctrl-C will send INT to children only! */ +wait4 for children to die or stop - first child exits +wait4 for children to die or stop - second child exits +block CHLD TSTP TTIN TTOU +set fd# 255 foreground process group to our own one +block only CHLD +block only CHLD +block nothing +--- SIGCHLD (Child exited) @ 0 (0) --- + wait for it - no child (already waited for) + sigreturn() +read signal mask +lotsa sigactions... +read next command + + +execve("/bin/sh", ["sh"], [/* 34 vars */]) = 0 +rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0 +ioctl(0, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +ioctl(2, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 +rt_sigaction(SIGCHLD, {SIG_DFL}, {SIG_DFL}, 8) = 0 +rt_sigaction(SIGQUIT, {SIG_DFL}, {SIG_DFL}, 8) = 0 +rt_sigaction(SIGTERM, {SIG_DFL}, {SIG_DFL}, 8) = 0 +rt_sigaction(SIGHUP, {0x808f752, [HUP INT ILL TRAP ABRT BUS FPE USR1 SEGV USR2 PIPE ALRM TERM XCPU XFSZ VTALRM SYS], SA_RESTORER, 0x6ff7a4f8}, {SIG_DFL}, 8) = 0 +rt_sigaction(SIGINT, {0x808f752, [HUP INT ILL TRAP ABRT BUS FPE USR1 SEGV USR2 PIPE ALRM TERM XCPU XFSZ VTALRM SYS], SA_RESTORER, 0x6ff7a4f8}, {SIG_DFL}, 8) = 0 +rt_sigaction(SIGILL, {0x808f752, [HUP INT ILL TRAP ABRT BUS FPE USR1 SEGV USR2 PIPE ALRM TERM XCPU XFSZ VTALRM SYS], SA_RESTORER, 0x6ff7a4f8}, {SIG_DFL}, 8) = 0 +rt_sigaction(SIGTRAP, {0x808f752, [HUP INT ILL TRAP ABRT BUS FPE USR1 SEGV USR2 PIPE ALRM TERM XCPU XFSZ VTALRM SYS], SA_RESTORER, 0x6ff7a4f8}, {SIG_DFL}, 8) = 0 +rt_sigaction(SIGABRT, {0x808f752, [HUP INT ILL TRAP ABRT BUS FPE USR1 SEGV USR2 PIPE ALRM TERM XCPU XFSZ VTALRM SYS], SA_RESTORER, 0x6ff7a4f8}, {SIG_DFL}, 8) = 0 +rt_sigaction(SIGFPE, {0x808f752, [HUP INT ILL TRAP ABRT BUS FPE USR1 SEGV USR2 PIPE ALRM TERM XCPU XFSZ VTALRM SYS], SA_RESTORER, 0x6ff7a4f8}, {SIG_DFL}, 8) = 0 +rt_sigaction(SIGBUS, {0x808f752, [HUP INT ILL TRAP ABRT BUS FPE USR1 SEGV USR2 PIPE ALRM TERM XCPU XFSZ VTALRM SYS], SA_RESTORER, 0x6ff7a4f8}, {SIG_DFL}, 8) = 0 +rt_sigaction(SIGSEGV, {0x808f752, [HUP INT ILL TRAP ABRT BUS FPE USR1 SEGV USR2 PIPE ALRM TERM XCPU XFSZ VTALRM SYS], SA_RESTORER, 0x6ff7a4f8}, {SIG_DFL}, 8) = 0 +rt_sigaction(SIGSYS, {0x808f752, [HUP INT ILL TRAP ABRT BUS FPE USR1 SEGV USR2 PIPE ALRM TERM XCPU XFSZ VTALRM SYS], SA_RESTORER, 0x6ff7a4f8}, {SIG_DFL}, 8) = 0 +rt_sigaction(SIGPIPE, {0x808f752, [HUP INT ILL TRAP ABRT BUS FPE USR1 SEGV USR2 PIPE ALRM TERM XCPU XFSZ VTALRM SYS], SA_RESTORER, 0x6ff7a4f8}, {SIG_DFL}, 8) = 0 +rt_sigaction(SIGALRM, {0x808f752, [HUP INT ILL TRAP ABRT BUS FPE USR1 SEGV USR2 PIPE ALRM TERM XCPU XFSZ VTALRM SYS], SA_RESTORER, 0x6ff7a4f8}, {SIG_DFL}, 8) = 0 +rt_sigaction(SIGTERM, {0x808f752, [HUP INT ILL TRAP ABRT BUS FPE USR1 SEGV USR2 PIPE ALRM TERM XCPU XFSZ VTALRM SYS], SA_RESTORER, 0x6ff7a4f8}, {SIG_DFL}, 8) = 0 +rt_sigaction(SIGXCPU, {0x808f752, [HUP INT ILL TRAP ABRT BUS FPE USR1 SEGV USR2 PIPE ALRM TERM XCPU XFSZ VTALRM SYS], SA_RESTORER, 0x6ff7a4f8}, {SIG_DFL}, 8) = 0 +rt_sigaction(SIGXFSZ, {0x808f752, [HUP INT ILL TRAP ABRT BUS FPE USR1 SEGV USR2 PIPE ALRM TERM XCPU XFSZ VTALRM SYS], SA_RESTORER, 0x6ff7a4f8}, {SIG_DFL}, 8) = 0 +rt_sigaction(SIGVTALRM, {0x808f752, [HUP INT ILL TRAP ABRT BUS FPE USR1 SEGV USR2 PIPE ALRM TERM XCPU XFSZ VTALRM SYS], SA_RESTORER, 0x6ff7a4f8}, {SIG_DFL}, 8) = 0 +rt_sigaction(SIGUSR1, {0x808f752, [HUP INT ILL TRAP ABRT BUS FPE USR1 SEGV USR2 PIPE ALRM TERM XCPU XFSZ VTALRM SYS], SA_RESTORER, 0x6ff7a4f8}, {SIG_DFL}, 8) = 0 +rt_sigaction(SIGUSR2, {0x808f752, [HUP INT ILL TRAP ABRT BUS FPE USR1 SEGV USR2 PIPE ALRM TERM XCPU XFSZ VTALRM SYS], SA_RESTORER, 0x6ff7a4f8}, {SIG_DFL}, 8) = 0 +rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0 +rt_sigaction(SIGQUIT, {SIG_IGN}, {SIG_DFL}, 8) = 0 +rt_sigaction(SIGINT, {0x808f7d7, [], SA_RESTORER, 0x6ff7a4f8}, {0x808f752, [HUP INT ILL TRAP ABRT BUS FPE USR1 SEGV USR2 PIPE ALRM TERM XCPU XFSZ VTALRM SYS], SA_RESTORER, 0x6ff7a4f8}, 8) = 0 +rt_sigaction(SIGTERM, {SIG_IGN}, {0x808f752, [HUP INT ILL TRAP ABRT BUS FPE USR1 SEGV USR2 PIPE ALRM TERM XCPU XFSZ VTALRM SYS], SA_RESTORER, 0x6ff7a4f8}, 8) = 0 +rt_sigaction(SIGINT, {0x808f7d7, [], SA_RESTORER, 0x6ff7a4f8}, {0x808f7d7, [], SA_RESTORER, 0x6ff7a4f8}, 8) = 0 +rt_sigaction(SIGTSTP, {SIG_IGN}, {SIG_DFL}, 8) = 0 +rt_sigaction(SIGTTOU, {SIG_IGN}, {SIG_DFL}, 8) = 0 +rt_sigaction(SIGTTIN, {SIG_IGN}, {SIG_DFL}, 8) = 0 +rt_sigaction(SIGWINCH, {0x807dc33, [], SA_RESTORER, 0x6ff7a4f8}, {SIG_DFL}, 8) = 0 +getpid() = 19473 +getppid() = 19472 +rt_sigprocmask(SIG_BLOCK, ~[], [], 8) = 0 +rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 +getpgrp() = 1865 +dup(2) = 4 +fcntl64(255, F_GETFD) = -1 EBADF (Bad file descriptor) +dup2(4, 255) = 255 +close(4) = 0 +ioctl(255, TIOCGPGRP, [1865]) = 0 +getpid() = 19473 +setpgid(0, 19473) = 0 +rt_sigprocmask(SIG_BLOCK, [CHLD TSTP TTIN TTOU], [], 8) = 0 +ioctl(255, TIOCSPGRP, [19473]) = 0 +rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 +fcntl64(255, F_SETFD, FD_CLOEXEC) = 0 +rt_sigaction(SIGCHLD, {0x807c922, [], SA_RESTORER, 0x6ff7a4f8}, {SIG_DFL}, 8) = 0 +rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0 +fcntl64(0, F_GETFL) = 0x2 (flags O_RDWR) +... +rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0 +rt_sigaction(SIGINT, {0x808f7d7, [], SA_RESTORER, 0x6ff7a4f8}, {0x808f7d7, [], SA_RESTORER, 0x6ff7a4f8}, 8) = 0 +rt_sigprocmask(SIG_BLOCK, [CHLD TSTP TTIN TTOU], [], 8) = 0 +ioctl(255, TIOCSPGRP, [19473]) = 0 +rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 +rt_sigaction(SIGINT, {0x808f7d7, [], SA_RESTORER, 0x6ff7a4f8}, {0x808f7d7, [], SA_RESTORER, 0x6ff7a4f8}, 8) = 0 +rt_sigprocmask(SIG_BLOCK, ~[], [], 8) = 0 +rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 +rt_sigprocmask(SIG_BLOCK, [INT], [], 8) = 0 +rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 +rt_sigaction(SIGINT, {0x80ca530, [], SA_RESTORER, 0x6ff7a4f8}, {0x808f7d7, [], SA_RESTORER, 0x6ff7a4f8}, 8) = 0 +rt_sigaction(SIGTERM, {0x80ca530, [], SA_RESTORER, 0x6ff7a4f8}, {SIG_IGN}, 8) = 0 +rt_sigaction(SIGTERM, {SIG_IGN}, {0x80ca530, [], SA_RESTORER, 0x6ff7a4f8}, 8) = 0 +rt_sigaction(SIGQUIT, {0x80ca530, [], SA_RESTORER, 0x6ff7a4f8}, {SIG_IGN}, 8) = 0 +rt_sigaction(SIGQUIT, {SIG_IGN}, {0x80ca530, [], SA_RESTORER, 0x6ff7a4f8}, 8) = 0 +rt_sigaction(SIGALRM, {0x80ca530, [], SA_RESTORER, 0x6ff7a4f8}, {0x808f752, [HUP INT ILL TRAP ABRT BUS FPE USR1 SEGV USR2 PIPE ALRM TERM XCPU XFSZ VTALRM SYS], SA_RESTORER, 0x6ff7a4f8}, 8) = 0 +rt_sigaction(SIGTSTP, {0x80ca530, [], SA_RESTORER, 0x6ff7a4f8}, {SIG_IGN}, 8) = 0 +rt_sigaction(SIGTSTP, {SIG_IGN}, {0x80ca530, [], SA_RESTORER, 0x6ff7a4f8}, 8) = 0 +rt_sigaction(SIGTTOU, {0x80ca530, [], SA_RESTORER, 0x6ff7a4f8}, {SIG_IGN}, 8) = 0 +rt_sigaction(SIGTTOU, {SIG_IGN}, {0x80ca530, [], SA_RESTORER, 0x6ff7a4f8}, 8) = 0 +rt_sigaction(SIGTTIN, {0x80ca530, [], SA_RESTORER, 0x6ff7a4f8}, {SIG_IGN}, 8) = 0 +rt_sigaction(SIGTTIN, {SIG_IGN}, {0x80ca530, [], SA_RESTORER, 0x6ff7a4f8}, 8) = 0 +rt_sigaction(SIGWINCH, {0x80ca5cd, [], SA_RESTORER|SA_RESTART, 0x6ff7a4f8}, {0x807dc33, [], SA_RESTORER, 0x6ff7a4f8}, 8) = 0 +rt_sigprocmask(SIG_BLOCK, ~[], [], 8) = 0 +rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 +rt_sigprocmask(SIG_BLOCK, ~[], [], 8) = 0 +rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 +rt_sigprocmask(SIG_BLOCK, ~[], [], 8) = 0 +rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 +write(2, "sh-3.00# ", 9) = 9 +rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0 +read(0, "s", 1) = 1 +write(2, "s", 1) = 1 +rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0 +read(0, "l", 1) = 1 +write(2, "l", 1) = 1 +rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0 +... rest of "sleep 1 | sleep 2" entered... +rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0 +read(0, "2", 1) = 1 +write(2, "2", 1) = 1 +rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0 +read(0, "\r", 1) = 1 +write(2, "\n", 1) = 1 +rt_sigprocmask(SIG_BLOCK, [INT], [], 8) = 0 +ioctl(0, SNDCTL_TMR_STOP or TCSETSW, {B38400 opost isig icanon echo ...}) = 0 +rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 +rt_sigaction(SIGINT, {0x808f7d7, [], SA_RESTORER, 0x6ff7a4f8}, {0x80ca530, [], SA_RESTORER, 0x6ff7a4f8}, 8) = 0 +rt_sigaction(SIGTERM, {SIG_IGN}, {SIG_IGN}, 8) = 0 +rt_sigaction(SIGQUIT, {SIG_IGN}, {SIG_IGN}, 8) = 0 +rt_sigaction(SIGALRM, {0x808f752, [HUP INT ILL TRAP ABRT BUS FPE USR1 SEGV USR2 PIPE ALRM TERM XCPU XFSZ VTALRM SYS], SA_RESTORER, 0x6ff7a4f8}, {0x80ca530, [], SA_RESTORER, 0x6ff7a4f8}, 8) = 0 +rt_sigaction(SIGTSTP, {SIG_IGN}, {SIG_IGN}, 8) = 0 +rt_sigaction(SIGTTOU, {SIG_IGN}, {SIG_IGN}, 8) = 0 +rt_sigaction(SIGTTIN, {SIG_IGN}, {SIG_IGN}, 8) = 0 +rt_sigaction(SIGWINCH, {0x807dc33, [], SA_RESTORER, 0x6ff7a4f8}, {0x80ca5cd, [], SA_RESTORER|SA_RESTART, 0x6ff7a4f8}, 8) = 0 +rt_sigaction(SIGINT, {0x808f7d7, [], SA_RESTORER, 0x6ff7a4f8}, {0x808f7d7, [], SA_RESTORER, 0x6ff7a4f8}, 8) = 0 +rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0 +pipe([4, 5]) = 0 +rt_sigprocmask(SIG_BLOCK, [INT CHLD], [CHLD], 8) = 0 +fork() = 19755 +setpgid(19755, 19755) = 0 +rt_sigprocmask(SIG_SETMASK, [CHLD], NULL, 8) = 0 +close(5) = 0 +rt_sigprocmask(SIG_BLOCK, [INT CHLD], [CHLD], 8) = 0 +fork() = 19756 +setpgid(19756, 19755) = 0 +rt_sigprocmask(SIG_SETMASK, [CHLD], NULL, 8) = 0 +close(4) = 0 +rt_sigprocmask(SIG_BLOCK, [CHLD], [CHLD], 8) = 0 +rt_sigprocmask(SIG_BLOCK, [CHLD TSTP TTIN TTOU], [CHLD], 8) = 0 +ioctl(255, TIOCSPGRP, [19755]) = 0 +rt_sigprocmask(SIG_SETMASK, [CHLD], NULL, 8) = 0 +rt_sigprocmask(SIG_SETMASK, [CHLD], NULL, 8) = 0 +rt_sigprocmask(SIG_BLOCK, [CHLD], [CHLD], 8) = 0 +wait4(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], WUNTRACED, NULL) = 19755 +wait4(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], WUNTRACED, NULL) = 19756 +rt_sigprocmask(SIG_BLOCK, [CHLD TSTP TTIN TTOU], [CHLD], 8) = 0 +ioctl(255, TIOCSPGRP, [19473]) = 0 +rt_sigprocmask(SIG_SETMASK, [CHLD], NULL, 8) = 0 +rt_sigprocmask(SIG_SETMASK, [CHLD], NULL, 8) = 0 +rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 +--- SIGCHLD (Child exited) @ 0 (0) --- +wait4(-1, 0x77fc9c54, WNOHANG|WUNTRACED, NULL) = -1 ECHILD (No child processes) +sigreturn() = ? (mask now []) +rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0 +rt_sigaction(SIGINT, {0x808f7d7, [], SA_RESTORER, 0x6ff7a4f8}, {0x808f7d7, [], SA_RESTORER, 0x6ff7a4f8}, 8) = 0 +rt_sigprocmask(SIG_BLOCK, [CHLD TSTP TTIN TTOU], [], 8) = 0 +ioctl(255, TIOCSPGRP, [19473]) = 0 +rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 +rt_sigaction(SIGINT, {0x808f7d7, [], SA_RESTORER, 0x6ff7a4f8}, {0x808f7d7, [], SA_RESTORER, 0x6ff7a4f8}, 8) = 0 +rt_sigprocmask(SIG_BLOCK, [INT], [], 8) = 0 +rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 +rt_sigaction(SIGINT, {0x80ca530, [], SA_RESTORER, 0x6ff7a4f8}, {0x808f7d7, [], SA_RESTORER, 0x6ff7a4f8}, 8) = 0 +rt_sigaction(SIGTERM, {0x80ca530, [], SA_RESTORER, 0x6ff7a4f8}, {SIG_IGN}, 8) = 0 +rt_sigaction(SIGTERM, {SIG_IGN}, {0x80ca530, [], SA_RESTORER, 0x6ff7a4f8}, 8) = 0 +rt_sigaction(SIGQUIT, {0x80ca530, [], SA_RESTORER, 0x6ff7a4f8}, {SIG_IGN}, 8) = 0 +rt_sigaction(SIGQUIT, {SIG_IGN}, {0x80ca530, [], SA_RESTORER, 0x6ff7a4f8}, 8) = 0 +rt_sigaction(SIGALRM, {0x80ca530, [], SA_RESTORER, 0x6ff7a4f8}, {0x808f752, [HUP INT ILL TRAP ABRT BUS FPE USR1 SEGV USR2 PIPE ALRM TERM XCPU XFSZ VTALRM SYS], SA_RESTORER, 0x6ff7a4f8}, 8) = 0 +rt_sigaction(SIGTSTP, {0x80ca530, [], SA_RESTORER, 0x6ff7a4f8}, {SIG_IGN}, 8) = 0 +rt_sigaction(SIGTSTP, {SIG_IGN}, {0x80ca530, [], SA_RESTORER, 0x6ff7a4f8}, 8) = 0 +rt_sigaction(SIGTTOU, {0x80ca530, [], SA_RESTORER, 0x6ff7a4f8}, {SIG_IGN}, 8) = 0 +rt_sigaction(SIGTTOU, {SIG_IGN}, {0x80ca530, [], SA_RESTORER, 0x6ff7a4f8}, 8) = 0 +rt_sigaction(SIGTTIN, {0x80ca530, [], SA_RESTORER, 0x6ff7a4f8}, {SIG_IGN}, 8) = 0 +rt_sigaction(SIGTTIN, {SIG_IGN}, {0x80ca530, [], SA_RESTORER, 0x6ff7a4f8}, 8) = 0 +rt_sigaction(SIGWINCH, {0x80ca5cd, [], SA_RESTORER|SA_RESTART, 0x6ff7a4f8}, {0x807dc33, [], SA_RESTORER, 0x6ff7a4f8}, 8) = 0 +write(2, "sh-3.00# ", 9) = 9 + + +getpid() = 19755 +rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 +rt_sigaction(SIGTSTP, {SIG_DFL}, {SIG_IGN}, 8) = 0 +rt_sigaction(SIGTTIN, {SIG_DFL}, {SIG_IGN}, 8) = 0 +rt_sigaction(SIGTTOU, {SIG_DFL}, {SIG_IGN}, 8) = 0 +setpgid(19755, 19755) = 0 +rt_sigprocmask(SIG_BLOCK, [CHLD TSTP TTIN TTOU], [], 8) = 0 +ioctl(255, TIOCSPGRP, [19755]) = 0 +rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 +close(4) = 0 +dup2(5, 1) = 1 +close(5) = 0 +rt_sigaction(SIGINT, {SIG_DFL}, {0x808f7d7, [], SA_RESTORER, 0x6ff7a4f8}, 8) = 0 +rt_sigaction(SIGQUIT, {SIG_DFL}, {SIG_IGN}, 8) = 0 +rt_sigaction(SIGTERM, {SIG_DFL}, {SIG_IGN}, 8) = 0 +rt_sigaction(SIGCHLD, {SIG_DFL}, {0x807c922, [], SA_RESTORER, 0x6ff7a4f8}, 8) = 0 +execve("/bin/sleep", ["sleep", "1"], [/* 34 vars */]) = 0 +... +_exit(0) = ? + + +getpid() = 19756 +rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 +rt_sigaction(SIGTSTP, {SIG_DFL}, {SIG_IGN}, 8) = 0 +rt_sigaction(SIGTTIN, {SIG_DFL}, {SIG_IGN}, 8) = 0 +rt_sigaction(SIGTTOU, {SIG_DFL}, {SIG_IGN}, 8) = 0 +setpgid(19756, 19755) = 0 +rt_sigprocmask(SIG_BLOCK, [CHLD TSTP TTIN TTOU], [], 8) = 0 +ioctl(255, TIOCSPGRP, [19755]) = 0 +rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 +dup2(4, 0) = 0 +close(4) = 0 +rt_sigaction(SIGINT, {SIG_DFL}, {0x808f7d7, [], SA_RESTORER, 0x6ff7a4f8}, 8) = 0 +rt_sigaction(SIGQUIT, {SIG_DFL}, {SIG_IGN}, 8) = 0 +rt_sigaction(SIGTERM, {SIG_DFL}, {SIG_IGN}, 8) = 0 +rt_sigaction(SIGCHLD, {SIG_DFL}, {0x807c922, [], SA_RESTORER, 0x6ff7a4f8}, 8) = 0 +execve("/bin/sleep", ["sleep", "2"], [/* 34 vars */]) = 0 +... +_exit(0) = ? diff --git a/shell/ash.c b/shell/ash.c new file mode 100644 index 0000000..d6fd388 --- /dev/null +++ b/shell/ash.c @@ -0,0 +1,13762 @@ +/* vi: set sw=4 ts=4: */ +/* + * ash shell port for busybox + * + * Copyright (c) 1989, 1991, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Copyright (c) 1997-2005 Herbert Xu + * was re-ported from NetBSD and debianized. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + * + * Original BSD copyright notice is retained at the end of this file. + */ + +/* + * rewrite arith.y to micro stack based cryptic algorithm by + * Copyright (c) 2001 Aaron Lehmann + * + * Modified by Paul Mundt (c) 2004 to support + * dynamic variables. + * + * Modified by Vladimir Oleynik (c) 2001-2005 to be + * used in busybox and size optimizations, + * rewrote arith (see notes to this), added locale support, + * rewrote dynamic variables. + */ + +/* + * The follow should be set to reflect the type of system you have: + * JOBS -> 1 if you have Berkeley job control, 0 otherwise. + * define SYSV if you are running under System V. + * define DEBUG=1 to compile in debugging ('set -o debug' to turn on) + * define DEBUG=2 to compile in and turn on debugging. + * + * When debugging is on, debugging info will be written to ./trace and + * a quit signal will generate a core dump. + */ +#define DEBUG 0 +#define PROFILE 0 + +#define IFS_BROKEN + +#define JOBS ENABLE_ASH_JOB_CONTROL + +#if DEBUG +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#endif + +#include "busybox.h" /* for applet_names */ +#include +#include +#include +#if JOBS || ENABLE_ASH_READ_NCHARS +#include +#endif + +#ifndef PIPE_BUF +#define PIPE_BUF 4096 /* amount of buffering in a pipe */ +#endif + +#if defined(__uClinux__) +#error "Do not even bother, ash will not run on uClinux" +#endif + + +/* ============ Hash table sizes. Configurable. */ + +#define VTABSIZE 39 +#define ATABSIZE 39 +#define CMDTABLESIZE 31 /* should be prime */ + + +/* ============ Misc helpers */ + +#define xbarrier() do { __asm__ __volatile__ ("": : :"memory"); } while (0) + +/* C99 say: "char" declaration may be signed or unsigned default */ +#define signed_char2int(sc) ((int)((signed char)sc)) + + +/* ============ Shell options */ + +static const char *const optletters_optnames[] = { + "e" "errexit", + "f" "noglob", + "I" "ignoreeof", + "i" "interactive", + "m" "monitor", + "n" "noexec", + "s" "stdin", + "x" "xtrace", + "v" "verbose", + "C" "noclobber", + "a" "allexport", + "b" "notify", + "u" "nounset", + "\0" "vi" +#if DEBUG + ,"\0" "nolog" + ,"\0" "debug" +#endif +}; + +#define optletters(n) optletters_optnames[(n)][0] +#define optnames(n) (&optletters_optnames[(n)][1]) + +enum { NOPTS = ARRAY_SIZE(optletters_optnames) }; + + +/* ============ Misc data */ + +static const char homestr[] ALIGN1 = "HOME"; +static const char snlfmt[] ALIGN1 = "%s\n"; +static const char illnum[] ALIGN1 = "Illegal number: %s"; + +/* + * We enclose jmp_buf in a structure so that we can declare pointers to + * jump locations. The global variable handler contains the location to + * jump to when an exception occurs, and the global variable exception + * contains a code identifying the exception. To implement nested + * exception handlers, the user should save the value of handler on entry + * to an inner scope, set handler to point to a jmploc structure for the + * inner scope, and restore handler on exit from the scope. + */ +struct jmploc { + jmp_buf loc; +}; + +struct globals_misc { + /* pid of main shell */ + int rootpid; + /* shell level: 0 for the main shell, 1 for its children, and so on */ + int shlvl; +#define rootshell (!shlvl) + char *minusc; /* argument to -c option */ + + char *curdir; // = nullstr; /* current working directory */ + char *physdir; // = nullstr; /* physical working directory */ + + char *arg0; /* value of $0 */ + + struct jmploc *exception_handler; + +// disabled by vda: cannot understand how it was supposed to work - +// cannot fix bugs. That's why you have to explain your non-trivial designs! +// /* do we generate EXSIG events */ +// int exsig; /* counter */ + volatile int suppressint; /* counter */ + volatile /*sig_atomic_t*/ smallint intpending; /* 1 = got SIGINT */ + /* last pending signal */ + volatile /*sig_atomic_t*/ smallint pendingsig; + smallint exception; /* kind of exception (0..5) */ + /* exceptions */ +#define EXINT 0 /* SIGINT received */ +#define EXERROR 1 /* a generic error */ +#define EXSHELLPROC 2 /* execute a shell procedure */ +#define EXEXEC 3 /* command execution failed */ +#define EXEXIT 4 /* exit the shell */ +#define EXSIG 5 /* trapped signal in wait(1) */ + + smallint isloginsh; + char nullstr[1]; /* zero length string */ + + char optlist[NOPTS]; +#define eflag optlist[0] +#define fflag optlist[1] +#define Iflag optlist[2] +#define iflag optlist[3] +#define mflag optlist[4] +#define nflag optlist[5] +#define sflag optlist[6] +#define xflag optlist[7] +#define vflag optlist[8] +#define Cflag optlist[9] +#define aflag optlist[10] +#define bflag optlist[11] +#define uflag optlist[12] +#define viflag optlist[13] +#if DEBUG +#define nolog optlist[14] +#define debug optlist[15] +#endif + + /* trap handler commands */ + /* + * Sigmode records the current value of the signal handlers for the various + * modes. A value of zero means that the current handler is not known. + * S_HARD_IGN indicates that the signal was ignored on entry to the shell, + */ + char sigmode[NSIG - 1]; +#define S_DFL 1 /* default signal handling (SIG_DFL) */ +#define S_CATCH 2 /* signal is caught */ +#define S_IGN 3 /* signal is ignored (SIG_IGN) */ +#define S_HARD_IGN 4 /* signal is ignored permenantly */ +#define S_RESET 5 /* temporary - to reset a hard ignored sig */ + + /* indicates specified signal received */ + char gotsig[NSIG - 1]; /* offset by 1: "signal" 0 is meaningless */ + char *trap[NSIG]; + + /* Rarely referenced stuff */ +#if ENABLE_ASH_RANDOM_SUPPORT + /* Random number generators */ + int32_t random_galois_LFSR; /* Galois LFSR (fast but weak). signed! */ + uint32_t random_LCG; /* LCG (fast but weak) */ +#endif + pid_t backgndpid; /* pid of last background process */ + smallint job_warning; /* user was warned about stopped jobs (can be 2, 1 or 0). */ +}; +extern struct globals_misc *const ash_ptr_to_globals_misc; +#define G_misc (*ash_ptr_to_globals_misc) +#define rootpid (G_misc.rootpid ) +#define shlvl (G_misc.shlvl ) +#define minusc (G_misc.minusc ) +#define curdir (G_misc.curdir ) +#define physdir (G_misc.physdir ) +#define arg0 (G_misc.arg0 ) +#define exception_handler (G_misc.exception_handler) +#define exception (G_misc.exception ) +#define suppressint (G_misc.suppressint ) +#define intpending (G_misc.intpending ) +//#define exsig (G_misc.exsig ) +#define pendingsig (G_misc.pendingsig ) +#define isloginsh (G_misc.isloginsh ) +#define nullstr (G_misc.nullstr ) +#define optlist (G_misc.optlist ) +#define sigmode (G_misc.sigmode ) +#define gotsig (G_misc.gotsig ) +#define trap (G_misc.trap ) +#define random_galois_LFSR (G_misc.random_galois_LFSR) +#define random_LCG (G_misc.random_LCG ) +#define backgndpid (G_misc.backgndpid ) +#define job_warning (G_misc.job_warning) +#define INIT_G_misc() do { \ + (*(struct globals_misc**)&ash_ptr_to_globals_misc) = xzalloc(sizeof(G_misc)); \ + barrier(); \ + curdir = nullstr; \ + physdir = nullstr; \ +} while (0) + + +/* ============ Utility functions */ +static int isdigit_str9(const char *str) +{ + int maxlen = 9 + 1; /* max 9 digits: 999999999 */ + while (--maxlen && isdigit(*str)) + str++; + return (*str == '\0'); +} + + +/* ============ Interrupts / exceptions */ +/* + * These macros allow the user to suspend the handling of interrupt signals + * over a period of time. This is similar to SIGHOLD or to sigblock, but + * much more efficient and portable. (But hacking the kernel is so much + * more fun than worrying about efficiency and portability. :-)) + */ +#define INT_OFF do { \ + suppressint++; \ + xbarrier(); \ +} while (0) + +/* + * Called to raise an exception. Since C doesn't include exceptions, we + * just do a longjmp to the exception handler. The type of exception is + * stored in the global variable "exception". + */ +static void raise_exception(int) NORETURN; +static void +raise_exception(int e) +{ +#if DEBUG + if (exception_handler == NULL) + abort(); +#endif + INT_OFF; + exception = e; + longjmp(exception_handler->loc, 1); +} + +/* + * Called from trap.c when a SIGINT is received. (If the user specifies + * that SIGINT is to be trapped or ignored using the trap builtin, then + * this routine is not called.) Suppressint is nonzero when interrupts + * are held using the INT_OFF macro. (The test for iflag is just + * defensive programming.) + */ +static void raise_interrupt(void) NORETURN; +static void +raise_interrupt(void) +{ + int i; + + intpending = 0; + /* Signal is not automatically unmasked after it is raised, + * do it ourself - unmask all signals */ + sigprocmask_allsigs(SIG_UNBLOCK); + /* pendingsig = 0; - now done in onsig() */ + + i = EXSIG; + if (gotsig[SIGINT - 1] && !trap[SIGINT]) { + if (!(rootshell && iflag)) { + /* Kill ourself with SIGINT */ + signal(SIGINT, SIG_DFL); + raise(SIGINT); + } + i = EXINT; + } + raise_exception(i); + /* NOTREACHED */ +} + +#if ENABLE_ASH_OPTIMIZE_FOR_SIZE +static void +int_on(void) +{ + if (--suppressint == 0 && intpending) { + raise_interrupt(); + } +} +#define INT_ON int_on() +static void +force_int_on(void) +{ + suppressint = 0; + if (intpending) + raise_interrupt(); +} +#define FORCE_INT_ON force_int_on() +#else +#define INT_ON do { \ + xbarrier(); \ + if (--suppressint == 0 && intpending) \ + raise_interrupt(); \ +} while (0) +#define FORCE_INT_ON do { \ + xbarrier(); \ + suppressint = 0; \ + if (intpending) \ + raise_interrupt(); \ +} while (0) +#endif /* ASH_OPTIMIZE_FOR_SIZE */ + +#define SAVE_INT(v) ((v) = suppressint) + +#define RESTORE_INT(v) do { \ + xbarrier(); \ + suppressint = (v); \ + if (suppressint == 0 && intpending) \ + raise_interrupt(); \ +} while (0) + +/* + * Ignore a signal. Only one usage site - in forkchild() + */ +static void +ignoresig(int signo) +{ + if (sigmode[signo - 1] != S_IGN && sigmode[signo - 1] != S_HARD_IGN) { + signal(signo, SIG_IGN); + } + sigmode[signo - 1] = S_HARD_IGN; +} + +/* + * Signal handler. Only one usage site - in setsignal() + */ +static void +onsig(int signo) +{ + gotsig[signo - 1] = 1; + pendingsig = signo; + + if (/* exsig || */ (signo == SIGINT && !trap[SIGINT])) { + if (!suppressint) { + pendingsig = 0; + raise_interrupt(); /* does not return */ + } + intpending = 1; + } +} + + +/* ============ Stdout/stderr output */ + +static void +outstr(const char *p, FILE *file) +{ + INT_OFF; + fputs(p, file); + INT_ON; +} + +static void +flush_stdout_stderr(void) +{ + INT_OFF; + fflush(stdout); + fflush(stderr); + INT_ON; +} + +static void +flush_stderr(void) +{ + INT_OFF; + fflush(stderr); + INT_ON; +} + +static void +outcslow(int c, FILE *dest) +{ + INT_OFF; + putc(c, dest); + fflush(dest); + INT_ON; +} + +static int out1fmt(const char *, ...) __attribute__((__format__(__printf__,1,2))); +static int +out1fmt(const char *fmt, ...) +{ + va_list ap; + int r; + + INT_OFF; + va_start(ap, fmt); + r = vprintf(fmt, ap); + va_end(ap); + INT_ON; + return r; +} + +static int fmtstr(char *, size_t, const char *, ...) __attribute__((__format__(__printf__,3,4))); +static int +fmtstr(char *outbuf, size_t length, const char *fmt, ...) +{ + va_list ap; + int ret; + + va_start(ap, fmt); + INT_OFF; + ret = vsnprintf(outbuf, length, fmt, ap); + va_end(ap); + INT_ON; + return ret; +} + +static void +out1str(const char *p) +{ + outstr(p, stdout); +} + +static void +out2str(const char *p) +{ + outstr(p, stderr); + flush_stderr(); +} + + +/* ============ Parser structures */ + +/* control characters in argument strings */ +#define CTLESC '\201' /* escape next character */ +#define CTLVAR '\202' /* variable defn */ +#define CTLENDVAR '\203' +#define CTLBACKQ '\204' +#define CTLQUOTE 01 /* ored with CTLBACKQ code if in quotes */ +/* CTLBACKQ | CTLQUOTE == '\205' */ +#define CTLARI '\206' /* arithmetic expression */ +#define CTLENDARI '\207' +#define CTLQUOTEMARK '\210' + +/* variable substitution byte (follows CTLVAR) */ +#define VSTYPE 0x0f /* type of variable substitution */ +#define VSNUL 0x10 /* colon--treat the empty string as unset */ +#define VSQUOTE 0x80 /* inside double quotes--suppress splitting */ + +/* values of VSTYPE field */ +#define VSNORMAL 0x1 /* normal variable: $var or ${var} */ +#define VSMINUS 0x2 /* ${var-text} */ +#define VSPLUS 0x3 /* ${var+text} */ +#define VSQUESTION 0x4 /* ${var?message} */ +#define VSASSIGN 0x5 /* ${var=text} */ +#define VSTRIMRIGHT 0x6 /* ${var%pattern} */ +#define VSTRIMRIGHTMAX 0x7 /* ${var%%pattern} */ +#define VSTRIMLEFT 0x8 /* ${var#pattern} */ +#define VSTRIMLEFTMAX 0x9 /* ${var##pattern} */ +#define VSLENGTH 0xa /* ${#var} */ +#if ENABLE_ASH_BASH_COMPAT +#define VSSUBSTR 0xc /* ${var:position:length} */ +#define VSREPLACE 0xd /* ${var/pattern/replacement} */ +#define VSREPLACEALL 0xe /* ${var//pattern/replacement} */ +#endif + +static const char dolatstr[] ALIGN1 = { + CTLVAR, VSNORMAL|VSQUOTE, '@', '=', '\0' +}; + +#define NCMD 0 +#define NPIPE 1 +#define NREDIR 2 +#define NBACKGND 3 +#define NSUBSHELL 4 +#define NAND 5 +#define NOR 6 +#define NSEMI 7 +#define NIF 8 +#define NWHILE 9 +#define NUNTIL 10 +#define NFOR 11 +#define NCASE 12 +#define NCLIST 13 +#define NDEFUN 14 +#define NARG 15 +#define NTO 16 +#if ENABLE_ASH_BASH_COMPAT +#define NTO2 17 +#endif +#define NCLOBBER 18 +#define NFROM 19 +#define NFROMTO 20 +#define NAPPEND 21 +#define NTOFD 22 +#define NFROMFD 23 +#define NHERE 24 +#define NXHERE 25 +#define NNOT 26 +#define N_NUMBER 27 + +union node; + +struct ncmd { + smallint type; /* Nxxxx */ + union node *assign; + union node *args; + union node *redirect; +}; + +struct npipe { + smallint type; + smallint pipe_backgnd; + struct nodelist *cmdlist; +}; + +struct nredir { + smallint type; + union node *n; + union node *redirect; +}; + +struct nbinary { + smallint type; + union node *ch1; + union node *ch2; +}; + +struct nif { + smallint type; + union node *test; + union node *ifpart; + union node *elsepart; +}; + +struct nfor { + smallint type; + union node *args; + union node *body; + char *var; +}; + +struct ncase { + smallint type; + union node *expr; + union node *cases; +}; + +struct nclist { + smallint type; + union node *next; + union node *pattern; + union node *body; +}; + +struct narg { + smallint type; + union node *next; + char *text; + struct nodelist *backquote; +}; + +/* nfile and ndup layout must match! + * NTOFD (>&fdnum) uses ndup structure, but we may discover mid-flight + * that it is actually NTO2 (>&file), and change its type. + */ +struct nfile { + smallint type; + union node *next; + int fd; + int _unused_dupfd; + union node *fname; + char *expfname; +}; + +struct ndup { + smallint type; + union node *next; + int fd; + int dupfd; + union node *vname; + char *_unused_expfname; +}; + +struct nhere { + smallint type; + union node *next; + int fd; + union node *doc; +}; + +struct nnot { + smallint type; + union node *com; +}; + +union node { + smallint type; + struct ncmd ncmd; + struct npipe npipe; + struct nredir nredir; + struct nbinary nbinary; + struct nif nif; + struct nfor nfor; + struct ncase ncase; + struct nclist nclist; + struct narg narg; + struct nfile nfile; + struct ndup ndup; + struct nhere nhere; + struct nnot nnot; +}; + +struct nodelist { + struct nodelist *next; + union node *n; +}; + +struct funcnode { + int count; + union node n; +}; + +/* + * Free a parse tree. + */ +static void +freefunc(struct funcnode *f) +{ + if (f && --f->count < 0) + free(f); +} + + +/* ============ Debugging output */ + +#if DEBUG + +static FILE *tracefile; + +static void +trace_printf(const char *fmt, ...) +{ + va_list va; + + if (debug != 1) + return; + va_start(va, fmt); + vfprintf(tracefile, fmt, va); + va_end(va); +} + +static void +trace_vprintf(const char *fmt, va_list va) +{ + if (debug != 1) + return; + vfprintf(tracefile, fmt, va); +} + +static void +trace_puts(const char *s) +{ + if (debug != 1) + return; + fputs(s, tracefile); +} + +static void +trace_puts_quoted(char *s) +{ + char *p; + char c; + + if (debug != 1) + return; + putc('"', tracefile); + for (p = s; *p; p++) { + switch (*p) { + case '\n': c = 'n'; goto backslash; + case '\t': c = 't'; goto backslash; + case '\r': c = 'r'; goto backslash; + case '"': c = '"'; goto backslash; + case '\\': c = '\\'; goto backslash; + case CTLESC: c = 'e'; goto backslash; + case CTLVAR: c = 'v'; goto backslash; + case CTLVAR+CTLQUOTE: c = 'V'; goto backslash; + case CTLBACKQ: c = 'q'; goto backslash; + case CTLBACKQ+CTLQUOTE: c = 'Q'; goto backslash; + backslash: + putc('\\', tracefile); + putc(c, tracefile); + break; + default: + if (*p >= ' ' && *p <= '~') + putc(*p, tracefile); + else { + putc('\\', tracefile); + putc(*p >> 6 & 03, tracefile); + putc(*p >> 3 & 07, tracefile); + putc(*p & 07, tracefile); + } + break; + } + } + putc('"', tracefile); +} + +static void +trace_puts_args(char **ap) +{ + if (debug != 1) + return; + if (!*ap) + return; + while (1) { + trace_puts_quoted(*ap); + if (!*++ap) { + putc('\n', tracefile); + break; + } + putc(' ', tracefile); + } +} + +static void +opentrace(void) +{ + char s[100]; +#ifdef O_APPEND + int flags; +#endif + + if (debug != 1) { + if (tracefile) + fflush(tracefile); + /* leave open because libedit might be using it */ + return; + } + strcpy(s, "./trace"); + if (tracefile) { + if (!freopen(s, "a", tracefile)) { + fprintf(stderr, "Can't re-open %s\n", s); + debug = 0; + return; + } + } else { + tracefile = fopen(s, "a"); + if (tracefile == NULL) { + fprintf(stderr, "Can't open %s\n", s); + debug = 0; + return; + } + } +#ifdef O_APPEND + flags = fcntl(fileno(tracefile), F_GETFL); + if (flags >= 0) + fcntl(fileno(tracefile), F_SETFL, flags | O_APPEND); +#endif + setlinebuf(tracefile); + fputs("\nTracing started.\n", tracefile); +} + +static void +indent(int amount, char *pfx, FILE *fp) +{ + int i; + + for (i = 0; i < amount; i++) { + if (pfx && i == amount - 1) + fputs(pfx, fp); + putc('\t', fp); + } +} + +/* little circular references here... */ +static void shtree(union node *n, int ind, char *pfx, FILE *fp); + +static void +sharg(union node *arg, FILE *fp) +{ + char *p; + struct nodelist *bqlist; + int subtype; + + if (arg->type != NARG) { + out1fmt("\n", arg->type); + abort(); + } + bqlist = arg->narg.backquote; + for (p = arg->narg.text; *p; p++) { + switch (*p) { + case CTLESC: + putc(*++p, fp); + break; + case CTLVAR: + putc('$', fp); + putc('{', fp); + subtype = *++p; + if (subtype == VSLENGTH) + putc('#', fp); + + while (*p != '=') + putc(*p++, fp); + + if (subtype & VSNUL) + putc(':', fp); + + switch (subtype & VSTYPE) { + case VSNORMAL: + putc('}', fp); + break; + case VSMINUS: + putc('-', fp); + break; + case VSPLUS: + putc('+', fp); + break; + case VSQUESTION: + putc('?', fp); + break; + case VSASSIGN: + putc('=', fp); + break; + case VSTRIMLEFT: + putc('#', fp); + break; + case VSTRIMLEFTMAX: + putc('#', fp); + putc('#', fp); + break; + case VSTRIMRIGHT: + putc('%', fp); + break; + case VSTRIMRIGHTMAX: + putc('%', fp); + putc('%', fp); + break; + case VSLENGTH: + break; + default: + out1fmt("", subtype); + } + break; + case CTLENDVAR: + putc('}', fp); + break; + case CTLBACKQ: + case CTLBACKQ|CTLQUOTE: + putc('$', fp); + putc('(', fp); + shtree(bqlist->n, -1, NULL, fp); + putc(')', fp); + break; + default: + putc(*p, fp); + break; + } + } +} + +static void +shcmd(union node *cmd, FILE *fp) +{ + union node *np; + int first; + const char *s; + int dftfd; + + first = 1; + for (np = cmd->ncmd.args; np; np = np->narg.next) { + if (!first) + putc(' ', fp); + sharg(np, fp); + first = 0; + } + for (np = cmd->ncmd.redirect; np; np = np->nfile.next) { + if (!first) + putc(' ', fp); + dftfd = 0; + switch (np->nfile.type) { + case NTO: s = ">>"+1; dftfd = 1; break; + case NCLOBBER: s = ">|"; dftfd = 1; break; + case NAPPEND: s = ">>"; dftfd = 1; break; +#if ENABLE_ASH_BASH_COMPAT + case NTO2: +#endif + case NTOFD: s = ">&"; dftfd = 1; break; + case NFROM: s = "<"; break; + case NFROMFD: s = "<&"; break; + case NFROMTO: s = "<>"; break; + default: s = "*error*"; break; + } + if (np->nfile.fd != dftfd) + fprintf(fp, "%d", np->nfile.fd); + fputs(s, fp); + if (np->nfile.type == NTOFD || np->nfile.type == NFROMFD) { + fprintf(fp, "%d", np->ndup.dupfd); + } else { + sharg(np->nfile.fname, fp); + } + first = 0; + } +} + +static void +shtree(union node *n, int ind, char *pfx, FILE *fp) +{ + struct nodelist *lp; + const char *s; + + if (n == NULL) + return; + + indent(ind, pfx, fp); + switch (n->type) { + case NSEMI: + s = "; "; + goto binop; + case NAND: + s = " && "; + goto binop; + case NOR: + s = " || "; + binop: + shtree(n->nbinary.ch1, ind, NULL, fp); + /* if (ind < 0) */ + fputs(s, fp); + shtree(n->nbinary.ch2, ind, NULL, fp); + break; + case NCMD: + shcmd(n, fp); + if (ind >= 0) + putc('\n', fp); + break; + case NPIPE: + for (lp = n->npipe.cmdlist; lp; lp = lp->next) { + shcmd(lp->n, fp); + if (lp->next) + fputs(" | ", fp); + } + if (n->npipe.pipe_backgnd) + fputs(" &", fp); + if (ind >= 0) + putc('\n', fp); + break; + default: + fprintf(fp, "", n->type); + if (ind >= 0) + putc('\n', fp); + break; + } +} + +static void +showtree(union node *n) +{ + trace_puts("showtree called\n"); + shtree(n, 1, NULL, stdout); +} + +#define TRACE(param) trace_printf param +#define TRACEV(param) trace_vprintf param + +#else + +#define TRACE(param) +#define TRACEV(param) + +#endif /* DEBUG */ + + +/* ============ Parser data */ + +/* + * ash_vmsg() needs parsefile->fd, hence parsefile definition is moved up. + */ +struct strlist { + struct strlist *next; + char *text; +}; + +struct alias; + +struct strpush { + struct strpush *prev; /* preceding string on stack */ + char *prevstring; + int prevnleft; +#if ENABLE_ASH_ALIAS + struct alias *ap; /* if push was associated with an alias */ +#endif + char *string; /* remember the string since it may change */ +}; + +struct parsefile { + struct parsefile *prev; /* preceding file on stack */ + int linno; /* current line */ + int fd; /* file descriptor (or -1 if string) */ + int nleft; /* number of chars left in this line */ + int lleft; /* number of chars left in this buffer */ + char *nextc; /* next char in buffer */ + char *buf; /* input buffer */ + struct strpush *strpush; /* for pushing strings at this level */ + struct strpush basestrpush; /* so pushing one is fast */ +}; + +static struct parsefile basepf; /* top level input file */ +static struct parsefile *g_parsefile = &basepf; /* current input file */ +static int startlinno; /* line # where last token started */ +static char *commandname; /* currently executing command */ +static struct strlist *cmdenviron; /* environment for builtin command */ +static uint8_t exitstatus; /* exit status of last command */ + + +/* ============ Message printing */ + +static void +ash_vmsg(const char *msg, va_list ap) +{ + fprintf(stderr, "%s: ", arg0); + if (commandname) { + if (strcmp(arg0, commandname)) + fprintf(stderr, "%s: ", commandname); + if (!iflag || g_parsefile->fd) + fprintf(stderr, "line %d: ", startlinno); + } + vfprintf(stderr, msg, ap); + outcslow('\n', stderr); +} + +/* + * Exverror is called to raise the error exception. If the second argument + * is not NULL then error prints an error message using printf style + * formatting. It then raises the error exception. + */ +static void ash_vmsg_and_raise(int, const char *, va_list) NORETURN; +static void +ash_vmsg_and_raise(int cond, const char *msg, va_list ap) +{ +#if DEBUG + if (msg) { + TRACE(("ash_vmsg_and_raise(%d, \"", cond)); + TRACEV((msg, ap)); + TRACE(("\") pid=%d\n", getpid())); + } else + TRACE(("ash_vmsg_and_raise(%d, NULL) pid=%d\n", cond, getpid())); + if (msg) +#endif + ash_vmsg(msg, ap); + + flush_stdout_stderr(); + raise_exception(cond); + /* NOTREACHED */ +} + +static void ash_msg_and_raise_error(const char *, ...) NORETURN; +static void +ash_msg_and_raise_error(const char *msg, ...) +{ + va_list ap; + + va_start(ap, msg); + ash_vmsg_and_raise(EXERROR, msg, ap); + /* NOTREACHED */ + va_end(ap); +} + +static void ash_msg_and_raise(int, const char *, ...) NORETURN; +static void +ash_msg_and_raise(int cond, const char *msg, ...) +{ + va_list ap; + + va_start(ap, msg); + ash_vmsg_and_raise(cond, msg, ap); + /* NOTREACHED */ + va_end(ap); +} + +/* + * error/warning routines for external builtins + */ +static void +ash_msg(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + ash_vmsg(fmt, ap); + va_end(ap); +} + +/* + * Return a string describing an error. The returned string may be a + * pointer to a static buffer that will be overwritten on the next call. + * Action describes the operation that got the error. + */ +static const char * +errmsg(int e, const char *em) +{ + if (e == ENOENT || e == ENOTDIR) { + return em; + } + return strerror(e); +} + + +/* ============ Memory allocation */ + +/* + * It appears that grabstackstr() will barf with such alignments + * because stalloc() will return a string allocated in a new stackblock. + */ +#define SHELL_ALIGN(nbytes) (((nbytes) + SHELL_SIZE) & ~SHELL_SIZE) +enum { + /* Most machines require the value returned from malloc to be aligned + * in some way. The following macro will get this right + * on many machines. */ + SHELL_SIZE = sizeof(union {int i; char *cp; double d; }) - 1, + /* Minimum size of a block */ + MINSIZE = SHELL_ALIGN(504), +}; + +struct stack_block { + struct stack_block *prev; + char space[MINSIZE]; +}; + +struct stackmark { + struct stack_block *stackp; + char *stacknxt; + size_t stacknleft; + struct stackmark *marknext; +}; + + +struct globals_memstack { + struct stack_block *g_stackp; // = &stackbase; + struct stackmark *markp; + char *g_stacknxt; // = stackbase.space; + char *sstrend; // = stackbase.space + MINSIZE; + size_t g_stacknleft; // = MINSIZE; + int herefd; // = -1; + struct stack_block stackbase; +}; +extern struct globals_memstack *const ash_ptr_to_globals_memstack; +#define G_memstack (*ash_ptr_to_globals_memstack) +#define g_stackp (G_memstack.g_stackp ) +#define markp (G_memstack.markp ) +#define g_stacknxt (G_memstack.g_stacknxt ) +#define sstrend (G_memstack.sstrend ) +#define g_stacknleft (G_memstack.g_stacknleft) +#define herefd (G_memstack.herefd ) +#define stackbase (G_memstack.stackbase ) +#define INIT_G_memstack() do { \ + (*(struct globals_memstack**)&ash_ptr_to_globals_memstack) = xzalloc(sizeof(G_memstack)); \ + barrier(); \ + g_stackp = &stackbase; \ + g_stacknxt = stackbase.space; \ + g_stacknleft = MINSIZE; \ + sstrend = stackbase.space + MINSIZE; \ + herefd = -1; \ +} while (0) + +#define stackblock() ((void *)g_stacknxt) +#define stackblocksize() g_stacknleft + + +static void * +ckrealloc(void * p, size_t nbytes) +{ + p = realloc(p, nbytes); + if (!p) + ash_msg_and_raise_error(bb_msg_memory_exhausted); + return p; +} + +static void * +ckmalloc(size_t nbytes) +{ + return ckrealloc(NULL, nbytes); +} + +static void * +ckzalloc(size_t nbytes) +{ + return memset(ckmalloc(nbytes), 0, nbytes); +} + +/* + * Make a copy of a string in safe storage. + */ +static char * +ckstrdup(const char *s) +{ + char *p = strdup(s); + if (!p) + ash_msg_and_raise_error(bb_msg_memory_exhausted); + return p; +} + +/* + * Parse trees for commands are allocated in lifo order, so we use a stack + * to make this more efficient, and also to avoid all sorts of exception + * handling code to handle interrupts in the middle of a parse. + * + * The size 504 was chosen because the Ultrix malloc handles that size + * well. + */ +static void * +stalloc(size_t nbytes) +{ + char *p; + size_t aligned; + + aligned = SHELL_ALIGN(nbytes); + if (aligned > g_stacknleft) { + size_t len; + size_t blocksize; + struct stack_block *sp; + + blocksize = aligned; + if (blocksize < MINSIZE) + blocksize = MINSIZE; + len = sizeof(struct stack_block) - MINSIZE + blocksize; + if (len < blocksize) + ash_msg_and_raise_error(bb_msg_memory_exhausted); + INT_OFF; + sp = ckmalloc(len); + sp->prev = g_stackp; + g_stacknxt = sp->space; + g_stacknleft = blocksize; + sstrend = g_stacknxt + blocksize; + g_stackp = sp; + INT_ON; + } + p = g_stacknxt; + g_stacknxt += aligned; + g_stacknleft -= aligned; + return p; +} + +static void * +stzalloc(size_t nbytes) +{ + return memset(stalloc(nbytes), 0, nbytes); +} + +static void +stunalloc(void *p) +{ +#if DEBUG + if (!p || (g_stacknxt < (char *)p) || ((char *)p < g_stackp->space)) { + write(STDERR_FILENO, "stunalloc\n", 10); + abort(); + } +#endif + g_stacknleft += g_stacknxt - (char *)p; + g_stacknxt = p; +} + +/* + * Like strdup but works with the ash stack. + */ +static char * +ststrdup(const char *p) +{ + size_t len = strlen(p) + 1; + return memcpy(stalloc(len), p, len); +} + +static void +setstackmark(struct stackmark *mark) +{ + mark->stackp = g_stackp; + mark->stacknxt = g_stacknxt; + mark->stacknleft = g_stacknleft; + mark->marknext = markp; + markp = mark; +} + +static void +popstackmark(struct stackmark *mark) +{ + struct stack_block *sp; + + if (!mark->stackp) + return; + + INT_OFF; + markp = mark->marknext; + while (g_stackp != mark->stackp) { + sp = g_stackp; + g_stackp = sp->prev; + free(sp); + } + g_stacknxt = mark->stacknxt; + g_stacknleft = mark->stacknleft; + sstrend = mark->stacknxt + mark->stacknleft; + INT_ON; +} + +/* + * When the parser reads in a string, it wants to stick the string on the + * stack and only adjust the stack pointer when it knows how big the + * string is. Stackblock (defined in stack.h) returns a pointer to a block + * of space on top of the stack and stackblocklen returns the length of + * this block. Growstackblock will grow this space by at least one byte, + * possibly moving it (like realloc). Grabstackblock actually allocates the + * part of the block that has been used. + */ +static void +growstackblock(void) +{ + size_t newlen; + + newlen = g_stacknleft * 2; + if (newlen < g_stacknleft) + ash_msg_and_raise_error(bb_msg_memory_exhausted); + if (newlen < 128) + newlen += 128; + + if (g_stacknxt == g_stackp->space && g_stackp != &stackbase) { + struct stack_block *oldstackp; + struct stackmark *xmark; + struct stack_block *sp; + struct stack_block *prevstackp; + size_t grosslen; + + INT_OFF; + oldstackp = g_stackp; + sp = g_stackp; + prevstackp = sp->prev; + grosslen = newlen + sizeof(struct stack_block) - MINSIZE; + sp = ckrealloc(sp, grosslen); + sp->prev = prevstackp; + g_stackp = sp; + g_stacknxt = sp->space; + g_stacknleft = newlen; + sstrend = sp->space + newlen; + + /* + * Stack marks pointing to the start of the old block + * must be relocated to point to the new block + */ + xmark = markp; + while (xmark != NULL && xmark->stackp == oldstackp) { + xmark->stackp = g_stackp; + xmark->stacknxt = g_stacknxt; + xmark->stacknleft = g_stacknleft; + xmark = xmark->marknext; + } + INT_ON; + } else { + char *oldspace = g_stacknxt; + size_t oldlen = g_stacknleft; + char *p = stalloc(newlen); + + /* free the space we just allocated */ + g_stacknxt = memcpy(p, oldspace, oldlen); + g_stacknleft += newlen; + } +} + +static void +grabstackblock(size_t len) +{ + len = SHELL_ALIGN(len); + g_stacknxt += len; + g_stacknleft -= len; +} + +/* + * The following routines are somewhat easier to use than the above. + * The user declares a variable of type STACKSTR, which may be declared + * to be a register. The macro STARTSTACKSTR initializes things. Then + * the user uses the macro STPUTC to add characters to the string. In + * effect, STPUTC(c, p) is the same as *p++ = c except that the stack is + * grown as necessary. When the user is done, she can just leave the + * string there and refer to it using stackblock(). Or she can allocate + * the space for it using grabstackstr(). If it is necessary to allow + * someone else to use the stack temporarily and then continue to grow + * the string, the user should use grabstack to allocate the space, and + * then call ungrabstr(p) to return to the previous mode of operation. + * + * USTPUTC is like STPUTC except that it doesn't check for overflow. + * CHECKSTACKSPACE can be called before USTPUTC to ensure that there + * is space for at least one character. + */ +static void * +growstackstr(void) +{ + size_t len = stackblocksize(); + if (herefd >= 0 && len >= 1024) { + full_write(herefd, stackblock(), len); + return stackblock(); + } + growstackblock(); + return (char *)stackblock() + len; +} + +/* + * Called from CHECKSTRSPACE. + */ +static char * +makestrspace(size_t newlen, char *p) +{ + size_t len = p - g_stacknxt; + size_t size = stackblocksize(); + + for (;;) { + size_t nleft; + + size = stackblocksize(); + nleft = size - len; + if (nleft >= newlen) + break; + growstackblock(); + } + return (char *)stackblock() + len; +} + +static char * +stack_nputstr(const char *s, size_t n, char *p) +{ + p = makestrspace(n, p); + p = (char *)memcpy(p, s, n) + n; + return p; +} + +static char * +stack_putstr(const char *s, char *p) +{ + return stack_nputstr(s, strlen(s), p); +} + +static char * +_STPUTC(int c, char *p) +{ + if (p == sstrend) + p = growstackstr(); + *p++ = c; + return p; +} + +#define STARTSTACKSTR(p) ((p) = stackblock()) +#define STPUTC(c, p) ((p) = _STPUTC((c), (p))) +#define CHECKSTRSPACE(n, p) do { \ + char *q = (p); \ + size_t l = (n); \ + size_t m = sstrend - q; \ + if (l > m) \ + (p) = makestrspace(l, q); \ +} while (0) +#define USTPUTC(c, p) (*(p)++ = (c)) +#define STACKSTRNUL(p) do { \ + if ((p) == sstrend) \ + (p) = growstackstr(); \ + *(p) = '\0'; \ +} while (0) +#define STUNPUTC(p) (--(p)) +#define STTOPC(p) ((p)[-1]) +#define STADJUST(amount, p) ((p) += (amount)) + +#define grabstackstr(p) stalloc((char *)(p) - (char *)stackblock()) +#define ungrabstackstr(s, p) stunalloc(s) +#define stackstrend() ((void *)sstrend) + + +/* ============ String helpers */ + +/* + * prefix -- see if pfx is a prefix of string. + */ +static char * +prefix(const char *string, const char *pfx) +{ + while (*pfx) { + if (*pfx++ != *string++) + return NULL; + } + return (char *) string; +} + +/* + * Check for a valid number. This should be elsewhere. + */ +static int +is_number(const char *p) +{ + do { + if (!isdigit(*p)) + return 0; + } while (*++p != '\0'); + return 1; +} + +/* + * Convert a string of digits to an integer, printing an error message on + * failure. + */ +static int +number(const char *s) +{ + if (!is_number(s)) + ash_msg_and_raise_error(illnum, s); + return atoi(s); +} + +/* + * Produce a possibly single quoted string suitable as input to the shell. + * The return string is allocated on the stack. + */ +static char * +single_quote(const char *s) +{ + char *p; + + STARTSTACKSTR(p); + + do { + char *q; + size_t len; + + len = strchrnul(s, '\'') - s; + + q = p = makestrspace(len + 3, p); + + *q++ = '\''; + q = (char *)memcpy(q, s, len) + len; + *q++ = '\''; + s += len; + + STADJUST(q - p, p); + + len = strspn(s, "'"); + if (!len) + break; + + q = p = makestrspace(len + 3, p); + + *q++ = '"'; + q = (char *)memcpy(q, s, len) + len; + *q++ = '"'; + s += len; + + STADJUST(q - p, p); + } while (*s); + + USTPUTC(0, p); + + return stackblock(); +} + + +/* ============ nextopt */ + +static char **argptr; /* argument list for builtin commands */ +static char *optionarg; /* set by nextopt (like getopt) */ +static char *optptr; /* used by nextopt */ + +/* + * XXX - should get rid of. Have all builtins use getopt(3). + * The library getopt must have the BSD extension static variable + * "optreset", otherwise it can't be used within the shell safely. + * + * Standard option processing (a la getopt) for builtin routines. + * The only argument that is passed to nextopt is the option string; + * the other arguments are unnecessary. It returns the character, + * or '\0' on end of input. + */ +static int +nextopt(const char *optstring) +{ + char *p; + const char *q; + char c; + + p = optptr; + if (p == NULL || *p == '\0') { + /* We ate entire "-param", take next one */ + p = *argptr; + if (p == NULL) + return '\0'; + if (*p != '-') + return '\0'; + if (*++p == '\0') /* just "-" ? */ + return '\0'; + argptr++; + if (LONE_DASH(p)) /* "--" ? */ + return '\0'; + /* p => next "-param" */ + } + /* p => some option char in the middle of a "-param" */ + c = *p++; + for (q = optstring; *q != c;) { + if (*q == '\0') + ash_msg_and_raise_error("illegal option -%c", c); + if (*++q == ':') + q++; + } + if (*++q == ':') { + if (*p == '\0') { + p = *argptr++; + if (p == NULL) + ash_msg_and_raise_error("no arg for -%c option", c); + } + optionarg = p; + p = NULL; + } + optptr = p; + return c; +} + + +/* ============ Shell variables */ + +/* + * The parsefile structure pointed to by the global variable parsefile + * contains information about the current file being read. + */ +struct shparam { + int nparam; /* # of positional parameters (without $0) */ +#if ENABLE_ASH_GETOPTS + int optind; /* next parameter to be processed by getopts */ + int optoff; /* used by getopts */ +#endif + unsigned char malloced; /* if parameter list dynamically allocated */ + char **p; /* parameter list */ +}; + +/* + * Free the list of positional parameters. + */ +static void +freeparam(volatile struct shparam *param) +{ + if (param->malloced) { + char **ap, **ap1; + ap = ap1 = param->p; + while (*ap) + free(*ap++); + free(ap1); + } +} + +#if ENABLE_ASH_GETOPTS +static void getoptsreset(const char *value); +#endif + +struct var { + struct var *next; /* next entry in hash list */ + int flags; /* flags are defined above */ + const char *text; /* name=value */ + void (*func)(const char *); /* function to be called when */ + /* the variable gets set/unset */ +}; + +struct localvar { + struct localvar *next; /* next local variable in list */ + struct var *vp; /* the variable that was made local */ + int flags; /* saved flags */ + const char *text; /* saved text */ +}; + +/* flags */ +#define VEXPORT 0x01 /* variable is exported */ +#define VREADONLY 0x02 /* variable cannot be modified */ +#define VSTRFIXED 0x04 /* variable struct is statically allocated */ +#define VTEXTFIXED 0x08 /* text is statically allocated */ +#define VSTACK 0x10 /* text is allocated on the stack */ +#define VUNSET 0x20 /* the variable is not set */ +#define VNOFUNC 0x40 /* don't call the callback function */ +#define VNOSET 0x80 /* do not set variable - just readonly test */ +#define VNOSAVE 0x100 /* when text is on the heap before setvareq */ +#if ENABLE_ASH_RANDOM_SUPPORT +# define VDYNAMIC 0x200 /* dynamic variable */ +#else +# define VDYNAMIC 0 +#endif + +#ifdef IFS_BROKEN +static const char defifsvar[] ALIGN1 = "IFS= \t\n"; +#define defifs (defifsvar + 4) +#else +static const char defifs[] ALIGN1 = " \t\n"; +#endif + + +/* Need to be before varinit_data[] */ +#if ENABLE_LOCALE_SUPPORT +static void +change_lc_all(const char *value) +{ + if (value && *value != '\0') + setlocale(LC_ALL, value); +} +static void +change_lc_ctype(const char *value) +{ + if (value && *value != '\0') + setlocale(LC_CTYPE, value); +} +#endif +#if ENABLE_ASH_MAIL +static void chkmail(void); +static void changemail(const char *); +#endif +static void changepath(const char *); +#if ENABLE_ASH_RANDOM_SUPPORT +static void change_random(const char *); +#endif + +static const struct { + int flags; + const char *text; + void (*func)(const char *); +} varinit_data[] = { +#ifdef IFS_BROKEN + { VSTRFIXED|VTEXTFIXED , defifsvar , NULL }, +#else + { VSTRFIXED|VTEXTFIXED|VUNSET, "IFS\0" , NULL }, +#endif +#if ENABLE_ASH_MAIL + { VSTRFIXED|VTEXTFIXED|VUNSET, "MAIL\0" , changemail }, + { VSTRFIXED|VTEXTFIXED|VUNSET, "MAILPATH\0", changemail }, +#endif + { VSTRFIXED|VTEXTFIXED , bb_PATH_root_path, changepath }, + { VSTRFIXED|VTEXTFIXED , "PS1=$ " , NULL }, + { VSTRFIXED|VTEXTFIXED , "PS2=> " , NULL }, + { VSTRFIXED|VTEXTFIXED , "PS4=+ " , NULL }, +#if ENABLE_ASH_GETOPTS + { VSTRFIXED|VTEXTFIXED , "OPTIND=1" , getoptsreset }, +#endif +#if ENABLE_ASH_RANDOM_SUPPORT + { VSTRFIXED|VTEXTFIXED|VUNSET|VDYNAMIC, "RANDOM\0", change_random }, +#endif +#if ENABLE_LOCALE_SUPPORT + { VSTRFIXED|VTEXTFIXED|VUNSET, "LC_ALL\0" , change_lc_all }, + { VSTRFIXED|VTEXTFIXED|VUNSET, "LC_CTYPE\0", change_lc_ctype }, +#endif +#if ENABLE_FEATURE_EDITING_SAVEHISTORY + { VSTRFIXED|VTEXTFIXED|VUNSET, "HISTFILE\0", NULL }, +#endif +}; + +struct redirtab; + +struct globals_var { + struct shparam shellparam; /* $@ current positional parameters */ + struct redirtab *redirlist; + int g_nullredirs; + int preverrout_fd; /* save fd2 before print debug if xflag is set. */ + struct var *vartab[VTABSIZE]; + struct var varinit[ARRAY_SIZE(varinit_data)]; +}; +extern struct globals_var *const ash_ptr_to_globals_var; +#define G_var (*ash_ptr_to_globals_var) +#define shellparam (G_var.shellparam ) +//#define redirlist (G_var.redirlist ) +#define g_nullredirs (G_var.g_nullredirs ) +#define preverrout_fd (G_var.preverrout_fd) +#define vartab (G_var.vartab ) +#define varinit (G_var.varinit ) +#define INIT_G_var() do { \ + unsigned i; \ + (*(struct globals_var**)&ash_ptr_to_globals_var) = xzalloc(sizeof(G_var)); \ + barrier(); \ + for (i = 0; i < ARRAY_SIZE(varinit_data); i++) { \ + varinit[i].flags = varinit_data[i].flags; \ + varinit[i].text = varinit_data[i].text; \ + varinit[i].func = varinit_data[i].func; \ + } \ +} while (0) + +#define vifs varinit[0] +#if ENABLE_ASH_MAIL +# define vmail (&vifs)[1] +# define vmpath (&vmail)[1] +# define vpath (&vmpath)[1] +#else +# define vpath (&vifs)[1] +#endif +#define vps1 (&vpath)[1] +#define vps2 (&vps1)[1] +#define vps4 (&vps2)[1] +#if ENABLE_ASH_GETOPTS +# define voptind (&vps4)[1] +# if ENABLE_ASH_RANDOM_SUPPORT +# define vrandom (&voptind)[1] +# endif +#else +# if ENABLE_ASH_RANDOM_SUPPORT +# define vrandom (&vps4)[1] +# endif +#endif + +/* + * The following macros access the values of the above variables. + * They have to skip over the name. They return the null string + * for unset variables. + */ +#define ifsval() (vifs.text + 4) +#define ifsset() ((vifs.flags & VUNSET) == 0) +#if ENABLE_ASH_MAIL +# define mailval() (vmail.text + 5) +# define mpathval() (vmpath.text + 9) +# define mpathset() ((vmpath.flags & VUNSET) == 0) +#endif +#define pathval() (vpath.text + 5) +#define ps1val() (vps1.text + 4) +#define ps2val() (vps2.text + 4) +#define ps4val() (vps4.text + 4) +#if ENABLE_ASH_GETOPTS +# define optindval() (voptind.text + 7) +#endif + + +#define is_name(c) ((c) == '_' || isalpha((unsigned char)(c))) +#define is_in_name(c) ((c) == '_' || isalnum((unsigned char)(c))) + +#if ENABLE_ASH_GETOPTS +static void +getoptsreset(const char *value) +{ + shellparam.optind = number(value); + shellparam.optoff = -1; +} +#endif + +/* + * Return of a legal variable name (a letter or underscore followed by zero or + * more letters, underscores, and digits). + */ +static char * +endofname(const char *name) +{ + char *p; + + p = (char *) name; + if (!is_name(*p)) + return p; + while (*++p) { + if (!is_in_name(*p)) + break; + } + return p; +} + +/* + * Compares two strings up to the first = or '\0'. The first + * string must be terminated by '='; the second may be terminated by + * either '=' or '\0'. + */ +static int +varcmp(const char *p, const char *q) +{ + int c, d; + + while ((c = *p) == (d = *q)) { + if (!c || c == '=') + goto out; + p++; + q++; + } + if (c == '=') + c = '\0'; + if (d == '=') + d = '\0'; + out: + return c - d; +} + +static int +varequal(const char *a, const char *b) +{ + return !varcmp(a, b); +} + +/* + * Find the appropriate entry in the hash table from the name. + */ +static struct var ** +hashvar(const char *p) +{ + unsigned hashval; + + hashval = ((unsigned char) *p) << 4; + while (*p && *p != '=') + hashval += (unsigned char) *p++; + return &vartab[hashval % VTABSIZE]; +} + +static int +vpcmp(const void *a, const void *b) +{ + return varcmp(*(const char **)a, *(const char **)b); +} + +/* + * This routine initializes the builtin variables. + */ +static void +initvar(void) +{ + struct var *vp; + struct var *end; + struct var **vpp; + + /* + * PS1 depends on uid + */ +#if ENABLE_FEATURE_EDITING && ENABLE_FEATURE_EDITING_FANCY_PROMPT + vps1.text = "PS1=\\w \\$ "; +#else + if (!geteuid()) + vps1.text = "PS1=# "; +#endif + vp = varinit; + end = vp + ARRAY_SIZE(varinit); + do { + vpp = hashvar(vp->text); + vp->next = *vpp; + *vpp = vp; + } while (++vp < end); +} + +static struct var ** +findvar(struct var **vpp, const char *name) +{ + for (; *vpp; vpp = &(*vpp)->next) { + if (varequal((*vpp)->text, name)) { + break; + } + } + return vpp; +} + +/* + * Find the value of a variable. Returns NULL if not set. + */ +static char * +lookupvar(const char *name) +{ + struct var *v; + + v = *findvar(hashvar(name), name); + if (v) { +#if ENABLE_ASH_RANDOM_SUPPORT + /* + * Dynamic variables are implemented roughly the same way they are + * in bash. Namely, they're "special" so long as they aren't unset. + * As soon as they're unset, they're no longer dynamic, and dynamic + * lookup will no longer happen at that point. -- PFM. + */ + if ((v->flags & VDYNAMIC)) + (*v->func)(NULL); +#endif + if (!(v->flags & VUNSET)) + return strchrnul(v->text, '=') + 1; + } + return NULL; +} + +/* + * Search the environment of a builtin command. + */ +static char * +bltinlookup(const char *name) +{ + struct strlist *sp; + + for (sp = cmdenviron; sp; sp = sp->next) { + if (varequal(sp->text, name)) + return strchrnul(sp->text, '=') + 1; + } + return lookupvar(name); +} + +/* + * Same as setvar except that the variable and value are passed in + * the first argument as name=value. Since the first argument will + * be actually stored in the table, it should not be a string that + * will go away. + * Called with interrupts off. + */ +static void +setvareq(char *s, int flags) +{ + struct var *vp, **vpp; + + vpp = hashvar(s); + flags |= (VEXPORT & (((unsigned) (1 - aflag)) - 1)); + vp = *findvar(vpp, s); + if (vp) { + if ((vp->flags & (VREADONLY|VDYNAMIC)) == VREADONLY) { + const char *n; + + if (flags & VNOSAVE) + free(s); + n = vp->text; + ash_msg_and_raise_error("%.*s: is read only", strchrnul(n, '=') - n, n); + } + + if (flags & VNOSET) + return; + + if (vp->func && (flags & VNOFUNC) == 0) + (*vp->func)(strchrnul(s, '=') + 1); + + if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0) + free((char*)vp->text); + + flags |= vp->flags & ~(VTEXTFIXED|VSTACK|VNOSAVE|VUNSET); + } else { + if (flags & VNOSET) + return; + /* not found */ + vp = ckzalloc(sizeof(*vp)); + vp->next = *vpp; + /*vp->func = NULL; - ckzalloc did it */ + *vpp = vp; + } + if (!(flags & (VTEXTFIXED|VSTACK|VNOSAVE))) + s = ckstrdup(s); + vp->text = s; + vp->flags = flags; +} + +/* + * Set the value of a variable. The flags argument is ored with the + * flags of the variable. If val is NULL, the variable is unset. + */ +static void +setvar(const char *name, const char *val, int flags) +{ + char *p, *q; + size_t namelen; + char *nameeq; + size_t vallen; + + q = endofname(name); + p = strchrnul(q, '='); + namelen = p - name; + if (!namelen || p != q) + ash_msg_and_raise_error("%.*s: bad variable name", namelen, name); + vallen = 0; + if (val == NULL) { + flags |= VUNSET; + } else { + vallen = strlen(val); + } + INT_OFF; + nameeq = ckmalloc(namelen + vallen + 2); + p = (char *)memcpy(nameeq, name, namelen) + namelen; + if (val) { + *p++ = '='; + p = (char *)memcpy(p, val, vallen) + vallen; + } + *p = '\0'; + setvareq(nameeq, flags | VNOSAVE); + INT_ON; +} + +#if ENABLE_ASH_GETOPTS +/* + * Safe version of setvar, returns 1 on success 0 on failure. + */ +static int +setvarsafe(const char *name, const char *val, int flags) +{ + int err; + volatile int saveint; + struct jmploc *volatile savehandler = exception_handler; + struct jmploc jmploc; + + SAVE_INT(saveint); + if (setjmp(jmploc.loc)) + err = 1; + else { + exception_handler = &jmploc; + setvar(name, val, flags); + err = 0; + } + exception_handler = savehandler; + RESTORE_INT(saveint); + return err; +} +#endif + +/* + * Unset the specified variable. + */ +static int +unsetvar(const char *s) +{ + struct var **vpp; + struct var *vp; + int retval; + + vpp = findvar(hashvar(s), s); + vp = *vpp; + retval = 2; + if (vp) { + int flags = vp->flags; + + retval = 1; + if (flags & VREADONLY) + goto out; +#if ENABLE_ASH_RANDOM_SUPPORT + vp->flags &= ~VDYNAMIC; +#endif + if (flags & VUNSET) + goto ok; + if ((flags & VSTRFIXED) == 0) { + INT_OFF; + if ((flags & (VTEXTFIXED|VSTACK)) == 0) + free((char*)vp->text); + *vpp = vp->next; + free(vp); + INT_ON; + } else { + setvar(s, 0, 0); + vp->flags &= ~VEXPORT; + } + ok: + retval = 0; + } + out: + return retval; +} + +/* + * Process a linked list of variable assignments. + */ +static void +listsetvar(struct strlist *list_set_var, int flags) +{ + struct strlist *lp = list_set_var; + + if (!lp) + return; + INT_OFF; + do { + setvareq(lp->text, flags); + lp = lp->next; + } while (lp); + INT_ON; +} + +/* + * Generate a list of variables satisfying the given conditions. + */ +static char ** +listvars(int on, int off, char ***end) +{ + struct var **vpp; + struct var *vp; + char **ep; + int mask; + + STARTSTACKSTR(ep); + vpp = vartab; + mask = on | off; + do { + for (vp = *vpp; vp; vp = vp->next) { + if ((vp->flags & mask) == on) { + if (ep == stackstrend()) + ep = growstackstr(); + *ep++ = (char *) vp->text; + } + } + } while (++vpp < vartab + VTABSIZE); + if (ep == stackstrend()) + ep = growstackstr(); + if (end) + *end = ep; + *ep++ = NULL; + return grabstackstr(ep); +} + + +/* ============ Path search helper + * + * The variable path (passed by reference) should be set to the start + * of the path before the first call; padvance will update + * this value as it proceeds. Successive calls to padvance will return + * the possible path expansions in sequence. If an option (indicated by + * a percent sign) appears in the path entry then the global variable + * pathopt will be set to point to it; otherwise pathopt will be set to + * NULL. + */ +static const char *pathopt; /* set by padvance */ + +static char * +padvance(const char **path, const char *name) +{ + const char *p; + char *q; + const char *start; + size_t len; + + if (*path == NULL) + return NULL; + start = *path; + for (p = start; *p && *p != ':' && *p != '%'; p++) + continue; + len = p - start + strlen(name) + 2; /* "2" is for '/' and '\0' */ + while (stackblocksize() < len) + growstackblock(); + q = stackblock(); + if (p != start) { + memcpy(q, start, p - start); + q += p - start; + *q++ = '/'; + } + strcpy(q, name); + pathopt = NULL; + if (*p == '%') { + pathopt = ++p; + while (*p && *p != ':') + p++; + } + if (*p == ':') + *path = p + 1; + else + *path = NULL; + return stalloc(len); +} + + +/* ============ Prompt */ + +static smallint doprompt; /* if set, prompt the user */ +static smallint needprompt; /* true if interactive and at start of line */ + +#if ENABLE_FEATURE_EDITING +static line_input_t *line_input_state; +static const char *cmdedit_prompt; +static void +putprompt(const char *s) +{ + if (ENABLE_ASH_EXPAND_PRMT) { + free((char*)cmdedit_prompt); + cmdedit_prompt = ckstrdup(s); + return; + } + cmdedit_prompt = s; +} +#else +static void +putprompt(const char *s) +{ + out2str(s); +} +#endif + +#if ENABLE_ASH_EXPAND_PRMT +/* expandstr() needs parsing machinery, so it is far away ahead... */ +static const char *expandstr(const char *ps); +#else +#define expandstr(s) s +#endif + +static void +setprompt(int whichprompt) +{ + const char *prompt; +#if ENABLE_ASH_EXPAND_PRMT + struct stackmark smark; +#endif + + needprompt = 0; + + switch (whichprompt) { + case 1: + prompt = ps1val(); + break; + case 2: + prompt = ps2val(); + break; + default: /* 0 */ + prompt = nullstr; + } +#if ENABLE_ASH_EXPAND_PRMT + setstackmark(&smark); + stalloc(stackblocksize()); +#endif + putprompt(expandstr(prompt)); +#if ENABLE_ASH_EXPAND_PRMT + popstackmark(&smark); +#endif +} + + +/* ============ The cd and pwd commands */ + +#define CD_PHYSICAL 1 +#define CD_PRINT 2 + +static int docd(const char *, int); + +static int +cdopt(void) +{ + int flags = 0; + int i, j; + + j = 'L'; + while ((i = nextopt("LP"))) { + if (i != j) { + flags ^= CD_PHYSICAL; + j = i; + } + } + + return flags; +} + +/* + * Update curdir (the name of the current directory) in response to a + * cd command. + */ +static const char * +updatepwd(const char *dir) +{ + char *new; + char *p; + char *cdcomppath; + const char *lim; + + cdcomppath = ststrdup(dir); + STARTSTACKSTR(new); + if (*dir != '/') { + if (curdir == nullstr) + return 0; + new = stack_putstr(curdir, new); + } + new = makestrspace(strlen(dir) + 2, new); + lim = (char *)stackblock() + 1; + if (*dir != '/') { + if (new[-1] != '/') + USTPUTC('/', new); + if (new > lim && *lim == '/') + lim++; + } else { + USTPUTC('/', new); + cdcomppath++; + if (dir[1] == '/' && dir[2] != '/') { + USTPUTC('/', new); + cdcomppath++; + lim++; + } + } + p = strtok(cdcomppath, "/"); + while (p) { + switch (*p) { + case '.': + if (p[1] == '.' && p[2] == '\0') { + while (new > lim) { + STUNPUTC(new); + if (new[-1] == '/') + break; + } + break; + } + if (p[1] == '\0') + break; + /* fall through */ + default: + new = stack_putstr(p, new); + USTPUTC('/', new); + } + p = strtok(0, "/"); + } + if (new > lim) + STUNPUTC(new); + *new = 0; + return stackblock(); +} + +/* + * Find out what the current directory is. If we already know the current + * directory, this routine returns immediately. + */ +static char * +getpwd(void) +{ + char *dir = getcwd(NULL, 0); /* huh, using glibc extension? */ + return dir ? dir : nullstr; +} + +static void +setpwd(const char *val, int setold) +{ + char *oldcur, *dir; + + oldcur = dir = curdir; + + if (setold) { + setvar("OLDPWD", oldcur, VEXPORT); + } + INT_OFF; + if (physdir != nullstr) { + if (physdir != oldcur) + free(physdir); + physdir = nullstr; + } + if (oldcur == val || !val) { + char *s = getpwd(); + physdir = s; + if (!val) + dir = s; + } else + dir = ckstrdup(val); + if (oldcur != dir && oldcur != nullstr) { + free(oldcur); + } + curdir = dir; + INT_ON; + setvar("PWD", dir, VEXPORT); +} + +static void hashcd(void); + +/* + * Actually do the chdir. We also call hashcd to let the routines in exec.c + * know that the current directory has changed. + */ +static int +docd(const char *dest, int flags) +{ + const char *dir = 0; + int err; + + TRACE(("docd(\"%s\", %d) called\n", dest, flags)); + + INT_OFF; + if (!(flags & CD_PHYSICAL)) { + dir = updatepwd(dest); + if (dir) + dest = dir; + } + err = chdir(dest); + if (err) + goto out; + setpwd(dir, 1); + hashcd(); + out: + INT_ON; + return err; +} + +static int +cdcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) +{ + const char *dest; + const char *path; + const char *p; + char c; + struct stat statb; + int flags; + + flags = cdopt(); + dest = *argptr; + if (!dest) + dest = bltinlookup(homestr); + else if (LONE_DASH(dest)) { + dest = bltinlookup("OLDPWD"); + flags |= CD_PRINT; + } + if (!dest) + dest = nullstr; + if (*dest == '/') + goto step7; + if (*dest == '.') { + c = dest[1]; + dotdot: + switch (c) { + case '\0': + case '/': + goto step6; + case '.': + c = dest[2]; + if (c != '.') + goto dotdot; + } + } + if (!*dest) + dest = "."; + path = bltinlookup("CDPATH"); + if (!path) { + step6: + step7: + p = dest; + goto docd; + } + do { + c = *path; + p = padvance(&path, dest); + if (stat(p, &statb) >= 0 && S_ISDIR(statb.st_mode)) { + if (c && c != ':') + flags |= CD_PRINT; + docd: + if (!docd(p, flags)) + goto out; + break; + } + } while (path); + ash_msg_and_raise_error("can't cd to %s", dest); + /* NOTREACHED */ + out: + if (flags & CD_PRINT) + out1fmt(snlfmt, curdir); + return 0; +} + +static int +pwdcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) +{ + int flags; + const char *dir = curdir; + + flags = cdopt(); + if (flags) { + if (physdir == nullstr) + setpwd(dir, 0); + dir = physdir; + } + out1fmt(snlfmt, dir); + return 0; +} + + +/* ============ ... */ + + +#define IBUFSIZ COMMON_BUFSIZE +/* buffer for top level input file */ +#define basebuf bb_common_bufsiz1 + +/* Syntax classes */ +#define CWORD 0 /* character is nothing special */ +#define CNL 1 /* newline character */ +#define CBACK 2 /* a backslash character */ +#define CSQUOTE 3 /* single quote */ +#define CDQUOTE 4 /* double quote */ +#define CENDQUOTE 5 /* a terminating quote */ +#define CBQUOTE 6 /* backwards single quote */ +#define CVAR 7 /* a dollar sign */ +#define CENDVAR 8 /* a '}' character */ +#define CLP 9 /* a left paren in arithmetic */ +#define CRP 10 /* a right paren in arithmetic */ +#define CENDFILE 11 /* end of file */ +#define CCTL 12 /* like CWORD, except it must be escaped */ +#define CSPCL 13 /* these terminate a word */ +#define CIGN 14 /* character should be ignored */ + +#if ENABLE_ASH_ALIAS +#define SYNBASE 130 +#define PEOF -130 +#define PEOA -129 +#define PEOA_OR_PEOF PEOA +#else +#define SYNBASE 129 +#define PEOF -129 +#define PEOA_OR_PEOF PEOF +#endif + +/* number syntax index */ +#define BASESYNTAX 0 /* not in quotes */ +#define DQSYNTAX 1 /* in double quotes */ +#define SQSYNTAX 2 /* in single quotes */ +#define ARISYNTAX 3 /* in arithmetic */ +#define PSSYNTAX 4 /* prompt */ + +#if ENABLE_ASH_OPTIMIZE_FOR_SIZE +#define USE_SIT_FUNCTION +#endif + +#if ENABLE_ASH_MATH_SUPPORT +static const char S_I_T[][4] = { +#if ENABLE_ASH_ALIAS + { CSPCL, CIGN, CIGN, CIGN }, /* 0, PEOA */ +#endif + { CSPCL, CWORD, CWORD, CWORD }, /* 1, ' ' */ + { CNL, CNL, CNL, CNL }, /* 2, \n */ + { CWORD, CCTL, CCTL, CWORD }, /* 3, !*-/:=?[]~ */ + { CDQUOTE, CENDQUOTE, CWORD, CWORD }, /* 4, '"' */ + { CVAR, CVAR, CWORD, CVAR }, /* 5, $ */ + { CSQUOTE, CWORD, CENDQUOTE, CWORD }, /* 6, "'" */ + { CSPCL, CWORD, CWORD, CLP }, /* 7, ( */ + { CSPCL, CWORD, CWORD, CRP }, /* 8, ) */ + { CBACK, CBACK, CCTL, CBACK }, /* 9, \ */ + { CBQUOTE, CBQUOTE, CWORD, CBQUOTE }, /* 10, ` */ + { CENDVAR, CENDVAR, CWORD, CENDVAR }, /* 11, } */ +#ifndef USE_SIT_FUNCTION + { CENDFILE, CENDFILE, CENDFILE, CENDFILE }, /* 12, PEOF */ + { CWORD, CWORD, CWORD, CWORD }, /* 13, 0-9A-Za-z */ + { CCTL, CCTL, CCTL, CCTL } /* 14, CTLESC ... */ +#endif +}; +#else +static const char S_I_T[][3] = { +#if ENABLE_ASH_ALIAS + { CSPCL, CIGN, CIGN }, /* 0, PEOA */ +#endif + { CSPCL, CWORD, CWORD }, /* 1, ' ' */ + { CNL, CNL, CNL }, /* 2, \n */ + { CWORD, CCTL, CCTL }, /* 3, !*-/:=?[]~ */ + { CDQUOTE, CENDQUOTE, CWORD }, /* 4, '"' */ + { CVAR, CVAR, CWORD }, /* 5, $ */ + { CSQUOTE, CWORD, CENDQUOTE }, /* 6, "'" */ + { CSPCL, CWORD, CWORD }, /* 7, ( */ + { CSPCL, CWORD, CWORD }, /* 8, ) */ + { CBACK, CBACK, CCTL }, /* 9, \ */ + { CBQUOTE, CBQUOTE, CWORD }, /* 10, ` */ + { CENDVAR, CENDVAR, CWORD }, /* 11, } */ +#ifndef USE_SIT_FUNCTION + { CENDFILE, CENDFILE, CENDFILE }, /* 12, PEOF */ + { CWORD, CWORD, CWORD }, /* 13, 0-9A-Za-z */ + { CCTL, CCTL, CCTL } /* 14, CTLESC ... */ +#endif +}; +#endif /* ASH_MATH_SUPPORT */ + +#ifdef USE_SIT_FUNCTION + +static int +SIT(int c, int syntax) +{ + static const char spec_symbls[] ALIGN1 = "\t\n !\"$&'()*-/:;<=>?[\\]`|}~"; +#if ENABLE_ASH_ALIAS + static const char syntax_index_table[] ALIGN1 = { + 1, 2, 1, 3, 4, 5, 1, 6, /* "\t\n !\"$&'" */ + 7, 8, 3, 3, 3, 3, 1, 1, /* "()*-/:;<" */ + 3, 1, 3, 3, 9, 3, 10, 1, /* "=>?[\\]`|" */ + 11, 3 /* "}~" */ + }; +#else + static const char syntax_index_table[] ALIGN1 = { + 0, 1, 0, 2, 3, 4, 0, 5, /* "\t\n !\"$&'" */ + 6, 7, 2, 2, 2, 2, 0, 0, /* "()*-/:;<" */ + 2, 0, 2, 2, 8, 2, 9, 0, /* "=>?[\\]`|" */ + 10, 2 /* "}~" */ + }; +#endif + const char *s; + int indx; + + if (c == PEOF) /* 2^8+2 */ + return CENDFILE; +#if ENABLE_ASH_ALIAS + if (c == PEOA) /* 2^8+1 */ + indx = 0; + else +#endif + + if ((unsigned char)c >= (unsigned char)(CTLESC) + && (unsigned char)c <= (unsigned char)(CTLQUOTEMARK) + ) { + return CCTL; + } else { + s = strchrnul(spec_symbls, c); + if (*s == '\0') + return CWORD; + indx = syntax_index_table[s - spec_symbls]; + } + return S_I_T[indx][syntax]; +} + +#else /* !USE_SIT_FUNCTION */ + +#if ENABLE_ASH_ALIAS +#define CSPCL_CIGN_CIGN_CIGN 0 +#define CSPCL_CWORD_CWORD_CWORD 1 +#define CNL_CNL_CNL_CNL 2 +#define CWORD_CCTL_CCTL_CWORD 3 +#define CDQUOTE_CENDQUOTE_CWORD_CWORD 4 +#define CVAR_CVAR_CWORD_CVAR 5 +#define CSQUOTE_CWORD_CENDQUOTE_CWORD 6 +#define CSPCL_CWORD_CWORD_CLP 7 +#define CSPCL_CWORD_CWORD_CRP 8 +#define CBACK_CBACK_CCTL_CBACK 9 +#define CBQUOTE_CBQUOTE_CWORD_CBQUOTE 10 +#define CENDVAR_CENDVAR_CWORD_CENDVAR 11 +#define CENDFILE_CENDFILE_CENDFILE_CENDFILE 12 +#define CWORD_CWORD_CWORD_CWORD 13 +#define CCTL_CCTL_CCTL_CCTL 14 +#else +#define CSPCL_CWORD_CWORD_CWORD 0 +#define CNL_CNL_CNL_CNL 1 +#define CWORD_CCTL_CCTL_CWORD 2 +#define CDQUOTE_CENDQUOTE_CWORD_CWORD 3 +#define CVAR_CVAR_CWORD_CVAR 4 +#define CSQUOTE_CWORD_CENDQUOTE_CWORD 5 +#define CSPCL_CWORD_CWORD_CLP 6 +#define CSPCL_CWORD_CWORD_CRP 7 +#define CBACK_CBACK_CCTL_CBACK 8 +#define CBQUOTE_CBQUOTE_CWORD_CBQUOTE 9 +#define CENDVAR_CENDVAR_CWORD_CENDVAR 10 +#define CENDFILE_CENDFILE_CENDFILE_CENDFILE 11 +#define CWORD_CWORD_CWORD_CWORD 12 +#define CCTL_CCTL_CCTL_CCTL 13 +#endif + +static const char syntax_index_table[258] = { + /* BASESYNTAX_DQSYNTAX_SQSYNTAX_ARISYNTAX */ + /* 0 PEOF */ CENDFILE_CENDFILE_CENDFILE_CENDFILE, +#if ENABLE_ASH_ALIAS + /* 1 PEOA */ CSPCL_CIGN_CIGN_CIGN, +#endif + /* 2 -128 0x80 */ CWORD_CWORD_CWORD_CWORD, + /* 3 -127 CTLESC */ CCTL_CCTL_CCTL_CCTL, + /* 4 -126 CTLVAR */ CCTL_CCTL_CCTL_CCTL, + /* 5 -125 CTLENDVAR */ CCTL_CCTL_CCTL_CCTL, + /* 6 -124 CTLBACKQ */ CCTL_CCTL_CCTL_CCTL, + /* 7 -123 CTLQUOTE */ CCTL_CCTL_CCTL_CCTL, + /* 8 -122 CTLARI */ CCTL_CCTL_CCTL_CCTL, + /* 9 -121 CTLENDARI */ CCTL_CCTL_CCTL_CCTL, + /* 10 -120 CTLQUOTEMARK */ CCTL_CCTL_CCTL_CCTL, + /* 11 -119 */ CWORD_CWORD_CWORD_CWORD, + /* 12 -118 */ CWORD_CWORD_CWORD_CWORD, + /* 13 -117 */ CWORD_CWORD_CWORD_CWORD, + /* 14 -116 */ CWORD_CWORD_CWORD_CWORD, + /* 15 -115 */ CWORD_CWORD_CWORD_CWORD, + /* 16 -114 */ CWORD_CWORD_CWORD_CWORD, + /* 17 -113 */ CWORD_CWORD_CWORD_CWORD, + /* 18 -112 */ CWORD_CWORD_CWORD_CWORD, + /* 19 -111 */ CWORD_CWORD_CWORD_CWORD, + /* 20 -110 */ CWORD_CWORD_CWORD_CWORD, + /* 21 -109 */ CWORD_CWORD_CWORD_CWORD, + /* 22 -108 */ CWORD_CWORD_CWORD_CWORD, + /* 23 -107 */ CWORD_CWORD_CWORD_CWORD, + /* 24 -106 */ CWORD_CWORD_CWORD_CWORD, + /* 25 -105 */ CWORD_CWORD_CWORD_CWORD, + /* 26 -104 */ CWORD_CWORD_CWORD_CWORD, + /* 27 -103 */ CWORD_CWORD_CWORD_CWORD, + /* 28 -102 */ CWORD_CWORD_CWORD_CWORD, + /* 29 -101 */ CWORD_CWORD_CWORD_CWORD, + /* 30 -100 */ CWORD_CWORD_CWORD_CWORD, + /* 31 -99 */ CWORD_CWORD_CWORD_CWORD, + /* 32 -98 */ CWORD_CWORD_CWORD_CWORD, + /* 33 -97 */ CWORD_CWORD_CWORD_CWORD, + /* 34 -96 */ CWORD_CWORD_CWORD_CWORD, + /* 35 -95 */ CWORD_CWORD_CWORD_CWORD, + /* 36 -94 */ CWORD_CWORD_CWORD_CWORD, + /* 37 -93 */ CWORD_CWORD_CWORD_CWORD, + /* 38 -92 */ CWORD_CWORD_CWORD_CWORD, + /* 39 -91 */ CWORD_CWORD_CWORD_CWORD, + /* 40 -90 */ CWORD_CWORD_CWORD_CWORD, + /* 41 -89 */ CWORD_CWORD_CWORD_CWORD, + /* 42 -88 */ CWORD_CWORD_CWORD_CWORD, + /* 43 -87 */ CWORD_CWORD_CWORD_CWORD, + /* 44 -86 */ CWORD_CWORD_CWORD_CWORD, + /* 45 -85 */ CWORD_CWORD_CWORD_CWORD, + /* 46 -84 */ CWORD_CWORD_CWORD_CWORD, + /* 47 -83 */ CWORD_CWORD_CWORD_CWORD, + /* 48 -82 */ CWORD_CWORD_CWORD_CWORD, + /* 49 -81 */ CWORD_CWORD_CWORD_CWORD, + /* 50 -80 */ CWORD_CWORD_CWORD_CWORD, + /* 51 -79 */ CWORD_CWORD_CWORD_CWORD, + /* 52 -78 */ CWORD_CWORD_CWORD_CWORD, + /* 53 -77 */ CWORD_CWORD_CWORD_CWORD, + /* 54 -76 */ CWORD_CWORD_CWORD_CWORD, + /* 55 -75 */ CWORD_CWORD_CWORD_CWORD, + /* 56 -74 */ CWORD_CWORD_CWORD_CWORD, + /* 57 -73 */ CWORD_CWORD_CWORD_CWORD, + /* 58 -72 */ CWORD_CWORD_CWORD_CWORD, + /* 59 -71 */ CWORD_CWORD_CWORD_CWORD, + /* 60 -70 */ CWORD_CWORD_CWORD_CWORD, + /* 61 -69 */ CWORD_CWORD_CWORD_CWORD, + /* 62 -68 */ CWORD_CWORD_CWORD_CWORD, + /* 63 -67 */ CWORD_CWORD_CWORD_CWORD, + /* 64 -66 */ CWORD_CWORD_CWORD_CWORD, + /* 65 -65 */ CWORD_CWORD_CWORD_CWORD, + /* 66 -64 */ CWORD_CWORD_CWORD_CWORD, + /* 67 -63 */ CWORD_CWORD_CWORD_CWORD, + /* 68 -62 */ CWORD_CWORD_CWORD_CWORD, + /* 69 -61 */ CWORD_CWORD_CWORD_CWORD, + /* 70 -60 */ CWORD_CWORD_CWORD_CWORD, + /* 71 -59 */ CWORD_CWORD_CWORD_CWORD, + /* 72 -58 */ CWORD_CWORD_CWORD_CWORD, + /* 73 -57 */ CWORD_CWORD_CWORD_CWORD, + /* 74 -56 */ CWORD_CWORD_CWORD_CWORD, + /* 75 -55 */ CWORD_CWORD_CWORD_CWORD, + /* 76 -54 */ CWORD_CWORD_CWORD_CWORD, + /* 77 -53 */ CWORD_CWORD_CWORD_CWORD, + /* 78 -52 */ CWORD_CWORD_CWORD_CWORD, + /* 79 -51 */ CWORD_CWORD_CWORD_CWORD, + /* 80 -50 */ CWORD_CWORD_CWORD_CWORD, + /* 81 -49 */ CWORD_CWORD_CWORD_CWORD, + /* 82 -48 */ CWORD_CWORD_CWORD_CWORD, + /* 83 -47 */ CWORD_CWORD_CWORD_CWORD, + /* 84 -46 */ CWORD_CWORD_CWORD_CWORD, + /* 85 -45 */ CWORD_CWORD_CWORD_CWORD, + /* 86 -44 */ CWORD_CWORD_CWORD_CWORD, + /* 87 -43 */ CWORD_CWORD_CWORD_CWORD, + /* 88 -42 */ CWORD_CWORD_CWORD_CWORD, + /* 89 -41 */ CWORD_CWORD_CWORD_CWORD, + /* 90 -40 */ CWORD_CWORD_CWORD_CWORD, + /* 91 -39 */ CWORD_CWORD_CWORD_CWORD, + /* 92 -38 */ CWORD_CWORD_CWORD_CWORD, + /* 93 -37 */ CWORD_CWORD_CWORD_CWORD, + /* 94 -36 */ CWORD_CWORD_CWORD_CWORD, + /* 95 -35 */ CWORD_CWORD_CWORD_CWORD, + /* 96 -34 */ CWORD_CWORD_CWORD_CWORD, + /* 97 -33 */ CWORD_CWORD_CWORD_CWORD, + /* 98 -32 */ CWORD_CWORD_CWORD_CWORD, + /* 99 -31 */ CWORD_CWORD_CWORD_CWORD, + /* 100 -30 */ CWORD_CWORD_CWORD_CWORD, + /* 101 -29 */ CWORD_CWORD_CWORD_CWORD, + /* 102 -28 */ CWORD_CWORD_CWORD_CWORD, + /* 103 -27 */ CWORD_CWORD_CWORD_CWORD, + /* 104 -26 */ CWORD_CWORD_CWORD_CWORD, + /* 105 -25 */ CWORD_CWORD_CWORD_CWORD, + /* 106 -24 */ CWORD_CWORD_CWORD_CWORD, + /* 107 -23 */ CWORD_CWORD_CWORD_CWORD, + /* 108 -22 */ CWORD_CWORD_CWORD_CWORD, + /* 109 -21 */ CWORD_CWORD_CWORD_CWORD, + /* 110 -20 */ CWORD_CWORD_CWORD_CWORD, + /* 111 -19 */ CWORD_CWORD_CWORD_CWORD, + /* 112 -18 */ CWORD_CWORD_CWORD_CWORD, + /* 113 -17 */ CWORD_CWORD_CWORD_CWORD, + /* 114 -16 */ CWORD_CWORD_CWORD_CWORD, + /* 115 -15 */ CWORD_CWORD_CWORD_CWORD, + /* 116 -14 */ CWORD_CWORD_CWORD_CWORD, + /* 117 -13 */ CWORD_CWORD_CWORD_CWORD, + /* 118 -12 */ CWORD_CWORD_CWORD_CWORD, + /* 119 -11 */ CWORD_CWORD_CWORD_CWORD, + /* 120 -10 */ CWORD_CWORD_CWORD_CWORD, + /* 121 -9 */ CWORD_CWORD_CWORD_CWORD, + /* 122 -8 */ CWORD_CWORD_CWORD_CWORD, + /* 123 -7 */ CWORD_CWORD_CWORD_CWORD, + /* 124 -6 */ CWORD_CWORD_CWORD_CWORD, + /* 125 -5 */ CWORD_CWORD_CWORD_CWORD, + /* 126 -4 */ CWORD_CWORD_CWORD_CWORD, + /* 127 -3 */ CWORD_CWORD_CWORD_CWORD, + /* 128 -2 */ CWORD_CWORD_CWORD_CWORD, + /* 129 -1 */ CWORD_CWORD_CWORD_CWORD, + /* 130 0 */ CWORD_CWORD_CWORD_CWORD, + /* 131 1 */ CWORD_CWORD_CWORD_CWORD, + /* 132 2 */ CWORD_CWORD_CWORD_CWORD, + /* 133 3 */ CWORD_CWORD_CWORD_CWORD, + /* 134 4 */ CWORD_CWORD_CWORD_CWORD, + /* 135 5 */ CWORD_CWORD_CWORD_CWORD, + /* 136 6 */ CWORD_CWORD_CWORD_CWORD, + /* 137 7 */ CWORD_CWORD_CWORD_CWORD, + /* 138 8 */ CWORD_CWORD_CWORD_CWORD, + /* 139 9 "\t" */ CSPCL_CWORD_CWORD_CWORD, + /* 140 10 "\n" */ CNL_CNL_CNL_CNL, + /* 141 11 */ CWORD_CWORD_CWORD_CWORD, + /* 142 12 */ CWORD_CWORD_CWORD_CWORD, + /* 143 13 */ CWORD_CWORD_CWORD_CWORD, + /* 144 14 */ CWORD_CWORD_CWORD_CWORD, + /* 145 15 */ CWORD_CWORD_CWORD_CWORD, + /* 146 16 */ CWORD_CWORD_CWORD_CWORD, + /* 147 17 */ CWORD_CWORD_CWORD_CWORD, + /* 148 18 */ CWORD_CWORD_CWORD_CWORD, + /* 149 19 */ CWORD_CWORD_CWORD_CWORD, + /* 150 20 */ CWORD_CWORD_CWORD_CWORD, + /* 151 21 */ CWORD_CWORD_CWORD_CWORD, + /* 152 22 */ CWORD_CWORD_CWORD_CWORD, + /* 153 23 */ CWORD_CWORD_CWORD_CWORD, + /* 154 24 */ CWORD_CWORD_CWORD_CWORD, + /* 155 25 */ CWORD_CWORD_CWORD_CWORD, + /* 156 26 */ CWORD_CWORD_CWORD_CWORD, + /* 157 27 */ CWORD_CWORD_CWORD_CWORD, + /* 158 28 */ CWORD_CWORD_CWORD_CWORD, + /* 159 29 */ CWORD_CWORD_CWORD_CWORD, + /* 160 30 */ CWORD_CWORD_CWORD_CWORD, + /* 161 31 */ CWORD_CWORD_CWORD_CWORD, + /* 162 32 " " */ CSPCL_CWORD_CWORD_CWORD, + /* 163 33 "!" */ CWORD_CCTL_CCTL_CWORD, + /* 164 34 """ */ CDQUOTE_CENDQUOTE_CWORD_CWORD, + /* 165 35 "#" */ CWORD_CWORD_CWORD_CWORD, + /* 166 36 "$" */ CVAR_CVAR_CWORD_CVAR, + /* 167 37 "%" */ CWORD_CWORD_CWORD_CWORD, + /* 168 38 "&" */ CSPCL_CWORD_CWORD_CWORD, + /* 169 39 "'" */ CSQUOTE_CWORD_CENDQUOTE_CWORD, + /* 170 40 "(" */ CSPCL_CWORD_CWORD_CLP, + /* 171 41 ")" */ CSPCL_CWORD_CWORD_CRP, + /* 172 42 "*" */ CWORD_CCTL_CCTL_CWORD, + /* 173 43 "+" */ CWORD_CWORD_CWORD_CWORD, + /* 174 44 "," */ CWORD_CWORD_CWORD_CWORD, + /* 175 45 "-" */ CWORD_CCTL_CCTL_CWORD, + /* 176 46 "." */ CWORD_CWORD_CWORD_CWORD, + /* 177 47 "/" */ CWORD_CCTL_CCTL_CWORD, + /* 178 48 "0" */ CWORD_CWORD_CWORD_CWORD, + /* 179 49 "1" */ CWORD_CWORD_CWORD_CWORD, + /* 180 50 "2" */ CWORD_CWORD_CWORD_CWORD, + /* 181 51 "3" */ CWORD_CWORD_CWORD_CWORD, + /* 182 52 "4" */ CWORD_CWORD_CWORD_CWORD, + /* 183 53 "5" */ CWORD_CWORD_CWORD_CWORD, + /* 184 54 "6" */ CWORD_CWORD_CWORD_CWORD, + /* 185 55 "7" */ CWORD_CWORD_CWORD_CWORD, + /* 186 56 "8" */ CWORD_CWORD_CWORD_CWORD, + /* 187 57 "9" */ CWORD_CWORD_CWORD_CWORD, + /* 188 58 ":" */ CWORD_CCTL_CCTL_CWORD, + /* 189 59 ";" */ CSPCL_CWORD_CWORD_CWORD, + /* 190 60 "<" */ CSPCL_CWORD_CWORD_CWORD, + /* 191 61 "=" */ CWORD_CCTL_CCTL_CWORD, + /* 192 62 ">" */ CSPCL_CWORD_CWORD_CWORD, + /* 193 63 "?" */ CWORD_CCTL_CCTL_CWORD, + /* 194 64 "@" */ CWORD_CWORD_CWORD_CWORD, + /* 195 65 "A" */ CWORD_CWORD_CWORD_CWORD, + /* 196 66 "B" */ CWORD_CWORD_CWORD_CWORD, + /* 197 67 "C" */ CWORD_CWORD_CWORD_CWORD, + /* 198 68 "D" */ CWORD_CWORD_CWORD_CWORD, + /* 199 69 "E" */ CWORD_CWORD_CWORD_CWORD, + /* 200 70 "F" */ CWORD_CWORD_CWORD_CWORD, + /* 201 71 "G" */ CWORD_CWORD_CWORD_CWORD, + /* 202 72 "H" */ CWORD_CWORD_CWORD_CWORD, + /* 203 73 "I" */ CWORD_CWORD_CWORD_CWORD, + /* 204 74 "J" */ CWORD_CWORD_CWORD_CWORD, + /* 205 75 "K" */ CWORD_CWORD_CWORD_CWORD, + /* 206 76 "L" */ CWORD_CWORD_CWORD_CWORD, + /* 207 77 "M" */ CWORD_CWORD_CWORD_CWORD, + /* 208 78 "N" */ CWORD_CWORD_CWORD_CWORD, + /* 209 79 "O" */ CWORD_CWORD_CWORD_CWORD, + /* 210 80 "P" */ CWORD_CWORD_CWORD_CWORD, + /* 211 81 "Q" */ CWORD_CWORD_CWORD_CWORD, + /* 212 82 "R" */ CWORD_CWORD_CWORD_CWORD, + /* 213 83 "S" */ CWORD_CWORD_CWORD_CWORD, + /* 214 84 "T" */ CWORD_CWORD_CWORD_CWORD, + /* 215 85 "U" */ CWORD_CWORD_CWORD_CWORD, + /* 216 86 "V" */ CWORD_CWORD_CWORD_CWORD, + /* 217 87 "W" */ CWORD_CWORD_CWORD_CWORD, + /* 218 88 "X" */ CWORD_CWORD_CWORD_CWORD, + /* 219 89 "Y" */ CWORD_CWORD_CWORD_CWORD, + /* 220 90 "Z" */ CWORD_CWORD_CWORD_CWORD, + /* 221 91 "[" */ CWORD_CCTL_CCTL_CWORD, + /* 222 92 "\" */ CBACK_CBACK_CCTL_CBACK, + /* 223 93 "]" */ CWORD_CCTL_CCTL_CWORD, + /* 224 94 "^" */ CWORD_CWORD_CWORD_CWORD, + /* 225 95 "_" */ CWORD_CWORD_CWORD_CWORD, + /* 226 96 "`" */ CBQUOTE_CBQUOTE_CWORD_CBQUOTE, + /* 227 97 "a" */ CWORD_CWORD_CWORD_CWORD, + /* 228 98 "b" */ CWORD_CWORD_CWORD_CWORD, + /* 229 99 "c" */ CWORD_CWORD_CWORD_CWORD, + /* 230 100 "d" */ CWORD_CWORD_CWORD_CWORD, + /* 231 101 "e" */ CWORD_CWORD_CWORD_CWORD, + /* 232 102 "f" */ CWORD_CWORD_CWORD_CWORD, + /* 233 103 "g" */ CWORD_CWORD_CWORD_CWORD, + /* 234 104 "h" */ CWORD_CWORD_CWORD_CWORD, + /* 235 105 "i" */ CWORD_CWORD_CWORD_CWORD, + /* 236 106 "j" */ CWORD_CWORD_CWORD_CWORD, + /* 237 107 "k" */ CWORD_CWORD_CWORD_CWORD, + /* 238 108 "l" */ CWORD_CWORD_CWORD_CWORD, + /* 239 109 "m" */ CWORD_CWORD_CWORD_CWORD, + /* 240 110 "n" */ CWORD_CWORD_CWORD_CWORD, + /* 241 111 "o" */ CWORD_CWORD_CWORD_CWORD, + /* 242 112 "p" */ CWORD_CWORD_CWORD_CWORD, + /* 243 113 "q" */ CWORD_CWORD_CWORD_CWORD, + /* 244 114 "r" */ CWORD_CWORD_CWORD_CWORD, + /* 245 115 "s" */ CWORD_CWORD_CWORD_CWORD, + /* 246 116 "t" */ CWORD_CWORD_CWORD_CWORD, + /* 247 117 "u" */ CWORD_CWORD_CWORD_CWORD, + /* 248 118 "v" */ CWORD_CWORD_CWORD_CWORD, + /* 249 119 "w" */ CWORD_CWORD_CWORD_CWORD, + /* 250 120 "x" */ CWORD_CWORD_CWORD_CWORD, + /* 251 121 "y" */ CWORD_CWORD_CWORD_CWORD, + /* 252 122 "z" */ CWORD_CWORD_CWORD_CWORD, + /* 253 123 "{" */ CWORD_CWORD_CWORD_CWORD, + /* 254 124 "|" */ CSPCL_CWORD_CWORD_CWORD, + /* 255 125 "}" */ CENDVAR_CENDVAR_CWORD_CENDVAR, + /* 256 126 "~" */ CWORD_CCTL_CCTL_CWORD, + /* 257 127 */ CWORD_CWORD_CWORD_CWORD, +}; + +#define SIT(c, syntax) (S_I_T[(int)syntax_index_table[((int)c)+SYNBASE]][syntax]) + +#endif /* USE_SIT_FUNCTION */ + + +/* ============ Alias handling */ + +#if ENABLE_ASH_ALIAS + +#define ALIASINUSE 1 +#define ALIASDEAD 2 + +struct alias { + struct alias *next; + char *name; + char *val; + int flag; +}; + + +static struct alias **atab; // [ATABSIZE]; +#define INIT_G_alias() do { \ + atab = xzalloc(ATABSIZE * sizeof(atab[0])); \ +} while (0) + + +static struct alias ** +__lookupalias(const char *name) { + unsigned int hashval; + struct alias **app; + const char *p; + unsigned int ch; + + p = name; + + ch = (unsigned char)*p; + hashval = ch << 4; + while (ch) { + hashval += ch; + ch = (unsigned char)*++p; + } + app = &atab[hashval % ATABSIZE]; + + for (; *app; app = &(*app)->next) { + if (strcmp(name, (*app)->name) == 0) { + break; + } + } + + return app; +} + +static struct alias * +lookupalias(const char *name, int check) +{ + struct alias *ap = *__lookupalias(name); + + if (check && ap && (ap->flag & ALIASINUSE)) + return NULL; + return ap; +} + +static struct alias * +freealias(struct alias *ap) +{ + struct alias *next; + + if (ap->flag & ALIASINUSE) { + ap->flag |= ALIASDEAD; + return ap; + } + + next = ap->next; + free(ap->name); + free(ap->val); + free(ap); + return next; +} + +static void +setalias(const char *name, const char *val) +{ + struct alias *ap, **app; + + app = __lookupalias(name); + ap = *app; + INT_OFF; + if (ap) { + if (!(ap->flag & ALIASINUSE)) { + free(ap->val); + } + ap->val = ckstrdup(val); + ap->flag &= ~ALIASDEAD; + } else { + /* not found */ + ap = ckzalloc(sizeof(struct alias)); + ap->name = ckstrdup(name); + ap->val = ckstrdup(val); + /*ap->flag = 0; - ckzalloc did it */ + /*ap->next = NULL;*/ + *app = ap; + } + INT_ON; +} + +static int +unalias(const char *name) +{ + struct alias **app; + + app = __lookupalias(name); + + if (*app) { + INT_OFF; + *app = freealias(*app); + INT_ON; + return 0; + } + + return 1; +} + +static void +rmaliases(void) +{ + struct alias *ap, **app; + int i; + + INT_OFF; + for (i = 0; i < ATABSIZE; i++) { + app = &atab[i]; + for (ap = *app; ap; ap = *app) { + *app = freealias(*app); + if (ap == *app) { + app = &ap->next; + } + } + } + INT_ON; +} + +static void +printalias(const struct alias *ap) +{ + out1fmt("%s=%s\n", ap->name, single_quote(ap->val)); +} + +/* + * TODO - sort output + */ +static int +aliascmd(int argc UNUSED_PARAM, char **argv) +{ + char *n, *v; + int ret = 0; + struct alias *ap; + + if (!argv[1]) { + int i; + + for (i = 0; i < ATABSIZE; i++) { + for (ap = atab[i]; ap; ap = ap->next) { + printalias(ap); + } + } + return 0; + } + while ((n = *++argv) != NULL) { + v = strchr(n+1, '='); + if (v == NULL) { /* n+1: funny ksh stuff */ + ap = *__lookupalias(n); + if (ap == NULL) { + fprintf(stderr, "%s: %s not found\n", "alias", n); + ret = 1; + } else + printalias(ap); + } else { + *v++ = '\0'; + setalias(n, v); + } + } + + return ret; +} + +static int +unaliascmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) +{ + int i; + + while ((i = nextopt("a")) != '\0') { + if (i == 'a') { + rmaliases(); + return 0; + } + } + for (i = 0; *argptr; argptr++) { + if (unalias(*argptr)) { + fprintf(stderr, "%s: %s not found\n", "unalias", *argptr); + i = 1; + } + } + + return i; +} + +#endif /* ASH_ALIAS */ + + +/* ============ jobs.c */ + +/* Mode argument to forkshell. Don't change FORK_FG or FORK_BG. */ +#define FORK_FG 0 +#define FORK_BG 1 +#define FORK_NOJOB 2 + +/* mode flags for showjob(s) */ +#define SHOW_PGID 0x01 /* only show pgid - for jobs -p */ +#define SHOW_PID 0x04 /* include process pid */ +#define SHOW_CHANGED 0x08 /* only jobs whose state has changed */ + +/* + * A job structure contains information about a job. A job is either a + * single process or a set of processes contained in a pipeline. In the + * latter case, pidlist will be non-NULL, and will point to a -1 terminated + * array of pids. + */ + +struct procstat { + pid_t pid; /* process id */ + int status; /* last process status from wait() */ + char *cmd; /* text of command being run */ +}; + +struct job { + struct procstat ps0; /* status of process */ + struct procstat *ps; /* status or processes when more than one */ +#if JOBS + int stopstatus; /* status of a stopped job */ +#endif + uint32_t + nprocs: 16, /* number of processes */ + state: 8, +#define JOBRUNNING 0 /* at least one proc running */ +#define JOBSTOPPED 1 /* all procs are stopped */ +#define JOBDONE 2 /* all procs are completed */ +#if JOBS + sigint: 1, /* job was killed by SIGINT */ + jobctl: 1, /* job running under job control */ +#endif + waited: 1, /* true if this entry has been waited for */ + used: 1, /* true if this entry is in used */ + changed: 1; /* true if status has changed */ + struct job *prev_job; /* previous job */ +}; + +static struct job *makejob(/*union node *,*/ int); +#if !JOBS +#define forkshell(job, node, mode) forkshell(job, mode) +#endif +static int forkshell(struct job *, union node *, int); +static int waitforjob(struct job *); + +#if !JOBS +enum { doing_jobctl = 0 }; +#define setjobctl(on) do {} while (0) +#else +static smallint doing_jobctl; //references:8 +static void setjobctl(int); +#endif + +/* + * Set the signal handler for the specified signal. The routine figures + * out what it should be set to. + */ +static void +setsignal(int signo) +{ + int action; + char *t, tsig; + struct sigaction act; + + t = trap[signo]; + action = S_IGN; + if (t == NULL) + action = S_DFL; + else if (*t != '\0') + action = S_CATCH; + if (rootshell && action == S_DFL) { + switch (signo) { + case SIGINT: + if (iflag || minusc || sflag == 0) + action = S_CATCH; + break; + case SIGQUIT: +#if DEBUG + if (debug) + break; +#endif + /* FALLTHROUGH */ + case SIGTERM: + if (iflag) + action = S_IGN; + break; +#if JOBS + case SIGTSTP: + case SIGTTOU: + if (mflag) + action = S_IGN; + break; +#endif + } + } + + t = &sigmode[signo - 1]; + tsig = *t; + if (tsig == 0) { + /* + * current setting unknown + */ + if (sigaction(signo, NULL, &act) == -1) { + /* + * Pretend it worked; maybe we should give a warning + * here, but other shells don't. We don't alter + * sigmode, so that we retry every time. + */ + return; + } + tsig = S_RESET; /* force to be set */ + if (act.sa_handler == SIG_IGN) { + tsig = S_HARD_IGN; + if (mflag + && (signo == SIGTSTP || signo == SIGTTIN || signo == SIGTTOU) + ) { + tsig = S_IGN; /* don't hard ignore these */ + } + } + } + if (tsig == S_HARD_IGN || tsig == action) + return; + act.sa_handler = SIG_DFL; + switch (action) { + case S_CATCH: + act.sa_handler = onsig; + break; + case S_IGN: + act.sa_handler = SIG_IGN; + break; + } + *t = action; + act.sa_flags = 0; + sigfillset(&act.sa_mask); + sigaction_set(signo, &act); +} + +/* mode flags for set_curjob */ +#define CUR_DELETE 2 +#define CUR_RUNNING 1 +#define CUR_STOPPED 0 + +/* mode flags for dowait */ +#define DOWAIT_NONBLOCK WNOHANG +#define DOWAIT_BLOCK 0 + +#if JOBS +/* pgrp of shell on invocation */ +static int initialpgrp; //references:2 +static int ttyfd = -1; //5 +#endif +/* array of jobs */ +static struct job *jobtab; //5 +/* size of array */ +static unsigned njobs; //4 +/* current job */ +static struct job *curjob; //lots +/* number of presumed living untracked jobs */ +static int jobless; //4 + +static void +set_curjob(struct job *jp, unsigned mode) +{ + struct job *jp1; + struct job **jpp, **curp; + + /* first remove from list */ + jpp = curp = &curjob; + do { + jp1 = *jpp; + if (jp1 == jp) + break; + jpp = &jp1->prev_job; + } while (1); + *jpp = jp1->prev_job; + + /* Then re-insert in correct position */ + jpp = curp; + switch (mode) { + default: +#if DEBUG + abort(); +#endif + case CUR_DELETE: + /* job being deleted */ + break; + case CUR_RUNNING: + /* newly created job or backgrounded job, + put after all stopped jobs. */ + do { + jp1 = *jpp; +#if JOBS + if (!jp1 || jp1->state != JOBSTOPPED) +#endif + break; + jpp = &jp1->prev_job; + } while (1); + /* FALLTHROUGH */ +#if JOBS + case CUR_STOPPED: +#endif + /* newly stopped job - becomes curjob */ + jp->prev_job = *jpp; + *jpp = jp; + break; + } +} + +#if JOBS || DEBUG +static int +jobno(const struct job *jp) +{ + return jp - jobtab + 1; +} +#endif + +/* + * Convert a job name to a job structure. + */ +#if !JOBS +#define getjob(name, getctl) getjob(name) +#endif +static struct job * +getjob(const char *name, int getctl) +{ + struct job *jp; + struct job *found; + const char *err_msg = "No such job: %s"; + unsigned num; + int c; + const char *p; + char *(*match)(const char *, const char *); + + jp = curjob; + p = name; + if (!p) + goto currentjob; + + if (*p != '%') + goto err; + + c = *++p; + if (!c) + goto currentjob; + + if (!p[1]) { + if (c == '+' || c == '%') { + currentjob: + err_msg = "No current job"; + goto check; + } + if (c == '-') { + if (jp) + jp = jp->prev_job; + err_msg = "No previous job"; + check: + if (!jp) + goto err; + goto gotit; + } + } + + if (is_number(p)) { +// TODO: number() instead? It does error checking... + num = atoi(p); + if (num < njobs) { + jp = jobtab + num - 1; + if (jp->used) + goto gotit; + goto err; + } + } + + match = prefix; + if (*p == '?') { + match = strstr; + p++; + } + + found = 0; + while (1) { + if (!jp) + goto err; + if (match(jp->ps[0].cmd, p)) { + if (found) + goto err; + found = jp; + err_msg = "%s: ambiguous"; + } + jp = jp->prev_job; + } + + gotit: +#if JOBS + err_msg = "job %s not created under job control"; + if (getctl && jp->jobctl == 0) + goto err; +#endif + return jp; + err: + ash_msg_and_raise_error(err_msg, name); +} + +/* + * Mark a job structure as unused. + */ +static void +freejob(struct job *jp) +{ + struct procstat *ps; + int i; + + INT_OFF; + for (i = jp->nprocs, ps = jp->ps; --i >= 0; ps++) { + if (ps->cmd != nullstr) + free(ps->cmd); + } + if (jp->ps != &jp->ps0) + free(jp->ps); + jp->used = 0; + set_curjob(jp, CUR_DELETE); + INT_ON; +} + +#if JOBS +static void +xtcsetpgrp(int fd, pid_t pgrp) +{ + if (tcsetpgrp(fd, pgrp)) + ash_msg_and_raise_error("can't set tty process group (%m)"); +} + +/* + * Turn job control on and off. + * + * Note: This code assumes that the third arg to ioctl is a character + * pointer, which is true on Berkeley systems but not System V. Since + * System V doesn't have job control yet, this isn't a problem now. + * + * Called with interrupts off. + */ +static void +setjobctl(int on) +{ + int fd; + int pgrp; + + if (on == doing_jobctl || rootshell == 0) + return; + if (on) { + int ofd; + ofd = fd = open(_PATH_TTY, O_RDWR); + if (fd < 0) { + /* BTW, bash will try to open(ttyname(0)) if open("/dev/tty") fails. + * That sometimes helps to acquire controlling tty. + * Obviously, a workaround for bugs when someone + * failed to provide a controlling tty to bash! :) */ + fd = 2; + while (!isatty(fd)) + if (--fd < 0) + goto out; + } + fd = fcntl(fd, F_DUPFD, 10); + if (ofd >= 0) + close(ofd); + if (fd < 0) + goto out; + /* fd is a tty at this point */ + close_on_exec_on(fd); + do { /* while we are in the background */ + pgrp = tcgetpgrp(fd); + if (pgrp < 0) { + out: + ash_msg("can't access tty; job control turned off"); + mflag = on = 0; + goto close; + } + if (pgrp == getpgrp()) + break; + killpg(0, SIGTTIN); + } while (1); + initialpgrp = pgrp; + + setsignal(SIGTSTP); + setsignal(SIGTTOU); + setsignal(SIGTTIN); + pgrp = rootpid; + setpgid(0, pgrp); + xtcsetpgrp(fd, pgrp); + } else { + /* turning job control off */ + fd = ttyfd; + pgrp = initialpgrp; + /* was xtcsetpgrp, but this can make exiting ash + * loop forever if pty is already deleted */ + tcsetpgrp(fd, pgrp); + setpgid(0, pgrp); + setsignal(SIGTSTP); + setsignal(SIGTTOU); + setsignal(SIGTTIN); + close: + if (fd >= 0) + close(fd); + fd = -1; + } + ttyfd = fd; + doing_jobctl = on; +} + +static int +killcmd(int argc, char **argv) +{ + int i = 1; + if (argv[1] && strcmp(argv[1], "-l") != 0) { + do { + if (argv[i][0] == '%') { + struct job *jp = getjob(argv[i], 0); + unsigned pid = jp->ps[0].pid; + /* Enough space for ' -NNN' */ + argv[i] = alloca(sizeof(int)*3 + 3); + /* kill_main has matching code to expect + * leading space. Needed to not confuse + * negative pids with "kill -SIGNAL_NO" syntax */ + sprintf(argv[i], " -%u", pid); + } + } while (argv[++i]); + } + return kill_main(argc, argv); +} + +static void +showpipe(struct job *jp, FILE *out) +{ + struct procstat *sp; + struct procstat *spend; + + spend = jp->ps + jp->nprocs; + for (sp = jp->ps + 1; sp < spend; sp++) + fprintf(out, " | %s", sp->cmd); + outcslow('\n', out); + flush_stdout_stderr(); +} + + +static int +restartjob(struct job *jp, int mode) +{ + struct procstat *ps; + int i; + int status; + pid_t pgid; + + INT_OFF; + if (jp->state == JOBDONE) + goto out; + jp->state = JOBRUNNING; + pgid = jp->ps->pid; + if (mode == FORK_FG) + xtcsetpgrp(ttyfd, pgid); + killpg(pgid, SIGCONT); + ps = jp->ps; + i = jp->nprocs; + do { + if (WIFSTOPPED(ps->status)) { + ps->status = -1; + } + ps++; + } while (--i); + out: + status = (mode == FORK_FG) ? waitforjob(jp) : 0; + INT_ON; + return status; +} + +static int +fg_bgcmd(int argc UNUSED_PARAM, char **argv) +{ + struct job *jp; + FILE *out; + int mode; + int retval; + + mode = (**argv == 'f') ? FORK_FG : FORK_BG; + nextopt(nullstr); + argv = argptr; + out = stdout; + do { + jp = getjob(*argv, 1); + if (mode == FORK_BG) { + set_curjob(jp, CUR_RUNNING); + fprintf(out, "[%d] ", jobno(jp)); + } + outstr(jp->ps->cmd, out); + showpipe(jp, out); + retval = restartjob(jp, mode); + } while (*argv && *++argv); + return retval; +} +#endif + +static int +sprint_status(char *s, int status, int sigonly) +{ + int col; + int st; + + col = 0; + if (!WIFEXITED(status)) { +#if JOBS + if (WIFSTOPPED(status)) + st = WSTOPSIG(status); + else +#endif + st = WTERMSIG(status); + if (sigonly) { + if (st == SIGINT || st == SIGPIPE) + goto out; +#if JOBS + if (WIFSTOPPED(status)) + goto out; +#endif + } + st &= 0x7f; + col = fmtstr(s, 32, strsignal(st)); + if (WCOREDUMP(status)) { + col += fmtstr(s + col, 16, " (core dumped)"); + } + } else if (!sigonly) { + st = WEXITSTATUS(status); + if (st) + col = fmtstr(s, 16, "Done(%d)", st); + else + col = fmtstr(s, 16, "Done"); + } + out: + return col; +} + +static int +dowait(int wait_flags, struct job *job) +{ + int pid; + int status; + struct job *jp; + struct job *thisjob; + int state; + + TRACE(("dowait(0x%x) called\n", wait_flags)); + + /* Do a wait system call. If job control is compiled in, we accept + * stopped processes. wait_flags may have WNOHANG, preventing blocking. + * NB: _not_ safe_waitpid, we need to detect EINTR */ + pid = waitpid(-1, &status, + (doing_jobctl ? (wait_flags | WUNTRACED) : wait_flags)); + TRACE(("wait returns pid=%d, status=0x%x\n", pid, status)); + + if (pid <= 0) { + /* If we were doing blocking wait and (probably) got EINTR, + * check for pending sigs received while waiting. + * (NB: can be moved into callers if needed) */ + if (wait_flags == DOWAIT_BLOCK && pendingsig) + raise_exception(EXSIG); + return pid; + } + INT_OFF; + thisjob = NULL; + for (jp = curjob; jp; jp = jp->prev_job) { + struct procstat *sp; + struct procstat *spend; + if (jp->state == JOBDONE) + continue; + state = JOBDONE; + spend = jp->ps + jp->nprocs; + sp = jp->ps; + do { + if (sp->pid == pid) { + TRACE(("Job %d: changing status of proc %d " + "from 0x%x to 0x%x\n", + jobno(jp), pid, sp->status, status)); + sp->status = status; + thisjob = jp; + } + if (sp->status == -1) + state = JOBRUNNING; +#if JOBS + if (state == JOBRUNNING) + continue; + if (WIFSTOPPED(sp->status)) { + jp->stopstatus = sp->status; + state = JOBSTOPPED; + } +#endif + } while (++sp < spend); + if (thisjob) + goto gotjob; + } +#if JOBS + if (!WIFSTOPPED(status)) +#endif + jobless--; + goto out; + + gotjob: + if (state != JOBRUNNING) { + thisjob->changed = 1; + + if (thisjob->state != state) { + TRACE(("Job %d: changing state from %d to %d\n", + jobno(thisjob), thisjob->state, state)); + thisjob->state = state; +#if JOBS + if (state == JOBSTOPPED) { + set_curjob(thisjob, CUR_STOPPED); + } +#endif + } + } + + out: + INT_ON; + + if (thisjob && thisjob == job) { + char s[48 + 1]; + int len; + + len = sprint_status(s, status, 1); + if (len) { + s[len] = '\n'; + s[len + 1] = '\0'; + out2str(s); + } + } + return pid; +} + +#if JOBS +static void +showjob(FILE *out, struct job *jp, int mode) +{ + struct procstat *ps; + struct procstat *psend; + int col; + int indent_col; + char s[80]; + + ps = jp->ps; + + if (mode & SHOW_PGID) { + /* just output process (group) id of pipeline */ + fprintf(out, "%d\n", ps->pid); + return; + } + + col = fmtstr(s, 16, "[%d] ", jobno(jp)); + indent_col = col; + + if (jp == curjob) + s[col - 2] = '+'; + else if (curjob && jp == curjob->prev_job) + s[col - 2] = '-'; + + if (mode & SHOW_PID) + col += fmtstr(s + col, 16, "%d ", ps->pid); + + psend = ps + jp->nprocs; + + if (jp->state == JOBRUNNING) { + strcpy(s + col, "Running"); + col += sizeof("Running") - 1; + } else { + int status = psend[-1].status; + if (jp->state == JOBSTOPPED) + status = jp->stopstatus; + col += sprint_status(s + col, status, 0); + } + + goto start; + + do { + /* for each process */ + col = fmtstr(s, 48, " |\n%*c%d ", indent_col, ' ', ps->pid) - 3; + start: + fprintf(out, "%s%*c%s", + s, 33 - col >= 0 ? 33 - col : 0, ' ', ps->cmd + ); + if (!(mode & SHOW_PID)) { + showpipe(jp, out); + break; + } + if (++ps == psend) { + outcslow('\n', out); + break; + } + } while (1); + + jp->changed = 0; + + if (jp->state == JOBDONE) { + TRACE(("showjob: freeing job %d\n", jobno(jp))); + freejob(jp); + } +} + +/* + * Print a list of jobs. If "change" is nonzero, only print jobs whose + * statuses have changed since the last call to showjobs. + */ +static void +showjobs(FILE *out, int mode) +{ + struct job *jp; + + TRACE(("showjobs(%x) called\n", mode)); + + /* If not even one job changed, there is nothing to do */ + while (dowait(DOWAIT_NONBLOCK, NULL) > 0) + continue; + + for (jp = curjob; jp; jp = jp->prev_job) { + if (!(mode & SHOW_CHANGED) || jp->changed) { + showjob(out, jp, mode); + } + } +} + +static int +jobscmd(int argc UNUSED_PARAM, char **argv) +{ + int mode, m; + + mode = 0; + while ((m = nextopt("lp"))) { + if (m == 'l') + mode = SHOW_PID; + else + mode = SHOW_PGID; + } + + argv = argptr; + if (*argv) { + do + showjob(stdout, getjob(*argv,0), mode); + while (*++argv); + } else + showjobs(stdout, mode); + + return 0; +} +#endif /* JOBS */ + +static int +getstatus(struct job *job) +{ + int status; + int retval; + + status = job->ps[job->nprocs - 1].status; + retval = WEXITSTATUS(status); + if (!WIFEXITED(status)) { +#if JOBS + retval = WSTOPSIG(status); + if (!WIFSTOPPED(status)) +#endif + { + /* XXX: limits number of signals */ + retval = WTERMSIG(status); +#if JOBS + if (retval == SIGINT) + job->sigint = 1; +#endif + } + retval += 128; + } + TRACE(("getstatus: job %d, nproc %d, status %x, retval %x\n", + jobno(job), job->nprocs, status, retval)); + return retval; +} + +static int +waitcmd(int argc UNUSED_PARAM, char **argv) +{ + struct job *job; + int retval; + struct job *jp; + +// exsig++; +// xbarrier(); + if (pendingsig) + raise_exception(EXSIG); + + nextopt(nullstr); + retval = 0; + + argv = argptr; + if (!*argv) { + /* wait for all jobs */ + for (;;) { + jp = curjob; + while (1) { + if (!jp) /* no running procs */ + goto ret; + if (jp->state == JOBRUNNING) + break; + jp->waited = 1; + jp = jp->prev_job; + } + dowait(DOWAIT_BLOCK, NULL); + } + } + + retval = 127; + do { + if (**argv != '%') { + pid_t pid = number(*argv); + job = curjob; + while (1) { + if (!job) + goto repeat; + if (job->ps[job->nprocs - 1].pid == pid) + break; + job = job->prev_job; + } + } else + job = getjob(*argv, 0); + /* loop until process terminated or stopped */ + while (job->state == JOBRUNNING) + dowait(DOWAIT_BLOCK, NULL); + job->waited = 1; + retval = getstatus(job); + repeat: + ; + } while (*++argv); + + ret: + return retval; +} + +static struct job * +growjobtab(void) +{ + size_t len; + ptrdiff_t offset; + struct job *jp, *jq; + + len = njobs * sizeof(*jp); + jq = jobtab; + jp = ckrealloc(jq, len + 4 * sizeof(*jp)); + + offset = (char *)jp - (char *)jq; + if (offset) { + /* Relocate pointers */ + size_t l = len; + + jq = (struct job *)((char *)jq + l); + while (l) { + l -= sizeof(*jp); + jq--; +#define joff(p) ((struct job *)((char *)(p) + l)) +#define jmove(p) (p) = (void *)((char *)(p) + offset) + if (joff(jp)->ps == &jq->ps0) + jmove(joff(jp)->ps); + if (joff(jp)->prev_job) + jmove(joff(jp)->prev_job); + } + if (curjob) + jmove(curjob); +#undef joff +#undef jmove + } + + njobs += 4; + jobtab = jp; + jp = (struct job *)((char *)jp + len); + jq = jp + 3; + do { + jq->used = 0; + } while (--jq >= jp); + return jp; +} + +/* + * Return a new job structure. + * Called with interrupts off. + */ +static struct job * +makejob(/*union node *node,*/ int nprocs) +{ + int i; + struct job *jp; + + for (i = njobs, jp = jobtab; ; jp++) { + if (--i < 0) { + jp = growjobtab(); + break; + } + if (jp->used == 0) + break; + if (jp->state != JOBDONE || !jp->waited) + continue; +#if JOBS + if (doing_jobctl) + continue; +#endif + freejob(jp); + break; + } + memset(jp, 0, sizeof(*jp)); +#if JOBS + /* jp->jobctl is a bitfield. + * "jp->jobctl |= jobctl" likely to give awful code */ + if (doing_jobctl) + jp->jobctl = 1; +#endif + jp->prev_job = curjob; + curjob = jp; + jp->used = 1; + jp->ps = &jp->ps0; + if (nprocs > 1) { + jp->ps = ckmalloc(nprocs * sizeof(struct procstat)); + } + TRACE(("makejob(%d) returns %%%d\n", nprocs, + jobno(jp))); + return jp; +} + +#if JOBS +/* + * Return a string identifying a command (to be printed by the + * jobs command). + */ +static char *cmdnextc; + +static void +cmdputs(const char *s) +{ + static const char vstype[VSTYPE + 1][3] = { + "", "}", "-", "+", "?", "=", + "%", "%%", "#", "##" + USE_ASH_BASH_COMPAT(, ":", "/", "//") + }; + + const char *p, *str; + char c, cc[2] = " "; + char *nextc; + int subtype = 0; + int quoted = 0; + + nextc = makestrspace((strlen(s) + 1) * 8, cmdnextc); + p = s; + while ((c = *p++) != 0) { + str = NULL; + switch (c) { + case CTLESC: + c = *p++; + break; + case CTLVAR: + subtype = *p++; + if ((subtype & VSTYPE) == VSLENGTH) + str = "${#"; + else + str = "${"; + if (!(subtype & VSQUOTE) == !(quoted & 1)) + goto dostr; + quoted ^= 1; + c = '"'; + break; + case CTLENDVAR: + str = "\"}" + !(quoted & 1); + quoted >>= 1; + subtype = 0; + goto dostr; + case CTLBACKQ: + str = "$(...)"; + goto dostr; + case CTLBACKQ+CTLQUOTE: + str = "\"$(...)\""; + goto dostr; +#if ENABLE_ASH_MATH_SUPPORT + case CTLARI: + str = "$(("; + goto dostr; + case CTLENDARI: + str = "))"; + goto dostr; +#endif + case CTLQUOTEMARK: + quoted ^= 1; + c = '"'; + break; + case '=': + if (subtype == 0) + break; + if ((subtype & VSTYPE) != VSNORMAL) + quoted <<= 1; + str = vstype[subtype & VSTYPE]; + if (subtype & VSNUL) + c = ':'; + else + goto checkstr; + break; + case '\'': + case '\\': + case '"': + case '$': + /* These can only happen inside quotes */ + cc[0] = c; + str = cc; + c = '\\'; + break; + default: + break; + } + USTPUTC(c, nextc); + checkstr: + if (!str) + continue; + dostr: + while ((c = *str++)) { + USTPUTC(c, nextc); + } + } + if (quoted & 1) { + USTPUTC('"', nextc); + } + *nextc = 0; + cmdnextc = nextc; +} + +/* cmdtxt() and cmdlist() call each other */ +static void cmdtxt(union node *n); + +static void +cmdlist(union node *np, int sep) +{ + for (; np; np = np->narg.next) { + if (!sep) + cmdputs(" "); + cmdtxt(np); + if (sep && np->narg.next) + cmdputs(" "); + } +} + +static void +cmdtxt(union node *n) +{ + union node *np; + struct nodelist *lp; + const char *p; + + if (!n) + return; + switch (n->type) { + default: +#if DEBUG + abort(); +#endif + case NPIPE: + lp = n->npipe.cmdlist; + for (;;) { + cmdtxt(lp->n); + lp = lp->next; + if (!lp) + break; + cmdputs(" | "); + } + break; + case NSEMI: + p = "; "; + goto binop; + case NAND: + p = " && "; + goto binop; + case NOR: + p = " || "; + binop: + cmdtxt(n->nbinary.ch1); + cmdputs(p); + n = n->nbinary.ch2; + goto donode; + case NREDIR: + case NBACKGND: + n = n->nredir.n; + goto donode; + case NNOT: + cmdputs("!"); + n = n->nnot.com; + donode: + cmdtxt(n); + break; + case NIF: + cmdputs("if "); + cmdtxt(n->nif.test); + cmdputs("; then "); + n = n->nif.ifpart; + if (n->nif.elsepart) { + cmdtxt(n); + cmdputs("; else "); + n = n->nif.elsepart; + } + p = "; fi"; + goto dotail; + case NSUBSHELL: + cmdputs("("); + n = n->nredir.n; + p = ")"; + goto dotail; + case NWHILE: + p = "while "; + goto until; + case NUNTIL: + p = "until "; + until: + cmdputs(p); + cmdtxt(n->nbinary.ch1); + n = n->nbinary.ch2; + p = "; done"; + dodo: + cmdputs("; do "); + dotail: + cmdtxt(n); + goto dotail2; + case NFOR: + cmdputs("for "); + cmdputs(n->nfor.var); + cmdputs(" in "); + cmdlist(n->nfor.args, 1); + n = n->nfor.body; + p = "; done"; + goto dodo; + case NDEFUN: + cmdputs(n->narg.text); + p = "() { ... }"; + goto dotail2; + case NCMD: + cmdlist(n->ncmd.args, 1); + cmdlist(n->ncmd.redirect, 0); + break; + case NARG: + p = n->narg.text; + dotail2: + cmdputs(p); + break; + case NHERE: + case NXHERE: + p = "<<..."; + goto dotail2; + case NCASE: + cmdputs("case "); + cmdputs(n->ncase.expr->narg.text); + cmdputs(" in "); + for (np = n->ncase.cases; np; np = np->nclist.next) { + cmdtxt(np->nclist.pattern); + cmdputs(") "); + cmdtxt(np->nclist.body); + cmdputs(";; "); + } + p = "esac"; + goto dotail2; + case NTO: + p = ">"; + goto redir; + case NCLOBBER: + p = ">|"; + goto redir; + case NAPPEND: + p = ">>"; + goto redir; +#if ENABLE_ASH_BASH_COMPAT + case NTO2: +#endif + case NTOFD: + p = ">&"; + goto redir; + case NFROM: + p = "<"; + goto redir; + case NFROMFD: + p = "<&"; + goto redir; + case NFROMTO: + p = "<>"; + redir: + cmdputs(utoa(n->nfile.fd)); + cmdputs(p); + if (n->type == NTOFD || n->type == NFROMFD) { + cmdputs(utoa(n->ndup.dupfd)); + break; + } + n = n->nfile.fname; + goto donode; + } +} + +static char * +commandtext(union node *n) +{ + char *name; + + STARTSTACKSTR(cmdnextc); + cmdtxt(n); + name = stackblock(); + TRACE(("commandtext: name %p, end %p\n\t\"%s\"\n", + name, cmdnextc, cmdnextc)); + return ckstrdup(name); +} +#endif /* JOBS */ + +/* + * Fork off a subshell. If we are doing job control, give the subshell its + * own process group. Jp is a job structure that the job is to be added to. + * N is the command that will be evaluated by the child. Both jp and n may + * be NULL. The mode parameter can be one of the following: + * FORK_FG - Fork off a foreground process. + * FORK_BG - Fork off a background process. + * FORK_NOJOB - Like FORK_FG, but don't give the process its own + * process group even if job control is on. + * + * When job control is turned off, background processes have their standard + * input redirected to /dev/null (except for the second and later processes + * in a pipeline). + * + * Called with interrupts off. + */ +/* + * Clear traps on a fork. + */ +static void +clear_traps(void) +{ + char **tp; + + for (tp = trap; tp < &trap[NSIG]; tp++) { + if (*tp && **tp) { /* trap not NULL or "" (SIG_IGN) */ + INT_OFF; + free(*tp); + *tp = NULL; + if (tp != &trap[0]) + setsignal(tp - trap); + INT_ON; + } + } +} + +/* Lives far away from here, needed for forkchild */ +static void closescript(void); + +/* Called after fork(), in child */ +static void +forkchild(struct job *jp, /*union node *n,*/ int mode) +{ + int oldlvl; + + TRACE(("Child shell %d\n", getpid())); + oldlvl = shlvl; + shlvl++; + + closescript(); + clear_traps(); +#if JOBS + /* do job control only in root shell */ + doing_jobctl = 0; + if (mode != FORK_NOJOB && jp->jobctl && !oldlvl) { + pid_t pgrp; + + if (jp->nprocs == 0) + pgrp = getpid(); + else + pgrp = jp->ps[0].pid; + /* This can fail because we are doing it in the parent also */ + (void)setpgid(0, pgrp); + if (mode == FORK_FG) + xtcsetpgrp(ttyfd, pgrp); + setsignal(SIGTSTP); + setsignal(SIGTTOU); + } else +#endif + if (mode == FORK_BG) { + ignoresig(SIGINT); + ignoresig(SIGQUIT); + if (jp->nprocs == 0) { + close(0); + if (open(bb_dev_null, O_RDONLY) != 0) + ash_msg_and_raise_error("can't open %s", bb_dev_null); + } + } + if (!oldlvl && iflag) { + setsignal(SIGINT); + setsignal(SIGQUIT); + setsignal(SIGTERM); + } + for (jp = curjob; jp; jp = jp->prev_job) + freejob(jp); + jobless = 0; +} + +/* Called after fork(), in parent */ +#if !JOBS +#define forkparent(jp, n, mode, pid) forkparent(jp, mode, pid) +#endif +static void +forkparent(struct job *jp, union node *n, int mode, pid_t pid) +{ + TRACE(("In parent shell: child = %d\n", pid)); + if (!jp) { + while (jobless && dowait(DOWAIT_NONBLOCK, NULL) > 0) + continue; + jobless++; + return; + } +#if JOBS + if (mode != FORK_NOJOB && jp->jobctl) { + int pgrp; + + if (jp->nprocs == 0) + pgrp = pid; + else + pgrp = jp->ps[0].pid; + /* This can fail because we are doing it in the child also */ + setpgid(pid, pgrp); + } +#endif + if (mode == FORK_BG) { + backgndpid = pid; /* set $! */ + set_curjob(jp, CUR_RUNNING); + } + if (jp) { + struct procstat *ps = &jp->ps[jp->nprocs++]; + ps->pid = pid; + ps->status = -1; + ps->cmd = nullstr; +#if JOBS + if (doing_jobctl && n) + ps->cmd = commandtext(n); +#endif + } +} + +static int +forkshell(struct job *jp, union node *n, int mode) +{ + int pid; + + TRACE(("forkshell(%%%d, %p, %d) called\n", jobno(jp), n, mode)); + pid = fork(); + if (pid < 0) { + TRACE(("Fork failed, errno=%d", errno)); + if (jp) + freejob(jp); + ash_msg_and_raise_error("can't fork"); + } + if (pid == 0) + forkchild(jp, /*n,*/ mode); + else + forkparent(jp, n, mode, pid); + return pid; +} + +/* + * Wait for job to finish. + * + * Under job control we have the problem that while a child process is + * running interrupts generated by the user are sent to the child but not + * to the shell. This means that an infinite loop started by an inter- + * active user may be hard to kill. With job control turned off, an + * interactive user may place an interactive program inside a loop. If + * the interactive program catches interrupts, the user doesn't want + * these interrupts to also abort the loop. The approach we take here + * is to have the shell ignore interrupt signals while waiting for a + * foreground process to terminate, and then send itself an interrupt + * signal if the child process was terminated by an interrupt signal. + * Unfortunately, some programs want to do a bit of cleanup and then + * exit on interrupt; unless these processes terminate themselves by + * sending a signal to themselves (instead of calling exit) they will + * confuse this approach. + * + * Called with interrupts off. + */ +static int +waitforjob(struct job *jp) +{ + int st; + + TRACE(("waitforjob(%%%d) called\n", jobno(jp))); + while (jp->state == JOBRUNNING) { + dowait(DOWAIT_BLOCK, jp); + } + st = getstatus(jp); +#if JOBS + if (jp->jobctl) { + xtcsetpgrp(ttyfd, rootpid); + /* + * This is truly gross. + * If we're doing job control, then we did a TIOCSPGRP which + * caused us (the shell) to no longer be in the controlling + * session -- so we wouldn't have seen any ^C/SIGINT. So, we + * intuit from the subprocess exit status whether a SIGINT + * occurred, and if so interrupt ourselves. Yuck. - mycroft + */ + if (jp->sigint) /* TODO: do the same with all signals */ + raise(SIGINT); /* ... by raise(jp->sig) instead? */ + } + if (jp->state == JOBDONE) +#endif + freejob(jp); + return st; +} + +/* + * return 1 if there are stopped jobs, otherwise 0 + */ +static int +stoppedjobs(void) +{ + struct job *jp; + int retval; + + retval = 0; + if (job_warning) + goto out; + jp = curjob; + if (jp && jp->state == JOBSTOPPED) { + out2str("You have stopped jobs.\n"); + job_warning = 2; + retval++; + } + out: + return retval; +} + + +/* ============ redir.c + * + * Code for dealing with input/output redirection. + */ + +#define EMPTY -2 /* marks an unused slot in redirtab */ +#define CLOSED -3 /* marks a slot of previously-closed fd */ + +/* + * Open a file in noclobber mode. + * The code was copied from bash. + */ +static int +noclobberopen(const char *fname) +{ + int r, fd; + struct stat finfo, finfo2; + + /* + * If the file exists and is a regular file, return an error + * immediately. + */ + r = stat(fname, &finfo); + if (r == 0 && S_ISREG(finfo.st_mode)) { + errno = EEXIST; + return -1; + } + + /* + * If the file was not present (r != 0), make sure we open it + * exclusively so that if it is created before we open it, our open + * will fail. Make sure that we do not truncate an existing file. + * Note that we don't turn on O_EXCL unless the stat failed -- if the + * file was not a regular file, we leave O_EXCL off. + */ + if (r != 0) + return open(fname, O_WRONLY|O_CREAT|O_EXCL, 0666); + fd = open(fname, O_WRONLY|O_CREAT, 0666); + + /* If the open failed, return the file descriptor right away. */ + if (fd < 0) + return fd; + + /* + * OK, the open succeeded, but the file may have been changed from a + * non-regular file to a regular file between the stat and the open. + * We are assuming that the O_EXCL open handles the case where FILENAME + * did not exist and is symlinked to an existing file between the stat + * and open. + */ + + /* + * If we can open it and fstat the file descriptor, and neither check + * revealed that it was a regular file, and the file has not been + * replaced, return the file descriptor. + */ + if (fstat(fd, &finfo2) == 0 && !S_ISREG(finfo2.st_mode) + && finfo.st_dev == finfo2.st_dev && finfo.st_ino == finfo2.st_ino) + return fd; + + /* The file has been replaced. badness. */ + close(fd); + errno = EEXIST; + return -1; +} + +/* + * Handle here documents. Normally we fork off a process to write the + * data to a pipe. If the document is short, we can stuff the data in + * the pipe without forking. + */ +/* openhere needs this forward reference */ +static void expandhere(union node *arg, int fd); +static int +openhere(union node *redir) +{ + int pip[2]; + size_t len = 0; + + if (pipe(pip) < 0) + ash_msg_and_raise_error("pipe call failed"); + if (redir->type == NHERE) { + len = strlen(redir->nhere.doc->narg.text); + if (len <= PIPE_BUF) { + full_write(pip[1], redir->nhere.doc->narg.text, len); + goto out; + } + } + if (forkshell((struct job *)NULL, (union node *)NULL, FORK_NOJOB) == 0) { + /* child */ + close(pip[0]); + signal(SIGINT, SIG_IGN); + signal(SIGQUIT, SIG_IGN); + signal(SIGHUP, SIG_IGN); +#ifdef SIGTSTP + signal(SIGTSTP, SIG_IGN); +#endif + signal(SIGPIPE, SIG_DFL); + if (redir->type == NHERE) + full_write(pip[1], redir->nhere.doc->narg.text, len); + else /* NXHERE */ + expandhere(redir->nhere.doc, pip[1]); + _exit(EXIT_SUCCESS); + } + out: + close(pip[1]); + return pip[0]; +} + +static int +openredirect(union node *redir) +{ + char *fname; + int f; + + switch (redir->nfile.type) { + case NFROM: + fname = redir->nfile.expfname; + f = open(fname, O_RDONLY); + if (f < 0) + goto eopen; + break; + case NFROMTO: + fname = redir->nfile.expfname; + f = open(fname, O_RDWR|O_CREAT|O_TRUNC, 0666); + if (f < 0) + goto ecreate; + break; + case NTO: +#if ENABLE_ASH_BASH_COMPAT + case NTO2: +#endif + /* Take care of noclobber mode. */ + if (Cflag) { + fname = redir->nfile.expfname; + f = noclobberopen(fname); + if (f < 0) + goto ecreate; + break; + } + /* FALLTHROUGH */ + case NCLOBBER: + fname = redir->nfile.expfname; + f = open(fname, O_WRONLY|O_CREAT|O_TRUNC, 0666); + if (f < 0) + goto ecreate; + break; + case NAPPEND: + fname = redir->nfile.expfname; + f = open(fname, O_WRONLY|O_CREAT|O_APPEND, 0666); + if (f < 0) + goto ecreate; + break; + default: +#if DEBUG + abort(); +#endif + /* Fall through to eliminate warning. */ +/* Our single caller does this itself */ +// case NTOFD: +// case NFROMFD: +// f = -1; +// break; + case NHERE: + case NXHERE: + f = openhere(redir); + break; + } + + return f; + ecreate: + ash_msg_and_raise_error("can't create %s: %s", fname, errmsg(errno, "nonexistent directory")); + eopen: + ash_msg_and_raise_error("can't open %s: %s", fname, errmsg(errno, "no such file")); +} + +/* + * Copy a file descriptor to be >= to. Returns -1 + * if the source file descriptor is closed, EMPTY if there are no unused + * file descriptors left. + */ +/* 0x800..00: bit to set in "to" to request dup2 instead of fcntl(F_DUPFD). + * old code was doing close(to) prior to copyfd() to achieve the same */ +enum { + COPYFD_EXACT = (int)~(INT_MAX), + COPYFD_RESTORE = (int)((unsigned)COPYFD_EXACT >> 1), +}; +static int +copyfd(int from, int to) +{ + int newfd; + + if (to & COPYFD_EXACT) { + to &= ~COPYFD_EXACT; + /*if (from != to)*/ + newfd = dup2(from, to); + } else { + newfd = fcntl(from, F_DUPFD, to); + } + if (newfd < 0) { + if (errno == EMFILE) + return EMPTY; + /* Happens when source fd is not open: try "echo >&99" */ + ash_msg_and_raise_error("%d: %m", from); + } + return newfd; +} + +/* Struct def and variable are moved down to the first usage site */ +struct two_fd_t { + int orig, copy; +}; +struct redirtab { + struct redirtab *next; + int nullredirs; + int pair_count; + struct two_fd_t two_fd[0]; +}; +#define redirlist (G_var.redirlist) + +static int need_to_remember(struct redirtab *rp, int fd) +{ + int i; + + if (!rp) /* remembering was not requested */ + return 0; + + for (i = 0; i < rp->pair_count; i++) { + if (rp->two_fd[i].orig == fd) { + /* already remembered */ + return 0; + } + } + return 1; +} + +/* "hidden" fd is a fd used to read scripts, or a copy of such */ +static int is_hidden_fd(struct redirtab *rp, int fd) +{ + int i; + struct parsefile *pf; + + if (fd == -1) + return 0; + pf = g_parsefile; + while (pf) { + if (fd == pf->fd) { + return 1; + } + pf = pf->prev; + } + if (!rp) + return 0; + fd |= COPYFD_RESTORE; + for (i = 0; i < rp->pair_count; i++) { + if (rp->two_fd[i].copy == fd) { + return 1; + } + } + return 0; +} + +/* + * Process a list of redirection commands. If the REDIR_PUSH flag is set, + * old file descriptors are stashed away so that the redirection can be + * undone by calling popredir. If the REDIR_BACKQ flag is set, then the + * standard output, and the standard error if it becomes a duplicate of + * stdout, is saved in memory. + */ +/* flags passed to redirect */ +#define REDIR_PUSH 01 /* save previous values of file descriptors */ +#define REDIR_SAVEFD2 03 /* set preverrout */ +static void +redirect(union node *redir, int flags) +{ + struct redirtab *sv; + int sv_pos; + int i; + int fd; + int newfd; + int copied_fd2 = -1; + + g_nullredirs++; + if (!redir) { + return; + } + + sv = NULL; + sv_pos = 0; + INT_OFF; + if (flags & REDIR_PUSH) { + union node *tmp = redir; + do { + sv_pos++; +#if ENABLE_ASH_BASH_COMPAT + if (redir->nfile.type == NTO2) + sv_pos++; +#endif + tmp = tmp->nfile.next; + } while (tmp); + sv = ckmalloc(sizeof(*sv) + sv_pos * sizeof(sv->two_fd[0])); + sv->next = redirlist; + sv->pair_count = sv_pos; + redirlist = sv; + sv->nullredirs = g_nullredirs - 1; + g_nullredirs = 0; + while (sv_pos > 0) { + sv_pos--; + sv->two_fd[sv_pos].orig = sv->two_fd[sv_pos].copy = EMPTY; + } + } + + do { + fd = redir->nfile.fd; + if (redir->nfile.type == NTOFD || redir->nfile.type == NFROMFD) { + int right_fd = redir->ndup.dupfd; + /* redirect from/to same file descriptor? */ + if (right_fd == fd) + continue; + /* echo >&10 and 10 is a fd opened to the sh script? */ + if (is_hidden_fd(sv, right_fd)) { + errno = EBADF; /* as if it is closed */ + ash_msg_and_raise_error("%d: %m", right_fd); + } + newfd = -1; + } else { + newfd = openredirect(redir); /* always >= 0 */ + if (fd == newfd) { + /* Descriptor wasn't open before redirect. + * Mark it for close in the future */ + if (need_to_remember(sv, fd)) { + goto remember_to_close; + } + continue; + } + } +#if ENABLE_ASH_BASH_COMPAT + redirect_more: +#endif + if (need_to_remember(sv, fd)) { + /* Copy old descriptor */ + i = fcntl(fd, F_DUPFD, 10); +/* You'd expect copy to be CLOEXECed. Currently these extra "saved" fds + * are closed in popredir() in the child, preventing them from leaking + * into child. (popredir() also cleans up the mess in case of failures) + */ + if (i == -1) { + i = errno; + if (i != EBADF) { + /* Strange error (e.g. "too many files" EMFILE?) */ + if (newfd >= 0) + close(newfd); + errno = i; + ash_msg_and_raise_error("%d: %m", fd); + /* NOTREACHED */ + } + /* EBADF: it is not open - good, remember to close it */ + remember_to_close: + i = CLOSED; + } else { /* fd is open, save its copy */ + /* "exec fd>&-" should not close fds + * which point to script file(s). + * Force them to be restored afterwards */ + if (is_hidden_fd(sv, fd)) + i |= COPYFD_RESTORE; + } + if (fd == 2) + copied_fd2 = i; + sv->two_fd[sv_pos].orig = fd; + sv->two_fd[sv_pos].copy = i; + sv_pos++; + } + if (newfd < 0) { + /* NTOFD/NFROMFD: copy redir->ndup.dupfd to fd */ + if (redir->ndup.dupfd < 0) { /* "fd>&-" */ + close(fd); + } else { + copyfd(redir->ndup.dupfd, fd | COPYFD_EXACT); + } + } else if (fd != newfd) { /* move newfd to fd */ + copyfd(newfd, fd | COPYFD_EXACT); +#if ENABLE_ASH_BASH_COMPAT + if (!(redir->nfile.type == NTO2 && fd == 2)) +#endif + close(newfd); + } +#if ENABLE_ASH_BASH_COMPAT + if (redir->nfile.type == NTO2 && fd == 1) { + /* We already redirected it to fd 1, now copy it to 2 */ + newfd = 1; + fd = 2; + goto redirect_more; + } +#endif + } while ((redir = redir->nfile.next) != NULL); + + INT_ON; + if ((flags & REDIR_SAVEFD2) && copied_fd2 >= 0) + preverrout_fd = copied_fd2; +} + +/* + * Undo the effects of the last redirection. + */ +static void +popredir(int drop, int restore) +{ + struct redirtab *rp; + int i; + + if (--g_nullredirs >= 0) + return; + INT_OFF; + rp = redirlist; + for (i = 0; i < rp->pair_count; i++) { + int fd = rp->two_fd[i].orig; + int copy = rp->two_fd[i].copy; + if (copy == CLOSED) { + if (!drop) + close(fd); + continue; + } + if (copy != EMPTY) { + if (!drop || (restore && (copy & COPYFD_RESTORE))) { + copy &= ~COPYFD_RESTORE; + /*close(fd);*/ + copyfd(copy, fd | COPYFD_EXACT); + } + close(copy); + } + } + redirlist = rp->next; + g_nullredirs = rp->nullredirs; + free(rp); + INT_ON; +} + +/* + * Undo all redirections. Called on error or interrupt. + */ + +/* + * Discard all saved file descriptors. + */ +static void +clearredir(int drop) +{ + for (;;) { + g_nullredirs = 0; + if (!redirlist) + break; + popredir(drop, /*restore:*/ 0); + } +} + +static int +redirectsafe(union node *redir, int flags) +{ + int err; + volatile int saveint; + struct jmploc *volatile savehandler = exception_handler; + struct jmploc jmploc; + + SAVE_INT(saveint); + /* "echo 9>/dev/null; echo >&9; echo result: $?" - result should be 1, not 2! */ + err = setjmp(jmploc.loc); // huh?? was = setjmp(jmploc.loc) * 2; + if (!err) { + exception_handler = &jmploc; + redirect(redir, flags); + } + exception_handler = savehandler; + if (err && exception != EXERROR) + longjmp(exception_handler->loc, 1); + RESTORE_INT(saveint); + return err; +} + + +/* ============ Routines to expand arguments to commands + * + * We have to deal with backquotes, shell variables, and file metacharacters. + */ + +#if ENABLE_ASH_MATH_SUPPORT_64 +typedef int64_t arith_t; +#define arith_t_type long long +#else +typedef long arith_t; +#define arith_t_type long +#endif + +#if ENABLE_ASH_MATH_SUPPORT +static arith_t dash_arith(const char *); +static arith_t arith(const char *expr, int *perrcode); +#endif + +/* + * expandarg flags + */ +#define EXP_FULL 0x1 /* perform word splitting & file globbing */ +#define EXP_TILDE 0x2 /* do normal tilde expansion */ +#define EXP_VARTILDE 0x4 /* expand tildes in an assignment */ +#define EXP_REDIR 0x8 /* file glob for a redirection (1 match only) */ +#define EXP_CASE 0x10 /* keeps quotes around for CASE pattern */ +#define EXP_RECORD 0x20 /* need to record arguments for ifs breakup */ +#define EXP_VARTILDE2 0x40 /* expand tildes after colons only */ +#define EXP_WORD 0x80 /* expand word in parameter expansion */ +#define EXP_QWORD 0x100 /* expand word in quoted parameter expansion */ +/* + * _rmescape() flags + */ +#define RMESCAPE_ALLOC 0x1 /* Allocate a new string */ +#define RMESCAPE_GLOB 0x2 /* Add backslashes for glob */ +#define RMESCAPE_QUOTED 0x4 /* Remove CTLESC unless in quotes */ +#define RMESCAPE_GROW 0x8 /* Grow strings instead of stalloc */ +#define RMESCAPE_HEAP 0x10 /* Malloc strings instead of stalloc */ + +/* + * Structure specifying which parts of the string should be searched + * for IFS characters. + */ +struct ifsregion { + struct ifsregion *next; /* next region in list */ + int begoff; /* offset of start of region */ + int endoff; /* offset of end of region */ + int nulonly; /* search for nul bytes only */ +}; + +struct arglist { + struct strlist *list; + struct strlist **lastp; +}; + +/* output of current string */ +static char *expdest; +/* list of back quote expressions */ +static struct nodelist *argbackq; +/* first struct in list of ifs regions */ +static struct ifsregion ifsfirst; +/* last struct in list */ +static struct ifsregion *ifslastp; +/* holds expanded arg list */ +static struct arglist exparg; + +/* + * Our own itoa(). + */ +static int +cvtnum(arith_t num) +{ + int len; + + expdest = makestrspace(32, expdest); +#if ENABLE_ASH_MATH_SUPPORT_64 + len = fmtstr(expdest, 32, "%lld", (long long) num); +#else + len = fmtstr(expdest, 32, "%ld", num); +#endif + STADJUST(len, expdest); + return len; +} + +static size_t +esclen(const char *start, const char *p) +{ + size_t esc = 0; + + while (p > start && *--p == CTLESC) { + esc++; + } + return esc; +} + +/* + * Remove any CTLESC characters from a string. + */ +static char * +_rmescapes(char *str, int flag) +{ + static const char qchars[] ALIGN1 = { CTLESC, CTLQUOTEMARK, '\0' }; + + char *p, *q, *r; + unsigned inquotes; + int notescaped; + int globbing; + + p = strpbrk(str, qchars); + if (!p) { + return str; + } + q = p; + r = str; + if (flag & RMESCAPE_ALLOC) { + size_t len = p - str; + size_t fulllen = len + strlen(p) + 1; + + if (flag & RMESCAPE_GROW) { + r = makestrspace(fulllen, expdest); + } else if (flag & RMESCAPE_HEAP) { + r = ckmalloc(fulllen); + } else { + r = stalloc(fulllen); + } + q = r; + if (len > 0) { + q = (char *)memcpy(q, str, len) + len; + } + } + inquotes = (flag & RMESCAPE_QUOTED) ^ RMESCAPE_QUOTED; + globbing = flag & RMESCAPE_GLOB; + notescaped = globbing; + while (*p) { + if (*p == CTLQUOTEMARK) { + inquotes = ~inquotes; + p++; + notescaped = globbing; + continue; + } + if (*p == '\\') { + /* naked back slash */ + notescaped = 0; + goto copy; + } + if (*p == CTLESC) { + p++; + if (notescaped && inquotes && *p != '/') { + *q++ = '\\'; + } + } + notescaped = globbing; + copy: + *q++ = *p++; + } + *q = '\0'; + if (flag & RMESCAPE_GROW) { + expdest = r; + STADJUST(q - r + 1, expdest); + } + return r; +} +#define rmescapes(p) _rmescapes((p), 0) + +#define pmatch(a, b) !fnmatch((a), (b), 0) + +/* + * Prepare a pattern for a expmeta (internal glob(3)) call. + * + * Returns an stalloced string. + */ +static char * +preglob(const char *pattern, int quoted, int flag) +{ + flag |= RMESCAPE_GLOB; + if (quoted) { + flag |= RMESCAPE_QUOTED; + } + return _rmescapes((char *)pattern, flag); +} + +/* + * Put a string on the stack. + */ +static void +memtodest(const char *p, size_t len, int syntax, int quotes) +{ + char *q = expdest; + + q = makestrspace(len * 2, q); + + while (len--) { + int c = signed_char2int(*p++); + if (!c) + continue; + if (quotes && (SIT(c, syntax) == CCTL || SIT(c, syntax) == CBACK)) + USTPUTC(CTLESC, q); + USTPUTC(c, q); + } + + expdest = q; +} + +static void +strtodest(const char *p, int syntax, int quotes) +{ + memtodest(p, strlen(p), syntax, quotes); +} + +/* + * Record the fact that we have to scan this region of the + * string for IFS characters. + */ +static void +recordregion(int start, int end, int nulonly) +{ + struct ifsregion *ifsp; + + if (ifslastp == NULL) { + ifsp = &ifsfirst; + } else { + INT_OFF; + ifsp = ckzalloc(sizeof(*ifsp)); + /*ifsp->next = NULL; - ckzalloc did it */ + ifslastp->next = ifsp; + INT_ON; + } + ifslastp = ifsp; + ifslastp->begoff = start; + ifslastp->endoff = end; + ifslastp->nulonly = nulonly; +} + +static void +removerecordregions(int endoff) +{ + if (ifslastp == NULL) + return; + + if (ifsfirst.endoff > endoff) { + while (ifsfirst.next != NULL) { + struct ifsregion *ifsp; + INT_OFF; + ifsp = ifsfirst.next->next; + free(ifsfirst.next); + ifsfirst.next = ifsp; + INT_ON; + } + if (ifsfirst.begoff > endoff) + ifslastp = NULL; + else { + ifslastp = &ifsfirst; + ifsfirst.endoff = endoff; + } + return; + } + + ifslastp = &ifsfirst; + while (ifslastp->next && ifslastp->next->begoff < endoff) + ifslastp=ifslastp->next; + while (ifslastp->next != NULL) { + struct ifsregion *ifsp; + INT_OFF; + ifsp = ifslastp->next->next; + free(ifslastp->next); + ifslastp->next = ifsp; + INT_ON; + } + if (ifslastp->endoff > endoff) + ifslastp->endoff = endoff; +} + +static char * +exptilde(char *startp, char *p, int flag) +{ + char c; + char *name; + struct passwd *pw; + const char *home; + int quotes = flag & (EXP_FULL | EXP_CASE); + int startloc; + + name = p + 1; + + while ((c = *++p) != '\0') { + switch (c) { + case CTLESC: + return startp; + case CTLQUOTEMARK: + return startp; + case ':': + if (flag & EXP_VARTILDE) + goto done; + break; + case '/': + case CTLENDVAR: + goto done; + } + } + done: + *p = '\0'; + if (*name == '\0') { + home = lookupvar(homestr); + } else { + pw = getpwnam(name); + if (pw == NULL) + goto lose; + home = pw->pw_dir; + } + if (!home || !*home) + goto lose; + *p = c; + startloc = expdest - (char *)stackblock(); + strtodest(home, SQSYNTAX, quotes); + recordregion(startloc, expdest - (char *)stackblock(), 0); + return p; + lose: + *p = c; + return startp; +} + +/* + * Execute a command inside back quotes. If it's a builtin command, we + * want to save its output in a block obtained from malloc. Otherwise + * we fork off a subprocess and get the output of the command via a pipe. + * Should be called with interrupts off. + */ +struct backcmd { /* result of evalbackcmd */ + int fd; /* file descriptor to read from */ + int nleft; /* number of chars in buffer */ + char *buf; /* buffer */ + struct job *jp; /* job structure for command */ +}; + +/* These forward decls are needed to use "eval" code for backticks handling: */ +static uint8_t back_exitstatus; /* exit status of backquoted command */ +#define EV_EXIT 01 /* exit after evaluating tree */ +static void evaltree(union node *, int); + +static void +evalbackcmd(union node *n, struct backcmd *result) +{ + int saveherefd; + + result->fd = -1; + result->buf = NULL; + result->nleft = 0; + result->jp = NULL; + if (n == NULL) { + goto out; + } + + saveherefd = herefd; + herefd = -1; + + { + int pip[2]; + struct job *jp; + + if (pipe(pip) < 0) + ash_msg_and_raise_error("pipe call failed"); + jp = makejob(/*n,*/ 1); + if (forkshell(jp, n, FORK_NOJOB) == 0) { + FORCE_INT_ON; + close(pip[0]); + if (pip[1] != 1) { + /*close(1);*/ + copyfd(pip[1], 1 | COPYFD_EXACT); + close(pip[1]); + } + eflag = 0; + evaltree(n, EV_EXIT); /* actually evaltreenr... */ + /* NOTREACHED */ + } + close(pip[1]); + result->fd = pip[0]; + result->jp = jp; + } + herefd = saveherefd; + out: + TRACE(("evalbackcmd done: fd=%d buf=0x%x nleft=%d jp=0x%x\n", + result->fd, result->buf, result->nleft, result->jp)); +} + +/* + * Expand stuff in backwards quotes. + */ +static void +expbackq(union node *cmd, int quoted, int quotes) +{ + struct backcmd in; + int i; + char buf[128]; + char *p; + char *dest; + int startloc; + int syntax = quoted ? DQSYNTAX : BASESYNTAX; + struct stackmark smark; + + INT_OFF; + setstackmark(&smark); + dest = expdest; + startloc = dest - (char *)stackblock(); + grabstackstr(dest); + evalbackcmd(cmd, &in); + popstackmark(&smark); + + p = in.buf; + i = in.nleft; + if (i == 0) + goto read; + for (;;) { + memtodest(p, i, syntax, quotes); + read: + if (in.fd < 0) + break; + i = nonblock_safe_read(in.fd, buf, sizeof(buf)); + TRACE(("expbackq: read returns %d\n", i)); + if (i <= 0) + break; + p = buf; + } + + free(in.buf); + if (in.fd >= 0) { + close(in.fd); + back_exitstatus = waitforjob(in.jp); + } + INT_ON; + + /* Eat all trailing newlines */ + dest = expdest; + for (; dest > (char *)stackblock() && dest[-1] == '\n';) + STUNPUTC(dest); + expdest = dest; + + if (quoted == 0) + recordregion(startloc, dest - (char *)stackblock(), 0); + TRACE(("evalbackq: size=%d: \"%.*s\"\n", + (dest - (char *)stackblock()) - startloc, + (dest - (char *)stackblock()) - startloc, + stackblock() + startloc)); +} + +#if ENABLE_ASH_MATH_SUPPORT +/* + * Expand arithmetic expression. Backup to start of expression, + * evaluate, place result in (backed up) result, adjust string position. + */ +static void +expari(int quotes) +{ + char *p, *start; + int begoff; + int flag; + int len; + + /* ifsfree(); */ + + /* + * This routine is slightly over-complicated for + * efficiency. Next we scan backwards looking for the + * start of arithmetic. + */ + start = stackblock(); + p = expdest - 1; + *p = '\0'; + p--; + do { + int esc; + + while (*p != CTLARI) { + p--; +#if DEBUG + if (p < start) { + ash_msg_and_raise_error("missing CTLARI (shouldn't happen)"); + } +#endif + } + + esc = esclen(start, p); + if (!(esc % 2)) { + break; + } + + p -= esc + 1; + } while (1); + + begoff = p - start; + + removerecordregions(begoff); + + flag = p[1]; + + expdest = p; + + if (quotes) + rmescapes(p + 2); + + len = cvtnum(dash_arith(p + 2)); + + if (flag != '"') + recordregion(begoff, begoff + len, 0); +} +#endif + +/* argstr needs it */ +static char *evalvar(char *p, int flag, struct strlist *var_str_list); + +/* + * Perform variable and command substitution. If EXP_FULL is set, output CTLESC + * characters to allow for further processing. Otherwise treat + * $@ like $* since no splitting will be performed. + * + * var_str_list (can be NULL) is a list of "VAR=val" strings which take precedence + * over shell varables. Needed for "A=a B=$A; echo $B" case - we use it + * for correct expansion of "B=$A" word. + */ +static void +argstr(char *p, int flag, struct strlist *var_str_list) +{ + static const char spclchars[] ALIGN1 = { + '=', + ':', + CTLQUOTEMARK, + CTLENDVAR, + CTLESC, + CTLVAR, + CTLBACKQ, + CTLBACKQ | CTLQUOTE, +#if ENABLE_ASH_MATH_SUPPORT + CTLENDARI, +#endif + 0 + }; + const char *reject = spclchars; + int c; + int quotes = flag & (EXP_FULL | EXP_CASE); /* do CTLESC */ + int breakall = flag & EXP_WORD; + int inquotes; + size_t length; + int startloc; + + if (!(flag & EXP_VARTILDE)) { + reject += 2; + } else if (flag & EXP_VARTILDE2) { + reject++; + } + inquotes = 0; + length = 0; + if (flag & EXP_TILDE) { + char *q; + + flag &= ~EXP_TILDE; + tilde: + q = p; + if (*q == CTLESC && (flag & EXP_QWORD)) + q++; + if (*q == '~') + p = exptilde(p, q, flag); + } + start: + startloc = expdest - (char *)stackblock(); + for (;;) { + length += strcspn(p + length, reject); + c = p[length]; + if (c && (!(c & 0x80) +#if ENABLE_ASH_MATH_SUPPORT + || c == CTLENDARI +#endif + )) { + /* c == '=' || c == ':' || c == CTLENDARI */ + length++; + } + if (length > 0) { + int newloc; + expdest = stack_nputstr(p, length, expdest); + newloc = expdest - (char *)stackblock(); + if (breakall && !inquotes && newloc > startloc) { + recordregion(startloc, newloc, 0); + } + startloc = newloc; + } + p += length + 1; + length = 0; + + switch (c) { + case '\0': + goto breakloop; + case '=': + if (flag & EXP_VARTILDE2) { + p--; + continue; + } + flag |= EXP_VARTILDE2; + reject++; + /* fall through */ + case ':': + /* + * sort of a hack - expand tildes in variable + * assignments (after the first '=' and after ':'s). + */ + if (*--p == '~') { + goto tilde; + } + continue; + } + + switch (c) { + case CTLENDVAR: /* ??? */ + goto breakloop; + case CTLQUOTEMARK: + /* "$@" syntax adherence hack */ + if ( + !inquotes && + !memcmp(p, dolatstr, 4) && + (p[4] == CTLQUOTEMARK || ( + p[4] == CTLENDVAR && + p[5] == CTLQUOTEMARK + )) + ) { + p = evalvar(p + 1, flag, /* var_str_list: */ NULL) + 1; + goto start; + } + inquotes = !inquotes; + addquote: + if (quotes) { + p--; + length++; + startloc++; + } + break; + case CTLESC: + startloc++; + length++; + goto addquote; + case CTLVAR: + p = evalvar(p, flag, var_str_list); + goto start; + case CTLBACKQ: + c = 0; + case CTLBACKQ|CTLQUOTE: + expbackq(argbackq->n, c, quotes); + argbackq = argbackq->next; + goto start; +#if ENABLE_ASH_MATH_SUPPORT + case CTLENDARI: + p--; + expari(quotes); + goto start; +#endif + } + } + breakloop: + ; +} + +static char * +scanleft(char *startp, char *rmesc, char *rmescend UNUSED_PARAM, char *str, int quotes, + int zero) +{ +// This commented out code was added by James Simmons +// as part of a larger change when he added support for ${var/a/b}. +// However, it broke # and % operators: +// +//var=ababcdcd +// ok bad +//echo ${var#ab} abcdcd abcdcd +//echo ${var##ab} abcdcd abcdcd +//echo ${var#a*b} abcdcd ababcdcd (!) +//echo ${var##a*b} cdcd cdcd +//echo ${var#?} babcdcd ababcdcd (!) +//echo ${var##?} babcdcd babcdcd +//echo ${var#*} ababcdcd babcdcd (!) +//echo ${var##*} +//echo ${var%cd} ababcd ababcd +//echo ${var%%cd} ababcd abab (!) +//echo ${var%c*d} ababcd ababcd +//echo ${var%%c*d} abab ababcdcd (!) +//echo ${var%?} ababcdc ababcdc +//echo ${var%%?} ababcdc ababcdcd (!) +//echo ${var%*} ababcdcd ababcdcd +//echo ${var%%*} +// +// Commenting it back out helped. Remove it completely if it really +// is not needed. + + char *loc, *loc2; //, *full; + char c; + + loc = startp; + loc2 = rmesc; + do { + int match; // = strlen(str); + const char *s = loc2; + + c = *loc2; + if (zero) { + *loc2 = '\0'; + s = rmesc; + } + match = pmatch(str, s); // this line was deleted + +// // chop off end if its '*' +// full = strrchr(str, '*'); +// if (full && full != str) +// match--; +// +// // If str starts with '*' replace with s. +// if ((*str == '*') && strlen(s) >= match) { +// full = xstrdup(s); +// strncpy(full+strlen(s)-match+1, str+1, match-1); +// } else +// full = xstrndup(str, match); +// match = strncmp(s, full, strlen(full)); +// free(full); +// + *loc2 = c; + if (match) // if (!match) + return loc; + if (quotes && *loc == CTLESC) + loc++; + loc++; + loc2++; + } while (c); + return 0; +} + +static char * +scanright(char *startp, char *rmesc, char *rmescend, char *str, int quotes, + int zero) +{ + int esc = 0; + char *loc; + char *loc2; + + for (loc = str - 1, loc2 = rmescend; loc >= startp; loc2--) { + int match; + char c = *loc2; + const char *s = loc2; + if (zero) { + *loc2 = '\0'; + s = rmesc; + } + match = pmatch(str, s); + *loc2 = c; + if (match) + return loc; + loc--; + if (quotes) { + if (--esc < 0) { + esc = esclen(startp, loc); + } + if (esc % 2) { + esc--; + loc--; + } + } + } + return 0; +} + +static void varunset(const char *, const char *, const char *, int) NORETURN; +static void +varunset(const char *end, const char *var, const char *umsg, int varflags) +{ + const char *msg; + const char *tail; + + tail = nullstr; + msg = "parameter not set"; + if (umsg) { + if (*end == CTLENDVAR) { + if (varflags & VSNUL) + tail = " or null"; + } else + msg = umsg; + } + ash_msg_and_raise_error("%.*s: %s%s", end - var - 1, var, msg, tail); +} + +#if ENABLE_ASH_BASH_COMPAT +static char * +parse_sub_pattern(char *arg, int inquotes) +{ + char *idx, *repl = NULL; + unsigned char c; + + idx = arg; + while (1) { + c = *arg; + if (!c) + break; + if (c == '/') { + /* Only the first '/' seen is our separator */ + if (!repl) { + repl = idx + 1; + c = '\0'; + } + } + *idx++ = c; + if (!inquotes && c == '\\' && arg[1] == '\\') + arg++; /* skip both \\, not just first one */ + arg++; + } + *idx = c; /* NUL */ + + return repl; +} +#endif /* ENABLE_ASH_BASH_COMPAT */ + +static const char * +subevalvar(char *p, char *str, int strloc, int subtype, + int startloc, int varflags, int quotes, struct strlist *var_str_list) +{ + struct nodelist *saveargbackq = argbackq; + char *startp; + char *loc; + char *rmesc, *rmescend; + USE_ASH_BASH_COMPAT(char *repl = NULL;) + USE_ASH_BASH_COMPAT(char null = '\0';) + USE_ASH_BASH_COMPAT(int pos, len, orig_len;) + int saveherefd = herefd; + int amount, workloc, resetloc; + int zero; + char *(*scan)(char*, char*, char*, char*, int, int); + + herefd = -1; + argstr(p, (subtype != VSASSIGN && subtype != VSQUESTION) ? EXP_CASE : 0, + var_str_list); + STPUTC('\0', expdest); + herefd = saveherefd; + argbackq = saveargbackq; + startp = (char *)stackblock() + startloc; + + switch (subtype) { + case VSASSIGN: + setvar(str, startp, 0); + amount = startp - expdest; + STADJUST(amount, expdest); + return startp; + +#if ENABLE_ASH_BASH_COMPAT + case VSSUBSTR: + loc = str = stackblock() + strloc; +// TODO: number() instead? It does error checking... + pos = atoi(loc); + len = str - startp - 1; + + /* *loc != '\0', guaranteed by parser */ + if (quotes) { + char *ptr; + + /* We must adjust the length by the number of escapes we find. */ + for (ptr = startp; ptr < (str - 1); ptr++) { + if (*ptr == CTLESC) { + len--; + ptr++; + } + } + } + orig_len = len; + + if (*loc++ == ':') { +// TODO: number() instead? It does error checking... + len = atoi(loc); + } else { + len = orig_len; + while (*loc && *loc != ':') + loc++; + if (*loc++ == ':') +// TODO: number() instead? It does error checking... + len = atoi(loc); + } + if (pos >= orig_len) { + pos = 0; + len = 0; + } + if (len > (orig_len - pos)) + len = orig_len - pos; + + for (str = startp; pos; str++, pos--) { + if (quotes && *str == CTLESC) + str++; + } + for (loc = startp; len; len--) { + if (quotes && *str == CTLESC) + *loc++ = *str++; + *loc++ = *str++; + } + *loc = '\0'; + amount = loc - expdest; + STADJUST(amount, expdest); + return loc; +#endif + + case VSQUESTION: + varunset(p, str, startp, varflags); + /* NOTREACHED */ + } + resetloc = expdest - (char *)stackblock(); + + /* We'll comeback here if we grow the stack while handling + * a VSREPLACE or VSREPLACEALL, since our pointers into the + * stack will need rebasing, and we'll need to remove our work + * areas each time + */ + USE_ASH_BASH_COMPAT(restart:) + + amount = expdest - ((char *)stackblock() + resetloc); + STADJUST(-amount, expdest); + startp = (char *)stackblock() + startloc; + + rmesc = startp; + rmescend = (char *)stackblock() + strloc; + if (quotes) { + rmesc = _rmescapes(startp, RMESCAPE_ALLOC | RMESCAPE_GROW); + if (rmesc != startp) { + rmescend = expdest; + startp = (char *)stackblock() + startloc; + } + } + rmescend--; + str = (char *)stackblock() + strloc; + preglob(str, varflags & VSQUOTE, 0); + workloc = expdest - (char *)stackblock(); + +#if ENABLE_ASH_BASH_COMPAT + if (subtype == VSREPLACE || subtype == VSREPLACEALL) { + char *idx, *end, *restart_detect; + + if (!repl) { + repl = parse_sub_pattern(str, varflags & VSQUOTE); + if (!repl) + repl = &null; + } + + /* If there's no pattern to match, return the expansion unmolested */ + if (*str == '\0') + return 0; + + len = 0; + idx = startp; + end = str - 1; + while (idx < end) { + loc = scanright(idx, rmesc, rmescend, str, quotes, 1); + if (!loc) { + /* No match, advance */ + restart_detect = stackblock(); + STPUTC(*idx, expdest); + if (quotes && *idx == CTLESC) { + idx++; + len++; + STPUTC(*idx, expdest); + } + if (stackblock() != restart_detect) + goto restart; + idx++; + len++; + rmesc++; + continue; + } + + if (subtype == VSREPLACEALL) { + while (idx < loc) { + if (quotes && *idx == CTLESC) + idx++; + idx++; + rmesc++; + } + } else + idx = loc; + + for (loc = repl; *loc; loc++) { + restart_detect = stackblock(); + STPUTC(*loc, expdest); + if (stackblock() != restart_detect) + goto restart; + len++; + } + + if (subtype == VSREPLACE) { + while (*idx) { + restart_detect = stackblock(); + STPUTC(*idx, expdest); + if (stackblock() != restart_detect) + goto restart; + len++; + idx++; + } + break; + } + } + + /* We've put the replaced text into a buffer at workloc, now + * move it to the right place and adjust the stack. + */ + startp = stackblock() + startloc; + STPUTC('\0', expdest); + memmove(startp, stackblock() + workloc, len); + startp[len++] = '\0'; + amount = expdest - ((char *)stackblock() + startloc + len - 1); + STADJUST(-amount, expdest); + return startp; + } +#endif /* ENABLE_ASH_BASH_COMPAT */ + + subtype -= VSTRIMRIGHT; +#if DEBUG + if (subtype < 0 || subtype > 7) + abort(); +#endif + /* zero = subtype == VSTRIMLEFT || subtype == VSTRIMLEFTMAX */ + zero = subtype >> 1; + /* VSTRIMLEFT/VSTRIMRIGHTMAX -> scanleft */ + scan = (subtype & 1) ^ zero ? scanleft : scanright; + + loc = scan(startp, rmesc, rmescend, str, quotes, zero); + if (loc) { + if (zero) { + memmove(startp, loc, str - loc); + loc = startp + (str - loc) - 1; + } + *loc = '\0'; + amount = loc - expdest; + STADJUST(amount, expdest); + } + return loc; +} + +/* + * Add the value of a specialized variable to the stack string. + */ +static ssize_t +varvalue(char *name, int varflags, int flags, struct strlist *var_str_list) +{ + int num; + char *p; + int i; + int sep = 0; + int sepq = 0; + ssize_t len = 0; + char **ap; + int syntax; + int quoted = varflags & VSQUOTE; + int subtype = varflags & VSTYPE; + int quotes = flags & (EXP_FULL | EXP_CASE); + + if (quoted && (flags & EXP_FULL)) + sep = 1 << CHAR_BIT; + + syntax = quoted ? DQSYNTAX : BASESYNTAX; + switch (*name) { + case '$': + num = rootpid; + goto numvar; + case '?': + num = exitstatus; + goto numvar; + case '#': + num = shellparam.nparam; + goto numvar; + case '!': + num = backgndpid; + if (num == 0) + return -1; + numvar: + len = cvtnum(num); + break; + case '-': + p = makestrspace(NOPTS, expdest); + for (i = NOPTS - 1; i >= 0; i--) { + if (optlist[i]) { + USTPUTC(optletters(i), p); + len++; + } + } + expdest = p; + break; + case '@': + if (sep) + goto param; + /* fall through */ + case '*': + sep = ifsset() ? signed_char2int(ifsval()[0]) : ' '; + if (quotes && (SIT(sep, syntax) == CCTL || SIT(sep, syntax) == CBACK)) + sepq = 1; + param: + ap = shellparam.p; + if (!ap) + return -1; + while ((p = *ap++)) { + size_t partlen; + + partlen = strlen(p); + len += partlen; + + if (!(subtype == VSPLUS || subtype == VSLENGTH)) + memtodest(p, partlen, syntax, quotes); + + if (*ap && sep) { + char *q; + + len++; + if (subtype == VSPLUS || subtype == VSLENGTH) { + continue; + } + q = expdest; + if (sepq) + STPUTC(CTLESC, q); + STPUTC(sep, q); + expdest = q; + } + } + return len; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': +// TODO: number() instead? It does error checking... + num = atoi(name); + if (num < 0 || num > shellparam.nparam) + return -1; + p = num ? shellparam.p[num - 1] : arg0; + goto value; + default: + /* NB: name has form "VAR=..." */ + + /* "A=a B=$A" case: var_str_list is a list of "A=a" strings + * which should be considered before we check variables. */ + if (var_str_list) { + unsigned name_len = (strchrnul(name, '=') - name) + 1; + p = NULL; + do { + char *str, *eq; + str = var_str_list->text; + eq = strchr(str, '='); + if (!eq) /* stop at first non-assignment */ + break; + eq++; + if (name_len == (unsigned)(eq - str) + && strncmp(str, name, name_len) == 0) { + p = eq; + /* goto value; - WRONG! */ + /* think "A=1 A=2 B=$A" */ + } + var_str_list = var_str_list->next; + } while (var_str_list); + if (p) + goto value; + } + p = lookupvar(name); + value: + if (!p) + return -1; + + len = strlen(p); + if (!(subtype == VSPLUS || subtype == VSLENGTH)) + memtodest(p, len, syntax, quotes); + return len; + } + + if (subtype == VSPLUS || subtype == VSLENGTH) + STADJUST(-len, expdest); + return len; +} + +/* + * Expand a variable, and return a pointer to the next character in the + * input string. + */ +static char * +evalvar(char *p, int flag, struct strlist *var_str_list) +{ + char varflags; + char subtype; + char quoted; + char easy; + char *var; + int patloc; + int startloc; + ssize_t varlen; + + varflags = *p++; + subtype = varflags & VSTYPE; + quoted = varflags & VSQUOTE; + var = p; + easy = (!quoted || (*var == '@' && shellparam.nparam)); + startloc = expdest - (char *)stackblock(); + p = strchr(p, '=') + 1; + + again: + varlen = varvalue(var, varflags, flag, var_str_list); + if (varflags & VSNUL) + varlen--; + + if (subtype == VSPLUS) { + varlen = -1 - varlen; + goto vsplus; + } + + if (subtype == VSMINUS) { + vsplus: + if (varlen < 0) { + argstr( + p, flag | EXP_TILDE | + (quoted ? EXP_QWORD : EXP_WORD), + var_str_list + ); + goto end; + } + if (easy) + goto record; + goto end; + } + + if (subtype == VSASSIGN || subtype == VSQUESTION) { + if (varlen < 0) { + if (subevalvar(p, var, /* strloc: */ 0, + subtype, startloc, varflags, + /* quotes: */ 0, + var_str_list) + ) { + varflags &= ~VSNUL; + /* + * Remove any recorded regions beyond + * start of variable + */ + removerecordregions(startloc); + goto again; + } + goto end; + } + if (easy) + goto record; + goto end; + } + + if (varlen < 0 && uflag) + varunset(p, var, 0, 0); + + if (subtype == VSLENGTH) { + cvtnum(varlen > 0 ? varlen : 0); + goto record; + } + + if (subtype == VSNORMAL) { + if (easy) + goto record; + goto end; + } + +#if DEBUG + switch (subtype) { + case VSTRIMLEFT: + case VSTRIMLEFTMAX: + case VSTRIMRIGHT: + case VSTRIMRIGHTMAX: +#if ENABLE_ASH_BASH_COMPAT + case VSSUBSTR: + case VSREPLACE: + case VSREPLACEALL: +#endif + break; + default: + abort(); + } +#endif + + if (varlen >= 0) { + /* + * Terminate the string and start recording the pattern + * right after it + */ + STPUTC('\0', expdest); + patloc = expdest - (char *)stackblock(); + if (0 == subevalvar(p, /* str: */ NULL, patloc, subtype, + startloc, varflags, + /* quotes: */ flag & (EXP_FULL | EXP_CASE), + var_str_list) + ) { + int amount = expdest - ( + (char *)stackblock() + patloc - 1 + ); + STADJUST(-amount, expdest); + } + /* Remove any recorded regions beyond start of variable */ + removerecordregions(startloc); + record: + recordregion(startloc, expdest - (char *)stackblock(), quoted); + } + + end: + if (subtype != VSNORMAL) { /* skip to end of alternative */ + int nesting = 1; + for (;;) { + char c = *p++; + if (c == CTLESC) + p++; + else if (c == CTLBACKQ || c == (CTLBACKQ|CTLQUOTE)) { + if (varlen >= 0) + argbackq = argbackq->next; + } else if (c == CTLVAR) { + if ((*p++ & VSTYPE) != VSNORMAL) + nesting++; + } else if (c == CTLENDVAR) { + if (--nesting == 0) + break; + } + } + } + return p; +} + +/* + * Break the argument string into pieces based upon IFS and add the + * strings to the argument list. The regions of the string to be + * searched for IFS characters have been stored by recordregion. + */ +static void +ifsbreakup(char *string, struct arglist *arglist) +{ + struct ifsregion *ifsp; + struct strlist *sp; + char *start; + char *p; + char *q; + const char *ifs, *realifs; + int ifsspc; + int nulonly; + + start = string; + if (ifslastp != NULL) { + ifsspc = 0; + nulonly = 0; + realifs = ifsset() ? ifsval() : defifs; + ifsp = &ifsfirst; + do { + p = string + ifsp->begoff; + nulonly = ifsp->nulonly; + ifs = nulonly ? nullstr : realifs; + ifsspc = 0; + while (p < string + ifsp->endoff) { + q = p; + if (*p == CTLESC) + p++; + if (!strchr(ifs, *p)) { + p++; + continue; + } + if (!nulonly) + ifsspc = (strchr(defifs, *p) != NULL); + /* Ignore IFS whitespace at start */ + if (q == start && ifsspc) { + p++; + start = p; + continue; + } + *q = '\0'; + sp = stzalloc(sizeof(*sp)); + sp->text = start; + *arglist->lastp = sp; + arglist->lastp = &sp->next; + p++; + if (!nulonly) { + for (;;) { + if (p >= string + ifsp->endoff) { + break; + } + q = p; + if (*p == CTLESC) + p++; + if (strchr(ifs, *p) == NULL) { + p = q; + break; + } + if (strchr(defifs, *p) == NULL) { + if (ifsspc) { + p++; + ifsspc = 0; + } else { + p = q; + break; + } + } else + p++; + } + } + start = p; + } /* while */ + ifsp = ifsp->next; + } while (ifsp != NULL); + if (nulonly) + goto add; + } + + if (!*start) + return; + + add: + sp = stzalloc(sizeof(*sp)); + sp->text = start; + *arglist->lastp = sp; + arglist->lastp = &sp->next; +} + +static void +ifsfree(void) +{ + struct ifsregion *p; + + INT_OFF; + p = ifsfirst.next; + do { + struct ifsregion *ifsp; + ifsp = p->next; + free(p); + p = ifsp; + } while (p); + ifslastp = NULL; + ifsfirst.next = NULL; + INT_ON; +} + +/* + * Add a file name to the list. + */ +static void +addfname(const char *name) +{ + struct strlist *sp; + + sp = stzalloc(sizeof(*sp)); + sp->text = ststrdup(name); + *exparg.lastp = sp; + exparg.lastp = &sp->next; +} + +static char *expdir; + +/* + * Do metacharacter (i.e. *, ?, [...]) expansion. + */ +static void +expmeta(char *enddir, char *name) +{ + char *p; + const char *cp; + char *start; + char *endname; + int metaflag; + struct stat statb; + DIR *dirp; + struct dirent *dp; + int atend; + int matchdot; + + metaflag = 0; + start = name; + for (p = name; *p; p++) { + if (*p == '*' || *p == '?') + metaflag = 1; + else if (*p == '[') { + char *q = p + 1; + if (*q == '!') + q++; + for (;;) { + if (*q == '\\') + q++; + if (*q == '/' || *q == '\0') + break; + if (*++q == ']') { + metaflag = 1; + break; + } + } + } else if (*p == '\\') + p++; + else if (*p == '/') { + if (metaflag) + goto out; + start = p + 1; + } + } + out: + if (metaflag == 0) { /* we've reached the end of the file name */ + if (enddir != expdir) + metaflag++; + p = name; + do { + if (*p == '\\') + p++; + *enddir++ = *p; + } while (*p++); + if (metaflag == 0 || lstat(expdir, &statb) >= 0) + addfname(expdir); + return; + } + endname = p; + if (name < start) { + p = name; + do { + if (*p == '\\') + p++; + *enddir++ = *p++; + } while (p < start); + } + if (enddir == expdir) { + cp = "."; + } else if (enddir == expdir + 1 && *expdir == '/') { + cp = "/"; + } else { + cp = expdir; + enddir[-1] = '\0'; + } + dirp = opendir(cp); + if (dirp == NULL) + return; + if (enddir != expdir) + enddir[-1] = '/'; + if (*endname == 0) { + atend = 1; + } else { + atend = 0; + *endname++ = '\0'; + } + matchdot = 0; + p = start; + if (*p == '\\') + p++; + if (*p == '.') + matchdot++; + while (!intpending && (dp = readdir(dirp)) != NULL) { + if (dp->d_name[0] == '.' && !matchdot) + continue; + if (pmatch(start, dp->d_name)) { + if (atend) { + strcpy(enddir, dp->d_name); + addfname(expdir); + } else { + for (p = enddir, cp = dp->d_name; (*p++ = *cp++) != '\0';) + continue; + p[-1] = '/'; + expmeta(p, endname); + } + } + } + closedir(dirp); + if (!atend) + endname[-1] = '/'; +} + +static struct strlist * +msort(struct strlist *list, int len) +{ + struct strlist *p, *q = NULL; + struct strlist **lpp; + int half; + int n; + + if (len <= 1) + return list; + half = len >> 1; + p = list; + for (n = half; --n >= 0;) { + q = p; + p = p->next; + } + q->next = NULL; /* terminate first half of list */ + q = msort(list, half); /* sort first half of list */ + p = msort(p, len - half); /* sort second half */ + lpp = &list; + for (;;) { +#if ENABLE_LOCALE_SUPPORT + if (strcoll(p->text, q->text) < 0) +#else + if (strcmp(p->text, q->text) < 0) +#endif + { + *lpp = p; + lpp = &p->next; + p = *lpp; + if (p == NULL) { + *lpp = q; + break; + } + } else { + *lpp = q; + lpp = &q->next; + q = *lpp; + if (q == NULL) { + *lpp = p; + break; + } + } + } + return list; +} + +/* + * Sort the results of file name expansion. It calculates the number of + * strings to sort and then calls msort (short for merge sort) to do the + * work. + */ +static struct strlist * +expsort(struct strlist *str) +{ + int len; + struct strlist *sp; + + len = 0; + for (sp = str; sp; sp = sp->next) + len++; + return msort(str, len); +} + +static void +expandmeta(struct strlist *str /*, int flag*/) +{ + static const char metachars[] ALIGN1 = { + '*', '?', '[', 0 + }; + /* TODO - EXP_REDIR */ + + while (str) { + struct strlist **savelastp; + struct strlist *sp; + char *p; + + if (fflag) + goto nometa; + if (!strpbrk(str->text, metachars)) + goto nometa; + savelastp = exparg.lastp; + + INT_OFF; + p = preglob(str->text, 0, RMESCAPE_ALLOC | RMESCAPE_HEAP); + { + int i = strlen(str->text); + expdir = ckmalloc(i < 2048 ? 2048 : i); /* XXX */ + } + + expmeta(expdir, p); + free(expdir); + if (p != str->text) + free(p); + INT_ON; + if (exparg.lastp == savelastp) { + /* + * no matches + */ + nometa: + *exparg.lastp = str; + rmescapes(str->text); + exparg.lastp = &str->next; + } else { + *exparg.lastp = NULL; + *savelastp = sp = expsort(*savelastp); + while (sp->next != NULL) + sp = sp->next; + exparg.lastp = &sp->next; + } + str = str->next; + } +} + +/* + * Perform variable substitution and command substitution on an argument, + * placing the resulting list of arguments in arglist. If EXP_FULL is true, + * perform splitting and file name expansion. When arglist is NULL, perform + * here document expansion. + */ +static void +expandarg(union node *arg, struct arglist *arglist, int flag) +{ + struct strlist *sp; + char *p; + + argbackq = arg->narg.backquote; + STARTSTACKSTR(expdest); + ifsfirst.next = NULL; + ifslastp = NULL; + argstr(arg->narg.text, flag, + /* var_str_list: */ arglist ? arglist->list : NULL); + p = _STPUTC('\0', expdest); + expdest = p - 1; + if (arglist == NULL) { + return; /* here document expanded */ + } + p = grabstackstr(p); + exparg.lastp = &exparg.list; + /* + * TODO - EXP_REDIR + */ + if (flag & EXP_FULL) { + ifsbreakup(p, &exparg); + *exparg.lastp = NULL; + exparg.lastp = &exparg.list; + expandmeta(exparg.list /*, flag*/); + } else { + if (flag & EXP_REDIR) /*XXX - for now, just remove escapes */ + rmescapes(p); + sp = stzalloc(sizeof(*sp)); + sp->text = p; + *exparg.lastp = sp; + exparg.lastp = &sp->next; + } + if (ifsfirst.next) + ifsfree(); + *exparg.lastp = NULL; + if (exparg.list) { + *arglist->lastp = exparg.list; + arglist->lastp = exparg.lastp; + } +} + +/* + * Expand shell variables and backquotes inside a here document. + */ +static void +expandhere(union node *arg, int fd) +{ + herefd = fd; + expandarg(arg, (struct arglist *)NULL, 0); + full_write(fd, stackblock(), expdest - (char *)stackblock()); +} + +/* + * Returns true if the pattern matches the string. + */ +static int +patmatch(char *pattern, const char *string) +{ + return pmatch(preglob(pattern, 0, 0), string); +} + +/* + * See if a pattern matches in a case statement. + */ +static int +casematch(union node *pattern, char *val) +{ + struct stackmark smark; + int result; + + setstackmark(&smark); + argbackq = pattern->narg.backquote; + STARTSTACKSTR(expdest); + ifslastp = NULL; + argstr(pattern->narg.text, EXP_TILDE | EXP_CASE, + /* var_str_list: */ NULL); + STACKSTRNUL(expdest); + result = patmatch(stackblock(), val); + popstackmark(&smark); + return result; +} + + +/* ============ find_command */ + +struct builtincmd { + const char *name; + int (*builtin)(int, char **); + /* unsigned flags; */ +}; +#define IS_BUILTIN_SPECIAL(b) ((b)->name[0] & 1) +/* "regular" builtins always take precedence over commands, + * regardless of PATH=....%builtin... position */ +#define IS_BUILTIN_REGULAR(b) ((b)->name[0] & 2) +#define IS_BUILTIN_ASSIGN(b) ((b)->name[0] & 4) + +struct cmdentry { + smallint cmdtype; /* CMDxxx */ + union param { + int index; + /* index >= 0 for commands without path (slashes) */ + /* (TODO: what exactly does the value mean? PATH position?) */ + /* index == -1 for commands with slashes */ + /* index == (-2 - applet_no) for NOFORK applets */ + const struct builtincmd *cmd; + struct funcnode *func; + } u; +}; +/* values of cmdtype */ +#define CMDUNKNOWN -1 /* no entry in table for command */ +#define CMDNORMAL 0 /* command is an executable program */ +#define CMDFUNCTION 1 /* command is a shell function */ +#define CMDBUILTIN 2 /* command is a shell builtin */ + +/* action to find_command() */ +#define DO_ERR 0x01 /* prints errors */ +#define DO_ABS 0x02 /* checks absolute paths */ +#define DO_NOFUNC 0x04 /* don't return shell functions, for command */ +#define DO_ALTPATH 0x08 /* using alternate path */ +#define DO_ALTBLTIN 0x20 /* %builtin in alt. path */ + +static void find_command(char *, struct cmdentry *, int, const char *); + + +/* ============ Hashing commands */ + +/* + * When commands are first encountered, they are entered in a hash table. + * This ensures that a full path search will not have to be done for them + * on each invocation. + * + * We should investigate converting to a linear search, even though that + * would make the command name "hash" a misnomer. + */ + +struct tblentry { + struct tblentry *next; /* next entry in hash chain */ + union param param; /* definition of builtin function */ + smallint cmdtype; /* CMDxxx */ + char rehash; /* if set, cd done since entry created */ + char cmdname[1]; /* name of command */ +}; + +static struct tblentry **cmdtable; +#define INIT_G_cmdtable() do { \ + cmdtable = xzalloc(CMDTABLESIZE * sizeof(cmdtable[0])); \ +} while (0) + +static int builtinloc = -1; /* index in path of %builtin, or -1 */ + + +static void +tryexec(USE_FEATURE_SH_STANDALONE(int applet_no,) char *cmd, char **argv, char **envp) +{ + int repeated = 0; + +#if ENABLE_FEATURE_SH_STANDALONE + if (applet_no >= 0) { + if (APPLET_IS_NOEXEC(applet_no)) { + while (*envp) + putenv(*envp++); + run_applet_no_and_exit(applet_no, argv); + } + /* re-exec ourselves with the new arguments */ + execve(bb_busybox_exec_path, argv, envp); + /* If they called chroot or otherwise made the binary no longer + * executable, fall through */ + } +#endif + + repeat: +#ifdef SYSV + do { + execve(cmd, argv, envp); + } while (errno == EINTR); +#else + execve(cmd, argv, envp); +#endif + if (repeated) { + free(argv); + return; + } + if (errno == ENOEXEC) { + char **ap; + char **new; + + for (ap = argv; *ap; ap++) + continue; + ap = new = ckmalloc((ap - argv + 2) * sizeof(ap[0])); + ap[1] = cmd; + ap[0] = cmd = (char *)DEFAULT_SHELL; + ap += 2; + argv++; + while ((*ap++ = *argv++) != NULL) + continue; + argv = new; + repeated++; + goto repeat; + } +} + +/* + * Exec a program. Never returns. If you change this routine, you may + * have to change the find_command routine as well. + */ +static void shellexec(char **, const char *, int) NORETURN; +static void +shellexec(char **argv, const char *path, int idx) +{ + char *cmdname; + int e; + char **envp; + int exerrno; +#if ENABLE_FEATURE_SH_STANDALONE + int applet_no = -1; +#endif + + clearredir(/*drop:*/ 1); + envp = listvars(VEXPORT, VUNSET, 0); + if (strchr(argv[0], '/') != NULL +#if ENABLE_FEATURE_SH_STANDALONE + || (applet_no = find_applet_by_name(argv[0])) >= 0 +#endif + ) { + tryexec(USE_FEATURE_SH_STANDALONE(applet_no,) argv[0], argv, envp); + e = errno; + } else { + e = ENOENT; + while ((cmdname = padvance(&path, argv[0])) != NULL) { + if (--idx < 0 && pathopt == NULL) { + tryexec(USE_FEATURE_SH_STANDALONE(-1,) cmdname, argv, envp); + if (errno != ENOENT && errno != ENOTDIR) + e = errno; + } + stunalloc(cmdname); + } + } + + /* Map to POSIX errors */ + switch (e) { + case EACCES: + exerrno = 126; + break; + case ENOENT: + exerrno = 127; + break; + default: + exerrno = 2; + break; + } + exitstatus = exerrno; + TRACE(("shellexec failed for %s, errno %d, suppressint %d\n", + argv[0], e, suppressint)); + ash_msg_and_raise(EXEXEC, "%s: %s", argv[0], errmsg(e, "not found")); + /* NOTREACHED */ +} + +static void +printentry(struct tblentry *cmdp) +{ + int idx; + const char *path; + char *name; + + idx = cmdp->param.index; + path = pathval(); + do { + name = padvance(&path, cmdp->cmdname); + stunalloc(name); + } while (--idx >= 0); + out1fmt("%s%s\n", name, (cmdp->rehash ? "*" : nullstr)); +} + +/* + * Clear out command entries. The argument specifies the first entry in + * PATH which has changed. + */ +static void +clearcmdentry(int firstchange) +{ + struct tblentry **tblp; + struct tblentry **pp; + struct tblentry *cmdp; + + INT_OFF; + for (tblp = cmdtable; tblp < &cmdtable[CMDTABLESIZE]; tblp++) { + pp = tblp; + while ((cmdp = *pp) != NULL) { + if ((cmdp->cmdtype == CMDNORMAL && + cmdp->param.index >= firstchange) + || (cmdp->cmdtype == CMDBUILTIN && + builtinloc >= firstchange) + ) { + *pp = cmdp->next; + free(cmdp); + } else { + pp = &cmdp->next; + } + } + } + INT_ON; +} + +/* + * Locate a command in the command hash table. If "add" is nonzero, + * add the command to the table if it is not already present. The + * variable "lastcmdentry" is set to point to the address of the link + * pointing to the entry, so that delete_cmd_entry can delete the + * entry. + * + * Interrupts must be off if called with add != 0. + */ +static struct tblentry **lastcmdentry; + +static struct tblentry * +cmdlookup(const char *name, int add) +{ + unsigned int hashval; + const char *p; + struct tblentry *cmdp; + struct tblentry **pp; + + p = name; + hashval = (unsigned char)*p << 4; + while (*p) + hashval += (unsigned char)*p++; + hashval &= 0x7FFF; + pp = &cmdtable[hashval % CMDTABLESIZE]; + for (cmdp = *pp; cmdp; cmdp = cmdp->next) { + if (strcmp(cmdp->cmdname, name) == 0) + break; + pp = &cmdp->next; + } + if (add && cmdp == NULL) { + cmdp = *pp = ckzalloc(sizeof(struct tblentry) + + strlen(name) + /* + 1 - already done because + * tblentry::cmdname is char[1] */); + /*cmdp->next = NULL; - ckzalloc did it */ + cmdp->cmdtype = CMDUNKNOWN; + strcpy(cmdp->cmdname, name); + } + lastcmdentry = pp; + return cmdp; +} + +/* + * Delete the command entry returned on the last lookup. + */ +static void +delete_cmd_entry(void) +{ + struct tblentry *cmdp; + + INT_OFF; + cmdp = *lastcmdentry; + *lastcmdentry = cmdp->next; + if (cmdp->cmdtype == CMDFUNCTION) + freefunc(cmdp->param.func); + free(cmdp); + INT_ON; +} + +/* + * Add a new command entry, replacing any existing command entry for + * the same name - except special builtins. + */ +static void +addcmdentry(char *name, struct cmdentry *entry) +{ + struct tblentry *cmdp; + + cmdp = cmdlookup(name, 1); + if (cmdp->cmdtype == CMDFUNCTION) { + freefunc(cmdp->param.func); + } + cmdp->cmdtype = entry->cmdtype; + cmdp->param = entry->u; + cmdp->rehash = 0; +} + +static int +hashcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) +{ + struct tblentry **pp; + struct tblentry *cmdp; + int c; + struct cmdentry entry; + char *name; + + if (nextopt("r") != '\0') { + clearcmdentry(0); + return 0; + } + + if (*argptr == NULL) { + for (pp = cmdtable; pp < &cmdtable[CMDTABLESIZE]; pp++) { + for (cmdp = *pp; cmdp; cmdp = cmdp->next) { + if (cmdp->cmdtype == CMDNORMAL) + printentry(cmdp); + } + } + return 0; + } + + c = 0; + while ((name = *argptr) != NULL) { + cmdp = cmdlookup(name, 0); + if (cmdp != NULL + && (cmdp->cmdtype == CMDNORMAL + || (cmdp->cmdtype == CMDBUILTIN && builtinloc >= 0)) + ) { + delete_cmd_entry(); + } + find_command(name, &entry, DO_ERR, pathval()); + if (entry.cmdtype == CMDUNKNOWN) + c = 1; + argptr++; + } + return c; +} + +/* + * Called when a cd is done. Marks all commands so the next time they + * are executed they will be rehashed. + */ +static void +hashcd(void) +{ + struct tblentry **pp; + struct tblentry *cmdp; + + for (pp = cmdtable; pp < &cmdtable[CMDTABLESIZE]; pp++) { + for (cmdp = *pp; cmdp; cmdp = cmdp->next) { + if (cmdp->cmdtype == CMDNORMAL + || (cmdp->cmdtype == CMDBUILTIN + && !IS_BUILTIN_REGULAR(cmdp->param.cmd) + && builtinloc > 0) + ) { + cmdp->rehash = 1; + } + } + } +} + +/* + * Fix command hash table when PATH changed. + * Called before PATH is changed. The argument is the new value of PATH; + * pathval() still returns the old value at this point. + * Called with interrupts off. + */ +static void +changepath(const char *new) +{ + const char *old; + int firstchange; + int idx; + int idx_bltin; + + old = pathval(); + firstchange = 9999; /* assume no change */ + idx = 0; + idx_bltin = -1; + for (;;) { + if (*old != *new) { + firstchange = idx; + if ((*old == '\0' && *new == ':') + || (*old == ':' && *new == '\0')) + firstchange++; + old = new; /* ignore subsequent differences */ + } + if (*new == '\0') + break; + if (*new == '%' && idx_bltin < 0 && prefix(new + 1, "builtin")) + idx_bltin = idx; + if (*new == ':') + idx++; + new++, old++; + } + if (builtinloc < 0 && idx_bltin >= 0) + builtinloc = idx_bltin; /* zap builtins */ + if (builtinloc >= 0 && idx_bltin < 0) + firstchange = 0; + clearcmdentry(firstchange); + builtinloc = idx_bltin; +} + +#define TEOF 0 +#define TNL 1 +#define TREDIR 2 +#define TWORD 3 +#define TSEMI 4 +#define TBACKGND 5 +#define TAND 6 +#define TOR 7 +#define TPIPE 8 +#define TLP 9 +#define TRP 10 +#define TENDCASE 11 +#define TENDBQUOTE 12 +#define TNOT 13 +#define TCASE 14 +#define TDO 15 +#define TDONE 16 +#define TELIF 17 +#define TELSE 18 +#define TESAC 19 +#define TFI 20 +#define TFOR 21 +#define TIF 22 +#define TIN 23 +#define TTHEN 24 +#define TUNTIL 25 +#define TWHILE 26 +#define TBEGIN 27 +#define TEND 28 +typedef smallint token_id_t; + +/* first char is indicating which tokens mark the end of a list */ +static const char *const tokname_array[] = { + "\1end of file", + "\0newline", + "\0redirection", + "\0word", + "\0;", + "\0&", + "\0&&", + "\0||", + "\0|", + "\0(", + "\1)", + "\1;;", + "\1`", +#define KWDOFFSET 13 + /* the following are keywords */ + "\0!", + "\0case", + "\1do", + "\1done", + "\1elif", + "\1else", + "\1esac", + "\1fi", + "\0for", + "\0if", + "\0in", + "\1then", + "\0until", + "\0while", + "\0{", + "\1}", +}; + +static const char * +tokname(int tok) +{ + static char buf[16]; + +//try this: +//if (tok < TSEMI) return tokname_array[tok] + 1; +//sprintf(buf, "\"%s\"", tokname_array[tok] + 1); +//return buf; + + if (tok >= TSEMI) + buf[0] = '"'; + sprintf(buf + (tok >= TSEMI), "%s%c", + tokname_array[tok] + 1, (tok >= TSEMI ? '"' : 0)); + return buf; +} + +/* Wrapper around strcmp for qsort/bsearch/... */ +static int +pstrcmp(const void *a, const void *b) +{ + return strcmp((char*) a, (*(char**) b) + 1); +} + +static const char *const * +findkwd(const char *s) +{ + return bsearch(s, tokname_array + KWDOFFSET, + ARRAY_SIZE(tokname_array) - KWDOFFSET, + sizeof(tokname_array[0]), pstrcmp); +} + +/* + * Locate and print what a word is... + */ +static int +describe_command(char *command, int describe_command_verbose) +{ + struct cmdentry entry; + struct tblentry *cmdp; +#if ENABLE_ASH_ALIAS + const struct alias *ap; +#endif + const char *path = pathval(); + + if (describe_command_verbose) { + out1str(command); + } + + /* First look at the keywords */ + if (findkwd(command)) { + out1str(describe_command_verbose ? " is a shell keyword" : command); + goto out; + } + +#if ENABLE_ASH_ALIAS + /* Then look at the aliases */ + ap = lookupalias(command, 0); + if (ap != NULL) { + if (!describe_command_verbose) { + out1str("alias "); + printalias(ap); + return 0; + } + out1fmt(" is an alias for %s", ap->val); + goto out; + } +#endif + /* Then check if it is a tracked alias */ + cmdp = cmdlookup(command, 0); + if (cmdp != NULL) { + entry.cmdtype = cmdp->cmdtype; + entry.u = cmdp->param; + } else { + /* Finally use brute force */ + find_command(command, &entry, DO_ABS, path); + } + + switch (entry.cmdtype) { + case CMDNORMAL: { + int j = entry.u.index; + char *p; + if (j < 0) { + p = command; + } else { + do { + p = padvance(&path, command); + stunalloc(p); + } while (--j >= 0); + } + if (describe_command_verbose) { + out1fmt(" is%s %s", + (cmdp ? " a tracked alias for" : nullstr), p + ); + } else { + out1str(p); + } + break; + } + + case CMDFUNCTION: + if (describe_command_verbose) { + out1str(" is a shell function"); + } else { + out1str(command); + } + break; + + case CMDBUILTIN: + if (describe_command_verbose) { + out1fmt(" is a %sshell builtin", + IS_BUILTIN_SPECIAL(entry.u.cmd) ? + "special " : nullstr + ); + } else { + out1str(command); + } + break; + + default: + if (describe_command_verbose) { + out1str(": not found\n"); + } + return 127; + } + out: + outstr("\n", stdout); + return 0; +} + +static int +typecmd(int argc UNUSED_PARAM, char **argv) +{ + int i = 1; + int err = 0; + int verbose = 1; + + /* type -p ... ? (we don't bother checking for 'p') */ + if (argv[1] && argv[1][0] == '-') { + i++; + verbose = 0; + } + while (argv[i]) { + err |= describe_command(argv[i++], verbose); + } + return err; +} + +#if ENABLE_ASH_CMDCMD +static int +commandcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) +{ + int c; + enum { + VERIFY_BRIEF = 1, + VERIFY_VERBOSE = 2, + } verify = 0; + + while ((c = nextopt("pvV")) != '\0') + if (c == 'V') + verify |= VERIFY_VERBOSE; + else if (c == 'v') + verify |= VERIFY_BRIEF; +#if DEBUG + else if (c != 'p') + abort(); +#endif + /* Mimic bash: just "command -v" doesn't complain, it's a nop */ + if (verify && (*argptr != NULL)) { + return describe_command(*argptr, verify - VERIFY_BRIEF); + } + + return 0; +} +#endif + + +/* ============ eval.c */ + +static int funcblocksize; /* size of structures in function */ +static int funcstringsize; /* size of strings in node */ +static void *funcblock; /* block to allocate function from */ +static char *funcstring; /* block to allocate strings from */ + +/* flags in argument to evaltree */ +#define EV_EXIT 01 /* exit after evaluating tree */ +#define EV_TESTED 02 /* exit status is checked; ignore -e flag */ +#define EV_BACKCMD 04 /* command executing within back quotes */ + +static const short nodesize[N_NUMBER] = { + [NCMD ] = SHELL_ALIGN(sizeof(struct ncmd)), + [NPIPE ] = SHELL_ALIGN(sizeof(struct npipe)), + [NREDIR ] = SHELL_ALIGN(sizeof(struct nredir)), + [NBACKGND ] = SHELL_ALIGN(sizeof(struct nredir)), + [NSUBSHELL] = SHELL_ALIGN(sizeof(struct nredir)), + [NAND ] = SHELL_ALIGN(sizeof(struct nbinary)), + [NOR ] = SHELL_ALIGN(sizeof(struct nbinary)), + [NSEMI ] = SHELL_ALIGN(sizeof(struct nbinary)), + [NIF ] = SHELL_ALIGN(sizeof(struct nif)), + [NWHILE ] = SHELL_ALIGN(sizeof(struct nbinary)), + [NUNTIL ] = SHELL_ALIGN(sizeof(struct nbinary)), + [NFOR ] = SHELL_ALIGN(sizeof(struct nfor)), + [NCASE ] = SHELL_ALIGN(sizeof(struct ncase)), + [NCLIST ] = SHELL_ALIGN(sizeof(struct nclist)), + [NDEFUN ] = SHELL_ALIGN(sizeof(struct narg)), + [NARG ] = SHELL_ALIGN(sizeof(struct narg)), + [NTO ] = SHELL_ALIGN(sizeof(struct nfile)), +#if ENABLE_ASH_BASH_COMPAT + [NTO2 ] = SHELL_ALIGN(sizeof(struct nfile)), +#endif + [NCLOBBER ] = SHELL_ALIGN(sizeof(struct nfile)), + [NFROM ] = SHELL_ALIGN(sizeof(struct nfile)), + [NFROMTO ] = SHELL_ALIGN(sizeof(struct nfile)), + [NAPPEND ] = SHELL_ALIGN(sizeof(struct nfile)), + [NTOFD ] = SHELL_ALIGN(sizeof(struct ndup)), + [NFROMFD ] = SHELL_ALIGN(sizeof(struct ndup)), + [NHERE ] = SHELL_ALIGN(sizeof(struct nhere)), + [NXHERE ] = SHELL_ALIGN(sizeof(struct nhere)), + [NNOT ] = SHELL_ALIGN(sizeof(struct nnot)), +}; + +static void calcsize(union node *n); + +static void +sizenodelist(struct nodelist *lp) +{ + while (lp) { + funcblocksize += SHELL_ALIGN(sizeof(struct nodelist)); + calcsize(lp->n); + lp = lp->next; + } +} + +static void +calcsize(union node *n) +{ + if (n == NULL) + return; + funcblocksize += nodesize[n->type]; + switch (n->type) { + case NCMD: + calcsize(n->ncmd.redirect); + calcsize(n->ncmd.args); + calcsize(n->ncmd.assign); + break; + case NPIPE: + sizenodelist(n->npipe.cmdlist); + break; + case NREDIR: + case NBACKGND: + case NSUBSHELL: + calcsize(n->nredir.redirect); + calcsize(n->nredir.n); + break; + case NAND: + case NOR: + case NSEMI: + case NWHILE: + case NUNTIL: + calcsize(n->nbinary.ch2); + calcsize(n->nbinary.ch1); + break; + case NIF: + calcsize(n->nif.elsepart); + calcsize(n->nif.ifpart); + calcsize(n->nif.test); + break; + case NFOR: + funcstringsize += strlen(n->nfor.var) + 1; + calcsize(n->nfor.body); + calcsize(n->nfor.args); + break; + case NCASE: + calcsize(n->ncase.cases); + calcsize(n->ncase.expr); + break; + case NCLIST: + calcsize(n->nclist.body); + calcsize(n->nclist.pattern); + calcsize(n->nclist.next); + break; + case NDEFUN: + case NARG: + sizenodelist(n->narg.backquote); + funcstringsize += strlen(n->narg.text) + 1; + calcsize(n->narg.next); + break; + case NTO: +#if ENABLE_ASH_BASH_COMPAT + case NTO2: +#endif + case NCLOBBER: + case NFROM: + case NFROMTO: + case NAPPEND: + calcsize(n->nfile.fname); + calcsize(n->nfile.next); + break; + case NTOFD: + case NFROMFD: + calcsize(n->ndup.vname); + calcsize(n->ndup.next); + break; + case NHERE: + case NXHERE: + calcsize(n->nhere.doc); + calcsize(n->nhere.next); + break; + case NNOT: + calcsize(n->nnot.com); + break; + }; +} + +static char * +nodeckstrdup(char *s) +{ + char *rtn = funcstring; + + strcpy(funcstring, s); + funcstring += strlen(s) + 1; + return rtn; +} + +static union node *copynode(union node *); + +static struct nodelist * +copynodelist(struct nodelist *lp) +{ + struct nodelist *start; + struct nodelist **lpp; + + lpp = &start; + while (lp) { + *lpp = funcblock; + funcblock = (char *) funcblock + SHELL_ALIGN(sizeof(struct nodelist)); + (*lpp)->n = copynode(lp->n); + lp = lp->next; + lpp = &(*lpp)->next; + } + *lpp = NULL; + return start; +} + +static union node * +copynode(union node *n) +{ + union node *new; + + if (n == NULL) + return NULL; + new = funcblock; + funcblock = (char *) funcblock + nodesize[n->type]; + + switch (n->type) { + case NCMD: + new->ncmd.redirect = copynode(n->ncmd.redirect); + new->ncmd.args = copynode(n->ncmd.args); + new->ncmd.assign = copynode(n->ncmd.assign); + break; + case NPIPE: + new->npipe.cmdlist = copynodelist(n->npipe.cmdlist); + new->npipe.pipe_backgnd = n->npipe.pipe_backgnd; + break; + case NREDIR: + case NBACKGND: + case NSUBSHELL: + new->nredir.redirect = copynode(n->nredir.redirect); + new->nredir.n = copynode(n->nredir.n); + break; + case NAND: + case NOR: + case NSEMI: + case NWHILE: + case NUNTIL: + new->nbinary.ch2 = copynode(n->nbinary.ch2); + new->nbinary.ch1 = copynode(n->nbinary.ch1); + break; + case NIF: + new->nif.elsepart = copynode(n->nif.elsepart); + new->nif.ifpart = copynode(n->nif.ifpart); + new->nif.test = copynode(n->nif.test); + break; + case NFOR: + new->nfor.var = nodeckstrdup(n->nfor.var); + new->nfor.body = copynode(n->nfor.body); + new->nfor.args = copynode(n->nfor.args); + break; + case NCASE: + new->ncase.cases = copynode(n->ncase.cases); + new->ncase.expr = copynode(n->ncase.expr); + break; + case NCLIST: + new->nclist.body = copynode(n->nclist.body); + new->nclist.pattern = copynode(n->nclist.pattern); + new->nclist.next = copynode(n->nclist.next); + break; + case NDEFUN: + case NARG: + new->narg.backquote = copynodelist(n->narg.backquote); + new->narg.text = nodeckstrdup(n->narg.text); + new->narg.next = copynode(n->narg.next); + break; + case NTO: +#if ENABLE_ASH_BASH_COMPAT + case NTO2: +#endif + case NCLOBBER: + case NFROM: + case NFROMTO: + case NAPPEND: + new->nfile.fname = copynode(n->nfile.fname); + new->nfile.fd = n->nfile.fd; + new->nfile.next = copynode(n->nfile.next); + break; + case NTOFD: + case NFROMFD: + new->ndup.vname = copynode(n->ndup.vname); + new->ndup.dupfd = n->ndup.dupfd; + new->ndup.fd = n->ndup.fd; + new->ndup.next = copynode(n->ndup.next); + break; + case NHERE: + case NXHERE: + new->nhere.doc = copynode(n->nhere.doc); + new->nhere.fd = n->nhere.fd; + new->nhere.next = copynode(n->nhere.next); + break; + case NNOT: + new->nnot.com = copynode(n->nnot.com); + break; + }; + new->type = n->type; + return new; +} + +/* + * Make a copy of a parse tree. + */ +static struct funcnode * +copyfunc(union node *n) +{ + struct funcnode *f; + size_t blocksize; + + funcblocksize = offsetof(struct funcnode, n); + funcstringsize = 0; + calcsize(n); + blocksize = funcblocksize; + f = ckmalloc(blocksize + funcstringsize); + funcblock = (char *) f + offsetof(struct funcnode, n); + funcstring = (char *) f + blocksize; + copynode(n); + f->count = 0; + return f; +} + +/* + * Define a shell function. + */ +static void +defun(char *name, union node *func) +{ + struct cmdentry entry; + + INT_OFF; + entry.cmdtype = CMDFUNCTION; + entry.u.func = copyfunc(func); + addcmdentry(name, &entry); + INT_ON; +} + +static int evalskip; /* set if we are skipping commands */ +/* reasons for skipping commands (see comment on breakcmd routine) */ +#define SKIPBREAK (1 << 0) +#define SKIPCONT (1 << 1) +#define SKIPFUNC (1 << 2) +#define SKIPFILE (1 << 3) +#define SKIPEVAL (1 << 4) +static int skipcount; /* number of levels to skip */ +static int funcnest; /* depth of function calls */ +static int loopnest; /* current loop nesting level */ + +/* forward decl way out to parsing code - dotrap needs it */ +static int evalstring(char *s, int mask); + +/* + * Called to execute a trap. Perhaps we should avoid entering new trap + * handlers while we are executing a trap handler. + */ +static int +dotrap(void) +{ + char *p; + char *q; + int i; + int savestatus; + int skip; + + savestatus = exitstatus; + pendingsig = 0; + xbarrier(); + + for (i = 1, q = gotsig; i < NSIG; i++, q++) { + if (!*q) + continue; + *q = '\0'; + + p = trap[i]; + if (!p) + continue; + skip = evalstring(p, SKIPEVAL); + exitstatus = savestatus; + if (skip) + return skip; + } + + return 0; +} + +/* forward declarations - evaluation is fairly recursive business... */ +static void evalloop(union node *, int); +static void evalfor(union node *, int); +static void evalcase(union node *, int); +static void evalsubshell(union node *, int); +static void expredir(union node *); +static void evalpipe(union node *, int); +static void evalcommand(union node *, int); +static int evalbltin(const struct builtincmd *, int, char **); +static void prehash(union node *); + +/* + * Evaluate a parse tree. The value is left in the global variable + * exitstatus. + */ +static void +evaltree(union node *n, int flags) +{ + + struct jmploc *volatile savehandler = exception_handler; + struct jmploc jmploc; + int checkexit = 0; + void (*evalfn)(union node *, int); + int status; + + if (n == NULL) { + TRACE(("evaltree(NULL) called\n")); + goto out1; + } + TRACE(("pid %d, evaltree(%p: %d, %d) called\n", + getpid(), n, n->type, flags)); + + exception_handler = &jmploc; + { + int err = setjmp(jmploc.loc); + if (err) { + /* if it was a signal, check for trap handlers */ + if (exception == EXSIG) + goto out; + /* continue on the way out */ + exception_handler = savehandler; + longjmp(exception_handler->loc, err); + } + } + + switch (n->type) { + default: +#if DEBUG + out1fmt("Node type = %d\n", n->type); + fflush(stdout); + break; +#endif + case NNOT: + evaltree(n->nnot.com, EV_TESTED); + status = !exitstatus; + goto setstatus; + case NREDIR: + expredir(n->nredir.redirect); + status = redirectsafe(n->nredir.redirect, REDIR_PUSH); + if (!status) { + evaltree(n->nredir.n, flags & EV_TESTED); + status = exitstatus; + } + popredir(/*drop:*/ 0, /*restore:*/ 0 /* not sure */); + goto setstatus; + case NCMD: + evalfn = evalcommand; + checkexit: + if (eflag && !(flags & EV_TESTED)) + checkexit = ~0; + goto calleval; + case NFOR: + evalfn = evalfor; + goto calleval; + case NWHILE: + case NUNTIL: + evalfn = evalloop; + goto calleval; + case NSUBSHELL: + case NBACKGND: + evalfn = evalsubshell; + goto calleval; + case NPIPE: + evalfn = evalpipe; + goto checkexit; + case NCASE: + evalfn = evalcase; + goto calleval; + case NAND: + case NOR: + case NSEMI: { + +#if NAND + 1 != NOR +#error NAND + 1 != NOR +#endif +#if NOR + 1 != NSEMI +#error NOR + 1 != NSEMI +#endif + unsigned is_or = n->type - NAND; + evaltree( + n->nbinary.ch1, + (flags | ((is_or >> 1) - 1)) & EV_TESTED + ); + if (!exitstatus == is_or) + break; + if (!evalskip) { + n = n->nbinary.ch2; + evaln: + evalfn = evaltree; + calleval: + evalfn(n, flags); + break; + } + break; + } + case NIF: + evaltree(n->nif.test, EV_TESTED); + if (evalskip) + break; + if (exitstatus == 0) { + n = n->nif.ifpart; + goto evaln; + } else if (n->nif.elsepart) { + n = n->nif.elsepart; + goto evaln; + } + goto success; + case NDEFUN: + defun(n->narg.text, n->narg.next); + success: + status = 0; + setstatus: + exitstatus = status; + break; + } + + out: + exception_handler = savehandler; + out1: + if (checkexit & exitstatus) + evalskip |= SKIPEVAL; + else if (pendingsig && dotrap()) + goto exexit; + + if (flags & EV_EXIT) { + exexit: + raise_exception(EXEXIT); + } +} + +#if !defined(__alpha__) || (defined(__GNUC__) && __GNUC__ >= 3) +static +#endif +void evaltreenr(union node *, int) __attribute__ ((alias("evaltree"),__noreturn__)); + +static void +evalloop(union node *n, int flags) +{ + int status; + + loopnest++; + status = 0; + flags &= EV_TESTED; + for (;;) { + int i; + + evaltree(n->nbinary.ch1, EV_TESTED); + if (evalskip) { + skipping: + if (evalskip == SKIPCONT && --skipcount <= 0) { + evalskip = 0; + continue; + } + if (evalskip == SKIPBREAK && --skipcount <= 0) + evalskip = 0; + break; + } + i = exitstatus; + if (n->type != NWHILE) + i = !i; + if (i != 0) + break; + evaltree(n->nbinary.ch2, flags); + status = exitstatus; + if (evalskip) + goto skipping; + } + loopnest--; + exitstatus = status; +} + +static void +evalfor(union node *n, int flags) +{ + struct arglist arglist; + union node *argp; + struct strlist *sp; + struct stackmark smark; + + setstackmark(&smark); + arglist.list = NULL; + arglist.lastp = &arglist.list; + for (argp = n->nfor.args; argp; argp = argp->narg.next) { + expandarg(argp, &arglist, EXP_FULL | EXP_TILDE | EXP_RECORD); + /* XXX */ + if (evalskip) + goto out; + } + *arglist.lastp = NULL; + + exitstatus = 0; + loopnest++; + flags &= EV_TESTED; + for (sp = arglist.list; sp; sp = sp->next) { + setvar(n->nfor.var, sp->text, 0); + evaltree(n->nfor.body, flags); + if (evalskip) { + if (evalskip == SKIPCONT && --skipcount <= 0) { + evalskip = 0; + continue; + } + if (evalskip == SKIPBREAK && --skipcount <= 0) + evalskip = 0; + break; + } + } + loopnest--; + out: + popstackmark(&smark); +} + +static void +evalcase(union node *n, int flags) +{ + union node *cp; + union node *patp; + struct arglist arglist; + struct stackmark smark; + + setstackmark(&smark); + arglist.list = NULL; + arglist.lastp = &arglist.list; + expandarg(n->ncase.expr, &arglist, EXP_TILDE); + exitstatus = 0; + for (cp = n->ncase.cases; cp && evalskip == 0; cp = cp->nclist.next) { + for (patp = cp->nclist.pattern; patp; patp = patp->narg.next) { + if (casematch(patp, arglist.list->text)) { + if (evalskip == 0) { + evaltree(cp->nclist.body, flags); + } + goto out; + } + } + } + out: + popstackmark(&smark); +} + +/* + * Kick off a subshell to evaluate a tree. + */ +static void +evalsubshell(union node *n, int flags) +{ + struct job *jp; + int backgnd = (n->type == NBACKGND); + int status; + + expredir(n->nredir.redirect); + if (!backgnd && flags & EV_EXIT && !trap[0]) + goto nofork; + INT_OFF; + jp = makejob(/*n,*/ 1); + if (forkshell(jp, n, backgnd) == 0) { + INT_ON; + flags |= EV_EXIT; + if (backgnd) + flags &=~ EV_TESTED; + nofork: + redirect(n->nredir.redirect, 0); + evaltreenr(n->nredir.n, flags); + /* never returns */ + } + status = 0; + if (!backgnd) + status = waitforjob(jp); + exitstatus = status; + INT_ON; +} + +/* + * Compute the names of the files in a redirection list. + */ +static void fixredir(union node *, const char *, int); +static void +expredir(union node *n) +{ + union node *redir; + + for (redir = n; redir; redir = redir->nfile.next) { + struct arglist fn; + + fn.list = NULL; + fn.lastp = &fn.list; + switch (redir->type) { + case NFROMTO: + case NFROM: + case NTO: +#if ENABLE_ASH_BASH_COMPAT + case NTO2: +#endif + case NCLOBBER: + case NAPPEND: + expandarg(redir->nfile.fname, &fn, EXP_TILDE | EXP_REDIR); +#if ENABLE_ASH_BASH_COMPAT + store_expfname: +#endif + redir->nfile.expfname = fn.list->text; + break; + case NFROMFD: + case NTOFD: /* >& */ + if (redir->ndup.vname) { + expandarg(redir->ndup.vname, &fn, EXP_FULL | EXP_TILDE); + if (fn.list == NULL) + ash_msg_and_raise_error("redir error"); +#if ENABLE_ASH_BASH_COMPAT +//FIXME: we used expandarg with different args! + if (!isdigit_str9(fn.list->text)) { + /* >&file, not >&fd */ + if (redir->nfile.fd != 1) /* 123>&file - BAD */ + ash_msg_and_raise_error("redir error"); + redir->type = NTO2; + goto store_expfname; + } +#endif + fixredir(redir, fn.list->text, 1); + } + break; + } + } +} + +/* + * Evaluate a pipeline. All the processes in the pipeline are children + * of the process creating the pipeline. (This differs from some versions + * of the shell, which make the last process in a pipeline the parent + * of all the rest.) + */ +static void +evalpipe(union node *n, int flags) +{ + struct job *jp; + struct nodelist *lp; + int pipelen; + int prevfd; + int pip[2]; + + TRACE(("evalpipe(0x%lx) called\n", (long)n)); + pipelen = 0; + for (lp = n->npipe.cmdlist; lp; lp = lp->next) + pipelen++; + flags |= EV_EXIT; + INT_OFF; + jp = makejob(/*n,*/ pipelen); + prevfd = -1; + for (lp = n->npipe.cmdlist; lp; lp = lp->next) { + prehash(lp->n); + pip[1] = -1; + if (lp->next) { + if (pipe(pip) < 0) { + close(prevfd); + ash_msg_and_raise_error("pipe call failed"); + } + } + if (forkshell(jp, lp->n, n->npipe.pipe_backgnd) == 0) { + INT_ON; + if (pip[1] >= 0) { + close(pip[0]); + } + if (prevfd > 0) { + dup2(prevfd, 0); + close(prevfd); + } + if (pip[1] > 1) { + dup2(pip[1], 1); + close(pip[1]); + } + evaltreenr(lp->n, flags); + /* never returns */ + } + if (prevfd >= 0) + close(prevfd); + prevfd = pip[0]; + close(pip[1]); + } + if (n->npipe.pipe_backgnd == 0) { + exitstatus = waitforjob(jp); + TRACE(("evalpipe: job done exit status %d\n", exitstatus)); + } + INT_ON; +} + +/* + * Controls whether the shell is interactive or not. + */ +static void +setinteractive(int on) +{ + static smallint is_interactive; + + if (++on == is_interactive) + return; + is_interactive = on; + setsignal(SIGINT); + setsignal(SIGQUIT); + setsignal(SIGTERM); +#if !ENABLE_FEATURE_SH_EXTRA_QUIET + if (is_interactive > 1) { + /* Looks like they want an interactive shell */ + static smallint did_banner; + + if (!did_banner) { + out1fmt( + "\n\n" + "%s built-in shell (ash)\n" + "Enter 'help' for a list of built-in commands." + "\n\n", + bb_banner); + did_banner = 1; + } + } +#endif +} + +static void +optschanged(void) +{ +#if DEBUG + opentrace(); +#endif + setinteractive(iflag); + setjobctl(mflag); +#if ENABLE_FEATURE_EDITING_VI + if (viflag) + line_input_state->flags |= VI_MODE; + else + line_input_state->flags &= ~VI_MODE; +#else + viflag = 0; /* forcibly keep the option off */ +#endif +} + +static struct localvar *localvars; + +/* + * Called after a function returns. + * Interrupts must be off. + */ +static void +poplocalvars(void) +{ + struct localvar *lvp; + struct var *vp; + + while ((lvp = localvars) != NULL) { + localvars = lvp->next; + vp = lvp->vp; + TRACE(("poplocalvar %s", vp ? vp->text : "-")); + if (vp == NULL) { /* $- saved */ + memcpy(optlist, lvp->text, sizeof(optlist)); + free((char*)lvp->text); + optschanged(); + } else if ((lvp->flags & (VUNSET|VSTRFIXED)) == VUNSET) { + unsetvar(vp->text); + } else { + if (vp->func) + (*vp->func)(strchrnul(lvp->text, '=') + 1); + if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0) + free((char*)vp->text); + vp->flags = lvp->flags; + vp->text = lvp->text; + } + free(lvp); + } +} + +static int +evalfun(struct funcnode *func, int argc, char **argv, int flags) +{ + volatile struct shparam saveparam; + struct localvar *volatile savelocalvars; + struct jmploc *volatile savehandler; + struct jmploc jmploc; + int e; + + saveparam = shellparam; + savelocalvars = localvars; + e = setjmp(jmploc.loc); + if (e) { + goto funcdone; + } + INT_OFF; + savehandler = exception_handler; + exception_handler = &jmploc; + localvars = NULL; + shellparam.malloced = 0; + func->count++; + funcnest++; + INT_ON; + shellparam.nparam = argc - 1; + shellparam.p = argv + 1; +#if ENABLE_ASH_GETOPTS + shellparam.optind = 1; + shellparam.optoff = -1; +#endif + evaltree(&func->n, flags & EV_TESTED); + funcdone: + INT_OFF; + funcnest--; + freefunc(func); + poplocalvars(); + localvars = savelocalvars; + freeparam(&shellparam); + shellparam = saveparam; + exception_handler = savehandler; + INT_ON; + evalskip &= ~SKIPFUNC; + return e; +} + +#if ENABLE_ASH_CMDCMD +static char ** +parse_command_args(char **argv, const char **path) +{ + char *cp, c; + + for (;;) { + cp = *++argv; + if (!cp) + return 0; + if (*cp++ != '-') + break; + c = *cp++; + if (!c) + break; + if (c == '-' && !*cp) { + argv++; + break; + } + do { + switch (c) { + case 'p': + *path = bb_default_path; + break; + default: + /* run 'typecmd' for other options */ + return 0; + } + c = *cp++; + } while (c); + } + return argv; +} +#endif + +/* + * Make a variable a local variable. When a variable is made local, it's + * value and flags are saved in a localvar structure. The saved values + * will be restored when the shell function returns. We handle the name + * "-" as a special case. + */ +static void +mklocal(char *name) +{ + struct localvar *lvp; + struct var **vpp; + struct var *vp; + + INT_OFF; + lvp = ckzalloc(sizeof(struct localvar)); + if (LONE_DASH(name)) { + char *p; + p = ckmalloc(sizeof(optlist)); + lvp->text = memcpy(p, optlist, sizeof(optlist)); + vp = NULL; + } else { + char *eq; + + vpp = hashvar(name); + vp = *findvar(vpp, name); + eq = strchr(name, '='); + if (vp == NULL) { + if (eq) + setvareq(name, VSTRFIXED); + else + setvar(name, NULL, VSTRFIXED); + vp = *vpp; /* the new variable */ + lvp->flags = VUNSET; + } else { + lvp->text = vp->text; + lvp->flags = vp->flags; + vp->flags |= VSTRFIXED|VTEXTFIXED; + if (eq) + setvareq(name, 0); + } + } + lvp->vp = vp; + lvp->next = localvars; + localvars = lvp; + INT_ON; +} + +/* + * The "local" command. + */ +static int +localcmd(int argc UNUSED_PARAM, char **argv) +{ + char *name; + + argv = argptr; + while ((name = *argv++) != NULL) { + mklocal(name); + } + return 0; +} + +static int +falsecmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) +{ + return 1; +} + +static int +truecmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) +{ + return 0; +} + +static int +execcmd(int argc UNUSED_PARAM, char **argv) +{ + if (argv[1]) { + iflag = 0; /* exit on error */ + mflag = 0; + optschanged(); + shellexec(argv + 1, pathval(), 0); + } + return 0; +} + +/* + * The return command. + */ +static int +returncmd(int argc UNUSED_PARAM, char **argv) +{ + /* + * If called outside a function, do what ksh does; + * skip the rest of the file. + */ + evalskip = funcnest ? SKIPFUNC : SKIPFILE; + return argv[1] ? number(argv[1]) : exitstatus; +} + +/* Forward declarations for builtintab[] */ +static int breakcmd(int, char **); +static int dotcmd(int, char **); +static int evalcmd(int, char **); +static int exitcmd(int, char **); +static int exportcmd(int, char **); +#if ENABLE_ASH_GETOPTS +static int getoptscmd(int, char **); +#endif +#if !ENABLE_FEATURE_SH_EXTRA_QUIET +static int helpcmd(int, char **); +#endif +#if ENABLE_ASH_MATH_SUPPORT +static int letcmd(int, char **); +#endif +static int readcmd(int, char **); +static int setcmd(int, char **); +static int shiftcmd(int, char **); +static int timescmd(int, char **); +static int trapcmd(int, char **); +static int umaskcmd(int, char **); +static int unsetcmd(int, char **); +static int ulimitcmd(int, char **); + +#define BUILTIN_NOSPEC "0" +#define BUILTIN_SPECIAL "1" +#define BUILTIN_REGULAR "2" +#define BUILTIN_SPEC_REG "3" +#define BUILTIN_ASSIGN "4" +#define BUILTIN_SPEC_ASSG "5" +#define BUILTIN_REG_ASSG "6" +#define BUILTIN_SPEC_REG_ASSG "7" + +/* We do not handle [[ expr ]] bashism bash-compatibly, + * we make it a synonym of [ expr ]. + * Basically, word splitting and pathname expansion should NOT be performed + * Examples: + * no word splitting: a="a b"; [[ $a = "a b" ]]; echo $? should print "0" + * no pathname expansion: [[ /bin/m* = "/bin/m*" ]]; echo $? should print "0" + * Additional operators: + * || and && should work as -o and -a + * =~ regexp match + * Apart from the above, [[ expr ]] should work as [ expr ] + */ + +#define echocmd echo_main +#define printfcmd printf_main +#define testcmd test_main + +/* Keep these in proper order since it is searched via bsearch() */ +static const struct builtincmd builtintab[] = { + { BUILTIN_SPEC_REG ".", dotcmd }, + { BUILTIN_SPEC_REG ":", truecmd }, +#if ENABLE_ASH_BUILTIN_TEST + { BUILTIN_REGULAR "[", testcmd }, +#if ENABLE_ASH_BASH_COMPAT + { BUILTIN_REGULAR "[[", testcmd }, +#endif +#endif +#if ENABLE_ASH_ALIAS + { BUILTIN_REG_ASSG "alias", aliascmd }, +#endif +#if JOBS + { BUILTIN_REGULAR "bg", fg_bgcmd }, +#endif + { BUILTIN_SPEC_REG "break", breakcmd }, + { BUILTIN_REGULAR "cd", cdcmd }, + { BUILTIN_NOSPEC "chdir", cdcmd }, +#if ENABLE_ASH_CMDCMD + { BUILTIN_REGULAR "command", commandcmd }, +#endif + { BUILTIN_SPEC_REG "continue", breakcmd }, +#if ENABLE_ASH_BUILTIN_ECHO + { BUILTIN_REGULAR "echo", echocmd }, +#endif + { BUILTIN_SPEC_REG "eval", evalcmd }, + { BUILTIN_SPEC_REG "exec", execcmd }, + { BUILTIN_SPEC_REG "exit", exitcmd }, + { BUILTIN_SPEC_REG_ASSG "export", exportcmd }, + { BUILTIN_REGULAR "false", falsecmd }, +#if JOBS + { BUILTIN_REGULAR "fg", fg_bgcmd }, +#endif +#if ENABLE_ASH_GETOPTS + { BUILTIN_REGULAR "getopts", getoptscmd }, +#endif + { BUILTIN_NOSPEC "hash", hashcmd }, +#if !ENABLE_FEATURE_SH_EXTRA_QUIET + { BUILTIN_NOSPEC "help", helpcmd }, +#endif +#if JOBS + { BUILTIN_REGULAR "jobs", jobscmd }, + { BUILTIN_REGULAR "kill", killcmd }, +#endif +#if ENABLE_ASH_MATH_SUPPORT + { BUILTIN_NOSPEC "let", letcmd }, +#endif + { BUILTIN_ASSIGN "local", localcmd }, +#if ENABLE_ASH_BUILTIN_PRINTF + { BUILTIN_REGULAR "printf", printfcmd }, +#endif + { BUILTIN_NOSPEC "pwd", pwdcmd }, + { BUILTIN_REGULAR "read", readcmd }, + { BUILTIN_SPEC_REG_ASSG "readonly", exportcmd }, + { BUILTIN_SPEC_REG "return", returncmd }, + { BUILTIN_SPEC_REG "set", setcmd }, + { BUILTIN_SPEC_REG "shift", shiftcmd }, + { BUILTIN_SPEC_REG "source", dotcmd }, +#if ENABLE_ASH_BUILTIN_TEST + { BUILTIN_REGULAR "test", testcmd }, +#endif + { BUILTIN_SPEC_REG "times", timescmd }, + { BUILTIN_SPEC_REG "trap", trapcmd }, + { BUILTIN_REGULAR "true", truecmd }, + { BUILTIN_NOSPEC "type", typecmd }, + { BUILTIN_NOSPEC "ulimit", ulimitcmd }, + { BUILTIN_REGULAR "umask", umaskcmd }, +#if ENABLE_ASH_ALIAS + { BUILTIN_REGULAR "unalias", unaliascmd }, +#endif + { BUILTIN_SPEC_REG "unset", unsetcmd }, + { BUILTIN_REGULAR "wait", waitcmd }, +}; + +/* Should match the above table! */ +#define COMMANDCMD (builtintab + \ + 2 + \ + 1 * ENABLE_ASH_BUILTIN_TEST + \ + 1 * ENABLE_ASH_BUILTIN_TEST * ENABLE_ASH_BASH_COMPAT + \ + 1 * ENABLE_ASH_ALIAS + \ + 1 * ENABLE_ASH_JOB_CONTROL + \ + 3) +#define EXECCMD (builtintab + \ + 2 + \ + 1 * ENABLE_ASH_BUILTIN_TEST + \ + 1 * ENABLE_ASH_BUILTIN_TEST * ENABLE_ASH_BASH_COMPAT + \ + 1 * ENABLE_ASH_ALIAS + \ + 1 * ENABLE_ASH_JOB_CONTROL + \ + 3 + \ + 1 * ENABLE_ASH_CMDCMD + \ + 1 + \ + ENABLE_ASH_BUILTIN_ECHO + \ + 1) + +/* + * Search the table of builtin commands. + */ +static struct builtincmd * +find_builtin(const char *name) +{ + struct builtincmd *bp; + + bp = bsearch( + name, builtintab, ARRAY_SIZE(builtintab), sizeof(builtintab[0]), + pstrcmp + ); + return bp; +} + +/* + * Execute a simple command. + */ +static int +isassignment(const char *p) +{ + const char *q = endofname(p); + if (p == q) + return 0; + return *q == '='; +} +static int +bltincmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) +{ + /* Preserve exitstatus of a previous possible redirection + * as POSIX mandates */ + return back_exitstatus; +} +static void +evalcommand(union node *cmd, int flags) +{ + static const struct builtincmd null_bltin = { + "\0\0", bltincmd /* why three NULs? */ + }; + struct stackmark smark; + union node *argp; + struct arglist arglist; + struct arglist varlist; + char **argv; + int argc; + const struct strlist *sp; + struct cmdentry cmdentry; + struct job *jp; + char *lastarg; + const char *path; + int spclbltin; + int status; + char **nargv; + struct builtincmd *bcmd; + smallint cmd_is_exec; + smallint pseudovarflag = 0; + + /* First expand the arguments. */ + TRACE(("evalcommand(0x%lx, %d) called\n", (long)cmd, flags)); + setstackmark(&smark); + back_exitstatus = 0; + + cmdentry.cmdtype = CMDBUILTIN; + cmdentry.u.cmd = &null_bltin; + varlist.lastp = &varlist.list; + *varlist.lastp = NULL; + arglist.lastp = &arglist.list; + *arglist.lastp = NULL; + + argc = 0; + if (cmd->ncmd.args) { + bcmd = find_builtin(cmd->ncmd.args->narg.text); + pseudovarflag = bcmd && IS_BUILTIN_ASSIGN(bcmd); + } + + for (argp = cmd->ncmd.args; argp; argp = argp->narg.next) { + struct strlist **spp; + + spp = arglist.lastp; + if (pseudovarflag && isassignment(argp->narg.text)) + expandarg(argp, &arglist, EXP_VARTILDE); + else + expandarg(argp, &arglist, EXP_FULL | EXP_TILDE); + + for (sp = *spp; sp; sp = sp->next) + argc++; + } + + argv = nargv = stalloc(sizeof(char *) * (argc + 1)); + for (sp = arglist.list; sp; sp = sp->next) { + TRACE(("evalcommand arg: %s\n", sp->text)); + *nargv++ = sp->text; + } + *nargv = NULL; + + lastarg = NULL; + if (iflag && funcnest == 0 && argc > 0) + lastarg = nargv[-1]; + + preverrout_fd = 2; + expredir(cmd->ncmd.redirect); + status = redirectsafe(cmd->ncmd.redirect, REDIR_PUSH | REDIR_SAVEFD2); + + path = vpath.text; + for (argp = cmd->ncmd.assign; argp; argp = argp->narg.next) { + struct strlist **spp; + char *p; + + spp = varlist.lastp; + expandarg(argp, &varlist, EXP_VARTILDE); + + /* + * Modify the command lookup path, if a PATH= assignment + * is present + */ + p = (*spp)->text; + if (varequal(p, path)) + path = p; + } + + /* Print the command if xflag is set. */ + if (xflag) { + int n; + const char *p = " %s"; + + p++; + fdprintf(preverrout_fd, p, expandstr(ps4val())); + + sp = varlist.list; + for (n = 0; n < 2; n++) { + while (sp) { + fdprintf(preverrout_fd, p, sp->text); + sp = sp->next; + if (*p == '%') { + p--; + } + } + sp = arglist.list; + } + safe_write(preverrout_fd, "\n", 1); + } + + cmd_is_exec = 0; + spclbltin = -1; + + /* Now locate the command. */ + if (argc) { + const char *oldpath; + int cmd_flag = DO_ERR; + + path += 5; + oldpath = path; + for (;;) { + find_command(argv[0], &cmdentry, cmd_flag, path); + if (cmdentry.cmdtype == CMDUNKNOWN) { + flush_stderr(); + status = 127; + goto bail; + } + + /* implement bltin and command here */ + if (cmdentry.cmdtype != CMDBUILTIN) + break; + if (spclbltin < 0) + spclbltin = IS_BUILTIN_SPECIAL(cmdentry.u.cmd); + if (cmdentry.u.cmd == EXECCMD) + cmd_is_exec = 1; +#if ENABLE_ASH_CMDCMD + if (cmdentry.u.cmd == COMMANDCMD) { + path = oldpath; + nargv = parse_command_args(argv, &path); + if (!nargv) + break; + argc -= nargv - argv; + argv = nargv; + cmd_flag |= DO_NOFUNC; + } else +#endif + break; + } + } + + if (status) { + /* We have a redirection error. */ + if (spclbltin > 0) + raise_exception(EXERROR); + bail: + exitstatus = status; + goto out; + } + + /* Execute the command. */ + switch (cmdentry.cmdtype) { + default: + +#if ENABLE_FEATURE_SH_NOFORK +/* Hmmm... shouldn't it happen somewhere in forkshell() instead? + * Why "fork off a child process if necessary" doesn't apply to NOFORK? */ + { + /* find_command() encodes applet_no as (-2 - applet_no) */ + int applet_no = (- cmdentry.u.index - 2); + if (applet_no >= 0 && APPLET_IS_NOFORK(applet_no)) { + listsetvar(varlist.list, VEXPORT|VSTACK); + /* run _main() */ + exitstatus = run_nofork_applet(applet_no, argv); + break; + } + } +#endif + /* Fork off a child process if necessary. */ + if (!(flags & EV_EXIT) || trap[0]) { + INT_OFF; + jp = makejob(/*cmd,*/ 1); + if (forkshell(jp, cmd, FORK_FG) != 0) { + exitstatus = waitforjob(jp); + INT_ON; + break; + } + FORCE_INT_ON; + } + listsetvar(varlist.list, VEXPORT|VSTACK); + shellexec(argv, path, cmdentry.u.index); + /* NOTREACHED */ + + case CMDBUILTIN: + cmdenviron = varlist.list; + if (cmdenviron) { + struct strlist *list = cmdenviron; + int i = VNOSET; + if (spclbltin > 0 || argc == 0) { + i = 0; + if (cmd_is_exec && argc > 1) + i = VEXPORT; + } + listsetvar(list, i); + } + /* Tight loop with builtins only: + * "while kill -0 $child; do true; done" + * will never exit even if $child died, unless we do this + * to reap the zombie and make kill detect that it's gone: */ + dowait(DOWAIT_NONBLOCK, NULL); + + if (evalbltin(cmdentry.u.cmd, argc, argv)) { + int exit_status; + int i = exception; + if (i == EXEXIT) + goto raise; + exit_status = 2; + if (i == EXINT) + exit_status = 128 + SIGINT; + if (i == EXSIG) + exit_status = 128 + pendingsig; + exitstatus = exit_status; + if (i == EXINT || spclbltin > 0) { + raise: + longjmp(exception_handler->loc, 1); + } + FORCE_INT_ON; + } + break; + + case CMDFUNCTION: + listsetvar(varlist.list, 0); + /* See above for the rationale */ + dowait(DOWAIT_NONBLOCK, NULL); + if (evalfun(cmdentry.u.func, argc, argv, flags)) + goto raise; + break; + } + + out: + popredir(/*drop:*/ cmd_is_exec, /*restore:*/ cmd_is_exec); + if (lastarg) { + /* dsl: I think this is intended to be used to support + * '_' in 'vi' command mode during line editing... + * However I implemented that within libedit itself. + */ + setvar("_", lastarg, 0); + } + popstackmark(&smark); +} + +static int +evalbltin(const struct builtincmd *cmd, int argc, char **argv) +{ + char *volatile savecmdname; + struct jmploc *volatile savehandler; + struct jmploc jmploc; + int i; + + savecmdname = commandname; + i = setjmp(jmploc.loc); + if (i) + goto cmddone; + savehandler = exception_handler; + exception_handler = &jmploc; + commandname = argv[0]; + argptr = argv + 1; + optptr = NULL; /* initialize nextopt */ + exitstatus = (*cmd->builtin)(argc, argv); + flush_stdout_stderr(); + cmddone: + exitstatus |= ferror(stdout); + clearerr(stdout); + commandname = savecmdname; +// exsig = 0; + exception_handler = savehandler; + + return i; +} + +static int +goodname(const char *p) +{ + return !*endofname(p); +} + + +/* + * Search for a command. This is called before we fork so that the + * location of the command will be available in the parent as well as + * the child. The check for "goodname" is an overly conservative + * check that the name will not be subject to expansion. + */ +static void +prehash(union node *n) +{ + struct cmdentry entry; + + if (n->type == NCMD && n->ncmd.args && goodname(n->ncmd.args->narg.text)) + find_command(n->ncmd.args->narg.text, &entry, 0, pathval()); +} + + +/* ============ Builtin commands + * + * Builtin commands whose functions are closely tied to evaluation + * are implemented here. + */ + +/* + * Handle break and continue commands. Break, continue, and return are + * all handled by setting the evalskip flag. The evaluation routines + * above all check this flag, and if it is set they start skipping + * commands rather than executing them. The variable skipcount is + * the number of loops to break/continue, or the number of function + * levels to return. (The latter is always 1.) It should probably + * be an error to break out of more loops than exist, but it isn't + * in the standard shell so we don't make it one here. + */ +static int +breakcmd(int argc UNUSED_PARAM, char **argv) +{ + int n = argv[1] ? number(argv[1]) : 1; + + if (n <= 0) + ash_msg_and_raise_error(illnum, argv[1]); + if (n > loopnest) + n = loopnest; + if (n > 0) { + evalskip = (**argv == 'c') ? SKIPCONT : SKIPBREAK; + skipcount = n; + } + return 0; +} + + +/* ============ input.c + * + * This implements the input routines used by the parser. + */ + +enum { + INPUT_PUSH_FILE = 1, + INPUT_NOFILE_OK = 2, +}; + +static int plinno = 1; /* input line number */ +/* number of characters left in input buffer */ +static int parsenleft; /* copy of parsefile->nleft */ +static int parselleft; /* copy of parsefile->lleft */ +/* next character in input buffer */ +static char *parsenextc; /* copy of parsefile->nextc */ + +static smallint checkkwd; +/* values of checkkwd variable */ +#define CHKALIAS 0x1 +#define CHKKWD 0x2 +#define CHKNL 0x4 + +static void +popstring(void) +{ + struct strpush *sp = g_parsefile->strpush; + + INT_OFF; +#if ENABLE_ASH_ALIAS + if (sp->ap) { + if (parsenextc[-1] == ' ' || parsenextc[-1] == '\t') { + checkkwd |= CHKALIAS; + } + if (sp->string != sp->ap->val) { + free(sp->string); + } + sp->ap->flag &= ~ALIASINUSE; + if (sp->ap->flag & ALIASDEAD) { + unalias(sp->ap->name); + } + } +#endif + parsenextc = sp->prevstring; + parsenleft = sp->prevnleft; + g_parsefile->strpush = sp->prev; + if (sp != &(g_parsefile->basestrpush)) + free(sp); + INT_ON; +} + +static int +preadfd(void) +{ + int nr; + char *buf = g_parsefile->buf; + parsenextc = buf; + +#if ENABLE_FEATURE_EDITING + retry: + if (!iflag || g_parsefile->fd != STDIN_FILENO) + nr = nonblock_safe_read(g_parsefile->fd, buf, BUFSIZ - 1); + else { +#if ENABLE_FEATURE_TAB_COMPLETION + line_input_state->path_lookup = pathval(); +#endif + nr = read_line_input(cmdedit_prompt, buf, BUFSIZ, line_input_state); + if (nr == 0) { + /* Ctrl+C pressed */ + if (trap[SIGINT]) { + buf[0] = '\n'; + buf[1] = '\0'; + raise(SIGINT); + return 1; + } + goto retry; + } + if (nr < 0 && errno == 0) { + /* Ctrl+D pressed */ + nr = 0; + } + } +#else + nr = nonblock_safe_read(g_parsefile->fd, buf, BUFSIZ - 1); +#endif + +#if 0 +/* nonblock_safe_read() handles this problem */ + if (nr < 0) { + if (parsefile->fd == 0 && errno == EWOULDBLOCK) { + int flags = fcntl(0, F_GETFL); + if (flags >= 0 && (flags & O_NONBLOCK)) { + flags &= ~O_NONBLOCK; + if (fcntl(0, F_SETFL, flags) >= 0) { + out2str("sh: turning off NDELAY mode\n"); + goto retry; + } + } + } + } +#endif + return nr; +} + +/* + * Refill the input buffer and return the next input character: + * + * 1) If a string was pushed back on the input, pop it; + * 2) If an EOF was pushed back (parsenleft < -BIGNUM) or we are reading + * from a string so we can't refill the buffer, return EOF. + * 3) If the is more stuff in this buffer, use it else call read to fill it. + * 4) Process input up to the next newline, deleting nul characters. + */ +//#define pgetc_debug(...) bb_error_msg(__VA_ARGS__) +#define pgetc_debug(...) ((void)0) +static int +preadbuffer(void) +{ + char *q; + int more; + + while (g_parsefile->strpush) { +#if ENABLE_ASH_ALIAS + if (parsenleft == -1 && g_parsefile->strpush->ap + && parsenextc[-1] != ' ' && parsenextc[-1] != '\t' + ) { + pgetc_debug("preadbuffer PEOA"); + return PEOA; + } +#endif + popstring(); + /* try "pgetc" now: */ + pgetc_debug("internal pgetc at %d:%p'%s'", parsenleft, parsenextc, parsenextc); + if (--parsenleft >= 0) + return signed_char2int(*parsenextc++); + } + /* on both branches above parsenleft < 0. + * "pgetc" needs refilling. + */ + + /* -90 is -BIGNUM. Below we use -99 to mark "EOF on read", + * pungetc() may decrement it a few times. -90 is enough. + */ + if (parsenleft < -90 || g_parsefile->buf == NULL) { + pgetc_debug("preadbuffer PEOF1"); + /* even in failure keep them in lock step, + * for correct pungetc. */ + parsenextc++; + return PEOF; + } + + more = parselleft; + if (more <= 0) { + flush_stdout_stderr(); + again: + more = preadfd(); + if (more <= 0) { + parselleft = parsenleft = -99; + pgetc_debug("preadbuffer PEOF2"); + parsenextc++; + return PEOF; + } + } + + /* Find out where's the end of line. + * Set parsenleft/parselleft acordingly. + * NUL chars are deleted. + */ + q = parsenextc; + for (;;) { + char c; + + more--; + + c = *q; + if (c == '\0') { + memmove(q, q + 1, more); + } else { + q++; + if (c == '\n') { + parsenleft = q - parsenextc - 1; + break; + } + } + + if (more <= 0) { + parsenleft = q - parsenextc - 1; + if (parsenleft < 0) + goto again; + break; + } + } + parselleft = more; + + if (vflag) { + char save = *q; + *q = '\0'; + out2str(parsenextc); + *q = save; + } + + pgetc_debug("preadbuffer at %d:%p'%s'", parsenleft, parsenextc, parsenextc); + return signed_char2int(*parsenextc++); +} + +#define pgetc_as_macro() (--parsenleft >= 0 ? signed_char2int(*parsenextc++) : preadbuffer()) + +static int +pgetc(void) +{ + pgetc_debug("pgetc at %d:%p'%s'", parsenleft, parsenextc, parsenextc); + return pgetc_as_macro(); +} + +#if ENABLE_ASH_OPTIMIZE_FOR_SIZE +#define pgetc_fast() pgetc() +#else +#define pgetc_fast() pgetc_as_macro() +#endif + +/* + * Same as pgetc(), but ignores PEOA. + */ +#if ENABLE_ASH_ALIAS +static int +pgetc2(void) +{ + int c; + do { + c = pgetc_fast(); + } while (c == PEOA); + return c; +} +#else +#define pgetc2() pgetc() +#endif + +/* + * Read a line from the script. + */ +static char * +pfgets(char *line, int len) +{ + char *p = line; + int nleft = len; + int c; + + while (--nleft > 0) { + c = pgetc2(); + if (c == PEOF) { + if (p == line) + return NULL; + break; + } + *p++ = c; + if (c == '\n') + break; + } + *p = '\0'; + return line; +} + +/* + * Undo the last call to pgetc. Only one character may be pushed back. + * PEOF may be pushed back. + */ +static void +pungetc(void) +{ + parsenleft++; + parsenextc--; + pgetc_debug("pushed back to %d:%p'%s'", parsenleft, parsenextc, parsenextc); +} + +/* + * Push a string back onto the input at this current parsefile level. + * We handle aliases this way. + */ +#if !ENABLE_ASH_ALIAS +#define pushstring(s, ap) pushstring(s) +#endif +static void +pushstring(char *s, struct alias *ap) +{ + struct strpush *sp; + int len; + + len = strlen(s); + INT_OFF; + if (g_parsefile->strpush) { + sp = ckzalloc(sizeof(*sp)); + sp->prev = g_parsefile->strpush; + } else { + sp = &(g_parsefile->basestrpush); + } + g_parsefile->strpush = sp; + sp->prevstring = parsenextc; + sp->prevnleft = parsenleft; +#if ENABLE_ASH_ALIAS + sp->ap = ap; + if (ap) { + ap->flag |= ALIASINUSE; + sp->string = s; + } +#endif + parsenextc = s; + parsenleft = len; + INT_ON; +} + +/* + * To handle the "." command, a stack of input files is used. Pushfile + * adds a new entry to the stack and popfile restores the previous level. + */ +static void +pushfile(void) +{ + struct parsefile *pf; + + g_parsefile->nleft = parsenleft; + g_parsefile->lleft = parselleft; + g_parsefile->nextc = parsenextc; + g_parsefile->linno = plinno; + pf = ckzalloc(sizeof(*pf)); + pf->prev = g_parsefile; + pf->fd = -1; + /*pf->strpush = NULL; - ckzalloc did it */ + /*pf->basestrpush.prev = NULL;*/ + g_parsefile = pf; +} + +static void +popfile(void) +{ + struct parsefile *pf = g_parsefile; + + INT_OFF; + if (pf->fd >= 0) + close(pf->fd); + free(pf->buf); + while (pf->strpush) + popstring(); + g_parsefile = pf->prev; + free(pf); + parsenleft = g_parsefile->nleft; + parselleft = g_parsefile->lleft; + parsenextc = g_parsefile->nextc; + plinno = g_parsefile->linno; + INT_ON; +} + +/* + * Return to top level. + */ +static void +popallfiles(void) +{ + while (g_parsefile != &basepf) + popfile(); +} + +/* + * Close the file(s) that the shell is reading commands from. Called + * after a fork is done. + */ +static void +closescript(void) +{ + popallfiles(); + if (g_parsefile->fd > 0) { + close(g_parsefile->fd); + g_parsefile->fd = 0; + } +} + +/* + * Like setinputfile, but takes an open file descriptor. Call this with + * interrupts off. + */ +static void +setinputfd(int fd, int push) +{ + close_on_exec_on(fd); + if (push) { + pushfile(); + g_parsefile->buf = NULL; + } + g_parsefile->fd = fd; + if (g_parsefile->buf == NULL) + g_parsefile->buf = ckmalloc(IBUFSIZ); + parselleft = parsenleft = 0; + plinno = 1; +} + +/* + * Set the input to take input from a file. If push is set, push the + * old input onto the stack first. + */ +static int +setinputfile(const char *fname, int flags) +{ + int fd; + int fd2; + + INT_OFF; + fd = open(fname, O_RDONLY); + if (fd < 0) { + if (flags & INPUT_NOFILE_OK) + goto out; + ash_msg_and_raise_error("can't open %s", fname); + } + if (fd < 10) { + fd2 = copyfd(fd, 10); + close(fd); + if (fd2 < 0) + ash_msg_and_raise_error("out of file descriptors"); + fd = fd2; + } + setinputfd(fd, flags & INPUT_PUSH_FILE); + out: + INT_ON; + return fd; +} + +/* + * Like setinputfile, but takes input from a string. + */ +static void +setinputstring(char *string) +{ + INT_OFF; + pushfile(); + parsenextc = string; + parsenleft = strlen(string); + g_parsefile->buf = NULL; + plinno = 1; + INT_ON; +} + + +/* ============ mail.c + * + * Routines to check for mail. + */ + +#if ENABLE_ASH_MAIL + +#define MAXMBOXES 10 + +/* times of mailboxes */ +static time_t mailtime[MAXMBOXES]; +/* Set if MAIL or MAILPATH is changed. */ +static smallint mail_var_path_changed; + +/* + * Print appropriate message(s) if mail has arrived. + * If mail_var_path_changed is set, + * then the value of MAIL has mail_var_path_changed, + * so we just update the values. + */ +static void +chkmail(void) +{ + const char *mpath; + char *p; + char *q; + time_t *mtp; + struct stackmark smark; + struct stat statb; + + setstackmark(&smark); + mpath = mpathset() ? mpathval() : mailval(); + for (mtp = mailtime; mtp < mailtime + MAXMBOXES; mtp++) { + p = padvance(&mpath, nullstr); + if (p == NULL) + break; + if (*p == '\0') + continue; + for (q = p; *q; q++) + continue; +#if DEBUG + if (q[-1] != '/') + abort(); +#endif + q[-1] = '\0'; /* delete trailing '/' */ + if (stat(p, &statb) < 0) { + *mtp = 0; + continue; + } + if (!mail_var_path_changed && statb.st_mtime != *mtp) { + fprintf( + stderr, snlfmt, + pathopt ? pathopt : "you have mail" + ); + } + *mtp = statb.st_mtime; + } + mail_var_path_changed = 0; + popstackmark(&smark); +} + +static void +changemail(const char *val UNUSED_PARAM) +{ + mail_var_path_changed = 1; +} + +#endif /* ASH_MAIL */ + + +/* ============ ??? */ + +/* + * Set the shell parameters. + */ +static void +setparam(char **argv) +{ + char **newparam; + char **ap; + int nparam; + + for (nparam = 0; argv[nparam]; nparam++) + continue; + ap = newparam = ckmalloc((nparam + 1) * sizeof(*ap)); + while (*argv) { + *ap++ = ckstrdup(*argv++); + } + *ap = NULL; + freeparam(&shellparam); + shellparam.malloced = 1; + shellparam.nparam = nparam; + shellparam.p = newparam; +#if ENABLE_ASH_GETOPTS + shellparam.optind = 1; + shellparam.optoff = -1; +#endif +} + +/* + * Process shell options. The global variable argptr contains a pointer + * to the argument list; we advance it past the options. + * + * SUSv3 section 2.8.1 "Consequences of Shell Errors" says: + * For a non-interactive shell, an error condition encountered + * by a special built-in ... shall cause the shell to write a diagnostic message + * to standard error and exit as shown in the following table: + * Error Special Built-In + * ... + * Utility syntax error (option or operand error) Shall exit + * ... + * However, in bug 1142 (http://busybox.net/bugs/view.php?id=1142) + * we see that bash does not do that (set "finishes" with error code 1 instead, + * and shell continues), and people rely on this behavior! + * Testcase: + * set -o barfoo 2>/dev/null + * echo $? + * + * Oh well. Let's mimic that. + */ +static int +plus_minus_o(char *name, int val) +{ + int i; + + if (name) { + for (i = 0; i < NOPTS; i++) { + if (strcmp(name, optnames(i)) == 0) { + optlist[i] = val; + return 0; + } + } + ash_msg("illegal option %co %s", val ? '-' : '+', name); + return 1; + } + for (i = 0; i < NOPTS; i++) { + if (val) { + out1fmt("%-16s%s\n", optnames(i), optlist[i] ? "on" : "off"); + } else { + out1fmt("set %co %s\n", optlist[i] ? '-' : '+', optnames(i)); + } + } + return 0; +} +static void +setoption(int flag, int val) +{ + int i; + + for (i = 0; i < NOPTS; i++) { + if (optletters(i) == flag) { + optlist[i] = val; + return; + } + } + ash_msg_and_raise_error("illegal option %c%c", val ? '-' : '+', flag); + /* NOTREACHED */ +} +static int +options(int cmdline) +{ + char *p; + int val; + int c; + + if (cmdline) + minusc = NULL; + while ((p = *argptr) != NULL) { + c = *p++; + if (c != '-' && c != '+') + break; + argptr++; + val = 0; /* val = 0 if c == '+' */ + if (c == '-') { + val = 1; + if (p[0] == '\0' || LONE_DASH(p)) { + if (!cmdline) { + /* "-" means turn off -x and -v */ + if (p[0] == '\0') + xflag = vflag = 0; + /* "--" means reset params */ + else if (*argptr == NULL) + setparam(argptr); + } + break; /* "-" or "--" terminates options */ + } + } + /* first char was + or - */ + while ((c = *p++) != '\0') { + /* bash 3.2 indeed handles -c CMD and +c CMD the same */ + if (c == 'c' && cmdline) { + minusc = p; /* command is after shell args */ + } else if (c == 'o') { + if (plus_minus_o(*argptr, val)) { + /* it already printed err message */ + return 1; /* error */ + } + if (*argptr) + argptr++; + } else if (cmdline && (c == 'l')) { /* -l or +l == --login */ + isloginsh = 1; + /* bash does not accept +-login, we also won't */ + } else if (cmdline && val && (c == '-')) { /* long options */ + if (strcmp(p, "login") == 0) + isloginsh = 1; + break; + } else { + setoption(c, val); + } + } + } + return 0; +} + +/* + * The shift builtin command. + */ +static int +shiftcmd(int argc UNUSED_PARAM, char **argv) +{ + int n; + char **ap1, **ap2; + + n = 1; + if (argv[1]) + n = number(argv[1]); + if (n > shellparam.nparam) + n = 0; /* bash compat, was = shellparam.nparam; */ + INT_OFF; + shellparam.nparam -= n; + for (ap1 = shellparam.p; --n >= 0; ap1++) { + if (shellparam.malloced) + free(*ap1); + } + ap2 = shellparam.p; + while ((*ap2++ = *ap1++) != NULL) + continue; +#if ENABLE_ASH_GETOPTS + shellparam.optind = 1; + shellparam.optoff = -1; +#endif + INT_ON; + return 0; +} + +/* + * POSIX requires that 'set' (but not export or readonly) output the + * variables in lexicographic order - by the locale's collating order (sigh). + * Maybe we could keep them in an ordered balanced binary tree + * instead of hashed lists. + * For now just roll 'em through qsort for printing... + */ +static int +showvars(const char *sep_prefix, int on, int off) +{ + const char *sep; + char **ep, **epend; + + ep = listvars(on, off, &epend); + qsort(ep, epend - ep, sizeof(char *), vpcmp); + + sep = *sep_prefix ? " " : sep_prefix; + + for (; ep < epend; ep++) { + const char *p; + const char *q; + + p = strchrnul(*ep, '='); + q = nullstr; + if (*p) + q = single_quote(++p); + out1fmt("%s%s%.*s%s\n", sep_prefix, sep, (int)(p - *ep), *ep, q); + } + return 0; +} + +/* + * The set command builtin. + */ +static int +setcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) +{ + int retval; + + if (!argv[1]) + return showvars(nullstr, 0, VUNSET); + INT_OFF; + retval = 1; + if (!options(0)) { /* if no parse error... */ + retval = 0; + optschanged(); + if (*argptr != NULL) { + setparam(argptr); + } + } + INT_ON; + return retval; +} + +#if ENABLE_ASH_RANDOM_SUPPORT +static void +change_random(const char *value) +{ + /* Galois LFSR parameter */ + /* Taps at 32 31 29 1: */ + enum { MASK = 0x8000000b }; + /* Another example - taps at 32 31 30 10: */ + /* MASK = 0x00400007 */ + + if (value == NULL) { + /* "get", generate */ + uint32_t t; + + /* LCG has period of 2^32 and alternating lowest bit */ + random_LCG = 1664525 * random_LCG + 1013904223; + /* Galois LFSR has period of 2^32-1 = 3 * 5 * 17 * 257 * 65537 */ + t = (random_galois_LFSR << 1); + if (random_galois_LFSR < 0) /* if we just shifted 1 out of msb... */ + t ^= MASK; + random_galois_LFSR = t; + /* Both are weak, combining them gives better randomness + * and ~2^64 period. & 0x7fff is probably bash compat + * for $RANDOM range. Combining with subtraction is + * just for fun. + and ^ would work equally well. */ + t = (t - random_LCG) & 0x7fff; + /* set without recursion */ + setvar(vrandom.text, utoa(t), VNOFUNC); + vrandom.flags &= ~VNOFUNC; + } else { + /* set/reset */ + random_galois_LFSR = random_LCG = strtoul(value, (char **)NULL, 10); + } +} +#endif + +#if ENABLE_ASH_GETOPTS +static int +getopts(char *optstr, char *optvar, char **optfirst, int *param_optind, int *optoff) +{ + char *p, *q; + char c = '?'; + int done = 0; + int err = 0; + char s[12]; + char **optnext; + + if (*param_optind < 1) + return 1; + optnext = optfirst + *param_optind - 1; + + if (*param_optind <= 1 || *optoff < 0 || (int)strlen(optnext[-1]) < *optoff) + p = NULL; + else + p = optnext[-1] + *optoff; + if (p == NULL || *p == '\0') { + /* Current word is done, advance */ + p = *optnext; + if (p == NULL || *p != '-' || *++p == '\0') { + atend: + p = NULL; + done = 1; + goto out; + } + optnext++; + if (LONE_DASH(p)) /* check for "--" */ + goto atend; + } + + c = *p++; + for (q = optstr; *q != c;) { + if (*q == '\0') { + if (optstr[0] == ':') { + s[0] = c; + s[1] = '\0'; + err |= setvarsafe("OPTARG", s, 0); + } else { + fprintf(stderr, "Illegal option -%c\n", c); + unsetvar("OPTARG"); + } + c = '?'; + goto out; + } + if (*++q == ':') + q++; + } + + if (*++q == ':') { + if (*p == '\0' && (p = *optnext) == NULL) { + if (optstr[0] == ':') { + s[0] = c; + s[1] = '\0'; + err |= setvarsafe("OPTARG", s, 0); + c = ':'; + } else { + fprintf(stderr, "No arg for -%c option\n", c); + unsetvar("OPTARG"); + c = '?'; + } + goto out; + } + + if (p == *optnext) + optnext++; + err |= setvarsafe("OPTARG", p, 0); + p = NULL; + } else + err |= setvarsafe("OPTARG", nullstr, 0); + out: + *optoff = p ? p - *(optnext - 1) : -1; + *param_optind = optnext - optfirst + 1; + fmtstr(s, sizeof(s), "%d", *param_optind); + err |= setvarsafe("OPTIND", s, VNOFUNC); + s[0] = c; + s[1] = '\0'; + err |= setvarsafe(optvar, s, 0); + if (err) { + *param_optind = 1; + *optoff = -1; + flush_stdout_stderr(); + raise_exception(EXERROR); + } + return done; +} + +/* + * The getopts builtin. Shellparam.optnext points to the next argument + * to be processed. Shellparam.optptr points to the next character to + * be processed in the current argument. If shellparam.optnext is NULL, + * then it's the first time getopts has been called. + */ +static int +getoptscmd(int argc, char **argv) +{ + char **optbase; + + if (argc < 3) + ash_msg_and_raise_error("usage: getopts optstring var [arg]"); + if (argc == 3) { + optbase = shellparam.p; + if (shellparam.optind > shellparam.nparam + 1) { + shellparam.optind = 1; + shellparam.optoff = -1; + } + } else { + optbase = &argv[3]; + if (shellparam.optind > argc - 2) { + shellparam.optind = 1; + shellparam.optoff = -1; + } + } + + return getopts(argv[1], argv[2], optbase, &shellparam.optind, + &shellparam.optoff); +} +#endif /* ASH_GETOPTS */ + + +/* ============ Shell parser */ + +struct heredoc { + struct heredoc *next; /* next here document in list */ + union node *here; /* redirection node */ + char *eofmark; /* string indicating end of input */ + smallint striptabs; /* if set, strip leading tabs */ +}; + +static smallint tokpushback; /* last token pushed back */ +static smallint parsebackquote; /* nonzero if we are inside backquotes */ +static smallint quoteflag; /* set if (part of) last token was quoted */ +static token_id_t lasttoken; /* last token read (integer id Txxx) */ +static struct heredoc *heredoclist; /* list of here documents to read */ +static char *wordtext; /* text of last word returned by readtoken */ +static struct nodelist *backquotelist; +static union node *redirnode; +static struct heredoc *heredoc; +/* + * NEOF is returned by parsecmd when it encounters an end of file. It + * must be distinct from NULL, so we use the address of a variable that + * happens to be handy. + */ +#define NEOF ((union node *)&tokpushback) + +static void raise_error_syntax(const char *) NORETURN; +static void +raise_error_syntax(const char *msg) +{ + ash_msg_and_raise_error("syntax error: %s", msg); + /* NOTREACHED */ +} + +/* + * Called when an unexpected token is read during the parse. The argument + * is the token that is expected, or -1 if more than one type of token can + * occur at this point. + */ +static void raise_error_unexpected_syntax(int) NORETURN; +static void +raise_error_unexpected_syntax(int token) +{ + char msg[64]; + int l; + + l = sprintf(msg, "%s unexpected", tokname(lasttoken)); + if (token >= 0) + sprintf(msg + l, " (expecting %s)", tokname(token)); + raise_error_syntax(msg); + /* NOTREACHED */ +} + +#define EOFMARKLEN 79 + +/* parsing is heavily cross-recursive, need these forward decls */ +static union node *andor(void); +static union node *pipeline(void); +static union node *parse_command(void); +static void parseheredoc(void); +static char peektoken(void); +static int readtoken(void); + +static union node * +list(int nlflag) +{ + union node *n1, *n2, *n3; + int tok; + + checkkwd = CHKNL | CHKKWD | CHKALIAS; + if (nlflag == 2 && peektoken()) + return NULL; + n1 = NULL; + for (;;) { + n2 = andor(); + tok = readtoken(); + if (tok == TBACKGND) { + if (n2->type == NPIPE) { + n2->npipe.pipe_backgnd = 1; + } else { + if (n2->type != NREDIR) { + n3 = stzalloc(sizeof(struct nredir)); + n3->nredir.n = n2; + /*n3->nredir.redirect = NULL; - stzalloc did it */ + n2 = n3; + } + n2->type = NBACKGND; + } + } + if (n1 == NULL) { + n1 = n2; + } else { + n3 = stzalloc(sizeof(struct nbinary)); + n3->type = NSEMI; + n3->nbinary.ch1 = n1; + n3->nbinary.ch2 = n2; + n1 = n3; + } + switch (tok) { + case TBACKGND: + case TSEMI: + tok = readtoken(); + /* fall through */ + case TNL: + if (tok == TNL) { + parseheredoc(); + if (nlflag == 1) + return n1; + } else { + tokpushback = 1; + } + checkkwd = CHKNL | CHKKWD | CHKALIAS; + if (peektoken()) + return n1; + break; + case TEOF: + if (heredoclist) + parseheredoc(); + else + pungetc(); /* push back EOF on input */ + return n1; + default: + if (nlflag == 1) + raise_error_unexpected_syntax(-1); + tokpushback = 1; + return n1; + } + } +} + +static union node * +andor(void) +{ + union node *n1, *n2, *n3; + int t; + + n1 = pipeline(); + for (;;) { + t = readtoken(); + if (t == TAND) { + t = NAND; + } else if (t == TOR) { + t = NOR; + } else { + tokpushback = 1; + return n1; + } + checkkwd = CHKNL | CHKKWD | CHKALIAS; + n2 = pipeline(); + n3 = stzalloc(sizeof(struct nbinary)); + n3->type = t; + n3->nbinary.ch1 = n1; + n3->nbinary.ch2 = n2; + n1 = n3; + } +} + +static union node * +pipeline(void) +{ + union node *n1, *n2, *pipenode; + struct nodelist *lp, *prev; + int negate; + + negate = 0; + TRACE(("pipeline: entered\n")); + if (readtoken() == TNOT) { + negate = !negate; + checkkwd = CHKKWD | CHKALIAS; + } else + tokpushback = 1; + n1 = parse_command(); + if (readtoken() == TPIPE) { + pipenode = stzalloc(sizeof(struct npipe)); + pipenode->type = NPIPE; + /*pipenode->npipe.pipe_backgnd = 0; - stzalloc did it */ + lp = stzalloc(sizeof(struct nodelist)); + pipenode->npipe.cmdlist = lp; + lp->n = n1; + do { + prev = lp; + lp = stzalloc(sizeof(struct nodelist)); + checkkwd = CHKNL | CHKKWD | CHKALIAS; + lp->n = parse_command(); + prev->next = lp; + } while (readtoken() == TPIPE); + lp->next = NULL; + n1 = pipenode; + } + tokpushback = 1; + if (negate) { + n2 = stzalloc(sizeof(struct nnot)); + n2->type = NNOT; + n2->nnot.com = n1; + return n2; + } + return n1; +} + +static union node * +makename(void) +{ + union node *n; + + n = stzalloc(sizeof(struct narg)); + n->type = NARG; + /*n->narg.next = NULL; - stzalloc did it */ + n->narg.text = wordtext; + n->narg.backquote = backquotelist; + return n; +} + +static void +fixredir(union node *n, const char *text, int err) +{ + int fd; + + TRACE(("Fix redir %s %d\n", text, err)); + if (!err) + n->ndup.vname = NULL; + + fd = bb_strtou(text, NULL, 10); + if (!errno && fd >= 0) + n->ndup.dupfd = fd; + else if (LONE_DASH(text)) + n->ndup.dupfd = -1; + else { + if (err) + raise_error_syntax("bad fd number"); + n->ndup.vname = makename(); + } +} + +/* + * Returns true if the text contains nothing to expand (no dollar signs + * or backquotes). + */ +static int +noexpand(char *text) +{ + char *p; + char c; + + p = text; + while ((c = *p++) != '\0') { + if (c == CTLQUOTEMARK) + continue; + if (c == CTLESC) + p++; + else if (SIT(c, BASESYNTAX) == CCTL) + return 0; + } + return 1; +} + +static void +parsefname(void) +{ + union node *n = redirnode; + + if (readtoken() != TWORD) + raise_error_unexpected_syntax(-1); + if (n->type == NHERE) { + struct heredoc *here = heredoc; + struct heredoc *p; + int i; + + if (quoteflag == 0) + n->type = NXHERE; + TRACE(("Here document %d\n", n->type)); + if (!noexpand(wordtext) || (i = strlen(wordtext)) == 0 || i > EOFMARKLEN) + raise_error_syntax("illegal eof marker for << redirection"); + rmescapes(wordtext); + here->eofmark = wordtext; + here->next = NULL; + if (heredoclist == NULL) + heredoclist = here; + else { + for (p = heredoclist; p->next; p = p->next) + continue; + p->next = here; + } + } else if (n->type == NTOFD || n->type == NFROMFD) { + fixredir(n, wordtext, 0); + } else { + n->nfile.fname = makename(); + } +} + +static union node * +simplecmd(void) +{ + union node *args, **app; + union node *n = NULL; + union node *vars, **vpp; + union node **rpp, *redir; + int savecheckkwd; +#if ENABLE_ASH_BASH_COMPAT + smallint double_brackets_flag = 0; +#endif + + args = NULL; + app = &args; + vars = NULL; + vpp = &vars; + redir = NULL; + rpp = &redir; + + savecheckkwd = CHKALIAS; + for (;;) { + int t; + checkkwd = savecheckkwd; + t = readtoken(); + switch (t) { +#if ENABLE_ASH_BASH_COMPAT + case TAND: /* "&&" */ + case TOR: /* "||" */ + if (!double_brackets_flag) { + tokpushback = 1; + goto out; + } + wordtext = (char *) (t == TAND ? "-a" : "-o"); +#endif + case TWORD: + n = stzalloc(sizeof(struct narg)); + n->type = NARG; + /*n->narg.next = NULL; - stzalloc did it */ + n->narg.text = wordtext; +#if ENABLE_ASH_BASH_COMPAT + if (strcmp("[[", wordtext) == 0) + double_brackets_flag = 1; + else if (strcmp("]]", wordtext) == 0) + double_brackets_flag = 0; +#endif + n->narg.backquote = backquotelist; + if (savecheckkwd && isassignment(wordtext)) { + *vpp = n; + vpp = &n->narg.next; + } else { + *app = n; + app = &n->narg.next; + savecheckkwd = 0; + } + break; + case TREDIR: + *rpp = n = redirnode; + rpp = &n->nfile.next; + parsefname(); /* read name of redirection file */ + break; + case TLP: + if (args && app == &args->narg.next + && !vars && !redir + ) { + struct builtincmd *bcmd; + const char *name; + + /* We have a function */ + if (readtoken() != TRP) + raise_error_unexpected_syntax(TRP); + name = n->narg.text; + if (!goodname(name) + || ((bcmd = find_builtin(name)) && IS_BUILTIN_SPECIAL(bcmd)) + ) { + raise_error_syntax("bad function name"); + } + n->type = NDEFUN; + checkkwd = CHKNL | CHKKWD | CHKALIAS; + n->narg.next = parse_command(); + return n; + } + /* fall through */ + default: + tokpushback = 1; + goto out; + } + } + out: + *app = NULL; + *vpp = NULL; + *rpp = NULL; + n = stzalloc(sizeof(struct ncmd)); + n->type = NCMD; + n->ncmd.args = args; + n->ncmd.assign = vars; + n->ncmd.redirect = redir; + return n; +} + +static union node * +parse_command(void) +{ + union node *n1, *n2; + union node *ap, **app; + union node *cp, **cpp; + union node *redir, **rpp; + union node **rpp2; + int t; + + redir = NULL; + rpp2 = &redir; + + switch (readtoken()) { + default: + raise_error_unexpected_syntax(-1); + /* NOTREACHED */ + case TIF: + n1 = stzalloc(sizeof(struct nif)); + n1->type = NIF; + n1->nif.test = list(0); + if (readtoken() != TTHEN) + raise_error_unexpected_syntax(TTHEN); + n1->nif.ifpart = list(0); + n2 = n1; + while (readtoken() == TELIF) { + n2->nif.elsepart = stzalloc(sizeof(struct nif)); + n2 = n2->nif.elsepart; + n2->type = NIF; + n2->nif.test = list(0); + if (readtoken() != TTHEN) + raise_error_unexpected_syntax(TTHEN); + n2->nif.ifpart = list(0); + } + if (lasttoken == TELSE) + n2->nif.elsepart = list(0); + else { + n2->nif.elsepart = NULL; + tokpushback = 1; + } + t = TFI; + break; + case TWHILE: + case TUNTIL: { + int got; + n1 = stzalloc(sizeof(struct nbinary)); + n1->type = (lasttoken == TWHILE) ? NWHILE : NUNTIL; + n1->nbinary.ch1 = list(0); + got = readtoken(); + if (got != TDO) { + TRACE(("expecting DO got %s %s\n", tokname(got), + got == TWORD ? wordtext : "")); + raise_error_unexpected_syntax(TDO); + } + n1->nbinary.ch2 = list(0); + t = TDONE; + break; + } + case TFOR: + if (readtoken() != TWORD || quoteflag || !goodname(wordtext)) + raise_error_syntax("bad for loop variable"); + n1 = stzalloc(sizeof(struct nfor)); + n1->type = NFOR; + n1->nfor.var = wordtext; + checkkwd = CHKKWD | CHKALIAS; + if (readtoken() == TIN) { + app = ≈ + while (readtoken() == TWORD) { + n2 = stzalloc(sizeof(struct narg)); + n2->type = NARG; + /*n2->narg.next = NULL; - stzalloc did it */ + n2->narg.text = wordtext; + n2->narg.backquote = backquotelist; + *app = n2; + app = &n2->narg.next; + } + *app = NULL; + n1->nfor.args = ap; + if (lasttoken != TNL && lasttoken != TSEMI) + raise_error_unexpected_syntax(-1); + } else { + n2 = stzalloc(sizeof(struct narg)); + n2->type = NARG; + /*n2->narg.next = NULL; - stzalloc did it */ + n2->narg.text = (char *)dolatstr; + /*n2->narg.backquote = NULL;*/ + n1->nfor.args = n2; + /* + * Newline or semicolon here is optional (but note + * that the original Bourne shell only allowed NL). + */ + if (lasttoken != TNL && lasttoken != TSEMI) + tokpushback = 1; + } + checkkwd = CHKNL | CHKKWD | CHKALIAS; + if (readtoken() != TDO) + raise_error_unexpected_syntax(TDO); + n1->nfor.body = list(0); + t = TDONE; + break; + case TCASE: + n1 = stzalloc(sizeof(struct ncase)); + n1->type = NCASE; + if (readtoken() != TWORD) + raise_error_unexpected_syntax(TWORD); + n1->ncase.expr = n2 = stzalloc(sizeof(struct narg)); + n2->type = NARG; + /*n2->narg.next = NULL; - stzalloc did it */ + n2->narg.text = wordtext; + n2->narg.backquote = backquotelist; + do { + checkkwd = CHKKWD | CHKALIAS; + } while (readtoken() == TNL); + if (lasttoken != TIN) + raise_error_unexpected_syntax(TIN); + cpp = &n1->ncase.cases; + next_case: + checkkwd = CHKNL | CHKKWD; + t = readtoken(); + while (t != TESAC) { + if (lasttoken == TLP) + readtoken(); + *cpp = cp = stzalloc(sizeof(struct nclist)); + cp->type = NCLIST; + app = &cp->nclist.pattern; + for (;;) { + *app = ap = stzalloc(sizeof(struct narg)); + ap->type = NARG; + /*ap->narg.next = NULL; - stzalloc did it */ + ap->narg.text = wordtext; + ap->narg.backquote = backquotelist; + if (readtoken() != TPIPE) + break; + app = &ap->narg.next; + readtoken(); + } + //ap->narg.next = NULL; + if (lasttoken != TRP) + raise_error_unexpected_syntax(TRP); + cp->nclist.body = list(2); + + cpp = &cp->nclist.next; + + checkkwd = CHKNL | CHKKWD; + t = readtoken(); + if (t != TESAC) { + if (t != TENDCASE) + raise_error_unexpected_syntax(TENDCASE); + goto next_case; + } + } + *cpp = NULL; + goto redir; + case TLP: + n1 = stzalloc(sizeof(struct nredir)); + n1->type = NSUBSHELL; + n1->nredir.n = list(0); + /*n1->nredir.redirect = NULL; - stzalloc did it */ + t = TRP; + break; + case TBEGIN: + n1 = list(0); + t = TEND; + break; + case TWORD: + case TREDIR: + tokpushback = 1; + return simplecmd(); + } + + if (readtoken() != t) + raise_error_unexpected_syntax(t); + + redir: + /* Now check for redirection which may follow command */ + checkkwd = CHKKWD | CHKALIAS; + rpp = rpp2; + while (readtoken() == TREDIR) { + *rpp = n2 = redirnode; + rpp = &n2->nfile.next; + parsefname(); + } + tokpushback = 1; + *rpp = NULL; + if (redir) { + if (n1->type != NSUBSHELL) { + n2 = stzalloc(sizeof(struct nredir)); + n2->type = NREDIR; + n2->nredir.n = n1; + n1 = n2; + } + n1->nredir.redirect = redir; + } + return n1; +} + +#if ENABLE_ASH_BASH_COMPAT +static int decode_dollar_squote(void) +{ + static const char C_escapes[] ALIGN1 = "nrbtfav""x\\01234567"; + int c, cnt; + char *p; + char buf[4]; + + c = pgetc(); + p = strchr(C_escapes, c); + if (p) { + buf[0] = c; + p = buf; + cnt = 3; + if ((unsigned char)(c - '0') <= 7) { /* \ooo */ + do { + c = pgetc(); + *++p = c; + } while ((unsigned char)(c - '0') <= 7 && --cnt); + pungetc(); + } else if (c == 'x') { /* \xHH */ + do { + c = pgetc(); + *++p = c; + } while (isxdigit(c) && --cnt); + pungetc(); + if (cnt == 3) { /* \x but next char is "bad" */ + c = 'x'; + goto unrecognized; + } + } else { /* simple seq like \\ or \t */ + p++; + } + *p = '\0'; + p = buf; + c = bb_process_escape_sequence((void*)&p); + } else { /* unrecognized "\z": print both chars unless ' or " */ + if (c != '\'' && c != '"') { + unrecognized: + c |= 0x100; /* "please encode \, then me" */ + } + } + return c; +} +#endif + +/* + * If eofmark is NULL, read a word or a redirection symbol. If eofmark + * is not NULL, read a here document. In the latter case, eofmark is the + * word which marks the end of the document and striptabs is true if + * leading tabs should be stripped from the document. The argument firstc + * is the first character of the input token or document. + * + * Because C does not have internal subroutines, I have simulated them + * using goto's to implement the subroutine linkage. The following macros + * will run code that appears at the end of readtoken1. + */ +#define CHECKEND() {goto checkend; checkend_return:;} +#define PARSEREDIR() {goto parseredir; parseredir_return:;} +#define PARSESUB() {goto parsesub; parsesub_return:;} +#define PARSEBACKQOLD() {oldstyle = 1; goto parsebackq; parsebackq_oldreturn:;} +#define PARSEBACKQNEW() {oldstyle = 0; goto parsebackq; parsebackq_newreturn:;} +#define PARSEARITH() {goto parsearith; parsearith_return:;} +static int +readtoken1(int firstc, int syntax, char *eofmark, int striptabs) +{ + /* NB: syntax parameter fits into smallint */ + int c = firstc; + char *out; + int len; + char line[EOFMARKLEN + 1]; + struct nodelist *bqlist; + smallint quotef; + smallint dblquote; + smallint oldstyle; + smallint prevsyntax; /* syntax before arithmetic */ +#if ENABLE_ASH_EXPAND_PRMT + smallint pssyntax; /* we are expanding a prompt string */ +#endif + int varnest; /* levels of variables expansion */ + int arinest; /* levels of arithmetic expansion */ + int parenlevel; /* levels of parens in arithmetic */ + int dqvarnest; /* levels of variables expansion within double quotes */ + + USE_ASH_BASH_COMPAT(smallint bash_dollar_squote = 0;) + +#if __GNUC__ + /* Avoid longjmp clobbering */ + (void) &out; + (void) "ef; + (void) &dblquote; + (void) &varnest; + (void) &arinest; + (void) &parenlevel; + (void) &dqvarnest; + (void) &oldstyle; + (void) &prevsyntax; + (void) &syntax; +#endif + startlinno = plinno; + bqlist = NULL; + quotef = 0; + oldstyle = 0; + prevsyntax = 0; +#if ENABLE_ASH_EXPAND_PRMT + pssyntax = (syntax == PSSYNTAX); + if (pssyntax) + syntax = DQSYNTAX; +#endif + dblquote = (syntax == DQSYNTAX); + varnest = 0; + arinest = 0; + parenlevel = 0; + dqvarnest = 0; + + STARTSTACKSTR(out); + loop: + /* For each line, until end of word */ + { + CHECKEND(); /* set c to PEOF if at end of here document */ + for (;;) { /* until end of line or end of word */ + CHECKSTRSPACE(4, out); /* permit 4 calls to USTPUTC */ + switch (SIT(c, syntax)) { + case CNL: /* '\n' */ + if (syntax == BASESYNTAX) + goto endword; /* exit outer loop */ + USTPUTC(c, out); + plinno++; + if (doprompt) + setprompt(2); + c = pgetc(); + goto loop; /* continue outer loop */ + case CWORD: + USTPUTC(c, out); + break; + case CCTL: + if (eofmark == NULL || dblquote) + USTPUTC(CTLESC, out); +#if ENABLE_ASH_BASH_COMPAT + if (c == '\\' && bash_dollar_squote) { + c = decode_dollar_squote(); + if (c & 0x100) { + USTPUTC('\\', out); + c = (unsigned char)c; + } + } +#endif + USTPUTC(c, out); + break; + case CBACK: /* backslash */ + c = pgetc2(); + if (c == PEOF) { + USTPUTC(CTLESC, out); + USTPUTC('\\', out); + pungetc(); + } else if (c == '\n') { + if (doprompt) + setprompt(2); + } else { +#if ENABLE_ASH_EXPAND_PRMT + if (c == '$' && pssyntax) { + USTPUTC(CTLESC, out); + USTPUTC('\\', out); + } +#endif + if (dblquote && c != '\\' + && c != '`' && c != '$' + && (c != '"' || eofmark != NULL) + ) { + USTPUTC(CTLESC, out); + USTPUTC('\\', out); + } + if (SIT(c, SQSYNTAX) == CCTL) + USTPUTC(CTLESC, out); + USTPUTC(c, out); + quotef = 1; + } + break; + case CSQUOTE: + syntax = SQSYNTAX; + quotemark: + if (eofmark == NULL) { + USTPUTC(CTLQUOTEMARK, out); + } + break; + case CDQUOTE: + syntax = DQSYNTAX; + dblquote = 1; + goto quotemark; + case CENDQUOTE: + USE_ASH_BASH_COMPAT(bash_dollar_squote = 0;) + if (eofmark != NULL && arinest == 0 + && varnest == 0 + ) { + USTPUTC(c, out); + } else { + if (dqvarnest == 0) { + syntax = BASESYNTAX; + dblquote = 0; + } + quotef = 1; + goto quotemark; + } + break; + case CVAR: /* '$' */ + PARSESUB(); /* parse substitution */ + break; + case CENDVAR: /* '}' */ + if (varnest > 0) { + varnest--; + if (dqvarnest > 0) { + dqvarnest--; + } + USTPUTC(CTLENDVAR, out); + } else { + USTPUTC(c, out); + } + break; +#if ENABLE_ASH_MATH_SUPPORT + case CLP: /* '(' in arithmetic */ + parenlevel++; + USTPUTC(c, out); + break; + case CRP: /* ')' in arithmetic */ + if (parenlevel > 0) { + USTPUTC(c, out); + --parenlevel; + } else { + if (pgetc() == ')') { + if (--arinest == 0) { + USTPUTC(CTLENDARI, out); + syntax = prevsyntax; + dblquote = (syntax == DQSYNTAX); + } else + USTPUTC(')', out); + } else { + /* + * unbalanced parens + * (don't 2nd guess - no error) + */ + pungetc(); + USTPUTC(')', out); + } + } + break; +#endif + case CBQUOTE: /* '`' */ + PARSEBACKQOLD(); + break; + case CENDFILE: + goto endword; /* exit outer loop */ + case CIGN: + break; + default: + if (varnest == 0) { +#if ENABLE_ASH_BASH_COMPAT + if (c == '&') { + if (pgetc() == '>') + c = 0x100 + '>'; /* flag &> */ + pungetc(); + } +#endif + goto endword; /* exit outer loop */ + } +#if ENABLE_ASH_ALIAS + if (c != PEOA) +#endif + USTPUTC(c, out); + + } + c = pgetc_fast(); + } /* for (;;) */ + } + endword: +#if ENABLE_ASH_MATH_SUPPORT + if (syntax == ARISYNTAX) + raise_error_syntax("missing '))'"); +#endif + if (syntax != BASESYNTAX && !parsebackquote && eofmark == NULL) + raise_error_syntax("unterminated quoted string"); + if (varnest != 0) { + startlinno = plinno; + /* { */ + raise_error_syntax("missing '}'"); + } + USTPUTC('\0', out); + len = out - (char *)stackblock(); + out = stackblock(); + if (eofmark == NULL) { + if ((c == '>' || c == '<' USE_ASH_BASH_COMPAT( || c == 0x100 + '>')) + && quotef == 0 + ) { + if (isdigit_str9(out)) { + PARSEREDIR(); /* passed as params: out, c */ + lasttoken = TREDIR; + return lasttoken; + } + /* else: non-number X seen, interpret it + * as "NNNX>file" = "NNNX >file" */ + } + pungetc(); + } + quoteflag = quotef; + backquotelist = bqlist; + grabstackblock(len); + wordtext = out; + lasttoken = TWORD; + return lasttoken; +/* end of readtoken routine */ + +/* + * Check to see whether we are at the end of the here document. When this + * is called, c is set to the first character of the next input line. If + * we are at the end of the here document, this routine sets the c to PEOF. + */ +checkend: { + if (eofmark) { +#if ENABLE_ASH_ALIAS + if (c == PEOA) { + c = pgetc2(); + } +#endif + if (striptabs) { + while (c == '\t') { + c = pgetc2(); + } + } + if (c == *eofmark) { + if (pfgets(line, sizeof(line)) != NULL) { + char *p, *q; + + p = line; + for (q = eofmark + 1; *q && *p == *q; p++, q++) + continue; + if (*p == '\n' && *q == '\0') { + c = PEOF; + plinno++; + needprompt = doprompt; + } else { + pushstring(line, NULL); + } + } + } + } + goto checkend_return; +} + +/* + * Parse a redirection operator. The variable "out" points to a string + * specifying the fd to be redirected. The variable "c" contains the + * first character of the redirection operator. + */ +parseredir: { + /* out is already checked to be a valid number or "" */ + int fd = (*out == '\0' ? -1 : atoi(out)); + union node *np; + + np = stzalloc(sizeof(struct nfile)); + if (c == '>') { + np->nfile.fd = 1; + c = pgetc(); + if (c == '>') + np->type = NAPPEND; + else if (c == '|') + np->type = NCLOBBER; + else if (c == '&') + np->type = NTOFD; + /* it also can be NTO2 (>&file), but we can't figure it out yet */ + else { + np->type = NTO; + pungetc(); + } + } +#if ENABLE_ASH_BASH_COMPAT + else if (c == 0x100 + '>') { /* this flags &> redirection */ + np->nfile.fd = 1; + pgetc(); /* this is '>', no need to check */ + np->type = NTO2; + } +#endif + else { /* c == '<' */ + /*np->nfile.fd = 0; - stzalloc did it */ + c = pgetc(); + switch (c) { + case '<': + if (sizeof(struct nfile) != sizeof(struct nhere)) { + np = stzalloc(sizeof(struct nhere)); + /*np->nfile.fd = 0; - stzalloc did it */ + } + np->type = NHERE; + heredoc = stzalloc(sizeof(struct heredoc)); + heredoc->here = np; + c = pgetc(); + if (c == '-') { + heredoc->striptabs = 1; + } else { + /*heredoc->striptabs = 0; - stzalloc did it */ + pungetc(); + } + break; + + case '&': + np->type = NFROMFD; + break; + + case '>': + np->type = NFROMTO; + break; + + default: + np->type = NFROM; + pungetc(); + break; + } + } + if (fd >= 0) + np->nfile.fd = fd; + redirnode = np; + goto parseredir_return; +} + +/* + * Parse a substitution. At this point, we have read the dollar sign + * and nothing else. + */ + +/* is_special(c) evaluates to 1 for c in "!#$*-0123456789?@"; 0 otherwise + * (assuming ascii char codes, as the original implementation did) */ +#define is_special(c) \ + (((unsigned)(c) - 33 < 32) \ + && ((0xc1ff920dU >> ((unsigned)(c) - 33)) & 1)) +parsesub: { + int subtype; + int typeloc; + int flags; + char *p; + static const char types[] ALIGN1 = "}-+?="; + + c = pgetc(); + if (c <= PEOA_OR_PEOF + || (c != '(' && c != '{' && !is_name(c) && !is_special(c)) + ) { +#if ENABLE_ASH_BASH_COMPAT + if (c == '\'') + bash_dollar_squote = 1; + else +#endif + USTPUTC('$', out); + pungetc(); + } else if (c == '(') { /* $(command) or $((arith)) */ + if (pgetc() == '(') { +#if ENABLE_ASH_MATH_SUPPORT + PARSEARITH(); +#else + raise_error_syntax("you disabled math support for $((arith)) syntax"); +#endif + } else { + pungetc(); + PARSEBACKQNEW(); + } + } else { + USTPUTC(CTLVAR, out); + typeloc = out - (char *)stackblock(); + USTPUTC(VSNORMAL, out); + subtype = VSNORMAL; + if (c == '{') { + c = pgetc(); + if (c == '#') { + c = pgetc(); + if (c == '}') + c = '#'; + else + subtype = VSLENGTH; + } else + subtype = 0; + } + if (c > PEOA_OR_PEOF && is_name(c)) { + do { + STPUTC(c, out); + c = pgetc(); + } while (c > PEOA_OR_PEOF && is_in_name(c)); + } else if (isdigit(c)) { + do { + STPUTC(c, out); + c = pgetc(); + } while (isdigit(c)); + } else if (is_special(c)) { + USTPUTC(c, out); + c = pgetc(); + } else { + badsub: + raise_error_syntax("bad substitution"); + } + + STPUTC('=', out); + flags = 0; + if (subtype == 0) { + switch (c) { + case ':': + c = pgetc(); +#if ENABLE_ASH_BASH_COMPAT + if (c == ':' || c == '$' || isdigit(c)) { + pungetc(); + subtype = VSSUBSTR; + break; + } +#endif + flags = VSNUL; + /*FALLTHROUGH*/ + default: + p = strchr(types, c); + if (p == NULL) + goto badsub; + subtype = p - types + VSNORMAL; + break; + case '%': + case '#': { + int cc = c; + subtype = c == '#' ? VSTRIMLEFT : VSTRIMRIGHT; + c = pgetc(); + if (c == cc) + subtype++; + else + pungetc(); + break; + } +#if ENABLE_ASH_BASH_COMPAT + case '/': + subtype = VSREPLACE; + c = pgetc(); + if (c == '/') + subtype++; /* VSREPLACEALL */ + else + pungetc(); + break; +#endif + } + } else { + pungetc(); + } + if (dblquote || arinest) + flags |= VSQUOTE; + *((char *)stackblock() + typeloc) = subtype | flags; + if (subtype != VSNORMAL) { + varnest++; + if (dblquote || arinest) { + dqvarnest++; + } + } + } + goto parsesub_return; +} + +/* + * Called to parse command substitutions. Newstyle is set if the command + * is enclosed inside $(...); nlpp is a pointer to the head of the linked + * list of commands (passed by reference), and savelen is the number of + * characters on the top of the stack which must be preserved. + */ +parsebackq: { + struct nodelist **nlpp; + smallint savepbq; + union node *n; + char *volatile str; + struct jmploc jmploc; + struct jmploc *volatile savehandler; + size_t savelen; + smallint saveprompt = 0; + +#ifdef __GNUC__ + (void) &saveprompt; +#endif + savepbq = parsebackquote; + if (setjmp(jmploc.loc)) { + free(str); + parsebackquote = 0; + exception_handler = savehandler; + longjmp(exception_handler->loc, 1); + } + INT_OFF; + str = NULL; + savelen = out - (char *)stackblock(); + if (savelen > 0) { + str = ckmalloc(savelen); + memcpy(str, stackblock(), savelen); + } + savehandler = exception_handler; + exception_handler = &jmploc; + INT_ON; + if (oldstyle) { + /* We must read until the closing backquote, giving special + treatment to some slashes, and then push the string and + reread it as input, interpreting it normally. */ + char *pout; + int pc; + size_t psavelen; + char *pstr; + + + STARTSTACKSTR(pout); + for (;;) { + if (needprompt) { + setprompt(2); + } + pc = pgetc(); + switch (pc) { + case '`': + goto done; + + case '\\': + pc = pgetc(); + if (pc == '\n') { + plinno++; + if (doprompt) + setprompt(2); + /* + * If eating a newline, avoid putting + * the newline into the new character + * stream (via the STPUTC after the + * switch). + */ + continue; + } + if (pc != '\\' && pc != '`' && pc != '$' + && (!dblquote || pc != '"')) + STPUTC('\\', pout); + if (pc > PEOA_OR_PEOF) { + break; + } + /* fall through */ + + case PEOF: +#if ENABLE_ASH_ALIAS + case PEOA: +#endif + startlinno = plinno; + raise_error_syntax("EOF in backquote substitution"); + + case '\n': + plinno++; + needprompt = doprompt; + break; + + default: + break; + } + STPUTC(pc, pout); + } + done: + STPUTC('\0', pout); + psavelen = pout - (char *)stackblock(); + if (psavelen > 0) { + pstr = grabstackstr(pout); + setinputstring(pstr); + } + } + nlpp = &bqlist; + while (*nlpp) + nlpp = &(*nlpp)->next; + *nlpp = stzalloc(sizeof(**nlpp)); + /* (*nlpp)->next = NULL; - stzalloc did it */ + parsebackquote = oldstyle; + + if (oldstyle) { + saveprompt = doprompt; + doprompt = 0; + } + + n = list(2); + + if (oldstyle) + doprompt = saveprompt; + else if (readtoken() != TRP) + raise_error_unexpected_syntax(TRP); + + (*nlpp)->n = n; + if (oldstyle) { + /* + * Start reading from old file again, ignoring any pushed back + * tokens left from the backquote parsing + */ + popfile(); + tokpushback = 0; + } + while (stackblocksize() <= savelen) + growstackblock(); + STARTSTACKSTR(out); + if (str) { + memcpy(out, str, savelen); + STADJUST(savelen, out); + INT_OFF; + free(str); + str = NULL; + INT_ON; + } + parsebackquote = savepbq; + exception_handler = savehandler; + if (arinest || dblquote) + USTPUTC(CTLBACKQ | CTLQUOTE, out); + else + USTPUTC(CTLBACKQ, out); + if (oldstyle) + goto parsebackq_oldreturn; + goto parsebackq_newreturn; +} + +#if ENABLE_ASH_MATH_SUPPORT +/* + * Parse an arithmetic expansion (indicate start of one and set state) + */ +parsearith: { + if (++arinest == 1) { + prevsyntax = syntax; + syntax = ARISYNTAX; + USTPUTC(CTLARI, out); + if (dblquote) + USTPUTC('"', out); + else + USTPUTC(' ', out); + } else { + /* + * we collapse embedded arithmetic expansion to + * parenthesis, which should be equivalent + */ + USTPUTC('(', out); + } + goto parsearith_return; +} +#endif + +} /* end of readtoken */ + +/* + * Read the next input token. + * If the token is a word, we set backquotelist to the list of cmds in + * backquotes. We set quoteflag to true if any part of the word was + * quoted. + * If the token is TREDIR, then we set redirnode to a structure containing + * the redirection. + * In all cases, the variable startlinno is set to the number of the line + * on which the token starts. + * + * [Change comment: here documents and internal procedures] + * [Readtoken shouldn't have any arguments. Perhaps we should make the + * word parsing code into a separate routine. In this case, readtoken + * doesn't need to have any internal procedures, but parseword does. + * We could also make parseoperator in essence the main routine, and + * have parseword (readtoken1?) handle both words and redirection.] + */ +#define NEW_xxreadtoken +#ifdef NEW_xxreadtoken +/* singles must be first! */ +static const char xxreadtoken_chars[7] ALIGN1 = { + '\n', '(', ')', /* singles */ + '&', '|', ';', /* doubles */ + 0 +}; + +#define xxreadtoken_singles 3 +#define xxreadtoken_doubles 3 + +static const char xxreadtoken_tokens[] ALIGN1 = { + TNL, TLP, TRP, /* only single occurrence allowed */ + TBACKGND, TPIPE, TSEMI, /* if single occurrence */ + TEOF, /* corresponds to trailing nul */ + TAND, TOR, TENDCASE /* if double occurrence */ +}; + +static int +xxreadtoken(void) +{ + int c; + + if (tokpushback) { + tokpushback = 0; + return lasttoken; + } + if (needprompt) { + setprompt(2); + } + startlinno = plinno; + for (;;) { /* until token or start of word found */ + c = pgetc_fast(); + if (c == ' ' || c == '\t' USE_ASH_ALIAS( || c == PEOA)) + continue; + + if (c == '#') { + while ((c = pgetc()) != '\n' && c != PEOF) + continue; + pungetc(); + } else if (c == '\\') { + if (pgetc() != '\n') { + pungetc(); + break; /* return readtoken1(...) */ + } + startlinno = ++plinno; + if (doprompt) + setprompt(2); + } else { + const char *p; + + p = xxreadtoken_chars + sizeof(xxreadtoken_chars) - 1; + if (c != PEOF) { + if (c == '\n') { + plinno++; + needprompt = doprompt; + } + + p = strchr(xxreadtoken_chars, c); + if (p == NULL) + break; /* return readtoken1(...) */ + + if ((int)(p - xxreadtoken_chars) >= xxreadtoken_singles) { + int cc = pgetc(); + if (cc == c) { /* double occurrence? */ + p += xxreadtoken_doubles + 1; + } else { + pungetc(); +#if ENABLE_ASH_BASH_COMPAT + if (c == '&' && cc == '>') /* &> */ + break; /* return readtoken1(...) */ +#endif + } + } + } + lasttoken = xxreadtoken_tokens[p - xxreadtoken_chars]; + return lasttoken; + } + } /* for (;;) */ + + return readtoken1(c, BASESYNTAX, (char *) NULL, 0); +} +#else /* old xxreadtoken */ +#define RETURN(token) return lasttoken = token +static int +xxreadtoken(void) +{ + int c; + + if (tokpushback) { + tokpushback = 0; + return lasttoken; + } + if (needprompt) { + setprompt(2); + } + startlinno = plinno; + for (;;) { /* until token or start of word found */ + c = pgetc_fast(); + switch (c) { + case ' ': case '\t': +#if ENABLE_ASH_ALIAS + case PEOA: +#endif + continue; + case '#': + while ((c = pgetc()) != '\n' && c != PEOF) + continue; + pungetc(); + continue; + case '\\': + if (pgetc() == '\n') { + startlinno = ++plinno; + if (doprompt) + setprompt(2); + continue; + } + pungetc(); + goto breakloop; + case '\n': + plinno++; + needprompt = doprompt; + RETURN(TNL); + case PEOF: + RETURN(TEOF); + case '&': + if (pgetc() == '&') + RETURN(TAND); + pungetc(); + RETURN(TBACKGND); + case '|': + if (pgetc() == '|') + RETURN(TOR); + pungetc(); + RETURN(TPIPE); + case ';': + if (pgetc() == ';') + RETURN(TENDCASE); + pungetc(); + RETURN(TSEMI); + case '(': + RETURN(TLP); + case ')': + RETURN(TRP); + default: + goto breakloop; + } + } + breakloop: + return readtoken1(c, BASESYNTAX, (char *)NULL, 0); +#undef RETURN +} +#endif /* old xxreadtoken */ + +static int +readtoken(void) +{ + int t; +#if DEBUG + smallint alreadyseen = tokpushback; +#endif + +#if ENABLE_ASH_ALIAS + top: +#endif + + t = xxreadtoken(); + + /* + * eat newlines + */ + if (checkkwd & CHKNL) { + while (t == TNL) { + parseheredoc(); + t = xxreadtoken(); + } + } + + if (t != TWORD || quoteflag) { + goto out; + } + + /* + * check for keywords + */ + if (checkkwd & CHKKWD) { + const char *const *pp; + + pp = findkwd(wordtext); + if (pp) { + lasttoken = t = pp - tokname_array; + TRACE(("keyword %s recognized\n", tokname(t))); + goto out; + } + } + + if (checkkwd & CHKALIAS) { +#if ENABLE_ASH_ALIAS + struct alias *ap; + ap = lookupalias(wordtext, 1); + if (ap != NULL) { + if (*ap->val) { + pushstring(ap->val, ap); + } + goto top; + } +#endif + } + out: + checkkwd = 0; +#if DEBUG + if (!alreadyseen) + TRACE(("token %s %s\n", tokname(t), t == TWORD ? wordtext : "")); + else + TRACE(("reread token %s %s\n", tokname(t), t == TWORD ? wordtext : "")); +#endif + return t; +} + +static char +peektoken(void) +{ + int t; + + t = readtoken(); + tokpushback = 1; + return tokname_array[t][0]; +} + +/* + * Read and parse a command. Returns NEOF on end of file. (NULL is a + * valid parse tree indicating a blank line.) + */ +static union node * +parsecmd(int interact) +{ + int t; + + tokpushback = 0; + doprompt = interact; + if (doprompt) + setprompt(doprompt); + needprompt = 0; + t = readtoken(); + if (t == TEOF) + return NEOF; + if (t == TNL) + return NULL; + tokpushback = 1; + return list(1); +} + +/* + * Input any here documents. + */ +static void +parseheredoc(void) +{ + struct heredoc *here; + union node *n; + + here = heredoclist; + heredoclist = NULL; + + while (here) { + if (needprompt) { + setprompt(2); + } + readtoken1(pgetc(), here->here->type == NHERE? SQSYNTAX : DQSYNTAX, + here->eofmark, here->striptabs); + n = stzalloc(sizeof(struct narg)); + n->narg.type = NARG; + /*n->narg.next = NULL; - stzalloc did it */ + n->narg.text = wordtext; + n->narg.backquote = backquotelist; + here->here->nhere.doc = n; + here = here->next; + } +} + + +/* + * called by editline -- any expansions to the prompt should be added here. + */ +#if ENABLE_ASH_EXPAND_PRMT +static const char * +expandstr(const char *ps) +{ + union node n; + + /* XXX Fix (char *) cast. */ + setinputstring((char *)ps); + readtoken1(pgetc(), PSSYNTAX, nullstr, 0); + popfile(); + + n.narg.type = NARG; + n.narg.next = NULL; + n.narg.text = wordtext; + n.narg.backquote = backquotelist; + + expandarg(&n, NULL, 0); + return stackblock(); +} +#endif + +/* + * Execute a command or commands contained in a string. + */ +static int +evalstring(char *s, int mask) +{ + union node *n; + struct stackmark smark; + int skip; + + setinputstring(s); + setstackmark(&smark); + + skip = 0; + while ((n = parsecmd(0)) != NEOF) { + evaltree(n, 0); + popstackmark(&smark); + skip = evalskip; + if (skip) + break; + } + popfile(); + + skip &= mask; + evalskip = skip; + return skip; +} + +/* + * The eval command. + */ +static int +evalcmd(int argc UNUSED_PARAM, char **argv) +{ + char *p; + char *concat; + + if (argv[1]) { + p = argv[1]; + argv += 2; + if (argv[0]) { + STARTSTACKSTR(concat); + for (;;) { + concat = stack_putstr(p, concat); + p = *argv++; + if (p == NULL) + break; + STPUTC(' ', concat); + } + STPUTC('\0', concat); + p = grabstackstr(concat); + } + evalstring(p, ~SKIPEVAL); + + } + return exitstatus; +} + +/* + * Read and execute commands. "Top" is nonzero for the top level command + * loop; it turns on prompting if the shell is interactive. + */ +static int +cmdloop(int top) +{ + union node *n; + struct stackmark smark; + int inter; + int numeof = 0; + + TRACE(("cmdloop(%d) called\n", top)); + for (;;) { + int skip; + + setstackmark(&smark); +#if JOBS + if (doing_jobctl) + showjobs(stderr, SHOW_CHANGED); +#endif + inter = 0; + if (iflag && top) { + inter++; +#if ENABLE_ASH_MAIL + chkmail(); +#endif + } + n = parsecmd(inter); + /* showtree(n); DEBUG */ + if (n == NEOF) { + if (!top || numeof >= 50) + break; + if (!stoppedjobs()) { + if (!Iflag) + break; + out2str("\nUse \"exit\" to leave shell.\n"); + } + numeof++; + } else if (nflag == 0) { + /* job_warning can only be 2,1,0. Here 2->1, 1/0->0 */ + job_warning >>= 1; + numeof = 0; + evaltree(n, 0); + } + popstackmark(&smark); + skip = evalskip; + + if (skip) { + evalskip = 0; + return skip & SKIPEVAL; + } + } + return 0; +} + +/* + * Take commands from a file. To be compatible we should do a path + * search for the file, which is necessary to find sub-commands. + */ +static char * +find_dot_file(char *name) +{ + char *fullname; + const char *path = pathval(); + struct stat statb; + + /* don't try this for absolute or relative paths */ + if (strchr(name, '/')) + return name; + + while ((fullname = padvance(&path, name)) != NULL) { + if ((stat(fullname, &statb) == 0) && S_ISREG(statb.st_mode)) { + /* + * Don't bother freeing here, since it will + * be freed by the caller. + */ + return fullname; + } + stunalloc(fullname); + } + + /* not found in the PATH */ + ash_msg_and_raise_error("%s: not found", name); + /* NOTREACHED */ +} + +static int +dotcmd(int argc, char **argv) +{ + struct strlist *sp; + volatile struct shparam saveparam; + int status = 0; + + for (sp = cmdenviron; sp; sp = sp->next) + setvareq(ckstrdup(sp->text), VSTRFIXED | VTEXTFIXED); + + if (argv[1]) { /* That's what SVR2 does */ + char *fullname = find_dot_file(argv[1]); + argv += 2; + argc -= 2; + if (argc) { /* argc > 0, argv[0] != NULL */ + saveparam = shellparam; + shellparam.malloced = 0; + shellparam.nparam = argc; + shellparam.p = argv; + }; + + setinputfile(fullname, INPUT_PUSH_FILE); + commandname = fullname; + cmdloop(0); + popfile(); + + if (argc) { + freeparam(&shellparam); + shellparam = saveparam; + }; + status = exitstatus; + } + return status; +} + +static int +exitcmd(int argc UNUSED_PARAM, char **argv) +{ + if (stoppedjobs()) + return 0; + if (argv[1]) + exitstatus = number(argv[1]); + raise_exception(EXEXIT); + /* NOTREACHED */ +} + +/* + * Read a file containing shell functions. + */ +static void +readcmdfile(char *name) +{ + setinputfile(name, INPUT_PUSH_FILE); + cmdloop(0); + popfile(); +} + + +/* ============ find_command inplementation */ + +/* + * Resolve a command name. If you change this routine, you may have to + * change the shellexec routine as well. + */ +static void +find_command(char *name, struct cmdentry *entry, int act, const char *path) +{ + struct tblentry *cmdp; + int idx; + int prev; + char *fullname; + struct stat statb; + int e; + int updatetbl; + struct builtincmd *bcmd; + + /* If name contains a slash, don't use PATH or hash table */ + if (strchr(name, '/') != NULL) { + entry->u.index = -1; + if (act & DO_ABS) { + while (stat(name, &statb) < 0) { +#ifdef SYSV + if (errno == EINTR) + continue; +#endif + entry->cmdtype = CMDUNKNOWN; + return; + } + } + entry->cmdtype = CMDNORMAL; + return; + } + +/* #if ENABLE_FEATURE_SH_STANDALONE... moved after builtin check */ + + updatetbl = (path == pathval()); + if (!updatetbl) { + act |= DO_ALTPATH; + if (strstr(path, "%builtin") != NULL) + act |= DO_ALTBLTIN; + } + + /* If name is in the table, check answer will be ok */ + cmdp = cmdlookup(name, 0); + if (cmdp != NULL) { + int bit; + + switch (cmdp->cmdtype) { + default: +#if DEBUG + abort(); +#endif + case CMDNORMAL: + bit = DO_ALTPATH; + break; + case CMDFUNCTION: + bit = DO_NOFUNC; + break; + case CMDBUILTIN: + bit = DO_ALTBLTIN; + break; + } + if (act & bit) { + updatetbl = 0; + cmdp = NULL; + } else if (cmdp->rehash == 0) + /* if not invalidated by cd, we're done */ + goto success; + } + + /* If %builtin not in path, check for builtin next */ + bcmd = find_builtin(name); + if (bcmd) { + if (IS_BUILTIN_REGULAR(bcmd)) + goto builtin_success; + if (act & DO_ALTPATH) { + if (!(act & DO_ALTBLTIN)) + goto builtin_success; + } else if (builtinloc <= 0) { + goto builtin_success; + } + } + +#if ENABLE_FEATURE_SH_STANDALONE + { + int applet_no = find_applet_by_name(name); + if (applet_no >= 0) { + entry->cmdtype = CMDNORMAL; + entry->u.index = -2 - applet_no; + return; + } + } +#endif + + /* We have to search path. */ + prev = -1; /* where to start */ + if (cmdp && cmdp->rehash) { /* doing a rehash */ + if (cmdp->cmdtype == CMDBUILTIN) + prev = builtinloc; + else + prev = cmdp->param.index; + } + + e = ENOENT; + idx = -1; + loop: + while ((fullname = padvance(&path, name)) != NULL) { + stunalloc(fullname); + /* NB: code below will still use fullname + * despite it being "unallocated" */ + idx++; + if (pathopt) { + if (prefix(pathopt, "builtin")) { + if (bcmd) + goto builtin_success; + continue; + } + if ((act & DO_NOFUNC) + || !prefix(pathopt, "func") + ) { /* ignore unimplemented options */ + continue; + } + } + /* if rehash, don't redo absolute path names */ + if (fullname[0] == '/' && idx <= prev) { + if (idx < prev) + continue; + TRACE(("searchexec \"%s\": no change\n", name)); + goto success; + } + while (stat(fullname, &statb) < 0) { +#ifdef SYSV + if (errno == EINTR) + continue; +#endif + if (errno != ENOENT && errno != ENOTDIR) + e = errno; + goto loop; + } + e = EACCES; /* if we fail, this will be the error */ + if (!S_ISREG(statb.st_mode)) + continue; + if (pathopt) { /* this is a %func directory */ + stalloc(strlen(fullname) + 1); + /* NB: stalloc will return space pointed by fullname + * (because we don't have any intervening allocations + * between stunalloc above and this stalloc) */ + readcmdfile(fullname); + cmdp = cmdlookup(name, 0); + if (cmdp == NULL || cmdp->cmdtype != CMDFUNCTION) + ash_msg_and_raise_error("%s not defined in %s", name, fullname); + stunalloc(fullname); + goto success; + } + TRACE(("searchexec \"%s\" returns \"%s\"\n", name, fullname)); + if (!updatetbl) { + entry->cmdtype = CMDNORMAL; + entry->u.index = idx; + return; + } + INT_OFF; + cmdp = cmdlookup(name, 1); + cmdp->cmdtype = CMDNORMAL; + cmdp->param.index = idx; + INT_ON; + goto success; + } + + /* We failed. If there was an entry for this command, delete it */ + if (cmdp && updatetbl) + delete_cmd_entry(); + if (act & DO_ERR) + ash_msg("%s: %s", name, errmsg(e, "not found")); + entry->cmdtype = CMDUNKNOWN; + return; + + builtin_success: + if (!updatetbl) { + entry->cmdtype = CMDBUILTIN; + entry->u.cmd = bcmd; + return; + } + INT_OFF; + cmdp = cmdlookup(name, 1); + cmdp->cmdtype = CMDBUILTIN; + cmdp->param.cmd = bcmd; + INT_ON; + success: + cmdp->rehash = 0; + entry->cmdtype = cmdp->cmdtype; + entry->u = cmdp->param; +} + + +/* ============ trap.c */ + +/* + * The trap builtin. + */ +static int +trapcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) +{ + char *action; + char **ap; + int signo; + + nextopt(nullstr); + ap = argptr; + if (!*ap) { + for (signo = 0; signo < NSIG; signo++) { + if (trap[signo] != NULL) { + out1fmt("trap -- %s %s\n", + single_quote(trap[signo]), + get_signame(signo)); + } + } + return 0; + } + action = NULL; + if (ap[1]) + action = *ap++; + while (*ap) { + signo = get_signum(*ap); + if (signo < 0) + ash_msg_and_raise_error("%s: bad trap", *ap); + INT_OFF; + if (action) { + if (LONE_DASH(action)) + action = NULL; + else + action = ckstrdup(action); + } + free(trap[signo]); + trap[signo] = action; + if (signo != 0) + setsignal(signo); + INT_ON; + ap++; + } + return 0; +} + + +/* ============ Builtins */ + +#if !ENABLE_FEATURE_SH_EXTRA_QUIET +/* + * Lists available builtins + */ +static int +helpcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) +{ + unsigned col; + unsigned i; + + out1fmt("\nBuilt-in commands:\n-------------------\n"); + for (col = 0, i = 0; i < ARRAY_SIZE(builtintab); i++) { + col += out1fmt("%c%s", ((col == 0) ? '\t' : ' '), + builtintab[i].name + 1); + if (col > 60) { + out1fmt("\n"); + col = 0; + } + } +#if ENABLE_FEATURE_SH_STANDALONE + { + const char *a = applet_names; + while (*a) { + col += out1fmt("%c%s", ((col == 0) ? '\t' : ' '), a); + if (col > 60) { + out1fmt("\n"); + col = 0; + } + a += strlen(a) + 1; + } + } +#endif + out1fmt("\n\n"); + return EXIT_SUCCESS; +} +#endif /* FEATURE_SH_EXTRA_QUIET */ + +/* + * The export and readonly commands. + */ +static int +exportcmd(int argc UNUSED_PARAM, char **argv) +{ + struct var *vp; + char *name; + const char *p; + char **aptr; + int flag = argv[0][0] == 'r' ? VREADONLY : VEXPORT; + + if (nextopt("p") != 'p') { + aptr = argptr; + name = *aptr; + if (name) { + do { + p = strchr(name, '='); + if (p != NULL) { + p++; + } else { + vp = *findvar(hashvar(name), name); + if (vp) { + vp->flags |= flag; + continue; + } + } + setvar(name, p, flag); + } while ((name = *++aptr) != NULL); + return 0; + } + } + showvars(argv[0], flag, 0); + return 0; +} + +/* + * Delete a function if it exists. + */ +static void +unsetfunc(const char *name) +{ + struct tblentry *cmdp; + + cmdp = cmdlookup(name, 0); + if (cmdp!= NULL && cmdp->cmdtype == CMDFUNCTION) + delete_cmd_entry(); +} + +/* + * The unset builtin command. We unset the function before we unset the + * variable to allow a function to be unset when there is a readonly variable + * with the same name. + */ +static int +unsetcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) +{ + char **ap; + int i; + int flag = 0; + int ret = 0; + + while ((i = nextopt("vf")) != '\0') { + flag = i; + } + + for (ap = argptr; *ap; ap++) { + if (flag != 'f') { + i = unsetvar(*ap); + ret |= i; + if (!(i & 2)) + continue; + } + if (flag != 'v') + unsetfunc(*ap); + } + return ret & 1; +} + + +/* setmode.c */ + +#include + +static const unsigned char timescmd_str[] ALIGN1 = { + ' ', offsetof(struct tms, tms_utime), + '\n', offsetof(struct tms, tms_stime), + ' ', offsetof(struct tms, tms_cutime), + '\n', offsetof(struct tms, tms_cstime), + 0 +}; + +static int +timescmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) +{ + long clk_tck, s, t; + const unsigned char *p; + struct tms buf; + + clk_tck = sysconf(_SC_CLK_TCK); + times(&buf); + + p = timescmd_str; + do { + t = *(clock_t *)(((char *) &buf) + p[1]); + s = t / clk_tck; + out1fmt("%ldm%ld.%.3lds%c", + s/60, s%60, + ((t - s * clk_tck) * 1000) / clk_tck, + p[0]); + } while (*(p += 2)); + + return 0; +} + +#if ENABLE_ASH_MATH_SUPPORT +static arith_t +dash_arith(const char *s) +{ + arith_t result; + int errcode = 0; + + INT_OFF; + result = arith(s, &errcode); + if (errcode < 0) { + if (errcode == -3) + ash_msg_and_raise_error("exponent less than 0"); + if (errcode == -2) + ash_msg_and_raise_error("divide by zero"); + if (errcode == -5) + ash_msg_and_raise_error("expression recursion loop detected"); + raise_error_syntax(s); + } + INT_ON; + + return result; +} + +/* + * The let builtin. partial stolen from GNU Bash, the Bourne Again SHell. + * Copyright (C) 1987, 1989, 1991 Free Software Foundation, Inc. + * + * Copyright (C) 2003 Vladimir Oleynik + */ +static int +letcmd(int argc UNUSED_PARAM, char **argv) +{ + arith_t i; + + argv++; + if (!*argv) + ash_msg_and_raise_error("expression expected"); + do { + i = dash_arith(*argv); + } while (*++argv); + + return !i; +} +#endif /* ASH_MATH_SUPPORT */ + + +/* ============ miscbltin.c + * + * Miscellaneous builtins. + */ + +#undef rflag + +#if defined(__GLIBC__) && __GLIBC__ == 2 && __GLIBC_MINOR__ < 1 +typedef enum __rlimit_resource rlim_t; +#endif + +/* + * The read builtin. Options: + * -r Do not interpret '\' specially + * -s Turn off echo (tty only) + * -n NCHARS Read NCHARS max + * -p PROMPT Display PROMPT on stderr (if input is from tty) + * -t SECONDS Timeout after SECONDS (tty or pipe only) + * -u FD Read from given FD instead of fd 0 + * This uses unbuffered input, which may be avoidable in some cases. + * TODO: bash also has: + * -a ARRAY Read into array[0],[1],etc + * -d DELIM End on DELIM char, not newline + * -e Use line editing (tty only) + */ +static int +readcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) +{ + static const char *const arg_REPLY[] = { "REPLY", NULL }; + + char **ap; + int backslash; + char c; + int rflag; + char *prompt; + const char *ifs; + char *p; + int startword; + int status; + int i; + int fd = 0; +#if ENABLE_ASH_READ_NCHARS + int nchars = 0; /* if != 0, -n is in effect */ + int silent = 0; + struct termios tty, old_tty; +#endif +#if ENABLE_ASH_READ_TIMEOUT + unsigned end_ms = 0; + unsigned timeout = 0; +#endif + + rflag = 0; + prompt = NULL; + while ((i = nextopt("p:u:r" + USE_ASH_READ_TIMEOUT("t:") + USE_ASH_READ_NCHARS("n:s") + )) != '\0') { + switch (i) { + case 'p': + prompt = optionarg; + break; +#if ENABLE_ASH_READ_NCHARS + case 'n': + nchars = bb_strtou(optionarg, NULL, 10); + if (nchars < 0 || errno) + ash_msg_and_raise_error("invalid count"); + /* nchars == 0: off (bash 3.2 does this too) */ + break; + case 's': + silent = 1; + break; +#endif +#if ENABLE_ASH_READ_TIMEOUT + case 't': + timeout = bb_strtou(optionarg, NULL, 10); + if (errno || timeout > UINT_MAX / 2048) + ash_msg_and_raise_error("invalid timeout"); + timeout *= 1000; +#if 0 /* even bash have no -t N.NNN support */ + ts.tv_sec = bb_strtou(optionarg, &p, 10); + ts.tv_usec = 0; + /* EINVAL means number is ok, but not terminated by NUL */ + if (*p == '.' && errno == EINVAL) { + char *p2; + if (*++p) { + int scale; + ts.tv_usec = bb_strtou(p, &p2, 10); + if (errno) + ash_msg_and_raise_error("invalid timeout"); + scale = p2 - p; + /* normalize to usec */ + if (scale > 6) + ash_msg_and_raise_error("invalid timeout"); + while (scale++ < 6) + ts.tv_usec *= 10; + } + } else if (ts.tv_sec < 0 || errno) { + ash_msg_and_raise_error("invalid timeout"); + } + if (!(ts.tv_sec | ts.tv_usec)) { /* both are 0? */ + ash_msg_and_raise_error("invalid timeout"); + } +#endif /* if 0 */ + break; +#endif + case 'r': + rflag = 1; + break; + case 'u': + fd = bb_strtou(optionarg, NULL, 10); + if (fd < 0 || errno) + ash_msg_and_raise_error("invalid file descriptor"); + break; + default: + break; + } + } + if (prompt && isatty(fd)) { + out2str(prompt); + } + ap = argptr; + if (*ap == NULL) + ap = (char**)arg_REPLY; + ifs = bltinlookup("IFS"); + if (ifs == NULL) + ifs = defifs; +#if ENABLE_ASH_READ_NCHARS + tcgetattr(fd, &tty); + old_tty = tty; + if (nchars || silent) { + if (nchars) { + tty.c_lflag &= ~ICANON; + tty.c_cc[VMIN] = nchars < 256 ? nchars : 255; + } + if (silent) { + tty.c_lflag &= ~(ECHO | ECHOK | ECHONL); + } + /* if tcgetattr failed, tcsetattr will fail too. + * Ignoring, it's harmless. */ + tcsetattr(fd, TCSANOW, &tty); + } +#endif + + status = 0; + startword = 1; + backslash = 0; +#if ENABLE_ASH_READ_TIMEOUT + if (timeout) /* NB: ensuring end_ms is nonzero */ + end_ms = ((unsigned)(monotonic_us() / 1000) + timeout) | 1; +#endif + STARTSTACKSTR(p); + do { +#if ENABLE_ASH_READ_TIMEOUT + if (end_ms) { + struct pollfd pfd[1]; + pfd[0].fd = fd; + pfd[0].events = POLLIN; + timeout = end_ms - (unsigned)(monotonic_us() / 1000); + if ((int)timeout <= 0 /* already late? */ + || safe_poll(pfd, 1, timeout) != 1 /* no? wait... */ + ) { /* timed out! */ +#if ENABLE_ASH_READ_NCHARS + tcsetattr(fd, TCSANOW, &old_tty); +#endif + return 1; + } + } +#endif + if (nonblock_safe_read(fd, &c, 1) != 1) { + status = 1; + break; + } + if (c == '\0') + continue; + if (backslash) { + backslash = 0; + if (c != '\n') + goto put; + continue; + } + if (!rflag && c == '\\') { + backslash++; + continue; + } + if (c == '\n') + break; + if (startword && *ifs == ' ' && strchr(ifs, c)) { + continue; + } + startword = 0; + if (ap[1] != NULL && strchr(ifs, c) != NULL) { + STACKSTRNUL(p); + setvar(*ap, stackblock(), 0); + ap++; + startword = 1; + STARTSTACKSTR(p); + } else { + put: + STPUTC(c, p); + } + } +/* end of do {} while: */ +#if ENABLE_ASH_READ_NCHARS + while (--nchars); +#else + while (1); +#endif + +#if ENABLE_ASH_READ_NCHARS + tcsetattr(fd, TCSANOW, &old_tty); +#endif + + STACKSTRNUL(p); + /* Remove trailing blanks */ + while ((char *)stackblock() <= --p && strchr(ifs, *p) != NULL) + *p = '\0'; + setvar(*ap, stackblock(), 0); + while (*++ap != NULL) + setvar(*ap, nullstr, 0); + return status; +} + +static int +umaskcmd(int argc UNUSED_PARAM, char **argv) +{ + static const char permuser[3] ALIGN1 = "ugo"; + static const char permmode[3] ALIGN1 = "rwx"; + static const short permmask[] ALIGN2 = { + S_IRUSR, S_IWUSR, S_IXUSR, + S_IRGRP, S_IWGRP, S_IXGRP, + S_IROTH, S_IWOTH, S_IXOTH + }; + + char *ap; + mode_t mask; + int i; + int symbolic_mode = 0; + + while (nextopt("S") != '\0') { + symbolic_mode = 1; + } + + INT_OFF; + mask = umask(0); + umask(mask); + INT_ON; + + ap = *argptr; + if (ap == NULL) { + if (symbolic_mode) { + char buf[18]; + char *p = buf; + + for (i = 0; i < 3; i++) { + int j; + + *p++ = permuser[i]; + *p++ = '='; + for (j = 0; j < 3; j++) { + if ((mask & permmask[3 * i + j]) == 0) { + *p++ = permmode[j]; + } + } + *p++ = ','; + } + *--p = 0; + puts(buf); + } else { + out1fmt("%.4o\n", mask); + } + } else { + if (isdigit((unsigned char) *ap)) { + mask = 0; + do { + if (*ap >= '8' || *ap < '0') + ash_msg_and_raise_error(illnum, argv[1]); + mask = (mask << 3) + (*ap - '0'); + } while (*++ap != '\0'); + umask(mask); + } else { + mask = ~mask & 0777; + if (!bb_parse_mode(ap, &mask)) { + ash_msg_and_raise_error("illegal mode: %s", ap); + } + umask(~mask & 0777); + } + } + return 0; +} + +/* + * ulimit builtin + * + * This code, originally by Doug Gwyn, Doug Kingston, Eric Gisin, and + * Michael Rendell was ripped from pdksh 5.0.8 and hacked for use with + * ash by J.T. Conklin. + * + * Public domain. + */ + +struct limits { + uint8_t cmd; /* RLIMIT_xxx fit into it */ + uint8_t factor_shift; /* shift by to get rlim_{cur,max} values */ + char option; +}; + +static const struct limits limits_tbl[] = { +#ifdef RLIMIT_CPU + { RLIMIT_CPU, 0, 't' }, +#endif +#ifdef RLIMIT_FSIZE + { RLIMIT_FSIZE, 9, 'f' }, +#endif +#ifdef RLIMIT_DATA + { RLIMIT_DATA, 10, 'd' }, +#endif +#ifdef RLIMIT_STACK + { RLIMIT_STACK, 10, 's' }, +#endif +#ifdef RLIMIT_CORE + { RLIMIT_CORE, 9, 'c' }, +#endif +#ifdef RLIMIT_RSS + { RLIMIT_RSS, 10, 'm' }, +#endif +#ifdef RLIMIT_MEMLOCK + { RLIMIT_MEMLOCK, 10, 'l' }, +#endif +#ifdef RLIMIT_NPROC + { RLIMIT_NPROC, 0, 'p' }, +#endif +#ifdef RLIMIT_NOFILE + { RLIMIT_NOFILE, 0, 'n' }, +#endif +#ifdef RLIMIT_AS + { RLIMIT_AS, 10, 'v' }, +#endif +#ifdef RLIMIT_LOCKS + { RLIMIT_LOCKS, 0, 'w' }, +#endif +}; +static const char limits_name[] = +#ifdef RLIMIT_CPU + "time(seconds)" "\0" +#endif +#ifdef RLIMIT_FSIZE + "file(blocks)" "\0" +#endif +#ifdef RLIMIT_DATA + "data(kb)" "\0" +#endif +#ifdef RLIMIT_STACK + "stack(kb)" "\0" +#endif +#ifdef RLIMIT_CORE + "coredump(blocks)" "\0" +#endif +#ifdef RLIMIT_RSS + "memory(kb)" "\0" +#endif +#ifdef RLIMIT_MEMLOCK + "locked memory(kb)" "\0" +#endif +#ifdef RLIMIT_NPROC + "process" "\0" +#endif +#ifdef RLIMIT_NOFILE + "nofiles" "\0" +#endif +#ifdef RLIMIT_AS + "vmemory(kb)" "\0" +#endif +#ifdef RLIMIT_LOCKS + "locks" "\0" +#endif +; + +enum limtype { SOFT = 0x1, HARD = 0x2 }; + +static void +printlim(enum limtype how, const struct rlimit *limit, + const struct limits *l) +{ + rlim_t val; + + val = limit->rlim_max; + if (how & SOFT) + val = limit->rlim_cur; + + if (val == RLIM_INFINITY) + out1fmt("unlimited\n"); + else { + val >>= l->factor_shift; + out1fmt("%lld\n", (long long) val); + } +} + +static int +ulimitcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) +{ + int c; + rlim_t val = 0; + enum limtype how = SOFT | HARD; + const struct limits *l; + int set, all = 0; + int optc, what; + struct rlimit limit; + + what = 'f'; + while ((optc = nextopt("HSa" +#ifdef RLIMIT_CPU + "t" +#endif +#ifdef RLIMIT_FSIZE + "f" +#endif +#ifdef RLIMIT_DATA + "d" +#endif +#ifdef RLIMIT_STACK + "s" +#endif +#ifdef RLIMIT_CORE + "c" +#endif +#ifdef RLIMIT_RSS + "m" +#endif +#ifdef RLIMIT_MEMLOCK + "l" +#endif +#ifdef RLIMIT_NPROC + "p" +#endif +#ifdef RLIMIT_NOFILE + "n" +#endif +#ifdef RLIMIT_AS + "v" +#endif +#ifdef RLIMIT_LOCKS + "w" +#endif + )) != '\0') + switch (optc) { + case 'H': + how = HARD; + break; + case 'S': + how = SOFT; + break; + case 'a': + all = 1; + break; + default: + what = optc; + } + + for (l = limits_tbl; l->option != what; l++) + continue; + + set = *argptr ? 1 : 0; + if (set) { + char *p = *argptr; + + if (all || argptr[1]) + ash_msg_and_raise_error("too many arguments"); + if (strncmp(p, "unlimited\n", 9) == 0) + val = RLIM_INFINITY; + else { + val = (rlim_t) 0; + + while ((c = *p++) >= '0' && c <= '9') { + val = (val * 10) + (long)(c - '0'); + // val is actually 'unsigned long int' and can't get < 0 + if (val < (rlim_t) 0) + break; + } + if (c) + ash_msg_and_raise_error("bad number"); + val <<= l->factor_shift; + } + } + if (all) { + const char *lname = limits_name; + for (l = limits_tbl; l != &limits_tbl[ARRAY_SIZE(limits_tbl)]; l++) { + getrlimit(l->cmd, &limit); + out1fmt("%-20s ", lname); + lname += strlen(lname) + 1; + printlim(how, &limit, l); + } + return 0; + } + + getrlimit(l->cmd, &limit); + if (set) { + if (how & HARD) + limit.rlim_max = val; + if (how & SOFT) + limit.rlim_cur = val; + if (setrlimit(l->cmd, &limit) < 0) + ash_msg_and_raise_error("error setting limit (%m)"); + } else { + printlim(how, &limit, l); + } + return 0; +} + + +/* ============ Math support */ + +#if ENABLE_ASH_MATH_SUPPORT + +/* Copyright (c) 2001 Aaron Lehmann + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +/* This is my infix parser/evaluator. It is optimized for size, intended + * as a replacement for yacc-based parsers. However, it may well be faster + * than a comparable parser written in yacc. The supported operators are + * listed in #defines below. Parens, order of operations, and error handling + * are supported. This code is thread safe. The exact expression format should + * be that which POSIX specifies for shells. */ + +/* The code uses a simple two-stack algorithm. See + * http://www.onthenet.com.au/~grahamis/int2008/week02/lect02.html + * for a detailed explanation of the infix-to-postfix algorithm on which + * this is based (this code differs in that it applies operators immediately + * to the stack instead of adding them to a queue to end up with an + * expression). */ + +/* To use the routine, call it with an expression string and error return + * pointer */ + +/* + * Aug 24, 2001 Manuel Novoa III + * + * Reduced the generated code size by about 30% (i386) and fixed several bugs. + * + * 1) In arith_apply(): + * a) Cached values of *numptr and &(numptr[-1]). + * b) Removed redundant test for zero denominator. + * + * 2) In arith(): + * a) Eliminated redundant code for processing operator tokens by moving + * to a table-based implementation. Also folded handling of parens + * into the table. + * b) Combined all 3 loops which called arith_apply to reduce generated + * code size at the cost of speed. + * + * 3) The following expressions were treated as valid by the original code: + * 1() , 0! , 1 ( *3 ) . + * These bugs have been fixed by internally enclosing the expression in + * parens and then checking that all binary ops and right parens are + * preceded by a valid expression (NUM_TOKEN). + * + * Note: It may be desirable to replace Aaron's test for whitespace with + * ctype's isspace() if it is used by another busybox applet or if additional + * whitespace chars should be considered. Look below the "#include"s for a + * precompiler test. + */ + +/* + * Aug 26, 2001 Manuel Novoa III + * + * Return 0 for null expressions. Pointed out by Vladimir Oleynik. + * + * Merge in Aaron's comments previously posted to the busybox list, + * modified slightly to take account of my changes to the code. + * + */ + +/* + * (C) 2003 Vladimir Oleynik + * + * - allow access to variable, + * used recursive find value indirection (c=2*2; a="c"; $((a+=2)) produce 6) + * - realize assign syntax (VAR=expr, +=, *= etc) + * - realize exponentiation (** operator) + * - realize comma separated - expr, expr + * - realise ++expr --expr expr++ expr-- + * - realise expr ? expr : expr (but, second expr calculate always) + * - allow hexadecimal and octal numbers + * - was restored loses XOR operator + * - remove one goto label, added three ;-) + * - protect $((num num)) as true zero expr (Manuel`s error) + * - always use special isspace(), see comment from bash ;-) + */ + +#define arith_isspace(arithval) \ + (arithval == ' ' || arithval == '\n' || arithval == '\t') + +typedef unsigned char operator; + +/* An operator's token id is a bit of a bitfield. The lower 5 bits are the + * precedence, and 3 high bits are an ID unique across operators of that + * precedence. The ID portion is so that multiple operators can have the + * same precedence, ensuring that the leftmost one is evaluated first. + * Consider * and /. */ + +#define tok_decl(prec,id) (((id)<<5)|(prec)) +#define PREC(op) ((op) & 0x1F) + +#define TOK_LPAREN tok_decl(0,0) + +#define TOK_COMMA tok_decl(1,0) + +#define TOK_ASSIGN tok_decl(2,0) +#define TOK_AND_ASSIGN tok_decl(2,1) +#define TOK_OR_ASSIGN tok_decl(2,2) +#define TOK_XOR_ASSIGN tok_decl(2,3) +#define TOK_PLUS_ASSIGN tok_decl(2,4) +#define TOK_MINUS_ASSIGN tok_decl(2,5) +#define TOK_LSHIFT_ASSIGN tok_decl(2,6) +#define TOK_RSHIFT_ASSIGN tok_decl(2,7) + +#define TOK_MUL_ASSIGN tok_decl(3,0) +#define TOK_DIV_ASSIGN tok_decl(3,1) +#define TOK_REM_ASSIGN tok_decl(3,2) + +/* all assign is right associativity and precedence eq, but (7+3)<<5 > 256 */ +#define convert_prec_is_assing(prec) do { if (prec == 3) prec = 2; } while (0) + +/* conditional is right associativity too */ +#define TOK_CONDITIONAL tok_decl(4,0) +#define TOK_CONDITIONAL_SEP tok_decl(4,1) + +#define TOK_OR tok_decl(5,0) + +#define TOK_AND tok_decl(6,0) + +#define TOK_BOR tok_decl(7,0) + +#define TOK_BXOR tok_decl(8,0) + +#define TOK_BAND tok_decl(9,0) + +#define TOK_EQ tok_decl(10,0) +#define TOK_NE tok_decl(10,1) + +#define TOK_LT tok_decl(11,0) +#define TOK_GT tok_decl(11,1) +#define TOK_GE tok_decl(11,2) +#define TOK_LE tok_decl(11,3) + +#define TOK_LSHIFT tok_decl(12,0) +#define TOK_RSHIFT tok_decl(12,1) + +#define TOK_ADD tok_decl(13,0) +#define TOK_SUB tok_decl(13,1) + +#define TOK_MUL tok_decl(14,0) +#define TOK_DIV tok_decl(14,1) +#define TOK_REM tok_decl(14,2) + +/* exponent is right associativity */ +#define TOK_EXPONENT tok_decl(15,1) + +/* For now unary operators. */ +#define UNARYPREC 16 +#define TOK_BNOT tok_decl(UNARYPREC,0) +#define TOK_NOT tok_decl(UNARYPREC,1) + +#define TOK_UMINUS tok_decl(UNARYPREC+1,0) +#define TOK_UPLUS tok_decl(UNARYPREC+1,1) + +#define PREC_PRE (UNARYPREC+2) + +#define TOK_PRE_INC tok_decl(PREC_PRE, 0) +#define TOK_PRE_DEC tok_decl(PREC_PRE, 1) + +#define PREC_POST (UNARYPREC+3) + +#define TOK_POST_INC tok_decl(PREC_POST, 0) +#define TOK_POST_DEC tok_decl(PREC_POST, 1) + +#define SPEC_PREC (UNARYPREC+4) + +#define TOK_NUM tok_decl(SPEC_PREC, 0) +#define TOK_RPAREN tok_decl(SPEC_PREC, 1) + +#define NUMPTR (*numstackptr) + +static int +tok_have_assign(operator op) +{ + operator prec = PREC(op); + + convert_prec_is_assing(prec); + return (prec == PREC(TOK_ASSIGN) || + prec == PREC_PRE || prec == PREC_POST); +} + +static int +is_right_associativity(operator prec) +{ + return (prec == PREC(TOK_ASSIGN) || prec == PREC(TOK_EXPONENT) + || prec == PREC(TOK_CONDITIONAL)); +} + +typedef struct { + arith_t val; + arith_t contidional_second_val; + char contidional_second_val_initialized; + char *var; /* if NULL then is regular number, + else is variable name */ +} v_n_t; + +typedef struct chk_var_recursive_looped_t { + const char *var; + struct chk_var_recursive_looped_t *next; +} chk_var_recursive_looped_t; + +static chk_var_recursive_looped_t *prev_chk_var_recursive; + +static int +arith_lookup_val(v_n_t *t) +{ + if (t->var) { + const char * p = lookupvar(t->var); + + if (p) { + int errcode; + + /* recursive try as expression */ + chk_var_recursive_looped_t *cur; + chk_var_recursive_looped_t cur_save; + + for (cur = prev_chk_var_recursive; cur; cur = cur->next) { + if (strcmp(cur->var, t->var) == 0) { + /* expression recursion loop detected */ + return -5; + } + } + /* save current lookuped var name */ + cur = prev_chk_var_recursive; + cur_save.var = t->var; + cur_save.next = cur; + prev_chk_var_recursive = &cur_save; + + t->val = arith (p, &errcode); + /* restore previous ptr after recursiving */ + prev_chk_var_recursive = cur; + return errcode; + } + /* allow undefined var as 0 */ + t->val = 0; + } + return 0; +} + +/* "applying" a token means performing it on the top elements on the integer + * stack. For a unary operator it will only change the top element, but a + * binary operator will pop two arguments and push a result */ +static int +arith_apply(operator op, v_n_t *numstack, v_n_t **numstackptr) +{ + v_n_t *numptr_m1; + arith_t numptr_val, rez; + int ret_arith_lookup_val; + + /* There is no operator that can work without arguments */ + if (NUMPTR == numstack) goto err; + numptr_m1 = NUMPTR - 1; + + /* check operand is var with noninteger value */ + ret_arith_lookup_val = arith_lookup_val(numptr_m1); + if (ret_arith_lookup_val) + return ret_arith_lookup_val; + + rez = numptr_m1->val; + if (op == TOK_UMINUS) + rez *= -1; + else if (op == TOK_NOT) + rez = !rez; + else if (op == TOK_BNOT) + rez = ~rez; + else if (op == TOK_POST_INC || op == TOK_PRE_INC) + rez++; + else if (op == TOK_POST_DEC || op == TOK_PRE_DEC) + rez--; + else if (op != TOK_UPLUS) { + /* Binary operators */ + + /* check and binary operators need two arguments */ + if (numptr_m1 == numstack) goto err; + + /* ... and they pop one */ + --NUMPTR; + numptr_val = rez; + if (op == TOK_CONDITIONAL) { + if (!numptr_m1->contidional_second_val_initialized) { + /* protect $((expr1 ? expr2)) without ": expr" */ + goto err; + } + rez = numptr_m1->contidional_second_val; + } else if (numptr_m1->contidional_second_val_initialized) { + /* protect $((expr1 : expr2)) without "expr ? " */ + goto err; + } + numptr_m1 = NUMPTR - 1; + if (op != TOK_ASSIGN) { + /* check operand is var with noninteger value for not '=' */ + ret_arith_lookup_val = arith_lookup_val(numptr_m1); + if (ret_arith_lookup_val) + return ret_arith_lookup_val; + } + if (op == TOK_CONDITIONAL) { + numptr_m1->contidional_second_val = rez; + } + rez = numptr_m1->val; + if (op == TOK_BOR || op == TOK_OR_ASSIGN) + rez |= numptr_val; + else if (op == TOK_OR) + rez = numptr_val || rez; + else if (op == TOK_BAND || op == TOK_AND_ASSIGN) + rez &= numptr_val; + else if (op == TOK_BXOR || op == TOK_XOR_ASSIGN) + rez ^= numptr_val; + else if (op == TOK_AND) + rez = rez && numptr_val; + else if (op == TOK_EQ) + rez = (rez == numptr_val); + else if (op == TOK_NE) + rez = (rez != numptr_val); + else if (op == TOK_GE) + rez = (rez >= numptr_val); + else if (op == TOK_RSHIFT || op == TOK_RSHIFT_ASSIGN) + rez >>= numptr_val; + else if (op == TOK_LSHIFT || op == TOK_LSHIFT_ASSIGN) + rez <<= numptr_val; + else if (op == TOK_GT) + rez = (rez > numptr_val); + else if (op == TOK_LT) + rez = (rez < numptr_val); + else if (op == TOK_LE) + rez = (rez <= numptr_val); + else if (op == TOK_MUL || op == TOK_MUL_ASSIGN) + rez *= numptr_val; + else if (op == TOK_ADD || op == TOK_PLUS_ASSIGN) + rez += numptr_val; + else if (op == TOK_SUB || op == TOK_MINUS_ASSIGN) + rez -= numptr_val; + else if (op == TOK_ASSIGN || op == TOK_COMMA) + rez = numptr_val; + else if (op == TOK_CONDITIONAL_SEP) { + if (numptr_m1 == numstack) { + /* protect $((expr : expr)) without "expr ? " */ + goto err; + } + numptr_m1->contidional_second_val_initialized = op; + numptr_m1->contidional_second_val = numptr_val; + } else if (op == TOK_CONDITIONAL) { + rez = rez ? + numptr_val : numptr_m1->contidional_second_val; + } else if (op == TOK_EXPONENT) { + if (numptr_val < 0) + return -3; /* exponent less than 0 */ + else { + arith_t c = 1; + + if (numptr_val) + while (numptr_val--) + c *= rez; + rez = c; + } + } else if (numptr_val==0) /* zero divisor check */ + return -2; + else if (op == TOK_DIV || op == TOK_DIV_ASSIGN) + rez /= numptr_val; + else if (op == TOK_REM || op == TOK_REM_ASSIGN) + rez %= numptr_val; + } + if (tok_have_assign(op)) { + char buf[sizeof(arith_t_type)*3 + 2]; + + if (numptr_m1->var == NULL) { + /* Hmm, 1=2 ? */ + goto err; + } + /* save to shell variable */ +#if ENABLE_ASH_MATH_SUPPORT_64 + snprintf(buf, sizeof(buf), "%lld", (arith_t_type) rez); +#else + snprintf(buf, sizeof(buf), "%ld", (arith_t_type) rez); +#endif + setvar(numptr_m1->var, buf, 0); + /* after saving, make previous value for v++ or v-- */ + if (op == TOK_POST_INC) + rez--; + else if (op == TOK_POST_DEC) + rez++; + } + numptr_m1->val = rez; + /* protect geting var value, is number now */ + numptr_m1->var = NULL; + return 0; + err: + return -1; +} + +/* longest must be first */ +static const char op_tokens[] ALIGN1 = { + '<','<','=',0, TOK_LSHIFT_ASSIGN, + '>','>','=',0, TOK_RSHIFT_ASSIGN, + '<','<', 0, TOK_LSHIFT, + '>','>', 0, TOK_RSHIFT, + '|','|', 0, TOK_OR, + '&','&', 0, TOK_AND, + '!','=', 0, TOK_NE, + '<','=', 0, TOK_LE, + '>','=', 0, TOK_GE, + '=','=', 0, TOK_EQ, + '|','=', 0, TOK_OR_ASSIGN, + '&','=', 0, TOK_AND_ASSIGN, + '*','=', 0, TOK_MUL_ASSIGN, + '/','=', 0, TOK_DIV_ASSIGN, + '%','=', 0, TOK_REM_ASSIGN, + '+','=', 0, TOK_PLUS_ASSIGN, + '-','=', 0, TOK_MINUS_ASSIGN, + '-','-', 0, TOK_POST_DEC, + '^','=', 0, TOK_XOR_ASSIGN, + '+','+', 0, TOK_POST_INC, + '*','*', 0, TOK_EXPONENT, + '!', 0, TOK_NOT, + '<', 0, TOK_LT, + '>', 0, TOK_GT, + '=', 0, TOK_ASSIGN, + '|', 0, TOK_BOR, + '&', 0, TOK_BAND, + '*', 0, TOK_MUL, + '/', 0, TOK_DIV, + '%', 0, TOK_REM, + '+', 0, TOK_ADD, + '-', 0, TOK_SUB, + '^', 0, TOK_BXOR, + /* uniq */ + '~', 0, TOK_BNOT, + ',', 0, TOK_COMMA, + '?', 0, TOK_CONDITIONAL, + ':', 0, TOK_CONDITIONAL_SEP, + ')', 0, TOK_RPAREN, + '(', 0, TOK_LPAREN, + 0 +}; +/* ptr to ")" */ +#define endexpression (&op_tokens[sizeof(op_tokens)-7]) + +static arith_t +arith(const char *expr, int *perrcode) +{ + char arithval; /* Current character under analysis */ + operator lasttok, op; + operator prec; + operator *stack, *stackptr; + const char *p = endexpression; + int errcode; + v_n_t *numstack, *numstackptr; + unsigned datasizes = strlen(expr) + 2; + + /* Stack of integers */ + /* The proof that there can be no more than strlen(startbuf)/2+1 integers + * in any given correct or incorrect expression is left as an exercise to + * the reader. */ + numstackptr = numstack = alloca((datasizes / 2) * sizeof(numstack[0])); + /* Stack of operator tokens */ + stackptr = stack = alloca(datasizes * sizeof(stack[0])); + + *stackptr++ = lasttok = TOK_LPAREN; /* start off with a left paren */ + *perrcode = errcode = 0; + + while (1) { + arithval = *expr; + if (arithval == 0) { + if (p == endexpression) { + /* Null expression. */ + return 0; + } + + /* This is only reached after all tokens have been extracted from the + * input stream. If there are still tokens on the operator stack, they + * are to be applied in order. At the end, there should be a final + * result on the integer stack */ + + if (expr != endexpression + 1) { + /* If we haven't done so already, */ + /* append a closing right paren */ + expr = endexpression; + /* and let the loop process it. */ + continue; + } + /* At this point, we're done with the expression. */ + if (numstackptr != numstack+1) { + /* ... but if there isn't, it's bad */ + err: + *perrcode = -1; + return *perrcode; + } + if (numstack->var) { + /* expression is $((var)) only, lookup now */ + errcode = arith_lookup_val(numstack); + } + ret: + *perrcode = errcode; + return numstack->val; + } + + /* Continue processing the expression. */ + if (arith_isspace(arithval)) { + /* Skip whitespace */ + goto prologue; + } + p = endofname(expr); + if (p != expr) { + size_t var_name_size = (p-expr) + 1; /* trailing zero */ + + numstackptr->var = alloca(var_name_size); + safe_strncpy(numstackptr->var, expr, var_name_size); + expr = p; + num: + numstackptr->contidional_second_val_initialized = 0; + numstackptr++; + lasttok = TOK_NUM; + continue; + } + if (isdigit(arithval)) { + numstackptr->var = NULL; +#if ENABLE_ASH_MATH_SUPPORT_64 + numstackptr->val = strtoll(expr, (char **) &expr, 0); +#else + numstackptr->val = strtol(expr, (char **) &expr, 0); +#endif + goto num; + } + for (p = op_tokens; ; p++) { + const char *o; + + if (*p == 0) { + /* strange operator not found */ + goto err; + } + for (o = expr; *p && *o == *p; p++) + o++; + if (!*p) { + /* found */ + expr = o - 1; + break; + } + /* skip tail uncompared token */ + while (*p) + p++; + /* skip zero delim */ + p++; + } + op = p[1]; + + /* post grammar: a++ reduce to num */ + if (lasttok == TOK_POST_INC || lasttok == TOK_POST_DEC) + lasttok = TOK_NUM; + + /* Plus and minus are binary (not unary) _only_ if the last + * token was as number, or a right paren (which pretends to be + * a number, since it evaluates to one). Think about it. + * It makes sense. */ + if (lasttok != TOK_NUM) { + switch (op) { + case TOK_ADD: + op = TOK_UPLUS; + break; + case TOK_SUB: + op = TOK_UMINUS; + break; + case TOK_POST_INC: + op = TOK_PRE_INC; + break; + case TOK_POST_DEC: + op = TOK_PRE_DEC; + break; + } + } + /* We don't want a unary operator to cause recursive descent on the + * stack, because there can be many in a row and it could cause an + * operator to be evaluated before its argument is pushed onto the + * integer stack. */ + /* But for binary operators, "apply" everything on the operator + * stack until we find an operator with a lesser priority than the + * one we have just extracted. */ + /* Left paren is given the lowest priority so it will never be + * "applied" in this way. + * if associativity is right and priority eq, applied also skip + */ + prec = PREC(op); + if ((prec > 0 && prec < UNARYPREC) || prec == SPEC_PREC) { + /* not left paren or unary */ + if (lasttok != TOK_NUM) { + /* binary op must be preceded by a num */ + goto err; + } + while (stackptr != stack) { + if (op == TOK_RPAREN) { + /* The algorithm employed here is simple: while we don't + * hit an open paren nor the bottom of the stack, pop + * tokens and apply them */ + if (stackptr[-1] == TOK_LPAREN) { + --stackptr; + /* Any operator directly after a */ + lasttok = TOK_NUM; + /* close paren should consider itself binary */ + goto prologue; + } + } else { + operator prev_prec = PREC(stackptr[-1]); + + convert_prec_is_assing(prec); + convert_prec_is_assing(prev_prec); + if (prev_prec < prec) + break; + /* check right assoc */ + if (prev_prec == prec && is_right_associativity(prec)) + break; + } + errcode = arith_apply(*--stackptr, numstack, &numstackptr); + if (errcode) goto ret; + } + if (op == TOK_RPAREN) { + goto err; + } + } + + /* Push this operator to the stack and remember it. */ + *stackptr++ = lasttok = op; + prologue: + ++expr; + } /* while */ +} +#endif /* ASH_MATH_SUPPORT */ + + +/* ============ main() and helpers */ + +/* + * Called to exit the shell. + */ +static void exitshell(void) NORETURN; +static void +exitshell(void) +{ + struct jmploc loc; + char *p; + int status; + + status = exitstatus; + TRACE(("pid %d, exitshell(%d)\n", getpid(), status)); + if (setjmp(loc.loc)) { + if (exception == EXEXIT) +/* dash bug: it just does _exit(exitstatus) here + * but we have to do setjobctl(0) first! + * (bug is still not fixed in dash-0.5.3 - if you run dash + * under Midnight Commander, on exit from dash MC is backgrounded) */ + status = exitstatus; + goto out; + } + exception_handler = &loc; + p = trap[0]; + if (p) { + trap[0] = NULL; + evalstring(p, 0); + } + flush_stdout_stderr(); + out: + setjobctl(0); + _exit(status); + /* NOTREACHED */ +} + +static void +init(void) +{ + /* from input.c: */ + basepf.nextc = basepf.buf = basebuf; + + /* from trap.c: */ + signal(SIGCHLD, SIG_DFL); + + /* from var.c: */ + { + char **envp; + char ppid[sizeof(int)*3 + 1]; + const char *p; + struct stat st1, st2; + + initvar(); + for (envp = environ; envp && *envp; envp++) { + if (strchr(*envp, '=')) { + setvareq(*envp, VEXPORT|VTEXTFIXED); + } + } + + snprintf(ppid, sizeof(ppid), "%u", (unsigned) getppid()); + setvar("PPID", ppid, 0); + + p = lookupvar("PWD"); + if (p) + if (*p != '/' || stat(p, &st1) || stat(".", &st2) + || st1.st_dev != st2.st_dev || st1.st_ino != st2.st_ino) + p = '\0'; + setpwd(p, 0); + } +} + +/* + * Process the shell command line arguments. + */ +static void +procargs(char **argv) +{ + int i; + const char *xminusc; + char **xargv; + + xargv = argv; + arg0 = xargv[0]; + /* if (xargv[0]) - mmm, this is always true! */ + xargv++; + for (i = 0; i < NOPTS; i++) + optlist[i] = 2; + argptr = xargv; + if (options(1)) { + /* it already printed err message */ + raise_exception(EXERROR); + } + xargv = argptr; + xminusc = minusc; + if (*xargv == NULL) { + if (xminusc) + ash_msg_and_raise_error(bb_msg_requires_arg, "-c"); + sflag = 1; + } + if (iflag == 2 && sflag == 1 && isatty(0) && isatty(1)) + iflag = 1; + if (mflag == 2) + mflag = iflag; + for (i = 0; i < NOPTS; i++) + if (optlist[i] == 2) + optlist[i] = 0; +#if DEBUG == 2 + debug = 1; +#endif + /* POSIX 1003.2: first arg after -c cmd is $0, remainder $1... */ + if (xminusc) { + minusc = *xargv++; + if (*xargv) + goto setarg0; + } else if (!sflag) { + setinputfile(*xargv, 0); + setarg0: + arg0 = *xargv++; + commandname = arg0; + } + + shellparam.p = xargv; +#if ENABLE_ASH_GETOPTS + shellparam.optind = 1; + shellparam.optoff = -1; +#endif + /* assert(shellparam.malloced == 0 && shellparam.nparam == 0); */ + while (*xargv) { + shellparam.nparam++; + xargv++; + } + optschanged(); +} + +/* + * Read /etc/profile or .profile. + */ +static void +read_profile(const char *name) +{ + int skip; + + if (setinputfile(name, INPUT_PUSH_FILE | INPUT_NOFILE_OK) < 0) + return; + skip = cmdloop(0); + popfile(); + if (skip) + exitshell(); +} + +/* + * This routine is called when an error or an interrupt occurs in an + * interactive shell and control is returned to the main command loop. + */ +static void +reset(void) +{ + /* from eval.c: */ + evalskip = 0; + loopnest = 0; + /* from input.c: */ + parselleft = parsenleft = 0; /* clear input buffer */ + popallfiles(); + /* from parser.c: */ + tokpushback = 0; + checkkwd = 0; + /* from redir.c: */ + clearredir(/*drop:*/ 0); +} + +#if PROFILE +static short profile_buf[16384]; +extern int etext(); +#endif + +/* + * Main routine. We initialize things, parse the arguments, execute + * profiles if we're a login shell, and then call cmdloop to execute + * commands. The setjmp call sets up the location to jump to when an + * exception occurs. When an exception occurs the variable "state" + * is used to figure out how far we had gotten. + */ +int ash_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int ash_main(int argc UNUSED_PARAM, char **argv) +{ + char *shinit; + volatile int state; + struct jmploc jmploc; + struct stackmark smark; + + /* Initialize global data */ + INIT_G_misc(); + INIT_G_memstack(); + INIT_G_var(); +#if ENABLE_ASH_ALIAS + INIT_G_alias(); +#endif + INIT_G_cmdtable(); + +#if PROFILE + monitor(4, etext, profile_buf, sizeof(profile_buf), 50); +#endif + +#if ENABLE_FEATURE_EDITING + line_input_state = new_line_input_t(FOR_SHELL | WITH_PATH_LOOKUP); +#endif + state = 0; + if (setjmp(jmploc.loc)) { + int e; + int s; + + reset(); + + e = exception; + if (e == EXERROR) + exitstatus = 2; + s = state; + if (e == EXEXIT || s == 0 || iflag == 0 || shlvl) + exitshell(); + + if (e == EXINT) { + outcslow('\n', stderr); + } + popstackmark(&smark); + FORCE_INT_ON; /* enable interrupts */ + if (s == 1) + goto state1; + if (s == 2) + goto state2; + if (s == 3) + goto state3; + goto state4; + } + exception_handler = &jmploc; +#if DEBUG + opentrace(); + trace_puts("Shell args: "); + trace_puts_args(argv); +#endif + rootpid = getpid(); + +#if ENABLE_ASH_RANDOM_SUPPORT + /* Can use monotonic_ns() for better randomness but for now it is + * not used anywhere else in busybox... so avoid bloat */ + random_galois_LFSR = random_LCG = rootpid + monotonic_us(); +#endif + init(); + setstackmark(&smark); + procargs(argv); + +#if ENABLE_FEATURE_EDITING_SAVEHISTORY + if (iflag) { + const char *hp = lookupvar("HISTFILE"); + + if (hp == NULL) { + hp = lookupvar("HOME"); + if (hp != NULL) { + char *defhp = concat_path_file(hp, ".ash_history"); + setvar("HISTFILE", defhp, 0); + free(defhp); + } + } + } +#endif + if (argv[0] && argv[0][0] == '-') + isloginsh = 1; + if (isloginsh) { + state = 1; + read_profile("/etc/profile"); + state1: + state = 2; + read_profile(".profile"); + } + state2: + state = 3; + if ( +#ifndef linux + getuid() == geteuid() && getgid() == getegid() && +#endif + iflag + ) { + shinit = lookupvar("ENV"); + if (shinit != NULL && *shinit != '\0') { + read_profile(shinit); + } + } + state3: + state = 4; + if (minusc) + evalstring(minusc, 0); + + if (sflag || minusc == NULL) { +#if ENABLE_FEATURE_EDITING_SAVEHISTORY + if (iflag) { + const char *hp = lookupvar("HISTFILE"); + + if (hp != NULL) + line_input_state->hist_file = hp; + } +#endif + state4: /* XXX ??? - why isn't this before the "if" statement */ + cmdloop(1); + } +#if PROFILE + monitor(0); +#endif +#ifdef GPROF + { + extern void _mcleanup(void); + _mcleanup(); + } +#endif + exitshell(); + /* NOTREACHED */ +} + +#if DEBUG +const char *applet_name = "debug stuff usage"; +int main(int argc, char **argv) +{ + return ash_main(argc, argv); +} +#endif + + +/*- + * Copyright (c) 1989, 1991, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ diff --git a/shell/ash_doc.txt b/shell/ash_doc.txt new file mode 100644 index 0000000..28c5748 --- /dev/null +++ b/shell/ash_doc.txt @@ -0,0 +1,31 @@ + Wait + signals + +We had some bugs here which are hard to test in testsuite. + +Bug 1280 (http://busybox.net/bugs/view.php?id=1280): +was misbehaving in interactive ash. Correct behavior: + +$ sleep 20 & +$ wait +^C +$ wait +^C +$ wait +^C +... + +Bug 1984 (http://busybox.net/bugs/view.php?id=1984): +traps were not triggering: + +trap_handler_usr () { + echo trap usr +} +trap_handler_int () { + echo trap int +} +trap trap_handler_usr USR1 +trap trap_handler_int INT +sleep 3600 & +echo "Please do: kill -USR1 $$" +echo "or: kill -INT $$" +while true; do wait; echo wait interrupted; done diff --git a/shell/ash_ptr_hack.c b/shell/ash_ptr_hack.c new file mode 100644 index 0000000..68d9072 --- /dev/null +++ b/shell/ash_ptr_hack.c @@ -0,0 +1,29 @@ +/* vi: set sw=4 ts=4: */ +/* + * Copyright (C) 2008 by Denys Vlasenko + * + * Licensed under GPLv2, see file LICENSE in this tarball for details. + */ + +struct globals_misc; +struct globals_memstack; +struct globals_var; + +#ifndef GCC_COMBINE + +/* We cheat here. They are declared as const ptr in ash.c, + * but here we make them live in R/W memory */ +struct globals_misc *ash_ptr_to_globals_misc; +struct globals_memstack *ash_ptr_to_globals_memstack; +struct globals_var *ash_ptr_to_globals_var; + +#else + +/* gcc -combine will see through and complain */ +/* Using alternative method which is more likely to break + * on weird architectures, compilers, linkers and so on */ +struct globals_misc *const ash_ptr_to_globals_misc __attribute__ ((section (".data"))); +struct globals_memstack *const ash_ptr_to_globals_memstack __attribute__ ((section (".data"))); +struct globals_var *const ash_ptr_to_globals_var __attribute__ ((section (".data"))); + +#endif diff --git a/shell/ash_test/ash-alias/alias.right b/shell/ash_test/ash-alias/alias.right new file mode 100644 index 0000000..0667b21 --- /dev/null +++ b/shell/ash_test/ash-alias/alias.right @@ -0,0 +1,4 @@ +alias: 0 +alias: 0 +./alias.tests: line 25: qfoo: not found +quux diff --git a/shell/ash_test/ash-alias/alias.tests b/shell/ash_test/ash-alias/alias.tests new file mode 100755 index 0000000..8d07b0b --- /dev/null +++ b/shell/ash_test/ash-alias/alias.tests @@ -0,0 +1,37 @@ +# place holder for future alias testing +#ash# shopt -s expand_aliases + +# alias/unalias tests originally in builtins.tests + +unalias -a +# this should return success, according to POSIX.2 +alias +echo alias: $? +alias foo=bar +unalias foo +# this had better return success, according to POSIX.2 +alias +echo alias: $? + +# bug in all versions through bash-2.05b + +unalias qfoo qbar qbaz quux 2>/dev/null + +alias qfoo=qbar +alias qbar=qbaz +alias qbaz=quux +alias quux=qfoo + +qfoo + +unalias qfoo qbar qbaz quux + +unalias -a + +alias foo='echo ' +alias bar=baz +alias baz=quux + +foo bar + +unalias foo bar baz diff --git a/shell/ash_test/ash-arith/README.ash b/shell/ash_test/ash-arith/README.ash new file mode 100644 index 0000000..7da22ef --- /dev/null +++ b/shell/ash_test/ash-arith/README.ash @@ -0,0 +1 @@ +there is no support for (( )) constructs in ash diff --git a/shell/ash_test/ash-arith/arith-bash1.right b/shell/ash_test/ash-arith/arith-bash1.right new file mode 100644 index 0000000..b261da1 --- /dev/null +++ b/shell/ash_test/ash-arith/arith-bash1.right @@ -0,0 +1,2 @@ +1 +0 diff --git a/shell/ash_test/ash-arith/arith-bash1.tests b/shell/ash_test/ash-arith/arith-bash1.tests new file mode 100755 index 0000000..b37b730 --- /dev/null +++ b/shell/ash_test/ash-arith/arith-bash1.tests @@ -0,0 +1,5 @@ +# checks for [[ ]] + +# && and || +[[ a && "" ]]; echo $? +[[ a || "" ]]; echo $? diff --git a/shell/ash_test/ash-arith/arith-for.right b/shell/ash_test/ash-arith/arith-for.right new file mode 100644 index 0000000..88dbc15 --- /dev/null +++ b/shell/ash_test/ash-arith/arith-for.right @@ -0,0 +1,74 @@ +0 +1 +2 +0 +1 +2 +0 +1 +2 +0 +2 +4 +fx is a function +fx () +{ + i=0; + for ((1; i < 3; i++ )) + do + echo $i; + done; + for ((i=0; 1; i++ )) + do + if (( i >= 3 )); then + break; + fi; + echo $i; + done; + for ((i=0; i<3; 1)) + do + echo $i; + (( i++ )); + done; + i=0; + for ((1; 1; 1)) + do + if (( i > 2 )); then + break; + fi; + echo $i; + (( i++ )); + done; + i=0; + for ((1; 1; 1)) + do + if (( i > 2 )); then + break; + fi; + echo $i; + (( i++ )); + done +} +0 +1 +2 +0 +1 +2 +0 +1 +2 +0 +1 +2 +0 +1 +2 +./arith-for.tests: line 77: syntax error: arithmetic expression required +./arith-for.tests: line 77: syntax error: `(( i=0; "i < 3" ))' +2 +./arith-for.tests: line 83: syntax error: `;' unexpected +./arith-for.tests: line 83: syntax error: `(( i=0; i < 3; i++; 7 ))' +2 +20 +20 diff --git a/shell/ash_test/ash-arith/arith-for.testsx b/shell/ash_test/ash-arith/arith-for.testsx new file mode 100755 index 0000000..4fa30ff --- /dev/null +++ b/shell/ash_test/ash-arith/arith-for.testsx @@ -0,0 +1,94 @@ +fx() +{ +i=0 +for (( ; i < 3; i++ )) +do + echo $i +done + +for (( i=0; ; i++ )) +do + if (( i >= 3 )); then + break; + fi + echo $i +done + +for (( i=0; i<3; )) +do + echo $i + (( i++ )) +done + +i=0 +for (( ; ; )) +do + if (( i > 2 )); then + break; + fi + echo $i; + (( i++ )) +done + +i=0 +for ((;;)) +do + if (( i > 2 )); then + break; + fi + echo $i; + (( i++ )) +done +} + +for (( i=0; "i < 3" ; i++ )) +do + echo $i +done + +i=0 +for (( ; "i < 3"; i++ )) +do + echo $i +done + +for (( i=0; ; i++ )) +do + if (( i >= 3 )); then + break; + fi + echo $i +done + +for ((i = 0; ;i++ )) +do + echo $i + if (( i < 3 )); then + (( i++ )) + continue; + fi + break +done + +type fx +fx + +# errors +for (( i=0; "i < 3" )) +do + echo $i +done +echo $? + +for (( i=0; i < 3; i++; 7 )) +do + echo $i +done +echo $? + +# one-liners added in post-bash-2.04 +for ((i=0; i < 20; i++)) do : ; done +echo $i + +for ((i=0; i < 20; i++)) { : ; } +echo $i diff --git a/shell/ash_test/ash-arith/arith.right b/shell/ash_test/ash-arith/arith.right new file mode 100644 index 0000000..3ea7ce6 --- /dev/null +++ b/shell/ash_test/ash-arith/arith.right @@ -0,0 +1,138 @@ +Format: 'expected actual' +163 163 +4 4 +16 16 +8 8 +2 2 +4 4 +2 2 +2 2 +1 1 +0 0 +0 0 +0 0 +1 1 +1 1 +2 2 +-3 -3 +-2 -2 +1 1 +0 0 +2 2 +131072 131072 +29 29 +33 33 +49 49 +1 1 +1 1 +0 0 +0 0 +1 1 +1 1 +1 1 +2 2 +3 3 +1 1 +58 58 +2 2 +60 60 +1 1 +256 256 +16 16 +62 62 +4 4 +29 29 +5 5 +-4 -4 +4 4 +1 1 +32 32 +32 32 +1 1 +1 1 +32 32 +20 20 +30 30 +20 20 +30 30 +./arith.tests: line 117: syntax error: 1 ? 20 : x+=2 +6 6 +6,5,3 6,5,3 +263 263 +255 255 +40 40 +./arith.tests: line 163: syntax error: 7 = 43 +./arith.tests: line 165: divide by zero +./arith.tests: let: line 166: syntax error: jv += $iv +./arith.tests: line 167: syntax error: jv += $iv +./arith.tests: let: line 168: syntax error: rv = 7 + (43 * 6 +abc +def +ghi +./arith.tests: line 191: syntax error: ( 4 + A ) + 4 +16 16 +./arith.tests: line 196: syntax error: 4 ? : 3 + 5 +./arith.tests: line 197: syntax error: 1 ? 20 +./arith.tests: line 198: syntax error: 4 ? 20 : +9 9 +./arith.tests: line 205: syntax error: 0 && B=42 +./arith.tests: line 208: syntax error: 1 || B=88 +9 9 +9 9 +9 9 +7 7 +7 +4 4 +32767 32767 +32768 32768 +131072 131072 +2147483647 2147483647 +1 1 +4 4 +4 4 +5 5 +5 5 +4 4 +3 3 +3 3 +4 4 +4 4 +./arith.tests: line 257: syntax error: 7-- +./arith.tests: line 259: syntax error: --x=7 +./arith.tests: line 260: syntax error: ++x=7 +./arith.tests: line 262: syntax error: x++=7 +./arith.tests: line 263: syntax error: x--=7 +4 4 +7 7 +-7 -7 +./arith1.sub: line 2: syntax error: 4-- +./arith1.sub: line 3: syntax error: 4++ +./arith1.sub: line 4: syntax error: 4 -- +./arith1.sub: line 5: syntax error: 4 ++ +6 6 +3 3 +7 7 +4 4 +0 0 +3 3 +7 7 +2 2 +-2 -2 +1 1 +./arith1.sub: line 37: syntax error: +++7 +./arith2.sub: line 2: syntax error: --7 +./arith2.sub: line 3: syntax error: ++7 +./arith2.sub: line 4: syntax error: -- 7 +./arith2.sub: line 5: syntax error: ++ 7 +5 5 +1 1 +4 4 +0 0 +./arith2.sub: line 42: syntax error: -- - 7 +./arith2.sub: line 47: syntax error: ++ + 7 +8 12 +./arith.tests: line 290: syntax error: a b +42 +42 +42 +./arith.tests: line 302: a[b[c]d]=e: not found diff --git a/shell/ash_test/ash-arith/arith.tests b/shell/ash_test/ash-arith/arith.tests new file mode 100755 index 0000000..d65758e --- /dev/null +++ b/shell/ash_test/ash-arith/arith.tests @@ -0,0 +1,302 @@ +#ash# set +o posix +#ash# declare -i iv jv + +echo "Format: 'expected actual'" + +iv=$(( 3 + 5 * 32 )) +echo 163 $iv +#ash# iv=iv+3 +#ash# echo 166 $iv +iv=2 +jv=iv + +let "jv *= 2" +echo 4 $jv +jv=$(( $jv << 2 )) +echo 16 $jv + +let jv="$jv / 2" +echo 8 $jv +#ash# jv="jv >> 2" + let jv="jv >> 2" +echo 2 $jv + +iv=$((iv+ $jv)) +echo 4 $iv +echo 2 $((iv -= jv)) +echo 2 $iv +echo 1 $(( iv == jv )) +echo 0 $(( iv != $jv )) +echo 0 $(( iv < jv )) +echo 0 $(( $iv > $jv )) +echo 1 $(( iv <= $jv )) +echo 1 $(( $iv >= jv )) + +echo 2 $jv +echo -3 $(( ~$jv )) +echo -2 $(( ~1 )) +echo 1 $(( ! 0 )) + +echo 0 $(( jv % 2 )) +echo 2 $(( $iv % 4 )) + +echo 131072 $(( iv <<= 16 )) +echo 29 $(( iv %= 33 )) + +echo 33 $(( 33 & 55 )) +echo 49 $(( 33 | 17 )) + +echo 1 $(( iv && $jv )) +echo 1 $(( $iv || jv )) + +echo 0 $(( iv && 0 )) +echo 0 $(( iv & 0 )) +echo 1 $(( iv && 1 )) +echo 1 $(( iv & 1 )) + +echo 1 $(( $jv || 0 )) +echo 2 $(( jv | 0 )) +echo 3 $(( jv | 1 )) +echo 1 $(( $jv || 1 )) + +let 'iv *= jv' +echo 58 $iv +echo 2 $jv +let "jv += $iv" +echo 60 $jv + +echo 1 $(( jv /= iv )) +echo 256 $(( jv <<= 8 )) +echo 16 $(( jv >>= 4 )) + +echo 62 $(( iv |= 4 )) +echo 4 $(( iv &= 4 )) + +echo 29 $(( iv += (jv + 9))) +echo 5 $(( (iv + 4) % 7 )) + +# unary plus, minus +echo -4 $(( +4 - 8 )) +echo 4 $(( -4 + 8 )) + +# conditional expressions +echo 1 $(( 4<5 ? 1 : 32)) +echo 32 $(( 4>5 ? 1 : 32)) +echo 32 $(( 4>(2+3) ? 1 : 32)) +echo 1 $(( 4<(2+3) ? 1 : 32)) +echo 1 $(( (2+2)<(2+3) ? 1 : 32)) +echo 32 $(( (2+2)>(2+3) ? 1 : 32)) + +# check that the unevaluated part of the ternary operator does not do +# evaluation or assignment +x=i+=2 +y=j+=2 +#ash# declare -i i=1 j=1 + i=1 + j=1 +echo 20 $((1 ? 20 : (x+=2))) +#ash# echo $i,$x # ash mishandles this +echo 30 $((0 ? (y+=2) : 30)) +#ash# echo $j,$y # ash mishandles this + +x=i+=2 +y=j+=2 +#ash# declare -i i=1 j=1 + i=1 + j=1 +echo 20 $((1 ? 20 : (x+=2))) +#ash# echo $i,$x # ash mishandles this +echo 30 $((0 ? (y+=2) : 30)) +#ash# echo $i,$y # ash mishandles this + +# check precedence of assignment vs. conditional operator +# should be an error +#ash# declare -i x=2 + x=2 +#ashnote# bash reports error but continues, ash aborts - using subshell to 'emulate' bash: +( y=$((1 ? 20 : x+=2)) ) + +# check precedence of assignment vs. conditional operator +#ash# declare -i x=2 + x=2 +# ash says "line NNN: syntax error: 0 ? x+=2 : 20" +#ash# echo 20 $((0 ? x+=2 : 20)) + +# associativity of assignment-operator operator +#ash# declare -i i=1 j=2 k=3 +i=1 +j=2 +k=3 +echo 6 $((i += j += k)) +echo 6,5,3 $i,$j,$k + +# octal, hex +echo 263 $(( 0x100 | 007 )) +echo 255 $(( 0xff )) +#ash# echo 255 $(( 16#ff )) +#ash# echo 127 $(( 16#FF/2 )) +#ash# echo 36 $(( 8#44 )) + +echo 40 $(( 8 ^ 32 )) + +#ash# # other bases +#ash# echo 10 $(( 16#a )) +#ash# echo 10 $(( 32#a )) +#ash# echo 10 $(( 56#a )) +#ash# echo 10 $(( 64#a )) +#ash# +#ash# echo 10 $(( 16#A )) +#ash# echo 10 $(( 32#A )) +#ash# echo 36 $(( 56#A )) +#ash# echo 36 $(( 64#A )) +#ash# +#ash# echo 62 $(( 64#@ )) +#ash# echo 63 $(( 64#_ )) + +#ash# # weird bases (error) +#ash# echo $(( 3425#56 )) + +#ash# # missing number after base +#ash# echo 0 $(( 2# )) + +# these should generate errors +( echo $(( 7 = 43 )) ) +#ash# echo $(( 2#44 )) +( echo $(( 44 / 0 )) ) +( let 'jv += $iv' ) +( echo $(( jv += \$iv )) ) +( let 'rv = 7 + (43 * 6' ) + +#ash# # more errors +#ash# declare -i i +#ash# i=0#4 +#ash# i=2#110#11 + +((echo abc; echo def;); echo ghi) + +#ash# if (((4+4) + (4 + 7))); then +#ash# echo ok +#ash# fi + +#ash# (()) # make sure the null expression works OK + +#ash# a=(0 2 4 6) +#ash# echo 6 $(( a[1] + a[2] )) +#ash# echo 1 $(( (a[1] + a[2]) == a[3] )) +#ash# (( (a[1] + a[2]) == a[3] )) ; echo 0 $? + +# test pushing and popping the expression stack +unset A +A="4 + " +( echo A $(( ( 4 + A ) + 4 )) ) +A="3 + 5" +echo 16 $(( ( 4 + A ) + 4 )) + +# badly-formed conditional expressions +( echo $(( 4 ? : $A )) ) +( echo $(( 1 ? 20 )) ) +( echo $(( 4 ? 20 : )) ) + +# precedence and short-circuit evaluation +B=9 +echo 9 $B + +# error +( echo $(( 0 && B=42 )); echo 9 $B ) + +# error +( echo $(( 1 || B=88 )); echo 9 $B ) + +# ash mistakenly evaluates B=... below +#ash# echo 0 $(( 0 && (B=42) )) +echo 9 $B +#ash# echo 0 $(( (${$} - $$) && (B=42) )) +echo 9 $B +#ash# echo 1 $(( 1 || (B=88) )) +echo 9 $B + + +# until command with (( )) command +x=7 + +echo 7 $x +#ash# until (( x == 4 )) + until test "$x" = 4 +do + echo $x + x=4 +done + +echo 4 $x + +# exponentiation +echo 32767 $(( 2**15 - 1)) +echo 32768 $(( 2**(16-1))) +echo 131072 $(( 2**16*2 )) +echo 2147483647 $(( 2**31-1)) +echo 1 $(( 2**0 )) + +# {pre,post}-{inc,dec}rement and associated errors + +x=4 + +echo 4 $x +echo 4 $(( x++ )) +echo 5 $x +echo 5 $(( x-- )) +echo 4 $x + +echo 3 $(( --x )) +echo 3 $x + +echo 4 $(( ++x )) +echo 4 $x + +# bash 3.2 apparently thinks that ++7 is 7 +#ash# echo 7 $(( ++7 )) +( echo $(( 7-- )) ) + +( echo $(( --x=7 )) ) +( echo $(( ++x=7 )) ) + +( echo $(( x++=7 )) ) +( echo $(( x--=7 )) ) + +echo 4 $x + +echo 7 $(( +7 )) +echo -7 $(( -7 )) + +# bash 3.2 apparently thinks that ++7 is 7 +#ash# echo $(( ++7 )) +#ash# echo $(( --7 )) + +${THIS_SH} ./arith1.sub +${THIS_SH} ./arith2.sub + +x=4 +y=7 + +#ash# (( x=8 , y=12 )) + x=8 + y=12 +echo $x $y + +#ash# # should be an error +#ash# (( x=9 y=41 )) + +# These are errors +unset b +( echo $((a b)) ) +#ash# ((a b)) + +n=42 +printf "%d\n" $n +printf "%i\n" $n +#ash# echo $(( 8#$(printf "%o\n" $n) )) +printf "%u\n" $n +#ash# echo $(( 16#$(printf "%x\n" $n) )) +#ash# echo $(( 16#$(printf "%X\n" $n) )) + +# causes longjmp botches through bash-2.05b +a[b[c]d]=e diff --git a/shell/ash_test/ash-arith/arith1.sub b/shell/ash_test/ash-arith/arith1.sub new file mode 100755 index 0000000..80aa999 --- /dev/null +++ b/shell/ash_test/ash-arith/arith1.sub @@ -0,0 +1,40 @@ +# test of redone post-increment and post-decrement code +( echo $(( 4-- )) ) +( echo $(( 4++ )) ) +( echo $(( 4 -- )) ) +( echo $(( 4 ++ )) ) + +#ash# (( array[0]++ )) +#ash# echo ${array} + +#ash# (( array[0] ++ )) +#ash# echo ${array} + +#ash# (( a++ )) +#ash# echo $a +#ash# (( a ++ )) +#ash# echo $a + a=2 + +echo 6 $(( a ++ + 4 )) +echo 3 $a + +echo 7 $(( a+++4 )) +echo 4 $a + +echo 0 $(( a---4 )) +echo 3 $a + +echo 7 $(( a -- + 4 )) +echo 2 $a + +echo -2 $(( a -- - 4 )) +echo 1 $a + +#ash# (( ++ + 7 )) + +#ash# (( ++ )) +( echo $(( +++7 )) ) +# bash 3.2 apparently thinks that ++ +7 is 7 +#ash# echo $(( ++ + 7 )) +#ash# (( -- )) diff --git a/shell/ash_test/ash-arith/arith2.sub b/shell/ash_test/ash-arith/arith2.sub new file mode 100755 index 0000000..f7e3c92 --- /dev/null +++ b/shell/ash_test/ash-arith/arith2.sub @@ -0,0 +1,57 @@ +# bash 3.2 apparently thinks that ++7 is 7 etc +( echo $(( --7 )) ) +( echo $(( ++7 )) ) +( echo $(( -- 7 )) ) +( echo $(( ++ 7 )) ) + +#ash# ((++array[0] )) +#ash# echo 1 $array +#ash# (( ++ array[0] )) +#ash# echo 2 $array + +#ash# (( ++a )) +#ash# echo 1 $a +#ash# (( ++ a )) +#ash# echo 2 $a + +#ash# (( --a )) +#ash# echo 1 $a +#ash# (( -- a )) +#ash# echo 0 $a + a=0 + +echo 5 $(( 4 + ++a )) +echo 1 $a + +# ash doesn't handle it right... +#ash# echo 6 $(( 4+++a )) +#ash# echo 2 $a + a=2 + +# ash doesn't handle it right... +#ash# echo 3 $(( 4---a )) +#ash# echo 1 $a + a=1 + +echo 4 $(( 4 - -- a )) +echo 0 $a + +#ash# (( -- )) +# bash 3.2 apparently thinks that ---7 is -7 +#ash# echo $(( ---7 )) +( echo $(( -- - 7 )) ) + +#ash# (( ++ )) +# bash 3.2: 7 +#ash# echo 7 $(( ++7 )) +( echo $(( ++ + 7 )) ) + +# bash 3.2: -7 +#ash# echo -7 $(( ++-7 )) +# bash 3.2: -7 +#ash# echo -7 $(( ++ - 7 )) + +# bash 3.2: 7 +#ash# echo 7 $(( +--7 )) +# bash 3.2: 7 +#ash# echo 7 $(( -- + 7 )) diff --git a/shell/ash_test/ash-heredoc/heredoc.right b/shell/ash_test/ash-heredoc/heredoc.right new file mode 100644 index 0000000..baf1151 --- /dev/null +++ b/shell/ash_test/ash-heredoc/heredoc.right @@ -0,0 +1,21 @@ +there +one - alpha +two - beta +three - gamma +hi\ +there$a +stuff +hi\ +there +EO\ +F +hi +tab 1 +tab 2 +tab 3 +abc +def ghi +jkl mno +fff is a shell function +hi +there diff --git a/shell/ash_test/ash-heredoc/heredoc.tests b/shell/ash_test/ash-heredoc/heredoc.tests new file mode 100755 index 0000000..b3cdc3f --- /dev/null +++ b/shell/ash_test/ash-heredoc/heredoc.tests @@ -0,0 +1,94 @@ +# check order and content of multiple here docs + +cat << EOF1 << EOF2 +hi +EOF1 +there +EOF2 + +while read line1; do + read line2 <&3 + echo $line1 - $line2 +done < /tmp/bash-zzz << EOF +abc +EOF +cat >> /tmp/bash-zzz << EOF +def ghi +jkl mno +EOF +cat /tmp/bash-zzz +rm -f /tmp/bash-zzz + +# make sure command printing puts the here-document as the last redirection +# on the line, and the function export code preserves syntactic correctness +fff() +{ + ed /tmp/foo </dev/null +/^name/d +w +q +ENDOFINPUT +aa=1 +} + +type fff +#ash# export -f fff +#ash# ${THIS_SH} -c 'type fff' + +# check that end of file delimits a here-document +# THIS MUST BE LAST! + +cat << EOF +hi +there diff --git a/shell/ash_test/ash-invert/invert.right b/shell/ash_test/ash-invert/invert.right new file mode 100644 index 0000000..5a9239a --- /dev/null +++ b/shell/ash_test/ash-invert/invert.right @@ -0,0 +1,10 @@ +1 +1 +1 +0 +0 +1 +0 +1 +0 +1 diff --git a/shell/ash_test/ash-invert/invert.tests b/shell/ash_test/ash-invert/invert.tests new file mode 100755 index 0000000..8393d95 --- /dev/null +++ b/shell/ash_test/ash-invert/invert.tests @@ -0,0 +1,19 @@ +# tests of return value inversion +# placeholder for future expansion + +# user subshells (...) did this wrong in bash versions before 2.04 + +! ( echo hello | grep h >/dev/null 2>&1 ); echo $? +! echo hello | grep h >/dev/null 2>&1 ; echo $? + +! true ; echo $? +! false; echo $? + +! (false) ; echo $? +! (true); echo $? + +! true | false ; echo $? +! false | true ; echo $? + +! (true | false) ; echo $? +! (false | true) ; echo $? diff --git a/shell/ash_test/ash-misc/shift1.right b/shell/ash_test/ash-misc/shift1.right new file mode 100644 index 0000000..b53453c --- /dev/null +++ b/shell/ash_test/ash-misc/shift1.right @@ -0,0 +1,9 @@ +2 3 4 +0: shift: line 1: Illegal number: -1 +1 2 3 4 +2 3 4 +3 4 +4 + +1 2 3 4 +1 2 3 4 diff --git a/shell/ash_test/ash-misc/shift1.tests b/shell/ash_test/ash-misc/shift1.tests new file mode 100755 index 0000000..0992d9b --- /dev/null +++ b/shell/ash_test/ash-misc/shift1.tests @@ -0,0 +1,10 @@ +$THIS_SH -c 'shift; echo "$@"' 0 1 2 3 4 +#We do abort on -1, but then we abort. bash executes echo. +$THIS_SH -c 'shift -1; echo "$@"' 0 1 2 3 4 +$THIS_SH -c 'shift 0; echo "$@"' 0 1 2 3 4 +$THIS_SH -c 'shift 1; echo "$@"' 0 1 2 3 4 +$THIS_SH -c 'shift 2; echo "$@"' 0 1 2 3 4 +$THIS_SH -c 'shift 3; echo "$@"' 0 1 2 3 4 +$THIS_SH -c 'shift 4; echo "$@"' 0 1 2 3 4 +$THIS_SH -c 'shift 5; echo "$@"' 0 1 2 3 4 +$THIS_SH -c 'shift 6; echo "$@"' 0 1 2 3 4 diff --git a/shell/ash_test/ash-quoting/dollar_squote_bash1.right b/shell/ash_test/ash-quoting/dollar_squote_bash1.right new file mode 100644 index 0000000..57536b1 --- /dev/null +++ b/shell/ash_test/ash-quoting/dollar_squote_bash1.right @@ -0,0 +1,9 @@ +a b +a +b c +def +a'b c"d e\f +a3b c3b e33f +a\80b c08b +a3b c30b +x y diff --git a/shell/ash_test/ash-quoting/dollar_squote_bash1.tests b/shell/ash_test/ash-quoting/dollar_squote_bash1.tests new file mode 100755 index 0000000..93a56ca --- /dev/null +++ b/shell/ash_test/ash-quoting/dollar_squote_bash1.tests @@ -0,0 +1,7 @@ +echo $'a\tb' +echo $'a\nb' $'c\nd''ef' +echo $'a\'b' $'c\"d' $'e\\f' +echo $'a\63b' $'c\063b' $'e\0633f' +echo $'a\80b' $'c\608b' +echo $'a\x33b' $'c\x330b' +echo $'x\x9y' diff --git a/shell/ash_test/ash-read/read_n.right b/shell/ash_test/ash-read/read_n.right new file mode 100644 index 0000000..1f81af0 --- /dev/null +++ b/shell/ash_test/ash-read/read_n.right @@ -0,0 +1,3 @@ +test +tes +tes diff --git a/shell/ash_test/ash-read/read_n.tests b/shell/ash_test/ash-read/read_n.tests new file mode 100755 index 0000000..12423ba --- /dev/null +++ b/shell/ash_test/ash-read/read_n.tests @@ -0,0 +1,3 @@ +echo 'test' | (read reply; echo "$reply") +echo 'test' | (read -n 3 reply; echo "$reply") +echo 'test' | (read -n3 reply; echo "$reply") diff --git a/shell/ash_test/ash-read/read_r.right b/shell/ash_test/ash-read/read_r.right new file mode 100644 index 0000000..3536bf7 --- /dev/null +++ b/shell/ash_test/ash-read/read_r.right @@ -0,0 +1,2 @@ +testbest +test\ diff --git a/shell/ash_test/ash-read/read_r.tests b/shell/ash_test/ash-read/read_r.tests new file mode 100755 index 0000000..2c4cc61 --- /dev/null +++ b/shell/ash_test/ash-read/read_r.tests @@ -0,0 +1,2 @@ +echo -e 'test\\\nbest' | (read reply; echo "$reply") +echo -e 'test\\\nbest' | (read -r reply; echo "$reply") diff --git a/shell/ash_test/ash-read/read_t.right b/shell/ash_test/ash-read/read_t.right new file mode 100644 index 0000000..04126cb --- /dev/null +++ b/shell/ash_test/ash-read/read_t.right @@ -0,0 +1,4 @@ +>< +>< +>test< +>test< diff --git a/shell/ash_test/ash-read/read_t.tests b/shell/ash_test/ash-read/read_t.tests new file mode 100755 index 0000000..d65f1ae --- /dev/null +++ b/shell/ash_test/ash-read/read_t.tests @@ -0,0 +1,10 @@ +# bash 3.2 outputs: + +# >< +{ echo -n 'te'; sleep 2; echo 'st'; } | (read -t 1 reply; echo ">$reply<") +# >< +{ sleep 2; echo 'test'; } | (read -t 1 reply; echo ">$reply<") +# >test< +{ echo -n 'te'; sleep 1; echo 'st'; } | (read -t 2 reply; echo ">$reply<") +# >test< +{ sleep 1; echo 'test'; } | (read -t 2 reply; echo ">$reply<") diff --git a/shell/ash_test/ash-redir/redir.right b/shell/ash_test/ash-redir/redir.right new file mode 100644 index 0000000..2a02d41 --- /dev/null +++ b/shell/ash_test/ash-redir/redir.right @@ -0,0 +1 @@ +TEST diff --git a/shell/ash_test/ash-redir/redir.tests b/shell/ash_test/ash-redir/redir.tests new file mode 100755 index 0000000..7a1a668 --- /dev/null +++ b/shell/ash_test/ash-redir/redir.tests @@ -0,0 +1,6 @@ +# test: closed fds should stay closed +exec 1>&- +echo TEST >TEST +echo JUNK # lost: stdout is closed +cat TEST >&2 +rm TEST diff --git a/shell/ash_test/ash-redir/redir2.right b/shell/ash_test/ash-redir/redir2.right new file mode 100644 index 0000000..d86bac9 --- /dev/null +++ b/shell/ash_test/ash-redir/redir2.right @@ -0,0 +1 @@ +OK diff --git a/shell/ash_test/ash-redir/redir2.tests b/shell/ash_test/ash-redir/redir2.tests new file mode 100755 index 0000000..61ccea3 --- /dev/null +++ b/shell/ash_test/ash-redir/redir2.tests @@ -0,0 +1,5 @@ +# ash once couldn't redirect above fd#9 +exec 1>/dev/null +(echo LOST1 >&22) 22>&1 +(echo LOST2 >&22) 22>&1 +(echo OK >&22) 22>&2 diff --git a/shell/ash_test/ash-redir/redir3.right b/shell/ash_test/ash-redir/redir3.right new file mode 100644 index 0000000..fd641a8 --- /dev/null +++ b/shell/ash_test/ash-redir/redir3.right @@ -0,0 +1,3 @@ +TEST +./redir3.tests: line 4: 9: Bad file descriptor +Output to fd#9: 1 diff --git a/shell/ash_test/ash-redir/redir3.tests b/shell/ash_test/ash-redir/redir3.tests new file mode 100755 index 0000000..f50a767 --- /dev/null +++ b/shell/ash_test/ash-redir/redir3.tests @@ -0,0 +1,5 @@ +# redirects to closed descriptors should not leave these descriptors" +# open afterwards +echo TEST 9>/dev/null +echo MUST ERROR OUT >&9 +echo "Output to fd#9: $?" diff --git a/shell/ash_test/ash-redir/redir4.right b/shell/ash_test/ash-redir/redir4.right new file mode 100644 index 0000000..d86bac9 --- /dev/null +++ b/shell/ash_test/ash-redir/redir4.right @@ -0,0 +1 @@ +OK diff --git a/shell/ash_test/ash-redir/redir4.tests b/shell/ash_test/ash-redir/redir4.tests new file mode 100755 index 0000000..4bdf5ae --- /dev/null +++ b/shell/ash_test/ash-redir/redir4.tests @@ -0,0 +1,72 @@ +# ash uses fd 10 (usually) for reading the script +exec 13>&- +exec 12>&- +exec 11>&- +exec 10>&- +# some amount of input is prefetched. +# make sure final echo is far enough to not be prefetched. +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +echo "OK" diff --git a/shell/ash_test/ash-redir/redir5.right b/shell/ash_test/ash-redir/redir5.right new file mode 100644 index 0000000..9d08777 --- /dev/null +++ b/shell/ash_test/ash-redir/redir5.right @@ -0,0 +1,2 @@ +./redir5.tests: line 2: 10: Bad file descriptor +OK diff --git a/shell/ash_test/ash-redir/redir5.tests b/shell/ash_test/ash-redir/redir5.tests new file mode 100755 index 0000000..91b0c1f --- /dev/null +++ b/shell/ash_test/ash-redir/redir5.tests @@ -0,0 +1,3 @@ +# ash uses fd 10 (usually) for reading the script +echo LOST >&10 +echo OK diff --git a/shell/ash_test/ash-redir/redir6.right b/shell/ash_test/ash-redir/redir6.right new file mode 100644 index 0000000..ed754df --- /dev/null +++ b/shell/ash_test/ash-redir/redir6.right @@ -0,0 +1,2 @@ +Hello +OK diff --git a/shell/ash_test/ash-redir/redir6.tests b/shell/ash_test/ash-redir/redir6.tests new file mode 100755 index 0000000..33b6d4c --- /dev/null +++ b/shell/ash_test/ash-redir/redir6.tests @@ -0,0 +1,3 @@ +# we had a bug where this would hang +(head -n 1 /dev/null & + +sleep 1 & +PID=$! + +# We must exit the loop in one second. +# We had bug 5304: builtins never waited for exited children +while kill -0 $PID >/dev/null 2>&1; do + true +done +echo Ok diff --git a/shell/ash_test/ash-signals/signal1.right b/shell/ash_test/ash-signals/signal1.right new file mode 100644 index 0000000..cf403ac --- /dev/null +++ b/shell/ash_test/ash-signals/signal1.right @@ -0,0 +1,20 @@ +got signal +trap -- 'echo got signal' USR1 +sent 1 signal +got signal +wait interrupted +trap -- 'echo got signal' USR1 +sent 2 signal +got signal +wait interrupted +trap -- 'echo got signal' USR1 +sent 3 signal +got signal +wait interrupted +trap -- 'echo got signal' USR1 +sent 4 signal +got signal +wait interrupted +trap -- 'echo got signal' USR1 +sent 5 signal +sleep completed diff --git a/shell/ash_test/ash-signals/signal1.tests b/shell/ash_test/ash-signals/signal1.tests new file mode 100755 index 0000000..098d21f --- /dev/null +++ b/shell/ash_test/ash-signals/signal1.tests @@ -0,0 +1,23 @@ +trap "echo got signal" USR1 + +for try in 1 2 3 4 5; do + kill -USR1 $$ + sleep 0.2 + echo "sent $try signal" +done & + +sleep 2 & + +sleeping=true +while $sleeping; do + trap + if wait %%; then + echo "sleep completed" + sleeping=false + elif [ $? == 127 ]; then + echo "BUG: no processes to wait for?!" + sleeping=false + else + echo "wait interrupted" + fi +done diff --git a/shell/ash_test/ash-signals/signal2.right b/shell/ash_test/ash-signals/signal2.right new file mode 100644 index 0000000..a2af919 --- /dev/null +++ b/shell/ash_test/ash-signals/signal2.right @@ -0,0 +1,3 @@ +child sleeps +child exits as expected +parent exits diff --git a/shell/ash_test/ash-signals/signal2.tests b/shell/ash_test/ash-signals/signal2.tests new file mode 100755 index 0000000..df639ca --- /dev/null +++ b/shell/ash_test/ash-signals/signal2.tests @@ -0,0 +1,18 @@ +#!/bin/sh + +$THIS_SH -c ' +cleanup() { + echo "child exits as expected" + exit +} +trap cleanup HUP +echo "child sleeps" +sleep 1 +echo "BAD exit from child!" +' & + +child=$! +sleep 0.1 # let child install handler first +kill -HUP $child +wait +echo "parent exits" diff --git a/shell/ash_test/ash-signals/signal3.right b/shell/ash_test/ash-signals/signal3.right new file mode 100644 index 0000000..3113ba5 --- /dev/null +++ b/shell/ash_test/ash-signals/signal3.right @@ -0,0 +1,4 @@ +child sleeps +child got HUP +child exits +parent exits diff --git a/shell/ash_test/ash-signals/signal3.tests b/shell/ash_test/ash-signals/signal3.tests new file mode 100755 index 0000000..b56c2d9 --- /dev/null +++ b/shell/ash_test/ash-signals/signal3.tests @@ -0,0 +1,17 @@ +#!/bin/sh + +$THIS_SH -c ' +hup() { + echo "child got HUP" +} +trap hup HUP +echo "child sleeps" +sleep 1 +echo "child exits" +' & + +child=$! +sleep 0.1 # let child install handler first +kill -HUP $child +wait +echo "parent exits" diff --git a/shell/ash_test/ash-standalone/noexec_gets_no_env.right b/shell/ash_test/ash-standalone/noexec_gets_no_env.right new file mode 100644 index 0000000..8522dff --- /dev/null +++ b/shell/ash_test/ash-standalone/noexec_gets_no_env.right @@ -0,0 +1,4 @@ +VAR7=VAL +0 +VAR8=VAL +0 diff --git a/shell/ash_test/ash-standalone/noexec_gets_no_env.tests b/shell/ash_test/ash-standalone/noexec_gets_no_env.tests new file mode 100755 index 0000000..0d347bd --- /dev/null +++ b/shell/ash_test/ash-standalone/noexec_gets_no_env.tests @@ -0,0 +1,5 @@ +export VAR7=VAL +env | grep ^VAR7= +echo $? +VAR8=VAL env | grep ^VAR8= +echo $? diff --git a/shell/ash_test/ash-standalone/nofork_trashes_getopt.right b/shell/ash_test/ash-standalone/nofork_trashes_getopt.right new file mode 100644 index 0000000..573541a --- /dev/null +++ b/shell/ash_test/ash-standalone/nofork_trashes_getopt.right @@ -0,0 +1 @@ +0 diff --git a/shell/ash_test/ash-standalone/nofork_trashes_getopt.tests b/shell/ash_test/ash-standalone/nofork_trashes_getopt.tests new file mode 100755 index 0000000..f42c507 --- /dev/null +++ b/shell/ash_test/ash-standalone/nofork_trashes_getopt.tests @@ -0,0 +1,6 @@ +# In this test, rm is NOFORK and it modifies getopt internal state +rm -f non_existent_file +# Subsequent hexdump is run as NOEXEC, and thus still uses this state +hexdump &1 +echo "should be empty: '$VAR'" diff --git a/shell/ash_test/ash-vars/var_posix1.right b/shell/ash_test/ash-vars/var_posix1.right new file mode 100644 index 0000000..55f3579 --- /dev/null +++ b/shell/ash_test/ash-vars/var_posix1.right @@ -0,0 +1,17 @@ +abcdcd +abcdcd +abcdcd +cdcd +babcdcd +babcdcd +ababcdcd + +ababcd +ababcd +ababcd +abab +ababcdc +ababcdc +ababcdcd + +end diff --git a/shell/ash_test/ash-vars/var_posix1.tests b/shell/ash_test/ash-vars/var_posix1.tests new file mode 100755 index 0000000..4139e2c --- /dev/null +++ b/shell/ash_test/ash-vars/var_posix1.tests @@ -0,0 +1,21 @@ +var=ababcdcd + +echo ${var#ab} +echo ${var##ab} +echo ${var#a*b} +echo ${var##a*b} +echo ${var#?} +echo ${var##?} +echo ${var#*} +echo ${var##*} + +echo ${var%cd} +echo ${var%%cd} +echo ${var%c*d} +echo ${var%%c*d} +echo ${var%?} +echo ${var%%?} +echo ${var%*} +echo ${var%%*} + +echo end diff --git a/shell/ash_test/printenv.c b/shell/ash_test/printenv.c new file mode 100644 index 0000000..c4ccda8 --- /dev/null +++ b/shell/ash_test/printenv.c @@ -0,0 +1,67 @@ +/* printenv -- minimal clone of BSD printenv(1). + + usage: printenv [varname] + + Chet Ramey + chet@po.cwru.edu +*/ + +/* Copyright (C) 1997-2002 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software; you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation; either version 2, or (at your option) any later + version. + + Bash is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License along + with Bash; see the file COPYING. If not, write to the Free Software + Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ + +#include +#include + +extern char **environ; + +int +main (argc, argv) + int argc; + char **argv; +{ + register char **envp, *eval; + int len; + + argv++; + argc--; + + /* printenv */ + if (argc == 0) + { + for (envp = environ; *envp; envp++) + puts (*envp); + exit(EXIT_SUCCESS); + } + + /* printenv varname */ + len = strlen (*argv); + for (envp = environ; *envp; envp++) + { + if (**argv == **envp && strncmp (*envp, *argv, len) == 0) + { + eval = *envp + len; + /* If the environment variable doesn't have an `=', ignore it. */ + if (*eval == '=') + { + puts (eval + 1); + exit(EXIT_SUCCESS); + } + } + } + exit(EXIT_FAILURE); +} diff --git a/shell/ash_test/recho.c b/shell/ash_test/recho.c new file mode 100644 index 0000000..fb48d9c --- /dev/null +++ b/shell/ash_test/recho.c @@ -0,0 +1,63 @@ +/* + recho -- really echo args, bracketed with <> and with invisible chars + made visible. + + Chet Ramey + chet@po.cwru.edu +*/ + +/* Copyright (C) 2002-2005 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software; you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation; either version 2, or (at your option) any later + version. + + Bash is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License along + with Bash; see the file COPYING. If not, write to the Free Software + Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ + +#include +#include + +void strprint(); + +int +main(argc, argv) +int argc; +char **argv; +{ + register int i; + + for (i = 1; i < argc; i++) { + printf("argv[%d] = <", i); + strprint(argv[i]); + printf(">\n"); + } + exit(EXIT_SUCCESS); +} + +void +strprint(str) +char *str; +{ + register unsigned char *s; + + for (s = (unsigned char *)str; s && *s; s++) { + if (*s < ' ') { + putchar('^'); + putchar(*s+64); + } else if (*s == 127) { + putchar('^'); + putchar('?'); + } else + putchar(*s); + } +} diff --git a/shell/ash_test/run-all b/shell/ash_test/run-all new file mode 100755 index 0000000..4d0f39a --- /dev/null +++ b/shell/ash_test/run-all @@ -0,0 +1,74 @@ +#!/bin/sh + +TOPDIR=$PWD + +test -x ash || { + echo "No ./ash - creating a link to ../../busybox" + ln -s ../../busybox ash +} +test -x printenv || gcc -O2 -o printenv printenv.c || exit $? +test -x recho || gcc -O2 -o recho recho.c || exit $? +test -x zecho || gcc -O2 -o zecho zecho.c || exit $? + +PATH="$PWD:$PATH" # for ash and recho/zecho/printenv +export PATH + +THIS_SH="$PWD/ash" +export THIS_SH + +do_test() +{ + test -d "$1" || return 0 + echo do_test "$1" + # $1 but with / replaced by # so that it can be used as filename part + noslash=`echo "$1" | sed 's:/:#:g'` + ( + cd "$1" || { echo "cannot cd $1!"; exit 1; } + for x in run-*; do + test -f "$x" || continue + case "$x" in + "$0"|run-minimal|run-gprof) ;; + *.orig|*~) ;; + #*) echo $x ; sh $x ;; + *) + sh "$x" >"$TOPDIR/$noslash-$x.fail" 2>&1 && \ + { echo "$1/$x: ok"; rm "$TOPDIR/$noslash-$x.fail"; } || echo "$1/$x: fail"; + ;; + esac + done + # Many bash run-XXX scripts just do this, + # no point in duplication it all over the place + for x in *.tests; do + test -x "$x" || continue + name="${x%%.tests}" + test -f "$name.right" || continue + { + "$THIS_SH" "./$x" >"$name.xx" 2>&1 + diff -u "$name.xx" "$name.right" >"$TOPDIR/$noslash-$x.fail" \ + && rm -f "$name.xx" "$TOPDIR/$noslash-$x.fail" + } && echo "$1/$x: ok" || echo "$1/$x: fail" + done + ) +} + +# main part of this script +# Usage: run-all [directories] + +if [ $# -lt 1 ]; then + # All sub directories + modules=`ls -d ash-*` + # If you want to test ash against hush and msh testsuites + # (have to copy hush_test and msh_test dirs to current dir first): + #modules=`ls -d ash-* hush_test/hush-* msh_test/msh-*` + + for module in $modules; do + do_test $module + done +else + while [ $# -ge 1 ]; do + if [ -d $1 ]; then + do_test $1 + fi + shift + done +fi diff --git a/shell/ash_test/zecho.c b/shell/ash_test/zecho.c new file mode 100644 index 0000000..bf876f6 --- /dev/null +++ b/shell/ash_test/zecho.c @@ -0,0 +1,39 @@ +/* zecho - bare-bones echo */ + +/* Copyright (C) 1996-2002 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software; you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation; either version 2, or (at your option) any later + version. + + Bash is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License along + with Bash; see the file COPYING. If not, write to the Free Software + Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ + +#include +#include + +int +main(argc, argv) +int argc; +char **argv; +{ + argv++; + + while (*argv) { + (void)printf("%s", *argv); + if (*++argv) + putchar(' '); + } + + putchar('\n'); + exit(EXIT_SUCCESS); +} diff --git a/shell/bbsh.c b/shell/bbsh.c new file mode 100644 index 0000000..897c022 --- /dev/null +++ b/shell/bbsh.c @@ -0,0 +1,223 @@ +/* vi: set ts=4 : + * + * bbsh - busybox shell + * + * Copyright 2006 Rob Landley + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +// A section of code that gets repeatedly or conditionally executed is stored +// as a string and parsed each time it's run. + + + +// Wheee, debugging. + +// Terminal control +#define ENABLE_BBSH_TTY 0 + +// &, fg, bg, jobs. (ctrl-z with tty.) +#define ENABLE_BBSH_JOBCTL 0 + +// Flow control (if, while, for, functions { }) +#define ENABLE_BBSH_FLOWCTL 0 + +#define ENABLE_BBSH_ENVVARS 0 // Environment variable support + +// Local and synthetic variables, fancy prompts, set, $?, etc. +#define ENABLE_BBSH_LOCALVARS 0 + +// Pipes and redirects: | > < >> << && || & () ; +#define ENABLE_BBSH_PIPES 0 + +/* Fun: + + echo `echo hello#comment " woot` and more +*/ + +#include "libbb.h" + +// A single executable, its arguments, and other information we know about it. +#define BBSH_FLAG_EXIT 1 +#define BBSH_FLAG_SUSPEND 2 +#define BBSH_FLAG_PIPE 4 +#define BBSH_FLAG_AND 8 +#define BBSH_FLAG_OR 16 +#define BBSH_FLAG_AMP 32 +#define BBSH_FLAG_SEMI 64 +#define BBSH_FLAG_PAREN 128 + +// What we know about a single process. +struct command { + struct command *next; + int flags; // exit, suspend, && || + int pid; // pid (or exit code) + int argc; + char *argv[0]; +}; + +// A collection of processes piped into/waiting on each other. +struct pipeline { + struct pipeline *next; + int job_id; + struct command *cmd; + char *cmdline; + int cmdlinelen; +}; + +static void free_list(void *list, void (*freeit)(void *data)) +{ + while (list) { + void **next = (void **)list; + void *list_next = *next; + freeit(list); + free(list); + list = list_next; + } +} + +// 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. +static char *parse_word(char *start, struct command **cmd) +{ + char *end; + + // Detect end of line (and truncate line at comment) + if (ENABLE_BBSH_PIPES && strchr("><&|(;", *start)) return 0; + + // Grab next word. (Add dequote and envvar logic here) + end = start; + end = skip_non_whitespace(end); + (*cmd)->argv[(*cmd)->argc++] = xstrndup(start, end-start); + + // Allocate more space if there's no room for NULL terminator. + + if (!((*cmd)->argc & 7)) + *cmd = xrealloc(*cmd, + sizeof(struct command) + ((*cmd)->argc+8)*sizeof(char *)); + (*cmd)->argv[(*cmd)->argc] = 0; + return end; +} + +// Parse a line of text into a pipeline. +// Returns a pointer to the next line. + +static char *parse_pipeline(char *cmdline, struct pipeline *line) +{ + struct command **cmd = &(line->cmd); + char *start = line->cmdline = cmdline; + + if (!cmdline) return 0; + + if (ENABLE_BBSH_JOBCTL) line->cmdline = cmdline; + + // Parse command into argv[] + for (;;) { + char *end; + + // Skip leading whitespace and detect end of line. + start = skip_whitespace(start); + if (!*start || *start=='#') { + if (ENABLE_BBSH_JOBCTL) line->cmdlinelen = start-cmdline; + return 0; + } + + // Allocate next command structure if necessary + if (!*cmd) *cmd = xzalloc(sizeof(struct command)+8*sizeof(char *)); + + // Parse next argument and add the results to argv[] + end = parse_word(start, cmd); + + // If we hit the end of this command, how did it end? + if (!end) { + if (ENABLE_BBSH_PIPES && *start) { + if (*start==';') { + start++; + break; + } + // handle | & < > >> << || && + } + break; + } + start = end; + } + + if (ENABLE_BBSH_JOBCTL) line->cmdlinelen = start-cmdline; + + return start; +} + +// Execute the commands in a pipeline +static int run_pipeline(struct pipeline *line) +{ + struct command *cmd = line->cmd; + if (!cmd || !cmd->argc) return 0; + + // Handle local commands. This is totally fake and plastic. + if (cmd->argc==2 && !strcmp(cmd->argv[0],"cd")) + chdir(cmd->argv[1]); + else if (!strcmp(cmd->argv[0],"exit")) + exit(cmd->argc>1 ? atoi(cmd->argv[1]) : 0); + else { + int status; + pid_t pid=fork(); + if (!pid) { + run_applet_and_exit(cmd->argv[0],cmd->argc,cmd->argv); + execvp(cmd->argv[0],cmd->argv); + printf("No %s", cmd->argv[0]); + exit(EXIT_FAILURE); + } else waitpid(pid, &status, 0); + } + + return 0; +} + +static void free_cmd(void *data) +{ + struct command *cmd=(struct command *)data; + + while (cmd->argc) free(cmd->argv[--cmd->argc]); +} + + +static void handle(char *command) +{ + struct pipeline line; + char *start = command; + + for (;;) { + memset(&line,0,sizeof(struct pipeline)); + start = parse_pipeline(start, &line); + if (!line.cmd) break; + + run_pipeline(&line); + free_list(line.cmd, free_cmd); + } +} + +int bbsh_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int bbsh_main(int argc, char **argv) +{ + char *command=NULL; + FILE *f; + + getopt32(argv, "c:", &command); + + f = argv[optind] ? xfopen_for_read(argv[optind]) : NULL; + if (command) handle(command); + else { + unsigned cmdlen=0; + for (;;) { + if (!f) putchar('$'); + if (1 > getline(&command, &cmdlen,f ? : stdin)) break; + + handle(command); + } + if (ENABLE_FEATURE_CLEAN_UP) free(command); + } + + return 1; +} diff --git a/shell/cttyhack.c b/shell/cttyhack.c new file mode 100644 index 0000000..0aa4b8a --- /dev/null +++ b/shell/cttyhack.c @@ -0,0 +1,77 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under GPLv2 + * + * Copyright (c) 2007 Denys Vlasenko + */ +#include "libbb.h" + +/* From */ +struct vt_stat { + unsigned short v_active; /* active vt */ + unsigned short v_signal; /* signal to send */ + unsigned short v_state; /* vt bitmask */ +}; +enum { VT_GETSTATE = 0x5603 }; /* get global vt state info */ + +/* From */ +struct serial_struct { + int type; + int line; + unsigned int port; + int irq; + int flags; + int xmit_fifo_size; + int custom_divisor; + int baud_base; + unsigned short close_delay; + char io_type; + char reserved_char[1]; + int hub6; + unsigned short closing_wait; /* time to wait before closing */ + unsigned short closing_wait2; /* no longer used... */ + unsigned char *iomem_base; + unsigned short iomem_reg_shift; + unsigned int port_high; + unsigned long iomap_base; /* cookie passed into ioremap */ + int reserved[1]; +}; + +int cttyhack_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int cttyhack_main(int argc UNUSED_PARAM, char **argv) +{ + int fd; + char console[sizeof(int)*3 + 16]; + union { + struct vt_stat vt; + struct serial_struct sr; + char paranoia[sizeof(struct serial_struct) * 3]; + } u; + + if (!*++argv) { + bb_show_usage(); + } + + strcpy(console, "/dev/tty"); + if (ioctl(0, TIOCGSERIAL, &u.sr) == 0) { + /* this is a serial console */ + sprintf(console + 8, "S%d", u.sr.line); + } else if (ioctl(0, VT_GETSTATE, &u.vt) == 0) { + /* this is linux virtual tty */ + sprintf(console + 8, "S%d" + 1, u.vt.v_active); + } + + if (console[8]) { + fd = xopen(console, O_RDWR); + //bb_error_msg("switching to '%s'", console); + dup2(fd, 0); + dup2(fd, 1); + dup2(fd, 2); + while (fd > 2) close(fd--); + /* Some other session may have it as ctty. Steal it from them */ + ioctl(0, TIOCSCTTY, 1); + } + + BB_EXECVP(argv[0], argv); + bb_perror_msg_and_die("cannot exec '%s'", argv[0]); +} diff --git a/shell/hush.c b/shell/hush.c new file mode 100644 index 0000000..4212729 --- /dev/null +++ b/shell/hush.c @@ -0,0 +1,4749 @@ +/* vi: set sw=4 ts=4: */ +/* + * sh.c -- a prototype Bourne shell grammar parser + * Intended to follow the original Thompson and Ritchie + * "small and simple is beautiful" philosophy, which + * incidentally is a good match to today's BusyBox. + * + * Copyright (C) 2000,2001 Larry Doolittle + * + * Credits: + * The parser routines proper are all original material, first + * written Dec 2000 and Jan 2001 by Larry Doolittle. The + * execution engine, the builtins, and much of the underlying + * support has been adapted from busybox-0.49pre's lash, which is + * Copyright (C) 1999-2004 by Erik Andersen + * written by Erik Andersen . That, in turn, + * is based in part on ladsh.c, by Michael K. Johnson and Erik W. + * Troan, which they placed in the public domain. I don't know + * how much of the Johnson/Troan code has survived the repeated + * rewrites. + * + * Other credits: + * o_addchr() derived from similar w_addchar function in glibc-2.2. + * setup_redirect(), redirect_opt_num(), and big chunks of main() + * and many builtins derived from contributions by Erik Andersen + * miscellaneous bugfixes from Matt Kraai. + * + * There are two big (and related) architecture differences between + * this parser and the lash parser. One is that this version is + * actually designed from the ground up to understand nearly all + * of the Bourne grammar. The second, consequential change is that + * the parser and input reader have been turned inside out. Now, + * the parser is in control, and asks for input as needed. The old + * way had the input reader in control, and it asked for parsing to + * take place as needed. The new way makes it much easier to properly + * handle the recursion implicit in the various substitutions, especially + * across continuation lines. + * + * Bash grammar not implemented: (how many of these were in original sh?) + * $_ + * &> and >& redirection of stdout+stderr + * Brace Expansion + * Tilde Expansion + * fancy forms of Parameter Expansion + * aliases + * Arithmetic Expansion + * <(list) and >(list) Process Substitution + * reserved words: select, function + * Here Documents ( << word ) + * Functions + * Major bugs: + * job handling woefully incomplete and buggy (improved --vda) + * to-do: + * port selected bugfixes from post-0.49 busybox lash - done? + * change { and } from special chars to reserved words + * builtins: return, trap, ulimit + * test magic exec with redirection only + * check setting of global_argc and global_argv + * follow IFS rules more precisely, including update semantics + * figure out what to do with backslash-newline + * propagate syntax errors, die on resource errors? + * continuation lines, both explicit and implicit - done? + * memory leak finding and plugging - done? + * maybe change charmap[] to use 2-bit entries + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include "busybox.h" /* for APPLET_IS_NOFORK/NOEXEC */ +#include +/* #include */ +#if ENABLE_HUSH_CASE +#include +#endif + +#define HUSH_VER_STR "0.91" + +#if !BB_MMU && ENABLE_HUSH_TICK +//#undef ENABLE_HUSH_TICK +//#define ENABLE_HUSH_TICK 0 +#warning On NOMMU, hush command substitution is dangerous. +#warning Dont use it for commands which produce lots of output. +#warning For more info see shell/hush.c, generate_stream_from_list(). +#endif + +#if !BB_MMU && ENABLE_HUSH_JOB +#undef ENABLE_HUSH_JOB +#define ENABLE_HUSH_JOB 0 +#endif + +#if !ENABLE_HUSH_INTERACTIVE +#undef ENABLE_FEATURE_EDITING +#define ENABLE_FEATURE_EDITING 0 +#undef ENABLE_FEATURE_EDITING_FANCY_PROMPT +#define ENABLE_FEATURE_EDITING_FANCY_PROMPT 0 +#endif + + +/* Keep unconditionally on for now */ +#define HUSH_DEBUG 1 +/* In progress... */ +#define ENABLE_HUSH_FUNCTIONS 0 + + +/* If you comment out one of these below, it will be #defined later + * to perform debug printfs to stderr: */ +#define debug_printf(...) do {} while (0) +/* Finer-grained debug switches */ +#define debug_printf_parse(...) do {} while (0) +#define debug_print_tree(a, b) do {} while (0) +#define debug_printf_exec(...) do {} while (0) +#define debug_printf_env(...) do {} while (0) +#define debug_printf_jobs(...) do {} while (0) +#define debug_printf_expand(...) do {} while (0) +#define debug_printf_glob(...) do {} while (0) +#define debug_printf_list(...) do {} while (0) +#define debug_printf_subst(...) do {} while (0) +#define debug_printf_clean(...) do {} while (0) + +#ifndef debug_printf +#define debug_printf(...) fprintf(stderr, __VA_ARGS__) +#endif + +#ifndef debug_printf_parse +#define debug_printf_parse(...) fprintf(stderr, __VA_ARGS__) +#endif + +#ifndef debug_printf_exec +#define debug_printf_exec(...) fprintf(stderr, __VA_ARGS__) +#endif + +#ifndef debug_printf_env +#define debug_printf_env(...) fprintf(stderr, __VA_ARGS__) +#endif + +#ifndef debug_printf_jobs +#define debug_printf_jobs(...) fprintf(stderr, __VA_ARGS__) +#define DEBUG_JOBS 1 +#else +#define DEBUG_JOBS 0 +#endif + +#ifndef debug_printf_expand +#define debug_printf_expand(...) fprintf(stderr, __VA_ARGS__) +#define DEBUG_EXPAND 1 +#else +#define DEBUG_EXPAND 0 +#endif + +#ifndef debug_printf_glob +#define debug_printf_glob(...) fprintf(stderr, __VA_ARGS__) +#define DEBUG_GLOB 1 +#else +#define DEBUG_GLOB 0 +#endif + +#ifndef debug_printf_list +#define debug_printf_list(...) fprintf(stderr, __VA_ARGS__) +#endif + +#ifndef debug_printf_subst +#define debug_printf_subst(...) fprintf(stderr, __VA_ARGS__) +#endif + +#ifndef debug_printf_clean +/* broken, of course, but OK for testing */ +static const char *indenter(int i) +{ + static const char blanks[] ALIGN1 = + " "; + return &blanks[sizeof(blanks) - i - 1]; +} +#define debug_printf_clean(...) fprintf(stderr, __VA_ARGS__) +#define DEBUG_CLEAN 1 +#endif + +#if DEBUG_EXPAND +static void debug_print_strings(const char *prefix, char **vv) +{ + fprintf(stderr, "%s:\n", prefix); + while (*vv) + fprintf(stderr, " '%s'\n", *vv++); +} +#else +#define debug_print_strings(prefix, vv) ((void)0) +#endif + +/* + * Leak hunting. Use hush_leaktool.sh for post-processing. + */ +#ifdef FOR_HUSH_LEAKTOOL +/* suppress "warning: no previous prototype..." */ +void *xxmalloc(int lineno, size_t size); +void *xxrealloc(int lineno, void *ptr, size_t size); +char *xxstrdup(int lineno, const char *str); +void xxfree(void *ptr); +void *xxmalloc(int lineno, size_t size) +{ + void *ptr = xmalloc((size + 0xff) & ~0xff); + fprintf(stderr, "line %d: malloc %p\n", lineno, ptr); + return ptr; +} +void *xxrealloc(int lineno, void *ptr, size_t size) +{ + ptr = xrealloc(ptr, (size + 0xff) & ~0xff); + fprintf(stderr, "line %d: realloc %p\n", lineno, ptr); + return ptr; +} +char *xxstrdup(int lineno, const char *str) +{ + char *ptr = xstrdup(str); + fprintf(stderr, "line %d: strdup %p\n", lineno, ptr); + return ptr; +} +void xxfree(void *ptr) +{ + fprintf(stderr, "free %p\n", ptr); + free(ptr); +} +#define xmalloc(s) xxmalloc(__LINE__, s) +#define xrealloc(p, s) xxrealloc(__LINE__, p, s) +#define xstrdup(s) xxstrdup(__LINE__, s) +#define free(p) xxfree(p) +#endif + + +/* Do we support ANY keywords? */ +#if ENABLE_HUSH_IF || ENABLE_HUSH_LOOPS || ENABLE_HUSH_CASE +#define HAS_KEYWORDS 1 +#define IF_HAS_KEYWORDS(...) __VA_ARGS__ +#define IF_HAS_NO_KEYWORDS(...) +#else +#define HAS_KEYWORDS 0 +#define IF_HAS_KEYWORDS(...) +#define IF_HAS_NO_KEYWORDS(...) __VA_ARGS__ +#endif + + +#define SPECIAL_VAR_SYMBOL 3 +#define PARSEFLAG_EXIT_FROM_LOOP 1 + +typedef enum redir_type { + REDIRECT_INPUT = 1, + REDIRECT_OVERWRITE = 2, + REDIRECT_APPEND = 3, + REDIRECT_HEREIS = 4, + REDIRECT_IO = 5 +} redir_type; + +/* The descrip member of this structure is only used to make + * debugging output pretty */ +static const struct { + int mode; + signed char default_fd; + char descrip[3]; +} redir_table[] = { + { 0, 0, "()" }, + { O_RDONLY, 0, "<" }, + { O_CREAT|O_TRUNC|O_WRONLY, 1, ">" }, + { O_CREAT|O_APPEND|O_WRONLY, 1, ">>" }, + { O_RDONLY, -1, "<<" }, + { O_RDWR, 1, "<>" } +}; + +typedef enum pipe_style { + PIPE_SEQ = 1, + PIPE_AND = 2, + PIPE_OR = 3, + PIPE_BG = 4, +} pipe_style; + +typedef enum reserved_style { + RES_NONE = 0, +#if ENABLE_HUSH_IF + RES_IF , + RES_THEN , + RES_ELIF , + RES_ELSE , + RES_FI , +#endif +#if ENABLE_HUSH_LOOPS + RES_FOR , + RES_WHILE , + RES_UNTIL , + RES_DO , + RES_DONE , +#endif +#if ENABLE_HUSH_LOOPS || ENABLE_HUSH_CASE + RES_IN , +#endif +#if ENABLE_HUSH_CASE + RES_CASE , + /* two pseudo-keywords support contrived "case" syntax: */ + RES_MATCH , /* "word)" */ + RES_CASEI , /* "this command is inside CASE" */ + RES_ESAC , +#endif + RES_XXXX , + RES_SNTX +} reserved_style; + +struct redir_struct { + struct redir_struct *next; + char *rd_filename; /* filename */ + int fd; /* file descriptor being redirected */ + int dup; /* -1, or file descriptor being duplicated */ + smallint /*enum redir_type*/ rd_type; +}; + +struct command { + pid_t pid; /* 0 if exited */ + int assignment_cnt; /* how many argv[i] are assignments? */ + smallint is_stopped; /* is the command currently running? */ + smallint grp_type; + struct pipe *group; /* if non-NULL, this "prog" is {} group, + * subshell, or a compound statement */ + char **argv; /* command name and arguments */ + struct redir_struct *redirects; /* I/O redirections */ +}; +/* argv vector may contain variable references (^Cvar^C, ^C0^C etc) + * and on execution these are substituted with their values. + * Substitution can make _several_ words out of one argv[n]! + * Example: argv[0]=='.^C*^C.' here: echo .$*. + * References of the form ^C`cmd arg^C are `cmd arg` substitutions. + */ +#define GRP_NORMAL 0 +#define GRP_SUBSHELL 1 +#if ENABLE_HUSH_FUNCTIONS +#define GRP_FUNCTION 2 +#endif + +struct pipe { + struct pipe *next; + int num_cmds; /* total number of commands in job */ + int alive_cmds; /* number of commands running (not exited) */ + int stopped_cmds; /* number of commands alive, but stopped */ +#if ENABLE_HUSH_JOB + int jobid; /* job number */ + pid_t pgrp; /* process group ID for the job */ + char *cmdtext; /* name of job */ +#endif + struct command *cmds; /* array of commands in pipe */ + smallint followup; /* PIPE_BG, PIPE_SEQ, PIPE_OR, PIPE_AND */ + IF_HAS_KEYWORDS(smallint pi_inverted;) /* "! cmd | cmd" */ + IF_HAS_KEYWORDS(smallint res_word;) /* needed for if, for, while, until... */ +}; + +/* This holds pointers to the various results of parsing */ +struct parse_context { + struct command *command; + struct pipe *list_head; + struct pipe *pipe; + struct redir_struct *pending_redirect; +#if HAS_KEYWORDS + smallint ctx_res_w; + smallint ctx_inverted; /* "! cmd | cmd" */ +#if ENABLE_HUSH_CASE + smallint ctx_dsemicolon; /* ";;" seen */ +#endif + int old_flag; /* bitmask of FLAG_xxx, for figuring out valid reserved words */ + struct parse_context *stack; +#endif +}; + +/* On program start, environ points to initial environment. + * putenv adds new pointers into it, unsetenv removes them. + * Neither of these (de)allocates the strings. + * setenv allocates new strings in malloc space and does putenv, + * and thus setenv is unusable (leaky) for shell's purposes */ +#define setenv(...) setenv_is_leaky_dont_use() +struct variable { + struct variable *next; + char *varstr; /* points to "name=" portion */ + int max_len; /* if > 0, name is part of initial env; else name is malloced */ + smallint flg_export; /* putenv should be done on this var */ + smallint flg_read_only; +}; + +typedef struct o_string { + char *data; + int length; /* position where data is appended */ + int maxlen; + /* Misnomer! it's not "quoting", it's "protection against globbing"! + * (by prepending \ to *, ?, [ and to \ too) */ + smallint o_quote; + smallint o_glob; + smallint nonnull; + smallint has_empty_slot; + smallint o_assignment; /* 0:maybe, 1:yes, 2:no */ +} o_string; +enum { + MAYBE_ASSIGNMENT = 0, + DEFINITELY_ASSIGNMENT = 1, + NOT_ASSIGNMENT = 2, + WORD_IS_KEYWORD = 3, /* not assigment, but next word may be: "if v=xyz cmd;" */ +}; +/* Used for initialization: o_string foo = NULL_O_STRING; */ +#define NULL_O_STRING { NULL } + +/* I can almost use ordinary FILE*. Is open_memstream() universally + * available? Where is it documented? */ +typedef struct in_str { + const char *p; + /* eof_flag=1: last char in ->p is really an EOF */ + char eof_flag; /* meaningless if ->p == NULL */ + char peek_buf[2]; +#if ENABLE_HUSH_INTERACTIVE + smallint promptme; + smallint promptmode; /* 0: PS1, 1: PS2 */ +#endif + FILE *file; + int (*get) (struct in_str *); + int (*peek) (struct in_str *); +} in_str; +#define i_getch(input) ((input)->get(input)) +#define i_peek(input) ((input)->peek(input)) + +enum { + CHAR_ORDINARY = 0, + CHAR_ORDINARY_IF_QUOTED = 1, /* example: *, # */ + CHAR_IFS = 2, /* treated as ordinary if quoted */ + CHAR_SPECIAL = 3, /* example: $ */ +}; + +enum { + BC_BREAK = 1, + BC_CONTINUE = 2, +}; + + +/* "Globals" within this file */ + +/* Sorted roughly by size (smaller offsets == smaller code) */ +struct globals { +#if ENABLE_HUSH_INTERACTIVE + /* 'interactive_fd' is a fd# open to ctty, if we have one + * _AND_ if we decided to act interactively */ + int interactive_fd; + const char *PS1; + const char *PS2; +#endif +#if ENABLE_FEATURE_EDITING + line_input_t *line_input_state; +#endif + pid_t root_pid; + pid_t last_bg_pid; +#if ENABLE_HUSH_JOB + int run_list_level; + pid_t saved_tty_pgrp; + int last_jobid; + struct pipe *job_list; + struct pipe *toplevel_list; + smallint ctrl_z_flag; +#endif +#if ENABLE_HUSH_LOOPS + smallint flag_break_continue; +#endif + smallint fake_mode; + /* these three support $?, $#, and $1 */ + smalluint last_return_code; + char **global_argv; + int global_argc; +#if ENABLE_HUSH_LOOPS + unsigned depth_break_continue; + unsigned depth_of_loop; +#endif + const char *ifs; + const char *cwd; + struct variable *top_var; /* = &G.shell_ver (set in main()) */ + struct variable shell_ver; +#if ENABLE_FEATURE_SH_STANDALONE + struct nofork_save_area nofork_save; +#endif +#if ENABLE_HUSH_JOB + sigjmp_buf toplevel_jb; +#endif + unsigned char charmap[256]; + char user_input_buf[ENABLE_FEATURE_EDITING ? BUFSIZ : 2]; +}; + +#define G (*ptr_to_globals) +/* Not #defining name to G.name - this quickly gets unwieldy + * (too many defines). Also, I actually prefer to see when a variable + * is global, thus "G." prefix is a useful hint */ +#define INIT_G() do { \ + SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \ +} while (0) + + +#define JOB_STATUS_FORMAT "[%d] %-22s %.40s\n" + +#if 1 +/* Normal */ +static void syntax(const char *msg) +{ +#if ENABLE_HUSH_INTERACTIVE + /* Was using fancy stuff: + * (G.interactive_fd ? bb_error_msg : bb_error_msg_and_die)(...params...) + * but it SEGVs. ?! Oh well... explicit temp ptr works around that */ + void FAST_FUNC (*fp)(const char *s, ...); + fp = (G.interactive_fd ? bb_error_msg : bb_error_msg_and_die); + fp(msg ? "%s: %s" : "syntax error", "syntax error", msg); +#else + bb_error_msg_and_die(msg ? "%s: %s" : "syntax error", "syntax error", msg); +#endif +} + +#else +/* Debug */ +static void syntax_lineno(int line) +{ +#if ENABLE_HUSH_INTERACTIVE + void FAST_FUNC (*fp)(const char *s, ...); + fp = (G.interactive_fd ? bb_error_msg : bb_error_msg_and_die); + fp("syntax error hush.c:%d", line); +#else + bb_error_msg_and_die("syntax error hush.c:%d", line); +#endif +} +#define syntax(str) syntax_lineno(__LINE__) +#endif + +/* Index of subroutines: */ +/* in_str manipulations: */ +static int static_get(struct in_str *i); +static int static_peek(struct in_str *i); +static int file_get(struct in_str *i); +static int file_peek(struct in_str *i); +static void setup_file_in_str(struct in_str *i, FILE *f); +static void setup_string_in_str(struct in_str *i, const char *s); +/* "run" the final data structures: */ +#if !defined(DEBUG_CLEAN) +#define free_pipe_list(head, indent) free_pipe_list(head) +#define free_pipe(pi, indent) free_pipe(pi) +#endif +static int free_pipe_list(struct pipe *head, int indent); +static int free_pipe(struct pipe *pi, int indent); +/* really run the final data structures: */ +typedef struct nommu_save_t { + char **new_env; + char **old_env; + char **argv; +} nommu_save_t; +#if BB_MMU +#define pseudo_exec_argv(nommu_save, argv, assignment_cnt, argv_expanded) \ + pseudo_exec_argv(argv, assignment_cnt, argv_expanded) +#define pseudo_exec(nommu_save, command, argv_expanded) \ + pseudo_exec(command, argv_expanded) +#endif +static void pseudo_exec_argv(nommu_save_t *nommu_save, char **argv, int assignment_cnt, char **argv_expanded) NORETURN; +static void pseudo_exec(nommu_save_t *nommu_save, struct command *command, char **argv_expanded) NORETURN; +static int setup_redirects(struct command *prog, int squirrel[]); +static int run_list(struct pipe *pi); +static int run_pipe(struct pipe *pi); +/* data structure manipulation: */ +static int setup_redirect(struct parse_context *ctx, int fd, redir_type style, struct in_str *input); +static void initialize_context(struct parse_context *ctx); +static int done_word(o_string *dest, struct parse_context *ctx); +static int done_command(struct parse_context *ctx); +static void done_pipe(struct parse_context *ctx, pipe_style type); +/* primary string parsing: */ +static int redirect_dup_num(struct in_str *input); +static int redirect_opt_num(o_string *o); +#if ENABLE_HUSH_TICK +static int process_command_subs(o_string *dest, + struct in_str *input, const char *subst_end); +#endif +static int parse_group(o_string *dest, struct parse_context *ctx, struct in_str *input, int ch); +static const char *lookup_param(const char *src); +static int handle_dollar(o_string *dest, + struct in_str *input); +static int parse_stream(o_string *dest, struct parse_context *ctx, struct in_str *input0, const char *end_trigger); +/* setup: */ +static int parse_and_run_stream(struct in_str *inp, int parse_flag); +static int parse_and_run_string(const char *s, int parse_flag); +static int parse_and_run_file(FILE *f); +/* job management: */ +static int checkjobs(struct pipe* fg_pipe); +#if ENABLE_HUSH_JOB +static int checkjobs_and_fg_shell(struct pipe* fg_pipe); +static void insert_bg_job(struct pipe *pi); +static void remove_bg_job(struct pipe *pi); +static void delete_finished_bg_job(struct pipe *pi); +#else +int checkjobs_and_fg_shell(struct pipe* fg_pipe); /* never called */ +#endif +/* local variable support */ +static char **expand_strvec_to_strvec(char **argv); +/* used for eval */ +static char *expand_strvec_to_string(char **argv); +/* used for expansion of right hand of assignments */ +static char *expand_string_to_string(const char *str); +static struct variable *get_local_var(const char *name); +static int set_local_var(char *str, int flg_export); +static void unset_local_var(const char *name); + + +static const char hush_version_str[] ALIGN1 = "HUSH_VERSION="HUSH_VER_STR; + + +static int glob_needed(const char *s) +{ + while (*s) { + if (*s == '\\') + s++; + if (*s == '*' || *s == '[' || *s == '?') + return 1; + s++; + } + return 0; +} + +static int is_assignment(const char *s) +{ + if (!s || !(isalpha(*s) || *s == '_')) + return 0; + s++; + while (isalnum(*s) || *s == '_') + s++; + return *s == '='; +} + +/* Replace each \x with x in place, return ptr past NUL. */ +static char *unbackslash(char *src) +{ + char *dst = src; + while (1) { + if (*src == '\\') + src++; + if ((*dst++ = *src++) == '\0') + break; + } + return dst; +} + +static char **add_strings_to_strings(char **strings, char **add) +{ + int i; + unsigned count1; + unsigned count2; + char **v; + + v = strings; + count1 = 0; + if (v) { + while (*v) { + count1++; + v++; + } + } + count2 = 0; + v = add; + while (*v) { + count2++; + v++; + } + v = xrealloc(strings, (count1 + count2 + 1) * sizeof(char*)); + v[count1 + count2] = NULL; + i = count2; + while (--i >= 0) + v[count1 + i] = add[i]; + return v; +} + +static char **add_string_to_strings(char **strings, char *add) +{ + char *v[2]; + v[0] = add; + v[1] = NULL; + return add_strings_to_strings(strings, v); +} + +static void putenv_all(char **strings) +{ + if (!strings) + return; + while (*strings) { + debug_printf_env("putenv '%s'\n", *strings); + putenv(*strings++); + } +} + +static char **putenv_all_and_save_old(char **strings) +{ + char **old = NULL; + char **s = strings; + + if (!strings) + return old; + while (*strings) { + char *v, *eq; + + eq = strchr(*strings, '='); + if (eq) { + *eq = '\0'; + v = getenv(*strings); + *eq = '='; + if (v) { + /* v points to VAL in VAR=VAL, go back to VAR */ + v -= (eq - *strings) + 1; + old = add_string_to_strings(old, v); + } + } + strings++; + } + putenv_all(s); + return old; +} + +static void free_strings_and_unsetenv(char **strings, int unset) +{ + char **v; + + if (!strings) + return; + + v = strings; + while (*v) { + if (unset) { + char *copy; + /* *strchrnul(*v, '=') = '\0'; -- BAD + * In case *v was putenv'ed, we can't + * unsetenv(*v) after taking out '=': + * it won't work, env is modified by taking out! + * horror :( */ + copy = xstrndup(*v, strchrnul(*v, '=') - *v); + debug_printf_env("unsetenv '%s'\n", copy); + unsetenv(copy); + free(copy); + } + free(*v++); + } + free(strings); +} + +static void free_strings(char **strings) +{ + free_strings_and_unsetenv(strings, 0); +} + + +/* Function prototypes for builtins */ +static int builtin_cd(char **argv); +static int builtin_echo(char **argv); +static int builtin_eval(char **argv); +static int builtin_exec(char **argv); +static int builtin_exit(char **argv); +static int builtin_export(char **argv); +#if ENABLE_HUSH_JOB +static int builtin_fg_bg(char **argv); +static int builtin_jobs(char **argv); +#endif +#if ENABLE_HUSH_HELP +static int builtin_help(char **argv); +#endif +static int builtin_pwd(char **argv); +static int builtin_read(char **argv); +static int builtin_test(char **argv); +static int builtin_true(char **argv); +static int builtin_set(char **argv); +static int builtin_shift(char **argv); +static int builtin_source(char **argv); +static int builtin_umask(char **argv); +static int builtin_unset(char **argv); +#if ENABLE_HUSH_LOOPS +static int builtin_break(char **argv); +static int builtin_continue(char **argv); +#endif +//static int builtin_not_written(char **argv); + +/* Table of built-in functions. They can be forked or not, depending on + * context: within pipes, they fork. As simple commands, they do not. + * When used in non-forking context, they can change global variables + * in the parent shell process. If forked, of course they cannot. + * For example, 'unset foo | whatever' will parse and run, but foo will + * still be set at the end. */ +struct built_in_command { + const char *cmd; + int (*function)(char **argv); +#if ENABLE_HUSH_HELP + const char *descr; +#define BLTIN(cmd, func, help) { cmd, func, help } +#else +#define BLTIN(cmd, func, help) { cmd, func } +#endif +}; + +/* For now, echo and test are unconditionally enabled. + * Maybe make it configurable? */ +static const struct built_in_command bltins[] = { + BLTIN("." , builtin_source, "Run commands in a file"), + BLTIN(":" , builtin_true, "No-op"), + BLTIN("[" , builtin_test, "Test condition"), + BLTIN("[[" , builtin_test, "Test condition"), +#if ENABLE_HUSH_JOB + BLTIN("bg" , builtin_fg_bg, "Resume a job in the background"), +#endif +#if ENABLE_HUSH_LOOPS + BLTIN("break" , builtin_break, "Exit from a loop"), +#endif + BLTIN("cd" , builtin_cd, "Change directory"), +#if ENABLE_HUSH_LOOPS + BLTIN("continue", builtin_continue, "Start new loop iteration"), +#endif + BLTIN("echo" , builtin_echo, "Write to stdout"), + BLTIN("eval" , builtin_eval, "Construct and run shell command"), + BLTIN("exec" , builtin_exec, "Execute command, don't return to shell"), + BLTIN("exit" , builtin_exit, "Exit"), + BLTIN("export", builtin_export, "Set environment variable"), +#if ENABLE_HUSH_JOB + BLTIN("fg" , builtin_fg_bg, "Bring job into the foreground"), + BLTIN("jobs" , builtin_jobs, "List active jobs"), +#endif + BLTIN("pwd" , builtin_pwd, "Print current directory"), + BLTIN("read" , builtin_read, "Input environment variable"), +// BLTIN("return", builtin_not_written, "Return from a function"), + BLTIN("set" , builtin_set, "Set/unset shell local variables"), + BLTIN("shift" , builtin_shift, "Shift positional parameters"), +// BLTIN("trap" , builtin_not_written, "Trap signals"), + BLTIN("test" , builtin_test, "Test condition"), +// BLTIN("ulimit", builtin_not_written, "Control resource limits"), + BLTIN("umask" , builtin_umask, "Set file creation mask"), + BLTIN("unset" , builtin_unset, "Unset environment variable"), +#if ENABLE_HUSH_HELP + BLTIN("help" , builtin_help, "List shell built-in commands"), +#endif +}; + + +/* Signals are grouped, we handle them in batches */ +static void set_misc_sighandler(void (*handler)(int)) +{ + bb_signals(0 + + (1 << SIGINT) + + (1 << SIGQUIT) + + (1 << SIGTERM) + , handler); +} + +#if ENABLE_HUSH_JOB + +static void set_fatal_sighandler(void (*handler)(int)) +{ + bb_signals(0 + + (1 << SIGILL) + + (1 << SIGTRAP) + + (1 << SIGABRT) + + (1 << SIGFPE) + + (1 << SIGBUS) + + (1 << SIGSEGV) + /* bash 3.2 seems to handle these just like 'fatal' ones */ + + (1 << SIGHUP) + + (1 << SIGPIPE) + + (1 << SIGALRM) + , handler); +} +static void set_jobctrl_sighandler(void (*handler)(int)) +{ + bb_signals(0 + + (1 << SIGTSTP) + + (1 << SIGTTIN) + + (1 << SIGTTOU) + , handler); +} +/* SIGCHLD is special and handled separately */ + +static void set_every_sighandler(void (*handler)(int)) +{ + set_fatal_sighandler(handler); + set_jobctrl_sighandler(handler); + set_misc_sighandler(handler); + signal(SIGCHLD, handler); +} + +static void handler_ctrl_c(int sig UNUSED_PARAM) +{ + debug_printf_jobs("got sig %d\n", sig); +// as usual we can have all kinds of nasty problems with leaked malloc data here + siglongjmp(G.toplevel_jb, 1); +} + +static void handler_ctrl_z(int sig UNUSED_PARAM) +{ + pid_t pid; + + debug_printf_jobs("got tty sig %d in pid %d\n", sig, getpid()); + pid = fork(); + if (pid < 0) /* can't fork. Pretend there was no ctrl-Z */ + return; + G.ctrl_z_flag = 1; + if (!pid) { /* child */ + if (ENABLE_HUSH_JOB) + die_sleep = 0; /* let nofork's xfuncs die */ + bb_setpgrp(); + debug_printf_jobs("set pgrp for child %d ok\n", getpid()); + set_every_sighandler(SIG_DFL); + raise(SIGTSTP); /* resend TSTP so that child will be stopped */ + debug_printf_jobs("returning in child\n"); + /* return to nofork, it will eventually exit now, + * not return back to shell */ + return; + } + /* parent */ + /* finish filling up pipe info */ + G.toplevel_list->pgrp = pid; /* child is in its own pgrp */ + G.toplevel_list->cmds[0].pid = pid; + /* parent needs to longjmp out of running nofork. + * we will "return" exitcode 0, with child put in background */ +// as usual we can have all kinds of nasty problems with leaked malloc data here + debug_printf_jobs("siglongjmp in parent\n"); + siglongjmp(G.toplevel_jb, 1); +} + +/* Restores tty foreground process group, and exits. + * May be called as signal handler for fatal signal + * (will faithfully resend signal to itself, producing correct exit state) + * or called directly with -EXITCODE. + * We also call it if xfunc is exiting. */ +static void sigexit(int sig) NORETURN; +static void sigexit(int sig) +{ + /* Disable all signals: job control, SIGPIPE, etc. */ + sigprocmask_allsigs(SIG_BLOCK); + +#if ENABLE_HUSH_INTERACTIVE + if (G.interactive_fd) + tcsetpgrp(G.interactive_fd, G.saved_tty_pgrp); +#endif + + /* Not a signal, just exit */ + if (sig <= 0) + _exit(- sig); + + kill_myself_with_sig(sig); /* does not return */ +} + +/* Restores tty foreground process group, and exits. */ +static void hush_exit(int exitcode) NORETURN; +static void hush_exit(int exitcode) +{ + fflush(NULL); /* flush all streams */ + sigexit(- (exitcode & 0xff)); +} + +#else /* !JOB */ + +#define set_fatal_sighandler(handler) ((void)0) +#define set_jobctrl_sighandler(handler) ((void)0) +#define hush_exit(e) exit(e) + +#endif /* JOB */ + + +static const char *set_cwd(void) +{ + if (G.cwd == bb_msg_unknown) + G.cwd = NULL; /* xrealloc_getcwd_or_warn(arg) calls free(arg)! */ + G.cwd = xrealloc_getcwd_or_warn((char *)G.cwd); + if (!G.cwd) + G.cwd = bb_msg_unknown; + return G.cwd; +} + + +/* + * o_string support + */ +#define B_CHUNK (32 * sizeof(char*)) + +static void o_reset(o_string *o) +{ + o->length = 0; + o->nonnull = 0; + if (o->data) + o->data[0] = '\0'; +} + +static void o_free(o_string *o) +{ + free(o->data); + memset(o, 0, sizeof(*o)); +} + +static void o_grow_by(o_string *o, int len) +{ + if (o->length + len > o->maxlen) { + o->maxlen += (2*len > B_CHUNK ? 2*len : B_CHUNK); + o->data = xrealloc(o->data, 1 + o->maxlen); + } +} + +static void o_addchr(o_string *o, int ch) +{ + debug_printf("o_addchr: '%c' o->length=%d o=%p\n", ch, o->length, o); + o_grow_by(o, 1); + o->data[o->length] = ch; + o->length++; + o->data[o->length] = '\0'; +} + +static void o_addstr(o_string *o, const char *str, int len) +{ + o_grow_by(o, len); + memcpy(&o->data[o->length], str, len); + o->length += len; + o->data[o->length] = '\0'; +} + +static void o_addstr_duplicate_backslash(o_string *o, const char *str, int len) +{ + while (len) { + o_addchr(o, *str); + if (*str++ == '\\' + && (*str != '*' && *str != '?' && *str != '[') + ) { + o_addchr(o, '\\'); + } + len--; + } +} + +/* My analysis of quoting semantics tells me that state information + * is associated with a destination, not a source. + */ +static void o_addqchr(o_string *o, int ch) +{ + int sz = 1; + char *found = strchr("*?[\\", ch); + if (found) + sz++; + o_grow_by(o, sz); + if (found) { + o->data[o->length] = '\\'; + o->length++; + } + o->data[o->length] = ch; + o->length++; + o->data[o->length] = '\0'; +} + +static void o_addQchr(o_string *o, int ch) +{ + int sz = 1; + if (o->o_quote && strchr("*?[\\", ch)) { + sz++; + o->data[o->length] = '\\'; + o->length++; + } + o_grow_by(o, sz); + o->data[o->length] = ch; + o->length++; + o->data[o->length] = '\0'; +} + +static void o_addQstr(o_string *o, const char *str, int len) +{ + if (!o->o_quote) { + o_addstr(o, str, len); + return; + } + while (len) { + char ch; + int sz; + int ordinary_cnt = strcspn(str, "*?[\\"); + if (ordinary_cnt > len) /* paranoia */ + ordinary_cnt = len; + o_addstr(o, str, ordinary_cnt); + if (ordinary_cnt == len) + return; + str += ordinary_cnt; + len -= ordinary_cnt + 1; /* we are processing + 1 char below */ + + ch = *str++; + sz = 1; + if (ch) { /* it is necessarily one of "*?[\\" */ + sz++; + o->data[o->length] = '\\'; + o->length++; + } + o_grow_by(o, sz); + o->data[o->length] = ch; + o->length++; + o->data[o->length] = '\0'; + } +} + +/* A special kind of o_string for $VAR and `cmd` expansion. + * It contains char* list[] at the beginning, which is grown in 16 element + * increments. Actual string data starts at the next multiple of 16 * (char*). + * list[i] contains an INDEX (int!) into this string data. + * It means that if list[] needs to grow, data needs to be moved higher up + * but list[i]'s need not be modified. + * NB: remembering how many list[i]'s you have there is crucial. + * o_finalize_list() operation post-processes this structure - calculates + * and stores actual char* ptrs in list[]. Oh, it NULL terminates it as well. + */ +#if DEBUG_EXPAND || DEBUG_GLOB +static void debug_print_list(const char *prefix, o_string *o, int n) +{ + char **list = (char**)o->data; + int string_start = ((n + 0xf) & ~0xf) * sizeof(list[0]); + int i = 0; + fprintf(stderr, "%s: list:%p n:%d string_start:%d length:%d maxlen:%d\n", + prefix, list, n, string_start, o->length, o->maxlen); + while (i < n) { + fprintf(stderr, " list[%d]=%d '%s' %p\n", i, (int)list[i], + o->data + (int)list[i] + string_start, + o->data + (int)list[i] + string_start); + i++; + } + if (n) { + const char *p = o->data + (int)list[n - 1] + string_start; + fprintf(stderr, " total_sz:%d\n", (p + strlen(p) + 1) - o->data); + } +} +#else +#define debug_print_list(prefix, o, n) ((void)0) +#endif + +/* n = o_save_ptr_helper(str, n) "starts new string" by storing an index value + * in list[n] so that it points past last stored byte so far. + * It returns n+1. */ +static int o_save_ptr_helper(o_string *o, int n) +{ + char **list = (char**)o->data; + int string_start; + int string_len; + + if (!o->has_empty_slot) { + string_start = ((n + 0xf) & ~0xf) * sizeof(list[0]); + string_len = o->length - string_start; + if (!(n & 0xf)) { /* 0, 0x10, 0x20...? */ + debug_printf_list("list[%d]=%d string_start=%d (growing)\n", n, string_len, string_start); + /* list[n] points to string_start, make space for 16 more pointers */ + o->maxlen += 0x10 * sizeof(list[0]); + o->data = xrealloc(o->data, o->maxlen + 1); + list = (char**)o->data; + memmove(list + n + 0x10, list + n, string_len); + o->length += 0x10 * sizeof(list[0]); + } else + debug_printf_list("list[%d]=%d string_start=%d\n", n, string_len, string_start); + } else { + /* We have empty slot at list[n], reuse without growth */ + string_start = ((n+1 + 0xf) & ~0xf) * sizeof(list[0]); /* NB: n+1! */ + string_len = o->length - string_start; + debug_printf_list("list[%d]=%d string_start=%d (empty slot)\n", n, string_len, string_start); + o->has_empty_slot = 0; + } + list[n] = (char*)(ptrdiff_t)string_len; + return n + 1; +} + +/* "What was our last o_save_ptr'ed position (byte offset relative o->data)?" */ +static int o_get_last_ptr(o_string *o, int n) +{ + char **list = (char**)o->data; + int string_start = ((n + 0xf) & ~0xf) * sizeof(list[0]); + + return ((int)(ptrdiff_t)list[n-1]) + string_start; +} + +/* o_glob performs globbing on last list[], saving each result + * as a new list[]. */ +static int o_glob(o_string *o, int n) +{ + glob_t globdata; + int gr; + char *pattern; + + debug_printf_glob("start o_glob: n:%d o->data:%p\n", n, o->data); + if (!o->data) + return o_save_ptr_helper(o, n); + pattern = o->data + o_get_last_ptr(o, n); + debug_printf_glob("glob pattern '%s'\n", pattern); + if (!glob_needed(pattern)) { + literal: + o->length = unbackslash(pattern) - o->data; + debug_printf_glob("glob pattern '%s' is literal\n", pattern); + return o_save_ptr_helper(o, n); + } + + memset(&globdata, 0, sizeof(globdata)); + gr = glob(pattern, 0, NULL, &globdata); + debug_printf_glob("glob('%s'):%d\n", pattern, gr); + if (gr == GLOB_NOSPACE) + bb_error_msg_and_die("out of memory during glob"); + if (gr == GLOB_NOMATCH) { + globfree(&globdata); + goto literal; + } + if (gr != 0) { /* GLOB_ABORTED ? */ +//TODO: testcase for bad glob pattern behavior + bb_error_msg("glob(3) error %d on '%s'", gr, pattern); + } + if (globdata.gl_pathv && globdata.gl_pathv[0]) { + char **argv = globdata.gl_pathv; + o->length = pattern - o->data; /* "forget" pattern */ + while (1) { + o_addstr(o, *argv, strlen(*argv) + 1); + n = o_save_ptr_helper(o, n); + argv++; + if (!*argv) + break; + } + } + globfree(&globdata); + if (DEBUG_GLOB) + debug_print_list("o_glob returning", o, n); + return n; +} + +/* If o->o_glob == 1, glob the string so far remembered. + * Otherwise, just finish current list[] and start new */ +static int o_save_ptr(o_string *o, int n) +{ + if (o->o_glob) + return o_glob(o, n); /* o_save_ptr_helper is inside */ + return o_save_ptr_helper(o, n); +} + +/* "Please convert list[n] to real char* ptrs, and NULL terminate it." */ +static char **o_finalize_list(o_string *o, int n) +{ + char **list; + int string_start; + + n = o_save_ptr(o, n); /* force growth for list[n] if necessary */ + if (DEBUG_EXPAND) + debug_print_list("finalized", o, n); + debug_printf_expand("finalized n:%d\n", n); + list = (char**)o->data; + string_start = ((n + 0xf) & ~0xf) * sizeof(list[0]); + list[--n] = NULL; + while (n) { + n--; + list[n] = o->data + (int)(ptrdiff_t)list[n] + string_start; + } + return list; +} + + +/* + * in_str support + */ +static int static_get(struct in_str *i) +{ + int ch = *i->p++; + if (ch == '\0') return EOF; + return ch; +} + +static int static_peek(struct in_str *i) +{ + return *i->p; +} + +#if ENABLE_HUSH_INTERACTIVE + +#if ENABLE_FEATURE_EDITING +static void cmdedit_set_initial_prompt(void) +{ +#if !ENABLE_FEATURE_EDITING_FANCY_PROMPT + G.PS1 = NULL; +#else + G.PS1 = getenv("PS1"); + if (G.PS1 == NULL) + G.PS1 = "\\w \\$ "; +#endif +} +#endif /* EDITING */ + +static const char* setup_prompt_string(int promptmode) +{ + const char *prompt_str; + debug_printf("setup_prompt_string %d ", promptmode); +#if !ENABLE_FEATURE_EDITING_FANCY_PROMPT + /* Set up the prompt */ + if (promptmode == 0) { /* PS1 */ + free((char*)G.PS1); + G.PS1 = xasprintf("%s %c ", G.cwd, (geteuid() != 0) ? '$' : '#'); + prompt_str = G.PS1; + } else { + prompt_str = G.PS2; + } +#else + prompt_str = (promptmode == 0) ? G.PS1 : G.PS2; +#endif + debug_printf("result '%s'\n", prompt_str); + return prompt_str; +} + +static void get_user_input(struct in_str *i) +{ + int r; + const char *prompt_str; + + prompt_str = setup_prompt_string(i->promptmode); +#if ENABLE_FEATURE_EDITING + /* Enable command line editing only while a command line + * is actually being read */ + do { + r = read_line_input(prompt_str, G.user_input_buf, BUFSIZ-1, G.line_input_state); + } while (r == 0); /* repeat if Ctrl-C */ + i->eof_flag = (r < 0); + if (i->eof_flag) { /* EOF/error detected */ + G.user_input_buf[0] = EOF; /* yes, it will be truncated, it's ok */ + G.user_input_buf[1] = '\0'; + } +#else + fputs(prompt_str, stdout); + fflush(stdout); + G.user_input_buf[0] = r = fgetc(i->file); + /*G.user_input_buf[1] = '\0'; - already is and never changed */ + i->eof_flag = (r == EOF); +#endif + i->p = G.user_input_buf; +} + +#endif /* INTERACTIVE */ + +/* This is the magic location that prints prompts + * and gets data back from the user */ +static int file_get(struct in_str *i) +{ + int ch; + + /* If there is data waiting, eat it up */ + if (i->p && *i->p) { +#if ENABLE_HUSH_INTERACTIVE + take_cached: +#endif + ch = *i->p++; + if (i->eof_flag && !*i->p) + ch = EOF; + } else { + /* need to double check i->file because we might be doing something + * more complicated by now, like sourcing or substituting. */ +#if ENABLE_HUSH_INTERACTIVE + if (G.interactive_fd && i->promptme && i->file == stdin) { + do { + get_user_input(i); + } while (!*i->p); /* need non-empty line */ + i->promptmode = 1; /* PS2 */ + i->promptme = 0; + goto take_cached; + } +#endif + ch = fgetc(i->file); + } + debug_printf("file_get: got a '%c' %d\n", ch, ch); +#if ENABLE_HUSH_INTERACTIVE + if (ch == '\n') + i->promptme = 1; +#endif + return ch; +} + +/* All the callers guarantee this routine will never be + * used right after a newline, so prompting is not needed. + */ +static int file_peek(struct in_str *i) +{ + int ch; + if (i->p && *i->p) { + if (i->eof_flag && !i->p[1]) + return EOF; + return *i->p; + } + ch = fgetc(i->file); + i->eof_flag = (ch == EOF); + i->peek_buf[0] = ch; + i->peek_buf[1] = '\0'; + i->p = i->peek_buf; + debug_printf("file_peek: got a '%c' %d\n", *i->p, *i->p); + return ch; +} + +static void setup_file_in_str(struct in_str *i, FILE *f) +{ + i->peek = file_peek; + i->get = file_get; +#if ENABLE_HUSH_INTERACTIVE + i->promptme = 1; + i->promptmode = 0; /* PS1 */ +#endif + i->file = f; + i->p = NULL; +} + +static void setup_string_in_str(struct in_str *i, const char *s) +{ + i->peek = static_peek; + i->get = static_get; +#if ENABLE_HUSH_INTERACTIVE + i->promptme = 1; + i->promptmode = 0; /* PS1 */ +#endif + i->p = s; + i->eof_flag = 0; +} + + +/* squirrel != NULL means we squirrel away copies of stdin, stdout, + * and stderr if they are redirected. */ +static int setup_redirects(struct command *prog, int squirrel[]) +{ + int openfd, mode; + struct redir_struct *redir; + + for (redir = prog->redirects; redir; redir = redir->next) { + if (redir->dup == -1 && redir->rd_filename == NULL) { + /* something went wrong in the parse. Pretend it didn't happen */ + continue; + } + if (redir->dup == -1) { + char *p; + mode = redir_table[redir->rd_type].mode; +//TODO: check redir for names like '\\' + p = expand_string_to_string(redir->rd_filename); + openfd = open_or_warn(p, mode); + free(p); + if (openfd < 0) { + /* this could get lost if stderr has been redirected, but + bash and ash both lose it as well (though zsh doesn't!) */ + return 1; + } + } else { + openfd = redir->dup; + } + + if (openfd != redir->fd) { + if (squirrel && redir->fd < 3) { + squirrel[redir->fd] = dup(redir->fd); + } + if (openfd == -3) { + //close(openfd); // close(-3) ??! + } else { + dup2(openfd, redir->fd); + if (redir->dup == -1) + close(openfd); + } + } + } + return 0; +} + +static void restore_redirects(int squirrel[]) +{ + int i, fd; + for (i = 0; i < 3; i++) { + fd = squirrel[i]; + if (fd != -1) { + /* We simply die on error */ + xmove_fd(fd, i); + } + } +} + +static char **expand_assignments(char **argv, int count) +{ + int i; + char **p = NULL; + /* Expand assignments into one string each */ + for (i = 0; i < count; i++) { + p = add_string_to_strings(p, expand_string_to_string(argv[i])); + } + return p; +} + +/* Called after [v]fork() in run_pipe(), or from builtin_exec(). + * Never returns. + * XXX no exit() here. If you don't exec, use _exit instead. + * The at_exit handlers apparently confuse the calling process, + * in particular stdin handling. Not sure why? -- because of vfork! (vda) */ +static void pseudo_exec_argv(nommu_save_t *nommu_save, char **argv, int assignment_cnt, char **argv_expanded) +{ + int rcode; + char **new_env; + const struct built_in_command *x; + + /* If a variable is assigned in a forest, and nobody listens, + * was it ever really set? + */ + if (!argv[assignment_cnt]) + _exit(EXIT_SUCCESS); + + new_env = expand_assignments(argv, assignment_cnt); +#if BB_MMU + putenv_all(new_env); + free(new_env); /* optional */ +#else + nommu_save->new_env = new_env; + nommu_save->old_env = putenv_all_and_save_old(new_env); +#endif + if (argv_expanded) { + argv = argv_expanded; + } else { + argv = expand_strvec_to_strvec(argv); +#if !BB_MMU + nommu_save->argv = argv; +#endif + } + + /* + * Check if the command matches any of the builtins. + * Depending on context, this might be redundant. But it's + * easier to waste a few CPU cycles than it is to figure out + * if this is one of those cases. + */ + for (x = bltins; x != &bltins[ARRAY_SIZE(bltins)]; x++) { + if (strcmp(argv[0], x->cmd) == 0) { + debug_printf_exec("running builtin '%s'\n", argv[0]); + rcode = x->function(argv); + fflush(stdout); + _exit(rcode); + } + } + + /* Check if the command matches any busybox applets */ +#if ENABLE_FEATURE_SH_STANDALONE + if (strchr(argv[0], '/') == NULL) { + int a = find_applet_by_name(argv[0]); + if (a >= 0) { + if (APPLET_IS_NOEXEC(a)) { + debug_printf_exec("running applet '%s'\n", argv[0]); +// is it ok that run_applet_no_and_exit() does exit(), not _exit()? + run_applet_no_and_exit(a, argv); + } + /* re-exec ourselves with the new arguments */ + debug_printf_exec("re-execing applet '%s'\n", argv[0]); + execvp(bb_busybox_exec_path, argv); + /* If they called chroot or otherwise made the binary no longer + * executable, fall through */ + } + } +#endif + + debug_printf_exec("execing '%s'\n", argv[0]); + execvp(argv[0], argv); + bb_perror_msg("can't exec '%s'", argv[0]); + _exit(EXIT_FAILURE); +} + +/* Called after [v]fork() in run_pipe() + */ +static void pseudo_exec(nommu_save_t *nommu_save, struct command *command, char **argv_expanded) +{ + if (command->argv) + pseudo_exec_argv(nommu_save, command->argv, command->assignment_cnt, argv_expanded); + + if (command->group) { +#if !BB_MMU + bb_error_msg_and_die("nested lists are not supported on NOMMU"); +#else + int rcode; + debug_printf_exec("pseudo_exec: run_list\n"); + rcode = run_list(command->group); + /* OK to leak memory by not calling free_pipe_list, + * since this process is about to exit */ + _exit(rcode); +#endif + } + + /* Can happen. See what bash does with ">foo" by itself. */ + debug_printf("trying to pseudo_exec null command\n"); + _exit(EXIT_SUCCESS); +} + +#if ENABLE_HUSH_JOB +static const char *get_cmdtext(struct pipe *pi) +{ + char **argv; + char *p; + int len; + + /* This is subtle. ->cmdtext is created only on first backgrounding. + * (Think "cat, , fg, , fg, ...." here...) + * On subsequent bg argv is trashed, but we won't use it */ + if (pi->cmdtext) + return pi->cmdtext; + argv = pi->cmds[0].argv; + if (!argv || !argv[0]) { + pi->cmdtext = xzalloc(1); + return pi->cmdtext; + } + + len = 0; + do len += strlen(*argv) + 1; while (*++argv); + pi->cmdtext = p = xmalloc(len); + argv = pi->cmds[0].argv; + do { + len = strlen(*argv); + memcpy(p, *argv, len); + p += len; + *p++ = ' '; + } while (*++argv); + p[-1] = '\0'; + return pi->cmdtext; +} + +static void insert_bg_job(struct pipe *pi) +{ + struct pipe *thejob; + int i; + + /* Linear search for the ID of the job to use */ + pi->jobid = 1; + for (thejob = G.job_list; thejob; thejob = thejob->next) + if (thejob->jobid >= pi->jobid) + pi->jobid = thejob->jobid + 1; + + /* Add thejob to the list of running jobs */ + if (!G.job_list) { + thejob = G.job_list = xmalloc(sizeof(*thejob)); + } else { + for (thejob = G.job_list; thejob->next; thejob = thejob->next) + continue; + thejob->next = xmalloc(sizeof(*thejob)); + thejob = thejob->next; + } + + /* Physically copy the struct job */ + memcpy(thejob, pi, sizeof(struct pipe)); + thejob->cmds = xzalloc(sizeof(pi->cmds[0]) * pi->num_cmds); + /* We cannot copy entire pi->cmds[] vector! Double free()s will happen */ + for (i = 0; i < pi->num_cmds; i++) { +// TODO: do we really need to have so many fields which are just dead weight +// at execution stage? + thejob->cmds[i].pid = pi->cmds[i].pid; + /* all other fields are not used and stay zero */ + } + thejob->next = NULL; + thejob->cmdtext = xstrdup(get_cmdtext(pi)); + + /* We don't wait for background thejobs to return -- append it + to the list of backgrounded thejobs and leave it alone */ + printf("[%d] %d %s\n", thejob->jobid, thejob->cmds[0].pid, thejob->cmdtext); + G.last_bg_pid = thejob->cmds[0].pid; + G.last_jobid = thejob->jobid; +} + +static void remove_bg_job(struct pipe *pi) +{ + struct pipe *prev_pipe; + + if (pi == G.job_list) { + G.job_list = pi->next; + } else { + prev_pipe = G.job_list; + while (prev_pipe->next != pi) + prev_pipe = prev_pipe->next; + prev_pipe->next = pi->next; + } + if (G.job_list) + G.last_jobid = G.job_list->jobid; + else + G.last_jobid = 0; +} + +/* Remove a backgrounded job */ +static void delete_finished_bg_job(struct pipe *pi) +{ + remove_bg_job(pi); + pi->stopped_cmds = 0; + free_pipe(pi, 0); + free(pi); +} +#endif /* JOB */ + +/* Check to see if any processes have exited -- if they + * have, figure out why and see if a job has completed */ +static int checkjobs(struct pipe* fg_pipe) +{ + int attributes; + int status; +#if ENABLE_HUSH_JOB + struct pipe *pi; +#endif + pid_t childpid; + int rcode = 0; + + attributes = WUNTRACED; + if (fg_pipe == NULL) + attributes |= WNOHANG; + +/* Do we do this right? + * bash-3.00# sleep 20 | false + * + * [3]+ Stopped sleep 20 | false + * bash-3.00# echo $? + * 1 <========== bg pipe is not fully done, but exitcode is already known! + */ + +//FIXME: non-interactive bash does not continue even if all processes in fg pipe +//are stopped. Testcase: "cat | cat" in a script (not on command line) +// + killall -STOP cat + + wait_more: +// TODO: safe_waitpid? + while ((childpid = waitpid(-1, &status, attributes)) > 0) { + int i; + const int dead = WIFEXITED(status) || WIFSIGNALED(status); +#if DEBUG_JOBS + if (WIFSTOPPED(status)) + debug_printf_jobs("pid %d stopped by sig %d (exitcode %d)\n", + childpid, WSTOPSIG(status), WEXITSTATUS(status)); + if (WIFSIGNALED(status)) + debug_printf_jobs("pid %d killed by sig %d (exitcode %d)\n", + childpid, WTERMSIG(status), WEXITSTATUS(status)); + if (WIFEXITED(status)) + debug_printf_jobs("pid %d exited, exitcode %d\n", + childpid, WEXITSTATUS(status)); +#endif + /* Were we asked to wait for fg pipe? */ + if (fg_pipe) { + for (i = 0; i < fg_pipe->num_cmds; i++) { + debug_printf_jobs("check pid %d\n", fg_pipe->cmds[i].pid); + if (fg_pipe->cmds[i].pid != childpid) + continue; + /* printf("process %d exit %d\n", i, WEXITSTATUS(status)); */ + if (dead) { + fg_pipe->cmds[i].pid = 0; + fg_pipe->alive_cmds--; + if (i == fg_pipe->num_cmds - 1) { + /* last process gives overall exitstatus */ + rcode = WEXITSTATUS(status); + IF_HAS_KEYWORDS(if (fg_pipe->pi_inverted) rcode = !rcode;) + } + } else { + fg_pipe->cmds[i].is_stopped = 1; + fg_pipe->stopped_cmds++; + } + debug_printf_jobs("fg_pipe: alive_cmds %d stopped_cmds %d\n", + fg_pipe->alive_cmds, fg_pipe->stopped_cmds); + if (fg_pipe->alive_cmds - fg_pipe->stopped_cmds <= 0) { + /* All processes in fg pipe have exited/stopped */ +#if ENABLE_HUSH_JOB + if (fg_pipe->alive_cmds) + insert_bg_job(fg_pipe); +#endif + return rcode; + } + /* There are still running processes in the fg pipe */ + goto wait_more; /* do waitpid again */ + } + /* it wasnt fg_pipe, look for process in bg pipes */ + } + +#if ENABLE_HUSH_JOB + /* We asked to wait for bg or orphaned children */ + /* No need to remember exitcode in this case */ + for (pi = G.job_list; pi; pi = pi->next) { + for (i = 0; i < pi->num_cmds; i++) { + if (pi->cmds[i].pid == childpid) + goto found_pi_and_prognum; + } + } + /* Happens when shell is used as init process (init=/bin/sh) */ + debug_printf("checkjobs: pid %d was not in our list!\n", childpid); + continue; /* do waitpid again */ + + found_pi_and_prognum: + if (dead) { + /* child exited */ + pi->cmds[i].pid = 0; + pi->alive_cmds--; + if (!pi->alive_cmds) { + printf(JOB_STATUS_FORMAT, pi->jobid, + "Done", pi->cmdtext); + delete_finished_bg_job(pi); + } + } else { + /* child stopped */ + pi->cmds[i].is_stopped = 1; + pi->stopped_cmds++; + } +#endif + } /* while (waitpid succeeds)... */ + + /* wait found no children or failed */ + + if (childpid && errno != ECHILD) + bb_perror_msg("waitpid"); + return rcode; +} + +#if ENABLE_HUSH_JOB +static int checkjobs_and_fg_shell(struct pipe* fg_pipe) +{ + pid_t p; + int rcode = checkjobs(fg_pipe); + /* Job finished, move the shell to the foreground */ + p = getpgid(0); /* pgid of our process */ + debug_printf_jobs("fg'ing ourself: getpgid(0)=%d\n", (int)p); + tcsetpgrp(G.interactive_fd, p); + return rcode; +} +#endif + +/* run_pipe() starts all the jobs, but doesn't wait for anything + * to finish. See checkjobs(). + * + * return code is normally -1, when the caller has to wait for children + * to finish to determine the exit status of the pipe. If the pipe + * is a simple builtin command, however, the action is done by the + * time run_pipe returns, and the exit code is provided as the + * return value. + * + * The input of the pipe is always stdin, the output is always + * stdout. The outpipe[] mechanism in BusyBox-0.48 lash is bogus, + * because it tries to avoid running the command substitution in + * subshell, when that is in fact necessary. The subshell process + * now has its stdout directed to the input of the appropriate pipe, + * so this routine is noticeably simpler. + * + * Returns -1 only if started some children. IOW: we have to + * mask out retvals of builtins etc with 0xff! + */ +static int run_pipe(struct pipe *pi) +{ + int i; + int nextin; + int pipefds[2]; /* pipefds[0] is for reading */ + struct command *command; + char **argv_expanded; + char **argv; + const struct built_in_command *x; + char *p; + /* it is not always needed, but we aim to smaller code */ + int squirrel[] = { -1, -1, -1 }; + int rcode; + const int single_and_fg = (pi->num_cmds == 1 && pi->followup != PIPE_BG); + + debug_printf_exec("run_pipe start: single_and_fg=%d\n", single_and_fg); + +#if ENABLE_HUSH_JOB + pi->pgrp = -1; +#endif + pi->alive_cmds = 1; + pi->stopped_cmds = 0; + + /* Check if this is a simple builtin (not part of a pipe). + * Builtins within pipes have to fork anyway, and are handled in + * pseudo_exec. "echo foo | read bar" doesn't work on bash, either. + */ + command = &(pi->cmds[0]); + +#if ENABLE_HUSH_FUNCTIONS + if (single_and_fg && command->group && command->grp_type == GRP_FUNCTION) { + /* We "execute" function definition */ + bb_error_msg("here we ought to remember function definition, and go on"); + return EXIT_SUCCESS; + } +#endif + + if (single_and_fg && command->group && command->grp_type == GRP_NORMAL) { + debug_printf("non-subshell grouping\n"); + setup_redirects(command, squirrel); + debug_printf_exec(": run_list\n"); + rcode = run_list(command->group) & 0xff; + restore_redirects(squirrel); + debug_printf_exec("run_pipe return %d\n", rcode); + IF_HAS_KEYWORDS(if (pi->pi_inverted) rcode = !rcode;) + return rcode; + } + + argv = command->argv; + argv_expanded = NULL; + + if (single_and_fg && argv != NULL) { + char **new_env = NULL; + char **old_env = NULL; + + i = command->assignment_cnt; + if (i != 0 && argv[i] == NULL) { + /* assignments, but no command: set local environment */ + for (i = 0; argv[i] != NULL; i++) { + debug_printf("local environment set: %s\n", argv[i]); + p = expand_string_to_string(argv[i]); + set_local_var(p, 0); + } + return EXIT_SUCCESS; /* don't worry about errors in set_local_var() yet */ + } + + /* Expand the rest into (possibly) many strings each */ + argv_expanded = expand_strvec_to_strvec(argv + i); + + for (x = bltins; x != &bltins[ARRAY_SIZE(bltins)]; x++) { + if (strcmp(argv_expanded[0], x->cmd) != 0) + continue; + if (x->function == builtin_exec && argv_expanded[1] == NULL) { + debug_printf("exec with redirects only\n"); + setup_redirects(command, NULL); + rcode = EXIT_SUCCESS; + goto clean_up_and_ret1; + } + debug_printf("builtin inline %s\n", argv_expanded[0]); + /* XXX setup_redirects acts on file descriptors, not FILEs. + * This is perfect for work that comes after exec(). + * Is it really safe for inline use? Experimentally, + * things seem to work with glibc. */ + setup_redirects(command, squirrel); + new_env = expand_assignments(argv, command->assignment_cnt); + old_env = putenv_all_and_save_old(new_env); + debug_printf_exec(": builtin '%s' '%s'...\n", x->cmd, argv_expanded[1]); + rcode = x->function(argv_expanded) & 0xff; +#if ENABLE_FEATURE_SH_STANDALONE + clean_up_and_ret: +#endif + restore_redirects(squirrel); + free_strings_and_unsetenv(new_env, 1); + putenv_all(old_env); + free(old_env); /* not free_strings()! */ + clean_up_and_ret1: + free(argv_expanded); + IF_HAS_KEYWORDS(if (pi->pi_inverted) rcode = !rcode;) + debug_printf_exec("run_pipe return %d\n", rcode); + return rcode; + } +#if ENABLE_FEATURE_SH_STANDALONE + i = find_applet_by_name(argv_expanded[0]); + if (i >= 0 && APPLET_IS_NOFORK(i)) { + setup_redirects(command, squirrel); + save_nofork_data(&G.nofork_save); + new_env = expand_assignments(argv, command->assignment_cnt); + old_env = putenv_all_and_save_old(new_env); + debug_printf_exec(": run_nofork_applet '%s' '%s'...\n", argv_expanded[0], argv_expanded[1]); + rcode = run_nofork_applet_prime(&G.nofork_save, i, argv_expanded); + goto clean_up_and_ret; + } +#endif + } + + /* NB: argv_expanded may already be created, and that + * might include `cmd` runs! Do not rerun it! We *must* + * use argv_expanded if it's non-NULL */ + + /* Disable job control signals for shell (parent) and + * for initial child code after fork */ + set_jobctrl_sighandler(SIG_IGN); + + /* Going to fork a child per each pipe member */ + pi->alive_cmds = 0; + nextin = 0; + + for (i = 0; i < pi->num_cmds; i++) { +#if !BB_MMU + volatile nommu_save_t nommu_save; + nommu_save.new_env = NULL; + nommu_save.old_env = NULL; + nommu_save.argv = NULL; +#endif + command = &(pi->cmds[i]); + if (command->argv) { + debug_printf_exec(": pipe member '%s' '%s'...\n", command->argv[0], command->argv[1]); + } else + debug_printf_exec(": pipe member with no argv\n"); + + /* pipes are inserted between pairs of commands */ + pipefds[0] = 0; + pipefds[1] = 1; + if ((i + 1) < pi->num_cmds) + xpipe(pipefds); + + command->pid = BB_MMU ? fork() : vfork(); + if (!command->pid) { /* child */ + if (ENABLE_HUSH_JOB) + die_sleep = 0; /* let nofork's xfuncs die */ +#if ENABLE_HUSH_JOB + /* Every child adds itself to new process group + * with pgid == pid_of_first_child_in_pipe */ + if (G.run_list_level == 1 && G.interactive_fd) { + pid_t pgrp; + /* Don't do pgrp restore anymore on fatal signals */ + set_fatal_sighandler(SIG_DFL); + pgrp = pi->pgrp; + if (pgrp < 0) /* true for 1st process only */ + pgrp = getpid(); + if (setpgid(0, pgrp) == 0 && pi->followup != PIPE_BG) { + /* We do it in *every* child, not just first, + * to avoid races */ + tcsetpgrp(G.interactive_fd, pgrp); + } + } +#endif + xmove_fd(nextin, 0); + xmove_fd(pipefds[1], 1); /* write end */ + if (pipefds[0] > 1) + close(pipefds[0]); /* read end */ + /* Like bash, explicit redirects override pipes, + * and the pipe fd is available for dup'ing. */ + setup_redirects(command, NULL); + + /* Restore default handlers just prior to exec */ + set_jobctrl_sighandler(SIG_DFL); + set_misc_sighandler(SIG_DFL); + signal(SIGCHLD, SIG_DFL); + /* Stores to nommu_save list of env vars putenv'ed + * (NOMMU, on MMU we don't need that) */ + /* cast away volatility... */ + pseudo_exec((nommu_save_t*) &nommu_save, command, argv_expanded); + /* pseudo_exec() does not return */ + } + /* parent */ +#if !BB_MMU + /* Clean up after vforked child */ + free(nommu_save.argv); + free_strings_and_unsetenv(nommu_save.new_env, 1); + putenv_all(nommu_save.old_env); +#endif + free(argv_expanded); + argv_expanded = NULL; + if (command->pid < 0) { /* [v]fork failed */ + /* Clearly indicate, was it fork or vfork */ + bb_perror_msg(BB_MMU ? "fork" : "vfork"); + } else { + pi->alive_cmds++; +#if ENABLE_HUSH_JOB + /* Second and next children need to know pid of first one */ + if (pi->pgrp < 0) + pi->pgrp = command->pid; +#endif + } + + if (i) + close(nextin); + if ((i + 1) < pi->num_cmds) + close(pipefds[1]); /* write end */ + /* Pass read (output) pipe end to next iteration */ + nextin = pipefds[0]; + } + + if (!pi->alive_cmds) { + debug_printf_exec("run_pipe return 1 (all forks failed, no children)\n"); + return 1; + } + + debug_printf_exec("run_pipe return -1 (%u children started)\n", pi->alive_cmds); + return -1; +} + +#ifndef debug_print_tree +static void debug_print_tree(struct pipe *pi, int lvl) +{ + static const char *const PIPE[] = { + [PIPE_SEQ] = "SEQ", + [PIPE_AND] = "AND", + [PIPE_OR ] = "OR" , + [PIPE_BG ] = "BG" , + }; + static const char *RES[] = { + [RES_NONE ] = "NONE" , +#if ENABLE_HUSH_IF + [RES_IF ] = "IF" , + [RES_THEN ] = "THEN" , + [RES_ELIF ] = "ELIF" , + [RES_ELSE ] = "ELSE" , + [RES_FI ] = "FI" , +#endif +#if ENABLE_HUSH_LOOPS + [RES_FOR ] = "FOR" , + [RES_WHILE] = "WHILE", + [RES_UNTIL] = "UNTIL", + [RES_DO ] = "DO" , + [RES_DONE ] = "DONE" , +#endif +#if ENABLE_HUSH_LOOPS || ENABLE_HUSH_CASE + [RES_IN ] = "IN" , +#endif +#if ENABLE_HUSH_CASE + [RES_CASE ] = "CASE" , + [RES_MATCH] = "MATCH", + [RES_CASEI] = "CASEI", + [RES_ESAC ] = "ESAC" , +#endif + [RES_XXXX ] = "XXXX" , + [RES_SNTX ] = "SNTX" , + }; + static const char *const GRPTYPE[] = { + "()", + "{}", +#if ENABLE_HUSH_FUNCTIONS + "func()", +#endif + }; + + int pin, prn; + + pin = 0; + while (pi) { + fprintf(stderr, "%*spipe %d res_word=%s followup=%d %s\n", lvl*2, "", + pin, RES[pi->res_word], pi->followup, PIPE[pi->followup]); + prn = 0; + while (prn < pi->num_cmds) { + struct command *command = &pi->cmds[prn]; + char **argv = command->argv; + + fprintf(stderr, "%*s prog %d assignment_cnt:%d", lvl*2, "", prn, command->assignment_cnt); + if (command->group) { + fprintf(stderr, " group %s: (argv=%p)\n", + GRPTYPE[command->grp_type], + argv); + debug_print_tree(command->group, lvl+1); + prn++; + continue; + } + if (argv) while (*argv) { + fprintf(stderr, " '%s'", *argv); + argv++; + } + fprintf(stderr, "\n"); + prn++; + } + pi = pi->next; + pin++; + } +} +#endif + +/* NB: called by pseudo_exec, and therefore must not modify any + * global data until exec/_exit (we can be a child after vfork!) */ +static int run_list(struct pipe *pi) +{ +#if ENABLE_HUSH_CASE + char *case_word = NULL; +#endif +#if ENABLE_HUSH_LOOPS + struct pipe *loop_top = NULL; + char *for_varname = NULL; + char **for_lcur = NULL; + char **for_list = NULL; +#endif + smallint flag_skip = 1; + smalluint rcode = 0; /* probably just for compiler */ +#if ENABLE_HUSH_IF || ENABLE_HUSH_CASE + smalluint cond_code = 0; +#else + enum { cond_code = 0, }; +#endif + /*enum reserved_style*/ smallint rword = RES_NONE; + /*enum reserved_style*/ smallint skip_more_for_this_rword = RES_XXXX; + + debug_printf_exec("run_list start lvl %d\n", G.run_list_level + 1); + +#if ENABLE_HUSH_LOOPS + /* Check syntax for "for" */ + for (struct pipe *cpipe = pi; cpipe; cpipe = cpipe->next) { + if (cpipe->res_word != RES_FOR && cpipe->res_word != RES_IN) + continue; + /* current word is FOR or IN (BOLD in comments below) */ + if (cpipe->next == NULL) { + syntax("malformed for"); + debug_printf_exec("run_list lvl %d return 1\n", G.run_list_level); + return 1; + } + /* "FOR v; do ..." and "for v IN a b; do..." are ok */ + if (cpipe->next->res_word == RES_DO) + continue; + /* next word is not "do". It must be "in" then ("FOR v in ...") */ + if (cpipe->res_word == RES_IN /* "for v IN a b; not_do..."? */ + || cpipe->next->res_word != RES_IN /* FOR v not_do_and_not_in..."? */ + ) { + syntax("malformed for"); + debug_printf_exec("run_list lvl %d return 1\n", G.run_list_level); + return 1; + } + } +#endif + + /* Past this point, all code paths should jump to ret: label + * in order to return, no direct "return" statements please. + * This helps to ensure that no memory is leaked. */ + +#if ENABLE_HUSH_JOB + /* Example of nested list: "while true; do { sleep 1 | exit 2; } done". + * We are saving state before entering outermost list ("while...done") + * so that ctrl-Z will correctly background _entire_ outermost list, + * not just a part of it (like "sleep 1 | exit 2") */ + if (++G.run_list_level == 1 && G.interactive_fd) { + if (sigsetjmp(G.toplevel_jb, 1)) { + /* ctrl-Z forked and we are parent; or ctrl-C. + * Sighandler has longjmped us here */ + signal(SIGINT, SIG_IGN); + signal(SIGTSTP, SIG_IGN); + /* Restore level (we can be coming from deep inside + * nested levels) */ + G.run_list_level = 1; +#if ENABLE_FEATURE_SH_STANDALONE + if (G.nofork_save.saved) { /* if save area is valid */ + debug_printf_jobs("exiting nofork early\n"); + restore_nofork_data(&G.nofork_save); + } +#endif + if (G.ctrl_z_flag) { + /* ctrl-Z has forked and stored pid of the child in pi->pid. + * Remember this child as background job */ + insert_bg_job(pi); + } else { + /* ctrl-C. We just stop doing whatever we were doing */ + bb_putchar('\n'); + } + USE_HUSH_LOOPS(loop_top = NULL;) + USE_HUSH_LOOPS(G.depth_of_loop = 0;) + rcode = 0; + goto ret; + } + /* ctrl-Z handler will store pid etc in pi */ + G.toplevel_list = pi; + G.ctrl_z_flag = 0; +#if ENABLE_FEATURE_SH_STANDALONE + G.nofork_save.saved = 0; /* in case we will run a nofork later */ +#endif + signal_SA_RESTART_empty_mask(SIGTSTP, handler_ctrl_z); + signal(SIGINT, handler_ctrl_c); + } +#endif /* JOB */ + + /* Go through list of pipes, (maybe) executing them. */ + for (; pi; pi = USE_HUSH_LOOPS(rword == RES_DONE ? loop_top : ) pi->next) { + IF_HAS_KEYWORDS(rword = pi->res_word;) + IF_HAS_NO_KEYWORDS(rword = RES_NONE;) + debug_printf_exec(": rword=%d cond_code=%d skip_more=%d\n", + rword, cond_code, skip_more_for_this_rword); +#if ENABLE_HUSH_LOOPS + if ((rword == RES_WHILE || rword == RES_UNTIL || rword == RES_FOR) + && loop_top == NULL /* avoid bumping G.depth_of_loop twice */ + ) { + /* start of a loop: remember where loop starts */ + loop_top = pi; + G.depth_of_loop++; + } +#endif + if (rword == skip_more_for_this_rword && flag_skip) { + if (pi->followup == PIPE_SEQ) + flag_skip = 0; + /* it is " && CMD" or " || CMD" + * and we should not execute CMD */ + continue; + } + flag_skip = 1; + skip_more_for_this_rword = RES_XXXX; +#if ENABLE_HUSH_IF + if (cond_code) { + if (rword == RES_THEN) { + /* "if THEN cmd": skip cmd */ + continue; + } + } else { + if (rword == RES_ELSE || rword == RES_ELIF) { + /* "if then ... ELSE/ELIF cmd": + * skip cmd and all following ones */ + break; + } + } +#endif +#if ENABLE_HUSH_LOOPS + if (rword == RES_FOR) { /* && pi->num_cmds - always == 1 */ + if (!for_lcur) { + /* first loop through for */ + + static const char encoded_dollar_at[] ALIGN1 = { + SPECIAL_VAR_SYMBOL, '@' | 0x80, SPECIAL_VAR_SYMBOL, '\0' + }; /* encoded representation of "$@" */ + static const char *const encoded_dollar_at_argv[] = { + encoded_dollar_at, NULL + }; /* argv list with one element: "$@" */ + char **vals; + + vals = (char**)encoded_dollar_at_argv; + if (pi->next->res_word == RES_IN) { + /* if no variable values after "in" we skip "for" */ + if (!pi->next->cmds[0].argv) + break; + vals = pi->next->cmds[0].argv; + } /* else: "for var; do..." -> assume "$@" list */ + /* create list of variable values */ + debug_print_strings("for_list made from", vals); + for_list = expand_strvec_to_strvec(vals); + for_lcur = for_list; + debug_print_strings("for_list", for_list); + for_varname = pi->cmds[0].argv[0]; + pi->cmds[0].argv[0] = NULL; + } + free(pi->cmds[0].argv[0]); + if (!*for_lcur) { + /* "for" loop is over, clean up */ + free(for_list); + for_list = NULL; + for_lcur = NULL; + pi->cmds[0].argv[0] = for_varname; + break; + } + /* insert next value from for_lcur */ +//TODO: does it need escaping? + pi->cmds[0].argv[0] = xasprintf("%s=%s", for_varname, *for_lcur++); + pi->cmds[0].assignment_cnt = 1; + } + if (rword == RES_IN) /* "for v IN list;..." - "in" has no cmds anyway */ + continue; + if (rword == RES_DONE) { + continue; /* "done" has no cmds too */ + } +#endif +#if ENABLE_HUSH_CASE + if (rword == RES_CASE) { + case_word = expand_strvec_to_string(pi->cmds->argv); + continue; + } + if (rword == RES_MATCH) { + char **argv; + + if (!case_word) /* "case ... matched_word) ... WORD)": we executed selected branch, stop */ + break; + /* all prev words didn't match, does this one match? */ + argv = pi->cmds->argv; + while (*argv) { + char *pattern = expand_string_to_string(*argv); + /* TODO: which FNM_xxx flags to use? */ + cond_code = (fnmatch(pattern, case_word, /*flags:*/ 0) != 0); + free(pattern); + if (cond_code == 0) { /* match! we will execute this branch */ + free(case_word); /* make future "word)" stop */ + case_word = NULL; + break; + } + argv++; + } + continue; + } + if (rword == RES_CASEI) { /* inside of a case branch */ + if (cond_code != 0) + continue; /* not matched yet, skip this pipe */ + } +#endif + if (pi->num_cmds == 0) + continue; + + /* After analyzing all keywords and conditions, we decided + * to execute this pipe. NB: has to do checkjobs(NULL) + * after run_pipe() to collect any background children, + * even if list execution is to be stopped. */ + debug_printf_exec(": run_pipe with %d members\n", pi->num_cmds); + { + int r; +#if ENABLE_HUSH_LOOPS + G.flag_break_continue = 0; +#endif + rcode = r = run_pipe(pi); /* NB: rcode is a smallint */ + if (r != -1) { + /* we only ran a builtin: rcode is already known + * and we don't need to wait for anything. */ +#if ENABLE_HUSH_LOOPS + /* was it "break" or "continue"? */ + if (G.flag_break_continue) { + smallint fbc = G.flag_break_continue; + /* we might fall into outer *loop*, + * don't want to break it too */ + if (loop_top) { + G.depth_break_continue--; + if (G.depth_break_continue == 0) + G.flag_break_continue = 0; + /* else: e.g. "continue 2" should *break* once, *then* continue */ + } /* else: "while... do... { we are here (innermost list is not a loop!) };...done" */ + if (G.depth_break_continue != 0 || fbc == BC_BREAK) + goto check_jobs_and_break; + /* "continue": simulate end of loop */ + rword = RES_DONE; + continue; + } +#endif + } else if (pi->followup == PIPE_BG) { + /* what does bash do with attempts to background builtins? */ + /* even bash 3.2 doesn't do that well with nested bg: + * try "{ { sleep 10; echo DEEP; } & echo HERE; } &". + * I'm NOT treating inner &'s as jobs */ +#if ENABLE_HUSH_JOB + if (G.run_list_level == 1) + insert_bg_job(pi); +#endif + rcode = 0; /* EXIT_SUCCESS */ + } else { +#if ENABLE_HUSH_JOB + if (G.run_list_level == 1 && G.interactive_fd) { + /* waits for completion, then fg's main shell */ + rcode = checkjobs_and_fg_shell(pi); + debug_printf_exec(": checkjobs_and_fg_shell returned %d\n", rcode); + } else +#endif + { /* this one just waits for completion */ + rcode = checkjobs(pi); + debug_printf_exec(": checkjobs returned %d\n", rcode); + } + } + } + debug_printf_exec(": setting last_return_code=%d\n", rcode); + G.last_return_code = rcode; + + /* Analyze how result affects subsequent commands */ +#if ENABLE_HUSH_IF + if (rword == RES_IF || rword == RES_ELIF) + cond_code = rcode; +#endif +#if ENABLE_HUSH_LOOPS + if (rword == RES_WHILE) { + if (rcode) { + rcode = 0; /* "while false; do...done" - exitcode 0 */ + goto check_jobs_and_break; + } + } + if (rword == RES_UNTIL) { + if (!rcode) { + check_jobs_and_break: + checkjobs(NULL); + break; + } + } +#endif + if ((rcode == 0 && pi->followup == PIPE_OR) + || (rcode != 0 && pi->followup == PIPE_AND) + ) { + skip_more_for_this_rword = rword; + } + checkjobs(NULL); + } /* for (pi) */ + +#if ENABLE_HUSH_JOB + if (G.ctrl_z_flag) { + /* ctrl-Z forked somewhere in the past, we are the child, + * and now we completed running the list. Exit. */ +//TODO: _exit? + exit(rcode); + } + ret: + if (!--G.run_list_level && G.interactive_fd) { + signal(SIGTSTP, SIG_IGN); + signal(SIGINT, SIG_IGN); + } +#endif + debug_printf_exec("run_list lvl %d return %d\n", G.run_list_level + 1, rcode); +#if ENABLE_HUSH_LOOPS + if (loop_top) + G.depth_of_loop--; + free(for_list); +#endif +#if ENABLE_HUSH_CASE + free(case_word); +#endif + return rcode; +} + +/* return code is the exit status of the pipe */ +static int free_pipe(struct pipe *pi, int indent) +{ + char **p; + struct command *command; + struct redir_struct *r, *rnext; + int a, i, ret_code = 0; + + if (pi->stopped_cmds > 0) + return ret_code; + debug_printf_clean("%s run pipe: (pid %d)\n", indenter(indent), getpid()); + for (i = 0; i < pi->num_cmds; i++) { + command = &pi->cmds[i]; + debug_printf_clean("%s command %d:\n", indenter(indent), i); + if (command->argv) { + for (a = 0, p = command->argv; *p; a++, p++) { + debug_printf_clean("%s argv[%d] = %s\n", indenter(indent), a, *p); + } + free_strings(command->argv); + command->argv = NULL; + } else if (command->group) { + debug_printf_clean("%s begin group (grp_type:%d)\n", indenter(indent), command->grp_type); + ret_code = free_pipe_list(command->group, indent+3); + debug_printf_clean("%s end group\n", indenter(indent)); + } else { + debug_printf_clean("%s (nil)\n", indenter(indent)); + } + for (r = command->redirects; r; r = rnext) { + debug_printf_clean("%s redirect %d%s", indenter(indent), r->fd, redir_table[r->rd_type].descrip); + if (r->dup == -1) { + /* guard against the case >$FOO, where foo is unset or blank */ + if (r->rd_filename) { + debug_printf_clean(" %s\n", r->rd_filename); + free(r->rd_filename); + r->rd_filename = NULL; + } + } else { + debug_printf_clean("&%d\n", r->dup); + } + rnext = r->next; + free(r); + } + command->redirects = NULL; + } + free(pi->cmds); /* children are an array, they get freed all at once */ + pi->cmds = NULL; +#if ENABLE_HUSH_JOB + free(pi->cmdtext); + pi->cmdtext = NULL; +#endif + return ret_code; +} + +static int free_pipe_list(struct pipe *head, int indent) +{ + int rcode = 0; /* if list has no members */ + struct pipe *pi, *next; + + for (pi = head; pi; pi = next) { +#if HAS_KEYWORDS + debug_printf_clean("%s pipe reserved mode %d\n", indenter(indent), pi->res_word); +#endif + rcode = free_pipe(pi, indent); + debug_printf_clean("%s pipe followup code %d\n", indenter(indent), pi->followup); + next = pi->next; + /*pi->next = NULL;*/ + free(pi); + } + return rcode; +} + +/* Select which version we will use */ +static int run_and_free_list(struct pipe *pi) +{ + int rcode = 0; + debug_printf_exec("run_and_free_list entered\n"); + if (!G.fake_mode) { + debug_printf_exec(": run_list with %d members\n", pi->num_cmds); + rcode = run_list(pi); + } + /* free_pipe_list has the side effect of clearing memory. + * In the long run that function can be merged with run_list, + * but doing that now would hobble the debugging effort. */ + free_pipe_list(pi, /* indent: */ 0); + debug_printf_exec("run_and_free_list return %d\n", rcode); + return rcode; +} + + +/* expand_strvec_to_strvec() takes a list of strings, expands + * all variable references within and returns a pointer to + * a list of expanded strings, possibly with larger number + * of strings. (Think VAR="a b"; echo $VAR). + * This new list is allocated as a single malloc block. + * NULL-terminated list of char* pointers is at the beginning of it, + * followed by strings themself. + * Caller can deallocate entire list by single free(list). */ + +/* Store given string, finalizing the word and starting new one whenever + * we encounter IFS char(s). This is used for expanding variable values. + * End-of-string does NOT finalize word: think about 'echo -$VAR-' */ +static int expand_on_ifs(o_string *output, int n, const char *str) +{ + while (1) { + int word_len = strcspn(str, G.ifs); + if (word_len) { + if (output->o_quote || !output->o_glob) + o_addQstr(output, str, word_len); + else /* protect backslashes against globbing up :) */ + o_addstr_duplicate_backslash(output, str, word_len); + str += word_len; + } + if (!*str) /* EOL - do not finalize word */ + break; + o_addchr(output, '\0'); + debug_print_list("expand_on_ifs", output, n); + n = o_save_ptr(output, n); + str += strspn(str, G.ifs); /* skip ifs chars */ + } + debug_print_list("expand_on_ifs[1]", output, n); + return n; +} + +/* Expand all variable references in given string, adding words to list[] + * at n, n+1,... positions. Return updated n (so that list[n] is next one + * to be filled). This routine is extremely tricky: has to deal with + * variables/parameters with whitespace, $* and $@, and constructs like + * 'echo -$*-'. If you play here, you must run testsuite afterwards! */ +static int expand_vars_to_list(o_string *output, int n, char *arg, char or_mask) +{ + /* or_mask is either 0 (normal case) or 0x80 + * (expansion of right-hand side of assignment == 1-element expand. + * It will also do no globbing, and thus we must not backslash-quote!) */ + + char first_ch, ored_ch; + int i; + const char *val; + char *p; + + ored_ch = 0; + + debug_printf_expand("expand_vars_to_list: arg '%s'\n", arg); + debug_print_list("expand_vars_to_list", output, n); + n = o_save_ptr(output, n); + debug_print_list("expand_vars_to_list[0]", output, n); + + while ((p = strchr(arg, SPECIAL_VAR_SYMBOL)) != NULL) { +#if ENABLE_HUSH_TICK + o_string subst_result = NULL_O_STRING; +#endif + o_addstr(output, arg, p - arg); + debug_print_list("expand_vars_to_list[1]", output, n); + arg = ++p; + p = strchr(p, SPECIAL_VAR_SYMBOL); + + first_ch = arg[0] | or_mask; /* forced to "quoted" if or_mask = 0x80 */ + /* "$@" is special. Even if quoted, it can still + * expand to nothing (not even an empty string) */ + if ((first_ch & 0x7f) != '@') + ored_ch |= first_ch; + val = NULL; + switch (first_ch & 0x7f) { + /* Highest bit in first_ch indicates that var is double-quoted */ + case '$': /* pid */ + val = utoa(G.root_pid); + break; + case '!': /* bg pid */ + val = G.last_bg_pid ? utoa(G.last_bg_pid) : (char*)""; + break; + case '?': /* exitcode */ + val = utoa(G.last_return_code); + break; + case '#': /* argc */ + val = utoa(G.global_argc ? G.global_argc-1 : 0); + break; + case '*': + case '@': + i = 1; + if (!G.global_argv[i]) + break; + ored_ch |= first_ch; /* do it for "$@" _now_, when we know it's not empty */ + if (!(first_ch & 0x80)) { /* unquoted $* or $@ */ + smallint sv = output->o_quote; + /* unquoted var's contents should be globbed, so don't quote */ + output->o_quote = 0; + while (G.global_argv[i]) { + n = expand_on_ifs(output, n, G.global_argv[i]); + debug_printf_expand("expand_vars_to_list: argv %d (last %d)\n", i, G.global_argc - 1); + if (G.global_argv[i++][0] && G.global_argv[i]) { + /* this argv[] is not empty and not last: + * put terminating NUL, start new word */ + o_addchr(output, '\0'); + debug_print_list("expand_vars_to_list[2]", output, n); + n = o_save_ptr(output, n); + debug_print_list("expand_vars_to_list[3]", output, n); + } + } + output->o_quote = sv; + } else + /* If or_mask is nonzero, we handle assignment 'a=....$@.....' + * and in this case should treat it like '$*' - see 'else...' below */ + if (first_ch == ('@'|0x80) && !or_mask) { /* quoted $@ */ + while (1) { + o_addQstr(output, G.global_argv[i], strlen(G.global_argv[i])); + if (++i >= G.global_argc) + break; + o_addchr(output, '\0'); + debug_print_list("expand_vars_to_list[4]", output, n); + n = o_save_ptr(output, n); + } + } else { /* quoted $*: add as one word */ + while (1) { + o_addQstr(output, G.global_argv[i], strlen(G.global_argv[i])); + if (!G.global_argv[++i]) + break; + if (G.ifs[0]) + o_addchr(output, G.ifs[0]); + } + } + break; + case SPECIAL_VAR_SYMBOL: /* */ + /* "Empty variable", used to make "" etc to not disappear */ + arg++; + ored_ch = 0x80; + break; +#if ENABLE_HUSH_TICK + case '`': { /* `cmd */ + struct in_str input; + *p = '\0'; + arg++; +//TODO: can we just stuff it into "output" directly? + debug_printf_subst("SUBST '%s' first_ch %x\n", arg, first_ch); + setup_string_in_str(&input, arg); + process_command_subs(&subst_result, &input, NULL); + debug_printf_subst("SUBST RES '%s'\n", subst_result.data); + val = subst_result.data; + goto store_val; + } +#endif + default: /* varname */ + *p = '\0'; + arg[0] = first_ch & 0x7f; + if (isdigit(arg[0])) { + i = xatoi_u(arg); + if (i < G.global_argc) + val = G.global_argv[i]; + /* else val remains NULL: $N with too big N */ + } else + val = lookup_param(arg); + arg[0] = first_ch; +#if ENABLE_HUSH_TICK + store_val: +#endif + *p = SPECIAL_VAR_SYMBOL; + if (!(first_ch & 0x80)) { /* unquoted $VAR */ + debug_printf_expand("unquoted '%s', output->o_quote:%d\n", val, output->o_quote); + if (val) { + /* unquoted var's contents should be globbed, so don't quote */ + smallint sv = output->o_quote; + output->o_quote = 0; + n = expand_on_ifs(output, n, val); + val = NULL; + output->o_quote = sv; + } + } else { /* quoted $VAR, val will be appended below */ + debug_printf_expand("quoted '%s', output->o_quote:%d\n", val, output->o_quote); + } + } + if (val) { + o_addQstr(output, val, strlen(val)); + } + +#if ENABLE_HUSH_TICK + o_free(&subst_result); +#endif + arg = ++p; + } /* end of "while (SPECIAL_VAR_SYMBOL is found) ..." */ + + if (arg[0]) { + debug_print_list("expand_vars_to_list[a]", output, n); + /* this part is literal, and it was already pre-quoted + * if needed (much earlier), do not use o_addQstr here! */ + o_addstr(output, arg, strlen(arg) + 1); + debug_print_list("expand_vars_to_list[b]", output, n); + } else if (output->length == o_get_last_ptr(output, n) /* expansion is empty */ + && !(ored_ch & 0x80) /* and all vars were not quoted. */ + ) { + n--; + /* allow to reuse list[n] later without re-growth */ + output->has_empty_slot = 1; + } else { + o_addchr(output, '\0'); + } + return n; +} + +static char **expand_variables(char **argv, int or_mask) +{ + int n; + char **list; + char **v; + o_string output = NULL_O_STRING; + + if (or_mask & 0x100) { + output.o_quote = 1; /* protect against globbing for "$var" */ + /* (unquoted $var will temporarily switch it off) */ + output.o_glob = 1; + } + + n = 0; + v = argv; + while (*v) { + n = expand_vars_to_list(&output, n, *v, (char)or_mask); + v++; + } + debug_print_list("expand_variables", &output, n); + + /* output.data (malloced in one block) gets returned in "list" */ + list = o_finalize_list(&output, n); + debug_print_strings("expand_variables[1]", list); + return list; +} + +static char **expand_strvec_to_strvec(char **argv) +{ + return expand_variables(argv, 0x100); +} + +/* Used for expansion of right hand of assignments */ +/* NB: should NOT do globbing! "export v=/bin/c*; env | grep ^v=" outputs + * "v=/bin/c*" */ +static char *expand_string_to_string(const char *str) +{ + char *argv[2], **list; + + argv[0] = (char*)str; + argv[1] = NULL; + list = expand_variables(argv, 0x80); /* 0x80: make one-element expansion */ + if (HUSH_DEBUG) + if (!list[0] || list[1]) + bb_error_msg_and_die("BUG in varexp2"); + /* actually, just move string 2*sizeof(char*) bytes back */ + strcpy((char*)list, list[0]); + debug_printf_expand("string_to_string='%s'\n", (char*)list); + return (char*)list; +} + +/* Used for "eval" builtin */ +static char* expand_strvec_to_string(char **argv) +{ + char **list; + + list = expand_variables(argv, 0x80); + /* Convert all NULs to spaces */ + if (list[0]) { + int n = 1; + while (list[n]) { + if (HUSH_DEBUG) + if (list[n-1] + strlen(list[n-1]) + 1 != list[n]) + bb_error_msg_and_die("BUG in varexp3"); + list[n][-1] = ' '; /* TODO: or to G.ifs[0]? */ + n++; + } + } + strcpy((char*)list, list[0]); + debug_printf_expand("strvec_to_string='%s'\n", (char*)list); + return (char*)list; +} + + +/* Used to get/check local shell variables */ +static struct variable *get_local_var(const char *name) +{ + struct variable *cur; + int len; + + if (!name) + return NULL; + len = strlen(name); + for (cur = G.top_var; cur; cur = cur->next) { + if (strncmp(cur->varstr, name, len) == 0 && cur->varstr[len] == '=') + return cur; + } + return NULL; +} + +/* str holds "NAME=VAL" and is expected to be malloced. + * We take ownership of it. */ +static int set_local_var(char *str, int flg_export) +{ + struct variable *cur; + char *value; + int name_len; + + value = strchr(str, '='); + if (!value) { /* not expected to ever happen? */ + free(str); + return -1; + } + + name_len = value - str + 1; /* including '=' */ + cur = G.top_var; /* cannot be NULL (we have HUSH_VERSION and it's RO) */ + while (1) { + if (strncmp(cur->varstr, str, name_len) != 0) { + if (!cur->next) { + /* Bail out. Note that now cur points + * to last var in linked list */ + break; + } + cur = cur->next; + continue; + } + /* We found an existing var with this name */ + *value = '\0'; + if (cur->flg_read_only) { + bb_error_msg("%s: readonly variable", str); + free(str); + return -1; + } + debug_printf_env("%s: unsetenv '%s'\n", __func__, str); + unsetenv(str); /* just in case */ + *value = '='; + if (strcmp(cur->varstr, str) == 0) { + free_and_exp: + free(str); + goto exp; + } + if (cur->max_len >= strlen(str)) { + /* This one is from startup env, reuse space */ + strcpy(cur->varstr, str); + goto free_and_exp; + } + /* max_len == 0 signifies "malloced" var, which we can + * (and has to) free */ + if (!cur->max_len) + free(cur->varstr); + cur->max_len = 0; + goto set_str_and_exp; + } + + /* Not found - create next variable struct */ + cur->next = xzalloc(sizeof(*cur)); + cur = cur->next; + + set_str_and_exp: + cur->varstr = str; + exp: + if (flg_export) + cur->flg_export = 1; + if (cur->flg_export) { + debug_printf_env("%s: putenv '%s'\n", __func__, cur->varstr); + return putenv(cur->varstr); + } + return 0; +} + +static void unset_local_var(const char *name) +{ + struct variable *cur; + struct variable *prev = prev; /* for gcc */ + int name_len; + + if (!name) + return; + name_len = strlen(name); + cur = G.top_var; + while (cur) { + if (strncmp(cur->varstr, name, name_len) == 0 && cur->varstr[name_len] == '=') { + if (cur->flg_read_only) { + bb_error_msg("%s: readonly variable", name); + return; + } + /* prev is ok to use here because 1st variable, HUSH_VERSION, + * is ro, and we cannot reach this code on the 1st pass */ + prev->next = cur->next; + debug_printf_env("%s: unsetenv '%s'\n", __func__, cur->varstr); + unsetenv(cur->varstr); + if (!cur->max_len) + free(cur->varstr); + free(cur); + return; + } + prev = cur; + cur = cur->next; + } +} + +/* The src parameter allows us to peek forward to a possible &n syntax + * for file descriptor duplication, e.g., "2>&1". + * Return code is 0 normally, 1 if a syntax error is detected in src. + * Resource errors (in xmalloc) cause the process to exit */ +static int setup_redirect(struct parse_context *ctx, int fd, redir_type style, + struct in_str *input) +{ + struct command *command = ctx->command; + struct redir_struct *redir = command->redirects; + struct redir_struct *last_redir = NULL; + + /* Create a new redir_struct and drop it onto the end of the linked list */ + while (redir) { + last_redir = redir; + redir = redir->next; + } + redir = xzalloc(sizeof(struct redir_struct)); + /* redir->next = NULL; */ + /* redir->rd_filename = NULL; */ + if (last_redir) { + last_redir->next = redir; + } else { + command->redirects = redir; + } + + redir->rd_type = style; + redir->fd = (fd == -1) ? redir_table[style].default_fd : fd; + + debug_printf("Redirect type %d%s\n", redir->fd, redir_table[style].descrip); + + /* Check for a '2>&1' type redirect */ + redir->dup = redirect_dup_num(input); + if (redir->dup == -2) + return 1; /* syntax error */ + if (redir->dup != -1) { + /* Erik had a check here that the file descriptor in question + * is legit; I postpone that to "run time" + * A "-" representation of "close me" shows up as a -3 here */ + debug_printf("Duplicating redirect '%d>&%d'\n", redir->fd, redir->dup); + } else { + /* We do _not_ try to open the file that src points to, + * since we need to return and let src be expanded first. + * Set ctx->pending_redirect, so we know what to do at the + * end of the next parsed word. */ + ctx->pending_redirect = redir; + } + return 0; +} + +static struct pipe *new_pipe(void) +{ + struct pipe *pi; + pi = xzalloc(sizeof(struct pipe)); + /*pi->followup = 0; - deliberately invalid value */ + /*pi->res_word = RES_NONE; - RES_NONE is 0 anyway */ + return pi; +} + +static void initialize_context(struct parse_context *ctx) +{ + memset(ctx, 0, sizeof(*ctx)); + ctx->pipe = ctx->list_head = new_pipe(); + /* Create the memory for command, roughly: + * ctx->pipe->cmds = new struct command; + * ctx->command = &ctx->pipe->cmds[0]; + */ + done_command(ctx); +} + +/* If a reserved word is found and processed, parse context is modified + * and 1 is returned. + * Handles if, then, elif, else, fi, for, while, until, do, done. + * case, function, and select are obnoxious, save those for later. + */ +#if HAS_KEYWORDS +struct reserved_combo { + char literal[6]; + unsigned char res; + unsigned char assignment_flag; + int flag; +}; +enum { + FLAG_END = (1 << RES_NONE ), +#if ENABLE_HUSH_IF + FLAG_IF = (1 << RES_IF ), + FLAG_THEN = (1 << RES_THEN ), + FLAG_ELIF = (1 << RES_ELIF ), + FLAG_ELSE = (1 << RES_ELSE ), + FLAG_FI = (1 << RES_FI ), +#endif +#if ENABLE_HUSH_LOOPS + FLAG_FOR = (1 << RES_FOR ), + FLAG_WHILE = (1 << RES_WHILE), + FLAG_UNTIL = (1 << RES_UNTIL), + FLAG_DO = (1 << RES_DO ), + FLAG_DONE = (1 << RES_DONE ), + FLAG_IN = (1 << RES_IN ), +#endif +#if ENABLE_HUSH_CASE + FLAG_MATCH = (1 << RES_MATCH), + FLAG_ESAC = (1 << RES_ESAC ), +#endif + FLAG_START = (1 << RES_XXXX ), +}; + +static const struct reserved_combo* match_reserved_word(o_string *word) +{ + /* Mostly a list of accepted follow-up reserved words. + * FLAG_END means we are done with the sequence, and are ready + * to turn the compound list into a command. + * FLAG_START means the word must start a new compound list. + */ + static const struct reserved_combo reserved_list[] = { +#if ENABLE_HUSH_IF + { "!", RES_NONE, NOT_ASSIGNMENT , 0 }, + { "if", RES_IF, WORD_IS_KEYWORD, FLAG_THEN | FLAG_START }, + { "then", RES_THEN, WORD_IS_KEYWORD, FLAG_ELIF | FLAG_ELSE | FLAG_FI }, + { "elif", RES_ELIF, WORD_IS_KEYWORD, FLAG_THEN }, + { "else", RES_ELSE, WORD_IS_KEYWORD, FLAG_FI }, + { "fi", RES_FI, NOT_ASSIGNMENT , FLAG_END }, +#endif +#if ENABLE_HUSH_LOOPS + { "for", RES_FOR, NOT_ASSIGNMENT , FLAG_IN | FLAG_DO | FLAG_START }, + { "while", RES_WHILE, WORD_IS_KEYWORD, FLAG_DO | FLAG_START }, + { "until", RES_UNTIL, WORD_IS_KEYWORD, FLAG_DO | FLAG_START }, + { "in", RES_IN, NOT_ASSIGNMENT , FLAG_DO }, + { "do", RES_DO, WORD_IS_KEYWORD, FLAG_DONE }, + { "done", RES_DONE, NOT_ASSIGNMENT , FLAG_END }, +#endif +#if ENABLE_HUSH_CASE + { "case", RES_CASE, NOT_ASSIGNMENT , FLAG_MATCH | FLAG_START }, + { "esac", RES_ESAC, NOT_ASSIGNMENT , FLAG_END }, +#endif + }; + const struct reserved_combo *r; + + for (r = reserved_list; r < reserved_list + ARRAY_SIZE(reserved_list); r++) { + if (strcmp(word->data, r->literal) == 0) + return r; + } + return NULL; +} +static int reserved_word(o_string *word, struct parse_context *ctx) +{ +#if ENABLE_HUSH_CASE + static const struct reserved_combo reserved_match = { + "", RES_MATCH, NOT_ASSIGNMENT , FLAG_MATCH | FLAG_ESAC + }; +#endif + const struct reserved_combo *r; + + r = match_reserved_word(word); + if (!r) + return 0; + + debug_printf("found reserved word %s, res %d\n", r->literal, r->res); +#if ENABLE_HUSH_CASE + if (r->res == RES_IN && ctx->ctx_res_w == RES_CASE) + /* "case word IN ..." - IN part starts first match part */ + r = &reserved_match; + else +#endif + if (r->flag == 0) { /* '!' */ + if (ctx->ctx_inverted) { /* bash doesn't accept '! ! true' */ + syntax(NULL); + IF_HAS_KEYWORDS(ctx->ctx_res_w = RES_SNTX;) + } + ctx->ctx_inverted = 1; + return 1; + } + if (r->flag & FLAG_START) { + struct parse_context *new; + debug_printf("push stack\n"); + new = xmalloc(sizeof(*new)); + *new = *ctx; /* physical copy */ + initialize_context(ctx); + ctx->stack = new; + } else if (/*ctx->ctx_res_w == RES_NONE ||*/ !(ctx->old_flag & (1 << r->res))) { + syntax(NULL); + ctx->ctx_res_w = RES_SNTX; + return 1; + } + ctx->ctx_res_w = r->res; + ctx->old_flag = r->flag; + if (ctx->old_flag & FLAG_END) { + struct parse_context *old; + debug_printf("pop stack\n"); + done_pipe(ctx, PIPE_SEQ); + old = ctx->stack; + old->command->group = ctx->list_head; + old->command->grp_type = GRP_NORMAL; + *ctx = *old; /* physical copy */ + free(old); + } + word->o_assignment = r->assignment_flag; + return 1; +} +#endif + +//TODO: many, many callers don't check error from done_word() + +/* Word is complete, look at it and update parsing context. + * Normal return is 0. Syntax errors return 1. */ +static int done_word(o_string *word, struct parse_context *ctx) +{ + struct command *command = ctx->command; + + debug_printf_parse("done_word entered: '%s' %p\n", word->data, command); + if (word->length == 0 && word->nonnull == 0) { + debug_printf_parse("done_word return 0: true null, ignored\n"); + return 0; + } + /* If this word wasn't an assignment, next ones definitely + * can't be assignments. Even if they look like ones. */ + if (word->o_assignment != DEFINITELY_ASSIGNMENT + && word->o_assignment != WORD_IS_KEYWORD + ) { + word->o_assignment = NOT_ASSIGNMENT; + } else { + if (word->o_assignment == DEFINITELY_ASSIGNMENT) + command->assignment_cnt++; + word->o_assignment = MAYBE_ASSIGNMENT; + } + + if (ctx->pending_redirect) { + /* We do not glob in e.g. >*.tmp case. bash seems to glob here + * only if run as "bash", not "sh" */ + ctx->pending_redirect->rd_filename = xstrdup(word->data); + word->o_assignment = NOT_ASSIGNMENT; + debug_printf("word stored in rd_filename: '%s'\n", word->data); + } else { + /* "{ echo foo; } echo bar" - bad */ + /* NB: bash allows e.g. "if true; then { echo foo; } fi". TODO? */ + if (command->group) { + syntax(NULL); + debug_printf_parse("done_word return 1: syntax error, groups and arglists don't mix\n"); + return 1; + } +#if HAS_KEYWORDS +#if ENABLE_HUSH_CASE + if (ctx->ctx_dsemicolon + && strcmp(word->data, "esac") != 0 /* not "... pattern) cmd;; esac" */ + ) { + /* already done when ctx_dsemicolon was set to 1: */ + /* ctx->ctx_res_w = RES_MATCH; */ + ctx->ctx_dsemicolon = 0; + } else +#endif + + if (!command->argv /* if it's the first word... */ +#if ENABLE_HUSH_LOOPS + && ctx->ctx_res_w != RES_FOR /* ...not after FOR or IN */ + && ctx->ctx_res_w != RES_IN +#endif + ) { + debug_printf_parse(": checking '%s' for reserved-ness\n", word->data); + if (reserved_word(word, ctx)) { + o_reset(word); + debug_printf_parse("done_word return %d\n", (ctx->ctx_res_w == RES_SNTX)); + return (ctx->ctx_res_w == RES_SNTX); + } + } +#endif + if (word->nonnull /* word had "xx" or 'xx' at least as part of it. */ + /* optimization: and if it's ("" or '') or ($v... or `cmd`...): */ + && (word->data[0] == '\0' || word->data[0] == SPECIAL_VAR_SYMBOL) + /* (otherwise it's known to be not empty and is already safe) */ + ) { + /* exclude "$@" - it can expand to no word despite "" */ + char *p = word->data; + while (p[0] == SPECIAL_VAR_SYMBOL + && (p[1] & 0x7f) == '@' + && p[2] == SPECIAL_VAR_SYMBOL + ) { + p += 3; + } + if (p == word->data || p[0] != '\0') { + /* saw no "$@", or not only "$@" but some + * real text is there too */ + /* insert "empty variable" reference, this makes + * e.g. "", $empty"" etc to not disappear */ + o_addchr(word, SPECIAL_VAR_SYMBOL); + o_addchr(word, SPECIAL_VAR_SYMBOL); + } + } + command->argv = add_string_to_strings(command->argv, xstrdup(word->data)); + debug_print_strings("word appended to argv", command->argv); + } + + o_reset(word); + ctx->pending_redirect = NULL; + +#if ENABLE_HUSH_LOOPS + /* Force FOR to have just one word (variable name) */ + /* NB: basically, this makes hush see "for v in ..." syntax as if + * as it is "for v; in ...". FOR and IN become two pipe structs + * in parse tree. */ + if (ctx->ctx_res_w == RES_FOR) { +//TODO: check that command->argv[0] is a valid variable name! + done_pipe(ctx, PIPE_SEQ); + } +#endif +#if ENABLE_HUSH_CASE + /* Force CASE to have just one word */ + if (ctx->ctx_res_w == RES_CASE) { + done_pipe(ctx, PIPE_SEQ); + } +#endif + debug_printf_parse("done_word return 0\n"); + return 0; +} + +/* Command (member of a pipe) is complete. The only possible error here + * is out of memory, in which case xmalloc exits. */ +static int done_command(struct parse_context *ctx) +{ + /* The command is really already in the pipe structure, so + * advance the pipe counter and make a new, null command. */ + struct pipe *pi = ctx->pipe; + struct command *command = ctx->command; + + if (command) { + if (command->group == NULL + && command->argv == NULL + && command->redirects == NULL + ) { + debug_printf_parse("done_command: skipping null cmd, num_cmds=%d\n", pi->num_cmds); + return pi->num_cmds; + } + pi->num_cmds++; + debug_printf_parse("done_command: ++num_cmds=%d\n", pi->num_cmds); + } else { + debug_printf_parse("done_command: initializing, num_cmds=%d\n", pi->num_cmds); + } + + /* Only real trickiness here is that the uncommitted + * command structure is not counted in pi->num_cmds. */ + pi->cmds = xrealloc(pi->cmds, sizeof(*pi->cmds) * (pi->num_cmds+1)); + command = &pi->cmds[pi->num_cmds]; + memset(command, 0, sizeof(*command)); + + ctx->command = command; + /* but ctx->pipe and ctx->list_head remain unchanged */ + + return pi->num_cmds; /* used only for 0/nonzero check */ +} + +static void done_pipe(struct parse_context *ctx, pipe_style type) +{ + int not_null; + + debug_printf_parse("done_pipe entered, followup %d\n", type); + /* Close previous command */ + not_null = done_command(ctx); + ctx->pipe->followup = type; + IF_HAS_KEYWORDS(ctx->pipe->pi_inverted = ctx->ctx_inverted;) + IF_HAS_KEYWORDS(ctx->ctx_inverted = 0;) + IF_HAS_KEYWORDS(ctx->pipe->res_word = ctx->ctx_res_w;) + + /* Without this check, even just on command line generates + * tree of three NOPs (!). Which is harmless but annoying. + * IOW: it is safe to do it unconditionally. + * RES_NONE case is for "for a in; do ..." (empty IN set) + * to work, possibly other cases too. */ + if (not_null IF_HAS_KEYWORDS(|| ctx->ctx_res_w != RES_NONE)) { + struct pipe *new_p; + debug_printf_parse("done_pipe: adding new pipe: " + "not_null:%d ctx->ctx_res_w:%d\n", + not_null, ctx->ctx_res_w); + new_p = new_pipe(); + ctx->pipe->next = new_p; + ctx->pipe = new_p; + ctx->command = NULL; /* needed! */ + /* RES_THEN, RES_DO etc are "sticky" - + * they remain set for commands inside if/while. + * This is used to control execution. + * RES_FOR and RES_IN are NOT sticky (needed to support + * cases where variable or value happens to match a keyword): + */ +#if ENABLE_HUSH_LOOPS + if (ctx->ctx_res_w == RES_FOR + || ctx->ctx_res_w == RES_IN) + ctx->ctx_res_w = RES_NONE; +#endif +#if ENABLE_HUSH_CASE + if (ctx->ctx_res_w == RES_MATCH) + ctx->ctx_res_w = RES_CASEI; +#endif + /* Create the memory for command, roughly: + * ctx->pipe->cmds = new struct command; + * ctx->command = &ctx->pipe->cmds[0]; + */ + done_command(ctx); + } + debug_printf_parse("done_pipe return\n"); +} + +/* Peek ahead in the in_str to find out if we have a "&n" construct, + * as in "2>&1", that represents duplicating a file descriptor. + * Return either -2 (syntax error), -1 (no &), or the number found. + */ +static int redirect_dup_num(struct in_str *input) +{ + int ch, d = 0, ok = 0; + ch = i_peek(input); + if (ch != '&') return -1; + + i_getch(input); /* get the & */ + ch = i_peek(input); + if (ch == '-') { + i_getch(input); + return -3; /* "-" represents "close me" */ + } + while (isdigit(ch)) { + d = d*10 + (ch-'0'); + ok = 1; + i_getch(input); + ch = i_peek(input); + } + if (ok) return d; + + bb_error_msg("ambiguous redirect"); + return -2; +} + +/* If a redirect is immediately preceded by a number, that number is + * supposed to tell which file descriptor to redirect. This routine + * looks for such preceding numbers. In an ideal world this routine + * needs to handle all the following classes of redirects... + * echo 2>foo # redirects fd 2 to file "foo", nothing passed to echo + * echo 49>foo # redirects fd 49 to file "foo", nothing passed to echo + * echo -2>foo # redirects fd 1 to file "foo", "-2" passed to echo + * echo 49x>foo # redirects fd 1 to file "foo", "49x" passed to echo + * A -1 output from this program means no valid number was found, so the + * caller should use the appropriate default for this redirection. + */ +static int redirect_opt_num(o_string *o) +{ + int num; + + if (o->length == 0) + return -1; + for (num = 0; num < o->length; num++) { + if (!isdigit(o->data[num])) { + return -1; + } + } + num = atoi(o->data); + o_reset(o); + return num; +} + +#if ENABLE_HUSH_TICK +static FILE *generate_stream_from_list(struct pipe *head) +{ + FILE *pf; + int pid, channel[2]; + + xpipe(channel); +/* *** NOMMU WARNING *** */ +/* By using vfork here, we suspend parent till child exits or execs. + * If child will not do it before it fills the pipe, it can block forever + * in write(STDOUT_FILENO), and parent (shell) will be also stuck. + * Try this script: + * yes "0123456789012345678901234567890" | dd bs=32 count=64k >TESTFILE + * huge=`cat TESTFILE` # will block here forever + * echo OK + */ + pid = BB_MMU ? fork() : vfork(); + if (pid < 0) + bb_perror_msg_and_die(BB_MMU ? "fork" : "vfork"); + if (pid == 0) { /* child */ + if (ENABLE_HUSH_JOB) + die_sleep = 0; /* let nofork's xfuncs die */ + close(channel[0]); /* NB: close _first_, then move fd! */ + xmove_fd(channel[1], 1); + /* Prevent it from trying to handle ctrl-z etc */ +#if ENABLE_HUSH_JOB + G.run_list_level = 1; +#endif + /* Process substitution is not considered to be usual + * 'command execution'. + * SUSv3 says ctrl-Z should be ignored, ctrl-C should not. */ + /* Not needed, we are relying on it being disabled + * everywhere outside actual command execution. */ + /*set_jobctrl_sighandler(SIG_IGN);*/ + set_misc_sighandler(SIG_DFL); + /* Freeing 'head' here would break NOMMU. */ + _exit(run_list(head)); + } + close(channel[1]); + pf = fdopen(channel[0], "r"); + return pf; + /* 'head' is freed by the caller */ +} + +/* Return code is exit status of the process that is run. */ +static int process_command_subs(o_string *dest, + struct in_str *input, + const char *subst_end) +{ + int retcode, ch, eol_cnt; + o_string result = NULL_O_STRING; + struct parse_context inner; + FILE *p; + struct in_str pipe_str; + + initialize_context(&inner); + + /* Recursion to generate command */ + retcode = parse_stream(&result, &inner, input, subst_end); + if (retcode != 0) + return retcode; /* syntax error or EOF */ + done_word(&result, &inner); + done_pipe(&inner, PIPE_SEQ); + o_free(&result); + + p = generate_stream_from_list(inner.list_head); + if (p == NULL) + return 1; + close_on_exec_on(fileno(p)); + setup_file_in_str(&pipe_str, p); + + /* Now send results of command back into original context */ + eol_cnt = 0; + while ((ch = i_getch(&pipe_str)) != EOF) { + if (ch == '\n') { + eol_cnt++; + continue; + } + while (eol_cnt) { + o_addchr(dest, '\n'); + eol_cnt--; + } + o_addQchr(dest, ch); + } + + debug_printf("done reading from pipe, pclose()ing\n"); + /* This is the step that wait()s for the child. Should be pretty + * safe, since we just read an EOF from its stdout. We could try + * to do better, by using wait(), and keeping track of background jobs + * at the same time. That would be a lot of work, and contrary + * to the KISS philosophy of this program. */ + retcode = fclose(p); + free_pipe_list(inner.list_head, /* indent: */ 0); + debug_printf("closed FILE from child, retcode=%d\n", retcode); + return retcode; +} +#endif + +static int parse_group(o_string *dest, struct parse_context *ctx, + struct in_str *input, int ch) +{ + /* dest contains characters seen prior to ( or {. + * Typically it's empty, but for functions defs, + * it contains function name (without '()'). */ + int rcode; + const char *endch = NULL; + struct parse_context sub; + struct command *command = ctx->command; + + debug_printf_parse("parse_group entered\n"); +#if ENABLE_HUSH_FUNCTIONS + if (ch == 'F') { /* function definition? */ + bb_error_msg("aha '%s' is a function, parsing it...", dest->data); + //command->fname = dest->data; + command->grp_type = GRP_FUNCTION; +//TODO: review every o_reset() location... do they handle all o_string fields correctly? + memset(dest, 0, sizeof(*dest)); + } +#endif + if (command->argv /* word [word](... */ + || dest->length /* word(... */ + || dest->nonnull /* ""(... */ + ) { + syntax(NULL); + debug_printf_parse("parse_group return 1: syntax error, groups and arglists don't mix\n"); + return 1; + } + initialize_context(&sub); + endch = "}"; + if (ch == '(') { + endch = ")"; + command->grp_type = GRP_SUBSHELL; + } + rcode = parse_stream(dest, &sub, input, endch); + if (rcode == 0) { + done_word(dest, &sub); /* finish off the final word in the subcontext */ + done_pipe(&sub, PIPE_SEQ); /* and the final command there, too */ + command->group = sub.list_head; + } + debug_printf_parse("parse_group return %d\n", rcode); + return rcode; + /* command remains "open", available for possible redirects */ +} + +/* Basically useful version until someone wants to get fancier, + * see the bash man page under "Parameter Expansion" */ +static const char *lookup_param(const char *src) +{ + struct variable *var = get_local_var(src); + if (var) + return strchr(var->varstr, '=') + 1; + return NULL; +} + +#if ENABLE_HUSH_TICK +/* Subroutines for copying $(...) and `...` things */ +static void add_till_backquote(o_string *dest, struct in_str *input); +/* '...' */ +static void add_till_single_quote(o_string *dest, struct in_str *input) +{ + while (1) { + int ch = i_getch(input); + if (ch == EOF) + break; + if (ch == '\'') + break; + o_addchr(dest, ch); + } +} +/* "...\"...`..`...." - do we need to handle "...$(..)..." too? */ +static void add_till_double_quote(o_string *dest, struct in_str *input) +{ + while (1) { + int ch = i_getch(input); + if (ch == '"') + break; + if (ch == '\\') { /* \x. Copy both chars. */ + o_addchr(dest, ch); + ch = i_getch(input); + } + if (ch == EOF) + break; + o_addchr(dest, ch); + if (ch == '`') { + add_till_backquote(dest, input); + o_addchr(dest, ch); + continue; + } + //if (ch == '$') ... + } +} +/* Process `cmd` - copy contents until "`" is seen. Complicated by + * \` quoting. + * "Within the backquoted style of command substitution, backslash + * shall retain its literal meaning, except when followed by: '$', '`', or '\'. + * The search for the matching backquote shall be satisfied by the first + * backquote found without a preceding backslash; during this search, + * if a non-escaped backquote is encountered within a shell comment, + * a here-document, an embedded command substitution of the $(command) + * form, or a quoted string, undefined results occur. A single-quoted + * or double-quoted string that begins, but does not end, within the + * "`...`" sequence produces undefined results." + * Example Output + * echo `echo '\'TEST\`echo ZZ\`BEST` \TESTZZBEST + */ +static void add_till_backquote(o_string *dest, struct in_str *input) +{ + while (1) { + int ch = i_getch(input); + if (ch == '`') + break; + if (ch == '\\') { /* \x. Copy both chars unless it is \` */ + int ch2 = i_getch(input); + if (ch2 != '`' && ch2 != '$' && ch2 != '\\') + o_addchr(dest, ch); + ch = ch2; + } + if (ch == EOF) + break; + o_addchr(dest, ch); + } +} +/* Process $(cmd) - copy contents until ")" is seen. Complicated by + * quoting and nested ()s. + * "With the $(command) style of command substitution, all characters + * following the open parenthesis to the matching closing parenthesis + * constitute the command. Any valid shell script can be used for command, + * except a script consisting solely of redirections which produces + * unspecified results." + * Example Output + * echo $(echo '(TEST)' BEST) (TEST) BEST + * echo $(echo 'TEST)' BEST) TEST) BEST + * echo $(echo \(\(TEST\) BEST) ((TEST) BEST + */ +static void add_till_closing_curly_brace(o_string *dest, struct in_str *input) +{ + int count = 0; + while (1) { + int ch = i_getch(input); + if (ch == EOF) + break; + if (ch == '(') + count++; + if (ch == ')') + if (--count < 0) + break; + o_addchr(dest, ch); + if (ch == '\'') { + add_till_single_quote(dest, input); + o_addchr(dest, ch); + continue; + } + if (ch == '"') { + add_till_double_quote(dest, input); + o_addchr(dest, ch); + continue; + } + if (ch == '\\') { /* \x. Copy verbatim. Important for \(, \) */ + ch = i_getch(input); + if (ch == EOF) + break; + o_addchr(dest, ch); + continue; + } + } +} +#endif /* ENABLE_HUSH_TICK */ + +/* Return code: 0 for OK, 1 for syntax error */ +static int handle_dollar(o_string *dest, struct in_str *input) +{ + int ch = i_peek(input); /* first character after the $ */ + unsigned char quote_mask = dest->o_quote ? 0x80 : 0; + + debug_printf_parse("handle_dollar entered: ch='%c'\n", ch); + if (isalpha(ch)) { + i_getch(input); + make_var: + o_addchr(dest, SPECIAL_VAR_SYMBOL); + while (1) { + debug_printf_parse(": '%c'\n", ch); + o_addchr(dest, ch | quote_mask); + quote_mask = 0; + ch = i_peek(input); + if (!isalnum(ch) && ch != '_') + break; + i_getch(input); + } + o_addchr(dest, SPECIAL_VAR_SYMBOL); + } else if (isdigit(ch)) { + make_one_char_var: + i_getch(input); + o_addchr(dest, SPECIAL_VAR_SYMBOL); + debug_printf_parse(": '%c'\n", ch); + o_addchr(dest, ch | quote_mask); + o_addchr(dest, SPECIAL_VAR_SYMBOL); + } else switch (ch) { + case '$': /* pid */ + case '!': /* last bg pid */ + case '?': /* last exit code */ + case '#': /* number of args */ + case '*': /* args */ + case '@': /* args */ + goto make_one_char_var; + case '{': + o_addchr(dest, SPECIAL_VAR_SYMBOL); + i_getch(input); + /* XXX maybe someone will try to escape the '}' */ + while (1) { + ch = i_getch(input); + if (ch == '}') + break; + if (!isalnum(ch) && ch != '_') { + syntax("unterminated ${name}"); + debug_printf_parse("handle_dollar return 1: unterminated ${name}\n"); + return 1; + } + debug_printf_parse(": '%c'\n", ch); + o_addchr(dest, ch | quote_mask); + quote_mask = 0; + } + o_addchr(dest, SPECIAL_VAR_SYMBOL); + break; +#if ENABLE_HUSH_TICK + case '(': { + //int pos = dest->length; + i_getch(input); + o_addchr(dest, SPECIAL_VAR_SYMBOL); + o_addchr(dest, quote_mask | '`'); + add_till_closing_curly_brace(dest, input); + //debug_printf_subst("SUBST RES2 '%s'\n", dest->data + pos); + o_addchr(dest, SPECIAL_VAR_SYMBOL); + break; + } +#endif + case '_': + i_getch(input); + ch = i_peek(input); + if (isalnum(ch)) { /* it's $_name or $_123 */ + ch = '_'; + goto make_var; + } + /* else: it's $_ */ + case '-': + /* still unhandled, but should be eventually */ + bb_error_msg("unhandled syntax: $%c", ch); + return 1; + break; + default: + o_addQchr(dest, '$'); + } + debug_printf_parse("handle_dollar return 0\n"); + return 0; +} + +/* Scan input, call done_word() whenever full IFS delimited word was seen. + * Call done_pipe if '\n' was seen (and end_trigger != NULL). + * Return code is 0 if end_trigger char is met, + * -1 on EOF (but if end_trigger == NULL then return 0), + * 1 for syntax error */ +static int parse_stream(o_string *dest, struct parse_context *ctx, + struct in_str *input, const char *end_trigger) +{ + int ch, m; + int redir_fd; + redir_type redir_style; + int shadow_quote = dest->o_quote; + int next; + + /* Only double-quote state is handled in the state variable dest->o_quote. + * A single-quote triggers a bypass of the main loop until its mate is + * found. When recursing, quote state is passed in via dest->o_quote. */ + + debug_printf_parse("parse_stream entered, end_trigger='%s' dest->o_assignment:%d\n", end_trigger, dest->o_assignment); + + while (1) { + m = CHAR_IFS; + next = '\0'; + ch = i_getch(input); + if (ch != EOF) { + m = G.charmap[ch]; + if (ch != '\n') { + next = i_peek(input); + } + } + debug_printf_parse(": ch=%c (%d) m=%d quote=%d\n", + ch, ch, m, dest->o_quote); + if (m == CHAR_ORDINARY + || (m != CHAR_SPECIAL && shadow_quote) + ) { + if (ch == EOF) { + syntax("unterminated \""); + debug_printf_parse("parse_stream return 1: unterminated \"\n"); + return 1; + } + o_addQchr(dest, ch); + if ((dest->o_assignment == MAYBE_ASSIGNMENT + || dest->o_assignment == WORD_IS_KEYWORD) + && ch == '=' + && is_assignment(dest->data) + ) { + dest->o_assignment = DEFINITELY_ASSIGNMENT; + } + continue; + } + if (m == CHAR_IFS) { + if (done_word(dest, ctx)) { + debug_printf_parse("parse_stream return 1: done_word!=0\n"); + return 1; + } + if (ch == EOF) + break; + /* If we aren't performing a substitution, treat + * a newline as a command separator. + * [why we don't handle it exactly like ';'? --vda] */ + if (end_trigger && ch == '\n') { +#if ENABLE_HUSH_CASE + /* "case ... in word) ..." - + * newlines are ignored (but ';' wouldn't be) */ + if (dest->length == 0 // && argv[0] == NULL + && ctx->ctx_res_w == RES_MATCH + ) { + continue; + } +#endif + done_pipe(ctx, PIPE_SEQ); + dest->o_assignment = MAYBE_ASSIGNMENT; + } + } + if (end_trigger) { + if (!shadow_quote && strchr(end_trigger, ch)) { + /* Special case: (...word) makes last word terminate, + * as if ';' is seen */ + if (ch == ')') { + done_word(dest, ctx); +//err chk? + done_pipe(ctx, PIPE_SEQ); + dest->o_assignment = MAYBE_ASSIGNMENT; + } + if (!HAS_KEYWORDS + IF_HAS_KEYWORDS(|| (ctx->ctx_res_w == RES_NONE && ctx->old_flag == 0)) + ) { + debug_printf_parse("parse_stream return 0: end_trigger char found\n"); + return 0; + } + } + } + if (m == CHAR_IFS) + continue; + + if (dest->o_assignment == MAYBE_ASSIGNMENT) { + /* ch is a special char and thus this word + * cannot be an assignment: */ + dest->o_assignment = NOT_ASSIGNMENT; + } + + switch (ch) { + case '#': + if (dest->length == 0 && !shadow_quote) { + while (1) { + ch = i_peek(input); + if (ch == EOF || ch == '\n') + break; + i_getch(input); + } + } else { + o_addQchr(dest, ch); + } + break; + case '\\': + if (next == EOF) { + syntax("\\"); + debug_printf_parse("parse_stream return 1: \\\n"); + return 1; + } + /* bash: + * "The backslash retains its special meaning [in "..."] + * only when followed by one of the following characters: + * $, `, ", \, or . A double quote may be quoted + * within double quotes by preceding it with a backslash. + * If enabled, history expansion will be performed unless + * an ! appearing in double quotes is escaped using + * a backslash. The backslash preceding the ! is not removed." + */ + if (shadow_quote) { //NOT SURE dest->o_quote) { + if (strchr("$`\"\\", next) != NULL) { + o_addqchr(dest, i_getch(input)); + } else { + o_addqchr(dest, '\\'); + } + } else { + o_addchr(dest, '\\'); + o_addchr(dest, i_getch(input)); + } + break; + case '$': + if (handle_dollar(dest, input) != 0) { + debug_printf_parse("parse_stream return 1: handle_dollar returned non-0\n"); + return 1; + } + break; + case '\'': + dest->nonnull = 1; + while (1) { + ch = i_getch(input); + if (ch == EOF) { + syntax("unterminated '"); + debug_printf_parse("parse_stream return 1: unterminated '\n"); + return 1; + } + if (ch == '\'') + break; + if (dest->o_assignment == NOT_ASSIGNMENT) + o_addqchr(dest, ch); + else + o_addchr(dest, ch); + } + break; + case '"': + dest->nonnull = 1; + shadow_quote ^= 1; /* invert */ + if (dest->o_assignment == NOT_ASSIGNMENT) + dest->o_quote ^= 1; + break; +#if ENABLE_HUSH_TICK + case '`': { + //int pos = dest->length; + o_addchr(dest, SPECIAL_VAR_SYMBOL); + o_addchr(dest, shadow_quote /*or dest->o_quote??*/ ? 0x80 | '`' : '`'); + add_till_backquote(dest, input); + o_addchr(dest, SPECIAL_VAR_SYMBOL); + //debug_printf_subst("SUBST RES3 '%s'\n", dest->data + pos); + break; + } +#endif + case '>': + redir_fd = redirect_opt_num(dest); + done_word(dest, ctx); + redir_style = REDIRECT_OVERWRITE; + if (next == '>') { + redir_style = REDIRECT_APPEND; + i_getch(input); + } +#if 0 + else if (next == '(') { + syntax(">(process) not supported"); + debug_printf_parse("parse_stream return 1: >(process) not supported\n"); + return 1; + } +#endif + setup_redirect(ctx, redir_fd, redir_style, input); + break; + case '<': + redir_fd = redirect_opt_num(dest); + done_word(dest, ctx); + redir_style = REDIRECT_INPUT; + if (next == '<') { + redir_style = REDIRECT_HEREIS; + i_getch(input); + } else if (next == '>') { + redir_style = REDIRECT_IO; + i_getch(input); + } +#if 0 + else if (next == '(') { + syntax("<(process) not supported"); + debug_printf_parse("parse_stream return 1: <(process) not supported\n"); + return 1; + } +#endif + setup_redirect(ctx, redir_fd, redir_style, input); + break; + case ';': +#if ENABLE_HUSH_CASE + case_semi: +#endif + done_word(dest, ctx); + done_pipe(ctx, PIPE_SEQ); +#if ENABLE_HUSH_CASE + /* Eat multiple semicolons, detect + * whether it means something special */ + while (1) { + ch = i_peek(input); + if (ch != ';') + break; + i_getch(input); + if (ctx->ctx_res_w == RES_CASEI) { + ctx->ctx_dsemicolon = 1; + ctx->ctx_res_w = RES_MATCH; + break; + } + } +#endif + new_cmd: + /* We just finished a cmd. New one may start + * with an assignment */ + dest->o_assignment = MAYBE_ASSIGNMENT; + break; + case '&': + done_word(dest, ctx); + if (next == '&') { + i_getch(input); + done_pipe(ctx, PIPE_AND); + } else { + done_pipe(ctx, PIPE_BG); + } + goto new_cmd; + case '|': + done_word(dest, ctx); +#if ENABLE_HUSH_CASE + if (ctx->ctx_res_w == RES_MATCH) + break; /* we are in case's "word | word)" */ +#endif + if (next == '|') { /* || */ + i_getch(input); + done_pipe(ctx, PIPE_OR); + } else { + /* we could pick up a file descriptor choice here + * with redirect_opt_num(), but bash doesn't do it. + * "echo foo 2| cat" yields "foo 2". */ + done_command(ctx); + } + goto new_cmd; + case '(': +#if ENABLE_HUSH_CASE + /* "case... in [(]word)..." - skip '(' */ + if (ctx->ctx_res_w == RES_MATCH + && ctx->command->argv == NULL /* not (word|(... */ + && dest->length == 0 /* not word(... */ + && dest->nonnull == 0 /* not ""(... */ + ) { + continue; + } +#endif +#if ENABLE_HUSH_FUNCTIONS + if (dest->length != 0 /* not just () but word() */ + && dest->nonnull == 0 /* not a"b"c() */ + && ctx->command->argv == NULL /* it's the first word */ +//TODO: "func ( ) {...}" - note spaces - is valid format too in bash + && i_peek(input) == ')' + && !match_reserved_word(dest) + ) { + bb_error_msg("seems like a function definition"); + i_getch(input); + do { +//TODO: do it properly. + ch = i_getch(input); + } while (ch == ' ' || ch == '\n'); + if (ch != '{') { + syntax("was expecting {"); + debug_printf_parse("parse_stream return 1\n"); + return 1; + } + ch = 'F'; /* magic value */ + } +#endif + case '{': + if (parse_group(dest, ctx, input, ch) != 0) { + debug_printf_parse("parse_stream return 1: parse_group returned non-0\n"); + return 1; + } + goto new_cmd; + case ')': +#if ENABLE_HUSH_CASE + if (ctx->ctx_res_w == RES_MATCH) + goto case_semi; +#endif + case '}': + /* proper use of this character is caught by end_trigger: + * if we see {, we call parse_group(..., end_trigger='}') + * and it will match } earlier (not here). */ + syntax("unexpected } or )"); + debug_printf_parse("parse_stream return 1: unexpected '}'\n"); + return 1; + default: + if (HUSH_DEBUG) + bb_error_msg_and_die("BUG: unexpected %c\n", ch); + } + } /* while (1) */ + debug_printf_parse("parse_stream return %d\n", -(end_trigger != NULL)); + if (end_trigger) + return -1; + return 0; +} + +static void set_in_charmap(const char *set, int code) +{ + while (*set) + G.charmap[(unsigned char)*set++] = code; +} + +static void update_charmap(void) +{ + G.ifs = getenv("IFS"); + if (G.ifs == NULL) + G.ifs = " \t\n"; + /* Precompute a list of 'flow through' behavior so it can be treated + * quickly up front. Computation is necessary because of IFS. + * Special case handling of IFS == " \t\n" is not implemented. + * The charmap[] array only really needs two bits each, + * and on most machines that would be faster (reduced L1 cache use). + */ + memset(G.charmap, CHAR_ORDINARY, sizeof(G.charmap)); +#if ENABLE_HUSH_TICK + set_in_charmap("\\$\"`", CHAR_SPECIAL); +#else + set_in_charmap("\\$\"", CHAR_SPECIAL); +#endif + set_in_charmap("<>;&|(){}#'", CHAR_ORDINARY_IF_QUOTED); + set_in_charmap(G.ifs, CHAR_IFS); /* are ordinary if quoted */ +} + +/* Most recursion does not come through here, the exception is + * from builtin_source() and builtin_eval() */ +static int parse_and_run_stream(struct in_str *inp, int parse_flag) +{ + struct parse_context ctx; + o_string temp = NULL_O_STRING; + int rcode; + + do { + initialize_context(&ctx); + update_charmap(); +#if ENABLE_HUSH_INTERACTIVE + inp->promptmode = 0; /* PS1 */ +#endif + /* We will stop & execute after each ';' or '\n'. + * Example: "sleep 9999; echo TEST" + ctrl-C: + * TEST should be printed */ + temp.o_assignment = MAYBE_ASSIGNMENT; + rcode = parse_stream(&temp, &ctx, inp, ";\n"); +#if HAS_KEYWORDS + if (rcode != 1 && ctx.old_flag != 0) { + syntax(NULL); + } +#endif + if (rcode != 1 IF_HAS_KEYWORDS(&& ctx.old_flag == 0)) { + done_word(&temp, &ctx); + done_pipe(&ctx, PIPE_SEQ); + debug_print_tree(ctx.list_head, 0); + debug_printf_exec("parse_stream_outer: run_and_free_list\n"); + run_and_free_list(ctx.list_head); + } else { + /* We arrive here also if rcode == 1 (error in parse_stream) */ +#if HAS_KEYWORDS + if (ctx.old_flag != 0) { + free(ctx.stack); + o_reset(&temp); + } +#endif + /*temp.nonnull = 0; - o_free does it below */ + /*temp.o_quote = 0; - o_free does it below */ + free_pipe_list(ctx.list_head, /* indent: */ 0); + /* Discard all unprocessed line input, force prompt on */ + inp->p = NULL; +#if ENABLE_HUSH_INTERACTIVE + inp->promptme = 1; +#endif + } + o_free(&temp); + /* loop on syntax errors, return on EOF: */ + } while (rcode != -1 && !(parse_flag & PARSEFLAG_EXIT_FROM_LOOP)); + return 0; +} + +static int parse_and_run_string(const char *s, int parse_flag) +{ + struct in_str input; + setup_string_in_str(&input, s); + return parse_and_run_stream(&input, parse_flag); +} + +static int parse_and_run_file(FILE *f) +{ + int rcode; + struct in_str input; + setup_file_in_str(&input, f); + rcode = parse_and_run_stream(&input, 0 /* parse_flag */); + return rcode; +} + +#if ENABLE_HUSH_JOB +/* Make sure we have a controlling tty. If we get started under a job + * aware app (like bash for example), make sure we are now in charge so + * we don't fight over who gets the foreground */ +static void setup_job_control(void) +{ + pid_t shell_pgrp; + + shell_pgrp = getpgrp(); + close_on_exec_on(G.interactive_fd); + + /* If we were ran as 'hush &', + * sleep until we are in the foreground. */ + while (tcgetpgrp(G.interactive_fd) != shell_pgrp) { + /* Send TTIN to ourself (should stop us) */ + kill(- shell_pgrp, SIGTTIN); + shell_pgrp = getpgrp(); + } + + /* Ignore job-control and misc signals. */ + set_jobctrl_sighandler(SIG_IGN); + set_misc_sighandler(SIG_IGN); +//huh? signal(SIGCHLD, SIG_IGN); + + /* We _must_ restore tty pgrp on fatal signals */ + set_fatal_sighandler(sigexit); + + /* Put ourselves in our own process group. */ + bb_setpgrp(); /* is the same as setpgid(our_pid, our_pid); */ + /* Grab control of the terminal. */ + tcsetpgrp(G.interactive_fd, getpid()); +} +#endif + + +int hush_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int hush_main(int argc, char **argv) +{ + static const struct variable const_shell_ver = { + .next = NULL, + .varstr = (char*)hush_version_str, + .max_len = 1, /* 0 can provoke free(name) */ + .flg_export = 1, + .flg_read_only = 1, + }; + + int opt; + FILE *input; + char **e; + struct variable *cur_var; + + INIT_G(); + + G.root_pid = getpid(); + + /* Deal with HUSH_VERSION */ + G.shell_ver = const_shell_ver; /* copying struct here */ + G.top_var = &G.shell_ver; + debug_printf_env("unsetenv '%s'\n", "HUSH_VERSION"); + unsetenv("HUSH_VERSION"); /* in case it exists in initial env */ + /* Initialize our shell local variables with the values + * currently living in the environment */ + cur_var = G.top_var; + e = environ; + if (e) while (*e) { + char *value = strchr(*e, '='); + if (value) { /* paranoia */ + cur_var->next = xzalloc(sizeof(*cur_var)); + cur_var = cur_var->next; + cur_var->varstr = *e; + cur_var->max_len = strlen(*e); + cur_var->flg_export = 1; + } + e++; + } + debug_printf_env("putenv '%s'\n", hush_version_str); + putenv((char *)hush_version_str); /* reinstate HUSH_VERSION */ + +#if ENABLE_FEATURE_EDITING + G.line_input_state = new_line_input_t(FOR_SHELL); +#endif + /* XXX what should these be while sourcing /etc/profile? */ + G.global_argc = argc; + G.global_argv = argv; + /* Initialize some more globals to non-zero values */ + set_cwd(); +#if ENABLE_HUSH_INTERACTIVE +#if ENABLE_FEATURE_EDITING + cmdedit_set_initial_prompt(); +#endif + G.PS2 = "> "; +#endif + + if (EXIT_SUCCESS) /* otherwise is already done */ + G.last_return_code = EXIT_SUCCESS; + + if (argv[0] && argv[0][0] == '-') { + debug_printf("sourcing /etc/profile\n"); + input = fopen_for_read("/etc/profile"); + if (input != NULL) { + close_on_exec_on(fileno(input)); + parse_and_run_file(input); + fclose(input); + } + } + input = stdin; + + while ((opt = getopt(argc, argv, "c:xif")) > 0) { + switch (opt) { + case 'c': + G.global_argv = argv + optind; + G.global_argc = argc - optind; + opt = parse_and_run_string(optarg, 0 /* parse_flag */); + goto final_return; + case 'i': + /* Well, we cannot just declare interactiveness, + * we have to have some stuff (ctty, etc) */ + /* G.interactive_fd++; */ + break; + case 'f': + G.fake_mode = 1; + break; + default: +#ifndef BB_VER + fprintf(stderr, "Usage: sh [FILE]...\n" + " or: sh -c command [args]...\n\n"); + exit(EXIT_FAILURE); +#else + bb_show_usage(); +#endif + } + } +#if ENABLE_HUSH_JOB + /* A shell is interactive if the '-i' flag was given, or if all of + * the following conditions are met: + * no -c command + * no arguments remaining or the -s flag given + * standard input is a terminal + * standard output is a terminal + * Refer to Posix.2, the description of the 'sh' utility. */ + if (argv[optind] == NULL && input == stdin + && isatty(STDIN_FILENO) && isatty(STDOUT_FILENO) + ) { + G.saved_tty_pgrp = tcgetpgrp(STDIN_FILENO); + debug_printf("saved_tty_pgrp=%d\n", G.saved_tty_pgrp); + if (G.saved_tty_pgrp >= 0) { + /* try to dup to high fd#, >= 255 */ + G.interactive_fd = fcntl(STDIN_FILENO, F_DUPFD, 255); + if (G.interactive_fd < 0) { + /* try to dup to any fd */ + G.interactive_fd = dup(STDIN_FILENO); + if (G.interactive_fd < 0) + /* give up */ + G.interactive_fd = 0; + } + // TODO: track & disallow any attempts of user + // to (inadvertently) close/redirect it + } + } + debug_printf("G.interactive_fd=%d\n", G.interactive_fd); + if (G.interactive_fd) { + fcntl(G.interactive_fd, F_SETFD, FD_CLOEXEC); + /* Looks like they want an interactive shell */ + setup_job_control(); + /* -1 is special - makes xfuncs longjmp, not exit + * (we reset die_sleep = 0 whereever we [v]fork) */ + die_sleep = -1; + if (setjmp(die_jmp)) { + /* xfunc has failed! die die die */ + hush_exit(xfunc_error_retval); + } +#if !ENABLE_FEATURE_SH_EXTRA_QUIET + printf("\n\n%s hush - the humble shell v"HUSH_VER_STR"\n", bb_banner); + printf("Enter 'help' for a list of built-in commands.\n\n"); +#endif + } +#elif ENABLE_HUSH_INTERACTIVE +/* no job control compiled, only prompt/line editing */ + if (argv[optind] == NULL && input == stdin + && isatty(STDIN_FILENO) && isatty(STDOUT_FILENO) + ) { + G.interactive_fd = fcntl(STDIN_FILENO, F_DUPFD, 255); + if (G.interactive_fd < 0) { + /* try to dup to any fd */ + G.interactive_fd = dup(STDIN_FILENO); + if (G.interactive_fd < 0) + /* give up */ + G.interactive_fd = 0; + } + if (G.interactive_fd) { + fcntl(G.interactive_fd, F_SETFD, FD_CLOEXEC); + set_misc_sighandler(SIG_IGN); + } + } +#endif + + if (argv[optind] == NULL) { + opt = parse_and_run_file(stdin); + } else { + debug_printf("\nrunning script '%s'\n", argv[optind]); + G.global_argv = argv + optind; + G.global_argc = argc - optind; + input = xfopen_for_read(argv[optind]); + fcntl(fileno(input), F_SETFD, FD_CLOEXEC); + opt = parse_and_run_file(input); + } + + final_return: + +#if ENABLE_FEATURE_CLEAN_UP + fclose(input); + if (G.cwd != bb_msg_unknown) + free((char*)G.cwd); + cur_var = G.top_var->next; + while (cur_var) { + struct variable *tmp = cur_var; + if (!cur_var->max_len) + free(cur_var->varstr); + cur_var = cur_var->next; + free(tmp); + } +#endif + hush_exit(opt ? opt : G.last_return_code); +} + + +#if ENABLE_LASH +int lash_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int lash_main(int argc, char **argv) +{ + //bb_error_msg("lash is deprecated, please use hush instead"); + return hush_main(argc, argv); +} +#endif + + +/* + * Built-ins + */ +static int builtin_true(char **argv UNUSED_PARAM) +{ + return 0; +} + +static int builtin_test(char **argv) +{ + int argc = 0; + while (*argv) { + argc++; + argv++; + } + return test_main(argc, argv - argc); +} + +static int builtin_echo(char **argv) +{ + int argc = 0; + while (*argv) { + argc++; + argv++; + } + return echo_main(argc, argv - argc); +} + +static int builtin_eval(char **argv) +{ + int rcode = EXIT_SUCCESS; + + if (argv[1]) { + char *str = expand_strvec_to_string(argv + 1); + parse_and_run_string(str, PARSEFLAG_EXIT_FROM_LOOP); + free(str); + rcode = G.last_return_code; + } + return rcode; +} + +static int builtin_cd(char **argv) +{ + const char *newdir; + if (argv[1] == NULL) { + // bash does nothing (exitcode 0) if HOME is ""; if it's unset, + // bash says "bash: cd: HOME not set" and does nothing (exitcode 1) + newdir = getenv("HOME") ? : "/"; + } else + newdir = argv[1]; + if (chdir(newdir)) { + printf("cd: %s: %s\n", newdir, strerror(errno)); + return EXIT_FAILURE; + } + set_cwd(); + return EXIT_SUCCESS; +} + +static int builtin_exec(char **argv) +{ + if (argv[1] == NULL) + return EXIT_SUCCESS; /* bash does this */ + { +#if !BB_MMU + nommu_save_t dummy; +#endif +// FIXME: if exec fails, bash does NOT exit! We do... + pseudo_exec_argv(&dummy, argv + 1, 0, NULL); + /* never returns */ + } +} + +static int builtin_exit(char **argv) +{ +// TODO: bash does it ONLY on top-level sh exit (+interacive only?) + //puts("exit"); /* bash does it */ +// TODO: warn if we have background jobs: "There are stopped jobs" +// On second consecutive 'exit', exit anyway. + if (argv[1] == NULL) + hush_exit(G.last_return_code); + /* mimic bash: exit 123abc == exit 255 + error msg */ + xfunc_error_retval = 255; + /* bash: exit -2 == exit 254, no error msg */ + hush_exit(xatoi(argv[1]) & 0xff); +} + +static int builtin_export(char **argv) +{ + const char *value; + char *name = argv[1]; + + if (name == NULL) { + // TODO: + // ash emits: export VAR='VAL' + // bash: declare -x VAR="VAL" + // (both also escape as needed (quotes, $, etc)) + char **e = environ; + if (e) + while (*e) + puts(*e++); + return EXIT_SUCCESS; + } + + value = strchr(name, '='); + if (!value) { + /* They are exporting something without a =VALUE */ + struct variable *var; + + var = get_local_var(name); + if (var) { + var->flg_export = 1; + debug_printf_env("%s: putenv '%s'\n", __func__, var->varstr); + putenv(var->varstr); + } + /* bash does not return an error when trying to export + * an undefined variable. Do likewise. */ + return EXIT_SUCCESS; + } + + set_local_var(xstrdup(name), 1); + return EXIT_SUCCESS; +} + +#if ENABLE_HUSH_JOB +/* built-in 'fg' and 'bg' handler */ +static int builtin_fg_bg(char **argv) +{ + int i, jobnum; + struct pipe *pi; + + if (!G.interactive_fd) + return EXIT_FAILURE; + /* If they gave us no args, assume they want the last backgrounded task */ + if (!argv[1]) { + for (pi = G.job_list; pi; pi = pi->next) { + if (pi->jobid == G.last_jobid) { + goto found; + } + } + bb_error_msg("%s: no current job", argv[0]); + return EXIT_FAILURE; + } + if (sscanf(argv[1], "%%%d", &jobnum) != 1) { + bb_error_msg("%s: bad argument '%s'", argv[0], argv[1]); + return EXIT_FAILURE; + } + for (pi = G.job_list; pi; pi = pi->next) { + if (pi->jobid == jobnum) { + goto found; + } + } + bb_error_msg("%s: %d: no such job", argv[0], jobnum); + return EXIT_FAILURE; + found: + // TODO: bash prints a string representation + // of job being foregrounded (like "sleep 1 | cat") + if (*argv[0] == 'f') { + /* Put the job into the foreground. */ + tcsetpgrp(G.interactive_fd, pi->pgrp); + } + + /* Restart the processes in the job */ + debug_printf_jobs("reviving %d procs, pgrp %d\n", pi->num_cmds, pi->pgrp); + for (i = 0; i < pi->num_cmds; i++) { + debug_printf_jobs("reviving pid %d\n", pi->cmds[i].pid); + pi->cmds[i].is_stopped = 0; + } + pi->stopped_cmds = 0; + + i = kill(- pi->pgrp, SIGCONT); + if (i < 0) { + if (errno == ESRCH) { + delete_finished_bg_job(pi); + return EXIT_SUCCESS; + } else { + bb_perror_msg("kill (SIGCONT)"); + } + } + + if (*argv[0] == 'f') { + remove_bg_job(pi); + return checkjobs_and_fg_shell(pi); + } + return EXIT_SUCCESS; +} +#endif + +#if ENABLE_HUSH_HELP +static int builtin_help(char **argv UNUSED_PARAM) +{ + const struct built_in_command *x; + + printf("\nBuilt-in commands:\n"); + printf("-------------------\n"); + for (x = bltins; x != &bltins[ARRAY_SIZE(bltins)]; x++) { + printf("%s\t%s\n", x->cmd, x->descr); + } + printf("\n\n"); + return EXIT_SUCCESS; +} +#endif + +#if ENABLE_HUSH_JOB +static int builtin_jobs(char **argv UNUSED_PARAM) +{ + struct pipe *job; + const char *status_string; + + for (job = G.job_list; job; job = job->next) { + if (job->alive_cmds == job->stopped_cmds) + status_string = "Stopped"; + else + status_string = "Running"; + + printf(JOB_STATUS_FORMAT, job->jobid, status_string, job->cmdtext); + } + return EXIT_SUCCESS; +} +#endif + +static int builtin_pwd(char **argv UNUSED_PARAM) +{ + puts(set_cwd()); + return EXIT_SUCCESS; +} + +static int builtin_read(char **argv) +{ + char *string; + const char *name = argv[1] ? argv[1] : "REPLY"; + + string = xmalloc_reads(STDIN_FILENO, xasprintf("%s=", name), NULL); + return set_local_var(string, 0); +} + +/* built-in 'set [VAR=value]' handler */ +static int builtin_set(char **argv) +{ + char *temp = argv[1]; + struct variable *e; + + if (temp == NULL) + for (e = G.top_var; e; e = e->next) + puts(e->varstr); + else + set_local_var(xstrdup(temp), 0); + + return EXIT_SUCCESS; +} + +static int builtin_shift(char **argv) +{ + int n = 1; + if (argv[1]) { + n = atoi(argv[1]); + } + if (n >= 0 && n < G.global_argc) { + G.global_argv[n] = G.global_argv[0]; + G.global_argc -= n; + G.global_argv += n; + return EXIT_SUCCESS; + } + return EXIT_FAILURE; +} + +static int builtin_source(char **argv) +{ + FILE *input; + int status; + + if (argv[1] == NULL) + return EXIT_FAILURE; + + /* XXX search through $PATH is missing */ + input = fopen_for_read(argv[1]); + if (!input) { + bb_error_msg("can't open '%s'", argv[1]); + return EXIT_FAILURE; + } + close_on_exec_on(fileno(input)); + + /* Now run the file */ + /* XXX argv and argc are broken; need to save old G.global_argv + * (pointer only is OK!) on this stack frame, + * set G.global_argv=argv+1, recurse, and restore. */ + status = parse_and_run_file(input); + fclose(input); + return status; +} + +static int builtin_umask(char **argv) +{ + mode_t new_umask; + const char *arg = argv[1]; + char *end; + if (arg) { + new_umask = strtoul(arg, &end, 8); + if (*end != '\0' || end == arg) { + return EXIT_FAILURE; + } + } else { + new_umask = umask(0); + printf("%.3o\n", (unsigned) new_umask); + } + umask(new_umask); + return EXIT_SUCCESS; +} + +static int builtin_unset(char **argv) +{ + /* bash always returns true */ + unset_local_var(argv[1]); + return EXIT_SUCCESS; +} + +#if ENABLE_HUSH_LOOPS +static int builtin_break(char **argv) +{ + if (G.depth_of_loop == 0) { + bb_error_msg("%s: only meaningful in a loop", argv[0]); + return EXIT_SUCCESS; /* bash compat */ + } + G.flag_break_continue++; /* BC_BREAK = 1 */ + G.depth_break_continue = 1; + if (argv[1]) { + G.depth_break_continue = bb_strtou(argv[1], NULL, 10); + if (errno || !G.depth_break_continue || argv[2]) { + bb_error_msg("%s: bad arguments", argv[0]); + G.flag_break_continue = BC_BREAK; + G.depth_break_continue = UINT_MAX; + } + } + if (G.depth_of_loop < G.depth_break_continue) + G.depth_break_continue = G.depth_of_loop; + return EXIT_SUCCESS; +} + +static int builtin_continue(char **argv) +{ + G.flag_break_continue = 1; /* BC_CONTINUE = 2 = 1+1 */ + return builtin_break(argv); +} +#endif diff --git a/shell/hush_doc.txt b/shell/hush_doc.txt new file mode 100644 index 0000000..c68dc24 --- /dev/null +++ b/shell/hush_doc.txt @@ -0,0 +1,143 @@ +2008-07-14 + + Command parsing + +Command parsing results in a list of "pipe" structures. +This list correspond not only to usual "pipe1 || pipe2 && pipe3" +lists, but it also controls execution of if, while, etc statements. +Every such statement is a list for hush. List consists of pipes. + +struct pipe fields: + smallint res_word - "none" for normal commands, + "if" for if condition etc + struct child_prog progs[] - array of commands in pipe + smallint followup - how this pipe is related to next: is it + "pipe; pipe", "pipe & pipe" "pipe && pipe", + "pipe || pipe"? + +Blocks of commands { pipe; pipe; } and (pipe; pipe) are represented +as one pipe struct with one progs[0] element which is a "group" - +struct child_prog can contain a list of pipes. Sometimes these +"groups" are created implicitly, e.g. every control +statement (if, while, etc) sits inside its own group. + +res_word controls statement execution. Examples: + +"echo Hello" - +pipe 0 res_word=NONE followup=SEQ prog[0] 'echo' 'Hello' +pipe 1 res_word=NONE followup=SEQ + +"echo foo || echo bar" - +pipe 0 res_word=NONE followup=OR prog[0] 'echo' 'foo' +pipe 1 res_word=NONE followup=SEQ prog[0] 'echo' 'bar' +pipe 2 res_word=NONE followup=SEQ + +"if true; then echo Hello; true; fi" - +res_word=NONE followup=SEQ + prog 0 group {}: + pipe 0 res_word=IF followup=SEQ prog[0] 'true' + pipe 1 res_word=THEN followup=SEQ prog[0] 'echo' 'Hello' + pipe 2 res_word=THEN followup=SEQ prog[0] 'true' + pipe 3 res_word=FI followup=SEQ + pipe 4 res_word=NONE followup=(null) +pipe 1 res_word=NONE followup=SEQ + +Above you see that if is a list, and it sits in a {} group +implicitly created by hush. Also note two THEN res_word's - +it is explained below. + +"if true; then { echo Hello; true; }; fi" - +pipe 0 res_word=NONE followup=SEQ + prog 0 group {}: + pipe 0 res_word=IF followup=SEQ prog[0] 'true' + pipe 1 res_word=THEN followup=SEQ + prog 0 group {}: + pipe 0 res_word=NONE followup=SEQ prog[0] 'echo' 'Hello' + pipe 1 res_word=NONE followup=SEQ prog[0] 'true' + pipe 2 res_word=NONE followup=SEQ + pipe 2 res_word=NONE followup=(null) +pipe 1 res_word=NONE followup=SEQ + +"for v in a b; do echo $v; true; done" - +pipe 0 res_word=NONE followup=SEQ + prog 0 group {}: + pipe 0 res_word=FOR followup=SEQ prog[0] 'v' + pipe 1 res_word=IN followup=SEQ prog[0] 'a' 'b' + pipe 2 res_word=DO followup=SEQ prog[0] 'echo' '$v' + pipe 3 res_word=DO followup=SEQ prog[0] 'true' + pipe 4 res_word=DONE followup=SEQ + pipe 5 res_word=NONE followup=(null) +pipe 1 res_word=NONE followup=SEQ + +Note how "THEN" and "DO" does not just mark the first pipe, +it "sticks" to all pipes in the body. This is used when +hush executes parsed pipes. + +Dummy trailing pipes with no commands are artifacts of imperfect +parsing algorithm - done_pipe() appends new pipe struct beforehand +and last one ends up empty and unused. + +"for" and "case" statements (ab)use progs[] to keep their data +instead of argv vector progs[] usually do. "for" keyword is forcing +pipe termination after first word, which makes hush see +"for v in..." as "for v; in...". "case" keyword does the same. +Other judiciuosly placed hacks make hush see +"case word in a) cmd1;; b) cmd2;; esac" as if it was +"case word; match a; cmd; match b; cmd2; esac" +("match" is a fictitious keyword here): + +"case word in a) cmd1;; b) cmd2; esac" - +pipe 0 res_word=NONE followup=1 SEQ + prog 0 group {}: + pipe 0 res_word=CASE followup=SEQ prog[0] 'word' + pipe 1 res_word=MATCH followup=SEQ prog[0] 'a' + pipe 2 res_word=CASEI followup=SEQ prog[0] 'cmd1' + pipe 3 res_word=MATCH followup=SEQ prog[0] 'b' + pipe 4 res_word=CASEI followup=SEQ prog[0] 'cmd2' + pipe 5 res_word=CASEI followup=SEQ prog[0] 'cmd3' + pipe 6 res_word=ESAC followup=SEQ + pipe 7 res_word=NONE followup=(null) +pipe 1 res_word=NONE followup=SEQ + + +2008-01 + + Command execution + +/* callsite: process_command_subs */ +generate_stream_from_list(struct pipe *head) - handles `cmds` + create UNIX pipe + [v]fork + child: + redirect pipe output to stdout + _exit(run_list(head)); /* leaks memory */ + parent: + return UNIX pipe's output fd + /* head is freed by the caller */ + +/* callsite: parse_and_run_stream */ +run_and_free_list(struct pipe *) + run_list(struct pipe *) + free_pipe_list(struct pipe *) + +/* callsites: generate_stream_from_list, run_and_free_list, pseudo_exec, run_pipe */ +run_list(struct pipe *) - handles "cmd; cmd2 && cmd3", while/for/do loops + run_pipe - for every pipe in list + +/* callsite: run_list */ +run_pipe - runs "cmd1 | cmd2 | cmd3 [&]" + run_list - used if only one cmd and it is of the form "{cmds;}" + forks for every cmd if more than one cmd or if & is there + pseudo_exec - runs each "cmdN" (handles builtins etc) + +/* callsite: run_pipe */ +pseudo_exec - runs "cmd" (handles builtins etc) + exec - execs external programs + run_list - used if cmdN is "(cmds)" or "{cmds;}" + /* problem: putenv's malloced strings into environ - + ** with vfork they will leak into parent process + */ + /* problem with ENABLE_FEATURE_SH_STANDALONE: + ** run_applet_no_and_exit(a, argv) uses exit - this can interfere + ** with vfork - switch to _exit there? + */ diff --git a/shell/hush_leaktool.sh b/shell/hush_leaktool.sh new file mode 100644 index 0000000..54161b3 --- /dev/null +++ b/shell/hush_leaktool.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +# hush's stderr with leak debug enabled +output=output + +freelist=`grep 'free 0x' "$output" | cut -d' ' -f2 | sort | uniq | xargs` + +grep -v free "$output" >temp1 +for freed in $freelist; do + echo Dropping $freed + grep -v $freed temp2 + mv temp2 temp1 +done diff --git a/shell/hush_test/hush-glob/glob1.right b/shell/hush_test/hush-glob/glob1.right new file mode 100644 index 0000000..f29ab4e --- /dev/null +++ b/shell/hush_test/hush-glob/glob1.right @@ -0,0 +1,2 @@ +glob1.tests +glob1.tests diff --git a/shell/hush_test/hush-glob/glob1.tests b/shell/hush_test/hush-glob/glob1.tests new file mode 100755 index 0000000..f980ce0 --- /dev/null +++ b/shell/hush_test/hush-glob/glob1.tests @@ -0,0 +1,2 @@ +echo *glob1?t[e]sts* +echo "glob1"?'t'[e]s* diff --git a/shell/hush_test/hush-glob/glob_and_assign.right b/shell/hush_test/hush-glob/glob_and_assign.right new file mode 100644 index 0000000..d46e443 --- /dev/null +++ b/shell/hush_test/hush-glob/glob_and_assign.right @@ -0,0 +1,6 @@ +ZVAR=z.tmp ZVAR=*.tmp ZVAR=[z].tmp +ZVAR=z.tmp ZVAR=*.tmp ZVAR=[z].tmp +*.tmp +ZVAR=z.tmp z.tmp +ZVAR=z.tmp ZVAR=*.tmp ZVAR=[z].tmp +ZVAR=z.tmp ZVAR=*.tmp ZVAR=[z].tmp diff --git a/shell/hush_test/hush-glob/glob_and_assign.tests b/shell/hush_test/hush-glob/glob_and_assign.tests new file mode 100755 index 0000000..0b158f2 --- /dev/null +++ b/shell/hush_test/hush-glob/glob_and_assign.tests @@ -0,0 +1,10 @@ +>ZVAR=z.tmp +>z.tmp +ZVAR=*.tmp echo ZVAR=*.tmp "ZVAR=*.tmp" "ZVAR=[z].tmp" +ZVAR=*.tmp /bin/echo ZVAR=*.tmp "ZVAR=*.tmp" "ZVAR=[z].tmp" +ZVAR=*.tmp +echo "$ZVAR" +echo $ZVAR +echo ZVAR=*.tmp "ZVAR=*.tmp" "ZVAR=[z].tmp" +/bin/echo ZVAR=*.tmp "ZVAR=*.tmp" "ZVAR=[z].tmp" +rm ZVAR=z.tmp z.tmp diff --git a/shell/hush_test/hush-glob/glob_redir.right b/shell/hush_test/hush-glob/glob_redir.right new file mode 100644 index 0000000..fbd0309 --- /dev/null +++ b/shell/hush_test/hush-glob/glob_redir.right @@ -0,0 +1,2 @@ +z.tmp: +?.tmp: TEST diff --git a/shell/hush_test/hush-glob/glob_redir.tests b/shell/hush_test/hush-glob/glob_redir.tests new file mode 100755 index 0000000..621d120 --- /dev/null +++ b/shell/hush_test/hush-glob/glob_redir.tests @@ -0,0 +1,9 @@ +# Redirections are not globbed. +# bash: +# if run as "sh", they are not globbed, but +# if run as "bash", they are! +>z.tmp +echo TEST >?.tmp +echo 'z.tmp:' `cat 'z.tmp'` +echo '?.tmp:' `cat '?.tmp'` +rm 'z.tmp' '?.tmp' diff --git a/shell/hush_test/hush-misc/assignment1.right b/shell/hush_test/hush-misc/assignment1.right new file mode 100644 index 0000000..d0a13d3 --- /dev/null +++ b/shell/hush_test/hush-misc/assignment1.right @@ -0,0 +1,9 @@ +if1:0 +while1:0 +until1:0 +if2:0 +while2:0 +until2:0 +if3:0 +while3:0 +until3:0 diff --git a/shell/hush_test/hush-misc/assignment1.tests b/shell/hush_test/hush-misc/assignment1.tests new file mode 100755 index 0000000..033b352 --- /dev/null +++ b/shell/hush_test/hush-misc/assignment1.tests @@ -0,0 +1,42 @@ +# Assignments after some keywords should still work + +if a=1 true; then a=1 true; elif a=1 true; then a=1 true; else a=1 true; fi +echo if1:$? +while a=1 true; do a=1 true; break; done +echo while1:$? +until a=1 false; do a=1 true; break; done +echo until1:$? + +if a=1 true + then a=1 true + elif a=1 true + then a=1 true + else a=1 true + fi +echo if2:$? +while a=1 true + do a=1 true + break + done +echo while2:$? +until a=1 false + do a=1 true + break + done +echo until2:$? + +if + a=1 true; then + a=1 true; elif + a=1 true; then + a=1 true; else + a=1 true; fi +echo if3:$? +while + a=1 true; do + a=1 true; break; done +echo while3:$? +until + a=1 false; do + a=1 true; break; done +echo until3:$? diff --git a/shell/hush_test/hush-misc/assignment2.rigth b/shell/hush_test/hush-misc/assignment2.rigth new file mode 100644 index 0000000..591552c --- /dev/null +++ b/shell/hush_test/hush-misc/assignment2.rigth @@ -0,0 +1,2 @@ +hush: can't exec 'a=b': No such file or directory +1 diff --git a/shell/hush_test/hush-misc/assignment2.tests b/shell/hush_test/hush-misc/assignment2.tests new file mode 100755 index 0000000..540e01e --- /dev/null +++ b/shell/hush_test/hush-misc/assignment2.tests @@ -0,0 +1,4 @@ +# This must not be interpreted as an assignment +a''=b true +echo $? +# (buglet: $? should be 127. it is currently 1) diff --git a/shell/hush_test/hush-misc/break1.right b/shell/hush_test/hush-misc/break1.right new file mode 100644 index 0000000..04a4b17 --- /dev/null +++ b/shell/hush_test/hush-misc/break1.right @@ -0,0 +1,2 @@ +A +OK:0 diff --git a/shell/hush_test/hush-misc/break1.tests b/shell/hush_test/hush-misc/break1.tests new file mode 100755 index 0000000..912f149 --- /dev/null +++ b/shell/hush_test/hush-misc/break1.tests @@ -0,0 +1,3 @@ +while true; do echo A; break; echo B; done +echo OK:$? + diff --git a/shell/hush_test/hush-misc/break2.right b/shell/hush_test/hush-misc/break2.right new file mode 100644 index 0000000..8a15cb9 --- /dev/null +++ b/shell/hush_test/hush-misc/break2.right @@ -0,0 +1,3 @@ +A +AA +OK:0 diff --git a/shell/hush_test/hush-misc/break2.tests b/shell/hush_test/hush-misc/break2.tests new file mode 100755 index 0000000..7da9faf --- /dev/null +++ b/shell/hush_test/hush-misc/break2.tests @@ -0,0 +1,6 @@ +while true; do + echo A + while true; do echo AA; break 2; echo BB; done + echo B +done +echo OK:$? diff --git a/shell/hush_test/hush-misc/break3.right b/shell/hush_test/hush-misc/break3.right new file mode 100644 index 0000000..04a4b17 --- /dev/null +++ b/shell/hush_test/hush-misc/break3.right @@ -0,0 +1,2 @@ +A +OK:0 diff --git a/shell/hush_test/hush-misc/break3.tests b/shell/hush_test/hush-misc/break3.tests new file mode 100755 index 0000000..d138dca --- /dev/null +++ b/shell/hush_test/hush-misc/break3.tests @@ -0,0 +1,2 @@ +v=break; while true; do echo A; $v; echo B; break; echo C; done +echo OK:$? diff --git a/shell/hush_test/hush-misc/break4.right b/shell/hush_test/hush-misc/break4.right new file mode 100644 index 0000000..6f41c14 --- /dev/null +++ b/shell/hush_test/hush-misc/break4.right @@ -0,0 +1,6 @@ +A +AA +TRUE +A +AA +OK:0 diff --git a/shell/hush_test/hush-misc/break4.tests b/shell/hush_test/hush-misc/break4.tests new file mode 100755 index 0000000..67da288 --- /dev/null +++ b/shell/hush_test/hush-misc/break4.tests @@ -0,0 +1,12 @@ +cond=true +while $cond; do + echo A + if test "$cond" = true; then + cond='echo TRUE' + else + cond=false + fi + while true; do echo AA; continue 2; echo BB; done + echo B +done +echo OK:$? diff --git a/shell/hush_test/hush-misc/break5.right b/shell/hush_test/hush-misc/break5.right new file mode 100644 index 0000000..0b9df2a --- /dev/null +++ b/shell/hush_test/hush-misc/break5.right @@ -0,0 +1,13 @@ +A +B +0 +A:a +B +D +A:b +B +D +A:c +B +D +0 diff --git a/shell/hush_test/hush-misc/break5.tests b/shell/hush_test/hush-misc/break5.tests new file mode 100755 index 0000000..273e040 --- /dev/null +++ b/shell/hush_test/hush-misc/break5.tests @@ -0,0 +1,4 @@ +while true; do echo A; { echo B; break; echo C; }; echo D; done +echo $? +for v in a b c; do echo A:$v; (echo B; break; echo C); echo D; done +echo $? diff --git a/shell/hush_test/hush-misc/builtin1.right b/shell/hush_test/hush-misc/builtin1.right new file mode 100644 index 0000000..2e55ecb --- /dev/null +++ b/shell/hush_test/hush-misc/builtin1.right @@ -0,0 +1,2 @@ +VARIABLE=export +OK:0 diff --git a/shell/hush_test/hush-misc/builtin1.tests b/shell/hush_test/hush-misc/builtin1.tests new file mode 100755 index 0000000..1a2941f --- /dev/null +++ b/shell/hush_test/hush-misc/builtin1.tests @@ -0,0 +1,6 @@ +# builtins, unlike keywords like "while", can be constructed +# with substitutions +VARIABLE=export +$VARIABLE VARIABLE +env | grep ^VARIABLE +echo OK:$? diff --git a/shell/hush_test/hush-misc/case1.right b/shell/hush_test/hush-misc/case1.right new file mode 100644 index 0000000..e9e371a --- /dev/null +++ b/shell/hush_test/hush-misc/case1.right @@ -0,0 +1,14 @@ +OK_1 +OK_1 +OK_21 +OK_22 +OK_23 +OK_31 +OK_32 +OK_41 +OK_42 +OK_43 +OK_44 +OK_51 +OK_52 +OK_53 diff --git a/shell/hush_test/hush-misc/case1.tests b/shell/hush_test/hush-misc/case1.tests new file mode 100755 index 0000000..0174893 --- /dev/null +++ b/shell/hush_test/hush-misc/case1.tests @@ -0,0 +1,25 @@ +case w in a) echo SKIP;; w) echo OK_1;; w) echo WRONG;; esac + +case w in + a) echo SKIP;; + w)echo OK_1 ;; + w) + echo WRONG + ;; +esac + +t=w +case $t in a) echo SKIP;; w) echo OK_21;; w) echo WRONG;; esac; +case "$t" in a) echo SKIP;; w) echo OK_22;; w) echo WRONG;; esac; +case w in a) echo SKIP;; $t) echo OK_23;; "$t") echo WRONG;; esac; + +case '' in a) echo SKIP;; w) echo WRONG;; *) echo OK_31;; esac; +case '' in a) echo SKIP;; '') echo OK_32;; *) echo WRONG;; esac; + +case `echo w` in a) echo SKIP;; w) echo OK_41;; w) echo WRONG;; esac; +case "`echo w`" in a) echo SKIP;; w) echo OK_42;; w) echo WRONG;; esac; +case `echo w w` in a) echo SKIP;; w) echo WRONG;; 'w w') echo OK_43;; esac; +case `echo w w` in a) echo SKIP;; w) echo WRONG;; w*) echo OK_44;; esac; + +case w in `echo w`) echo OK_51;; `echo WRONG >&2`w) echo WRONG;; esac; +case w in `echo OK_52 >&2`) echo SKIP;; `echo`w) echo OK_53;; esac; diff --git a/shell/hush_test/hush-misc/colon.right b/shell/hush_test/hush-misc/colon.right new file mode 100644 index 0000000..2a87d02 --- /dev/null +++ b/shell/hush_test/hush-misc/colon.right @@ -0,0 +1,2 @@ +0 +OK: 0 diff --git a/shell/hush_test/hush-misc/colon.tests b/shell/hush_test/hush-misc/colon.tests new file mode 100755 index 0000000..cb8ab53 --- /dev/null +++ b/shell/hush_test/hush-misc/colon.tests @@ -0,0 +1,5 @@ +false +: +echo $? +(while :; do exit; done) +echo OK: $? diff --git a/shell/hush_test/hush-misc/continue1.right b/shell/hush_test/hush-misc/continue1.right new file mode 100644 index 0000000..c4a5565 --- /dev/null +++ b/shell/hush_test/hush-misc/continue1.right @@ -0,0 +1,8 @@ +A:a +A:b +A:c +OK1 +A:a +A:b +A:c +OK2 diff --git a/shell/hush_test/hush-misc/continue1.tests b/shell/hush_test/hush-misc/continue1.tests new file mode 100755 index 0000000..72d3566 --- /dev/null +++ b/shell/hush_test/hush-misc/continue1.tests @@ -0,0 +1,4 @@ +for v in a b c; do echo A:$v; continue 666; done +echo OK1 +for v in a b c; do echo A:$v; continue 666; done +echo OK2 diff --git a/shell/hush_test/hush-misc/empty_for.right b/shell/hush_test/hush-misc/empty_for.right new file mode 100644 index 0000000..290d39b --- /dev/null +++ b/shell/hush_test/hush-misc/empty_for.right @@ -0,0 +1 @@ +OK: 0 diff --git a/shell/hush_test/hush-misc/empty_for.tests b/shell/hush_test/hush-misc/empty_for.tests new file mode 100755 index 0000000..0cb52e8 --- /dev/null +++ b/shell/hush_test/hush-misc/empty_for.tests @@ -0,0 +1,3 @@ +false +for a in; do echo "HELLO"; done +echo OK: $? diff --git a/shell/hush_test/hush-misc/empty_for2.right b/shell/hush_test/hush-misc/empty_for2.right new file mode 100644 index 0000000..1acee9e --- /dev/null +++ b/shell/hush_test/hush-misc/empty_for2.right @@ -0,0 +1,4 @@ +PARAM:abc +PARAM:d e +PARAM:123 +OK: 0 diff --git a/shell/hush_test/hush-misc/empty_for2.tests b/shell/hush_test/hush-misc/empty_for2.tests new file mode 100755 index 0000000..2b12ec2 --- /dev/null +++ b/shell/hush_test/hush-misc/empty_for2.tests @@ -0,0 +1,6 @@ +if test $# = 0; then + exec "$THIS_SH" $0 abc "d e" 123 +fi +false +for v; do echo "PARAM:$v"; done +echo OK: $? diff --git a/shell/hush_test/hush-misc/for_with_keywords.right b/shell/hush_test/hush-misc/for_with_keywords.right new file mode 100644 index 0000000..eb04e9a --- /dev/null +++ b/shell/hush_test/hush-misc/for_with_keywords.right @@ -0,0 +1,4 @@ +do +done +then +OK: 0 diff --git a/shell/hush_test/hush-misc/for_with_keywords.tests b/shell/hush_test/hush-misc/for_with_keywords.tests new file mode 100755 index 0000000..a8b8e42 --- /dev/null +++ b/shell/hush_test/hush-misc/for_with_keywords.tests @@ -0,0 +1,2 @@ +for if in do done then; do echo $if; done +echo OK: $? diff --git a/shell/hush_test/hush-misc/pid.right b/shell/hush_test/hush-misc/pid.right new file mode 100644 index 0000000..573541a --- /dev/null +++ b/shell/hush_test/hush-misc/pid.right @@ -0,0 +1 @@ +0 diff --git a/shell/hush_test/hush-misc/pid.tests b/shell/hush_test/hush-misc/pid.tests new file mode 100755 index 0000000..eaeaa71 --- /dev/null +++ b/shell/hush_test/hush-misc/pid.tests @@ -0,0 +1 @@ +test `(echo $$)` = `echo $$`; echo $? diff --git a/shell/hush_test/hush-misc/read.right b/shell/hush_test/hush-misc/read.right new file mode 100644 index 0000000..0e50e2a --- /dev/null +++ b/shell/hush_test/hush-misc/read.right @@ -0,0 +1,4 @@ +read +cat +echo "REPLY=$REPLY" +REPLY=exec 1 diff --git a/shell/hush_test/hush-parsing/quote2.tests b/shell/hush_test/hush-parsing/quote2.tests new file mode 100755 index 0000000..bd966f3 --- /dev/null +++ b/shell/hush_test/hush-parsing/quote2.tests @@ -0,0 +1,2 @@ +a=1 +echo ">$a" diff --git a/shell/hush_test/hush-parsing/quote3.right b/shell/hush_test/hush-parsing/quote3.right new file mode 100644 index 0000000..bbe46df --- /dev/null +++ b/shell/hush_test/hush-parsing/quote3.right @@ -0,0 +1,12 @@ +Testing: in "" +.. +Testing: in '' +.. +Testing: in $empty +Testing: in $empty"" +.. +Testing: in $empty'' +.. +Testing: in "$empty" +.. +Finished diff --git a/shell/hush_test/hush-parsing/quote3.tests b/shell/hush_test/hush-parsing/quote3.tests new file mode 100755 index 0000000..b5fd597 --- /dev/null +++ b/shell/hush_test/hush-parsing/quote3.tests @@ -0,0 +1,21 @@ +empty='' + +echo 'Testing: in ""' +for a in ""; do echo ".$a."; done + +echo 'Testing: in '"''" +for a in ''; do echo ".$a."; done + +echo 'Testing: in $empty' +for a in $empty; do echo ".$a."; done + +echo 'Testing: in $empty""' +for a in $empty""; do echo ".$a."; done + +echo 'Testing: in $empty'"''" +for a in $empty''; do echo ".$a."; done + +echo 'Testing: in "$empty"' +for a in "$empty"; do echo ".$a."; done + +echo Finished diff --git a/shell/hush_test/hush-parsing/quote4.right b/shell/hush_test/hush-parsing/quote4.right new file mode 100644 index 0000000..b2901ea --- /dev/null +++ b/shell/hush_test/hush-parsing/quote4.right @@ -0,0 +1 @@ +a b diff --git a/shell/hush_test/hush-parsing/quote4.tests b/shell/hush_test/hush-parsing/quote4.tests new file mode 100755 index 0000000..f1dabfa --- /dev/null +++ b/shell/hush_test/hush-parsing/quote4.tests @@ -0,0 +1,2 @@ +a_b='a b' +echo "$a_b" diff --git a/shell/hush_test/hush-parsing/redir_space.right b/shell/hush_test/hush-parsing/redir_space.right new file mode 100644 index 0000000..0842952 --- /dev/null +++ b/shell/hush_test/hush-parsing/redir_space.right @@ -0,0 +1,3 @@ +z1.tmp: 1 +z2.tmp: 1 +"z1.tmp z2.tmp": TEST 0 diff --git a/shell/hush_test/hush-parsing/redir_space.tests b/shell/hush_test/hush-parsing/redir_space.tests new file mode 100755 index 0000000..c0b5430 --- /dev/null +++ b/shell/hush_test/hush-parsing/redir_space.tests @@ -0,0 +1,6 @@ +v='z1.tmp z2.tmp' +echo TEST >$v +echo 'z1.tmp:' `cat 'z1.tmp' 2>/dev/null; echo $?` +echo 'z2.tmp:' `cat 'z2.tmp' 2>/dev/null; echo $?` +echo '"z1.tmp z2.tmp":' `cat 'z1.tmp z2.tmp' 2>/dev/null; echo $?` +rm z*.tmp diff --git a/shell/hush_test/hush-parsing/starquoted.right b/shell/hush_test/hush-parsing/starquoted.right new file mode 100644 index 0000000..b56323f --- /dev/null +++ b/shell/hush_test/hush-parsing/starquoted.right @@ -0,0 +1,8 @@ +.1 abc d e f. +.1. +.abc. +.d e f. +.-1 abc d e f-. +.-1. +.abc. +.d e f-. diff --git a/shell/hush_test/hush-parsing/starquoted.tests b/shell/hush_test/hush-parsing/starquoted.tests new file mode 100755 index 0000000..2fe49b1 --- /dev/null +++ b/shell/hush_test/hush-parsing/starquoted.tests @@ -0,0 +1,8 @@ +if test $# = 0; then + exec "$THIS_SH" "$0" 1 abc 'd e f' +fi + +for a in "$*"; do echo ".$a."; done +for a in "$@"; do echo ".$a."; done +for a in "-$*-"; do echo ".$a."; done +for a in "-$@-"; do echo ".$a."; done diff --git a/shell/hush_test/hush-parsing/starquoted2.right b/shell/hush_test/hush-parsing/starquoted2.right new file mode 100644 index 0000000..46f2436 --- /dev/null +++ b/shell/hush_test/hush-parsing/starquoted2.right @@ -0,0 +1,2 @@ +Should be printed +Should be printed diff --git a/shell/hush_test/hush-parsing/starquoted2.tests b/shell/hush_test/hush-parsing/starquoted2.tests new file mode 100755 index 0000000..782d71b --- /dev/null +++ b/shell/hush_test/hush-parsing/starquoted2.tests @@ -0,0 +1,14 @@ +if test $# != 0; then + exec "$THIS_SH" "$0" +fi + +# No params! +for a in "$*"; do echo Should be printed; done +for a in "$@"; do echo Should not be printed; done +# Yes, believe it or not, bash is mesmerized by "$@" and stops +# treating "" as "this word cannot be expanded to nothing, +# but must be at least null string". Now it can be expanded to nothing. +for a in "$@"""; do echo Should not be printed; done +for a in """$@"; do echo Should not be printed; done +for a in """$@"''"$@"''; do echo Should not be printed; done +for a in ""; do echo Should be printed; done diff --git a/shell/hush_test/hush-psubst/tick.right b/shell/hush_test/hush-psubst/tick.right new file mode 100644 index 0000000..6ed281c --- /dev/null +++ b/shell/hush_test/hush-psubst/tick.right @@ -0,0 +1,2 @@ +1 +1 diff --git a/shell/hush_test/hush-psubst/tick.tests b/shell/hush_test/hush-psubst/tick.tests new file mode 100755 index 0000000..1f749a9 --- /dev/null +++ b/shell/hush_test/hush-psubst/tick.tests @@ -0,0 +1,4 @@ +true +false; echo `echo $?` +true +{ false; echo `echo $?`; } diff --git a/shell/hush_test/hush-psubst/tick2.right b/shell/hush_test/hush-psubst/tick2.right new file mode 100644 index 0000000..216c883 --- /dev/null +++ b/shell/hush_test/hush-psubst/tick2.right @@ -0,0 +1 @@ +BAZ diff --git a/shell/hush_test/hush-psubst/tick2.tests b/shell/hush_test/hush-psubst/tick2.tests new file mode 100755 index 0000000..db4e944 --- /dev/null +++ b/shell/hush_test/hush-psubst/tick2.tests @@ -0,0 +1,5 @@ +if false; then + echo "FOO" + tmp=`echo BAR >&2` +fi +echo BAZ diff --git a/shell/hush_test/hush-psubst/tick3.right b/shell/hush_test/hush-psubst/tick3.right new file mode 100644 index 0000000..dc84e92 --- /dev/null +++ b/shell/hush_test/hush-psubst/tick3.right @@ -0,0 +1,6 @@ +\TESTZZBEST +$TEST +Q +a\bc +a"c +done:0 diff --git a/shell/hush_test/hush-psubst/tick3.tests b/shell/hush_test/hush-psubst/tick3.tests new file mode 100755 index 0000000..2b055bb --- /dev/null +++ b/shell/hush_test/hush-psubst/tick3.tests @@ -0,0 +1,10 @@ +TEST=Q +# \` is special +echo `echo '\'TEST\`echo ZZ\`BEST` +# \$ and \\ are special +echo `echo \\$TEST` +echo `echo \$TEST` +echo a`echo \\\\b`c +# \" etc are NOT special (passed verbatim WITH \)! +echo a`echo \"`c +echo done:$? diff --git a/shell/hush_test/hush-psubst/tick4.right b/shell/hush_test/hush-psubst/tick4.right new file mode 100644 index 0000000..d8030ea --- /dev/null +++ b/shell/hush_test/hush-psubst/tick4.right @@ -0,0 +1,7 @@ +(TEST) BEST +TEST) BEST +((TEST) BEST +) +abc +a)c +OK: 0 diff --git a/shell/hush_test/hush-psubst/tick4.tests b/shell/hush_test/hush-psubst/tick4.tests new file mode 100755 index 0000000..f2305fb --- /dev/null +++ b/shell/hush_test/hush-psubst/tick4.tests @@ -0,0 +1,7 @@ +echo $(echo '(TEST)' BEST) +echo $(echo 'TEST)' BEST) +echo $(echo \(\(TEST\) BEST) +echo $(echo \)) +echo $(echo a"`echo "b"`"c ) +echo $(echo a"`echo ")"`"c ) +echo OK: $? diff --git a/shell/hush_test/hush-vars/empty.right b/shell/hush_test/hush-vars/empty.right new file mode 100644 index 0000000..2cb3c70 --- /dev/null +++ b/shell/hush_test/hush-vars/empty.right @@ -0,0 +1,3 @@ +a b c d e f 1 2 3 4 5 6 7 8 9 0 A B C D E F +a b c d e f 1 2 3 4 5 6 7 8 9 0 A B C D E F +a b c d e f 1 2 3 4 5 6 7 8 9 0 A B C D E F diff --git a/shell/hush_test/hush-vars/empty.tests b/shell/hush_test/hush-vars/empty.tests new file mode 100755 index 0000000..a9c247e --- /dev/null +++ b/shell/hush_test/hush-vars/empty.tests @@ -0,0 +1,5 @@ +e= + +echo a b c d e f 1 2 3 4 5 6 7 8 9 0 A B C D E F +echo a $e b $e c $e d $e e $e f $e 1 $e 2 $e 3 $e 4 $e 5 $e 6 $e 7 $e 8 $e 9 $e 0 $e A $e B $e C $e D $e E $e F +echo $e a $e b $e c $e d $e e $e f $e 1 $e 2 $e 3 $e 4 $e 5 $e 6 $e 7 $e 8 $e 9 $e 0 $e A $e B $e C $e D $e E $e F diff --git a/shell/hush_test/hush-vars/glob_and_vars.right b/shell/hush_test/hush-vars/glob_and_vars.right new file mode 100644 index 0000000..3ac7ec5 --- /dev/null +++ b/shell/hush_test/hush-vars/glob_and_vars.right @@ -0,0 +1 @@ +./glob_and_vars.right ./glob_and_vars.tests diff --git a/shell/hush_test/hush-vars/glob_and_vars.tests b/shell/hush_test/hush-vars/glob_and_vars.tests new file mode 100755 index 0000000..482cf9d --- /dev/null +++ b/shell/hush_test/hush-vars/glob_and_vars.tests @@ -0,0 +1,2 @@ +v=. +echo $v/glob_and_vars.[tr]* diff --git a/shell/hush_test/hush-vars/param_glob.right b/shell/hush_test/hush-vars/param_glob.right new file mode 100644 index 0000000..bdee8fe --- /dev/null +++ b/shell/hush_test/hush-vars/param_glob.right @@ -0,0 +1,4 @@ +param_glob.tests +param_glob.tests +param_glob.t* +param_glob.t* diff --git a/shell/hush_test/hush-vars/param_glob.tests b/shell/hush_test/hush-vars/param_glob.tests new file mode 100755 index 0000000..801d58e --- /dev/null +++ b/shell/hush_test/hush-vars/param_glob.tests @@ -0,0 +1,10 @@ +if test $# = 0; then + #BUG in builtin_exec! will glob param! + #exec "$THIS_SH" "$0" 'param_glob.t*' + "$THIS_SH" "$0" 'param_glob.t*' + exit +fi +echo $* +echo $@ +echo "$*" +echo "$@" diff --git a/shell/hush_test/hush-vars/star.right b/shell/hush_test/hush-vars/star.right new file mode 100644 index 0000000..0ecc55b --- /dev/null +++ b/shell/hush_test/hush-vars/star.right @@ -0,0 +1,6 @@ +.1. +.abc. +.d. +.e. +.f. +.1 abc d e f. diff --git a/shell/hush_test/hush-vars/star.tests b/shell/hush_test/hush-vars/star.tests new file mode 100755 index 0000000..5554c40 --- /dev/null +++ b/shell/hush_test/hush-vars/star.tests @@ -0,0 +1,8 @@ +if test $# = 0; then + exec "$THIS_SH" star.tests 1 abc 'd e f' +fi +# 'd e f' should be split into 3 separate args: +for a in $*; do echo ".$a."; done + +# must produce .1 abc d e f. +for a in "$*"; do echo ".$a."; done diff --git a/shell/hush_test/hush-vars/var1.right b/shell/hush_test/hush-vars/var1.right new file mode 100644 index 0000000..14b2314 --- /dev/null +++ b/shell/hush_test/hush-vars/var1.right @@ -0,0 +1,4 @@ +http://busybox.net +http://busybox.net_abc +1 +1 diff --git a/shell/hush_test/hush-vars/var1.tests b/shell/hush_test/hush-vars/var1.tests new file mode 100755 index 0000000..0a63696 --- /dev/null +++ b/shell/hush_test/hush-vars/var1.tests @@ -0,0 +1,9 @@ +URL=http://busybox.net + +echo $URL +echo ${URL}_abc + +true +false; echo $? +true +{ false; echo $?; } diff --git a/shell/hush_test/hush-vars/var2.right b/shell/hush_test/hush-vars/var2.right new file mode 100644 index 0000000..40bf4bf --- /dev/null +++ b/shell/hush_test/hush-vars/var2.right @@ -0,0 +1,2 @@ +http://busybox.net +http://busybox.net_abc diff --git a/shell/hush_test/hush-vars/var2.tests b/shell/hush_test/hush-vars/var2.tests new file mode 100755 index 0000000..1292410 --- /dev/null +++ b/shell/hush_test/hush-vars/var2.tests @@ -0,0 +1,4 @@ +_1=http://busybox.net + +echo $_1 +echo ${_1}_abc diff --git a/shell/hush_test/hush-vars/var_expand_in_assign.right b/shell/hush_test/hush-vars/var_expand_in_assign.right new file mode 100644 index 0000000..352210d --- /dev/null +++ b/shell/hush_test/hush-vars/var_expand_in_assign.right @@ -0,0 +1,5 @@ +. . +.abc d e. +.abc d e. +.abc d e. +.abc d e. diff --git a/shell/hush_test/hush-vars/var_expand_in_assign.tests b/shell/hush_test/hush-vars/var_expand_in_assign.tests new file mode 100755 index 0000000..18cdc74 --- /dev/null +++ b/shell/hush_test/hush-vars/var_expand_in_assign.tests @@ -0,0 +1,15 @@ +if test $# = 0; then + exec "$THIS_SH" "$0" abc "d e" +fi + +space=' ' +echo .$space. + +a=$* +echo .$a. +a=$@ +echo .$a. +a="$*" +echo .$a. +a="$@" +echo .$a. diff --git a/shell/hush_test/hush-vars/var_expand_in_redir.right b/shell/hush_test/hush-vars/var_expand_in_redir.right new file mode 100644 index 0000000..423299c --- /dev/null +++ b/shell/hush_test/hush-vars/var_expand_in_redir.right @@ -0,0 +1,3 @@ +TEST1 +TEST2 +TEST3 diff --git a/shell/hush_test/hush-vars/var_expand_in_redir.tests b/shell/hush_test/hush-vars/var_expand_in_redir.tests new file mode 100755 index 0000000..bda6bdd --- /dev/null +++ b/shell/hush_test/hush-vars/var_expand_in_redir.tests @@ -0,0 +1,13 @@ +if test $# = 0; then + exec "$THIS_SH" "$0" abc "d e" +fi + +echo TEST1 >"$1.out" +echo TEST2 >"$2.out" +# bash says: "$@.out": ambiguous redirect +# ash handles it as if it is '$*' - we do the same +echo TEST3 >"$@.out" + +cat abc.out "d e.out" "abc d e.out" + +rm abc.out "d e.out" "abc d e.out" diff --git a/shell/hush_test/hush-vars/var_leaks.right b/shell/hush_test/hush-vars/var_leaks.right new file mode 100644 index 0000000..d86bac9 --- /dev/null +++ b/shell/hush_test/hush-vars/var_leaks.right @@ -0,0 +1 @@ +OK diff --git a/shell/hush_test/hush-vars/var_leaks.tests b/shell/hush_test/hush-vars/var_leaks.tests new file mode 100755 index 0000000..27c8c65 --- /dev/null +++ b/shell/hush_test/hush-vars/var_leaks.tests @@ -0,0 +1,14 @@ +# external program +a=b /bin/true +env | grep ^a= + +# builtin +a=b true +env | grep ^a= + +# exec with redirection only +# in bash, this leaks! +a=b exec 1>&1 +env | grep ^a= + +echo OK diff --git a/shell/hush_test/hush-vars/var_preserved.right b/shell/hush_test/hush-vars/var_preserved.right new file mode 100644 index 0000000..2a9917c --- /dev/null +++ b/shell/hush_test/hush-vars/var_preserved.right @@ -0,0 +1,4 @@ +a=b +a=b +a=b +OK diff --git a/shell/hush_test/hush-vars/var_preserved.tests b/shell/hush_test/hush-vars/var_preserved.tests new file mode 100755 index 0000000..1bddd87 --- /dev/null +++ b/shell/hush_test/hush-vars/var_preserved.tests @@ -0,0 +1,16 @@ +export a=b + +# external program +a=c /bin/true +env | grep ^a= + +# builtin +a=d true +env | grep ^a= + +# exec with redirection only +# in bash, this leaks! +a=e exec 1>&1 +env | grep ^a= + +echo OK diff --git a/shell/hush_test/hush-vars/var_subst_in_for.right b/shell/hush_test/hush-vars/var_subst_in_for.right new file mode 100644 index 0000000..c8aca1c --- /dev/null +++ b/shell/hush_test/hush-vars/var_subst_in_for.right @@ -0,0 +1,40 @@ +Testing: in x y z +.x. +.y. +.z. +Testing: in u $empty v +.u. +.v. +Testing: in u " $empty" v +.u. +. . +.v. +Testing: in u $empty $empty$a v +.u. +.a. +.v. +Testing: in $a_b +.a. +.b. +Testing: in $* +.abc. +.d. +.e. +Testing: in $@ +.abc. +.d. +.e. +Testing: in -$*- +.-abc. +.d. +.e-. +Testing: in -$@- +.-abc. +.d. +.e-. +Testing: in $a_b -$a_b- +.a. +.b. +.-a. +.b-. +Finished diff --git a/shell/hush_test/hush-vars/var_subst_in_for.tests b/shell/hush_test/hush-vars/var_subst_in_for.tests new file mode 100755 index 0000000..433c606 --- /dev/null +++ b/shell/hush_test/hush-vars/var_subst_in_for.tests @@ -0,0 +1,40 @@ +if test $# = 0; then + exec "$THIS_SH" "$0" abc "d e" +fi + +echo 'Testing: in x y z' +for a in x y z; do echo ".$a."; done + +echo 'Testing: in u $empty v' +empty='' +for a in u $empty v; do echo ".$a."; done + +echo 'Testing: in u " $empty" v' +empty='' +for a in u " $empty" v; do echo ".$a."; done + +echo 'Testing: in u $empty $empty$a v' +a='a' +for a in u $empty $empty$a v; do echo ".$a."; done + +echo 'Testing: in $a_b' +a_b='a b' +for a in $a_b; do echo ".$a."; done + +echo 'Testing: in $*' +for a in $*; do echo ".$a."; done + +echo 'Testing: in $@' +for a in $@; do echo ".$a."; done + +echo 'Testing: in -$*-' +for a in -$*-; do echo ".$a."; done + +echo 'Testing: in -$@-' +for a in -$@-; do echo ".$a."; done + +echo 'Testing: in $a_b -$a_b-' +a_b='a b' +for a in $a_b -$a_b-; do echo ".$a."; done + +echo Finished diff --git a/shell/hush_test/hush-z_slow/leak_var.right b/shell/hush_test/hush-z_slow/leak_var.right new file mode 100644 index 0000000..7bccc1e --- /dev/null +++ b/shell/hush_test/hush-z_slow/leak_var.right @@ -0,0 +1,2 @@ +Measuring memory leak... +vsz does not grow diff --git a/shell/hush_test/hush-z_slow/leak_var.tests b/shell/hush_test/hush-z_slow/leak_var.tests new file mode 100755 index 0000000..b3e13e3 --- /dev/null +++ b/shell/hush_test/hush-z_slow/leak_var.tests @@ -0,0 +1,138 @@ +pid=$$ + +# Warm up +beg=`ps -o pid,vsz | grep "^ *$pid "` +i=1 +while test $i != X; do + unset t + t=111111111111111111111111111111111111111111111111111111111111111111111111 + export t + unset t + t=111111111111111111111111111111111111111111111111111111111111111111111111 + export t + unset t + t=111111111111111111111111111111111111111111111111111111111111111111111111 + export t + unset t + t=111111111111111111111111111111111111111111111111111111111111111111111111 + export t + unset t + t=111111111111111111111111111111111111111111111111111111111111111111111111 + export t + i=1$i + if test $i = 1111111111111111111111111111111111111111111111; then i=2; fi + if test $i = 1111111111111111111111111111111111111111111112; then i=3; fi + if test $i = 1111111111111111111111111111111111111111111113; then i=4; fi + if test $i = 1111111111111111111111111111111111111111111114; then i=5; fi + if test $i = 1111111111111111111111111111111111111111111115; then i=6; fi + if test $i = 1111111111111111111111111111111111111111111116; then i=7; fi + if test $i = 1111111111111111111111111111111111111111111117; then i=8; fi + if test $i = 1111111111111111111111111111111111111111111118; then i=9; fi + if test $i = 1111111111111111111111111111111111111111111119; then i=a; fi + if test $i = 111111111111111111111111111111111111111111111a; then i=b; fi + if test $i = 111111111111111111111111111111111111111111111b; then i=c; fi + if test $i = 111111111111111111111111111111111111111111111c; then i=d; fi + if test $i = 111111111111111111111111111111111111111111111d; then i=e; fi + if test $i = 111111111111111111111111111111111111111111111e; then i=f; fi + if test $i = 111111111111111111111111111111111111111111111f; then i=g; fi + if test $i = 111111111111111111111111111111111111111111111g; then i=h; fi + if test $i = 111111111111111111111111111111111111111111111h; then i=i; fi + if test $i = 111111111111111111111111111111111111111111111i; then i=j; fi + if test $i = 111111111111111111111111111111111111111111111j; then i=X; fi +done +end=`ps -o pid,vsz | grep "^ *$pid "` + +# Warm up again (I do need it on my machine) +beg=`ps -o pid,vsz | grep "^ *$pid "` +i=1 +while test $i != X; do + unset t + t=111111111111111111111111111111111111111111111111111111111111111111111111 + export t + unset t + t=111111111111111111111111111111111111111111111111111111111111111111111111 + export t + unset t + t=111111111111111111111111111111111111111111111111111111111111111111111111 + export t + unset t + t=111111111111111111111111111111111111111111111111111111111111111111111111 + export t + unset t + t=111111111111111111111111111111111111111111111111111111111111111111111111 + export t + i=1$i + if test $i = 1111111111111111111111111111111111111111111111; then i=2; fi + if test $i = 1111111111111111111111111111111111111111111112; then i=3; fi + if test $i = 1111111111111111111111111111111111111111111113; then i=4; fi + if test $i = 1111111111111111111111111111111111111111111114; then i=5; fi + if test $i = 1111111111111111111111111111111111111111111115; then i=6; fi + if test $i = 1111111111111111111111111111111111111111111116; then i=7; fi + if test $i = 1111111111111111111111111111111111111111111117; then i=8; fi + if test $i = 1111111111111111111111111111111111111111111118; then i=9; fi + if test $i = 1111111111111111111111111111111111111111111119; then i=a; fi + if test $i = 111111111111111111111111111111111111111111111a; then i=b; fi + if test $i = 111111111111111111111111111111111111111111111b; then i=c; fi + if test $i = 111111111111111111111111111111111111111111111c; then i=d; fi + if test $i = 111111111111111111111111111111111111111111111d; then i=e; fi + if test $i = 111111111111111111111111111111111111111111111e; then i=f; fi + if test $i = 111111111111111111111111111111111111111111111f; then i=g; fi + if test $i = 111111111111111111111111111111111111111111111g; then i=h; fi + if test $i = 111111111111111111111111111111111111111111111h; then i=i; fi + if test $i = 111111111111111111111111111111111111111111111i; then i=j; fi + if test $i = 111111111111111111111111111111111111111111111j; then i=X; fi +done +end=`ps -o pid,vsz | grep "^ *$pid "` +if test "$beg" != "$end"; then + true echo "vsz grows: $beg -> $end" +else + true echo "vsz does not grow" +fi + +echo "Measuring memory leak..." +beg=`ps -o pid,vsz | grep "^ *$pid "` +i=1 +while test $i != X; do + unset t + t=111111111111111111111111111111111111111111111111111111111111111111111111 + export t + unset t + t=111111111111111111111111111111111111111111111111111111111111111111111111 + export t + unset t + t=111111111111111111111111111111111111111111111111111111111111111111111111 + export t + unset t + t=111111111111111111111111111111111111111111111111111111111111111111111111 + export t + unset t + t=111111111111111111111111111111111111111111111111111111111111111111111111 + export t + i=1$i + if test $i = 1111111111111111111111111111111111111111111111; then i=2; fi + if test $i = 1111111111111111111111111111111111111111111112; then i=3; fi + if test $i = 1111111111111111111111111111111111111111111113; then i=4; fi + if test $i = 1111111111111111111111111111111111111111111114; then i=5; fi + if test $i = 1111111111111111111111111111111111111111111115; then i=6; fi + if test $i = 1111111111111111111111111111111111111111111116; then i=7; fi + if test $i = 1111111111111111111111111111111111111111111117; then i=8; fi + if test $i = 1111111111111111111111111111111111111111111118; then i=9; fi + if test $i = 1111111111111111111111111111111111111111111119; then i=a; fi + if test $i = 111111111111111111111111111111111111111111111a; then i=b; fi + if test $i = 111111111111111111111111111111111111111111111b; then i=c; fi + if test $i = 111111111111111111111111111111111111111111111c; then i=d; fi + if test $i = 111111111111111111111111111111111111111111111d; then i=e; fi + if test $i = 111111111111111111111111111111111111111111111e; then i=f; fi + if test $i = 111111111111111111111111111111111111111111111f; then i=g; fi + if test $i = 111111111111111111111111111111111111111111111g; then i=h; fi + if test $i = 111111111111111111111111111111111111111111111h; then i=i; fi + if test $i = 111111111111111111111111111111111111111111111i; then i=j; fi + if test $i = 111111111111111111111111111111111111111111111j; then i=X; fi +done +end=`ps -o pid,vsz | grep "^ *$pid "` + +if test "$beg" != "$end"; then + echo "vsz grows: $beg -> $end" +else + echo "vsz does not grow" +fi diff --git a/shell/hush_test/hush-z_slow/leak_var2.right b/shell/hush_test/hush-z_slow/leak_var2.right new file mode 100644 index 0000000..7bccc1e --- /dev/null +++ b/shell/hush_test/hush-z_slow/leak_var2.right @@ -0,0 +1,2 @@ +Measuring memory leak... +vsz does not grow diff --git a/shell/hush_test/hush-z_slow/leak_var2.tests b/shell/hush_test/hush-z_slow/leak_var2.tests new file mode 100755 index 0000000..09f2475 --- /dev/null +++ b/shell/hush_test/hush-z_slow/leak_var2.tests @@ -0,0 +1,63 @@ +pid=$$ + +t=1 +export t + +# Warm up +beg=`ps -o pid,vsz | grep "^ *$pid "` +i=1 +while test $i != X; do + t=111111111111111111111111111111111111111111111111111111111111111111111110$i + t=111111111111111111111111111111111111111111111111111111111111111111111111$i true + t=111111111111111111111111111111111111111111111111111111111111111111111112$i /bin/true + t=111111111111111111111111111111111111111111111111111111111111111111111113$i exec 1>&1 + i=1$i + if test $i = 1111111111111111111111111111111111111111111111; then i=2; fi + if test $i = 1111111111111111111111111111111111111111111112; then i=3; fi + if test $i = 1111111111111111111111111111111111111111111113; then i=4; fi + if test $i = 1111111111111111111111111111111111111111111114; then i=X; fi +done +end=`ps -o pid,vsz | grep "^ *$pid "` + +# Warm up again (I do need it on my machine) +beg=`ps -o pid,vsz | grep "^ *$pid "` +i=1 +while test $i != X; do + t=111111111111111111111111111111111111111111111111111111111111111111111110$i + t=111111111111111111111111111111111111111111111111111111111111111111111111$i true + t=111111111111111111111111111111111111111111111111111111111111111111111112$i /bin/true + t=111111111111111111111111111111111111111111111111111111111111111111111113$i exec 1>&1 + i=1$i + if test $i = 1111111111111111111111111111111111111111111111; then i=2; fi + if test $i = 1111111111111111111111111111111111111111111112; then i=3; fi + if test $i = 1111111111111111111111111111111111111111111113; then i=4; fi + if test $i = 1111111111111111111111111111111111111111111114; then i=X; fi +done +end=`ps -o pid,vsz | grep "^ *$pid "` +if test "$beg" != "$end"; then + true echo "vsz grows: $beg -> $end" +else + true echo "vsz does not grow" +fi + +echo "Measuring memory leak..." +beg=`ps -o pid,vsz | grep "^ *$pid "` +i=1 +while test $i != X; do + t=111111111111111111111111111111111111111111111111111111111111111111111110$i + t=111111111111111111111111111111111111111111111111111111111111111111111111$i true + t=111111111111111111111111111111111111111111111111111111111111111111111112$i /bin/true + t=111111111111111111111111111111111111111111111111111111111111111111111113$i exec 1>&1 + i=1$i + if test $i = 1111111111111111111111111111111111111111111111; then i=2; fi + if test $i = 1111111111111111111111111111111111111111111112; then i=3; fi + if test $i = 1111111111111111111111111111111111111111111113; then i=4; fi + if test $i = 1111111111111111111111111111111111111111111114; then i=X; fi +done +end=`ps -o pid,vsz | grep "^ *$pid "` + +if test "$beg" != "$end"; then + echo "vsz grows: $beg -> $end" +else + echo "vsz does not grow" +fi diff --git a/shell/hush_test/run-all b/shell/hush_test/run-all new file mode 100755 index 0000000..b79af2f --- /dev/null +++ b/shell/hush_test/run-all @@ -0,0 +1,73 @@ +#!/bin/sh + +unset LANG LANGUAGE +unset LC_COLLATE +unset LC_CTYPE +unset LC_MONETARY +unset LC_MESSAGES +unset LC_NUMERIC +unset LC_TIME +unset LC_ALL + +test -x hush || { + echo "No ./hush - creating a link to ../../busybox" + ln -s ../../busybox hush +} + +PATH="$PWD:$PATH" # for hush and recho/zecho/printenv +export PATH + +THIS_SH="$PWD/hush" +export THIS_SH + +do_test() +{ + test -d "$1" || return 0 +# echo Running tests in directory "$1" + ( + cd "$1" || { echo "cannot cd $1!"; exit 1; } + for x in run-*; do + test -f "$x" || continue + case "$x" in + "$0"|run-minimal|run-gprof) ;; + *.orig|*~) ;; + #*) echo $x ; sh $x ;; + *) + sh "$x" >"../$1-$x.fail" 2>&1 && \ + { echo "$1/$x: ok"; rm "../$1-$x.fail"; } || echo "$1/$x: fail"; + ;; + esac + done + # Many bash run-XXX scripts just do this, + # no point in duplication it all over the place + for x in *.tests; do + test -x "$x" || continue + name="${x%%.tests}" + test -f "$name.right" || continue +# echo Running test: "$name.right" + { + "$THIS_SH" "./$x" >"$name.xx" 2>&1 + diff -u "$name.xx" "$name.right" >"../$1-$x.fail" && rm -f "$name.xx" "../$1-$x.fail" + } && echo "$1/$x: ok" || echo "$1/$x: fail" + done + ) +} + +# Main part of this script +# Usage: run-all [directories] + +if [ $# -lt 1 ]; then + # All sub directories + modules=`ls -d hush-*` + + for module in $modules; do + do_test $module + done +else + while [ $# -ge 1 ]; do + if [ -d $1 ]; then + do_test $1 + fi + shift + done +fi diff --git a/shell/lash_unused.c b/shell/lash_unused.c new file mode 100644 index 0000000..90b1f56 --- /dev/null +++ b/shell/lash_unused.c @@ -0,0 +1,1570 @@ +/* vi: set sw=4 ts=4: */ +/* + * lash -- the BusyBox Lame-Ass SHell + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Based in part on ladsh.c by Michael K. Johnson and Erik W. Troan, which is + * under the following liberal license: "We have placed this source code in the + * public domain. Use it in any project, free or commercial." + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +/* This shell's parsing engine is officially at a dead-end. Future + * work shell work should be done using hush, msh, or ash. This is + * still a very useful, small shell -- it just don't need any more + * features beyond what it already has... + */ + +//For debugging/development on the shell only... +//#define DEBUG_SHELL + +#include +#include + +#include "libbb.h" + +#define expand_t glob_t + +/* Always enable for the moment... */ +#define CONFIG_LASH_PIPE_N_REDIRECTS +#define CONFIG_LASH_JOB_CONTROL +#define ENABLE_LASH_PIPE_N_REDIRECTS 1 +#define ENABLE_LASH_JOB_CONTROL 1 + + +enum { MAX_READ = 128 }; /* size of input buffer for 'read' builtin */ +#define JOB_STATUS_FORMAT "[%d] %-22s %.40s\n" + + +#if ENABLE_LASH_PIPE_N_REDIRECTS +enum redir_type { REDIRECT_INPUT, REDIRECT_OVERWRITE, + REDIRECT_APPEND +}; +#endif + +enum { + DEFAULT_CONTEXT = 0x1, + IF_TRUE_CONTEXT = 0x2, + IF_FALSE_CONTEXT = 0x4, + THEN_EXP_CONTEXT = 0x8, + ELSE_EXP_CONTEXT = 0x10 +}; + +#define LASH_OPT_DONE (1) +#define LASH_OPT_SAW_QUOTE (2) + +#if ENABLE_LASH_PIPE_N_REDIRECTS +struct redir_struct { + enum redir_type type; /* type of redirection */ + int fd; /* file descriptor being redirected */ + char *filename; /* file to redirect fd to */ +}; +#endif + +struct child_prog { + pid_t pid; /* 0 if exited */ + char **argv; /* program name and arguments */ + int num_redirects; /* elements in redirection array */ + int is_stopped; /* is the program currently running? */ + struct job *family; /* pointer back to the child's parent job */ +#if ENABLE_LASH_PIPE_N_REDIRECTS + struct redir_struct *redirects; /* I/O redirects */ +#endif +}; + +struct jobset { + struct job *head; /* head of list of running jobs */ + struct job *fg; /* current foreground job */ +}; + +struct job { + int jobid; /* job number */ + int num_progs; /* total number of programs in job */ + int running_progs; /* number of programs running */ + char *text; /* name of job */ + char *cmdbuf; /* buffer various argv's point into */ + pid_t pgrp; /* process group ID for the job */ + struct child_prog *progs; /* array of programs in job */ + struct job *next; /* to track background commands */ + int stopped_progs; /* number of programs alive, but stopped */ + unsigned int job_context; /* bitmask defining current context */ + struct jobset *job_list; +}; + +struct built_in_command { + const char *cmd; /* name */ + const char *descr; /* description */ + int (*function) (struct child_prog *); /* function ptr */ +}; + +/* function prototypes for builtins */ +static int builtin_cd(struct child_prog *cmd); +static int builtin_exec(struct child_prog *cmd); +static int builtin_exit(struct child_prog *cmd); +static int builtin_fg_bg(struct child_prog *cmd); +static int builtin_help(struct child_prog *cmd); +static int builtin_jobs(struct child_prog *dummy); +static int builtin_pwd(struct child_prog *dummy); +static int builtin_export(struct child_prog *cmd); +static int builtin_source(struct child_prog *cmd); +static int builtin_unset(struct child_prog *cmd); +static int builtin_read(struct child_prog *cmd); + + +/* function prototypes for shell stuff */ +static void checkjobs(struct jobset *job_list); +static void remove_job(struct jobset *j_list, struct job *job); +static int get_command_bufsiz(FILE *source, char *command); +static int parse_command(char **command_ptr, struct job *job, int *inbg); +static int run_command(struct job *newjob, int inbg, int outpipe[2]); +static int pseudo_exec(struct child_prog *cmd) NORETURN; +static int busy_loop(FILE *input); + + +/* Table of built-in functions (these are non-forking builtins, meaning they + * can change global variables in the parent shell process but they will not + * work with pipes and redirects; 'unset foo | whatever' will not work) */ +static const struct built_in_command bltins[] = { + {"bg" , "Resume a job in the background", builtin_fg_bg}, + {"cd" , "Change working directory", builtin_cd}, + {"exec" , "Exec command, replacing this shell with the exec'd process", builtin_exec}, + {"exit" , "Exit from shell()", builtin_exit}, + {"fg" , "Bring job into the foreground", builtin_fg_bg}, + {"jobs" , "Lists the active jobs", builtin_jobs}, + {"export", "Set environment variable", builtin_export}, + {"unset" , "Unset environment variable", builtin_unset}, + {"read" , "Input environment variable", builtin_read}, + {"." , "Source-in and run commands in a file", builtin_source}, + /* These were "forked applets", but distinction was nuked */ + /* Original comment retained: */ + /* Table of forking built-in functions (things that fork cannot change global + * variables in the parent process, such as the current working directory) */ + {"pwd" , "Print current directory", builtin_pwd}, + {"help" , "List shell built-in commands", builtin_help}, + /* to do: add ulimit */ +}; + + +#define VEC_LAST(v) v[ARRAY_SIZE(v)-1] + + +static int shell_context; /* Type prompt trigger (PS1 or PS2) */ + + +/* Globals that are static to this file */ +static char *cwd; +static char *local_pending_command; +static struct jobset job_list = { NULL, NULL }; +static int global_argc; +static char **global_argv; +static llist_t *close_me_list; +static int last_return_code; +static int last_bg_pid; +static unsigned int last_jobid; +static int shell_terminal; +static const char *PS1; +static const char *PS2 = "> "; + + +#ifdef DEBUG_SHELL +static inline void debug_printf(const char *format, ...) +{ + va_list args; + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); +} +#else +static inline void debug_printf(const char UNUSED_PARAM *format, ...) { } +#endif + +/* + Most builtins need access to the struct child_prog that has + their arguments, previously coded as cmd->progs[0]. That coding + can exhibit a bug, if the builtin is not the first command in + a pipeline: "echo foo | exec sort" will attempt to exec foo. + +builtin previous use notes +------ ----------------- --------- +cd cmd->progs[0] +exec cmd->progs[0] squashed bug: didn't look for applets or forking builtins +exit cmd->progs[0] +fg_bg cmd->progs[0], job_list->head, job_list->fg +help 0 +jobs job_list->head +pwd 0 +export cmd->progs[0] +source cmd->progs[0] +unset cmd->progs[0] +read cmd->progs[0] + +I added "struct job *family;" to struct child_prog, +and switched API to builtin_foo(struct child_prog *child); +So cmd->text becomes child->family->text + cmd->job_context becomes child->family->job_context + cmd->progs[0] becomes *child + job_list becomes child->family->job_list + */ + + +static void update_cwd(void) +{ + cwd = xrealloc_getcwd_or_warn(cwd); + if (!cwd) + cwd = xstrdup(bb_msg_unknown); +} + +/* built-in 'cd ' handler */ +static int builtin_cd(struct child_prog *child) +{ + char *newdir; + + if (child->argv[1] == NULL) + newdir = getenv("HOME"); + else + newdir = child->argv[1]; + if (chdir(newdir)) { + bb_perror_msg("cd: %s", newdir); + return EXIT_FAILURE; + } + update_cwd(); + return EXIT_SUCCESS; +} + +/* built-in 'exec' handler */ +static int builtin_exec(struct child_prog *child) +{ + if (child->argv[1] == NULL) + return EXIT_SUCCESS; /* Really? */ + child->argv++; + while (close_me_list) + close((long)llist_pop(&close_me_list)); + pseudo_exec(child); + /* never returns */ +} + +/* built-in 'exit' handler */ +static int builtin_exit(struct child_prog *child) +{ + if (child->argv[1] == NULL) + exit(EXIT_SUCCESS); + + exit(atoi(child->argv[1])); +} + +/* built-in 'fg' and 'bg' handler */ +static int builtin_fg_bg(struct child_prog *child) +{ + int i, jobnum; + struct job *job; + + /* If they gave us no args, assume they want the last backgrounded task */ + if (!child->argv[1]) { + for (job = child->family->job_list->head; job; job = job->next) { + if (job->jobid == last_jobid) { + goto found; + } + } + bb_error_msg("%s: no current job", child->argv[0]); + return EXIT_FAILURE; + } + if (sscanf(child->argv[1], "%%%d", &jobnum) != 1) { + bb_error_msg(bb_msg_invalid_arg, child->argv[1], child->argv[0]); + return EXIT_FAILURE; + } + for (job = child->family->job_list->head; job; job = job->next) { + if (job->jobid == jobnum) { + goto found; + } + } + bb_error_msg("%s: %d: no such job", child->argv[0], jobnum); + return EXIT_FAILURE; + found: + if (*child->argv[0] == 'f') { + /* Put the job into the foreground. */ + tcsetpgrp(shell_terminal, job->pgrp); + + child->family->job_list->fg = job; + } + + /* Restart the processes in the job */ + for (i = 0; i < job->num_progs; i++) + job->progs[i].is_stopped = 0; + + job->stopped_progs = 0; + + i = kill(- job->pgrp, SIGCONT); + if (i < 0) { + if (errno == ESRCH) { + remove_job(&job_list, job); + } else { + bb_perror_msg("kill (SIGCONT)"); + } + } + + return EXIT_SUCCESS; +} + +/* built-in 'help' handler */ +static int builtin_help(struct child_prog UNUSED_PARAM *dummy) +{ + const struct built_in_command *x; + + printf("\nBuilt-in commands:\n" + "-------------------\n"); + for (x = bltins; x <= &VEC_LAST(bltins); x++) { + if (x->descr == NULL) + continue; + printf("%s\t%s\n", x->cmd, x->descr); + } + bb_putchar('\n'); + return EXIT_SUCCESS; +} + +/* built-in 'jobs' handler */ +static int builtin_jobs(struct child_prog *child) +{ + struct job *job; + const char *status_string; + + for (job = child->family->job_list->head; job; job = job->next) { + if (job->running_progs == job->stopped_progs) + status_string = "Stopped"; + else + status_string = "Running"; + + printf(JOB_STATUS_FORMAT, job->jobid, status_string, job->text); + } + return EXIT_SUCCESS; +} + + +/* built-in 'pwd' handler */ +static int builtin_pwd(struct child_prog UNUSED_PARAM *dummy) +{ + update_cwd(); + puts(cwd); + return EXIT_SUCCESS; +} + +/* built-in 'export VAR=value' handler */ +static int builtin_export(struct child_prog *child) +{ + int res; + char *v = child->argv[1]; + + if (v == NULL) { + char **e; + for (e = environ; *e; e++) { + puts(*e); + } + return 0; + } + res = putenv(v); + if (res) + bb_perror_msg("export"); +#if ENABLE_FEATURE_EDITING_FANCY_PROMPT + if (strncmp(v, "PS1=", 4) == 0) + PS1 = getenv("PS1"); +#endif + +#if ENABLE_LOCALE_SUPPORT + // TODO: why getenv? "" would be just as good... + if (strncmp(v, "LC_ALL=", 7) == 0) + setlocale(LC_ALL, getenv("LC_ALL")); + if (strncmp(v, "LC_CTYPE=", 9) == 0) + setlocale(LC_CTYPE, getenv("LC_CTYPE")); +#endif + + return res; +} + +/* built-in 'read VAR' handler */ +static int builtin_read(struct child_prog *child) +{ + int res = 0, len; + char *s; + char string[MAX_READ]; + + if (child->argv[1]) { + /* argument (VAR) given: put "VAR=" into buffer */ + safe_strncpy(string, child->argv[1], MAX_READ-1); + len = strlen(string); + string[len++] = '='; + string[len] = '\0'; + fgets(&string[len], sizeof(string) - len, stdin); /* read string */ + res = strlen(string); + if (res > len) + string[--res] = '\0'; /* chomp trailing newline */ + /* + ** string should now contain "VAR=" + ** copy it (putenv() won't do that, so we must make sure + ** the string resides in a static buffer!) + */ + res = -1; + s = strdup(string); + if (s) + res = putenv(s); + if (res) + bb_perror_msg("read"); + } else + fgets(string, sizeof(string), stdin); + + return res; +} + +/* Built-in '.' handler (read-in and execute commands from file) */ +static int builtin_source(struct child_prog *child) +{ + FILE *input; + int status; + + input = fopen_or_warn(child->argv[1], "r"); + if (!input) { + return EXIT_FAILURE; + } + + llist_add_to(&close_me_list, (void *)(long)fileno(input)); + /* Now run the file */ + status = busy_loop(input); + fclose(input); + llist_pop(&close_me_list); + return status; +} + +/* built-in 'unset VAR' handler */ +static int builtin_unset(struct child_prog *child) +{ + if (child->argv[1] == NULL) { + printf(bb_msg_requires_arg, "unset"); + return EXIT_FAILURE; + } + unsetenv(child->argv[1]); + return EXIT_SUCCESS; +} + +#if ENABLE_LASH_JOB_CONTROL +/* free up all memory from a job */ +static void free_job(struct job *cmd) +{ + int i; + struct jobset *keep; + + for (i = 0; i < cmd->num_progs; i++) { + free(cmd->progs[i].argv); +#if ENABLE_LASH_PIPE_N_REDIRECTS + free(cmd->progs[i].redirects); +#endif + } + free(cmd->progs); + free(cmd->text); + free(cmd->cmdbuf); + keep = cmd->job_list; + memset(cmd, 0, sizeof(struct job)); + cmd->job_list = keep; +} + +/* remove a job from a jobset */ +static void remove_job(struct jobset *j_list, struct job *job) +{ + struct job *prevjob; + + free_job(job); + if (job == j_list->head) { + j_list->head = job->next; + } else { + prevjob = j_list->head; + while (prevjob->next != job) + prevjob = prevjob->next; + prevjob->next = job->next; + } + + if (j_list->head) + last_jobid = j_list->head->jobid; + else + last_jobid = 0; + + free(job); +} + +/* Checks to see if any background processes have exited -- if they + have, figure out why and see if a job has completed */ +static void checkjobs(struct jobset *j_list) +{ + struct job *job; + pid_t childpid; + int status; + int prognum = 0; + + while ((childpid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) { + for (job = j_list->head; job; job = job->next) { + prognum = 0; + while (prognum < job->num_progs && + job->progs[prognum].pid != childpid) prognum++; + if (prognum < job->num_progs) + break; + } + + /* This happens on backticked commands */ + if (job == NULL) + return; + + if (WIFEXITED(status) || WIFSIGNALED(status)) { + /* child exited */ + job->running_progs--; + job->progs[prognum].pid = 0; + + if (!job->running_progs) { + printf(JOB_STATUS_FORMAT, job->jobid, "Done", job->text); + last_jobid = 0; + remove_job(j_list, job); + } + } else { + /* child stopped */ + job->stopped_progs++; + job->progs[prognum].is_stopped = 1; + } + } + + if (childpid == -1 && errno != ECHILD) + bb_perror_msg("waitpid"); +} +#else +static void checkjobs(struct jobset *j_list) +{ +} +static void free_job(struct job *cmd) +{ +} +static void remove_job(struct jobset *j_list, struct job *job) +{ +} +#endif + +#if ENABLE_LASH_PIPE_N_REDIRECTS +/* squirrel != NULL means we squirrel away copies of stdin, stdout, + * and stderr if they are redirected. */ +static int setup_redirects(struct child_prog *prog, int squirrel[]) +{ + int i; + int openfd; + int mode = O_RDONLY; + struct redir_struct *redir = prog->redirects; + + for (i = 0; i < prog->num_redirects; i++, redir++) { + switch (redir->type) { + case REDIRECT_INPUT: + mode = O_RDONLY; + break; + case REDIRECT_OVERWRITE: + mode = O_WRONLY | O_CREAT | O_TRUNC; + break; + case REDIRECT_APPEND: + mode = O_WRONLY | O_CREAT | O_APPEND; + break; + } + + openfd = open_or_warn(redir->filename, mode); + if (openfd < 0) { + /* this could get lost if stderr has been redirected, but + bash and ash both lose it as well (though zsh doesn't!) */ + return 1; + } + + if (openfd != redir->fd) { + if (squirrel && redir->fd < 3) { + squirrel[redir->fd] = dup(redir->fd); + close_on_exec_on(squirrel[redir->fd]); + } + dup2(openfd, redir->fd); + close(openfd); + } + } + + return 0; +} + +static void restore_redirects(int squirrel[]) +{ + int i, fd; + for (i = 0; i < 3; i++) { + fd = squirrel[i]; + if (fd != -1) { + /* No error checking. I sure wouldn't know what + * to do with an error if I found one! */ + dup2(fd, i); + close(fd); + } + } +} +#else +static inline int setup_redirects(struct child_prog *prog, int squirrel[]) +{ + return 0; +} +static inline void restore_redirects(int squirrel[]) +{ +} +#endif + +static inline void cmdedit_set_initial_prompt(void) +{ +#if !ENABLE_FEATURE_EDITING_FANCY_PROMPT + PS1 = NULL; +#else + PS1 = getenv("PS1"); + if (PS1 == 0) + PS1 = "\\w \\$ "; +#endif +} + +static inline const char* setup_prompt_string(void) +{ +#if !ENABLE_FEATURE_EDITING_FANCY_PROMPT + /* Set up the prompt */ + if (shell_context == 0) { + char *ns; + free((char*)PS1); + ns = xmalloc(strlen(cwd)+4); + sprintf(ns, "%s %c ", cwd, (geteuid() != 0) ? '$': '#'); + PS1 = ns; + return ns; + } else { + return PS2; + } +#else + return (shell_context == 0)? PS1 : PS2; +#endif +} + +#if ENABLE_FEATURE_EDITING +static line_input_t *line_input_state; +#endif + +static int get_command_bufsiz(FILE *source, char *command) +{ + const char *prompt_str; + + if (source == NULL) { + if (local_pending_command) { + /* a command specified (-c option): return it & mark it done */ + strncpy(command, local_pending_command, BUFSIZ); + local_pending_command = NULL; + return 0; + } + return 1; + } + + if (source == stdin) { + prompt_str = setup_prompt_string(); + +#if ENABLE_FEATURE_EDITING + /* + ** enable command line editing only while a command line + ** is actually being read; otherwise, we'll end up bequeathing + ** atexit() handlers and other unwanted stuff to our + ** child processes (rob@sysgo.de) + */ + read_line_input(prompt_str, command, BUFSIZ, line_input_state); + return 0; +#else + fputs(prompt_str, stdout); +#endif + } + + if (!fgets(command, BUFSIZ - 2, source)) { + if (source == stdin) + bb_putchar('\n'); + return 1; + } + + return 0; +} + +static char * strsep_space(char *string, int * ix) +{ + /* Short circuit the trivial case */ + if (!string || ! string[*ix]) + return NULL; + + /* Find the end of the token. */ + while (string[*ix] && !isspace(string[*ix]) ) { + (*ix)++; + } + + /* Find the end of any whitespace trailing behind + * the token and let that be part of the token */ + while (string[*ix] && (isspace)(string[*ix]) ) { + (*ix)++; + } + + if (!*ix) { + /* Nothing useful was found */ + return NULL; + } + + return xstrndup(string, *ix); +} + +static int expand_arguments(char *command) +{ + static const char out_of_space[] ALIGN1 = "out of space during expansion"; + + int total_length = 0, length, i, retval, ix = 0; + expand_t expand_result; + char *tmpcmd, *cmd, *cmd_copy; + char *src, *dst, *var; + int flags = GLOB_NOCHECK +#ifdef GLOB_BRACE + | GLOB_BRACE +#endif +#ifdef GLOB_TILDE + | GLOB_TILDE +#endif + ; + + /* get rid of the terminating \n */ + chomp(command); + + /* Fix up escape sequences to be the Real Thing(tm) */ + while (command && command[ix]) { + if (command[ix] == '\\') { + const char *tmp = command+ix+1; + command[ix] = bb_process_escape_sequence( &tmp ); + memmove(command+ix + 1, tmp, strlen(tmp)+1); + } + ix++; + } + /* Use glob and then fixup environment variables and such */ + + /* It turns out that glob is very stupid. We have to feed it one word at a + * time since it can't cope with a full string. Here we convert command + * (char*) into cmd (char**, one word per string) */ + + /* We need a clean copy, so strsep can mess up the copy while + * we write stuff into the original (in a minute) */ + cmd = cmd_copy = xstrdup(command); + *command = '\0'; + for (ix = 0, tmpcmd = cmd; + (tmpcmd = strsep_space(cmd, &ix)) != NULL; cmd += ix, ix = 0) { + if (*tmpcmd == '\0') + break; + /* we need to trim() the result for glob! */ + trim(tmpcmd); + retval = glob(tmpcmd, flags, NULL, &expand_result); + free(tmpcmd); /* Free mem allocated by strsep_space */ + if (retval == GLOB_NOSPACE) { + /* Mem may have been allocated... */ + globfree(&expand_result); + bb_error_msg(out_of_space); + return FALSE; + } else if (retval != 0) { + /* Some other error. GLOB_NOMATCH shouldn't + * happen because of the GLOB_NOCHECK flag in + * the glob call. */ + bb_error_msg("syntax error"); + return FALSE; + } else { + /* Convert from char** (one word per string) to a simple char*, + * but don't overflow command which is BUFSIZ in length */ + for (i = 0; i < expand_result.gl_pathc; i++) { + length = strlen(expand_result.gl_pathv[i]); + if (total_length+length+1 >= BUFSIZ) { + bb_error_msg(out_of_space); + return FALSE; + } + strcat(command+total_length, " "); + total_length += 1; + strcat(command+total_length, expand_result.gl_pathv[i]); + total_length += length; + } + globfree(&expand_result); + } + } + free(cmd_copy); + trim(command); + + /* Now do the shell variable substitutions which + * wordexp can't do for us, namely $? and $! */ + src = command; + while ((dst = strchr(src,'$')) != NULL) { + var = NULL; + switch (*(dst+1)) { + case '?': + var = itoa(last_return_code); + break; + case '!': + if (last_bg_pid == -1) + *var = '\0'; + else + var = itoa(last_bg_pid); + break; + /* Everything else like $$, $#, $[0-9], etc. should all be + * expanded by wordexp(), so we can in theory skip that stuff + * here, but just to be on the safe side (i.e., since uClibc + * wordexp doesn't do this stuff yet), lets leave it in for + * now. */ + case '$': + var = itoa(getpid()); + break; + case '#': + var = itoa(global_argc - 1); + break; + case '0':case '1':case '2':case '3':case '4': + case '5':case '6':case '7':case '8':case '9': + { + int ixx = *(dst+1)-48+1; + if (ixx >= global_argc) { + var = '\0'; + } else { + var = global_argv[ixx]; + } + } + break; + + } + if (var) { + /* a single character construction was found, and + * already handled in the case statement */ + src = dst + 2; + } else { + /* Looks like an environment variable */ + char delim_hold; + int num_skip_chars = 0; + int dstlen = strlen(dst); + /* Is this a ${foo} type variable? */ + if (dstlen >= 2 && *(dst+1) == '{') { + src = strchr(dst+1, '}'); + num_skip_chars = 1; + } else { + src = dst + 1; + while ((isalnum)(*src) || *src == '_') src++; + } + if (src == NULL) { + src = dst+dstlen; + } + delim_hold = *src; + *src = '\0'; /* temporary */ + var = getenv(dst + 1 + num_skip_chars); + *src = delim_hold; + src += num_skip_chars; + } + if (var == NULL) { + /* Seems we got an un-expandable variable. So delete it. */ + var = (char*)""; + } + { + int subst_len = strlen(var); + int trail_len = strlen(src); + if (dst+subst_len+trail_len >= command+BUFSIZ) { + bb_error_msg(out_of_space); + return FALSE; + } + /* Move stuff to the end of the string to accommodate + * filling the created gap with the new stuff */ + memmove(dst+subst_len, src, trail_len+1); + /* Now copy in the new stuff */ + memcpy(dst, var, subst_len); + src = dst+subst_len; + } + } + + return TRUE; +} + +/* Return cmd->num_progs as 0 if no command is present (e.g. an empty + line). If a valid command is found, command_ptr is set to point to + the beginning of the next command (if the original command had more + then one job associated with it) or NULL if no more commands are + present. */ +static int parse_command(char **command_ptr, struct job *job, int *inbg) +{ + char *command; + char *return_command = NULL; + char *src, *buf; + int argc_l; + int flag; + int argv_alloced; + char quote = '\0'; + struct child_prog *prog; +#if ENABLE_LASH_PIPE_N_REDIRECTS + int i; + char *chptr; +#endif + + /* skip leading white space */ + *command_ptr = skip_whitespace(*command_ptr); + + /* this handles empty lines or leading '#' characters */ + if (!**command_ptr || (**command_ptr == '#')) { + job->num_progs = 0; + return 0; + } + + *inbg = 0; + job->num_progs = 1; + job->progs = xmalloc(sizeof(*job->progs)); + + /* We set the argv elements to point inside of this string. The + memory is freed by free_job(). Allocate twice the original + length in case we need to quote every single character. + + Getting clean memory relieves us of the task of NULL + terminating things and makes the rest of this look a bit + cleaner (though it is, admittedly, a tad less efficient) */ + job->cmdbuf = command = xzalloc(2*strlen(*command_ptr) + 1); + job->text = NULL; + + prog = job->progs; + prog->num_redirects = 0; + prog->is_stopped = 0; + prog->family = job; +#if ENABLE_LASH_PIPE_N_REDIRECTS + prog->redirects = NULL; +#endif + + argv_alloced = 5; + prog->argv = xmalloc(sizeof(*prog->argv) * argv_alloced); + prog->argv[0] = job->cmdbuf; + + flag = argc_l = 0; + buf = command; + src = *command_ptr; + while (*src && !(flag & LASH_OPT_DONE)) { + if (quote == *src) { + quote = '\0'; + } else if (quote) { + if (*src == '\\') { + src++; + if (!*src) { + bb_error_msg("character expected after \\"); + free_job(job); + return 1; + } + + /* in shell, "\'" should yield \' */ + if (*src != quote) { + *buf++ = '\\'; + *buf++ = '\\'; + } + } else if (*src == '*' || *src == '?' || *src == '[' || + *src == ']') *buf++ = '\\'; + *buf++ = *src; + } else if (isspace(*src)) { + if (*prog->argv[argc_l] || (flag & LASH_OPT_SAW_QUOTE)) { + buf++, argc_l++; + /* +1 here leaves room for the NULL which ends argv */ + if ((argc_l + 1) == argv_alloced) { + argv_alloced += 5; + prog->argv = xrealloc(prog->argv, + sizeof(*prog->argv) * argv_alloced); + } + prog->argv[argc_l] = buf; + flag ^= LASH_OPT_SAW_QUOTE; + } + } else + switch (*src) { + case '"': + case '\'': + quote = *src; + flag |= LASH_OPT_SAW_QUOTE; + break; + + case '#': /* comment */ + if (*(src-1)== '$') + *buf++ = *src; + else + flag |= LASH_OPT_DONE; + break; + +#if ENABLE_LASH_PIPE_N_REDIRECTS + case '>': /* redirects */ + case '<': + i = prog->num_redirects++; + prog->redirects = xrealloc(prog->redirects, + sizeof(*prog->redirects) * (i + 1)); + + prog->redirects[i].fd = -1; + if (buf != prog->argv[argc_l]) { + /* the stuff before this character may be the file number + being redirected */ + prog->redirects[i].fd = + strtol(prog->argv[argc_l], &chptr, 10); + + if (*chptr && *prog->argv[argc_l]) { + buf++, argc_l++; + prog->argv[argc_l] = buf; + } + } + + if (prog->redirects[i].fd == -1) { + if (*src == '>') + prog->redirects[i].fd = 1; + else + prog->redirects[i].fd = 0; + } + + if (*src++ == '>') { + if (*src == '>') + prog->redirects[i].type = + REDIRECT_APPEND, src++; + else + prog->redirects[i].type = REDIRECT_OVERWRITE; + } else { + prog->redirects[i].type = REDIRECT_INPUT; + } + + /* This isn't POSIX sh compliant. Oh well. */ + chptr = src; + chptr = skip_whitespace(chptr); + + if (!*chptr) { + bb_error_msg("file name expected after %c", *(src-1)); + free_job(job); + job->num_progs = 0; + return 1; + } + + prog->redirects[i].filename = buf; + while (*chptr && !isspace(*chptr)) + *buf++ = *chptr++; + + src = chptr - 1; /* we src++ later */ + prog->argv[argc_l] = ++buf; + break; + + case '|': /* pipe */ + /* finish this command */ + if (*prog->argv[argc_l] || flag & LASH_OPT_SAW_QUOTE) + argc_l++; + if (!argc_l) { + goto empty_command_in_pipe; + } + prog->argv[argc_l] = NULL; + + /* and start the next */ + job->num_progs++; + job->progs = xrealloc(job->progs, + sizeof(*job->progs) * job->num_progs); + prog = job->progs + (job->num_progs - 1); + prog->num_redirects = 0; + prog->redirects = NULL; + prog->is_stopped = 0; + prog->family = job; + argc_l = 0; + + argv_alloced = 5; + prog->argv = xmalloc(sizeof(*prog->argv) * argv_alloced); + prog->argv[0] = ++buf; + + src++; + src = skip_whitespace(src); + + if (!*src) { +empty_command_in_pipe: + bb_error_msg("empty command in pipe"); + free_job(job); + job->num_progs = 0; + return 1; + } + src--; /* we'll ++ it at the end of the loop */ + + break; +#endif + +#if ENABLE_LASH_JOB_CONTROL + case '&': /* background */ + *inbg = 1; + /* fallthrough */ +#endif + case ';': /* multiple commands */ + flag |= LASH_OPT_DONE; + return_command = *command_ptr + (src - *command_ptr) + 1; + break; + + case '\\': + src++; + if (!*src) { + bb_error_msg("character expected after \\"); + free_job(job); + return 1; + } + if (*src == '*' || *src == '[' || *src == ']' + || *src == '?') *buf++ = '\\'; + /* fallthrough */ + default: + *buf++ = *src; + } + + src++; + } + + if (*prog->argv[argc_l] || flag & LASH_OPT_SAW_QUOTE) { + argc_l++; + } + if (!argc_l) { + free_job(job); + return 0; + } + prog->argv[argc_l] = NULL; + + if (!return_command) { + job->text = xstrdup(*command_ptr); + } else { + /* This leaves any trailing spaces, which is a bit sloppy */ + job->text = xstrndup(*command_ptr, return_command - *command_ptr); + } + + *command_ptr = return_command; + + return 0; +} + +/* Run the child_prog, no matter what kind of command it uses. + */ +static int pseudo_exec(struct child_prog *child) +{ + const struct built_in_command *x; + + /* Check if the command matches any of the non-forking builtins. + * Depending on context, this might be redundant. But it's + * easier to waste a few CPU cycles than it is to figure out + * if this is one of those cases. + */ + /* Check if the command matches any of the forking builtins. */ + for (x = bltins; x <= &VEC_LAST(bltins); x++) { + if (strcmp(child->argv[0], x->cmd) == 0) { + _exit(x->function(child)); + } + } + + /* Check if the command matches any busybox internal + * commands ("applets") here. Following discussions from + * November 2000 on busybox@busybox.net, don't use + * bb_get_last_path_component_nostrip(). This way explicit + * (with slashes) filenames will never be interpreted as an + * applet, just like with builtins. This way the user can + * override an applet with an explicit filename reference. + * The only downside to this change is that an explicit + * /bin/foo invocation will fork and exec /bin/foo, even if + * /bin/foo is a symlink to busybox. + */ + if (ENABLE_FEATURE_SH_STANDALONE) { + run_applet_and_exit(child->argv[0], child->argv); + } + + execvp(child->argv[0], child->argv); + + /* Do not use bb_perror_msg_and_die() here, since we must not + * call exit() but should call _exit() instead */ + bb_simple_perror_msg(child->argv[0]); + _exit(EXIT_FAILURE); +} + +static void insert_job(struct job *newjob, int inbg) +{ + struct job *thejob; + struct jobset *j_list = newjob->job_list; + + /* find the ID for thejob to use */ + newjob->jobid = 1; + for (thejob = j_list->head; thejob; thejob = thejob->next) + if (thejob->jobid >= newjob->jobid) + newjob->jobid = thejob->jobid + 1; + + /* add thejob to the list of running jobs */ + if (!j_list->head) { + thejob = j_list->head = xmalloc(sizeof(*thejob)); + } else { + for (thejob = j_list->head; thejob->next; thejob = thejob->next) /* nothing */; + thejob->next = xmalloc(sizeof(*thejob)); + thejob = thejob->next; + } + + *thejob = *newjob; /* physically copy the struct job */ + thejob->next = NULL; + thejob->running_progs = thejob->num_progs; + thejob->stopped_progs = 0; + +#if ENABLE_LASH_JOB_CONTROL + if (inbg) { + /* we don't wait for background thejobs to return -- append it + to the list of backgrounded thejobs and leave it alone */ + printf("[%d] %d\n", thejob->jobid, + newjob->progs[newjob->num_progs - 1].pid); + last_jobid = newjob->jobid; + last_bg_pid = newjob->progs[newjob->num_progs - 1].pid; + } else { + newjob->job_list->fg = thejob; + + /* move the new process group into the foreground */ + /* Ignore errors since child could have already exited */ + tcsetpgrp(shell_terminal, newjob->pgrp); + } +#endif +} + +static int run_command(struct job *newjob, int inbg, int outpipe[2]) +{ + /* struct job *thejob; */ + int i; + int nextin, nextout; + int pipefds[2]; /* pipefd[0] is for reading */ + const struct built_in_command *x; + struct child_prog *child; + + nextin = 0; + for (i = 0; i < newjob->num_progs; i++) { + child = &(newjob->progs[i]); + + nextout = 1; + if ((i + 1) < newjob->num_progs) { + xpipe(pipefds); + nextout = pipefds[1]; + } else if (outpipe[1] != -1) { + nextout = outpipe[1]; + } + + /* Check if the command matches any non-forking builtins, + * but only if this is a simple command. + * Non-forking builtins within pipes have to fork anyway, + * and are handled in pseudo_exec. "echo foo | read bar" + * is doomed to failure, and doesn't work on bash, either. + */ + if (newjob->num_progs == 1) { + int rcode; + int squirrel[] = {-1, -1, -1}; + + /* Check if the command sets an environment variable. */ + if (strchr(child->argv[0], '=') != NULL) { + child->argv[1] = child->argv[0]; + return builtin_export(child); + } + + for (x = bltins; x <= &VEC_LAST(bltins); x++) { + if (strcmp(child->argv[0], x->cmd) == 0) { + setup_redirects(child, squirrel); + rcode = x->function(child); + restore_redirects(squirrel); + return rcode; + } + } +#if ENABLE_FEATURE_SH_STANDALONE + { + int a = find_applet_by_name(child->argv[i]); + if (a >= 0 && APPLET_IS_NOFORK(a)) { + setup_redirects(child, squirrel); + rcode = run_nofork_applet(a, child->argv + i); + restore_redirects(squirrel); + return rcode; + } + } +#endif + } + +#if BB_MMU + child->pid = fork(); +#else + child->pid = vfork(); +#endif + if (!child->pid) { + /* Set the handling for job control signals back to the default. */ + signal(SIGINT, SIG_DFL); + signal(SIGQUIT, SIG_DFL); + signal(SIGTSTP, SIG_DFL); + signal(SIGTTIN, SIG_DFL); + signal(SIGTTOU, SIG_DFL); + signal(SIGCHLD, SIG_DFL); + + /* Close all open filehandles. */ + while (close_me_list) + close((long)llist_pop(&close_me_list)); + + if (outpipe[1] != -1) { + close(outpipe[0]); + } + if (nextin != 0) { + dup2(nextin, 0); + close(nextin); + } + + if (nextout != 1) { + dup2(nextout, 1); + dup2(nextout, 2); /* Really? */ + close(nextout); + close(pipefds[0]); + } + + /* explicit redirects override pipes */ + setup_redirects(child,NULL); + + pseudo_exec(child); + } + if (outpipe[1] != -1) { + close(outpipe[1]); + } + + /* put our child in the process group whose leader is the + first process in this pipe */ + setpgid(child->pid, newjob->progs[0].pid); + if (nextin != 0) + close(nextin); + if (nextout != 1) + close(nextout); + + /* If there isn't another process, nextin is garbage + but it doesn't matter */ + nextin = pipefds[0]; + } + + newjob->pgrp = newjob->progs[0].pid; + + insert_job(newjob, inbg); + + return 0; +} + +static int busy_loop(FILE *input) +{ + char *command; + char *next_command = NULL; + struct job newjob; + int i; + int inbg = 0; + int status; +#if ENABLE_LASH_JOB_CONTROL + pid_t parent_pgrp; + /* save current owner of TTY so we can restore it on exit */ + parent_pgrp = tcgetpgrp(shell_terminal); +#endif + newjob.job_list = &job_list; + newjob.job_context = DEFAULT_CONTEXT; + + command = xzalloc(BUFSIZ); + + while (1) { + if (!job_list.fg) { + /* no job is in the foreground */ + + /* see if any background processes have exited */ + checkjobs(&job_list); + + if (!next_command) { + if (get_command_bufsiz(input, command)) + break; + next_command = command; + } + + if (!expand_arguments(next_command)) { + free(command); + command = xzalloc(BUFSIZ); + next_command = NULL; + continue; + } + + if (!parse_command(&next_command, &newjob, &inbg) && + newjob.num_progs) { + int pipefds[2] = { -1, -1 }; + debug_printf("job=%p fed to run_command by busy_loop()'\n", + &newjob); + run_command(&newjob, inbg, pipefds); + } + else { + free(command); + command = xzalloc(BUFSIZ); + next_command = NULL; + } + } else { + /* a job is running in the foreground; wait for it */ + i = 0; + while (!job_list.fg->progs[i].pid || + job_list.fg->progs[i].is_stopped == 1) i++; + + if (waitpid(job_list.fg->progs[i].pid, &status, WUNTRACED) < 0) { + if (errno != ECHILD) { + bb_perror_msg_and_die("waitpid(%d)", job_list.fg->progs[i].pid); + } + } + + if (WIFEXITED(status) || WIFSIGNALED(status)) { + /* the child exited */ + job_list.fg->running_progs--; + job_list.fg->progs[i].pid = 0; + + last_return_code = WEXITSTATUS(status); + + if (!job_list.fg->running_progs) { + /* child exited */ + remove_job(&job_list, job_list.fg); + job_list.fg = NULL; + } + } +#if ENABLE_LASH_JOB_CONTROL + else { + /* the child was stopped */ + job_list.fg->stopped_progs++; + job_list.fg->progs[i].is_stopped = 1; + + if (job_list.fg->stopped_progs == job_list.fg->running_progs) { + printf("\n" JOB_STATUS_FORMAT, job_list.fg->jobid, + "Stopped", job_list.fg->text); + job_list.fg = NULL; + } + } + + if (!job_list.fg) { + /* move the shell to the foreground */ + /* suppress messages when run from /linuxrc mag@sysgo.de */ + if (tcsetpgrp(shell_terminal, getpgrp()) && errno != ENOTTY) + bb_perror_msg("tcsetpgrp"); + } +#endif + } + } + free(command); + +#if ENABLE_LASH_JOB_CONTROL + /* return controlling TTY back to parent process group before exiting */ + if (tcsetpgrp(shell_terminal, parent_pgrp) && errno != ENOTTY) + bb_perror_msg("tcsetpgrp"); +#endif + + /* return exit status if called with "-c" */ + if (input == NULL && WIFEXITED(status)) + return WEXITSTATUS(status); + + return 0; +} + +#if ENABLE_FEATURE_CLEAN_UP +static void free_memory(void) +{ + free(cwd); + + if (job_list.fg && !job_list.fg->running_progs) { + remove_job(&job_list, job_list.fg); + } +} +#else +void free_memory(void); +#endif + +#if ENABLE_LASH_JOB_CONTROL +/* Make sure we have a controlling tty. If we get started under a job + * aware app (like bash for example), make sure we are now in charge so + * we don't fight over who gets the foreground */ +static void setup_job_control(void) +{ + int status; + pid_t shell_pgrp; + + /* Loop until we are in the foreground. */ + while ((status = tcgetpgrp(shell_terminal)) >= 0) { + shell_pgrp = getpgrp(); + if (status == shell_pgrp) { + break; + } + kill(- shell_pgrp, SIGTTIN); + } + + /* Ignore interactive and job-control signals. */ + signal(SIGINT, SIG_IGN); + signal(SIGQUIT, SIG_IGN); + signal(SIGTSTP, SIG_IGN); + signal(SIGTTIN, SIG_IGN); + signal(SIGTTOU, SIG_IGN); + signal(SIGCHLD, SIG_IGN); + + /* Put ourselves in our own process group. */ + setsid(); + shell_pgrp = getpid(); + setpgid(shell_pgrp, shell_pgrp); + + /* Grab control of the terminal. */ + tcsetpgrp(shell_terminal, shell_pgrp); +} +#else +static inline void setup_job_control(void) +{ +} +#endif + +int lash_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int lash_main(int argc, char **argv) +{ + unsigned opt; + FILE *input = stdin; + + global_argc = argc; + global_argv = argv; + +#if ENABLE_FEATURE_EDITING + line_input_state = new_line_input_t(FOR_SHELL); +#endif + + /* These variables need re-initializing when recursing */ + last_jobid = 0; + close_me_list = NULL; + job_list.head = NULL; + job_list.fg = NULL; + last_return_code = 1; + + if (global_argv[0] && global_argv[0][0] == '-') { + FILE *prof_input; + prof_input = fopen_for_read("/etc/profile"); + if (prof_input) { + llist_add_to(&close_me_list, (void *)(long)fileno(prof_input)); + /* Now run the file */ + busy_loop(prof_input); + fclose_if_not_stdin(prof_input); + llist_pop(&close_me_list); + } + } + + opt = getopt32(argv, "+ic:", &local_pending_command); +#define LASH_OPT_i (1<<0) +#define LASH_OPT_c (1<<1) + if (opt & LASH_OPT_c) { + input = NULL; + optind++; + global_argv += optind; + } + /* A shell is interactive if the `-i' flag was given, or if all of + * the following conditions are met: + * no -c command + * no arguments remaining or the -s flag given + * standard input is a terminal + * standard output is a terminal + * Refer to Posix.2, the description of the `sh' utility. */ + if (global_argv[optind] == NULL && input == stdin + && isatty(STDIN_FILENO) && isatty(STDOUT_FILENO) + ) { + opt |= LASH_OPT_i; + } + setup_job_control(); + if (opt & LASH_OPT_i) { + /* Looks like they want an interactive shell */ + if (!ENABLE_FEATURE_SH_EXTRA_QUIET) { + printf("\n\n%s built-in shell (lash)\n" + "Enter 'help' for a list of built-in commands.\n\n", + bb_banner); + } + } else if (!local_pending_command && global_argv[optind]) { + //printf( "optind=%d argv[optind]='%s'\n", optind, argv[optind]); + input = xfopen_for_read(global_argv[optind]); + /* be lazy, never mark this closed */ + llist_add_to(&close_me_list, (void *)(long)fileno(input)); + } + + /* initialize the cwd -- this is never freed...*/ + update_cwd(); + + if (ENABLE_FEATURE_CLEAN_UP) atexit(free_memory); + + if (ENABLE_FEATURE_EDITING) cmdedit_set_initial_prompt(); + else PS1 = NULL; + + return busy_loop(input); +} diff --git a/shell/msh.c b/shell/msh.c new file mode 100644 index 0000000..0cb81fe --- /dev/null +++ b/shell/msh.c @@ -0,0 +1,5336 @@ +/* vi: set sw=4 ts=4: */ +/* + * Minix shell port for busybox + * + * This version of the Minix shell was adapted for use in busybox + * by Erik Andersen + * + * - backtick expansion did not work properly + * Jonas Holmberg + * Robert Schwebel + * Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include + +#ifdef STANDALONE +# ifndef _GNU_SOURCE +# define _GNU_SOURCE +# endif +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# define bb_dev_null "/dev/null" +# define DEFAULT_SHELL "/proc/self/exe" +# define CONFIG_BUSYBOX_EXEC_PATH "/proc/self/exe" +# define bb_banner "busybox standalone" +# define ENABLE_FEATURE_SH_STANDALONE 0 +# define bb_msg_memory_exhausted "memory exhausted" +# define xmalloc(size) malloc(size) +# define msh_main(argc,argv) main(argc,argv) +# define safe_read(fd,buf,count) read(fd,buf,count) +# define nonblock_safe_read(fd,buf,count) read(fd,buf,count) +# define NOT_LONE_DASH(s) ((s)[0] != '-' || (s)[1]) +# define LONE_CHAR(s,c) ((s)[0] == (c) && !(s)[1]) +# define NORETURN __attribute__ ((__noreturn__)) +static int find_applet_by_name(const char *applet) +{ + return -1; +} +static char *utoa_to_buf(unsigned n, char *buf, unsigned buflen) +{ + unsigned i, out, res; + assert(sizeof(unsigned) == 4); + if (buflen) { + out = 0; + for (i = 1000000000; i; i /= 10) { + res = n / i; + if (res || out || i == 1) { + if (!--buflen) break; + out++; + n -= res*i; + *buf++ = '0' + res; + } + } + } + return buf; +} +static char *itoa_to_buf(int n, char *buf, unsigned buflen) +{ + if (buflen && n < 0) { + n = -n; + *buf++ = '-'; + buflen--; + } + return utoa_to_buf((unsigned)n, buf, buflen); +} +static char local_buf[12]; +static char *itoa(int n) +{ + *(itoa_to_buf(n, local_buf, sizeof(local_buf))) = '\0'; + return local_buf; +} +#else +# include "busybox.h" /* for applet_names */ +#endif + +//#define MSHDEBUG 4 + +#ifdef MSHDEBUG +static int mshdbg = MSHDEBUG; + +#define DBGPRINTF(x) if (mshdbg > 0) printf x +#define DBGPRINTF0(x) if (mshdbg > 0) printf x +#define DBGPRINTF1(x) if (mshdbg > 1) printf x +#define DBGPRINTF2(x) if (mshdbg > 2) printf x +#define DBGPRINTF3(x) if (mshdbg > 3) printf x +#define DBGPRINTF4(x) if (mshdbg > 4) printf x +#define DBGPRINTF5(x) if (mshdbg > 5) printf x +#define DBGPRINTF6(x) if (mshdbg > 6) printf x +#define DBGPRINTF7(x) if (mshdbg > 7) printf x +#define DBGPRINTF8(x) if (mshdbg > 8) printf x +#define DBGPRINTF9(x) if (mshdbg > 9) printf x + +static int mshdbg_rc = 0; + +#define RCPRINTF(x) if (mshdbg_rc) printf x + +#else + +#define DBGPRINTF(x) +#define DBGPRINTF0(x) ((void)0) +#define DBGPRINTF1(x) ((void)0) +#define DBGPRINTF2(x) ((void)0) +#define DBGPRINTF3(x) ((void)0) +#define DBGPRINTF4(x) ((void)0) +#define DBGPRINTF5(x) ((void)0) +#define DBGPRINTF6(x) ((void)0) +#define DBGPRINTF7(x) ((void)0) +#define DBGPRINTF8(x) ((void)0) +#define DBGPRINTF9(x) ((void)0) + +#define RCPRINTF(x) ((void)0) + +#endif /* MSHDEBUG */ + + +#if ENABLE_FEATURE_EDITING_FANCY_PROMPT +# define DEFAULT_ROOT_PROMPT "\\u:\\w> " +# define DEFAULT_USER_PROMPT "\\u:\\w$ " +#else +# define DEFAULT_ROOT_PROMPT "# " +# define DEFAULT_USER_PROMPT "$ " +#endif + + +/* -------- sh.h -------- */ +/* + * shell + */ + +#define LINELIM 2100 +#define NPUSH 8 /* limit to input nesting */ + +#undef NOFILE +#define NOFILE 20 /* Number of open files */ +#define NUFILE 10 /* Number of user-accessible files */ +#define FDBASE 10 /* First file usable by Shell */ + +/* + * values returned by wait + */ +#define WAITSIG(s) ((s) & 0177) +#define WAITVAL(s) (((s) >> 8) & 0377) +#define WAITCORE(s) (((s) & 0200) != 0) + +/* + * library and system definitions + */ +typedef void xint; /* base type of jmp_buf, for not broken compilers */ + +/* + * shell components + */ +#define NOBLOCK ((struct op *)NULL) +#define NOWORD ((char *)NULL) +#define NOWORDS ((char **)NULL) +#define NOPIPE ((int *)NULL) + +/* + * redirection + */ +struct ioword { + smallint io_flag; /* action (below) */ + int io_fd; /* fd affected */ + char *io_name; /* file name */ +}; + +#define IOREAD 1 /* < */ +#define IOHERE 2 /* << (here file) */ +#define IOWRITE 4 /* > */ +#define IOCAT 8 /* >> */ +#define IOXHERE 16 /* ${}, ` in << */ +#define IODUP 32 /* >&digit */ +#define IOCLOSE 64 /* >&- */ + +#define IODEFAULT (-1) /* "default" IO fd */ + + +/* + * Description of a command or an operation on commands. + * Might eventually use a union. + */ +struct op { + smallint op_type; /* operation type, see Txxxx below */ + char **op_words; /* arguments to a command */ + struct ioword **ioact; /* IO actions (eg, < > >>) */ + struct op *left; + struct op *right; + char *str; /* identifier for case and for */ +}; + +#define TCOM 1 /* command */ +#define TPAREN 2 /* (c-list) */ +#define TPIPE 3 /* a | b */ +#define TLIST 4 /* a [&;] b */ +#define TOR 5 /* || */ +#define TAND 6 /* && */ +#define TFOR 7 +#define TDO 8 +#define TCASE 9 +#define TIF 10 +#define TWHILE 11 +#define TUNTIL 12 +#define TELIF 13 +#define TPAT 14 /* pattern in case */ +#define TBRACE 15 /* {c-list} */ +#define TASYNC 16 /* c & */ +/* Added to support "." file expansion */ +#define TDOT 17 + +/* Strings for names to make debug easier */ +#ifdef MSHDEBUG +static const char *const T_CMD_NAMES[] = { + "PLACEHOLDER", + "TCOM", + "TPAREN", + "TPIPE", + "TLIST", + "TOR", + "TAND", + "TFOR", + "TDO", + "TCASE", + "TIF", + "TWHILE", + "TUNTIL", + "TELIF", + "TPAT", + "TBRACE", + "TASYNC", + "TDOT", +}; +#endif + +#define AREASIZE (90000) + +/* + * flags to control evaluation of words + */ +#define DOSUB 1 /* interpret $, `, and quotes */ +#define DOBLANK 2 /* perform blank interpretation */ +#define DOGLOB 4 /* interpret [?* */ +#define DOKEY 8 /* move words with `=' to 2nd arg. list */ +#define DOTRIM 16 /* trim resulting string */ + +#define DOALL (DOSUB|DOBLANK|DOGLOB|DOKEY|DOTRIM) + + +struct brkcon { + jmp_buf brkpt; + struct brkcon *nextlev; +}; + + +static smallint trapset; /* trap pending (signal number) */ + +static smallint yynerrs; /* yacc (flag) */ + +/* moved to G: static char line[LINELIM]; */ + +#if ENABLE_FEATURE_EDITING +static char *current_prompt; +static line_input_t *line_input_state; +#endif + + +/* + * other functions + */ +static const char *rexecve(char *c, char **v, char **envp); +static char *evalstr(char *cp, int f); +static char *putn(int n); +static char *unquote(char *as); +static int rlookup(char *n); +static struct wdblock *glob(char *cp, struct wdblock *wb); +static int my_getc(int ec); +static int subgetc(char ec, int quoted); +static char **makenv(int all, struct wdblock *wb); +static char **eval(char **ap, int f); +static int setstatus(int s); +static int waitfor(int lastpid, int canintr); + +static void onintr(int s); /* SIGINT handler */ + +static int newenv(int f); +static void quitenv(void); +static void next(int f); +static void setdash(void); +static void onecommand(void); +static void runtrap(int i); + + +/* -------- area stuff -------- */ + +#define REGSIZE sizeof(struct region) +#define GROWBY (256) +/* #define SHRINKBY (64) */ +#undef SHRINKBY +#define FREE (32767) +#define BUSY (0) +#define ALIGN (sizeof(int)-1) + + +struct region { + struct region *next; + int area; +}; + + +/* -------- grammar stuff -------- */ +typedef union { + char *cp; + char **wp; + int i; + struct op *o; +} YYSTYPE; + +#define WORD 256 +#define LOGAND 257 +#define LOGOR 258 +#define BREAK 259 +#define IF 260 +#define THEN 261 +#define ELSE 262 +#define ELIF 263 +#define FI 264 +#define CASE 265 +#define ESAC 266 +#define FOR 267 +#define WHILE 268 +#define UNTIL 269 +#define DO 270 +#define DONE 271 +#define IN 272 +/* Added for "." file expansion */ +#define DOT 273 + +#define YYERRCODE 300 + +/* flags to yylex */ +#define CONTIN 01 /* skip new lines to complete command */ + +static struct op *pipeline(int cf); +static struct op *andor(void); +static struct op *c_list(void); +static int synio(int cf); +static void musthave(int c, int cf); +static struct op *simple(void); +static struct op *nested(int type, int mark); +static struct op *command(int cf); +static struct op *dogroup(int onlydone); +static struct op *thenpart(void); +static struct op *elsepart(void); +static struct op *caselist(void); +static struct op *casepart(void); +static char **pattern(void); +static char **wordlist(void); +static struct op *list(struct op *t1, struct op *t2); +static struct op *block(int type, struct op *t1, struct op *t2, char **wp); +static struct op *newtp(void); +static struct op *namelist(struct op *t); +static char **copyw(void); +static void word(char *cp); +static struct ioword **copyio(void); +static struct ioword *io(int u, int f, char *cp); +static int yylex(int cf); +static int collect(int c, int c1); +static int dual(int c); +static void diag(int ec); +static char *tree(unsigned size); + +/* -------- var.h -------- */ + +struct var { + char *value; + char *name; + struct var *next; + char status; +}; + +#define COPYV 1 /* flag to setval, suggesting copy */ +#define RONLY 01 /* variable is read-only */ +#define EXPORT 02 /* variable is to be exported */ +#define GETCELL 04 /* name & value space was got with getcell */ + +static int yyparse(void); + + +/* -------- io.h -------- */ +/* io buffer */ +struct iobuf { + unsigned id; /* buffer id */ + char buf[512]; /* buffer */ + char *bufp; /* pointer into buffer */ + char *ebufp; /* pointer to end of buffer */ +}; + +/* possible arguments to an IO function */ +struct ioarg { + const char *aword; + char **awordlist; + int afile; /* file descriptor */ + unsigned afid; /* buffer id */ + off_t afpos; /* file position */ + struct iobuf *afbuf; /* buffer for this file */ +}; + +/* an input generator's state */ +struct io { + int (*iofn) (struct ioarg *, struct io *); + struct ioarg *argp; + int peekc; + char prev; /* previous character read by readc() */ + char nlcount; /* for `'s */ + char xchar; /* for `'s */ + char task; /* reason for pushed IO */ +}; +/* ->task: */ +#define XOTHER 0 /* none of the below */ +#define XDOLL 1 /* expanding ${} */ +#define XGRAVE 2 /* expanding `'s */ +#define XIO 3 /* file IO */ + + +/* + * input generators for IO structure + */ +static int nlchar(struct ioarg *ap); +static int strchar(struct ioarg *ap); +static int qstrchar(struct ioarg *ap); +static int filechar(struct ioarg *ap); +static int herechar(struct ioarg *ap); +static int linechar(struct ioarg *ap); +static int gravechar(struct ioarg *ap, struct io *iop); +static int qgravechar(struct ioarg *ap, struct io *iop); +static int dolchar(struct ioarg *ap); +static int wdchar(struct ioarg *ap); +static void scraphere(void); +static void freehere(int area); +static void gethere(void); +static void markhere(char *s, struct ioword *iop); +static int herein(char *hname, int xdoll); +static int run(struct ioarg *argp, int (*f) (struct ioarg *)); + + +static int eofc(void); +static int readc(void); +static void unget(int c); +static void ioecho(char c); + + +/* + * IO control + */ +static void pushio(struct ioarg *argp, int (*f) (struct ioarg *)); +#define PUSHIO(what,arg,gen) ((temparg.what = (arg)), pushio(&temparg,(gen))) +static int remap(int fd); +static int openpipe(int *pv); +static void closepipe(int *pv); +static struct io *setbase(struct io *ip); + +/* -------- word.h -------- */ + +#define NSTART 16 /* default number of words to allow for initially */ + +struct wdblock { + short w_bsize; + short w_nword; + /* bounds are arbitrary */ + char *w_words[1]; +}; + +static struct wdblock *addword(char *wd, struct wdblock *wb); +static struct wdblock *newword(int nw); +static char **getwords(struct wdblock *wb); + +/* -------- misc stuff -------- */ + +static int dolabel(struct op *t, char **args); +static int dohelp(struct op *t, char **args); +static int dochdir(struct op *t, char **args); +static int doshift(struct op *t, char **args); +static int dologin(struct op *t, char **args); +static int doumask(struct op *t, char **args); +static int doexec(struct op *t, char **args); +static int dodot(struct op *t, char **args); +static int dowait(struct op *t, char **args); +static int doread(struct op *t, char **args); +static int doeval(struct op *t, char **args); +static int dotrap(struct op *t, char **args); +static int dobreak(struct op *t, char **args); +static int doexit(struct op *t, char **args); +static int doexport(struct op *t, char **args); +static int doreadonly(struct op *t, char **args); +static int doset(struct op *t, char **args); +static int dotimes(struct op *t, char **args); +static int docontinue(struct op *t, char **args); + +static int forkexec(struct op *t, int *pin, int *pout, int no_fork, char **wp); +static int execute(struct op *t, int *pin, int *pout, int no_fork); +static int iosetup(struct ioword *iop, int pipein, int pipeout); +static void brkset(struct brkcon *bc); +static int getsig(char *s); +static void setsig(int n, sighandler_t f); +static int getn(char *as); +static int brkcontin(char *cp, int val); +static void rdexp(char **wp, void (*f) (struct var *), int key); +static void badid(char *s); +static void varput(char *s, int out); +static int expand(const char *cp, struct wdblock **wbp, int f); +static char *blank(int f); +static int dollar(int quoted); +static int grave(int quoted); +static void globname(char *we, char *pp); +static char *generate(char *start1, char *end1, char *middle, char *end); +static int anyspcl(struct wdblock *wb); +static void readhere(char **name, char *s, int ec); +static int xxchar(struct ioarg *ap); + +struct here { + char *h_tag; + char h_dosub; + struct ioword *h_iop; + struct here *h_next; +}; + +static const char *const signame[] = { + "Signal 0", + "Hangup", + NULL, /* interrupt */ + "Quit", + "Illegal instruction", + "Trace/BPT trap", + "Abort", + "Bus error", + "Floating Point Exception", + "Killed", + "SIGUSR1", + "SIGSEGV", + "SIGUSR2", + NULL, /* broken pipe */ + "Alarm clock", + "Terminated" +}; + + +typedef int (*builtin_func_ptr)(struct op *, char **); + +struct builtincmd { + const char *name; + builtin_func_ptr builtinfunc; +}; + +static const struct builtincmd builtincmds[] = { + { "." , dodot }, + { ":" , dolabel }, + { "break" , dobreak }, + { "cd" , dochdir }, + { "continue", docontinue }, + { "eval" , doeval }, + { "exec" , doexec }, + { "exit" , doexit }, + { "export" , doexport }, + { "help" , dohelp }, + { "login" , dologin }, + { "newgrp" , dologin }, + { "read" , doread }, + { "readonly", doreadonly }, + { "set" , doset }, + { "shift" , doshift }, + { "times" , dotimes }, + { "trap" , dotrap }, + { "umask" , doumask }, + { "wait" , dowait }, + { NULL , NULL }, +}; + +static struct op *dowholefile(int /*, int*/); + + +/* Globals */ +static char **dolv; +static int dolc; +static uint8_t exstat; +static smallint gflg; /* (seems to be a parse error indicator) */ +static smallint interactive; /* Is this an interactive shell */ +static smallint execflg; +static smallint isbreak; /* "break" statement was seen */ +static int multiline; /* '\n' changed to ';' (counter) */ +static struct op *outtree; /* result from parser */ +static xint *failpt; +static xint *errpt; +static struct brkcon *brklist; +static struct wdblock *wdlist; +static struct wdblock *iolist; + +#ifdef MSHDEBUG +static struct var *mshdbg_var; +#endif +static struct var *vlist; /* dictionary */ +static struct var *homedir; /* home directory */ +static struct var *prompt; /* main prompt */ +static struct var *cprompt; /* continuation prompt */ +static struct var *path; /* search path for commands */ +static struct var *shell; /* shell to interpret command files */ +static struct var *ifs; /* field separators */ + +static int areanum; /* current allocation area */ +static smallint intr; /* interrupt pending (bool) */ +static smallint heedint = 1; /* heed interrupt signals (bool) */ +static int inparse; +static char *null = (char*)""; /* null value for variable */ +static void (*qflag)(int) = SIG_IGN; +static int startl; +static int peeksym; +static int nlseen; +static int iounit = IODEFAULT; +static YYSTYPE yylval; +static char *elinep; /* done in main(): = line + sizeof(line) - 5 */ + +static struct here *inhere; /* list of hear docs while parsing */ +static struct here *acthere; /* list of active here documents */ +static struct region *areabot; /* bottom of area */ +static struct region *areatop; /* top of area */ +static struct region *areanxt; /* starting point of scan */ +static void *brktop; +static void *brkaddr; + +#define AFID_NOBUF (~0) +#define AFID_ID 0 + + +/* + * parsing & execution environment + */ +struct env { + char *linep; + struct io *iobase; + struct io *iop; + xint *errpt; /* void * */ + int iofd; + struct env *oenv; +}; + + +struct globals { + struct env global_env; + struct ioarg temparg; // = { .afid = AFID_NOBUF }; /* temporary for PUSHIO */ + unsigned bufid; // = AFID_ID; /* buffer id counter */ + char ourtrap[_NSIG + 1]; + char *trap[_NSIG + 1]; + struct iobuf sharedbuf; /* in main(): set to { AFID_NOBUF } */ + struct iobuf mainbuf; /* in main(): set to { AFID_NOBUF } */ + struct ioarg ioargstack[NPUSH]; + /* + * flags: + * -e: quit on error + * -k: look for name=value everywhere on command line + * -n: no execution + * -t: exit after reading and executing one command + * -v: echo as read + * -x: trace + * -u: unset variables net diagnostic + */ + char flags['z' - 'a' + 1]; + char filechar_cmdbuf[BUFSIZ]; + char line[LINELIM]; + char child_cmd[LINELIM]; + + struct io iostack[NPUSH]; + + char grave__var_name[LINELIM]; + char grave__alt_value[LINELIM]; +}; + +#define G (*ptr_to_globals) +#define global_env (G.global_env ) +#define temparg (G.temparg ) +#define bufid (G.bufid ) +#define ourtrap (G.ourtrap ) +#define trap (G.trap ) +#define sharedbuf (G.sharedbuf ) +#define mainbuf (G.mainbuf ) +#define ioargstack (G.ioargstack ) +/* this looks weird, but is OK ... we index FLAG with 'a'...'z' */ +#define FLAG (G.flags - 'a' ) +#define filechar_cmdbuf (G.filechar_cmdbuf) +#define line (G.line ) +#define child_cmd (G.child_cmd ) +#define iostack (G.iostack ) +#define INIT_G() do { \ + SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \ + global_env.linep = line; \ + global_env.iobase = iostack; \ + global_env.iop = iostack - 1; \ + global_env.iofd = FDBASE; \ + temparg.afid = AFID_NOBUF; \ + bufid = AFID_ID; \ +} while (0) + + +/* in substitution */ +#define INSUB() (global_env.iop->task == XGRAVE || global_env.iop->task == XDOLL) + +#define RUN(what, arg, gen) ((temparg.what = (arg)), run(&temparg, (gen))) + +#ifdef MSHDEBUG +static void print_tree(struct op *head) +{ + if (head == NULL) { + DBGPRINTF(("PRINT_TREE: no tree\n")); + return; + } + + DBGPRINTF(("NODE: %p, left %p, right %p\n", head, head->left, + head->right)); + + if (head->left) + print_tree(head->left); + + if (head->right) + print_tree(head->right); +} +#endif /* MSHDEBUG */ + + +/* + * IO functions + */ +static void prs(const char *s) +{ + if (*s) + write(STDERR_FILENO, s, strlen(s)); +} + +static void prn(unsigned u) +{ + prs(itoa(u)); +} + +static void echo(char **wp) +{ + int i; + + prs("+"); + for (i = 0; wp[i]; i++) { + if (i) + prs(" "); + prs(wp[i]); + } + prs("\n"); +} + +static void closef(int i) +{ + if (i > 2) + close(i); +} + +static void closeall(void) +{ + int u; + + for (u = NUFILE; u < NOFILE;) + close(u++); +} + + +/* fail but return to process next command */ +static void fail(void) NORETURN; +static void fail(void) +{ + longjmp(failpt, 1); + /* NOTREACHED */ +} + +/* abort shell (or fail in subshell) */ +static void leave(void) NORETURN; +static void leave(void) +{ + DBGPRINTF(("LEAVE: leave called!\n")); + + if (execflg) + fail(); + scraphere(); + freehere(1); + runtrap(0); + _exit(exstat); + /* NOTREACHED */ +} + +static void warn(const char *s) +{ + if (*s) { + prs(s); + if (!exstat) + exstat = 255; + } + prs("\n"); + if (FLAG['e']) + leave(); +} + +static void err(const char *s) +{ + warn(s); + if (FLAG['n']) + return; + if (!interactive) + leave(); + if (global_env.errpt) + longjmp(global_env.errpt, 1); + closeall(); + global_env.iop = global_env.iobase = iostack; +} + + +/* -------- area.c -------- */ + +/* + * All memory between (char *)areabot and (char *)(areatop+1) is + * exclusively administered by the area management routines. + * It is assumed that sbrk() and brk() manipulate the high end. + */ + +#define sbrk(X) ({ \ + void * __q = (void *)-1; \ + if (brkaddr + (int)(X) < brktop) { \ + __q = brkaddr; \ + brkaddr += (int)(X); \ + } \ + __q; \ +}) + +static void initarea(void) +{ + brkaddr = xmalloc(AREASIZE); + brktop = brkaddr + AREASIZE; + + while ((long) sbrk(0) & ALIGN) + sbrk(1); + areabot = (struct region *) sbrk(REGSIZE); + + areabot->next = areabot; + areabot->area = BUSY; + areatop = areabot; + areanxt = areabot; +} + +static char *getcell(unsigned nbytes) +{ + int nregio; + struct region *p, *q; + int i; + + if (nbytes == 0) { + puts("getcell(0)"); + abort(); + } + /* silly and defeats the algorithm */ + /* + * round upwards and add administration area + */ + nregio = (nbytes + (REGSIZE - 1)) / REGSIZE + 1; + p = areanxt; + for (;;) { + if (p->area > areanum) { + /* + * merge free cells + */ + while ((q = p->next)->area > areanum && q != areanxt) + p->next = q->next; + /* + * exit loop if cell big enough + */ + if (q >= p + nregio) + goto found; + } + p = p->next; + if (p == areanxt) + break; + } + i = nregio >= GROWBY ? nregio : GROWBY; + p = (struct region *) sbrk(i * REGSIZE); + if (p == (struct region *) -1) + return NULL; + p--; + if (p != areatop) { + puts("not contig"); + abort(); /* allocated areas are contiguous */ + } + q = p + i; + p->next = q; + p->area = FREE; + q->next = areabot; + q->area = BUSY; + areatop = q; + found: + /* + * we found a FREE area big enough, pointed to by 'p', and up to 'q' + */ + areanxt = p + nregio; + if (areanxt < q) { + /* + * split into requested area and rest + */ + if (areanxt + 1 > q) { + puts("OOM"); + abort(); /* insufficient space left for admin */ + } + areanxt->next = q; + areanxt->area = FREE; + p->next = areanxt; + } + p->area = areanum; + return (char *) (p + 1); +} + +static void freecell(char *cp) +{ + struct region *p; + + p = (struct region *) cp; + if (p != NULL) { + p--; + if (p < areanxt) + areanxt = p; + p->area = FREE; + } +} +#define DELETE(obj) freecell((char *)obj) + +static void freearea(int a) +{ + struct region *p, *top; + + top = areatop; + for (p = areabot; p != top; p = p->next) + if (p->area >= a) + p->area = FREE; +} + +static void setarea(char *cp, int a) +{ + struct region *p; + + p = (struct region *) cp; + if (p != NULL) + (p - 1)->area = a; +} + +static int getarea(char *cp) +{ + return ((struct region *) cp - 1)->area; +} + +static void garbage(void) +{ + struct region *p, *q, *top; + + top = areatop; + for (p = areabot; p != top; p = p->next) { + if (p->area > areanum) { + while ((q = p->next)->area > areanum) + p->next = q->next; + areanxt = p; + } + } +#ifdef SHRINKBY + if (areatop >= q + SHRINKBY && q->area > areanum) { + brk((char *) (q + 1)); + q->next = areabot; + q->area = BUSY; + areatop = q; + } +#endif +} + +static void *get_space(int n) +{ + char *cp; + + cp = getcell(n); + if (cp == NULL) + err("out of string space"); + return cp; +} + +static char *strsave(const char *s, int a) +{ + char *cp; + + cp = get_space(strlen(s) + 1); + if (cp == NULL) { +// FIXME: I highly doubt this is good. + return (char*)""; + } + setarea(cp, a); + strcpy(cp, s); + return cp; +} + + +/* -------- var.c -------- */ + +static int eqname(const char *n1, const char *n2) +{ + for (; *n1 != '=' && *n1 != '\0'; n1++) + if (*n2++ != *n1) + return 0; + return *n2 == '\0' || *n2 == '='; +} + +static const char *findeq(const char *cp) +{ + while (*cp != '\0' && *cp != '=') + cp++; + return cp; +} + +/* + * Find the given name in the dictionary + * and return its value. If the name was + * not previously there, enter it now and + * return a null value. + */ +static struct var *lookup(const char *n) +{ +// FIXME: dirty hack + static struct var dummy; + + struct var *vp; + const char *cp; + char *xp; + int c; + + if (isdigit(*n)) { + dummy.name = (char*)n; + for (c = 0; isdigit(*n) && c < 1000; n++) + c = c * 10 + *n - '0'; + dummy.status = RONLY; + dummy.value = (c <= dolc ? dolv[c] : null); + return &dummy; + } + + for (vp = vlist; vp; vp = vp->next) + if (eqname(vp->name, n)) + return vp; + + cp = findeq(n); + vp = get_space(sizeof(*vp)); + if (vp == 0 || (vp->name = get_space((int) (cp - n) + 2)) == NULL) { + dummy.name = dummy.value = (char*)""; + return &dummy; + } + + xp = vp->name; + while ((*xp = *n++) != '\0' && *xp != '=') + xp++; + *xp++ = '='; + *xp = '\0'; + setarea((char *) vp, 0); + setarea((char *) vp->name, 0); + vp->value = null; + vp->next = vlist; + vp->status = GETCELL; + vlist = vp; + return vp; +} + +/* + * if name is not NULL, it must be + * a prefix of the space `val', + * and end with `='. + * this is all so that exporting + * values is reasonably painless. + */ +static void nameval(struct var *vp, const char *val, const char *name) +{ + const char *cp; + char *xp; + int fl; + + if (vp->status & RONLY) { + xp = vp->name; + while (*xp && *xp != '=') + fputc(*xp++, stderr); + err(" is read-only"); + return; + } + fl = 0; + if (name == NULL) { + xp = get_space(strlen(vp->name) + strlen(val) + 2); + if (xp == NULL) + return; + /* make string: name=value */ + setarea(xp, 0); + name = xp; + cp = vp->name; + while ((*xp = *cp++) != '\0' && *xp != '=') + xp++; + *xp++ = '='; + strcpy(xp, val); + val = xp; + fl = GETCELL; + } + if (vp->status & GETCELL) + freecell(vp->name); /* form new string `name=value' */ + vp->name = (char*)name; + vp->value = (char*)val; + vp->status |= fl; +} + +/* + * give variable at `vp' the value `val'. + */ +static void setval(struct var *vp, const char *val) +{ + nameval(vp, val, NULL); +} + +static void export(struct var *vp) +{ + vp->status |= EXPORT; +} + +static void ronly(struct var *vp) +{ + if (isalpha(vp->name[0]) || vp->name[0] == '_') /* not an internal symbol */ + vp->status |= RONLY; +} + +static int isassign(const char *s) +{ + unsigned char c; + DBGPRINTF7(("ISASSIGN: enter, s=%s\n", s)); + + c = *s; + /* no isalpha() - we shouldn't use locale */ + /* c | 0x20 - lowercase (Latin) letters */ + if (c != '_' && (unsigned)((c|0x20) - 'a') > 25) + /* not letter */ + return 0; + + while (1) { + c = *++s; + if (c == '=') + return 1; + if (c == '\0') + return 0; + if (c != '_' + && (unsigned)(c - '0') > 9 /* not number */ + && (unsigned)((c|0x20) - 'a') > 25 /* not letter */ + ) { + return 0; + } + } +} + +static int assign(const char *s, int cf) +{ + const char *cp; + struct var *vp; + + DBGPRINTF7(("ASSIGN: enter, s=%s, cf=%d\n", s, cf)); + + if (!isalpha(*s) && *s != '_') + return 0; + for (cp = s; *cp != '='; cp++) + if (*cp == '\0' || (!isalnum(*cp) && *cp != '_')) + return 0; + vp = lookup(s); + nameval(vp, ++cp, cf == COPYV ? NULL : s); + if (cf != COPYV) + vp->status &= ~GETCELL; + return 1; +} + +static int checkname(char *cp) +{ + DBGPRINTF7(("CHECKNAME: enter, cp=%s\n", cp)); + + if (!isalpha(*cp++) && *(cp - 1) != '_') + return 0; + while (*cp) + if (!isalnum(*cp++) && *(cp - 1) != '_') + return 0; + return 1; +} + +static void putvlist(int f, int out) +{ + struct var *vp; + + for (vp = vlist; vp; vp = vp->next) { + if (vp->status & f && (isalpha(*vp->name) || *vp->name == '_')) { + if (vp->status & EXPORT) + write(out, "export ", 7); + if (vp->status & RONLY) + write(out, "readonly ", 9); + write(out, vp->name, (int) (findeq(vp->name) - vp->name)); + write(out, "\n", 1); + } + } +} + + +/* + * trap handling + */ +static void sig(int i) +{ + trapset = i; + signal(i, sig); +} + +static void runtrap(int i) +{ + char *trapstr; + + trapstr = trap[i]; + if (trapstr == NULL) + return; + + if (i == 0) + trap[i] = NULL; + + RUN(aword, trapstr, nlchar); +} + + +static void setdash(void) +{ + char *cp; + int c; + char m['z' - 'a' + 1]; + + cp = m; + for (c = 'a'; c <= 'z'; c++) + if (FLAG[c]) + *cp++ = c; + *cp = '\0'; + setval(lookup("-"), m); +} + +static int newfile(char *s) +{ + int f; + + DBGPRINTF7(("NEWFILE: opening %s\n", s)); + + f = 0; + if (NOT_LONE_DASH(s)) { + DBGPRINTF(("NEWFILE: s is %s\n", s)); + f = open(s, O_RDONLY); + if (f < 0) { + prs(s); + err(": can't open"); + return 1; + } + } + + next(remap(f)); + return 0; +} + + +#ifdef UNUSED +struct op *scantree(struct op *head) +{ + struct op *dotnode; + + if (head == NULL) + return NULL; + + if (head->left != NULL) { + dotnode = scantree(head->left); + if (dotnode) + return dotnode; + } + + if (head->right != NULL) { + dotnode = scantree(head->right); + if (dotnode) + return dotnode; + } + + if (head->op_words == NULL) + return NULL; + + DBGPRINTF5(("SCANTREE: checking node %p\n", head)); + + if ((head->op_type != TDOT) && LONE_CHAR(head->op_words[0], '.')) { + DBGPRINTF5(("SCANTREE: dot found in node %p\n", head)); + return head; + } + + return NULL; +} +#endif + + +static void onecommand(void) +{ + int i; + jmp_buf m1; + + DBGPRINTF(("ONECOMMAND: enter, outtree=%p\n", outtree)); + + while (global_env.oenv) + quitenv(); + + areanum = 1; + freehere(areanum); + freearea(areanum); + garbage(); + wdlist = NULL; + iolist = NULL; + global_env.errpt = NULL; + global_env.linep = line; + yynerrs = 0; + multiline = 0; + inparse = 1; + intr = 0; + execflg = 0; + + failpt = m1; + setjmp(failpt); /* Bruce Evans' fix */ + failpt = m1; + if (setjmp(failpt) || yyparse() || intr) { + DBGPRINTF(("ONECOMMAND: this is not good.\n")); + + while (global_env.oenv) + quitenv(); + scraphere(); + if (!interactive && intr) + leave(); + inparse = 0; + intr = 0; + return; + } + + inparse = 0; + brklist = 0; + intr = 0; + execflg = 0; + + if (!FLAG['n']) { + DBGPRINTF(("ONECOMMAND: calling execute, t=outtree=%p\n", + outtree)); + execute(outtree, NOPIPE, NOPIPE, /* no_fork: */ 0); + } + + if (!interactive && intr) { + execflg = 0; + leave(); + } + + i = trapset; + if (i != 0) { + trapset = 0; + runtrap(i); + } +} + +static int newenv(int f) +{ + struct env *ep; + + DBGPRINTF(("NEWENV: f=%d (indicates quitenv and return)\n", f)); + + if (f) { + quitenv(); + return 1; + } + + ep = get_space(sizeof(*ep)); + if (ep == NULL) { + while (global_env.oenv) + quitenv(); + fail(); + } + *ep = global_env; + global_env.oenv = ep; + global_env.errpt = errpt; + + return 0; +} + +static void quitenv(void) +{ + struct env *ep; + int fd; + + DBGPRINTF(("QUITENV: global_env.oenv=%p\n", global_env.oenv)); + + ep = global_env.oenv; + if (ep != NULL) { + fd = global_env.iofd; + global_env = *ep; + /* should close `'d files */ + DELETE(ep); + while (--fd >= global_env.iofd) + close(fd); + } +} + +/* + * Is character c in s? + */ +static int any(int c, const char *s) +{ + while (*s) + if (*s++ == c) + return 1; + return 0; +} + +/* + * Is any character from s1 in s2? + */ +static int anys(const char *s1, const char *s2) +{ + while (*s1) + if (any(*s1++, s2)) + return 1; + return 0; +} + +static char *putn(int n) +{ + return itoa(n); +} + +static void next(int f) +{ + PUSHIO(afile, f, filechar); +} + +static void onintr(int s UNUSED_PARAM) /* ANSI C requires a parameter */ +{ + signal(SIGINT, onintr); + intr = 1; + if (interactive) { + if (inparse) { + prs("\n"); + fail(); + } + } else if (heedint) { + execflg = 0; + leave(); + } +} + + +/* -------- gmatch.c -------- */ +/* + * int gmatch(string, pattern) + * char *string, *pattern; + * + * Match a pattern as in sh(1). + */ + +#define CMASK 0377 +#define QUOTE 0200 +#define QMASK (CMASK & ~QUOTE) +#define NOT '!' /* might use ^ */ + +static const char *cclass(const char *p, int sub) +{ + int c, d, not, found; + + not = (*p == NOT); + if (not != 0) + p++; + found = not; + do { + if (*p == '\0') + return NULL; + c = *p & CMASK; + if (p[1] == '-' && p[2] != ']') { + d = p[2] & CMASK; + p++; + } else + d = c; + if (c == sub || (c <= sub && sub <= d)) + found = !not; + } while (*++p != ']'); + return found ? p + 1 : NULL; +} + +static int gmatch(const char *s, const char *p) +{ + int sc, pc; + + if (s == NULL || p == NULL) + return 0; + + while ((pc = *p++ & CMASK) != '\0') { + sc = *s++ & QMASK; + switch (pc) { + case '[': + p = cclass(p, sc); + if (p == NULL) + return 0; + break; + + case '?': + if (sc == 0) + return 0; + break; + + case '*': + s--; + do { + if (*p == '\0' || gmatch(s, p)) + return 1; + } while (*s++ != '\0'); + return 0; + + default: + if (sc != (pc & ~QUOTE)) + return 0; + } + } + return *s == '\0'; +} + + +/* -------- csyn.c -------- */ +/* + * shell: syntax (C version) + */ + +static void yyerror(const char *s) NORETURN; +static void yyerror(const char *s) +{ + yynerrs = 1; + if (interactive && global_env.iop <= iostack) { + multiline = 0; + while (eofc() == 0 && yylex(0) != '\n') + continue; + } + err(s); + fail(); +} + +static void zzerr(void) NORETURN; +static void zzerr(void) +{ + yyerror("syntax error"); +} + +int yyparse(void) +{ + DBGPRINTF7(("YYPARSE: enter...\n")); + + startl = 1; + peeksym = 0; + yynerrs = 0; + outtree = c_list(); + musthave('\n', 0); + return yynerrs; /* 0/1 */ +} + +static struct op *pipeline(int cf) +{ + struct op *t, *p; + int c; + + DBGPRINTF7(("PIPELINE: enter, cf=%d\n", cf)); + + t = command(cf); + + DBGPRINTF9(("PIPELINE: t=%p\n", t)); + + if (t != NULL) { + while ((c = yylex(0)) == '|') { + p = command(CONTIN); + if (p == NULL) { + DBGPRINTF8(("PIPELINE: error!\n")); + zzerr(); + } + + if (t->op_type != TPAREN && t->op_type != TCOM) { + /* shell statement */ + t = block(TPAREN, t, NOBLOCK, NOWORDS); + } + + t = block(TPIPE, t, p, NOWORDS); + } + peeksym = c; + } + + DBGPRINTF7(("PIPELINE: returning t=%p\n", t)); + return t; +} + +static struct op *andor(void) +{ + struct op *t, *p; + int c; + + DBGPRINTF7(("ANDOR: enter...\n")); + + t = pipeline(0); + + DBGPRINTF9(("ANDOR: t=%p\n", t)); + + if (t != NULL) { + while ((c = yylex(0)) == LOGAND || c == LOGOR) { + p = pipeline(CONTIN); + if (p == NULL) { + DBGPRINTF8(("ANDOR: error!\n")); + zzerr(); + } + + t = block(c == LOGAND ? TAND : TOR, t, p, NOWORDS); + } + + peeksym = c; + } + + DBGPRINTF7(("ANDOR: returning t=%p\n", t)); + return t; +} + +static struct op *c_list(void) +{ + struct op *t, *p; + int c; + + DBGPRINTF7(("C_LIST: enter...\n")); + + t = andor(); + + if (t != NULL) { + peeksym = yylex(0); + if (peeksym == '&') + t = block(TASYNC, t, NOBLOCK, NOWORDS); + + while ((c = yylex(0)) == ';' || c == '&' + || (multiline && c == '\n') + ) { + p = andor(); + if (p== NULL) + return t; + + peeksym = yylex(0); + if (peeksym == '&') + p = block(TASYNC, p, NOBLOCK, NOWORDS); + + t = list(t, p); + } /* WHILE */ + + peeksym = c; + } + /* IF */ + DBGPRINTF7(("C_LIST: returning t=%p\n", t)); + return t; +} + +static int synio(int cf) +{ + struct ioword *iop; + int i; + int c; + + DBGPRINTF7(("SYNIO: enter, cf=%d\n", cf)); + + c = yylex(cf); + if (c != '<' && c != '>') { + peeksym = c; + return 0; + } + + i = yylval.i; + musthave(WORD, 0); + iop = io(iounit, i, yylval.cp); + iounit = IODEFAULT; + + if (i & IOHERE) + markhere(yylval.cp, iop); + + DBGPRINTF7(("SYNIO: returning 1\n")); + return 1; +} + +static void musthave(int c, int cf) +{ + peeksym = yylex(cf); + if (peeksym != c) { + DBGPRINTF7(("MUSTHAVE: error!\n")); + zzerr(); + } + + peeksym = 0; +} + +static struct op *simple(void) +{ + struct op *t; + + t = NULL; + for (;;) { + switch (peeksym = yylex(0)) { + case '<': + case '>': + (void) synio(0); + break; + + case WORD: + if (t == NULL) { + t = newtp(); + t->op_type = TCOM; + } + peeksym = 0; + word(yylval.cp); + break; + + default: + return t; + } + } +} + +static struct op *nested(int type, int mark) +{ + struct op *t; + + DBGPRINTF3(("NESTED: enter, type=%d, mark=%d\n", type, mark)); + + multiline++; + t = c_list(); + musthave(mark, 0); + multiline--; + return block(type, t, NOBLOCK, NOWORDS); +} + +static struct op *command(int cf) +{ + struct op *t; + struct wdblock *iosave; + int c; + + DBGPRINTF(("COMMAND: enter, cf=%d\n", cf)); + + iosave = iolist; + iolist = NULL; + + if (multiline) + cf |= CONTIN; + + while (synio(cf)) + cf = 0; + + c = yylex(cf); + + switch (c) { + default: + peeksym = c; + t = simple(); + if (t == NULL) { + if (iolist == NULL) + return NULL; + t = newtp(); + t->op_type = TCOM; + } + break; + + case '(': + t = nested(TPAREN, ')'); + break; + + case '{': + t = nested(TBRACE, '}'); + break; + + case FOR: + t = newtp(); + t->op_type = TFOR; + musthave(WORD, 0); + startl = 1; + t->str = yylval.cp; + multiline++; + t->op_words = wordlist(); + c = yylex(0); + if (c != '\n' && c != ';') + peeksym = c; + t->left = dogroup(0); + multiline--; + break; + + case WHILE: + case UNTIL: + multiline++; + t = newtp(); + t->op_type = (c == WHILE ? TWHILE : TUNTIL); + t->left = c_list(); + t->right = dogroup(1); + /* t->op_words = NULL; - newtp() did this */ + multiline--; + break; + + case CASE: + t = newtp(); + t->op_type = TCASE; + musthave(WORD, 0); + t->str = yylval.cp; + startl++; + multiline++; + musthave(IN, CONTIN); + startl++; + + t->left = caselist(); + + musthave(ESAC, 0); + multiline--; + break; + + case IF: + multiline++; + t = newtp(); + t->op_type = TIF; + t->left = c_list(); + t->right = thenpart(); + musthave(FI, 0); + multiline--; + break; + + case DOT: + t = newtp(); + t->op_type = TDOT; + + musthave(WORD, 0); /* gets name of file */ + DBGPRINTF7(("COMMAND: DOT clause, yylval.cp is %s\n", yylval.cp)); + + word(yylval.cp); /* add word to wdlist */ + word(NOWORD); /* terminate wdlist */ + t->op_words = copyw(); /* dup wdlist */ + break; + + } + + while (synio(0)) + continue; + + t = namelist(t); + iolist = iosave; + + DBGPRINTF(("COMMAND: returning %p\n", t)); + + return t; +} + +static struct op *dowholefile(int type /*, int mark*/) +{ + struct op *t; + + DBGPRINTF(("DOWHOLEFILE: enter, type=%d\n", type /*, mark*/)); + + multiline++; + t = c_list(); + multiline--; + t = block(type, t, NOBLOCK, NOWORDS); + DBGPRINTF(("DOWHOLEFILE: return t=%p\n", t)); + return t; +} + +static struct op *dogroup(int onlydone) +{ + int c; + struct op *mylist; + + c = yylex(CONTIN); + if (c == DONE && onlydone) + return NULL; + if (c != DO) + zzerr(); + mylist = c_list(); + musthave(DONE, 0); + return mylist; +} + +static struct op *thenpart(void) +{ + int c; + struct op *t; + + c = yylex(0); + if (c != THEN) { + peeksym = c; + return NULL; + } + t = newtp(); + /*t->op_type = 0; - newtp() did this */ + t->left = c_list(); + if (t->left == NULL) + zzerr(); + t->right = elsepart(); + return t; +} + +static struct op *elsepart(void) +{ + int c; + struct op *t; + + switch (c = yylex(0)) { + case ELSE: + t = c_list(); + if (t == NULL) + zzerr(); + return t; + + case ELIF: + t = newtp(); + t->op_type = TELIF; + t->left = c_list(); + t->right = thenpart(); + return t; + + default: + peeksym = c; + return NULL; + } +} + +static struct op *caselist(void) +{ + struct op *t; + + t = NULL; + while ((peeksym = yylex(CONTIN)) != ESAC) { + DBGPRINTF(("CASELIST, doing yylex, peeksym=%d\n", peeksym)); + t = list(t, casepart()); + } + + DBGPRINTF(("CASELIST, returning t=%p\n", t)); + return t; +} + +static struct op *casepart(void) +{ + struct op *t; + + DBGPRINTF7(("CASEPART: enter...\n")); + + t = newtp(); + t->op_type = TPAT; + t->op_words = pattern(); + musthave(')', 0); + t->left = c_list(); + peeksym = yylex(CONTIN); + if (peeksym != ESAC) + musthave(BREAK, CONTIN); + + DBGPRINTF7(("CASEPART: made newtp(TPAT, t=%p)\n", t)); + + return t; +} + +static char **pattern(void) +{ + int c, cf; + + cf = CONTIN; + do { + musthave(WORD, cf); + word(yylval.cp); + cf = 0; + c = yylex(0); + } while (c == '|'); + peeksym = c; + word(NOWORD); + + return copyw(); +} + +static char **wordlist(void) +{ + int c; + + c = yylex(0); + if (c != IN) { + peeksym = c; + return NULL; + } + startl = 0; + while ((c = yylex(0)) == WORD) + word(yylval.cp); + word(NOWORD); + peeksym = c; + return copyw(); +} + +/* + * supporting functions + */ +static struct op *list(struct op *t1, struct op *t2) +{ + DBGPRINTF7(("LIST: enter, t1=%p, t2=%p\n", t1, t2)); + + if (t1 == NULL) + return t2; + if (t2 == NULL) + return t1; + + return block(TLIST, t1, t2, NOWORDS); +} + +static struct op *block(int type, struct op *t1, struct op *t2, char **wp) +{ + struct op *t; + + DBGPRINTF7(("BLOCK: enter, type=%d (%s)\n", type, T_CMD_NAMES[type])); + + t = newtp(); + t->op_type = type; + t->left = t1; + t->right = t2; + t->op_words = wp; + + DBGPRINTF7(("BLOCK: inserted %p between %p and %p\n", t, t1, t2)); + + return t; +} + +/* See if given string is a shell multiline (FOR, IF, etc) */ +static int rlookup(char *n) +{ + struct res { + char r_name[6]; + int16_t r_val; + }; + static const struct res restab[] = { + { "for" , FOR }, + { "case" , CASE }, + { "esac" , ESAC }, + { "while", WHILE }, + { "do" , DO }, + { "done" , DONE }, + { "if" , IF }, + { "in" , IN }, + { "then" , THEN }, + { "else" , ELSE }, + { "elif" , ELIF }, + { "until", UNTIL }, + { "fi" , FI }, + { ";;" , BREAK }, + { "||" , LOGOR }, + { "&&" , LOGAND }, + { "{" , '{' }, + { "}" , '}' }, + { "." , DOT }, + { }, + }; + + const struct res *rp; + + DBGPRINTF7(("RLOOKUP: enter, n is %s\n", n)); + + for (rp = restab; rp->r_name[0]; rp++) + if (strcmp(rp->r_name, n) == 0) { + DBGPRINTF7(("RLOOKUP: match, returning %d\n", rp->r_val)); + return rp->r_val; /* Return numeric code for shell multiline */ + } + + DBGPRINTF7(("RLOOKUP: NO match, returning 0\n")); + return 0; /* Not a shell multiline */ +} + +static struct op *newtp(void) +{ + struct op *t; + + t = (struct op *) tree(sizeof(*t)); + memset(t, 0, sizeof(*t)); + + DBGPRINTF3(("NEWTP: allocated %p\n", t)); + + return t; +} + +static struct op *namelist(struct op *t) +{ + DBGPRINTF7(("NAMELIST: enter, t=%p, type %s, iolist=%p\n", t, + T_CMD_NAMES[t->op_type], iolist)); + + if (iolist) { + iolist = addword((char *) NULL, iolist); + t->ioact = copyio(); + } else + t->ioact = NULL; + + if (t->op_type != TCOM) { + if (t->op_type != TPAREN && t->ioact != NULL) { + t = block(TPAREN, t, NOBLOCK, NOWORDS); + t->ioact = t->left->ioact; + t->left->ioact = NULL; + } + return t; + } + + word(NOWORD); + t->op_words = copyw(); + + return t; +} + +static char **copyw(void) +{ + char **wd; + + wd = getwords(wdlist); + wdlist = NULL; + return wd; +} + +static void word(char *cp) +{ + wdlist = addword(cp, wdlist); +} + +static struct ioword **copyio(void) +{ + struct ioword **iop; + + iop = (struct ioword **) getwords(iolist); + iolist = NULL; + return iop; +} + +static struct ioword *io(int u, int f, char *cp) +{ + struct ioword *iop; + + iop = (struct ioword *) tree(sizeof(*iop)); + iop->io_fd = u; + iop->io_flag = f; + iop->io_name = cp; + iolist = addword((char *) iop, iolist); + return iop; +} + +static int yylex(int cf) +{ + int c, c1; + int atstart; + + c = peeksym; + if (c > 0) { + peeksym = 0; + if (c == '\n') + startl = 1; + return c; + } + + nlseen = 0; + atstart = startl; + startl = 0; + yylval.i = 0; + global_env.linep = line; + +/* MALAMO */ + line[LINELIM - 1] = '\0'; + + loop: + while ((c = my_getc(0)) == ' ' || c == '\t') /* Skip whitespace */ + continue; + + switch (c) { + default: + if (any(c, "0123456789")) { + c1 = my_getc(0); + unget(c1); + if (c1 == '<' || c1 == '>') { + iounit = c - '0'; + goto loop; + } + *global_env.linep++ = c; + c = c1; + } + break; + + case '#': /* Comment, skip to next newline or End-of-string */ + while ((c = my_getc(0)) != '\0' && c != '\n') + continue; + unget(c); + goto loop; + + case 0: + DBGPRINTF5(("YYLEX: return 0, c=%d\n", c)); + return c; + + case '$': + DBGPRINTF9(("YYLEX: found $\n")); + *global_env.linep++ = c; + c = my_getc(0); + if (c == '{') { + c = collect(c, '}'); + if (c != '\0') + return c; + goto pack; + } + break; + + case '`': + case '\'': + case '"': + c = collect(c, c); + if (c != '\0') + return c; + goto pack; + + case '|': + case '&': + case ';': + startl = 1; + /* If more chars process them, else return NULL char */ + c1 = dual(c); + if (c1 != '\0') + return c1; + return c; + + case '^': + startl = 1; + return '|'; + case '>': + case '<': + diag(c); + return c; + + case '\n': + nlseen++; + gethere(); + startl = 1; + if (multiline || cf & CONTIN) { + if (interactive && global_env.iop <= iostack) { +#if ENABLE_FEATURE_EDITING + current_prompt = cprompt->value; +#else + prs(cprompt->value); +#endif + } + if (cf & CONTIN) + goto loop; + } + return c; + + case '(': + case ')': + startl = 1; + return c; + } + + unget(c); + + pack: + while ((c = my_getc(0)) != '\0' && !any(c, "`$ '\"\t;&<>()|^\n")) { + if (global_env.linep >= elinep) + err("word too long"); + else + *global_env.linep++ = c; + }; + + unget(c); + + if (any(c, "\"'`$")) + goto loop; + + *global_env.linep++ = '\0'; + + if (atstart) { + c = rlookup(line); + if (c != 0) { + startl = 1; + return c; + } + } + + yylval.cp = strsave(line, areanum); + return WORD; +} + + +static int collect(int c, int c1) +{ + char s[2]; + + DBGPRINTF8(("COLLECT: enter, c=%d, c1=%d\n", c, c1)); + + *global_env.linep++ = c; + while ((c = my_getc(c1)) != c1) { + if (c == 0) { + unget(c); + s[0] = c1; + s[1] = 0; + prs("no closing "); + yyerror(s); + return YYERRCODE; + } + if (interactive && c == '\n' && global_env.iop <= iostack) { +#if ENABLE_FEATURE_EDITING + current_prompt = cprompt->value; +#else + prs(cprompt->value); +#endif + } + *global_env.linep++ = c; + } + + *global_env.linep++ = c; + + DBGPRINTF8(("COLLECT: return 0, line is %s\n", line)); + + return 0; +} + +/* "multiline commands" helper func */ +/* see if next 2 chars form a shell multiline */ +static int dual(int c) +{ + char s[3]; + char *cp = s; + + DBGPRINTF8(("DUAL: enter, c=%d\n", c)); + + *cp++ = c; /* c is the given "peek" char */ + *cp++ = my_getc(0); /* get next char of input */ + *cp = '\0'; /* add EOS marker */ + + c = rlookup(s); /* see if 2 chars form a shell multiline */ + if (c == 0) + unget(*--cp); /* String is not a shell multiline, put peek char back */ + + return c; /* String is multiline, return numeric multiline (restab) code */ +} + +static void diag(int ec) +{ + int c; + + DBGPRINTF8(("DIAG: enter, ec=%d\n", ec)); + + c = my_getc(0); + if (c == '>' || c == '<') { + if (c != ec) + zzerr(); + yylval.i = (ec == '>' ? IOWRITE | IOCAT : IOHERE); + c = my_getc(0); + } else + yylval.i = (ec == '>' ? IOWRITE : IOREAD); + if (c != '&' || yylval.i == IOHERE) + unget(c); + else + yylval.i |= IODUP; +} + +static char *tree(unsigned size) +{ + char *t; + + t = getcell(size); + if (t == NULL) { + DBGPRINTF2(("TREE: getcell(%d) failed!\n", size)); + prs("command line too complicated\n"); + fail(); + /* NOTREACHED */ + } + return t; +} + + +/* VARARGS1 */ +/* ARGSUSED */ + +/* -------- exec.c -------- */ + +static struct op **find1case(struct op *t, const char *w) +{ + struct op *t1; + struct op **tp; + char **wp; + char *cp; + + if (t == NULL) { + DBGPRINTF3(("FIND1CASE: enter, t==NULL, returning.\n")); + return NULL; + } + + DBGPRINTF3(("FIND1CASE: enter, t->op_type=%d (%s)\n", t->op_type, + T_CMD_NAMES[t->op_type])); + + if (t->op_type == TLIST) { + tp = find1case(t->left, w); + if (tp != NULL) { + DBGPRINTF3(("FIND1CASE: found one to the left, returning tp=%p\n", tp)); + return tp; + } + t1 = t->right; /* TPAT */ + } else + t1 = t; + + for (wp = t1->op_words; *wp;) { + cp = evalstr(*wp++, DOSUB); + if (cp && gmatch(w, cp)) { + DBGPRINTF3(("FIND1CASE: returning &t1->left= %p.\n", + &t1->left)); + return &t1->left; + } + } + + DBGPRINTF(("FIND1CASE: returning NULL\n")); + return NULL; +} + +static struct op *findcase(struct op *t, const char *w) +{ + struct op **tp; + + tp = find1case(t, w); + return tp != NULL ? *tp : NULL; +} + +/* + * execute tree + */ + +static int execute(struct op *t, int *pin, int *pout, int no_fork) +{ + struct op *t1; + volatile int i, rv, a; + const char *cp; + char **wp, **wp2; + struct var *vp; + struct op *outtree_save; + struct brkcon bc; + +#if __GNUC__ + /* Avoid longjmp clobbering */ + (void) ℘ +#endif + + if (t == NULL) { + DBGPRINTF4(("EXECUTE: enter, t==null, returning.\n")); + return 0; + } + + DBGPRINTF(("EXECUTE: t=%p, t->op_type=%d (%s), t->op_words is %s\n", t, + t->op_type, T_CMD_NAMES[t->op_type], + ((t->op_words == NULL) ? "NULL" : t->op_words[0]))); + + rv = 0; + a = areanum++; + wp2 = t->op_words; + wp = (wp2 != NULL) + ? eval(wp2, t->op_type == TCOM ? DOALL : DOALL & ~DOKEY) + : NULL; + + switch (t->op_type) { + case TDOT: + DBGPRINTF3(("EXECUTE: TDOT\n")); + + outtree_save = outtree; + + newfile(evalstr(t->op_words[0], DOALL)); + + t->left = dowholefile(TLIST /*, 0*/); + t->right = NULL; + + outtree = outtree_save; + + if (t->left) + rv = execute(t->left, pin, pout, /* no_fork: */ 0); + if (t->right) + rv = execute(t->right, pin, pout, /* no_fork: */ 0); + break; + + case TPAREN: + rv = execute(t->left, pin, pout, /* no_fork: */ 0); + break; + + case TCOM: + rv = forkexec(t, pin, pout, no_fork, wp); + break; + + case TPIPE: + { + int pv[2]; + + rv = openpipe(pv); + if (rv < 0) + break; + pv[0] = remap(pv[0]); + pv[1] = remap(pv[1]); + (void) execute(t->left, pin, pv, /* no_fork: */ 0); + rv = execute(t->right, pv, pout, /* no_fork: */ 0); + } + break; + + case TLIST: + (void) execute(t->left, pin, pout, /* no_fork: */ 0); + rv = execute(t->right, pin, pout, /* no_fork: */ 0); + break; + + case TASYNC: + { + smallint hinteractive = interactive; + + DBGPRINTF7(("EXECUTE: TASYNC clause, calling vfork()...\n")); + + i = vfork(); + if (i == 0) { /* child */ + signal(SIGINT, SIG_IGN); + signal(SIGQUIT, SIG_IGN); + if (interactive) + signal(SIGTERM, SIG_DFL); + interactive = 0; + if (pin == NULL) { + close(0); + xopen(bb_dev_null, O_RDONLY); + } + _exit(execute(t->left, pin, pout, /* no_fork: */ 1)); + } + interactive = hinteractive; + if (i != -1) { + setval(lookup("!"), putn(i)); + closepipe(pin); + if (interactive) { + prs(putn(i)); + prs("\n"); + } + } else + rv = -1; + setstatus(rv); + } + break; + + case TOR: + case TAND: + rv = execute(t->left, pin, pout, /* no_fork: */ 0); + t1 = t->right; + if (t1 != NULL && (rv == 0) == (t->op_type == TAND)) + rv = execute(t1, pin, pout, /* no_fork: */ 0); + break; + + case TFOR: + if (wp == NULL) { + wp = dolv + 1; + i = dolc; + if (i < 0) + i = 0; + } else { + i = -1; + while (*wp++ != NULL) + continue; + } + vp = lookup(t->str); + while (setjmp(bc.brkpt)) + if (isbreak) + goto broken; + /* Restore areanum value. It may be incremented by execute() + * below, and then "continue" may jump back to setjmp above */ + areanum = a + 1; + freearea(areanum + 1); + brkset(&bc); + for (t1 = t->left; i-- && *wp != NULL;) { + setval(vp, *wp++); + rv = execute(t1, pin, pout, /* no_fork: */ 0); + } + brklist = brklist->nextlev; + break; + + case TWHILE: + case TUNTIL: + while (setjmp(bc.brkpt)) + if (isbreak) + goto broken; + /* Restore areanum value. It may be incremented by execute() + * below, and then "continue" may jump back to setjmp above */ + areanum = a + 1; + freearea(areanum + 1); + brkset(&bc); + t1 = t->left; + while ((execute(t1, pin, pout, /* no_fork: */ 0) == 0) == (t->op_type == TWHILE)) + rv = execute(t->right, pin, pout, /* no_fork: */ 0); + brklist = brklist->nextlev; + break; + + case TIF: + case TELIF: + if (t->right != NULL) { + rv = !execute(t->left, pin, pout, /* no_fork: */ 0) ? + execute(t->right->left, pin, pout, /* no_fork: */ 0) : + execute(t->right->right, pin, pout, /* no_fork: */ 0); + } + break; + + case TCASE: + cp = evalstr(t->str, DOSUB | DOTRIM); + if (cp == NULL) + cp = ""; + + DBGPRINTF7(("EXECUTE: TCASE, t->str is %s, cp is %s\n", + ((t->str == NULL) ? "NULL" : t->str), + ((cp == NULL) ? "NULL" : cp))); + + t1 = findcase(t->left, cp); + if (t1 != NULL) { + DBGPRINTF7(("EXECUTE: TCASE, calling execute(t=%p, t1=%p)...\n", t, t1)); + rv = execute(t1, pin, pout, /* no_fork: */ 0); + DBGPRINTF7(("EXECUTE: TCASE, back from execute(t=%p, t1=%p)...\n", t, t1)); + } + break; + + case TBRACE: +/* + iopp = t->ioact; + if (i) + while (*iopp) + if (iosetup(*iopp++, pin!=NULL, pout!=NULL)) { + rv = -1; + break; + } +*/ + if (rv >= 0) { + t1 = t->left; + if (t1) { + rv = execute(t1, pin, pout, /* no_fork: */ 0); + } + } + break; + + }; + + broken: +// Restoring op_words is most likely not needed now: see comment in forkexec() +// (also take a look at exec builtin (doexec) - it touches t->op_words) + t->op_words = wp2; + isbreak = 0; + freehere(areanum); + freearea(areanum); + areanum = a; + if (interactive && intr) { + closeall(); + fail(); + } + + i = trapset; + if (i != 0) { + trapset = 0; + runtrap(i); + } + + DBGPRINTF(("EXECUTE: returning from t=%p, rv=%d\n", t, rv)); + return rv; +} + +static builtin_func_ptr inbuilt(const char *s) +{ + const struct builtincmd *bp; + + for (bp = builtincmds; bp->name; bp++) + if (strcmp(bp->name, s) == 0) + return bp->builtinfunc; + return NULL; +} + +static int forkexec(struct op *t, int *pin, int *pout, int no_fork, char **wp) +{ + pid_t newpid; + int i; + builtin_func_ptr bltin = NULL; + const char *bltin_name = NULL; + const char *cp; + struct ioword **iopp; + int resetsig; + char **owp; + int forked; + + int *hpin = pin; + int *hpout = pout; + char *hwp; + smallint hinteractive; + smallint hintr; + smallint hexecflg; + struct brkcon *hbrklist; + +#if __GNUC__ + /* Avoid longjmp clobbering */ + (void) &pin; + (void) &pout; + (void) ℘ + (void) &bltin; + (void) &cp; + (void) &resetsig; + (void) &owp; +#endif + + DBGPRINTF(("FORKEXEC: t=%p, pin %p, pout %p, no_fork %d\n", t, pin, + pout, no_fork)); + DBGPRINTF7(("FORKEXEC: t->op_words is %s\n", + ((t->op_words == NULL) ? "NULL" : t->op_words[0]))); + owp = wp; + resetsig = 0; + if (t->op_type == TCOM) { + while (*wp++ != NULL) + continue; + cp = *wp; + + /* strip all initial assignments */ + /* FIXME: not correct wrt PATH=yyy command etc */ + if (FLAG['x']) { + DBGPRINTF9(("FORKEXEC: echo'ing, cp=%p, wp=%p, owp=%p\n", + cp, wp, owp)); + echo(cp ? wp : owp); + } + + if (cp == NULL) { + if (t->ioact == NULL) { + while ((cp = *owp++) != NULL && assign(cp, COPYV)) + continue; + DBGPRINTF(("FORKEXEC: returning setstatus(0)\n")); + return setstatus(0); + } + } else { /* cp != NULL */ + bltin_name = cp; + bltin = inbuilt(cp); + } + } + + forked = 0; + // We were pointing t->op_words to temporary (expanded) arg list: + // t->op_words = wp; + // and restored it later (in execute()), but "break" + // longjmps away (at "Run builtin" below), leaving t->op_words clobbered! + // See http://bugs.busybox.net/view.php?id=846. + // Now we do not touch t->op_words, but separately pass wp as param list + // to builtins + DBGPRINTF(("FORKEXEC: bltin %p, no_fork %d, owp %p\n", bltin, + no_fork, owp)); + /* Don't fork if it is a lone builtin (not in pipe) + * OR we are told to _not_ fork */ + if ((!bltin || pin || pout) /* not lone bltin AND */ + && !no_fork /* not told to avoid fork */ + ) { + /* Save values in case child alters them after vfork */ + hpin = pin; + hpout = pout; + hwp = *wp; + hinteractive = interactive; + hintr = intr; + hbrklist = brklist; + hexecflg = execflg; + + DBGPRINTF3(("FORKEXEC: calling vfork()...\n")); + newpid = vfork(); + if (newpid == -1) { + DBGPRINTF(("FORKEXEC: ERROR, can't vfork()!\n")); + return -1; + } + + if (newpid > 0) { /* Parent */ + /* Restore values */ + pin = hpin; + pout = hpout; + *wp = hwp; + interactive = hinteractive; + intr = hintr; + brklist = hbrklist; + execflg = hexecflg; + + closepipe(pin); + return (pout == NULL ? setstatus(waitfor(newpid, 0)) : 0); + } + + /* Child */ + DBGPRINTF(("FORKEXEC: child process, bltin=%p (%s)\n", bltin, bltin_name)); + if (interactive) { + signal(SIGINT, SIG_IGN); + signal(SIGQUIT, SIG_IGN); + resetsig = 1; + } + interactive = 0; + intr = 0; + forked = 1; + brklist = 0; + execflg = 0; + } + + if (owp) + while ((cp = *owp++) != NULL && assign(cp, COPYV)) + if (!bltin) + export(lookup(cp)); + + if (pin) { /* NB: close _first_, then move fds! */ + close(pin[1]); + xmove_fd(pin[0], 0); + } + if (pout) { + close(pout[0]); + xmove_fd(pout[1], 1); + } + + iopp = t->ioact; + if (iopp) { + if (bltin && bltin != doexec) { + prs(bltin_name); + err(": can't redirect shell command"); + if (forked) + _exit(-1); + return -1; + } + while (*iopp) { + if (iosetup(*iopp++, pin != NULL, pout != NULL)) { + /* system-detected error */ + if (forked) + _exit(-1); + return -1; + } + } + } + + if (bltin) { + if (forked || pin || pout) { + /* Builtin in pipe: disallowed */ + /* TODO: allow "exec"? */ + prs(bltin_name); + err(": can't run builtin as part of pipe"); + if (forked) + _exit(-1); + return -1; + } + /* Run builtin */ + i = setstatus(bltin(t, wp)); + if (forked) + _exit(i); + DBGPRINTF(("FORKEXEC: returning i=%d\n", i)); + return i; + } + + /* should use FIOCEXCL */ + for (i = FDBASE; i < NOFILE; i++) + close(i); + if (resetsig) { + signal(SIGINT, SIG_DFL); + signal(SIGQUIT, SIG_DFL); + } + + if (t->op_type == TPAREN) + _exit(execute(t->left, NOPIPE, NOPIPE, /* no_fork: */ 1)); + if (wp[0] == NULL) + _exit(EXIT_SUCCESS); + + cp = rexecve(wp[0], wp, makenv(0, NULL)); + prs(wp[0]); + prs(": "); + err(cp); + if (!execflg) + trap[0] = NULL; + + DBGPRINTF(("FORKEXEC: calling leave(), pid=%d\n", getpid())); + + leave(); + /* NOTREACHED */ + return 0; +} + +/* + * 0< 1> are ignored as required + * within pipelines. + */ +static int iosetup(struct ioword *iop, int pipein, int pipeout) +{ + int u = -1; + char *cp = NULL; + const char *msg; + + DBGPRINTF(("IOSETUP: iop %p, pipein %i, pipeout %i\n", iop, + pipein, pipeout)); + + if (iop->io_fd == IODEFAULT) /* take default */ + iop->io_fd = iop->io_flag & (IOREAD | IOHERE) ? 0 : 1; + + if (pipein && iop->io_fd == 0) + return 0; + + if (pipeout && iop->io_fd == 1) + return 0; + + msg = iop->io_flag & (IOREAD | IOHERE) ? "open" : "create"; + if ((iop->io_flag & IOHERE) == 0) { + cp = iop->io_name; /* huh?? */ + cp = evalstr(cp, DOSUB | DOTRIM); + if (cp == NULL) + return 1; + } + + if (iop->io_flag & IODUP) { + if (cp[1] || (!isdigit(*cp) && *cp != '-')) { + prs(cp); + err(": illegal >& argument"); + return 1; + } + if (*cp == '-') + iop->io_flag = IOCLOSE; + iop->io_flag &= ~(IOREAD | IOWRITE); + } + + switch (iop->io_flag) { + case IOREAD: + u = open(cp, O_RDONLY); + break; + + case IOHERE: + case IOHERE | IOXHERE: + u = herein(iop->io_name, iop->io_flag & IOXHERE); + cp = (char*)"here file"; + break; + + case IOWRITE | IOCAT: + u = open(cp, O_WRONLY); + if (u >= 0) { + lseek(u, (long) 0, SEEK_END); + break; + } + /* fall through to creation if >>file doesn't exist */ + + case IOWRITE: + u = creat(cp, 0666); + break; + + case IODUP: + u = dup2(*cp - '0', iop->io_fd); + break; + + case IOCLOSE: + close(iop->io_fd); + return 0; + } + + if (u < 0) { + prs(cp); + prs(": can't "); + warn(msg); + return 1; + } + xmove_fd(u, iop->io_fd); + return 0; +} + +/* + * Enter a new loop level (marked for break/continue). + */ +static void brkset(struct brkcon *bc) +{ + bc->nextlev = brklist; + brklist = bc; +} + +/* + * Wait for the last process created. + * Print a message for each process found + * that was killed by a signal. + * Ignore interrupt signals while waiting + * unless `canintr' is true. + */ +static int waitfor(int lastpid, int canintr) +{ + int pid, rv; + int s; + smallint oheedint = heedint; + + heedint = 0; + rv = 0; + do { + pid = wait(&s); + if (pid == -1) { + if (errno != EINTR || canintr) + break; + } else { + rv = WAITSIG(s); + if (rv != 0) { + if (rv < ARRAY_SIZE(signame)) { + if (signame[rv] != NULL) { + if (pid != lastpid) { + prn(pid); + prs(": "); + } + prs(signame[rv]); + } + } else { + if (pid != lastpid) { + prn(pid); + prs(": "); + } + prs("Signal "); + prn(rv); + prs(" "); + } + if (WAITCORE(s)) + prs(" - core dumped"); + if (rv >= ARRAY_SIZE(signame) || signame[rv]) + prs("\n"); + rv |= 0x80; + } else + rv = WAITVAL(s); + } + } while (pid != lastpid); + heedint = oheedint; + if (intr) { + if (interactive) { + if (canintr) + intr = 0; + } else { + if (exstat == 0) + exstat = rv; + onintr(0); + } + } + return rv; +} + +static int setstatus(int s) +{ + exstat = s; + setval(lookup("?"), putn(s)); + return s; +} + +/* + * PATH-searching interface to execve. + * If getenv("PATH") were kept up-to-date, + * execvp might be used. + */ +static const char *rexecve(char *c, char **v, char **envp) +{ + const char *sp; + char *tp; + int asis = 0; + char *name = c; + + if (ENABLE_FEATURE_SH_STANDALONE) { + if (find_applet_by_name(name) >= 0) { + /* We have to exec here since we vforked. Running + * run_applet_and_exit() won't work and bad things + * will happen. */ + execve(bb_busybox_exec_path, v, envp); + } + } + + DBGPRINTF(("REXECVE: c=%p, v=%p, envp=%p\n", c, v, envp)); + + sp = any('/', c) ? "" : path->value; + asis = (*sp == '\0'); + while (asis || *sp != '\0') { + asis = 0; + tp = global_env.linep; + for (; *sp != '\0'; tp++) { + *tp = *sp++; + if (*tp == ':') { + asis = (*sp == '\0'); + break; + } + } + if (tp != global_env.linep) + *tp++ = '/'; + strcpy(tp, c); + + DBGPRINTF3(("REXECVE: global_env.linep is %s\n", global_env.linep)); + + execve(global_env.linep, v, envp); + + switch (errno) { + case ENOEXEC: + /* File is executable but file format isnt recognized */ + /* Run it as a shell script */ + /* (execve above didnt do it itself, unlike execvp) */ + *v = global_env.linep; + v--; + tp = *v; + *v = (char*)DEFAULT_SHELL; + execve(DEFAULT_SHELL, v, envp); + *v = tp; + return "no shell"; + + case ENOMEM: + return (char *) bb_msg_memory_exhausted; + + case E2BIG: + return "argument list too long"; + } + } + if (errno == ENOENT) { + exstat = 127; /* standards require this */ + return "not found"; + } + exstat = 126; /* mimic bash */ + return "can't execute"; +} + +/* + * Run the command produced by generator `f' + * applied to stream `arg'. + */ +static int run(struct ioarg *argp, int (*f) (struct ioarg *)) +{ + struct op *otree; + struct wdblock *swdlist; + struct wdblock *siolist; + jmp_buf ev, rt; + xint *ofail; + int rv; + +#if __GNUC__ + /* Avoid longjmp clobbering */ + (void) &rv; +#endif + + DBGPRINTF(("RUN: enter, areanum %d, outtree %p, failpt %p\n", + areanum, outtree, failpt)); + + areanum++; + swdlist = wdlist; + siolist = iolist; + otree = outtree; + ofail = failpt; + rv = -1; + + errpt = ev; + if (newenv(setjmp(errpt)) == 0) { + wdlist = NULL; + iolist = NULL; + pushio(argp, f); + global_env.iobase = global_env.iop; + yynerrs = 0; + failpt = rt; + if (setjmp(failpt) == 0 && yyparse() == 0) + rv = execute(outtree, NOPIPE, NOPIPE, /* no_fork: */ 0); + quitenv(); + } else { + DBGPRINTF(("RUN: error from newenv()!\n")); + } + + wdlist = swdlist; + iolist = siolist; + failpt = ofail; + outtree = otree; + freearea(areanum--); + + return rv; +} + +/* -------- do.c -------- */ + +/* + * built-in commands: doX + */ + +static int dohelp(struct op *t UNUSED_PARAM, char **args UNUSED_PARAM) +{ + int col; + const struct builtincmd *x; + + puts("\nBuilt-in commands:\n" + "-------------------"); + + col = 0; + x = builtincmds; + while (x->name) { + col += printf("%c%s", ((col == 0) ? '\t' : ' '), x->name); + if (col > 60) { + bb_putchar('\n'); + col = 0; + } + x++; + } +#if ENABLE_FEATURE_SH_STANDALONE + { + const char *applet = applet_names; + + while (*applet) { + col += printf("%c%s", ((col == 0) ? '\t' : ' '), applet); + if (col > 60) { + bb_putchar('\n'); + col = 0; + } + applet += strlen(applet) + 1; + } + } +#endif + puts("\n"); + return EXIT_SUCCESS; +} + +static int dolabel(struct op *t UNUSED_PARAM, char **args UNUSED_PARAM) +{ + return 0; +} + +static int dochdir(struct op *t UNUSED_PARAM, char **args) +{ + const char *cp, *er; + + cp = args[1]; + if (cp == NULL) { + cp = homedir->value; + if (cp != NULL) + goto do_cd; + er = ": no home directory"; + } else { + do_cd: + if (chdir(cp) >= 0) + return 0; + er = ": bad directory"; + } + prs(cp != NULL ? cp : "cd"); + err(er); + return 1; +} + +static int doshift(struct op *t UNUSED_PARAM, char **args) +{ + int n; + + n = args[1] ? getn(args[1]) : 1; + if (dolc < n) { + err("nothing to shift"); + return 1; + } + dolv[n] = dolv[0]; + dolv += n; + dolc -= n; + setval(lookup("#"), putn(dolc)); + return 0; +} + +/* + * execute login and newgrp directly + */ +static int dologin(struct op *t UNUSED_PARAM, char **args) +{ + const char *cp; + + if (interactive) { + signal(SIGINT, SIG_DFL); + signal(SIGQUIT, SIG_DFL); + } + cp = rexecve(args[0], args, makenv(0, NULL)); + prs(args[0]); + prs(": "); + err(cp); + return 1; +} + +static int doumask(struct op *t UNUSED_PARAM, char **args) +{ + int i; + char *cp; + + cp = args[1]; + if (cp == NULL) { + i = umask(0); + umask(i); + printf("%04o\n", i); + } else { + i = bb_strtou(cp, NULL, 8); + if (errno) { + err("umask: bad octal number"); + return 1; + } + umask(i); + } + return 0; +} + +static int doexec(struct op *t, char **args) +{ + jmp_buf ex; + xint *ofail; + char **sv_words; + + t->ioact = NULL; + if (!args[1]) + return 1; + + execflg = 1; + ofail = failpt; + failpt = ex; + + sv_words = t->op_words; + t->op_words = args + 1; +// TODO: test what will happen with "exec break" - +// will it leave t->op_words pointing to garbage? +// (see http://bugs.busybox.net/view.php?id=846) + if (setjmp(failpt) == 0) + execute(t, NOPIPE, NOPIPE, /* no_fork: */ 1); + t->op_words = sv_words; + + failpt = ofail; + execflg = 0; + + return 1; +} + +static int dodot(struct op *t UNUSED_PARAM, char **args) +{ + int i; + const char *sp; + char *tp; + char *cp; + int maltmp; + + DBGPRINTF(("DODOT: enter, t=%p, tleft %p, tright %p, global_env.linep is %s\n", + t, t->left, t->right, ((global_env.linep == NULL) ? "NULL" : global_env.linep))); + + cp = args[1]; + if (cp == NULL) { + DBGPRINTF(("DODOT: bad args, ret 0\n")); + return 0; + } + DBGPRINTF(("DODOT: cp is %s\n", cp)); + + sp = any('/', cp) ? ":" : path->value; + + DBGPRINTF(("DODOT: sp is %s, global_env.linep is %s\n", + ((sp == NULL) ? "NULL" : sp), + ((global_env.linep == NULL) ? "NULL" : global_env.linep))); + + while (*sp) { + tp = global_env.linep; + while (*sp && (*tp = *sp++) != ':') + tp++; + if (tp != global_env.linep) + *tp++ = '/'; + strcpy(tp, cp); + + /* Original code */ + i = open(global_env.linep, O_RDONLY); + if (i >= 0) { + exstat = 0; + maltmp = remap(i); + DBGPRINTF(("DODOT: remap=%d, exstat=%d, global_env.iofd %d, i %d, global_env.linep is %s\n", + maltmp, exstat, global_env.iofd, i, global_env.linep)); + + next(maltmp); /* Basically a PUSHIO */ + + DBGPRINTF(("DODOT: returning exstat=%d\n", exstat)); + + return exstat; + } + } /* while */ + + prs(cp); + err(": not found"); + + return -1; +} + +static int dowait(struct op *t UNUSED_PARAM, char **args) +{ + int i; + char *cp; + + cp = args[1]; + if (cp != NULL) { + i = getn(cp); + if (i == 0) + return 0; + } else + i = -1; + setstatus(waitfor(i, 1)); + return 0; +} + +static int doread(struct op *t UNUSED_PARAM, char **args) +{ + char *cp, **wp; + int nb = 0; + int nl = 0; + + if (args[1] == NULL) { + err("Usage: read name ..."); + return 1; + } + for (wp = args + 1; *wp; wp++) { + for (cp = global_env.linep; !nl && cp < elinep - 1; cp++) { + nb = nonblock_safe_read(STDIN_FILENO, cp, sizeof(*cp)); + if (nb != sizeof(*cp)) + break; + nl = (*cp == '\n'); + if (nl || (wp[1] && any(*cp, ifs->value))) + break; + } + *cp = '\0'; + if (nb <= 0) + break; + setval(lookup(*wp), global_env.linep); + } + return nb <= 0; +} + +static int doeval(struct op *t UNUSED_PARAM, char **args) +{ + return RUN(awordlist, args + 1, wdchar); +} + +static int dotrap(struct op *t UNUSED_PARAM, char **args) +{ + int n, i; + int resetsig; + + if (args[1] == NULL) { + for (i = 0; i <= _NSIG; i++) + if (trap[i]) { + prn(i); + prs(": "); + prs(trap[i]); + prs("\n"); + } + return 0; + } + resetsig = isdigit(args[1][0]); + for (i = resetsig ? 1 : 2; args[i] != NULL; ++i) { + n = getsig(args[i]); + freecell(trap[n]); + trap[n] = 0; + if (!resetsig) { + if (args[1][0] != '\0') { + trap[n] = strsave(args[1], 0); + setsig(n, sig); + } else + setsig(n, SIG_IGN); + } else { + if (interactive) { + if (n == SIGINT) + setsig(n, onintr); + else + setsig(n, n == SIGQUIT ? SIG_IGN : SIG_DFL); + } else + setsig(n, SIG_DFL); + } + } + return 0; +} + +static int getsig(char *s) +{ + int n; + + n = getn(s); + if (n < 0 || n > _NSIG) { + err("trap: bad signal number"); + n = 0; + } + return n; +} + +static void setsig(int n, sighandler_t f) +{ + if (n == 0) + return; + if (signal(n, SIG_IGN) != SIG_IGN || ourtrap[n]) { + ourtrap[n] = 1; + signal(n, f); + } +} + +static int getn(char *as) +{ + char *s; + int n, m; + + s = as; + m = 1; + if (*s == '-') { + m = -1; + s++; + } + for (n = 0; isdigit(*s); s++) + n = (n * 10) + (*s - '0'); + if (*s) { + prs(as); + err(": bad number"); + } + return n * m; +} + +static int dobreak(struct op *t UNUSED_PARAM, char **args) +{ + return brkcontin(args[1], 1); +} + +static int docontinue(struct op *t UNUSED_PARAM, char **args) +{ + return brkcontin(args[1], 0); +} + +static int brkcontin(char *cp, int val) +{ + struct brkcon *bc; + int nl; + + nl = cp == NULL ? 1 : getn(cp); + if (nl <= 0) + nl = 999; + do { + bc = brklist; + if (bc == NULL) + break; + brklist = bc->nextlev; + } while (--nl); + if (nl) { + err("bad break/continue level"); + return 1; + } + isbreak = (val != 0); + longjmp(bc->brkpt, 1); + /* NOTREACHED */ +} + +static int doexit(struct op *t UNUSED_PARAM, char **args) +{ + char *cp; + + execflg = 0; + cp = args[1]; + if (cp != NULL) + setstatus(getn(cp)); + + DBGPRINTF(("DOEXIT: calling leave(), t=%p\n", t)); + + leave(); + /* NOTREACHED */ + return 0; +} + +static int doexport(struct op *t UNUSED_PARAM, char **args) +{ + rdexp(args + 1, export, EXPORT); + return 0; +} + +static int doreadonly(struct op *t UNUSED_PARAM, char **args) +{ + rdexp(args + 1, ronly, RONLY); + return 0; +} + +static void rdexp(char **wp, void (*f) (struct var *), int key) +{ + DBGPRINTF6(("RDEXP: enter, wp=%p, func=%p, key=%d\n", wp, f, key)); + DBGPRINTF6(("RDEXP: *wp=%s\n", *wp)); + + if (*wp != NULL) { + for (; *wp != NULL; wp++) { + if (isassign(*wp)) { + char *cp; + + assign(*wp, COPYV); + for (cp = *wp; *cp != '='; cp++) + continue; + *cp = '\0'; + } + if (checkname(*wp)) + (*f) (lookup(*wp)); + else + badid(*wp); + } + } else + putvlist(key, 1); +} + +static void badid(char *s) +{ + prs(s); + err(": bad identifier"); +} + +static int doset(struct op *t UNUSED_PARAM, char **args) +{ + struct var *vp; + char *cp; + int n; + + cp = args[1]; + if (cp == NULL) { + for (vp = vlist; vp; vp = vp->next) + varput(vp->name, 1); + return 0; + } + if (*cp == '-') { + args++; + if (*++cp == 0) + FLAG['x'] = FLAG['v'] = 0; + else { + for (; *cp; cp++) { + switch (*cp) { + case 'e': + if (!interactive) + FLAG['e']++; + break; + + default: + if (*cp >= 'a' && *cp <= 'z') + FLAG[(int) *cp]++; + break; + } + } + } + setdash(); + } + if (args[1]) { + args[0] = dolv[0]; + for (n = 1; args[n]; n++) + setarea((char *) args[n], 0); + dolc = n - 1; + dolv = args; + setval(lookup("#"), putn(dolc)); + setarea((char *) (dolv - 1), 0); + } + return 0; +} + +static void varput(char *s, int out) +{ + if (isalnum(*s) || *s == '_') { + write(out, s, strlen(s)); + write(out, "\n", 1); + } +} + + +/* + * Copyright (c) 1999 Herbert Xu + * This file contains code for the times builtin. + */ +static void times_fmt(char *buf, clock_t val, unsigned clk_tck) +{ + unsigned min, sec; + if (sizeof(val) > sizeof(int)) + sec = ((unsigned long)val) / clk_tck; + else + sec = ((unsigned)val) / clk_tck; + min = sec / 60; +#if ENABLE_DESKTOP + sprintf(buf, "%um%u.%03us", min, (sec - min * 60), + /* msec: */ ((unsigned)(val - (clock_t)sec * clk_tck)) * 1000 / clk_tck + ); +#else + sprintf(buf, "%um%us", min, (sec - min * 60)); +#endif +} + +static int dotimes(struct op *t UNUSED_PARAM, char **args UNUSED_PARAM) +{ + struct tms buf; + unsigned clk_tck = sysconf(_SC_CLK_TCK); + /* How much do we need for "NmN.NNNs" ? */ + enum { TIMEBUF_SIZE = sizeof(int)*3 + sizeof(int)*3 + 6 }; + char u[TIMEBUF_SIZE], s[TIMEBUF_SIZE]; + char cu[TIMEBUF_SIZE], cs[TIMEBUF_SIZE]; + + times(&buf); + + times_fmt(u, buf.tms_utime, clk_tck); + times_fmt(s, buf.tms_stime, clk_tck); + times_fmt(cu, buf.tms_cutime, clk_tck); + times_fmt(cs, buf.tms_cstime, clk_tck); + + printf("%s %s\n%s %s\n", u, s, cu, cs); + return 0; +} + + +/* -------- eval.c -------- */ + +/* + * ${} + * `command` + * blank interpretation + * quoting + * glob + */ + +static char **eval(char **ap, int f) +{ + struct wdblock *wb; + char **wp; + char **wf; + jmp_buf ev; + +#if __GNUC__ + /* Avoid longjmp clobbering */ + (void) ℘ + (void) ≈ +#endif + + DBGPRINTF4(("EVAL: enter, f=%d\n", f)); + + wp = NULL; + wb = NULL; + wf = NULL; + errpt = ev; + if (newenv(setjmp(errpt)) == 0) { + while (*ap && isassign(*ap)) + expand(*ap++, &wb, f & ~DOGLOB); + if (FLAG['k']) { + for (wf = ap; *wf; wf++) { + if (isassign(*wf)) + expand(*wf, &wb, f & ~DOGLOB); + } + } + for (wb = addword((char *) NULL, wb); *ap; ap++) { + if (!FLAG['k'] || !isassign(*ap)) + expand(*ap, &wb, f & ~DOKEY); + } + wb = addword((char *) 0, wb); + wp = getwords(wb); + quitenv(); + } else + gflg = 1; + + return gflg ? (char **) NULL : wp; +} + + +/* + * Make the exported environment from the exported + * names in the dictionary. Keyword assignments + * will already have been done. + */ +static char **makenv(int all, struct wdblock *wb) +{ + struct var *vp; + + DBGPRINTF5(("MAKENV: enter, all=%d\n", all)); + + for (vp = vlist; vp; vp = vp->next) + if (all || vp->status & EXPORT) + wb = addword(vp->name, wb); + wb = addword((char *) 0, wb); + return getwords(wb); +} + +static int expand(const char *cp, struct wdblock **wbp, int f) +{ + jmp_buf ev; + char *xp; + +#if __GNUC__ + /* Avoid longjmp clobbering */ + (void) &cp; +#endif + + DBGPRINTF3(("EXPAND: enter, f=%d\n", f)); + + gflg = 0; + + if (cp == NULL) + return 0; + + if (!anys("$`'\"", cp) && !anys(ifs->value, cp) + && ((f & DOGLOB) == 0 || !anys("[*?", cp)) + ) { + xp = strsave(cp, areanum); + if (f & DOTRIM) + unquote(xp); + *wbp = addword(xp, *wbp); + return 1; + } + errpt = ev; + if (newenv(setjmp(errpt)) == 0) { + PUSHIO(aword, cp, strchar); + global_env.iobase = global_env.iop; + while ((xp = blank(f)) && gflg == 0) { + global_env.linep = xp; + xp = strsave(xp, areanum); + if ((f & DOGLOB) == 0) { + if (f & DOTRIM) + unquote(xp); + *wbp = addword(xp, *wbp); + } else + *wbp = glob(xp, *wbp); + } + quitenv(); + } else + gflg = 1; + return gflg == 0; +} + +static char *evalstr(char *cp, int f) +{ + struct wdblock *wb; + + DBGPRINTF6(("EVALSTR: enter, cp=%p, f=%d\n", cp, f)); + + wb = NULL; + if (expand(cp, &wb, f)) { + if (wb == NULL || wb->w_nword == 0 + || (cp = wb->w_words[0]) == NULL + ) { +// TODO: I suspect that +// char *evalstr(char *cp, int f) is actually +// const char *evalstr(const char *cp, int f)! + cp = (char*)""; + } + DELETE(wb); + } else + cp = NULL; + return cp; +} + + +/* + * Blank interpretation and quoting + */ +static char *blank(int f) +{ + int c, c1; + char *sp; + int scanequals, foundequals; + + DBGPRINTF3(("BLANK: enter, f=%d\n", f)); + + sp = global_env.linep; + scanequals = f & DOKEY; + foundequals = 0; + + loop: + c = subgetc('"', foundequals); + switch (c) { + case 0: + if (sp == global_env.linep) + return 0; + *global_env.linep++ = 0; + return sp; + + default: + if (f & DOBLANK && any(c, ifs->value)) + goto loop; + break; + + case '"': + case '\'': + scanequals = 0; + if (INSUB()) + break; + for (c1 = c; (c = subgetc(c1, 1)) != c1;) { + if (c == 0) + break; + if (c == '\'' || !any(c, "$`\"")) + c |= QUOTE; + *global_env.linep++ = c; + } + c = 0; + } + unget(c); + if (!isalpha(c) && c != '_') + scanequals = 0; + for (;;) { + c = subgetc('"', foundequals); + if (c == 0 || + f & (DOBLANK && any(c, ifs->value)) || + (!INSUB() && any(c, "\"'"))) { + scanequals = 0; + unget(c); + if (any(c, "\"'")) + goto loop; + break; + } + if (scanequals) { + if (c == '=') { + foundequals = 1; + scanequals = 0; + } else if (!isalnum(c) && c != '_') + scanequals = 0; + } + *global_env.linep++ = c; + } + *global_env.linep++ = 0; + return sp; +} + +/* + * Get characters, substituting for ` and $ + */ +static int subgetc(char ec, int quoted) +{ + char c; + + DBGPRINTF3(("SUBGETC: enter, quoted=%d\n", quoted)); + + again: + c = my_getc(ec); + if (!INSUB() && ec != '\'') { + if (c == '`') { + if (grave(quoted) == 0) + return 0; + global_env.iop->task = XGRAVE; + goto again; + } + if (c == '$') { + c = dollar(quoted); + if (c == 0) { + global_env.iop->task = XDOLL; + goto again; + } + } + } + return c; +} + +/* + * Prepare to generate the string returned by ${} substitution. + */ +static int dollar(int quoted) +{ + int otask; + struct io *oiop; + char *dolp; + char *s, c, *cp = NULL; + struct var *vp; + + DBGPRINTF3(("DOLLAR: enter, quoted=%d\n", quoted)); + + c = readc(); + s = global_env.linep; + if (c != '{') { + *global_env.linep++ = c; + if (isalpha(c) || c == '_') { + while ((c = readc()) != 0 && (isalnum(c) || c == '_')) + if (global_env.linep < elinep) + *global_env.linep++ = c; + unget(c); + } + c = 0; + } else { + oiop = global_env.iop; + otask = global_env.iop->task; + + global_env.iop->task = XOTHER; + while ((c = subgetc('"', 0)) != 0 && c != '}' && c != '\n') + if (global_env.linep < elinep) + *global_env.linep++ = c; + if (oiop == global_env.iop) + global_env.iop->task = otask; + if (c != '}') { + err("unclosed ${"); + gflg = 1; + return c; + } + } + if (global_env.linep >= elinep) { + err("string in ${} too long"); + gflg = 1; + global_env.linep -= 10; + } + *global_env.linep = 0; + if (*s) + for (cp = s + 1; *cp; cp++) + if (any(*cp, "=-+?")) { + c = *cp; + *cp++ = 0; + break; + } + if (s[1] == 0 && (*s == '*' || *s == '@')) { + if (dolc > 1) { + /* currently this does not distinguish $* and $@ */ + /* should check dollar */ + global_env.linep = s; + PUSHIO(awordlist, dolv + 1, dolchar); + return 0; + } else { /* trap the nasty ${=} */ + s[0] = '1'; + s[1] = '\0'; + } + } + vp = lookup(s); + dolp = vp->value; + if (dolp == null) { + switch (c) { + case '=': + if (isdigit(*s)) { + err("can't use ${...=...} with $n"); + gflg = 1; + break; + } + setval(vp, cp); + dolp = vp->value; + break; + + case '-': + dolp = strsave(cp, areanum); + break; + + case '?': + if (*cp == 0) { + prs("missing value for "); + err(s); + } else + err(cp); + gflg = 1; + break; + } + } else if (c == '+') + dolp = strsave(cp, areanum); + if (FLAG['u'] && dolp == null) { + prs("unset variable: "); + err(s); + gflg = 1; + } + global_env.linep = s; + PUSHIO(aword, dolp, quoted ? qstrchar : strchar); + return 0; +} + +/* + * Run the command in `...` and read its output. + */ + +static int grave(int quoted) +{ + /* moved to G: static char child_cmd[LINELIM]; */ + + const char *cp; + int i; + int j; + int pf[2]; + const char *src; + char *dest; + int count; + int ignore; + int ignore_once; + char *argument_list[4]; + struct wdblock *wb = NULL; + +#if __GNUC__ + /* Avoid longjmp clobbering */ + (void) &cp; +#endif + + for (cp = global_env.iop->argp->aword; *cp != '`'; cp++) { + if (*cp == 0) { + err("no closing `"); + return 0; + } + } + + /* string copy with dollar expansion */ + src = global_env.iop->argp->aword; + dest = child_cmd; + count = 0; + ignore = 0; + ignore_once = 0; + while ((*src != '`') && (count < LINELIM)) { + if (*src == '\'') + ignore = !ignore; + if (*src == '\\') + ignore_once = 1; + if (*src == '$' && !ignore && !ignore_once) { + struct var *vp; + /* moved to G to reduce stack usage + char var_name[LINELIM]; + char alt_value[LINELIM]; + */ +#define var_name (G.grave__var_name) +#define alt_value (G.grave__alt_value) + int var_index = 0; + int alt_index = 0; + char operator = 0; + int braces = 0; + char *value; + + src++; + if (*src == '{') { + braces = 1; + src++; + } + + var_name[var_index++] = *src++; + while (isalnum(*src) || *src=='_') + var_name[var_index++] = *src++; + var_name[var_index] = 0; + + if (braces) { + switch (*src) { + case '}': + break; + case '-': + case '=': + case '+': + case '?': + operator = * src; + break; + default: + err("unclosed ${\n"); + return 0; + } + if (operator) { + src++; + while (*src && (*src != '}')) { + alt_value[alt_index++] = *src++; + } + alt_value[alt_index] = 0; + if (*src != '}') { + err("unclosed ${\n"); + return 0; + } + } + src++; + } + + if (isalpha(*var_name)) { + /* let subshell handle it instead */ + + char *namep = var_name; + + *dest++ = '$'; + if (braces) + *dest++ = '{'; + while (*namep) + *dest++ = *namep++; + if (operator) { + char *altp = alt_value; + *dest++ = operator; + while (*altp) + *dest++ = *altp++; + } + if (braces) + *dest++ = '}'; + + wb = addword(lookup(var_name)->name, wb); + } else { + /* expand */ + + vp = lookup(var_name); + if (vp->value != null) + value = (operator == '+') ? + alt_value : vp->value; + else if (operator == '?') { + err(alt_value); + return 0; + } else if (alt_index && (operator != '+')) { + value = alt_value; + if (operator == '=') + setval(vp, value); + } else + continue; + + while (*value && (count < LINELIM)) { + *dest++ = *value++; + count++; + } + } +#undef var_name +#undef alt_value + } else { + *dest++ = *src++; + count++; + ignore_once = 0; + } + } + *dest = '\0'; + + if (openpipe(pf) < 0) + return 0; + + while ((i = vfork()) == -1 && errno == EAGAIN) + continue; + + DBGPRINTF3(("GRAVE: i is %p\n", io)); + + if (i < 0) { + closepipe(pf); + err((char *) bb_msg_memory_exhausted); + return 0; + } + if (i != 0) { + waitpid(i, NULL, 0); // safe_waitpid? + global_env.iop->argp->aword = ++cp; + close(pf[1]); + PUSHIO(afile, remap(pf[0]), + (int (*)(struct ioarg *)) ((quoted) ? qgravechar : gravechar)); + return 1; + } + /* allow trapped signals */ + /* XXX - Maybe this signal stuff should go as well? */ + for (j = 0; j <= _NSIG; j++) + if (ourtrap[j] && signal(j, SIG_IGN) != SIG_IGN) + signal(j, SIG_DFL); + + /* Testcase where below checks are needed: + * close stdout & run this script: + * files=`ls` + * echo "$files" >zz + */ + xmove_fd(pf[1], 1); + if (pf[0] != 1) + close(pf[0]); + + argument_list[0] = (char *) DEFAULT_SHELL; + argument_list[1] = (char *) "-c"; + argument_list[2] = child_cmd; + argument_list[3] = NULL; + + cp = rexecve(argument_list[0], argument_list, makenv(1, wb)); + prs(argument_list[0]); + prs(": "); + err(cp); + _exit(EXIT_FAILURE); +} + + +static char *unquote(char *as) +{ + char *s; + + s = as; + if (s != NULL) + while (*s) + *s++ &= ~QUOTE; + return as; +} + +/* -------- glob.c -------- */ + +/* + * glob + */ + +#define scopy(x) strsave((x), areanum) +#define BLKSIZ 512 +#define NDENT ((BLKSIZ+sizeof(struct dirent)-1)/sizeof(struct dirent)) + +static struct wdblock *cl, *nl; +static const char spcl[] ALIGN1= "[?*"; + +static struct wdblock *glob(char *cp, struct wdblock *wb) +{ + int i; + char *pp; + + if (cp == 0) + return wb; + i = 0; + for (pp = cp; *pp; pp++) + if (any(*pp, spcl)) + i++; + else if (!any(*pp & ~QUOTE, spcl)) + *pp &= ~QUOTE; + if (i != 0) { + for (cl = addword(scopy(cp), NULL); anyspcl(cl); cl = nl) { + nl = newword(cl->w_nword * 2); + for (i = 0; i < cl->w_nword; i++) { /* for each argument */ + for (pp = cl->w_words[i]; *pp; pp++) + if (any(*pp, spcl)) { + globname(cl->w_words[i], pp); + break; + } + if (*pp == '\0') + nl = addword(scopy(cl->w_words[i]), nl); + } + for (i = 0; i < cl->w_nword; i++) + DELETE(cl->w_words[i]); + DELETE(cl); + } + if (cl->w_nword) { + for (i = 0; i < cl->w_nword; i++) + unquote(cl->w_words[i]); + qsort_string_vector(cl->w_words, cl->w_nword); + for (i = 0; i < cl->w_nword; i++) + wb = addword(cl->w_words[i], wb); + DELETE(cl); + return wb; + } + } + wb = addword(unquote(cp), wb); + return wb; +} + +static void globname(char *we, char *pp) +{ + char *np, *cp; + char *name, *gp, *dp; + int k; + DIR *dirp; + struct dirent *de; + char dname[NAME_MAX + 1]; + struct stat dbuf; + + for (np = we; np != pp; pp--) + if (pp[-1] == '/') + break; + dp = cp = get_space((int) (pp - np) + 3); + while (np < pp) + *cp++ = *np++; + *cp++ = '.'; + *cp = '\0'; + gp = cp = get_space(strlen(pp) + 1); + while (*np && *np != '/') + *cp++ = *np++; + *cp = '\0'; + dirp = opendir(dp); + if (dirp == 0) { + DELETE(dp); + DELETE(gp); + return; + } + dname[NAME_MAX] = '\0'; + while ((de = readdir(dirp)) != NULL) { + /* XXX Hmmm... What this could be? (abial) */ + /* if (ent[j].d_ino == 0) continue; + */ + strncpy(dname, de->d_name, NAME_MAX); + if (dname[0] == '.') + if (*gp != '.') + continue; + for (k = 0; k < NAME_MAX; k++) + if (any(dname[k], spcl)) + dname[k] |= QUOTE; + if (gmatch(dname, gp)) { + name = generate(we, pp, dname, np); + if (*np && !anys(np, spcl)) { + if (stat(name, &dbuf)) { + DELETE(name); + continue; + } + } + nl = addword(name, nl); + } + } + closedir(dirp); + DELETE(dp); + DELETE(gp); +} + +/* + * generate a pathname as below. + * start..end1 / middle end + * the slashes come for free + */ +static char *generate(char *start1, char *end1, char *middle, char *end) +{ + char *p; + char *op, *xp; + + p = op = get_space((int)(end1 - start1) + strlen(middle) + strlen(end) + 2); + xp = start1; + while (xp != end1) + *op++ = *xp++; + xp = middle; + while (*xp != '\0') + *op++ = *xp++; + strcpy(op, end); + return p; +} + +static int anyspcl(struct wdblock *wb) +{ + int i; + char **wd; + + wd = wb->w_words; + for (i = 0; i < wb->w_nword; i++) + if (anys(spcl, *wd++)) + return 1; + return 0; +} + + +/* -------- word.c -------- */ + +static struct wdblock *newword(int nw) +{ + struct wdblock *wb; + + wb = get_space(sizeof(*wb) + nw * sizeof(char *)); + wb->w_bsize = nw; + wb->w_nword = 0; + return wb; +} + +static struct wdblock *addword(char *wd, struct wdblock *wb) +{ + struct wdblock *wb2; + int nw; + + if (wb == NULL) + wb = newword(NSTART); + nw = wb->w_nword; + if (nw >= wb->w_bsize) { + wb2 = newword(nw * 2); + memcpy((char *) wb2->w_words, (char *) wb->w_words, + nw * sizeof(char *)); + wb2->w_nword = nw; + DELETE(wb); + wb = wb2; + } + wb->w_words[wb->w_nword++] = wd; + return wb; +} + +static char **getwords(struct wdblock *wb) +{ + char **wd; + int nb; + + if (wb == NULL) + return NULL; + if (wb->w_nword == 0) { + DELETE(wb); + return NULL; + } + nb = sizeof(*wd) * wb->w_nword; + wd = get_space(nb); + memcpy(wd, wb->w_words, nb); + DELETE(wb); /* perhaps should done by caller */ + return wd; +} + + +/* -------- io.c -------- */ + +/* + * shell IO + */ + +static int my_getc(int ec) +{ + int c; + + if (global_env.linep > elinep) { + while ((c = readc()) != '\n' && c) + continue; + err("input line too long"); + gflg = 1; + return c; + } + c = readc(); + if ((ec != '\'') && (ec != '`') && (global_env.iop->task != XGRAVE)) { + if (c == '\\') { + c = readc(); + if (c == '\n' && ec != '\"') + return my_getc(ec); + c |= QUOTE; + } + } + return c; +} + +static void unget(int c) +{ + if (global_env.iop >= global_env.iobase) + global_env.iop->peekc = c; +} + +static int eofc(void) +{ + return global_env.iop < global_env.iobase || (global_env.iop->peekc == 0 && global_env.iop->prev == 0); +} + +static int readc(void) +{ + int c; + + RCPRINTF(("READC: global_env.iop %p, global_env.iobase %p\n", global_env.iop, global_env.iobase)); + + for (; global_env.iop >= global_env.iobase; global_env.iop--) { + RCPRINTF(("READC: global_env.iop %p, peekc 0x%x\n", global_env.iop, global_env.iop->peekc)); + c = global_env.iop->peekc; + if (c != '\0') { + global_env.iop->peekc = 0; + return c; + } + if (global_env.iop->prev != 0) { + c = (*global_env.iop->iofn)(global_env.iop->argp, global_env.iop); + if (c != '\0') { + if (c == -1) { + global_env.iop++; + continue; + } + if (global_env.iop == iostack) + ioecho(c); + global_env.iop->prev = c; + return c; + } + if (global_env.iop->task == XIO && global_env.iop->prev != '\n') { + global_env.iop->prev = 0; + if (global_env.iop == iostack) + ioecho('\n'); + return '\n'; + } + } + if (global_env.iop->task == XIO) { + if (multiline) { + global_env.iop->prev = 0; + return 0; + } + if (interactive && global_env.iop == iostack + 1) { +#if ENABLE_FEATURE_EDITING + current_prompt = prompt->value; +#else + prs(prompt->value); +#endif + } + } + } /* FOR */ + + if (global_env.iop >= iostack) { + RCPRINTF(("READC: return 0, global_env.iop %p\n", global_env.iop)); + return 0; + } + + DBGPRINTF(("READC: leave()...\n")); + leave(); + /* NOTREACHED */ + return 0; +} + +static void ioecho(char c) +{ + if (FLAG['v']) + write(STDERR_FILENO, &c, sizeof c); +} + +static void pushio(struct ioarg *argp, int (*fn) (struct ioarg *)) +{ + DBGPRINTF(("PUSHIO: argp %p, argp->afid 0x%x, global_env.iop %p\n", argp, + argp->afid, global_env.iop)); + + /* Set env ptr for io source to next array spot and check for array overflow */ + if (++global_env.iop >= &iostack[NPUSH]) { + global_env.iop--; + err("Shell input nested too deeply"); + gflg = 1; + return; + } + + /* We did not overflow the NPUSH array spots so setup data structs */ + + global_env.iop->iofn = (int (*)(struct ioarg *, struct io *)) fn; /* Store data source func ptr */ + + if (argp->afid != AFID_NOBUF) + global_env.iop->argp = argp; + else { + + global_env.iop->argp = ioargstack + (global_env.iop - iostack); /* MAL - index into stack */ + *global_env.iop->argp = *argp; /* copy data from temp area into stack spot */ + + /* MAL - mainbuf is for 1st data source (command line?) and all nested use a single shared buffer? */ + + if (global_env.iop == &iostack[0]) + global_env.iop->argp->afbuf = &mainbuf; + else + global_env.iop->argp->afbuf = &sharedbuf; + + /* MAL - if not a termimal AND (commandline OR readable file) then give it a buffer id? */ + /* This line appears to be active when running scripts from command line */ + if ((isatty(global_env.iop->argp->afile) == 0) + && (global_env.iop == &iostack[0] + || lseek(global_env.iop->argp->afile, 0L, SEEK_CUR) != -1)) { + if (++bufid == AFID_NOBUF) /* counter rollover check, AFID_NOBUF = 11111111 */ + bufid = AFID_ID; /* AFID_ID = 0 */ + + global_env.iop->argp->afid = bufid; /* assign buffer id */ + } + + DBGPRINTF(("PUSHIO: iostack %p, global_env.iop %p, afbuf %p\n", + iostack, global_env.iop, global_env.iop->argp->afbuf)); + DBGPRINTF(("PUSHIO: mbuf %p, sbuf %p, bid %d, global_env.iop %p\n", + &mainbuf, &sharedbuf, bufid, global_env.iop)); + + } + + global_env.iop->prev = ~'\n'; + global_env.iop->peekc = 0; + global_env.iop->xchar = 0; + global_env.iop->nlcount = 0; + + if (fn == filechar || fn == linechar) + global_env.iop->task = XIO; + else if (fn == (int (*)(struct ioarg *)) gravechar + || fn == (int (*)(struct ioarg *)) qgravechar) + global_env.iop->task = XGRAVE; + else + global_env.iop->task = XOTHER; +} + +static struct io *setbase(struct io *ip) +{ + struct io *xp; + + xp = global_env.iobase; + global_env.iobase = ip; + return xp; +} + +/* + * Input generating functions + */ + +/* + * Produce the characters of a string, then a newline, then NUL. + */ +static int nlchar(struct ioarg *ap) +{ + char c; + + if (ap->aword == NULL) + return '\0'; + c = *ap->aword++; + if (c == '\0') { + ap->aword = NULL; + return '\n'; + } + return c; +} + +/* + * Given a list of words, produce the characters + * in them, with a space after each word. + */ +static int wdchar(struct ioarg *ap) +{ + char c; + char **wl; + + wl = ap->awordlist; + if (wl == NULL) + return 0; + if (*wl != NULL) { + c = *(*wl)++; + if (c != 0) + return c & 0177; + ap->awordlist++; + return ' '; + } + ap->awordlist = NULL; + return '\n'; +} + +/* + * Return the characters of a list of words, + * producing a space between them. + */ +static int dolchar(struct ioarg *ap) +{ + char *wp; + + wp = *ap->awordlist++; + if (wp != NULL) { + PUSHIO(aword, wp, *ap->awordlist == NULL ? strchar : xxchar); + return -1; + } + return 0; +} + +static int xxchar(struct ioarg *ap) +{ + int c; + + if (ap->aword == NULL) + return 0; + c = *ap->aword++; + if (c == '\0') { + ap->aword = NULL; + return ' '; + } + return c; +} + +/* + * Produce the characters from a single word (string). + */ +static int strchar(struct ioarg *ap) +{ + if (ap->aword == NULL) + return 0; + return *ap->aword++; +} + +/* + * Produce quoted characters from a single word (string). + */ +static int qstrchar(struct ioarg *ap) +{ + int c; + + if (ap->aword == NULL) + return 0; + c = *ap->aword++; + if (c) + c |= QUOTE; + return c; +} + +/* + * Return the characters from a file. + */ +static int filechar(struct ioarg *ap) +{ + int i; + char c; + struct iobuf *bp = ap->afbuf; + + if (ap->afid != AFID_NOBUF) { + i = (ap->afid != bp->id); + if (i || bp->bufp == bp->ebufp) { + if (i) + lseek(ap->afile, ap->afpos, SEEK_SET); + + i = nonblock_safe_read(ap->afile, bp->buf, sizeof(bp->buf)); + if (i <= 0) { + closef(ap->afile); + return 0; + } + + bp->id = ap->afid; + bp->bufp = bp->buf; + bp->ebufp = bp->bufp + i; + } + + ap->afpos++; + return *bp->bufp++ & 0177; + } +#if ENABLE_FEATURE_EDITING + if (interactive && isatty(ap->afile)) { + /* moved to G: static char filechar_cmdbuf[BUFSIZ]; */ + static int position = 0, size = 0; + + while (size == 0 || position >= size) { + size = read_line_input(current_prompt, filechar_cmdbuf, BUFSIZ, line_input_state); + if (size < 0) /* Error/EOF */ + exit(EXIT_SUCCESS); + position = 0; + /* if Ctrl-C, size == 0 and loop will repeat */ + } + c = filechar_cmdbuf[position]; + position++; + return c; + } +#endif + i = nonblock_safe_read(ap->afile, &c, sizeof(c)); + return i == sizeof(c) ? (c & 0x7f) : (closef(ap->afile), 0); +} + +/* + * Return the characters from a here temp file. + */ +static int herechar(struct ioarg *ap) +{ + char c; + + if (nonblock_safe_read(ap->afile, &c, sizeof(c)) != sizeof(c)) { + close(ap->afile); + c = '\0'; + } + return c; +} + +/* + * Return the characters produced by a process (`...`). + * Quote them if required, and remove any trailing newline characters. + */ +static int gravechar(struct ioarg *ap, struct io *iop) +{ + int c; + + c = qgravechar(ap, iop) & ~QUOTE; + if (c == '\n') + c = ' '; + return c; +} + +static int qgravechar(struct ioarg *ap, struct io *iop) +{ + int c; + + DBGPRINTF3(("QGRAVECHAR: enter, ap=%p, iop=%p\n", ap, iop)); + + if (iop->xchar) { + if (iop->nlcount) { + iop->nlcount--; + return '\n' | QUOTE; + } + c = iop->xchar; + iop->xchar = 0; + } else if ((c = filechar(ap)) == '\n') { + iop->nlcount = 1; + while ((c = filechar(ap)) == '\n') + iop->nlcount++; + iop->xchar = c; + if (c == 0) + return c; + iop->nlcount--; + c = '\n'; + } + return c != 0 ? c | QUOTE : 0; +} + +/* + * Return a single command (usually the first line) from a file. + */ +static int linechar(struct ioarg *ap) +{ + int c; + + c = filechar(ap); + if (c == '\n') { + if (!multiline) { + closef(ap->afile); + ap->afile = -1; /* illegal value */ + } + } + return c; +} + +/* + * Remap fd into shell's fd space + */ +static int remap(int fd) +{ + int i; + int map[NOFILE]; + int newfd; + + DBGPRINTF(("REMAP: fd=%d, global_env.iofd=%d\n", fd, global_env.iofd)); + + if (fd < global_env.iofd) { + for (i = 0; i < NOFILE; i++) + map[i] = 0; + + do { + map[fd] = 1; + newfd = dup(fd); + fd = newfd; + } while (fd >= 0 && fd < global_env.iofd); + + for (i = 0; i < NOFILE; i++) + if (map[i]) + close(i); + + if (fd < 0) + err("too many files open in shell"); + } + + return fd; +} + +static int openpipe(int *pv) +{ + int i; + + i = pipe(pv); + if (i < 0) + err("can't create pipe - try again"); + return i; +} + +static void closepipe(int *pv) +{ + if (pv != NULL) { + close(pv[0]); + close(pv[1]); + } +} + + +/* -------- here.c -------- */ + +/* + * here documents + */ + +static void markhere(char *s, struct ioword *iop) +{ + struct here *h, *lh; + + DBGPRINTF7(("MARKHERE: enter, s=%p\n", s)); + + h = get_space(sizeof(struct here)); + if (h == NULL) + return; + + h->h_tag = evalstr(s, DOSUB); + if (h->h_tag == 0) + return; + + h->h_iop = iop; + iop->io_name = 0; + h->h_next = NULL; + if (inhere == 0) + inhere = h; + else { + for (lh = inhere; lh != NULL; lh = lh->h_next) { + if (lh->h_next == 0) { + lh->h_next = h; + break; + } + } + } + iop->io_flag |= IOHERE | IOXHERE; + for (s = h->h_tag; *s; s++) { + if (*s & QUOTE) { + iop->io_flag &= ~IOXHERE; + *s &= ~QUOTE; + } + } + h->h_dosub = ((iop->io_flag & IOXHERE) ? '\0' : '\''); +} + +static void gethere(void) +{ + struct here *h, *hp; + + DBGPRINTF7(("GETHERE: enter...\n")); + + /* Scan here files first leaving inhere list in place */ + for (hp = h = inhere; h != NULL; hp = h, h = h->h_next) + readhere(&h->h_iop->io_name, h->h_tag, h->h_dosub /* NUL or ' */); + + /* Make inhere list active - keep list intact for scraphere */ + if (hp != NULL) { + hp->h_next = acthere; + acthere = inhere; + inhere = NULL; + } +} + +static void readhere(char **name, char *s, int ec) +{ + int tf; + char tname[30] = ".msh_XXXXXX"; + int c; + jmp_buf ev; + char myline[LINELIM + 1]; + char *thenext; + + DBGPRINTF7(("READHERE: enter, name=%p, s=%p\n", name, s)); + + tf = mkstemp(tname); + if (tf < 0) + return; + + *name = strsave(tname, areanum); + errpt = ev; + if (newenv(setjmp(errpt)) != 0) + unlink(tname); + else { + pushio(global_env.iop->argp, (int (*)(struct ioarg *)) global_env.iop->iofn); + global_env.iobase = global_env.iop; + for (;;) { + if (interactive && global_env.iop <= iostack) { +#if ENABLE_FEATURE_EDITING + current_prompt = cprompt->value; +#else + prs(cprompt->value); +#endif + } + thenext = myline; + while ((c = my_getc(ec)) != '\n' && c) { + if (ec == '\'') + c &= ~QUOTE; + if (thenext >= &myline[LINELIM]) { + c = 0; + break; + } + *thenext++ = c; + } + *thenext = 0; + if (strcmp(s, myline) == 0 || c == 0) + break; + *thenext++ = '\n'; + write(tf, myline, (int) (thenext - myline)); + } + if (c == 0) { + prs("here document `"); + prs(s); + err("' unclosed"); + } + quitenv(); + } + close(tf); +} + +/* + * open here temp file. + * if unquoted here, expand here temp file into second temp file. + */ +static int herein(char *hname, int xdoll) +{ + int hf; + int tf; + +#if __GNUC__ + /* Avoid longjmp clobbering */ + (void) &tf; +#endif + if (hname == NULL) + return -1; + + DBGPRINTF7(("HEREIN: hname is %s, xdoll=%d\n", hname, xdoll)); + + hf = open(hname, O_RDONLY); + if (hf < 0) + return -1; + + if (xdoll) { + char c; + char tname[30] = ".msh_XXXXXX"; + jmp_buf ev; + + tf = mkstemp(tname); + if (tf < 0) + return -1; + errpt = ev; + if (newenv(setjmp(errpt)) == 0) { + PUSHIO(afile, hf, herechar); + setbase(global_env.iop); + while ((c = subgetc(0, 0)) != 0) { + c &= ~QUOTE; + write(tf, &c, sizeof c); + } + quitenv(); + } else + unlink(tname); + close(tf); + tf = open(tname, O_RDONLY); + unlink(tname); + return tf; + } + return hf; +} + +static void scraphere(void) +{ + struct here *h; + + DBGPRINTF7(("SCRAPHERE: enter...\n")); + + for (h = inhere; h != NULL; h = h->h_next) { + if (h->h_iop && h->h_iop->io_name) + unlink(h->h_iop->io_name); + } + inhere = NULL; +} + +/* unlink here temp files before a freearea(area) */ +static void freehere(int area) +{ + struct here *h, *hl; + + DBGPRINTF6(("FREEHERE: enter, area=%d\n", area)); + + hl = NULL; + for (h = acthere; h != NULL; h = h->h_next) { + if (getarea((char *) h) >= area) { + if (h->h_iop->io_name != NULL) + unlink(h->h_iop->io_name); + if (hl == NULL) + acthere = h->h_next; + else + hl->h_next = h->h_next; + } else { + hl = h; + } + } +} + + +/* -------- sh.c -------- */ +/* + * shell + */ + +int msh_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int msh_main(int argc, char **argv) +{ + int f; + char *s; + int cflag; + char *name, **ap; + int (*iof) (struct ioarg *); + + INIT_G(); + + sharedbuf.id = AFID_NOBUF; + mainbuf.id = AFID_NOBUF; + elinep = line + sizeof(line) - 5; + +#if ENABLE_FEATURE_EDITING + line_input_state = new_line_input_t(FOR_SHELL); +#endif + + DBGPRINTF(("MSH_MAIN: argc %d, environ %p\n", argc, environ)); + + initarea(); + ap = environ; + if (ap != NULL) { + while (*ap) + assign(*ap++, !COPYV); + for (ap = environ; *ap;) + export(lookup(*ap++)); + } + closeall(); + areanum = 1; + + shell = lookup("SHELL"); + if (shell->value == null) + setval(shell, (char *)DEFAULT_SHELL); + export(shell); + + homedir = lookup("HOME"); + if (homedir->value == null) + setval(homedir, "/"); + export(homedir); + + setval(lookup("$"), putn(getpid())); + + path = lookup("PATH"); + if (path->value == null) { + /* Can be merged with same string elsewhere in bbox */ + if (geteuid() == 0) + setval(path, bb_default_root_path); + else + setval(path, bb_default_path); + } + export(path); + + ifs = lookup("IFS"); + if (ifs->value == null) + setval(ifs, " \t\n"); + +#ifdef MSHDEBUG + mshdbg_var = lookup("MSHDEBUG"); + if (mshdbg_var->value == null) + setval(mshdbg_var, "0"); +#endif + + prompt = lookup("PS1"); +#if ENABLE_FEATURE_EDITING_FANCY_PROMPT + if (prompt->value == null) +#endif + setval(prompt, DEFAULT_USER_PROMPT); + if (geteuid() == 0) { + setval(prompt, DEFAULT_ROOT_PROMPT); + prompt->status &= ~EXPORT; + } + cprompt = lookup("PS2"); +#if ENABLE_FEATURE_EDITING_FANCY_PROMPT + if (cprompt->value == null) +#endif + setval(cprompt, "> "); + + iof = filechar; + cflag = 0; + name = *argv++; + if (--argc >= 1) { + if (argv[0][0] == '-' && argv[0][1] != '\0') { + for (s = argv[0] + 1; *s; s++) + switch (*s) { + case 'c': + prompt->status &= ~EXPORT; + cprompt->status &= ~EXPORT; + setval(prompt, ""); + setval(cprompt, ""); + cflag = 1; + if (--argc > 0) + PUSHIO(aword, *++argv, iof = nlchar); + break; + + case 'q': + qflag = SIG_DFL; + break; + + case 's': + /* standard input */ + break; + + case 't': + prompt->status &= ~EXPORT; + setval(prompt, ""); + iof = linechar; + break; + + case 'i': + interactive = 1; + default: + if (*s >= 'a' && *s <= 'z') + FLAG[(int) *s]++; + } + } else { + argv--; + argc++; + } + + if (iof == filechar && --argc > 0) { + setval(prompt, ""); + setval(cprompt, ""); + prompt->status &= ~EXPORT; + cprompt->status &= ~EXPORT; + +/* Shell is non-interactive, activate printf-based debug */ +#ifdef MSHDEBUG + mshdbg = mshdbg_var->value[0] - '0'; + if (mshdbg < 0) + mshdbg = 0; +#endif + DBGPRINTF(("MSH_MAIN: calling newfile()\n")); + + name = *++argv; + if (newfile(name)) + exit(EXIT_FAILURE); /* Exit on error */ + } + } + + setdash(); + + /* This won't be true if PUSHIO has been called, say from newfile() above */ + if (global_env.iop < iostack) { + PUSHIO(afile, 0, iof); + if (isatty(0) && isatty(1) && !cflag) { + interactive = 1; +#if !ENABLE_FEATURE_SH_EXTRA_QUIET +#ifdef MSHDEBUG + printf("\n\n%s built-in shell (msh with debug)\n", bb_banner); +#else + printf("\n\n%s built-in shell (msh)\n", bb_banner); +#endif + printf("Enter 'help' for a list of built-in commands.\n\n"); +#endif + } + } + + signal(SIGQUIT, qflag); + if (name && name[0] == '-') { + interactive = 1; + f = open(".profile", O_RDONLY); + if (f >= 0) + next(remap(f)); + f = open("/etc/profile", O_RDONLY); + if (f >= 0) + next(remap(f)); + } + if (interactive) + signal(SIGTERM, sig); + + if (signal(SIGINT, SIG_IGN) != SIG_IGN) + signal(SIGINT, onintr); + +/* Handle "msh SCRIPT VAR=val params..." */ +/* Disabled: bash does not do it! */ +#if 0 + argv++; + /* skip leading args of the form VAR=val */ + while (*argv && assign(*argv, !COPYV)) { + argc--; + argv++; + } + argv--; +#endif + dolv = argv; + dolc = argc; + dolv[0] = name; + + setval(lookup("#"), putn((--dolc < 0) ? (dolc = 0) : dolc)); + + DBGPRINTF(("MSH_MAIN: begin FOR loop, interactive %d, global_env.iop %p, iostack %p\n", interactive, global_env.iop, iostack)); + + for (;;) { + if (interactive && global_env.iop <= iostack) { +#if ENABLE_FEATURE_EDITING + current_prompt = prompt->value; +#else + prs(prompt->value); +#endif + } + onecommand(); + /* Ensure that getenv("PATH") stays current */ + setenv("PATH", path->value, 1); + } + + DBGPRINTF(("MSH_MAIN: returning.\n")); +} + + +/* + * Copyright (c) 1987,1997, Prentice Hall + * All rights reserved. + * + * Redistribution and use of the MINIX operating system in source and + * binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * Neither the name of Prentice Hall nor the names of the software + * authors or contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS, AUTHORS, AND + * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL PRENTICE HALL OR ANY AUTHORS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ diff --git a/shell/msh_function.patch b/shell/msh_function.patch new file mode 100644 index 0000000..270b9ee --- /dev/null +++ b/shell/msh_function.patch @@ -0,0 +1,350 @@ +This is a "function" patch for msh which is in use by some busybox +users. Unfortunately it is far too buggy to be applied, but maybe +it's a useful starting point for future work. + +Function-related code is delimited by comments of the form + //funccode:start + ... + //funccode:end +for ease of grepping + +An example of buggy behavior: + +#f() { +# echo foo +# echo test`echo bar >&2` +# echo END f +#} + +function g { +# echo 2 foo +# echo 2 test`echo 2 bar >&2` +# f + echo END g +# echo "1:'$1' 2:'$2'" +} + +# Even this first block fails - it does not even call functions! +# (replacing "echo END g" above with "echo END" makes it run ok) +echo DRY RUN + echo 2 foo + echo 2 test`echo 2 bar >&2` + echo END g + echo "1:'$1' 2:'$2'" + echo foo + echo test`echo bar >&2` + echo END f +echo END DRY RUN + +exit + +# This would fail too +g "$1-one" "two$2" +echo DONE + + + +diff -d -urpN busybox.7/shell/msh.c busybox.8/shell/msh.c +--- busybox.7/shell/msh.c 2008-06-09 09:34:45.000000000 +0200 ++++ busybox.8/shell/msh.c 2008-06-09 09:38:17.000000000 +0200 +@@ -89,6 +89,14 @@ static char *itoa(int n) + + //#define MSHDEBUG 4 + ++/* Used only in "function" support code */ ++#ifdef KSDBG //funccode:start ++ #define KSDBG_PRINT_FUNCNAME fprintf(stderr, "in %s\n", __FUNCTION__) ++#else ++ #define KSDBG_PRINT_FUNCNAME ((void)0) ++#endif ++//funccode:end ++ + #ifdef MSHDEBUG + static int mshdbg = MSHDEBUG; + +@@ -220,6 +228,9 @@ struct op { + #define TASYNC 16 /* c & */ + /* Added to support "." file expansion */ + #define TDOT 17 ++#define TFUNC 18 //funccode:start ++#define TRETURN 19 ++ //funccode:end + + /* Strings for names to make debug easier */ + #ifdef MSHDEBUG +@@ -319,6 +330,27 @@ struct region { + int area; + }; + ++static int func_finished; //funccode:start ++struct func { ++ char* name; ++ int begin_addr; /* pos in buffer of function */ ++ int end_addr; ++}; ++#define MAX_FUNCS 100 ++ ++static struct func funcs[MAX_FUNCS]; ++ ++/* the max DEPTH of function call */ ++#define MAX_DEPTH 100 ++static struct _frame_s { ++ int argc; ++ char **argv; ++ int saved_return_addr; ++} frame[MAX_DEPTH]; ++ ++static void register_func(int begin, int end); ++static struct func* find_func(char* name); ++static void exec_func(struct func* f); //funccode:end + + /* -------- grammar stuff -------- */ + typedef union { +@@ -347,6 +379,8 @@ typedef union { + #define IN 272 + /* Added for "." file expansion */ + #define DOT 273 ++#define FUNC 274 //funccode:start ++#define RETURN 275 //funccode:end + + #define YYERRCODE 300 + +@@ -1722,6 +1756,40 @@ static struct op *simple(void) + (void) synio(0); + break; + ++ case FUNC: { //funccode:start ++ int stop_flag; ++ int number_brace; ++ int func_begin; ++ int func_end; ++ int c; ++ while ((c = my_getc(0)) == ' ' || c == '\t'|| c == '\n') /* skip whitespace */ ++ continue; ++ stop_flag = 1; ++ number_brace = 0; ++ func_begin = global_env.iobase->argp->afpos; ++ while (stop_flag) { ++ if (c == '{') ++ number_brace++; ++ if (c == '}') ++ number_brace--; ++ if (!number_brace) /* if we reach the brace of most outsite */ ++ stop_flag = 0; ++ c = my_getc(0); ++ } ++ unget(c); ++ unget(c); ++ func_end = global_env.iobase->argp->afpos; ++ register_func(func_begin, func_end); ++ peeksym = 0; ++ t = NULL; ++ return t; ++ } ++ case RETURN: ++ func_finished = 1; ++ peeksym = 0; ++ t = NULL; ++ return t; //funccode:end ++ + case WORD: + if (t == NULL) { + t = newtp(); +@@ -2265,6 +2333,13 @@ static int yylex(int cf) + case ')': + startl = 1; + return c; ++ case '{': //funccode:start ++ c = collect(c, '}'); ++ if (c != '\0') ++ return c; ++ break; ++ case '}': ++ return RETURN; //funccode:end + } + + unget(c); +@@ -2293,9 +2368,172 @@ static int yylex(int cf) + } + + yylval.cp = strsave(line, areanum); ++ /* To identify a subroutine */ //funccode:start ++ c = my_getc(0); ++ if (c && any(c, "(")) { ++ c = my_getc(0); ++ if (c && any(c, ")")) ++ return FUNC; ++ zzerr(); ++ } else ++ unget(c); ++ /* read the first char */ ++ /* To identify a function */ ++ if (strcmp(yylval.cp, "function") == 0) { ++ int ret = yylex(0); ++ /* read the function name after "function" */ ++ if (ret == WORD) ++ return (FUNC); ++ zzerr(); ++ } ++ { ++ struct func* f = find_func(yylval.cp); ++ if (f != NULL) { ++ exec_func(f); ++ return RETURN; ++ } ++ } ++ if (yylval.cp != NULL && strcmp(yylval.cp, "return") == 0) { ++ return RETURN; ++ } //funccode:end + return WORD; + } + ++static void register_func(int begin, int end) //funccode:start ++{ ++ struct func *p; ++ int i; ++ for (i = 0; i < MAX_FUNCS; i++) { ++ if (funcs[i].name == NULL) { ++ p = &funcs[i]; ++ break; ++ } ++ } ++ if (i == MAX_FUNCS) { ++ fprintf(stderr, "Too much functions beyond limit\n"); ++ leave(); ++ } ++ p->name = xstrdup(yylval.cp); ++ //fprintf(stderr, "register function,%d,%d,%s\n", begin, end, p->name); ++ KSDBG_PRINT_FUNCNAME; ++ /* io stream */ ++ p->begin_addr = begin; ++ p->end_addr = end; ++} ++ ++static struct func* find_func(char* name) ++{ ++ int i; ++ for (i = 0; i < MAX_FUNCS; i++) { ++ if (funcs[i].name == NULL) ++ continue; ++ if (!strcmp(funcs[i].name, name)) ++ return &funcs[i]; ++ } ++ KSDBG_PRINT_FUNCNAME; ++ //fprintf(stderr, "not found the function %s\n", name); ++ return NULL; ++ //zzerr(); ++} ++ ++/* Begin to execute the function */ ++static int cur_frame = 0; ++ ++static void exec_func(struct func* f) ++{ ++ int c; ++ int temp_argc; ++ char** temp_argv; ++ struct iobuf *bp; ++ ++ /* create a new frame, save the argument and return address to this frame */ ++ frame[cur_frame].argc = dolc; ++ frame[cur_frame].argv = dolv; ++ ++ cur_frame++; ++ /* do some argument parse and set arguments */ ++ temp_argv = xmalloc(sizeof(char *)); ++ temp_argv[0] = xstrdup(f->name); ++ temp_argc = 0; ++ global_env.iop->argp->afpos--; ++ global_env.iop->argp->afbuf->bufp--; ++// unget(c); ++ while (((c = yylex(0)) != '\n') && (yylval.cp != NULL)) { ++ temp_argc++; ++ temp_argv = xrealloc(temp_argv, sizeof(char *) * (temp_argc+1)); ++ /* parse $ var if passed argument is a variable */ ++ if (yylval.cp[0] == '$') { ++ struct var *arg = lookup(&yylval.cp[1]); ++ temp_argv[temp_argc] = xstrdup(arg->value); ++ //fprintf(stderr, "arg->value=%s\n", arg->value); ++ } else { ++ temp_argv[temp_argc] = xstrdup(yylval.cp); ++ //fprintf(stderr, "ARG:%s\n", yylval.cp); ++ } ++ } ++ /* ++ global_env.iop->argp->afpos--; ++ global_env.iop->argp->afbuf->bufp--; ++ */ ++ dolc = temp_argc; ++ dolv = temp_argv; ++ //unget(c); ++ //while ((c = my_getc(0)) == ' ' || c == '\t') /* Skip whitespace */ ++ // continue; ++ //unget(c); ++ frame[cur_frame].saved_return_addr = global_env.iop->argp->afpos; ++ ++ /* get function begin address and execute this function */ ++ ++ bp = global_env.iop->argp->afbuf; ++ bp->bufp = &(bp->buf[f->begin_addr]); ++ global_env.iop->argp->afpos = f->begin_addr; ++ ++ /* func_finished=0 means we are in a function and func_finished=1 means we are executing a function */ ++ func_finished = 0; ++ ++ //fprintf(stderr, "exec function %s\n", f->name); ++ KSDBG_PRINT_FUNCNAME; ++ for (;;) { ++ //fprintf(stderr, "afpos=%d,%s\n", global_env.iop->argp->afpos, yylval.cp); ++ if (global_env.iop->argp->afpos == f->end_addr) ++ break; ++ onecommand(); ++ /* we return from a function, when func_finished = 1 */ ++ if (func_finished) ++ break; ++ } ++ ++ { ++ //fprintf(stderr, "%s is finished @%d!\n", f->name, global_env.iop->argp->afpos); ++ int ret = frame[cur_frame].saved_return_addr; ++ /* workaround code for \n */ ++ if (dolc) ++ ret--; ++ /* get return address from current frame and jump to */ ++ global_env.iop->argp->afpos = ret; ++ global_env.iop->argp->afbuf->bufp = &(global_env.iop->argp->afbuf->buf[ret]); ++ } ++ /* ++ fprintf(stderr, "******** after execution ********************\n"); ++ fprintf(stderr, " %s \n############# %d\n", global_env.iop->argp->afbuf->bufp, ret); ++ fprintf(stderr, "*******************************\n"); ++ */ ++ /* we return to previous frame */ ++ cur_frame--; ++ /* free some space occupied by argument */ ++ while (dolc--) ++ free(dolv[dolc]); ++ free(dolv); ++ ++ /* recover argument for last function */ ++ dolv = frame[cur_frame].argv; ++ dolc = frame[cur_frame].argc; ++ /* If we are not in the outest frame, we should set ++ * func_finished to 0 that means we still in some function */ ++ if (cur_frame != 0) ++ func_finished = 0; ++} //funccode:end + + static int collect(int c, int c1) + { +@@ -2601,6 +2839,10 @@ static int execute(struct op *t, int *pi + execute(t->right->right, pin, pout, /* no_fork: */ 0); + } + break; ++ case TFUNC: //funccode:start ++ break; ++ case TRETURN: ++ break; //funccode:end + + case TCASE: + cp = evalstr(t->str, DOSUB | DOTRIM); diff --git a/shell/msh_test/msh-bugs/noeol3.right b/shell/msh_test/msh-bugs/noeol3.right new file mode 100644 index 0000000..56f8515 --- /dev/null +++ b/shell/msh_test/msh-bugs/noeol3.right @@ -0,0 +1 @@ +hush: syntax error: unterminated " diff --git a/shell/msh_test/msh-bugs/noeol3.tests b/shell/msh_test/msh-bugs/noeol3.tests new file mode 100755 index 0000000..ec958ed --- /dev/null +++ b/shell/msh_test/msh-bugs/noeol3.tests @@ -0,0 +1,2 @@ +# last line has no EOL! +echo "unterminated \ No newline at end of file diff --git a/shell/msh_test/msh-bugs/process_subst.right b/shell/msh_test/msh-bugs/process_subst.right new file mode 100644 index 0000000..397bc80 --- /dev/null +++ b/shell/msh_test/msh-bugs/process_subst.right @@ -0,0 +1,3 @@ +TESTzzBEST +TEST$(echo zz)BEST +TEST'BEST diff --git a/shell/msh_test/msh-bugs/process_subst.tests b/shell/msh_test/msh-bugs/process_subst.tests new file mode 100755 index 0000000..21996bc --- /dev/null +++ b/shell/msh_test/msh-bugs/process_subst.tests @@ -0,0 +1,3 @@ +echo "TEST`echo zz;echo;echo`BEST" +echo "TEST`echo '$(echo zz)'`BEST" +echo "TEST`echo "'"`BEST" diff --git a/shell/msh_test/msh-bugs/read.right b/shell/msh_test/msh-bugs/read.right new file mode 100644 index 0000000..0e50e2a --- /dev/null +++ b/shell/msh_test/msh-bugs/read.right @@ -0,0 +1,4 @@ +read +cat +echo "REPLY=$REPLY" +REPLY=exec "$1.out" +echo TEST2 >"$2.out" +# bash says: "$@.out": ambiguous redirect +# ash handles it as if it is '$*' - we do the same +echo TEST3 >"$@.out" + +cat abc.out "d e.out" "abc d e.out" + +rm abc.out "d e.out" "abc d e.out" diff --git a/shell/msh_test/msh-execution/exitcode_EACCES.right b/shell/msh_test/msh-execution/exitcode_EACCES.right new file mode 100644 index 0000000..b13682c --- /dev/null +++ b/shell/msh_test/msh-execution/exitcode_EACCES.right @@ -0,0 +1,2 @@ +./: cannot execute +126 diff --git a/shell/msh_test/msh-execution/exitcode_EACCES.tests b/shell/msh_test/msh-execution/exitcode_EACCES.tests new file mode 100755 index 0000000..26b5c61 --- /dev/null +++ b/shell/msh_test/msh-execution/exitcode_EACCES.tests @@ -0,0 +1,2 @@ +./ +echo $? diff --git a/shell/msh_test/msh-execution/exitcode_ENOENT.right b/shell/msh_test/msh-execution/exitcode_ENOENT.right new file mode 100644 index 0000000..dd49d2c --- /dev/null +++ b/shell/msh_test/msh-execution/exitcode_ENOENT.right @@ -0,0 +1,2 @@ +./does_not_exist_for_sure: not found +127 diff --git a/shell/msh_test/msh-execution/exitcode_ENOENT.tests b/shell/msh_test/msh-execution/exitcode_ENOENT.tests new file mode 100755 index 0000000..7f1b88a --- /dev/null +++ b/shell/msh_test/msh-execution/exitcode_ENOENT.tests @@ -0,0 +1,2 @@ +./does_not_exist_for_sure +echo $? diff --git a/shell/msh_test/msh-execution/many_continues.right b/shell/msh_test/msh-execution/many_continues.right new file mode 100644 index 0000000..d86bac9 --- /dev/null +++ b/shell/msh_test/msh-execution/many_continues.right @@ -0,0 +1 @@ +OK diff --git a/shell/msh_test/msh-execution/many_continues.tests b/shell/msh_test/msh-execution/many_continues.tests new file mode 100755 index 0000000..86c729a --- /dev/null +++ b/shell/msh_test/msh-execution/many_continues.tests @@ -0,0 +1,15 @@ +if test $# = 0; then + # Child will kill us in 1 second + "$THIS_SH" "$0" $$ & + + # Loop many, many times + trap 'echo OK; exit 0' 15 + while true; do + continue + done + echo BAD + exit 1 +fi + +sleep 1 +kill $1 diff --git a/shell/msh_test/msh-execution/nested_break.right b/shell/msh_test/msh-execution/nested_break.right new file mode 100644 index 0000000..4e8b6b0 --- /dev/null +++ b/shell/msh_test/msh-execution/nested_break.right @@ -0,0 +1,8 @@ +A +B +iteration +C +A +B +iteration +D diff --git a/shell/msh_test/msh-execution/nested_break.tests b/shell/msh_test/msh-execution/nested_break.tests new file mode 100755 index 0000000..f2e6f81 --- /dev/null +++ b/shell/msh_test/msh-execution/nested_break.tests @@ -0,0 +1,17 @@ +# Testcase for http://bugs.busybox.net/view.php?id=846 + +n=0 +while : +do + echo A + while : + do + echo B + break + done + echo iteration + [ $n = 1 ] && break + echo C + n=`expr $n + 1` +done +echo D diff --git a/shell/msh_test/msh-misc/tick.right b/shell/msh_test/msh-misc/tick.right new file mode 100644 index 0000000..6ed281c --- /dev/null +++ b/shell/msh_test/msh-misc/tick.right @@ -0,0 +1,2 @@ +1 +1 diff --git a/shell/msh_test/msh-misc/tick.tests b/shell/msh_test/msh-misc/tick.tests new file mode 100755 index 0000000..1f749a9 --- /dev/null +++ b/shell/msh_test/msh-misc/tick.tests @@ -0,0 +1,4 @@ +true +false; echo `echo $?` +true +{ false; echo `echo $?`; } diff --git a/shell/msh_test/msh-parsing/argv0.right b/shell/msh_test/msh-parsing/argv0.right new file mode 100644 index 0000000..d86bac9 --- /dev/null +++ b/shell/msh_test/msh-parsing/argv0.right @@ -0,0 +1 @@ +OK diff --git a/shell/msh_test/msh-parsing/argv0.tests b/shell/msh_test/msh-parsing/argv0.tests new file mode 100755 index 0000000..f5c40f6 --- /dev/null +++ b/shell/msh_test/msh-parsing/argv0.tests @@ -0,0 +1,4 @@ +if test $# = 0; then + exec "$THIS_SH" "$0" arg +fi +echo OK diff --git a/shell/msh_test/msh-parsing/noeol.right b/shell/msh_test/msh-parsing/noeol.right new file mode 100644 index 0000000..e427984 --- /dev/null +++ b/shell/msh_test/msh-parsing/noeol.right @@ -0,0 +1 @@ +HELLO diff --git a/shell/msh_test/msh-parsing/noeol.tests b/shell/msh_test/msh-parsing/noeol.tests new file mode 100755 index 0000000..a93113a --- /dev/null +++ b/shell/msh_test/msh-parsing/noeol.tests @@ -0,0 +1,2 @@ +# next line has no EOL! +echo HELLO \ No newline at end of file diff --git a/shell/msh_test/msh-parsing/noeol2.right b/shell/msh_test/msh-parsing/noeol2.right new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/shell/msh_test/msh-parsing/noeol2.right @@ -0,0 +1 @@ +1 diff --git a/shell/msh_test/msh-parsing/noeol2.tests b/shell/msh_test/msh-parsing/noeol2.tests new file mode 100755 index 0000000..1220f05 --- /dev/null +++ b/shell/msh_test/msh-parsing/noeol2.tests @@ -0,0 +1,7 @@ +# last line has no EOL! +if true +then + echo 1 +else + echo 2 +fi \ No newline at end of file diff --git a/shell/msh_test/msh-parsing/quote1.right b/shell/msh_test/msh-parsing/quote1.right new file mode 100644 index 0000000..cb38205 --- /dev/null +++ b/shell/msh_test/msh-parsing/quote1.right @@ -0,0 +1 @@ +'1' diff --git a/shell/msh_test/msh-parsing/quote1.tests b/shell/msh_test/msh-parsing/quote1.tests new file mode 100755 index 0000000..f558954 --- /dev/null +++ b/shell/msh_test/msh-parsing/quote1.tests @@ -0,0 +1,2 @@ +a=1 +echo "'$a'" diff --git a/shell/msh_test/msh-parsing/quote2.right b/shell/msh_test/msh-parsing/quote2.right new file mode 100644 index 0000000..3bc9edc --- /dev/null +++ b/shell/msh_test/msh-parsing/quote2.right @@ -0,0 +1 @@ +>1 diff --git a/shell/msh_test/msh-parsing/quote2.tests b/shell/msh_test/msh-parsing/quote2.tests new file mode 100755 index 0000000..bd966f3 --- /dev/null +++ b/shell/msh_test/msh-parsing/quote2.tests @@ -0,0 +1,2 @@ +a=1 +echo ">$a" diff --git a/shell/msh_test/msh-parsing/quote3.right b/shell/msh_test/msh-parsing/quote3.right new file mode 100644 index 0000000..069a46e --- /dev/null +++ b/shell/msh_test/msh-parsing/quote3.right @@ -0,0 +1,3 @@ +Testing: in $empty"" +.. +Finished diff --git a/shell/msh_test/msh-parsing/quote3.tests b/shell/msh_test/msh-parsing/quote3.tests new file mode 100755 index 0000000..075e785 --- /dev/null +++ b/shell/msh_test/msh-parsing/quote3.tests @@ -0,0 +1,8 @@ +if test $# = 0; then + exec "$THIS_SH" quote3.tests abc "d e" +fi + +echo 'Testing: in $empty""' +empty='' +for a in $empty""; do echo ".$a."; done +echo Finished diff --git a/shell/msh_test/msh-parsing/quote4.right b/shell/msh_test/msh-parsing/quote4.right new file mode 100644 index 0000000..b2901ea --- /dev/null +++ b/shell/msh_test/msh-parsing/quote4.right @@ -0,0 +1 @@ +a b diff --git a/shell/msh_test/msh-parsing/quote4.tests b/shell/msh_test/msh-parsing/quote4.tests new file mode 100755 index 0000000..f1dabfa --- /dev/null +++ b/shell/msh_test/msh-parsing/quote4.tests @@ -0,0 +1,2 @@ +a_b='a b' +echo "$a_b" diff --git a/shell/msh_test/msh-vars/star.right b/shell/msh_test/msh-vars/star.right new file mode 100644 index 0000000..0ecc55b --- /dev/null +++ b/shell/msh_test/msh-vars/star.right @@ -0,0 +1,6 @@ +.1. +.abc. +.d. +.e. +.f. +.1 abc d e f. diff --git a/shell/msh_test/msh-vars/star.tests b/shell/msh_test/msh-vars/star.tests new file mode 100755 index 0000000..5554c40 --- /dev/null +++ b/shell/msh_test/msh-vars/star.tests @@ -0,0 +1,8 @@ +if test $# = 0; then + exec "$THIS_SH" star.tests 1 abc 'd e f' +fi +# 'd e f' should be split into 3 separate args: +for a in $*; do echo ".$a."; done + +# must produce .1 abc d e f. +for a in "$*"; do echo ".$a."; done diff --git a/shell/msh_test/msh-vars/var.right b/shell/msh_test/msh-vars/var.right new file mode 100644 index 0000000..14b2314 --- /dev/null +++ b/shell/msh_test/msh-vars/var.right @@ -0,0 +1,4 @@ +http://busybox.net +http://busybox.net_abc +1 +1 diff --git a/shell/msh_test/msh-vars/var.tests b/shell/msh_test/msh-vars/var.tests new file mode 100755 index 0000000..0a63696 --- /dev/null +++ b/shell/msh_test/msh-vars/var.tests @@ -0,0 +1,9 @@ +URL=http://busybox.net + +echo $URL +echo ${URL}_abc + +true +false; echo $? +true +{ false; echo $?; } diff --git a/shell/msh_test/msh-vars/var_subst_in_for.right b/shell/msh_test/msh-vars/var_subst_in_for.right new file mode 100644 index 0000000..c8aca1c --- /dev/null +++ b/shell/msh_test/msh-vars/var_subst_in_for.right @@ -0,0 +1,40 @@ +Testing: in x y z +.x. +.y. +.z. +Testing: in u $empty v +.u. +.v. +Testing: in u " $empty" v +.u. +. . +.v. +Testing: in u $empty $empty$a v +.u. +.a. +.v. +Testing: in $a_b +.a. +.b. +Testing: in $* +.abc. +.d. +.e. +Testing: in $@ +.abc. +.d. +.e. +Testing: in -$*- +.-abc. +.d. +.e-. +Testing: in -$@- +.-abc. +.d. +.e-. +Testing: in $a_b -$a_b- +.a. +.b. +.-a. +.b-. +Finished diff --git a/shell/msh_test/msh-vars/var_subst_in_for.tests b/shell/msh_test/msh-vars/var_subst_in_for.tests new file mode 100755 index 0000000..4d1c112 --- /dev/null +++ b/shell/msh_test/msh-vars/var_subst_in_for.tests @@ -0,0 +1,40 @@ +if test $# = 0; then + exec "$THIS_SH" var_subst_in_for.tests abc "d e" +fi + +echo 'Testing: in x y z' +for a in x y z; do echo ".$a."; done + +echo 'Testing: in u $empty v' +empty='' +for a in u $empty v; do echo ".$a."; done + +echo 'Testing: in u " $empty" v' +empty='' +for a in u " $empty" v; do echo ".$a."; done + +echo 'Testing: in u $empty $empty$a v' +a='a' +for a in u $empty $empty$a v; do echo ".$a."; done + +echo 'Testing: in $a_b' +a_b='a b' +for a in $a_b; do echo ".$a."; done + +echo 'Testing: in $*' +for a in $*; do echo ".$a."; done + +echo 'Testing: in $@' +for a in $@; do echo ".$a."; done + +echo 'Testing: in -$*-' +for a in -$*-; do echo ".$a."; done + +echo 'Testing: in -$@-' +for a in -$@-; do echo ".$a."; done + +echo 'Testing: in $a_b -$a_b-' +a_b='a b' +for a in $a_b -$a_b-; do echo ".$a."; done + +echo Finished diff --git a/shell/msh_test/run-all b/shell/msh_test/run-all new file mode 100755 index 0000000..43bc9fc --- /dev/null +++ b/shell/msh_test/run-all @@ -0,0 +1,64 @@ +#!/bin/sh + +test -x msh || { + echo "No ./msh?! Perhaps you want to run 'ln -s ../../busybox msh'" + exit +} + +PATH="$PWD:$PATH" # for msh +export PATH + +THIS_SH="$PWD/msh" +export THIS_SH + +do_test() +{ + test -d "$1" || return 0 +# echo Running tests in directory "$1" + ( + cd "$1" || { echo "cannot cd $1!"; exit 1; } + for x in run-*; do + test -f "$x" || continue + case "$x" in + "$0"|run-minimal|run-gprof) ;; + *.orig|*~) ;; + #*) echo $x ; sh $x ;; + *) + sh "$x" >"../$1-$x.fail" 2>&1 && \ + { echo "$1/$x: ok"; rm "../$1-$x.fail"; } || echo "$1/$x: fail"; + ;; + esac + done + # Many bash run-XXX scripts just do this, + # no point in duplication it all over the place + for x in *.tests; do + test -x "$x" || continue + name="${x%%.tests}" + test -f "$name.right" || continue +# echo Running test: "$name.right" + { + "$THIS_SH" "./$x" >"$name.xx" 2>&1 + diff -u "$name.xx" "$name.right" >"../$1-$x.fail" && rm -f "$name.xx" "../$1-$x.fail" + } && echo "$1/$x: ok" || echo "$1/$x: fail" + done + ) +} + +# Main part of this script +# Usage: run-all [directories] + +if [ $# -lt 1 ]; then + # All sub directories + modules=`ls -d msh-*` + + for module in $modules; do + do_test $module + done +else + while [ $# -ge 1 ]; do + if [ -d $1 ]; then + do_test $1 + fi + shift + done +fi diff --git a/shell/susv3_doc.tar.bz2 b/shell/susv3_doc.tar.bz2 new file mode 100644 index 0000000..443a283 Binary files /dev/null and b/shell/susv3_doc.tar.bz2 differ -- cgit v1.2.3