X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=shell%2Fhush.c;h=1187cbe8fb60b7e5a9acd6af06a68c091c94e038;hb=12450dbeef1afee5ed27f8f3a2395edbdc7a9355;hp=d59a5de82252315bfca72f0e359d78b7f5ffbcab;hpb=ad4bd0548a5a9b630606ca95794cee8b31671c9a;p=oweals%2Fbusybox.git diff --git a/shell/hush.c b/shell/hush.c index d59a5de82..1187cbe8f 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -42,26 +42,43 @@ * <(list) and >(list) Process Substitution * Tilde Expansion * - * Bash stuff (maybe optionally enable?): + * Bash stuff (optionally enabled): * &> and >& redirection of stdout+stderr - * Brace expansion + * Brace Expansion * reserved words: [[ ]] function select * substrings ${var:1:5} + * let EXPR [EXPR...] + * Each EXPR is an arithmetic expression (ARITHMETIC EVALUATION) + * If the last arg evaluates to 0, let returns 1; 0 otherwise. + * NB: let `echo 'a=a + 1'` - error (IOW: multi-word expansion is used) + * ((EXPR)) + * The EXPR is evaluated according to ARITHMETIC EVALUATION. + * This is exactly equivalent to let "expression". * * TODOs: * grep for "TODO" and fix (some of them are easy) - * change { and } from special chars to reserved words - * $var refs in function do not pick up values set by "var=val func" * builtins: ulimit + * special variables (done: PWD) * follow IFS rules more precisely, including update semantics - * figure out what to do with backslash-newline - * continuation lines, both explicit and implicit - done? - * separate job control from interactiveness - * (testcase: booting with init=/bin/hush does not show prompt (2009-04)) + * export builtin should be special, its arguments are assignments + * and therefore expansion of them should be "one-word" expansion: + * $ export i=`echo 'a b'` # export has one arg: "i=a b" + * compare with: + * $ ls i=`echo 'a b'` # ls has two args: "i=a" and "b" + * ls: cannot access i=a: No such file or directory + * ls: cannot access b: No such file or directory + * Note1: same applies to local builtin. + * Note2: bash 3.2.33(1) does this only if export word itself + * is not quoted: + * $ export i=`echo 'aaa bbb'`; echo "$i" + * aaa bbb + * $ "export" i=`echo 'aaa bbb'`; echo "$i" + * aaa * * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. */ -#include "busybox.h" /* for APPLET_IS_NOFORK/NOEXEC */ +#include "busybox.h" /* for APPLET_IS_NOFORK/NOEXEC */ +#include /* for malloc_trim */ #include /* #include */ #if ENABLE_HUSH_CASE @@ -69,12 +86,17 @@ #endif #include "math.h" #include "match.h" +#if ENABLE_HUSH_RANDOM_SUPPORT +# include "random.h" +#else +# define CLEAR_RANDOM_T(rnd) ((void)0) +#endif #ifndef PIPE_BUF -# define PIPE_BUF 4096 /* amount of buffering in a pipe */ +# define PIPE_BUF 4096 /* amount of buffering in a pipe */ #endif -/* Debug build knobs */ +/* Build knobs */ #define LEAK_HUNTING 0 #define BUILD_AS_NOMMU 0 /* Enable/disable sanity checks. Ok to enable in production, @@ -82,6 +104,13 @@ * Keeping 1 for now even in released versions. */ #define HUSH_DEBUG 1 +/* Slightly bigger (+200 bytes), but faster hush. + * So far it only enables a trick with counting SIGCHLDs and forks, + * which allows us to do fewer waitpid's. + * (we can detect a case where neither forks were done nor SIGCHLDs happened + * and therefore waitpid will return the same result as last time) + */ +#define ENABLE_HUSH_FAST 0 #if BUILD_AS_NOMMU @@ -97,11 +126,10 @@ /* STANDALONE does not make sense, and won't compile */ # undef CONFIG_FEATURE_SH_STANDALONE # undef ENABLE_FEATURE_SH_STANDALONE -# undef USE_FEATURE_SH_STANDALONE -# define SKIP_FEATURE_SH_STANDALONE(...) __VA_ARGS__ +# undef IF_FEATURE_SH_STANDALONE +# define IF_FEATURE_SH_STANDALONE(...) +# define IF_NOT_FEATURE_SH_STANDALONE(...) __VA_ARGS__ # define ENABLE_FEATURE_SH_STANDALONE 0 -# define USE_FEATURE_SH_STANDALONE(...) -# define SKIP_FEATURE_SH_STANDALONE(...) __VA_ARGS__ #endif #if !ENABLE_HUSH_INTERACTIVE @@ -143,6 +171,8 @@ #define SPECIAL_VAR_SYMBOL 3 +struct variable; + static const char hush_version_str[] ALIGN1 = "HUSH_VERSION="BB_VER; /* This supports saving pointers malloced in vfork child, @@ -151,29 +181,12 @@ static const char hush_version_str[] ALIGN1 = "HUSH_VERSION="BB_VER; #if !BB_MMU typedef struct nommu_save_t { char **new_env; - char **old_env; + struct variable *old_vars; char **argv; char **argv_from_re_execing; } nommu_save_t; #endif -/* 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, 0, "<<" }, - { O_CREAT|O_RDWR, 1, "<>" }, -/* Should not be needed. Bogus default_fd helps in debugging */ -/* { O_RDONLY, 77, "<<" }, */ -}; - typedef enum reserved_style { RES_NONE = 0, #if ENABLE_HUSH_IF @@ -195,9 +208,10 @@ typedef enum reserved_style { #endif #if ENABLE_HUSH_CASE RES_CASE , - /* two pseudo-keywords support contrived "case" syntax: */ - RES_MATCH , /* "word)" */ - RES_CASEI , /* "this command is inside CASE" */ + /* three pseudo-keywords support contrived "case" syntax: */ + RES_CASE_IN, /* "case ... IN", turns into RES_MATCH when IN is observed */ + RES_MATCH , /* "word)" */ + RES_CASE_BODY, /* "this command is inside CASE" */ RES_ESAC , #endif RES_XXXX , @@ -239,12 +253,28 @@ typedef struct in_str { smallint promptmode; /* 0: PS1, 1: PS2 */ #endif FILE *file; - int (*get) (struct in_str *); - int (*peek) (struct in_str *); + int (*get) (struct in_str *) FAST_FUNC; + int (*peek) (struct in_str *) FAST_FUNC; } in_str; #define i_getch(input) ((input)->get(input)) #define i_peek(input) ((input)->peek(input)) +/* 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[] = { + { O_RDONLY, 0, "<" }, + { O_CREAT|O_TRUNC|O_WRONLY, 1, ">" }, + { O_CREAT|O_APPEND|O_WRONLY, 1, ">>" }, + { O_CREAT|O_RDWR, 1, "<>" }, + { O_RDONLY, 0, "<<" }, +/* Should not be needed. Bogus default_fd helps in debugging */ +/* { O_RDONLY, 77, "<<" }, */ +}; + struct redir_struct { struct redir_struct *next; char *rd_filename; /* filename */ @@ -254,18 +284,17 @@ struct redir_struct { smallint rd_type; /* (enum redir_type) */ /* note: for heredocs, rd_filename contains heredoc delimiter, * and subsequently heredoc itself; and rd_dup is a bitmask: - * 1: do we need to trim leading tabs? - * 2: is heredoc quoted (<<'delim' syntax) ? + * bit 0: do we need to trim leading tabs? + * bit 1: is heredoc quoted (<<'delim' syntax) ? */ }; typedef enum redir_type { - REDIRECT_INVALID = 0, - REDIRECT_INPUT = 1, - REDIRECT_OVERWRITE = 2, - REDIRECT_APPEND = 3, + REDIRECT_INPUT = 0, + REDIRECT_OVERWRITE = 1, + REDIRECT_APPEND = 2, + REDIRECT_IO = 3, REDIRECT_HEREDOC = 4, - REDIRECT_IO = 5, - REDIRECT_HEREDOC2 = 6, /* REDIRECT_HEREDOC after heredoc is loaded */ + REDIRECT_HEREDOC2 = 5, /* REDIRECT_HEREDOC after heredoc is loaded */ REDIRFD_CLOSE = -3, REDIRFD_SYNTAX_ERR = -2, @@ -281,25 +310,40 @@ 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; /* GRP_xxx */ -#define GRP_NORMAL 0 -#define GRP_SUBSHELL 1 + smallint cmd_type; /* CMD_xxx */ +#define CMD_NORMAL 0 +#define CMD_SUBSHELL 1 + +/* used for "[[ EXPR ]]" */ +#if ENABLE_HUSH_BASH_COMPAT +# define CMD_SINGLEWORD_NOGLOB 2 +#endif + +/* used for "export noglob=* glob* a=`echo a b`" */ +//#define CMD_SINGLEWORD_NOGLOB_COND 3 +// It is hard to implement correctly, it adds significant amounts of tricky code, +// and all this is only useful for really obscure export statements +// almost nobody would use anyway. #ifdef CMD_SINGLEWORD_NOGLOB_COND +// guards the code which implements it, but I have doubts it works +// in all cases (especially with mixed globbed/non-globbed arguments) + #if ENABLE_HUSH_FUNCTIONS -# define GRP_FUNCTION 2 +# define CMD_FUNCDEF 3 #endif - struct pipe *group; /* if non-NULL, this "command" is { list }, - * ( list ), or a compound statement */ + + /* if non-NULL, this "command" is { list }, ( list ), or a compound statement */ + struct pipe *group; #if !BB_MMU char *group_as_string; #endif #if ENABLE_HUSH_FUNCTIONS struct function *child_func; /* This field is used to prevent a bug here: - * while...do f1() {a;}; f1; f1 {b;}; f1; done + * while...do f1() {a;}; f1; f1() {b;}; f1; done * When we execute "f1() {a;}" cmd, we create new function and clear * cmd->group, cmd->group_as_string, cmd->argv[0]. - * when we execute "f1 {b;}", we notice that f1 exists, - * and that it's "parent cmd" struct is still "alive", + * When we execute "f1() {b;}", we notice that f1 exists, + * and that its "parent cmd" struct is still "alive", * we put those fields back into cmd->xxx * (struct function has ->parent_cmd ptr to facilitate that). * When we loop back, we can execute "f1() {a;}" again and set f1 correctly. @@ -391,6 +435,9 @@ struct parse_context { struct variable { struct variable *next; char *varstr; /* points to "name=" portion */ +#if ENABLE_HUSH_LOCAL + unsigned func_nest_level; +#endif 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; @@ -407,9 +454,9 @@ struct function { char *name; struct command *parent_cmd; struct pipe *body; -#if !BB_MMU +# if !BB_MMU char *body_as_string; -#endif +# endif }; #endif @@ -417,27 +464,44 @@ struct function { /* "Globals" within this file */ /* Sorted roughly by size (smaller offsets == smaller code) */ struct globals { + /* interactive_fd != 0 means we are an interactive shell. + * If we are, then saved_tty_pgrp can also be != 0, meaning + * that controlling tty is available. With saved_tty_pgrp == 0, + * job control still works, but terminal signals + * (^C, ^Z, ^Y, ^\) won't work at all, and background + * process groups can only be created with "cmd &". + * With saved_tty_pgrp != 0, hush will use tcsetpgrp() + * to give tty to the foreground process group, + * and will take it back when the group is stopped (^Z) + * or killed (^C). + */ #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; -#define G_interactive_fd (G.interactive_fd) +# define G_interactive_fd (G.interactive_fd) #else -#define G_interactive_fd 0 +# define G_interactive_fd 0 #endif #if ENABLE_FEATURE_EDITING line_input_t *line_input_state; #endif pid_t root_pid; + pid_t root_ppid; pid_t last_bg_pid; +#if ENABLE_HUSH_RANDOM_SUPPORT + random_t random_gen; +#endif #if ENABLE_HUSH_JOB int run_list_level; - pid_t saved_tty_pgrp; int last_jobid; + pid_t saved_tty_pgrp; struct pipe *job_list; - struct pipe *toplevel_list; +# define G_saved_tty_pgrp (G.saved_tty_pgrp) +#else +# define G_saved_tty_pgrp 0 #endif smallint flag_SIGINT; #if ENABLE_HUSH_LOOPS @@ -446,7 +510,7 @@ struct globals { #if ENABLE_HUSH_FUNCTIONS /* 0: outside of a function (or sourced file) * -1: inside of a function, ok to use return builtin - * 1: return is invoked, skip all till end of func. + * 1: return is invoked, skip all till end of func */ smallint flag_return_in_progress; #endif @@ -472,10 +536,17 @@ struct globals { struct variable shell_ver; #if ENABLE_HUSH_FUNCTIONS struct function *top_func; +# if ENABLE_HUSH_LOCAL + struct variable **shadowed_vars_pp; + unsigned func_nest_level; +# endif #endif /* Signal and trap handling */ -// unsigned count_SIGCHLD; -// unsigned handled_SIGCHLD; +#if ENABLE_HUSH_FAST + unsigned count_SIGCHLD; + unsigned handled_SIGCHLD; + smallint we_have_children; +#endif /* which signals have non-DFL handler (even with no traps set)? */ unsigned non_DFL_mask; char **traps; /* char *traps[NSIG] */ @@ -485,7 +556,7 @@ struct globals { unsigned long memleak_value; int debug_indent; #endif - char user_input_buf[ENABLE_FEATURE_EDITING ? BUFSIZ : 2]; + char user_input_buf[ENABLE_FEATURE_EDITING ? CONFIG_FEATURE_EDITING_MAX_LEN : 2]; }; #define G (*ptr_to_globals) /* Not #defining name to G.name - this quickly gets unwieldy @@ -497,39 +568,46 @@ struct globals { /* 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); +static int builtin_cd(char **argv) FAST_FUNC; +static int builtin_echo(char **argv) FAST_FUNC; +static int builtin_eval(char **argv) FAST_FUNC; +static int builtin_exec(char **argv) FAST_FUNC; +static int builtin_exit(char **argv) FAST_FUNC; +static int builtin_export(char **argv) FAST_FUNC; #if ENABLE_HUSH_JOB -static int builtin_fg_bg(char **argv); -static int builtin_jobs(char **argv); +static int builtin_fg_bg(char **argv) FAST_FUNC; +static int builtin_jobs(char **argv) FAST_FUNC; #endif #if ENABLE_HUSH_HELP -static int builtin_help(char **argv); +static int builtin_help(char **argv) FAST_FUNC; +#endif +#if ENABLE_HUSH_LOCAL +static int builtin_local(char **argv) FAST_FUNC; #endif #if HUSH_DEBUG -static int builtin_memleak(char **argv); -#endif -static int builtin_pwd(char **argv); -static int builtin_read(char **argv); -static int builtin_set(char **argv); -static int builtin_shift(char **argv); -static int builtin_source(char **argv); -static int builtin_test(char **argv); -static int builtin_trap(char **argv); -static int builtin_true(char **argv); -static int builtin_umask(char **argv); -static int builtin_unset(char **argv); -static int builtin_wait(char **argv); +static int builtin_memleak(char **argv) FAST_FUNC; +#endif +#if ENABLE_PRINTF +static int builtin_printf(char **argv) FAST_FUNC; +#endif +static int builtin_pwd(char **argv) FAST_FUNC; +static int builtin_read(char **argv) FAST_FUNC; +static int builtin_set(char **argv) FAST_FUNC; +static int builtin_shift(char **argv) FAST_FUNC; +static int builtin_source(char **argv) FAST_FUNC; +static int builtin_test(char **argv) FAST_FUNC; +static int builtin_trap(char **argv) FAST_FUNC; +static int builtin_type(char **argv) FAST_FUNC; +static int builtin_true(char **argv) FAST_FUNC; +static int builtin_umask(char **argv) FAST_FUNC; +static int builtin_unset(char **argv) FAST_FUNC; +static int builtin_wait(char **argv) FAST_FUNC; #if ENABLE_HUSH_LOOPS -static int builtin_break(char **argv); -static int builtin_continue(char **argv); +static int builtin_break(char **argv) FAST_FUNC; +static int builtin_continue(char **argv) FAST_FUNC; #endif #if ENABLE_HUSH_FUNCTIONS -static int builtin_return(char **argv); +static int builtin_return(char **argv) FAST_FUNC; #endif /* Table of built-in functions. They can be forked or not, depending on @@ -540,61 +618,70 @@ static int builtin_return(char **argv); * still be set at the end. */ struct built_in_command { const char *cmd; - int (*function)(char **argv); + int (*function)(char **argv) FAST_FUNC; #if ENABLE_HUSH_HELP const char *descr; -#define BLTIN(cmd, func, help) { cmd, func, help } +# define BLTIN(cmd, func, help) { cmd, func, help } #else -#define BLTIN(cmd, func, help) { cmd, func } +# 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"), +static const struct built_in_command bltins1[] = { + BLTIN("." , builtin_source , "Run commands in a file"), + BLTIN(":" , builtin_true , NULL), #if ENABLE_HUSH_JOB - BLTIN("bg" , builtin_fg_bg , "Resume a job in the background"), + BLTIN("bg" , builtin_fg_bg , "Resume a job in the background"), #endif #if ENABLE_HUSH_LOOPS - BLTIN("break" , builtin_break , "Exit from a loop"), + BLTIN("break" , builtin_break , "Exit from a loop"), #endif - BLTIN("cd" , builtin_cd , "Change directory"), + BLTIN("cd" , builtin_cd , "Change directory"), #if ENABLE_HUSH_LOOPS - BLTIN("continue", builtin_continue, "Start new loop iteration"), + 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"), + 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 variables"), #if ENABLE_HUSH_JOB - BLTIN("fg" , builtin_fg_bg , "Bring job into the foreground"), + BLTIN("fg" , builtin_fg_bg , "Bring job into the foreground"), #endif #if ENABLE_HUSH_HELP - BLTIN("help" , builtin_help , "List shell built-in commands"), + BLTIN("help" , builtin_help , NULL), #endif #if ENABLE_HUSH_JOB - BLTIN("jobs" , builtin_jobs , "List active jobs"), + BLTIN("jobs" , builtin_jobs , "List jobs"), +#endif +#if ENABLE_HUSH_LOCAL + BLTIN("local" , builtin_local , "Set local variables"), #endif #if HUSH_DEBUG - BLTIN("memleak" , builtin_memleak , "Debug tool"), + BLTIN("memleak" , builtin_memleak , NULL), #endif - BLTIN("pwd" , builtin_pwd , "Print current directory"), - BLTIN("read" , builtin_read , "Input environment variable"), + BLTIN("read" , builtin_read , "Input into variable"), #if ENABLE_HUSH_FUNCTIONS - BLTIN("return" , builtin_return , "Return from a function"), -#endif - BLTIN("set" , builtin_set , "Set/unset shell local variables"), - BLTIN("shift" , builtin_shift , "Shift positional parameters"), - BLTIN("test" , builtin_test , "Test condition"), - BLTIN("trap" , builtin_trap , "Trap signals"), -// BLTIN("ulimit" , builtin_return , "Control resource limits"), - BLTIN("umask" , builtin_umask , "Set file creation mask"), - BLTIN("unset" , builtin_unset , "Unset environment variable"), - BLTIN("wait" , builtin_wait , "Wait for process"), + BLTIN("return" , builtin_return , "Return from a function"), +#endif + BLTIN("set" , builtin_set , "Set/unset positional parameters"), + BLTIN("shift" , builtin_shift , "Shift positional parameters"), + BLTIN("trap" , builtin_trap , "Trap signals"), + BLTIN("type" , builtin_type , "Write a description of command type"), +// BLTIN("ulimit" , builtin_ulimit , "Control resource limits"), + BLTIN("umask" , builtin_umask , "Set file creation mask"), + BLTIN("unset" , builtin_unset , "Unset variables"), + BLTIN("wait" , builtin_wait , "Wait for process"), +}; +/* For now, echo and test are unconditionally enabled. + * Maybe make it configurable? */ +static const struct built_in_command bltins2[] = { + BLTIN("[" , builtin_test , NULL), + BLTIN("echo" , builtin_echo , NULL), +#if ENABLE_PRINTF + BLTIN("printf" , builtin_printf , NULL), +#endif + BLTIN("pwd" , builtin_pwd , NULL), + BLTIN("test" , builtin_test , NULL), }; @@ -606,7 +693,7 @@ static const struct built_in_command bltins[] = { # define debug_enter() (G.debug_indent++) # define debug_leave() (G.debug_indent--) #else -# define indent() ((void)0) +# define indent() ((void)0) # define debug_enter() ((void)0) # define debug_leave() ((void)0) #endif @@ -672,7 +759,7 @@ static void debug_print_strings(const char *prefix, char **vv) fprintf(stderr, " '%s'\n", *vv++); } #else -#define debug_print_strings(prefix, vv) ((void)0) +# define debug_print_strings(prefix, vv) ((void)0) #endif @@ -715,7 +802,7 @@ static void xxfree(void *ptr) * HUSH_DEBUG >= 2 prints line number in this file where it was detected. */ #if HUSH_DEBUG < 2 -# define die_if_script(lineno, fmt...) die_if_script(fmt) +# define die_if_script(lineno, ...) die_if_script(__VA_ARGS__) # define syntax_error(lineno, msg) syntax_error(msg) # define syntax_error_at(lineno, msg) syntax_error_at(msg) # define syntax_error_unterm_ch(lineno, ch) syntax_error_unterm_ch(ch) @@ -750,6 +837,11 @@ static void syntax_error_at(unsigned lineno, const char *msg) die_if_script(lineno, "syntax error at '%s'", msg); } +static void syntax_error_unterm_str(unsigned lineno, const char *s) +{ + die_if_script(lineno, "syntax error: unterminated %s", s); +} + /* It so happens that all such cases are totally fatal * even if shell is interactive: EOF while looking for closing * delimiter. There is nowhere to read stuff from after that, @@ -758,24 +850,17 @@ static void syntax_error_at(unsigned lineno, const char *msg) static void syntax_error_unterm_ch(unsigned lineno, char ch) NORETURN; static void syntax_error_unterm_ch(unsigned lineno, char ch) { - char msg[2]; - msg[0] = ch; - msg[1] = '\0'; - die_if_script(lineno, "syntax error: unterminated %s", msg); + char msg[2] = { ch, '\0' }; + syntax_error_unterm_str(lineno, msg); xfunc_die(); } -static void syntax_error_unterm_str(unsigned lineno, const char *s) -{ - die_if_script(lineno, "syntax error: unterminated %s", s); -} - -static void syntax_error_unexpected_ch(unsigned lineno, char ch) +static void syntax_error_unexpected_ch(unsigned lineno, int ch) { char msg[2]; msg[0] = ch; msg[1] = '\0'; - die_if_script(lineno, "syntax error: unexpected %s", msg); + die_if_script(lineno, "syntax error: unexpected %s", ch == EOF ? "EOF" : msg); } #if HUSH_DEBUG < 2 @@ -786,7 +871,7 @@ static void syntax_error_unexpected_ch(unsigned lineno, char ch) # undef syntax_error_unterm_str # undef syntax_error_unexpected_ch #else -# define die_if_script(fmt...) die_if_script(__LINE__, fmt) +# define die_if_script(...) die_if_script(__LINE__, __VA_ARGS__) # define syntax_error(msg) syntax_error(__LINE__, msg) # define syntax_error_at(msg) syntax_error_at(__LINE__, msg) # define syntax_error_unterm_ch(ch) syntax_error_unterm_ch(__LINE__, ch) @@ -795,6 +880,13 @@ static void syntax_error_unexpected_ch(unsigned lineno, char ch) #endif +#if ENABLE_HUSH_INTERACTIVE +static void cmdedit_update_prompt(void); +#else +# define cmdedit_update_prompt() ((void)0) +#endif + + /* Utility functions */ static int glob_needed(const char *s) @@ -822,7 +914,7 @@ static int is_well_formed_var_name(const char *s, char terminator) /* Replace each \x with x in place, return ptr past NUL. */ static char *unbackslash(char *src) { - char *dst = src; + char *dst = src = strchrnul(src, '\\'); while (1) { if (*src == '\\') src++; @@ -871,6 +963,7 @@ static char **xx_add_strings_to_strings(int lineno, char **strings, char **add, xx_add_strings_to_strings(__LINE__, strings, add, need_to_dup) #endif +/* Note: takes ownership of "add" ptr (it is not strdup'ed) */ static char **add_string_to_strings(char **strings, char *add) { char *v[2]; @@ -889,66 +982,20 @@ static char **xx_add_string_to_strings(int lineno, char **strings, char *add) xx_add_string_to_strings(__LINE__, strings, add) #endif -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) +static void free_strings(char **strings) { char **v; if (!strings) return; - v = strings; while (*v) { - if (unset) { - debug_printf_env("unsetenv '%s'\n", *v); - bb_unsetenv(*v); - } - free(*v++); + free(*v); + v++; } free(strings); } -static void free_strings(char **strings) -{ - free_strings_and_unsetenv(strings, 0); -} - /* Helpers for setting new $n and restoring them back */ @@ -1006,7 +1053,7 @@ static void restore_G_args(save_arg_t *sv, char **argv) * is finished or backgrounded. It is the same in interactive and * non-interactive shells, and is the same regardless of whether * a user trap handler is installed or a shell special one is in effect. - * ^C or ^Z from keyboard seem to execute "at once" because it usually + * ^C or ^Z from keyboard seems to execute "at once" because it usually * backgrounds (i.e. stops) or kills all members of currently running * pipe. * @@ -1020,13 +1067,13 @@ static void restore_G_args(save_arg_t *sv, char **argv) * If job control is off, backgrounded commands ("cmd &") * have SIGINT, SIGQUIT set to SIG_IGN. * - * Commands run in command substitution ("`cmd`") + * Commands which are run in command substitution ("`cmd`") * have SIGTTIN, SIGTTOU, SIGTSTP set to SIG_IGN. * - * Ordinary commands have signals set to SIG_IGN/DFL set as inherited + * Ordinary commands have signals set to SIG_IGN/DFL as inherited * by the shell from its parent. * - * Siganls which differ from SIG_DFL action + * Signals which differ from SIG_DFL action * (note: child (i.e., [v]forked) shell is not an interactive shell): * * SIGQUIT: ignore @@ -1073,27 +1120,39 @@ static void restore_G_args(save_arg_t *sv, char **argv) * (child shell is not interactive), * unset all traps (note: regardless of child shell's type - {}, (), etc) * after [v]fork, if we plan to exec: - * POSIX says pending signal mask is cleared in child - no need to clear it. + * POSIX says fork clears pending signal mask in child - no need to clear it. * Restore blocked signal set to one inherited by shell just prior to exec. * * Note: as a result, we do not use signal handlers much. The only uses - * are to count SIGCHLDs [disabled - bug somewhere, + bloat] + * are to count SIGCHLDs * and to restore tty pgrp on signal-induced exit. + * + * Note 2 (compat): + * Standard says "When a subshell is entered, traps that are not being ignored + * are set to the default actions". bash interprets it so that traps which + * are set to "" (ignore) are NOT reset to defaults. We do the same. */ enum { SPECIAL_INTERACTIVE_SIGS = 0 -#if ENABLE_HUSH_JOB - | (1 << SIGTTIN) | (1 << SIGTTOU) | (1 << SIGTSTP) - | (1 << SIGHUP) -#endif | (1 << SIGTERM) | (1 << SIGINT) + | (1 << SIGHUP) + , + SPECIAL_JOB_SIGS = 0 +#if ENABLE_HUSH_JOB + | (1 << SIGTTIN) + | (1 << SIGTTOU) + | (1 << SIGTSTP) +#endif }; -//static void SIGCHLD_handler(int sig UNUSED_PARAM) -//{ -// G.count_SIGCHLD++; -//} +#if ENABLE_HUSH_FAST +static void SIGCHLD_handler(int sig UNUSED_PARAM) +{ + G.count_SIGCHLD++; +//bb_error_msg("[%d] SIGCHLD_handler: G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD); +} +#endif #if ENABLE_HUSH_JOB @@ -1115,8 +1174,8 @@ static void sigexit(int sig) /* Careful: we can end up here after [v]fork. Do not restore * tty pgrp then, only top-level shell process does that */ - if (G_interactive_fd && getpid() == G.root_pid) - tcsetpgrp(G_interactive_fd, G.saved_tty_pgrp); + if (G_saved_tty_pgrp && getpid() == G.root_pid) + tcsetpgrp(G_interactive_fd, G_saved_tty_pgrp); /* Not a signal, just exit */ if (sig <= 0) @@ -1181,9 +1240,12 @@ static int check_and_run_traps(int sig) } /* not a trap: special action */ switch (sig) { -// case SIGCHLD: -// G.count_SIGCHLD++; -// break; +#if ENABLE_HUSH_FAST + case SIGCHLD: + G.count_SIGCHLD++; +//bb_error_msg("[%d] check_and_run_traps: G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD); + break; +#endif case SIGINT: /* Builtin was ^C'ed, make it look prettier: */ bb_putchar('\n'); @@ -1213,40 +1275,61 @@ static int check_and_run_traps(int sig) } -static const char *set_cwd(void) +static const char *get_cwd(int force) { - /* xrealloc_getcwd_or_warn(arg) calls free(arg), - * we must not try to free(bb_msg_unknown) */ - if (G.cwd == bb_msg_unknown) - G.cwd = NULL; - G.cwd = xrealloc_getcwd_or_warn((char *)G.cwd); - if (!G.cwd) - G.cwd = bb_msg_unknown; + if (force || G.cwd == NULL) { + /* xrealloc_getcwd_or_warn(arg) calls free(arg), + * we must not try to free(bb_msg_unknown) */ + if (G.cwd == bb_msg_unknown) + G.cwd = NULL; + G.cwd = xrealloc_getcwd_or_warn((char *)G.cwd); + if (!G.cwd) + G.cwd = bb_msg_unknown; + } return G.cwd; } -/* Get/check local shell variables */ -static struct variable *get_local_var(const char *name) +/* + * Shell and environment variable support + */ +static struct variable **get_ptr_to_local_var(const char *name) { + struct variable **pp; struct variable *cur; int len; - if (!name) - return NULL; len = strlen(name); - for (cur = G.top_var; cur; cur = cur->next) { + pp = &G.top_var; + while ((cur = *pp) != NULL) { if (strncmp(cur->varstr, name, len) == 0 && cur->varstr[len] == '=') - return cur; + return pp; + pp = &cur->next; } return NULL; } -static const char *get_local_var_value(const char *src) +static struct variable *get_local_var(const char *name) { - struct variable *var = get_local_var(src); - if (var) - return strchr(var->varstr, '=') + 1; + struct variable **pp = get_ptr_to_local_var(name); + if (pp) + return *pp; + return NULL; +} + +static const char *get_local_var_value(const char *name) +{ + struct variable **pp = get_ptr_to_local_var(name); + if (pp) + return strchr((*pp)->varstr, '=') + 1; + if (strcmp(name, "PPID") == 0) + return utoa(G.root_ppid); + // bash compat: UID? EUID? +#if ENABLE_HUSH_RANDOM_SUPPORT + if (strcmp(name, "RANDOM") == 0) { + return utoa(next_random(&G.random_gen)); + } +#endif return NULL; } @@ -1259,36 +1342,39 @@ static const char *get_local_var_value(const char *src) * -1: clear export flag and unsetenv the variable * flg_read_only is set only when we handle -R var=val */ -#if BB_MMU -#define set_local_var(str, flg_export, flg_read_only) \ +#if !BB_MMU && ENABLE_HUSH_LOCAL +/* all params are used */ +#elif BB_MMU && ENABLE_HUSH_LOCAL +#define set_local_var(str, flg_export, local_lvl, flg_read_only) \ + set_local_var(str, flg_export, local_lvl) +#elif BB_MMU && !ENABLE_HUSH_LOCAL +#define set_local_var(str, flg_export, local_lvl, flg_read_only) \ set_local_var(str, flg_export) +#elif !BB_MMU && !ENABLE_HUSH_LOCAL +#define set_local_var(str, flg_export, local_lvl, flg_read_only) \ + set_local_var(str, flg_export, flg_read_only) #endif -static int set_local_var(char *str, int flg_export, int flg_read_only) +static int set_local_var(char *str, int flg_export, int local_lvl, int flg_read_only) { + struct variable **var_pp; struct variable *cur; - char *value; + char *eq_sign; int name_len; - value = strchr(str, '='); - if (!value) { /* not expected to ever happen? */ + eq_sign = strchr(str, '='); + if (!eq_sign) { /* 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) { + name_len = eq_sign - str + 1; /* including '=' */ + var_pp = &G.top_var; + while ((cur = *var_pp) != NULL) { 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; + var_pp = &cur->next; continue; } /* We found an existing var with this name */ - *value = '\0'; if (cur->flg_read_only) { #if !BB_MMU if (!flg_read_only) @@ -1297,31 +1383,61 @@ static int set_local_var(char *str, int flg_export, int flg_read_only) free(str); return -1; } -//TODO: optimize out redundant unsetenv/putenv's? - debug_printf_env("%s: unsetenv '%s'\n", __func__, str); - unsetenv(str); /* just in case */ - *value = '='; - if (strcmp(cur->varstr, str) == 0) { + if (flg_export == -1) { // "&& cur->flg_export" ? + debug_printf_env("%s: unsetenv '%s'\n", __func__, str); + *eq_sign = '\0'; + unsetenv(str); + *eq_sign = '='; + } +#if ENABLE_HUSH_LOCAL + if (cur->func_nest_level < local_lvl) { + /* New variable is declared as local, + * and existing one is global, or local + * from enclosing function. + * Remove and save old one: */ + *var_pp = cur->next; + cur->next = *G.shadowed_vars_pp; + *G.shadowed_vars_pp = cur; + /* bash 3.2.33(1) and exported vars: + * # export z=z + * # f() { local z=a; env | grep ^z; } + * # f + * z=a + * # env | grep ^z + * z=z + */ + if (cur->flg_export) + flg_export = 1; + break; + } +#endif + if (strcmp(cur->varstr + name_len, eq_sign + 1) == 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) + if (cur->max_len != 0) { + if (cur->max_len >= strlen(str)) { + /* This one is from startup env, reuse space */ + strcpy(cur->varstr, str); + goto free_and_exp; + } + } else { + /* max_len == 0 signifies "malloced" var, which we can + * (and has to) free */ 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; + /* Not found - create new variable struct */ + cur = xzalloc(sizeof(*cur)); +#if ENABLE_HUSH_LOCAL + cur->func_nest_level = local_lvl; +#endif + cur->next = *var_pp; + *var_pp = cur; set_str_and_exp: cur->varstr = str; @@ -1331,6 +1447,8 @@ static int set_local_var(char *str, int flg_export, int flg_read_only) exp: if (flg_export == 1) cur->flg_export = 1; + if (name_len == 4 && cur->varstr[0] == 'P' && cur->varstr[1] == 'S') + cmdedit_update_prompt(); if (cur->flg_export) { if (flg_export == -1) { cur->flg_export = 0; @@ -1343,38 +1461,62 @@ static int set_local_var(char *str, int flg_export, int flg_read_only) return 0; } -static int unset_local_var(const char *name) +/* Used at startup and after each cd */ +static void set_pwd_var(int exp) +{ + set_local_var(xasprintf("PWD=%s", get_cwd(/*force:*/ 1)), + /*exp:*/ exp, /*lvl:*/ 0, /*ro:*/ 0); +} + +static int unset_local_var_len(const char *name, int name_len) { struct variable *cur; - struct variable *prev = prev; /* for gcc */ - int name_len; + struct variable **var_pp; if (!name) return EXIT_SUCCESS; - name_len = strlen(name); - cur = G.top_var; - while (cur) { + var_pp = &G.top_var; + while ((cur = *var_pp) != NULL) { 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 EXIT_FAILURE; } - /* 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; + *var_pp = cur->next; debug_printf_env("%s: unsetenv '%s'\n", __func__, cur->varstr); bb_unsetenv(cur->varstr); + if (name_len == 3 && cur->varstr[0] == 'P' && cur->varstr[1] == 'S') + cmdedit_update_prompt(); if (!cur->max_len) free(cur->varstr); free(cur); return EXIT_SUCCESS; } - prev = cur; - cur = cur->next; + var_pp = &cur->next; } return EXIT_SUCCESS; } +static int unset_local_var(const char *name) +{ + return unset_local_var_len(name, strlen(name)); +} + +static void unset_vars(char **strings) +{ + char **v; + + if (!strings) + return; + v = strings; + while (*v) { + const char *eq = strchrnul(*v, '='); + unset_local_var_len(*v, (int)(eq - *v)); + v++; + } + free(strings); +} + #if ENABLE_SH_MATH_SUPPORT #define is_name(c) ((c) == '_' || isalpha((unsigned char)(c))) #define is_in_name(c) ((c) == '_' || isalnum((unsigned char)(c))) @@ -1396,15 +1538,71 @@ static void arith_set_local_var(const char *name, const char *val, int flags) { /* arith code doesnt malloc space, so do it for it */ char *var = xasprintf("%s=%s", name, val); - set_local_var(var, flags, 0); + set_local_var(var, flags, /*lvl:*/ 0, /*ro:*/ 0); } #endif +/* + * Helpers for "var1=val1 var2=val2 cmd" feature + */ +static void add_vars(struct variable *var) +{ + struct variable *next; + + while (var) { + next = var->next; + var->next = G.top_var; + G.top_var = var; + if (var->flg_export) { + debug_printf_env("%s: restoring exported '%s'\n", __func__, var->varstr); + putenv(var->varstr); + } else { + debug_printf_env("%s: restoring variable '%s'\n", __func__, var->varstr); + } + var = next; + } +} + +static struct variable *set_vars_and_save_old(char **strings) +{ + char **s; + struct variable *old = NULL; + + if (!strings) + return old; + s = strings; + while (*s) { + struct variable *var_p; + struct variable **var_pp; + char *eq; + + eq = strchr(*s, '='); + if (eq) { + *eq = '\0'; + var_pp = get_ptr_to_local_var(*s); + *eq = '='; + if (var_pp) { + /* Remove variable from global linked list */ + var_p = *var_pp; + debug_printf_env("%s: removing '%s'\n", __func__, var_p->varstr); + *var_pp = var_p->next; + /* Add it to returned list */ + var_p->next = old; + old = var_p; + } + set_local_var(*s, /*exp:*/ 1, /*lvl:*/ 0, /*ro:*/ 0); + } + s++; + } + return old; +} + + /* * in_str support */ -static int static_get(struct in_str *i) +static int FAST_FUNC static_get(struct in_str *i) { int ch = *i->p++; if (ch != '\0') @@ -1413,21 +1611,25 @@ static int static_get(struct in_str *i) return EOF; } -static int static_peek(struct in_str *i) +static int FAST_FUNC static_peek(struct in_str *i) { return *i->p; } #if ENABLE_HUSH_INTERACTIVE -static void cmdedit_set_initial_prompt(void) +static void cmdedit_update_prompt(void) { if (ENABLE_FEATURE_EDITING_FANCY_PROMPT) { - G.PS1 = getenv("PS1"); + G.PS1 = get_local_var_value("PS1"); if (G.PS1 == NULL) G.PS1 = "\\w \\$ "; - } else + G.PS2 = get_local_var_value("PS2"); + } else { G.PS1 = NULL; + } + if (G.PS2 == NULL) + G.PS2 = "> "; } static const char* setup_prompt_string(int promptmode) @@ -1438,7 +1640,10 @@ static const char* setup_prompt_string(int promptmode) /* Set up the prompt */ if (promptmode == 0) { /* PS1 */ free((char*)G.PS1); - G.PS1 = xasprintf("%s %c ", G.cwd, (geteuid() != 0) ? '$' : '#'); + /* bash uses $PWD value, even if it is set by user. + * It uses current dir only if PWD is unset. + * We always use current dir. */ + G.PS1 = xasprintf("%s %c ", get_cwd(0), (geteuid() != 0) ? '$' : '#'); prompt_str = G.PS1; } else prompt_str = G.PS2; @@ -1461,7 +1666,7 @@ static void get_user_input(struct in_str *i) G.flag_SIGINT = 0; /* buglet: SIGINT will not make new prompt to appear _at once_, * only after . (^C will work) */ - r = read_line_input(prompt_str, G.user_input_buf, BUFSIZ-1, G.line_input_state); + r = read_line_input(prompt_str, G.user_input_buf, CONFIG_FEATURE_EDITING_MAX_LEN-1, G.line_input_state); /* catch *SIGINT* etc (^C is handled by read_line_input) */ check_and_run_traps(0); } while (r == 0 || G.flag_SIGINT); /* repeat if ^C or SIGINT */ @@ -1488,7 +1693,7 @@ static void get_user_input(struct in_str *i) /* This is the magic location that prints prompts * and gets data back from the user */ -static int file_get(struct in_str *i) +static int FAST_FUNC file_get(struct in_str *i) { int ch; @@ -1527,7 +1732,7 @@ static int file_get(struct in_str *i) /* All 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) +static int FAST_FUNC file_peek(struct in_str *i) { int ch; if (i->p && *i->p) { @@ -1630,7 +1835,7 @@ static void nommu_addchr(o_string *o, int ch) o_addchr(o, ch); } #else -#define nommu_addchr(o, str) ((void)0) +# define nommu_addchr(o, str) ((void)0) #endif static void o_addstr_with_NUL(o_string *o, const char *str) @@ -1750,7 +1955,7 @@ static void debug_print_list(const char *prefix, o_string *o, int n) } } #else -#define debug_print_list(prefix, o, n) ((void)0) +# 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 @@ -1969,26 +2174,27 @@ static char *expand_pseudo_dquoted(const char *str) * 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) +static NOINLINE 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 *dyn_val, *p; + /* 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 ored_ch; + char *p; - dyn_val = NULL; ored_ch = 0; - debug_printf_expand("expand_vars_to_list: arg '%s'\n", arg); + debug_printf_expand("expand_vars_to_list: arg:'%s' or_mask:%x\n", arg, or_mask); 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) { + char first_ch; + int i; + char *dyn_val = NULL; + const char *val = NULL; #if ENABLE_HUSH_TICK o_string subst_result = NULL_O_STRING; #endif @@ -2006,7 +2212,6 @@ static int expand_vars_to_list(o_string *output, int n, char *arg, char or_mask) 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 */ @@ -2179,16 +2384,16 @@ static int expand_vars_to_list(o_string *output, int n, char *arg, char or_mask) if (exp_op == '%' || exp_op == '#') { if (val) { /* we need to do a pattern match */ - bool zero; + bool match_at_left; char *loc; - scan_t scan = pick_scan(exp_op, *exp_word, &zero); + scan_t scan = pick_scan(exp_op, *exp_word, &match_at_left); if (exp_op == *exp_word) /* ## or %% */ ++exp_word; val = dyn_val = xstrdup(val); - loc = scan(dyn_val, exp_word, zero); - if (zero) + loc = scan(dyn_val, exp_word, match_at_left); + if (match_at_left) /* # or ## */ val = loc; - else + else if (loc) /* % or %% and match was found */ *loc = '\0'; } } else { @@ -2220,7 +2425,7 @@ static int expand_vars_to_list(o_string *output, int n, char *arg, char or_mask) val = NULL; } else { char *new_var = xasprintf("%s=%s", var, val); - set_local_var(new_var, 0, 0); + set_local_var(new_var, /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ 0); } } } @@ -2248,11 +2453,11 @@ static int expand_vars_to_list(o_string *output, int n, char *arg, char or_mask) } } /* default: */ } /* switch (char after ) */ + if (val) { o_addQstr(output, val, strlen(val)); } free(dyn_val); - dyn_val = NULL; /* Do the check to avoid writing to a const string */ if (*p != SPECIAL_VAR_SYMBOL) *p = SPECIAL_VAR_SYMBOL; @@ -2297,7 +2502,7 @@ static char **expand_variables(char **argv, int or_mask) n = 0; v = argv; while (*v) { - n = expand_vars_to_list(&output, n, *v, (char)or_mask); + n = expand_vars_to_list(&output, n, *v, (unsigned char)or_mask); v++; } debug_print_list("expand_variables", &output, n); @@ -2313,6 +2518,48 @@ static char **expand_strvec_to_strvec(char **argv) return expand_variables(argv, 0x100); } +#if ENABLE_HUSH_BASH_COMPAT +static char **expand_strvec_to_strvec_singleword_noglob(char **argv) +{ + return expand_variables(argv, 0x80); +} +#endif + +#ifdef CMD_SINGLEWORD_NOGLOB_COND +static char **expand_strvec_to_strvec_singleword_noglob_cond(char **argv) +{ + int n; + char **list; + char **v; + o_string output = NULL_O_STRING; + + n = 0; + v = argv; + while (*v) { + int is_var = is_well_formed_var_name(*v, '='); + /* is_var * 0x80: singleword expansion for vars */ + n = expand_vars_to_list(&output, n, *v, is_var * 0x80); + + /* Subtle! expand_vars_to_list did not glob last word yet. + * It does this only when fed with further data. + * Therefore we set globbing flags AFTER it, not before: + */ + + /* if it is not recognizably abc=...; then: */ + output.o_escape = !is_var; /* protect against globbing for "$var" */ + /* (unquoted $var will temporarily switch it off) */ + output.o_glob = !is_var; /* and indeed do globbing */ + v++; + } + debug_print_list("expand_cond", &output, n); + + /* output.data (malloced in one block) gets returned in "list" */ + list = o_finalize_list(&output, n); + debug_print_strings("expand_cond[1]", list); + return list; +} +#endif + /* Used for expansion of right hand of assignments */ /* NB: should NOT do globbing! "export v=/bin/c*; env | grep ^v=" outputs * "v=/bin/c*" */ @@ -2322,12 +2569,13 @@ static char *expand_string_to_string(const char *str) argv[0] = (char*)str; argv[1] = NULL; - list = expand_variables(argv, 0x80); /* 0x80: make one-element expansion */ + list = expand_variables(argv, 0x80); /* 0x80: singleword 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 */ overlapping_strcpy((char*)list, list[0]); + unbackslash((char*)list); debug_printf_expand("string_to_string='%s'\n", (char*)list); return (char*)list; } @@ -2369,49 +2617,59 @@ static char **expand_assignments(char **argv, int count) #if BB_MMU /* never called */ -void re_execute_shell(char ***to_free, const char *s, char *argv0, char **argv); +void re_execute_shell(char ***to_free, const char *s, + char *g_argv0, char **g_argv, + char **builtin_argv) NORETURN; static void reset_traps_to_defaults(void) { /* This function is always called in a child shell * after fork (not vfork, NOMMU doesn't use this function). - * Child shells are not interactive. - * SIGTTIN/SIGTTOU/SIGTSTP should not have special handling. - * Testcase: (while :; do :; done) + ^Z should background. - * Same goes for SIGTERM, SIGHUP, SIGINT. */ unsigned sig; unsigned mask; + /* Child shells are not interactive. + * SIGTTIN/SIGTTOU/SIGTSTP should not have special handling. + * Testcase: (while :; do :; done) + ^Z should background. + * Same goes for SIGTERM, SIGHUP, SIGINT. + */ if (!G.traps && !(G.non_DFL_mask & SPECIAL_INTERACTIVE_SIGS)) - return; + return; /* already no traps and no SPECIAL_INTERACTIVE_SIGS */ - /* Stupid. It can be done with *single* &= op, but we can't use - * the fact that G.blocked_set is implemented as a bitmask... */ + /* Switching off SPECIAL_INTERACTIVE_SIGS. + * Stupid. It can be done with *single* &= op, but we can't use + * the fact that G.blocked_set is implemented as a bitmask + * in libc... */ mask = (SPECIAL_INTERACTIVE_SIGS >> 1); sig = 1; while (1) { - if (mask & 1) - sigdelset(&G.blocked_set, sig); + if (mask & 1) { + /* Careful. Only if no trap or trap is not "" */ + if (!G.traps || !G.traps[sig] || G.traps[sig][0]) + sigdelset(&G.blocked_set, sig); + } mask >>= 1; if (!mask) break; sig++; } - + /* Our homegrown sig mask is saner to work with :) */ G.non_DFL_mask &= ~SPECIAL_INTERACTIVE_SIGS; + + /* Resetting all traps to default except empty ones */ mask = G.non_DFL_mask; if (G.traps) for (sig = 0; sig < NSIG; sig++, mask >>= 1) { - if (!G.traps[sig]) + if (!G.traps[sig] || !G.traps[sig][0]) continue; free(G.traps[sig]); G.traps[sig] = NULL; /* There is no signal for 0 (EXIT) */ if (sig == 0) continue; - /* There was a trap handler, we are removing it. + /* There was a trap handler, we just removed it. * But if sig still has non-DFL handling, - * we should not unblock it. */ + * we should not unblock the sig. */ if (mask & 1) continue; sigdelset(&G.blocked_set, sig); @@ -2421,15 +2679,19 @@ static void reset_traps_to_defaults(void) #else /* !BB_MMU */ -static void re_execute_shell(char ***to_free, const char *s, char *g_argv0, char **g_argv) NORETURN; -static void re_execute_shell(char ***to_free, const char *s, char *g_argv0, char **g_argv) +static void re_execute_shell(char ***to_free, const char *s, + char *g_argv0, char **g_argv, + char **builtin_argv) NORETURN; +static void re_execute_shell(char ***to_free, const char *s, + char *g_argv0, char **g_argv, + char **builtin_argv) { - char param_buf[sizeof("-$%x:%x:%x:%x") + sizeof(unsigned) * 4]; + char param_buf[sizeof("-$%x:%x:%x:%x:%x") + sizeof(unsigned) * 2]; char *heredoc_argv[4]; struct variable *cur; -#if ENABLE_HUSH_FUNCTIONS +# if ENABLE_HUSH_FUNCTIONS struct function *funcp; -#endif +# endif char **argv, **pp; unsigned cnt; @@ -2443,24 +2705,31 @@ static void re_execute_shell(char ***to_free, const char *s, char *g_argv0, char goto do_exec; } - sprintf(param_buf, "-$%x:%x:%x" USE_HUSH_LOOPS(":%x") + cnt = 0; + pp = builtin_argv; + if (pp) while (*pp++) + cnt++; + + sprintf(param_buf, "-$%x:%x:%x:%x:%x" IF_HUSH_LOOPS(":%x") , (unsigned) G.root_pid + , (unsigned) G.root_ppid , (unsigned) G.last_bg_pid , (unsigned) G.last_exitcode - USE_HUSH_LOOPS(, G.depth_of_loop) + , cnt + IF_HUSH_LOOPS(, G.depth_of_loop) ); /* 1:hush 2:-$::: * 3:-c 4: 5: 6:NULL */ - cnt = 6; + cnt += 6; for (cur = G.top_var; cur; cur = cur->next) { if (!cur->flg_export || cur->flg_read_only) cnt += 2; } -#if ENABLE_HUSH_FUNCTIONS +# if ENABLE_HUSH_FUNCTIONS for (funcp = G.top_func; funcp; funcp = funcp->next) cnt += 3; -#endif +# endif pp = g_argv; while (*pp++) cnt++; @@ -2478,13 +2747,13 @@ static void re_execute_shell(char ***to_free, const char *s, char *g_argv0, char *pp++ = cur->varstr; } } -#if ENABLE_HUSH_FUNCTIONS +# if ENABLE_HUSH_FUNCTIONS for (funcp = G.top_func; funcp; funcp = funcp->next) { *pp++ = (char *) "-F"; *pp++ = funcp->name; *pp++ = funcp->body_as_string; } -#endif +# endif /* We can pass activated traps here. Say, -Tnn:trap_string * * However, POSIX says that subshells reset signals with traps @@ -2506,6 +2775,11 @@ static void re_execute_shell(char ***to_free, const char *s, char *g_argv0, char */ *pp++ = (char *) "-c"; *pp++ = (char *) s; + if (builtin_argv) { + while (*++builtin_argv) + *pp++ = *builtin_argv; + *pp++ = (char *) ""; + } *pp++ = g_argv0; while (*g_argv) *pp++ = *g_argv++; @@ -2593,10 +2867,14 @@ static void setup_heredoc(struct redir_struct *redir) #else /* Delegate blocking writes to another process */ xmove_fd(pair.wr, STDOUT_FILENO); - re_execute_shell(&to_free, heredoc, NULL, NULL); + re_execute_shell(&to_free, heredoc, NULL, NULL, NULL); #endif } /* parent */ +#if ENABLE_HUSH_FAST + G.count_SIGCHLD++; +//bb_error_msg("[%d] fork in setup_heredoc: G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD); +#endif enable_restore_tty_pgrp_on_exit(); #if !BB_MMU free(to_free); @@ -2616,7 +2894,9 @@ static int setup_redirects(struct command *prog, int squirrel[]) for (redir = prog->redirects; redir; redir = redir->next) { if (redir->rd_type == REDIRECT_HEREDOC2) { /* rd_fd<rd_fd < 3) { + if (squirrel && redir->rd_fd < 3 + && squirrel[redir->rd_fd] < 0 + ) { squirrel[redir->rd_fd] = dup(redir->rd_fd); } /* for REDIRECT_HEREDOC2, rd_filename holds _contents_ @@ -2652,7 +2932,9 @@ static int setup_redirects(struct command *prog, int squirrel[]) } if (openfd != redir->rd_fd) { - if (squirrel && redir->rd_fd < 3) { + if (squirrel && redir->rd_fd < 3 + && squirrel[redir->rd_fd] < 0 + ) { squirrel[redir->rd_fd] = dup(redir->rd_fd); } if (openfd == REDIRFD_CLOSE) { @@ -2706,8 +2988,8 @@ static void free_pipe(struct pipe *pi) } /* not "else if": on syntax error, we may have both! */ if (command->group) { - debug_printf_clean(" begin group (grp_type:%d)\n", - command->grp_type); + debug_printf_clean(" begin group (cmd_type:%d)\n", + command->cmd_type); free_pipe_list(command->group); debug_printf_clean(" end group\n"); command->group = NULL; @@ -2775,28 +3057,81 @@ static struct pipe *parse_stream(char **pstring, static void parse_and_run_string(const char *s); -static const struct built_in_command* find_builtin(const char *name) +static char *find_in_path(const char *arg) { - const struct built_in_command *x; - for (x = bltins; x != &bltins[ARRAY_SIZE(bltins)]; x++) { - if (strcmp(name, x->cmd) != 0) + char *ret = NULL; + const char *PATH = get_local_var_value("PATH"); + + if (!PATH) + return NULL; + + while (1) { + const char *end = strchrnul(PATH, ':'); + int sz = end - PATH; /* must be int! */ + + free(ret); + if (sz != 0) { + ret = xasprintf("%.*s/%s", sz, PATH, arg); + } else { + /* We have xxx::yyyy in $PATH, + * it means "use current dir" */ + ret = xstrdup(arg); + } + if (access(ret, F_OK) == 0) + break; + + if (*end == '\0') { + free(ret); + return NULL; + } + PATH = end + 1; + } + + return ret; +} + +static const struct built_in_command* find_builtin_helper(const char *name, + const struct built_in_command *x, + const struct built_in_command *end) +{ + while (x != end) { + if (strcmp(name, x->cmd) != 0) { + x++; continue; + } debug_printf_exec("found builtin '%s'\n", name); return x; } return NULL; } +static const struct built_in_command* find_builtin1(const char *name) +{ + return find_builtin_helper(name, bltins1, &bltins1[ARRAY_SIZE(bltins1)]); +} +static const struct built_in_command* find_builtin(const char *name) +{ + const struct built_in_command *x = find_builtin1(name); + if (x) + return x; + return find_builtin_helper(name, bltins2, &bltins2[ARRAY_SIZE(bltins2)]); +} #if ENABLE_HUSH_FUNCTIONS -static const struct function *find_function(const char *name) +static struct function **find_function_slot(const char *name) { - const struct function *funcp = G.top_func; - while (funcp) { - if (strcmp(name, funcp->name) == 0) { + struct function **funcpp = &G.top_func; + while (*funcpp) { + if (strcmp(name, (*funcpp)->name) == 0) { break; } - funcp = funcp->next; + funcpp = &(*funcpp)->next; } + return funcpp; +} + +static const struct function *find_function(const char *name) +{ + const struct function *funcp = *find_function_slot(name); if (funcp) debug_printf_exec("found function '%s'\n", name); return funcp; @@ -2805,18 +3140,11 @@ static const struct function *find_function(const char *name) /* Note: takes ownership on name ptr */ static struct function *new_function(char *name) { - struct function *funcp; - struct function **funcpp = &G.top_func; + struct function **funcpp = find_function_slot(name); + struct function *funcp = *funcpp; - while ((funcp = *funcpp) != NULL) { - struct command *cmd; - - if (strcmp(funcp->name, name) != 0) { - funcpp = &funcp->next; - continue; - } - - cmd = funcp->parent_cmd; + if (funcp != NULL) { + struct command *cmd = funcp->parent_cmd; debug_printf_exec("func %p parent_cmd %p\n", funcp, cmd); if (!cmd) { debug_printf_exec("freeing & replacing function '%s'\n", funcp->name); @@ -2826,62 +3154,59 @@ static struct function *new_function(char *name) * body_as_string was not malloced! */ if (funcp->body) { free_pipe_list(funcp->body); -#if !BB_MMU +# if !BB_MMU free(funcp->body_as_string); -#endif +# endif } } else { debug_printf_exec("reinserting in tree & replacing function '%s'\n", funcp->name); cmd->argv[0] = funcp->name; cmd->group = funcp->body; -#if !BB_MMU +# if !BB_MMU cmd->group_as_string = funcp->body_as_string; -#endif +# endif } - goto skip; + } else { + debug_printf_exec("remembering new function '%s'\n", name); + funcp = *funcpp = xzalloc(sizeof(*funcp)); + /*funcp->next = NULL;*/ } - debug_printf_exec("remembering new function '%s'\n", name); - funcp = *funcpp = xzalloc(sizeof(*funcp)); - /*funcp->next = NULL;*/ - skip: + funcp->name = name; return funcp; } static void unset_func(const char *name) { - struct function *funcp; - struct function **funcpp = &G.top_func; - - while ((funcp = *funcpp) != NULL) { - if (strcmp(funcp->name, name) == 0) { - *funcpp = funcp->next; - /* funcp is unlinked now, deleting it */ + struct function **funcpp = find_function_slot(name); + struct function *funcp = *funcpp; + + if (funcp != NULL) { + debug_printf_exec("freeing function '%s'\n", funcp->name); + *funcpp = funcp->next; + /* funcp is unlinked now, deleting it. + * Note: if !funcp->body, the function was created by + * "-F name body", do not free ->body_as_string + * and ->name as they were not malloced. */ + if (funcp->body) { + free_pipe_list(funcp->body); free(funcp->name); - /* Note: if !funcp->body, do not free body_as_string! - * This is a special case of "-F name body" function: - * body_as_string was not malloced! */ - if (funcp->body) { - free_pipe_list(funcp->body); -#if !BB_MMU - free(funcp->body_as_string); -#endif - } - free(funcp); - break; +# if !BB_MMU + free(funcp->body_as_string); +# endif } - funcpp = &funcp->next; + free(funcp); } } -#if BB_MMU -#define exec_function(nommu_save, funcp, argv) \ +# if BB_MMU +#define exec_function(to_free, funcp, argv) \ exec_function(funcp, argv) -#endif -static void exec_function(nommu_save_t *nommu_save, +# endif +static void exec_function(char ***to_free, const struct function *funcp, char **argv) NORETURN; -static void exec_function(nommu_save_t *nommu_save, +static void exec_function(char ***to_free, const struct function *funcp, char **argv) { @@ -2898,10 +3223,11 @@ static void exec_function(nommu_save_t *nommu_save, fflush(NULL); _exit(n); # else - re_execute_shell(&nommu_save->argv_from_re_execing, + re_execute_shell(to_free, funcp->body_as_string, G.global_argv[0], - argv + 1); + argv + 1, + NULL); # endif } @@ -2909,38 +3235,102 @@ static int run_function(const struct function *funcp, char **argv) { int rc; save_arg_t sv; -#if ENABLE_HUSH_FUNCTIONS smallint sv_flg; -#endif save_and_replace_G_args(&sv, argv); -#if ENABLE_HUSH_FUNCTIONS + /* "we are in function, ok to use return" */ sv_flg = G.flag_return_in_progress; G.flag_return_in_progress = -1; -#endif +# if ENABLE_HUSH_LOCAL + G.func_nest_level++; +# endif /* On MMU, funcp->body is always non-NULL */ -#if !BB_MMU +# if !BB_MMU if (!funcp->body) { /* Function defined by -F */ parse_and_run_string(funcp->body_as_string); rc = G.last_exitcode; } else -#endif +# endif { rc = run_list(funcp->body); } -#if ENABLE_HUSH_FUNCTIONS +# if ENABLE_HUSH_LOCAL + { + struct variable *var; + struct variable **var_pp; + + var_pp = &G.top_var; + while ((var = *var_pp) != NULL) { + if (var->func_nest_level < G.func_nest_level) { + var_pp = &var->next; + continue; + } + /* Unexport */ + if (var->flg_export) + bb_unsetenv(var->varstr); + /* Remove from global list */ + *var_pp = var->next; + /* Free */ + if (!var->max_len) + free(var->varstr); + free(var); + } + G.func_nest_level--; + } +# endif G.flag_return_in_progress = sv_flg; -#endif + restore_G_args(&sv, argv); return rc; } +#endif /* ENABLE_HUSH_FUNCTIONS */ + + +#if BB_MMU +#define exec_builtin(to_free, x, argv) \ + exec_builtin(x, argv) +#else +#define exec_builtin(to_free, x, argv) \ + exec_builtin(to_free, argv) #endif +static void exec_builtin(char ***to_free, + const struct built_in_command *x, + char **argv) NORETURN; +static void exec_builtin(char ***to_free, + const struct built_in_command *x, + char **argv) +{ +#if BB_MMU + int rcode = x->function(argv); + fflush(NULL); + _exit(rcode); +#else + /* On NOMMU, we must never block! + * Example: { sleep 99 | read line; } & echo Ok + */ + re_execute_shell(to_free, + argv[0], + G.global_argv[0], + G.global_argv + 1, + argv); +#endif +} + +static void execvp_or_die(char **argv) NORETURN; +static void execvp_or_die(char **argv) +{ + debug_printf_exec("execing '%s'\n", argv[0]); + sigprocmask(SIG_SETMASK, &G.inherited_set, NULL); + execvp(argv[0], argv); + bb_perror_msg("can't execute '%s'", argv[0]); + _exit(127); /* bash compat */ +} #if BB_MMU #define pseudo_exec_argv(nommu_save, argv, assignment_cnt, argv_expanded) \ @@ -2957,7 +3347,7 @@ static int run_function(const struct function *funcp, char **argv) static void pseudo_exec_argv(nommu_save_t *nommu_save, char **argv, int assignment_cnt, char **argv_expanded) NORETURN; -static void pseudo_exec_argv(nommu_save_t *nommu_save, +static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save, char **argv, int assignment_cnt, char **argv_expanded) { @@ -2969,11 +3359,13 @@ static void pseudo_exec_argv(nommu_save_t *nommu_save, new_env = expand_assignments(argv, assignment_cnt); #if BB_MMU - putenv_all(new_env); + set_vars_and_save_old(new_env); free(new_env); /* optional */ + /* we can also destroy set_vars_and_save_old's return value, + * to save memory */ #else nommu_save->new_env = new_env; - nommu_save->old_env = putenv_all_and_save_old(new_env); + nommu_save->old_vars = set_vars_and_save_old(new_env); #endif if (argv_expanded) { argv = argv_expanded; @@ -2989,33 +3381,28 @@ static void pseudo_exec_argv(nommu_save_t *nommu_save, goto skip; #endif - /* On NOMMU, we must never block! - * Example: { sleep 99999 | read line } & echo Ok - * read builtin will block on read syscall, leaving parent blocked - * in vfork. Therefore we can't do this: - */ -#if BB_MMU /* 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. */ { - int rcode; - const struct built_in_command *x = find_builtin(argv[0]); + /* On NOMMU, it is more expensive to re-execute shell + * just in order to run echo or test builtin. + * It's better to skip it here and run corresponding + * non-builtin later. */ + const struct built_in_command *x; + x = BB_MMU ? find_builtin(argv[0]) : find_builtin1(argv[0]); if (x) { - rcode = x->function(argv); - fflush(NULL); - _exit(rcode); + exec_builtin(&nommu_save->argv_from_re_execing, x, argv); } } -#endif #if ENABLE_HUSH_FUNCTIONS /* Check if the command matches any functions */ { const struct function *funcp = find_function(argv[0]); if (funcp) { - exec_function(nommu_save, funcp, argv); + exec_function(&nommu_save->argv_from_re_execing, funcp, argv); } } #endif @@ -3044,11 +3431,7 @@ static void pseudo_exec_argv(nommu_save_t *nommu_save, #if ENABLE_FEATURE_SH_STANDALONE || BB_MMU skip: #endif - debug_printf_exec("execing '%s'\n", argv[0]); - sigprocmask(SIG_SETMASK, &G.inherited_set, NULL); - execvp(argv[0], argv); - bb_perror_msg("can't exec '%s'", argv[0]); - _exit(EXIT_FAILURE); + execvp_or_die(argv); } /* Called after [v]fork() in run_pipe @@ -3084,7 +3467,8 @@ static void pseudo_exec(nommu_save_t *nommu_save, re_execute_shell(&nommu_save->argv_from_re_execing, command->group_as_string, G.global_argv[0], - G.global_argv + 1); + G.global_argv + 1, + NULL); #endif } @@ -3116,7 +3500,7 @@ static const char *get_cmdtext(struct pipe *pi) len += strlen(*argv) + 1; } while (*++argv); p = xmalloc(len); - pi->cmdtext = p;// = xmalloc(len); + pi->cmdtext = p; argv = pi->cmds[0].argv; do { len = strlen(*argv); @@ -3204,15 +3588,30 @@ static int checkjobs(struct pipe* fg_pipe) debug_printf_jobs("checkjobs %p\n", fg_pipe); - errno = 0; -// if (G.handled_SIGCHLD == G.count_SIGCHLD) -// /* avoid doing syscall, nothing there anyway */ -// return rcode; - attributes = WUNTRACED; if (fg_pipe == NULL) attributes |= WNOHANG; + errno = 0; +#if ENABLE_HUSH_FAST + if (G.handled_SIGCHLD == G.count_SIGCHLD) { +//bb_error_msg("[%d] checkjobs: G.count_SIGCHLD:%d G.handled_SIGCHLD:%d children?:%d fg_pipe:%p", +//getpid(), G.count_SIGCHLD, G.handled_SIGCHLD, G.we_have_children, fg_pipe); + /* There was neither fork nor SIGCHLD since last waitpid */ + /* Avoid doing waitpid syscall if possible */ + if (!G.we_have_children) { + errno = ECHILD; + return -1; + } + if (fg_pipe == NULL) { /* is WNOHANG set? */ + /* We have children, but they did not exit + * or stop yet (we saw no SIGCHLD) */ + return 0; + } + /* else: !WNOHANG, waitpid will block, can't short-circuit */ + } +#endif + /* Do we do this right? * bash-3.00# sleep 20 | false * @@ -3226,13 +3625,20 @@ static int checkjobs(struct pipe* fg_pipe) int i; int dead; -// i = G.count_SIGCHLD; +#if ENABLE_HUSH_FAST + i = G.count_SIGCHLD; +#endif childpid = waitpid(-1, &status, attributes); if (childpid <= 0) { if (childpid && errno != ECHILD) bb_perror_msg("waitpid"); -// else /* Until next SIGCHLD, waitpid's are useless */ -// G.handled_SIGCHLD = i; +#if ENABLE_HUSH_FAST + else { /* Until next SIGCHLD, waitpid's are useless */ + G.we_have_children = (childpid == 0); + G.handled_SIGCHLD = i; +//bb_error_msg("[%d] checkjobs: waitpid returned <= 0, G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD); + } +#endif break; } dead = WIFEXITED(status) || WIFSIGNALED(status); @@ -3262,9 +3668,9 @@ static int checkjobs(struct pipe* fg_pipe) /* Note: is WIFSIGNALED, WEXITSTATUS = sig + 128 */ rcode = WEXITSTATUS(status); IF_HAS_KEYWORDS(if (fg_pipe->pi_inverted) rcode = !rcode;) - /* bash prints killing signal's name for *last* + /* bash prints killer signal's name for *last* * process in pipe (prints just newline for SIGINT). - * Mimic this. Example: "sleep 5" + ^\ + * Mimic this. Example: "sleep 5" + (^\ or kill -QUIT) */ if (WIFSIGNALED(status)) { int sig = WTERMSIG(status); @@ -3289,6 +3695,8 @@ static int checkjobs(struct pipe* fg_pipe) #endif return rcode; } + if (!fg_pipe->alive_cmds) + return rcode; } /* There are still running processes in the fg pipe */ goto wait_more; /* do waitpid again */ @@ -3336,10 +3744,12 @@ 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); + if (G_saved_tty_pgrp) { + /* Job finished, move the shell to the foreground */ + p = getpgrp(); /* our process group id */ + debug_printf_jobs("fg'ing ourself: getpgrp()=%d\n", (int)p); + tcsetpgrp(G_interactive_fd, p); + } return rcode; } #endif @@ -3370,7 +3780,7 @@ static int checkjobs_and_fg_shell(struct pipe* fg_pipe) * backgrounded: cmd & { list } & * subshell: ( list ) [&] */ -static int run_pipe(struct pipe *pi) +static NOINLINE int run_pipe(struct pipe *pi) { static const char *const null_ptr = NULL; int i; @@ -3386,14 +3796,14 @@ static int run_pipe(struct pipe *pi) debug_printf_exec("run_pipe start: members:%d\n", pi->num_cmds); debug_enter(); - USE_HUSH_JOB(pi->pgrp = -1;) + IF_HUSH_JOB(pi->pgrp = -1;) pi->stopped_cmds = 0; command = &(pi->cmds[0]); argv_expanded = NULL; if (pi->num_cmds != 1 || pi->followup == PIPE_BG - || command->grp_type == GRP_SUBSHELL + || command->cmd_type == CMD_SUBSHELL ) { goto must_fork; } @@ -3405,17 +3815,17 @@ static int run_pipe(struct pipe *pi) if (command->group) { #if ENABLE_HUSH_FUNCTIONS - if (command->grp_type == GRP_FUNCTION) { + if (command->cmd_type == CMD_FUNCDEF) { /* "executing" func () { list } */ struct function *funcp; funcp = new_function(command->argv[0]); /* funcp->name is already set to argv[0] */ funcp->body = command->group; -#if !BB_MMU +# if !BB_MMU funcp->body_as_string = command->group_as_string; command->group_as_string = NULL; -#endif +# endif command->group = NULL; command->argv[0] = NULL; debug_printf_exec("cmd %p has child func at %p\n", command, funcp); @@ -3450,7 +3860,7 @@ static int run_pipe(struct pipe *pi) enum { funcp = 0 }; #endif char **new_env = NULL; - char **old_env = NULL; + struct variable *old_vars = NULL; if (argv[command->assignment_cnt] == NULL) { /* Assignments, but no command */ @@ -3462,7 +3872,7 @@ static int run_pipe(struct pipe *pi) p = expand_string_to_string(*argv); debug_printf_exec("set shell var:'%s'->'%s'\n", *argv, p); - set_local_var(p, 0, 0); + set_local_var(p, /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ 0); argv++; } /* Do we need to flag set_local_var() errors? @@ -3475,7 +3885,27 @@ static int run_pipe(struct pipe *pi) } /* Expand the rest into (possibly) many strings each */ - argv_expanded = expand_strvec_to_strvec(argv + command->assignment_cnt); + if (0) {} +#if ENABLE_HUSH_BASH_COMPAT + else if (command->cmd_type == CMD_SINGLEWORD_NOGLOB) { + argv_expanded = expand_strvec_to_strvec_singleword_noglob(argv + command->assignment_cnt); + } +#endif +#ifdef CMD_SINGLEWORD_NOGLOB_COND + else if (command->cmd_type == CMD_SINGLEWORD_NOGLOB_COND) { + argv_expanded = expand_strvec_to_strvec_singleword_noglob_cond(argv + command->assignment_cnt); + + } +#endif + else { + argv_expanded = expand_strvec_to_strvec(argv + command->assignment_cnt); + } + + /* if someone gives us an empty string: `cmd with empty output` */ + if (!argv_expanded[0]) { + debug_leave(); + return 0; + } x = find_builtin(argv_expanded[0]); #if ENABLE_HUSH_FUNCTIONS @@ -3498,7 +3928,7 @@ static int run_pipe(struct pipe *pi) rcode = setup_redirects(command, squirrel); if (rcode == 0) { new_env = expand_assignments(argv, command->assignment_cnt); - old_env = putenv_all_and_save_old(new_env); + old_vars = set_vars_and_save_old(new_env); if (!funcp) { debug_printf_exec(": builtin '%s' '%s'...\n", x->cmd, argv_expanded[1]); @@ -3507,9 +3937,17 @@ static int run_pipe(struct pipe *pi) } #if ENABLE_HUSH_FUNCTIONS else { +# if ENABLE_HUSH_LOCAL + struct variable **sv; + sv = G.shadowed_vars_pp; + G.shadowed_vars_pp = &old_vars; +# endif debug_printf_exec(": function '%s' '%s'...\n", funcp->name, argv_expanded[1]); rcode = run_function(funcp, argv_expanded) & 0xff; +# if ENABLE_HUSH_LOCAL + G.shadowed_vars_pp = sv; +# endif } #endif } @@ -3517,11 +3955,8 @@ static int run_pipe(struct pipe *pi) clean_up_and_ret: #endif restore_redirects(squirrel); - free_strings_and_unsetenv(new_env, 1); - putenv_all(old_env); - /* Free the pointers, but the strings themselves - * are in environ now, don't use free_strings! */ - free(old_env); + unset_vars(new_env); + add_vars(old_vars); clean_up_and_ret1: free(argv_expanded); IF_HAS_KEYWORDS(if (pi->pi_inverted) rcode = !rcode;) @@ -3536,7 +3971,7 @@ static int run_pipe(struct pipe *pi) rcode = setup_redirects(command, squirrel); if (rcode == 0) { new_env = expand_assignments(argv, command->assignment_cnt); - old_env = putenv_all_and_save_old(new_env); + old_vars = set_vars_and_save_old(new_env); debug_printf_exec(": run_nofork_applet '%s' '%s'...\n", argv_expanded[0], argv_expanded[1]); rcode = run_nofork_applet(i, argv_expanded); @@ -3561,7 +3996,7 @@ static int run_pipe(struct pipe *pi) #if !BB_MMU volatile nommu_save_t nommu_save; nommu_save.new_env = NULL; - nommu_save.old_env = NULL; + nommu_save.old_vars = NULL; nommu_save.argv = NULL; nommu_save.argv_from_re_execing = NULL; #endif @@ -3583,6 +4018,7 @@ static int run_pipe(struct pipe *pi) if (!command->pid) { /* child */ #if ENABLE_HUSH_JOB disable_restore_tty_pgrp_on_exit(); + CLEAR_RANDOM_T(&G.random_gen); /* or else $RANDOM repeats in child */ /* Every child adds itself to new process group * with pgid == pid_of_first_child_in_pipe */ @@ -3591,7 +4027,10 @@ static int run_pipe(struct pipe *pi) pgrp = pi->pgrp; if (pgrp < 0) /* true for 1st process only */ pgrp = getpid(); - if (setpgid(0, pgrp) == 0 && pi->followup != PIPE_BG) { + if (setpgid(0, pgrp) == 0 + && pi->followup != PIPE_BG + && G_saved_tty_pgrp /* we have ctty */ + ) { /* We do it in *every* child, not just first, * to avoid races */ tcsetpgrp(G_interactive_fd, pgrp); @@ -3626,16 +4065,17 @@ static int run_pipe(struct pipe *pi) } /* parent or error */ +#if ENABLE_HUSH_FAST + G.count_SIGCHLD++; +//bb_error_msg("[%d] fork in run_pipe: G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD); +#endif enable_restore_tty_pgrp_on_exit(); #if !BB_MMU /* Clean up after vforked child */ free(nommu_save.argv); free(nommu_save.argv_from_re_execing); - free_strings_and_unsetenv(nommu_save.new_env, 1); - putenv_all(nommu_save.old_env); - /* Free the pointers, but the strings themselves - * are in environ now, don't use free_strings! */ - free(nommu_save.old_env); + unset_vars(nommu_save.new_env); + add_vars(nommu_save.old_vars); #endif free(argv_expanded); argv_expanded = NULL; @@ -3681,38 +4121,40 @@ static void debug_print_tree(struct pipe *pi, int lvl) }; static const char *RES[] = { [RES_NONE ] = "NONE" , -#if ENABLE_HUSH_IF +# if ENABLE_HUSH_IF [RES_IF ] = "IF" , [RES_THEN ] = "THEN" , [RES_ELIF ] = "ELIF" , [RES_ELSE ] = "ELSE" , [RES_FI ] = "FI" , -#endif -#if ENABLE_HUSH_LOOPS +# 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 +# endif +# if ENABLE_HUSH_LOOPS || ENABLE_HUSH_CASE [RES_IN ] = "IN" , -#endif -#if ENABLE_HUSH_CASE +# endif +# if ENABLE_HUSH_CASE [RES_CASE ] = "CASE" , + [RES_CASE_IN ] = "CASE_IN" , [RES_MATCH] = "MATCH", - [RES_CASEI] = "CASEI", + [RES_CASE_BODY] = "CASE_BODY", [RES_ESAC ] = "ESAC" , -#endif +# endif [RES_XXXX ] = "XXXX" , [RES_SNTX ] = "SNTX" , }; - static const char *const GRPTYPE[] = { + static const char *const CMDTYPE[] = { "{}", "()", -#if ENABLE_HUSH_FUNCTIONS + "[noglob]", +# if ENABLE_HUSH_FUNCTIONS "func()", -#endif +# endif }; int pin, prn; @@ -3731,7 +4173,7 @@ static void debug_print_tree(struct pipe *pi, int lvl) command->assignment_cnt); if (command->group) { fprintf(stderr, " group %s: (argv=%p)\n", - GRPTYPE[command->grp_type], + CMDTYPE[command->cmd_type], argv); debug_print_tree(command->group, lvl+1); prn++; @@ -3748,7 +4190,7 @@ static void debug_print_tree(struct pipe *pi, int lvl) pin++; } } -#endif +#endif /* debug_print_tree */ /* NB: called by pseudo_exec, and therefore must not modify any * global data until exec/_exit (we can be a child after vfork!) */ @@ -3759,7 +4201,6 @@ static int run_list(struct pipe *pi) #endif #if ENABLE_HUSH_LOOPS struct pipe *loop_top = NULL; - char *for_varname = NULL; char **for_lcur = NULL; char **for_list = NULL; #endif @@ -3821,7 +4262,7 @@ static int run_list(struct pipe *pi) rcode = G.last_exitcode; /* Go through list of pipes, (maybe) executing them. */ - for (; pi; pi = USE_HUSH_LOOPS(rword == RES_DONE ? loop_top : ) pi->next) { + for (; pi; pi = IF_HUSH_LOOPS(rword == RES_DONE ? loop_top : ) pi->next) { if (G.flag_SIGINT) break; @@ -3895,22 +4336,18 @@ static int run_list(struct pipe *pi) 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 */ /* note: *for_lcur already has quotes removed, $var expanded, etc */ - pi->cmds[0].argv[0] = xasprintf("%s=%s", for_varname, *for_lcur++); - pi->cmds[0].assignment_cnt = 1; + set_local_var(xasprintf("%s=%s", pi->cmds[0].argv[0], *for_lcur++), /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ 0); + continue; } if (rword == RES_IN) { continue; /* "for v IN list;..." - "in" has no cmds anyway */ @@ -3945,7 +4382,7 @@ static int run_list(struct pipe *pi) } continue; } - if (rword == RES_CASEI) { /* inside of a case branch */ + if (rword == RES_CASE_BODY) { /* inside of a case branch */ if (cond_code != 0) continue; /* not matched yet, skip this pipe */ } @@ -3997,8 +4434,11 @@ static int run_list(struct pipe *pi) } #endif #if ENABLE_HUSH_FUNCTIONS - if (G.flag_return_in_progress == 1) - goto check_jobs_and_break; + if (G.flag_return_in_progress == 1) { + /* same as "goto check_jobs_and_break" */ + checkjobs(NULL); + break; + } #endif } else if (pi->followup == PIPE_BG) { /* What does bash do with attempts to background builtins? */ @@ -4008,11 +4448,7 @@ static int run_list(struct pipe *pi) check_and_run_traps(0); #if ENABLE_HUSH_JOB if (G.run_list_level == 1) -{ -debug_printf_exec("insert_bg_job1\n"); insert_bg_job(pi); -debug_printf_exec("insert_bg_job2\n"); -} #endif G.last_exitcode = rcode = EXIT_SUCCESS; debug_printf_exec(": cmd&: exitcode EXIT_SUCCESS\n"); @@ -4190,7 +4626,9 @@ static void done_pipe(struct parse_context *ctx, pipe_style type) #endif #if ENABLE_HUSH_CASE if (ctx->ctx_res_w == RES_MATCH) - ctx->ctx_res_w = RES_CASEI; + ctx->ctx_res_w = RES_CASE_BODY; + if (ctx->ctx_res_w == RES_CASE) + ctx->ctx_res_w = RES_CASE_IN; #endif ctx->command = NULL; /* trick done_command below */ /* Create the memory for command, roughly: @@ -4226,25 +4664,25 @@ struct reserved_combo { }; enum { FLAG_END = (1 << RES_NONE ), -#if ENABLE_HUSH_IF +# 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 +# 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 +# endif +# if ENABLE_HUSH_CASE FLAG_MATCH = (1 << RES_MATCH), FLAG_ESAC = (1 << RES_ESAC ), -#endif +# endif FLAG_START = (1 << RES_XXXX ), }; @@ -4256,26 +4694,26 @@ static const struct reserved_combo* match_reserved_word(o_string *word) * FLAG_START means the word must start a new compound list. */ static const struct reserved_combo reserved_list[] = { -#if ENABLE_HUSH_IF +# 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 +# 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 +# endif +# if ENABLE_HUSH_CASE { "case", RES_CASE, NOT_ASSIGNMENT , FLAG_MATCH | FLAG_START }, { "esac", RES_ESAC, NOT_ASSIGNMENT , FLAG_END }, -#endif +# endif }; const struct reserved_combo *r; @@ -4289,11 +4727,11 @@ static const struct reserved_combo* match_reserved_word(o_string *word) */ static int reserved_word(o_string *word, struct parse_context *ctx) { -#if ENABLE_HUSH_CASE +# if ENABLE_HUSH_CASE static const struct reserved_combo reserved_match = { "", RES_MATCH, NOT_ASSIGNMENT , FLAG_MATCH | FLAG_ESAC }; -#endif +# endif const struct reserved_combo *r; if (word->o_quoted) @@ -4303,12 +4741,12 @@ static int reserved_word(o_string *word, struct parse_context *ctx) 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 */ +# if ENABLE_HUSH_CASE + if (r->res == RES_IN && ctx->ctx_res_w == RES_CASE_IN) { + /* "case word IN ..." - IN part starts first MATCH part */ r = &reserved_match; - else -#endif + } else +# endif if (r->flag == 0) { /* '!' */ if (ctx->ctx_inverted) { /* bash doesn't accept '! ! true' */ syntax_error("! ! command"); @@ -4348,20 +4786,20 @@ static int reserved_word(o_string *word, struct parse_context *ctx) debug_printf_parse("pop stack %p\n", ctx->stack); old = ctx->stack; old->command->group = ctx->list_head; - old->command->grp_type = GRP_NORMAL; -#if !BB_MMU + old->command->cmd_type = CMD_NORMAL; +# if !BB_MMU o_addstr(&old->as_string, ctx->as_string.data); o_free_unsafe(&ctx->as_string); old->command->group_as_string = xstrdup(old->as_string.data); debug_printf_parse("pop, remembering as:'%s'\n", old->command->group_as_string); -#endif +# endif *ctx = *old; /* physical copy */ free(old); } return 1; } -#endif +#endif /* HAS_KEYWORDS */ /* Word is complete, look at it and update parsing context. * Normal return is 0. Syntax errors return 1. @@ -4396,12 +4834,12 @@ static int done_word(o_string *word, struct parse_context *ctx) * Same with heredocs: * for <<\H delim is H; <<\\H, <<"\H", <<"\\H" - \H */ - unbackslash(ctx->pending_redirect->rd_filename); - /* Is it <<"HEREDOC"? */ - if (ctx->pending_redirect->rd_type == REDIRECT_HEREDOC - && word->o_quoted - ) { - ctx->pending_redirect->rd_dup |= HEREDOC_QUOTED; + if (ctx->pending_redirect->rd_type == REDIRECT_HEREDOC) { + unbackslash(ctx->pending_redirect->rd_filename); + /* Is it <<"HEREDOC"? */ + if (word->o_quoted) { + ctx->pending_redirect->rd_dup |= HEREDOC_QUOTED; + } } debug_printf_parse("word stored in rd_filename: '%s'\n", word->data); ctx->pending_redirect = NULL; @@ -4432,6 +4870,9 @@ static int done_word(o_string *word, struct parse_context *ctx) # if ENABLE_HUSH_LOOPS && ctx->ctx_res_w != RES_FOR /* ...not after FOR or IN */ && ctx->ctx_res_w != RES_IN +# endif +# if ENABLE_HUSH_CASE + && ctx->ctx_res_w != RES_CASE # endif ) { debug_printf_parse("checking '%s' for reserved-ness\n", word->data); @@ -4441,6 +4882,21 @@ static int done_word(o_string *word, struct parse_context *ctx) (ctx->ctx_res_w == RES_SNTX)); return (ctx->ctx_res_w == RES_SNTX); } +# ifdef CMD_SINGLEWORD_NOGLOB_COND + if (strcmp(word->data, "export") == 0 +# if ENABLE_HUSH_LOCAL + || strcmp(word->data, "local") == 0 +# endif + ) { + command->cmd_type = CMD_SINGLEWORD_NOGLOB_COND; + } else +# endif +# if ENABLE_HUSH_BASH_COMPAT + if (strcmp(word->data, "[[") == 0) { + command->cmd_type = CMD_SINGLEWORD_NOGLOB; + } + /* fall through */ +# endif } #endif if (command->group) { @@ -4719,7 +5175,7 @@ static int fetch_heredocs(int heredoc_cnt, struct parse_context *ctx, struct in_ char *p; redir->rd_type = REDIRECT_HEREDOC2; - /* redir->dup is (ab)used to indicate <<- */ + /* redir->rd_dup is (ab)used to indicate <<- */ p = fetch_till_str(&ctx->as_string, input, redir->rd_filename, redir->rd_dup & HEREDOC_SKIPTABS); if (!p) { @@ -4750,9 +5206,9 @@ static FILE *generate_stream_from_string(const char *s) { FILE *pf; int pid, channel[2]; -#if !BB_MMU +# if !BB_MMU char **to_free; -#endif +# endif xpipe(channel); pid = BB_MMU ? fork() : vfork(); @@ -4770,15 +5226,57 @@ static FILE *generate_stream_from_string(const char *s) + (1 << SIGTTIN) + (1 << SIGTTOU) , SIG_IGN); + CLEAR_RANDOM_T(&G.random_gen); /* or else $RANDOM repeats in child */ close(channel[0]); /* NB: close _first_, then move fd! */ xmove_fd(channel[1], 1); /* Prevent it from trying to handle ctrl-z etc */ - USE_HUSH_JOB(G.run_list_level = 1;) -#if BB_MMU + IF_HUSH_JOB(G.run_list_level = 1;) + /* Awful hack for `trap` or $(trap). + * + * http://www.opengroup.org/onlinepubs/009695399/utilities/trap.html + * contains an example where "trap" is executed in a subshell: + * + * save_traps=$(trap) + * ... + * eval "$save_traps" + * + * Standard does not say that "trap" in subshell shall print + * parent shell's traps. It only says that its output + * must have suitable form, but then, in the above example + * (which is not supposed to be normative), it implies that. + * + * bash (and probably other shell) does implement it + * (traps are reset to defaults, but "trap" still shows them), + * but as a result, "trap" logic is hopelessly messed up: + * + * # trap + * trap -- 'echo Ho' SIGWINCH <--- we have a handler + * # (trap) <--- trap is in subshell - no output (correct, traps are reset) + * # true | trap <--- trap is in subshell - no output (ditto) + * # echo `true | trap` <--- in subshell - output (but traps are reset!) + * trap -- 'echo Ho' SIGWINCH + * # echo `(trap)` <--- in subshell in subshell - output + * trap -- 'echo Ho' SIGWINCH + * # echo `true | (trap)` <--- in subshell in subshell in subshell - output! + * trap -- 'echo Ho' SIGWINCH + * + * The rules when to forget and when to not forget traps + * get really complex and nonsensical. + * + * Our solution: ONLY bare $(trap) or `trap` is special. + */ + s = skip_whitespace(s); + if (strncmp(s, "trap", 4) == 0 && (*skip_whitespace(s + 4) == '\0')) + { + static const char *const argv[] = { NULL, NULL }; + builtin_trap((char**)argv); + exit(0); /* not _exit() - we need to fflush */ + } +# if BB_MMU reset_traps_to_defaults(); parse_and_run_string(s); _exit(G.last_exitcode); -#else +# else /* We re-execute after vfork on NOMMU. This makes this script safe: * yes "0123456789012345678901234567890" | dd bs=32 count=64k >BIG * huge=`cat BIG` # was blocking here forever @@ -4787,15 +5285,20 @@ static FILE *generate_stream_from_string(const char *s) re_execute_shell(&to_free, s, G.global_argv[0], - G.global_argv + 1); -#endif + G.global_argv + 1, + NULL); +# endif } /* parent */ +# if ENABLE_HUSH_FAST + G.count_SIGCHLD++; +//bb_error_msg("[%d] fork in generate_stream_from_string: G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD); +# endif enable_restore_tty_pgrp_on_exit(); -#if !BB_MMU +# if !BB_MMU free(to_free); -#endif +# endif close(channel[1]); pf = fdopen(channel[0], "r"); return pf; @@ -4839,7 +5342,7 @@ static int process_command_subs(o_string *dest, const char *s) debug_printf("closed FILE from child. return 0\n"); return 0; } -#endif +#endif /* ENABLE_HUSH_TICK */ static int parse_group(o_string *dest, struct parse_context *ctx, struct in_str *input, int ch) @@ -4880,7 +5383,7 @@ static int parse_group(o_string *dest, struct parse_context *ctx, return 1; } nommu_addchr(&ctx->as_string, ch); - command->grp_type = GRP_FUNCTION; + command->cmd_type = CMD_FUNCDEF; goto skip; } #endif @@ -4900,7 +5403,7 @@ static int parse_group(o_string *dest, struct parse_context *ctx, endch = '}'; if (ch == '(') { endch = ')'; - command->grp_type = GRP_SUBSHELL; + command->cmd_type = CMD_SUBSHELL; } else { /* bash does not allow "{echo...", requires whitespace */ ch = i_getch(input); @@ -5089,7 +5592,6 @@ static int handle_dollar(o_string *as_string, o_string *dest, struct in_str *input) { - int expansion; int ch = i_peek(input); /* first character after the $ */ unsigned char quote_mask = dest->o_escape ? 0x80 : 0; @@ -5128,10 +5630,12 @@ static int handle_dollar(o_string *as_string, goto make_one_char_var; case '{': { bool first_char, all_digits; + int expansion; - o_addchr(dest, SPECIAL_VAR_SYMBOL); ch = i_getch(input); nommu_addchr(as_string, ch); + o_addchr(dest, SPECIAL_VAR_SYMBOL); + /* TODO: maybe someone will try to escape the '}' */ expansion = 0; first_char = true; @@ -5152,6 +5656,9 @@ static int handle_dollar(o_string *as_string, all_digits = true; goto char_ok; } + /* They're being verbose and doing ${?} */ + if (i_peek(input) == '}' && strchr("$!?#*@_", ch)) + goto char_ok; } if (expansion < 2 @@ -5203,7 +5710,7 @@ static int handle_dollar(o_string *as_string, o_addchr(dest, SPECIAL_VAR_SYMBOL); break; } -#if (ENABLE_SH_MATH_SUPPORT || ENABLE_HUSH_TICK) +#if ENABLE_SH_MATH_SUPPORT || ENABLE_HUSH_TICK case '(': { # if !BB_MMU int pos; @@ -5258,8 +5765,10 @@ static int handle_dollar(o_string *as_string, goto make_var; } /* else: it's $_ */ - /* TODO: */ - /* $_ Shell or shell script name; or last cmd name */ + /* TODO: $_ and $-: */ + /* $_ Shell or shell script name; or last argument of last command + * (if last command wasn't a pipe; if it was, bash sets $_ to ""); + * but in command's env, set to full pathname used to invoke it */ /* $- Option flags set by set builtin or shell options (-i etc) */ default: o_addQchr(dest, '$'); @@ -5299,7 +5808,7 @@ static int parse_stream_dquoted(o_string *as_string, if (ch != '\n') { next = i_peek(input); } - debug_printf_parse(": ch=%c (%d) escape=%d\n", + debug_printf_parse("\" ch=%c (%d) escape=%d\n", ch, ch, dest->o_escape); if (ch == '\\') { if (next == EOF) { @@ -5310,12 +5819,17 @@ static int parse_stream_dquoted(o_string *as_string, * "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. + * within double quotes by preceding it with a backslash." */ - if (strchr("$`\"\\", next) != NULL) { - o_addqchr(dest, i_getch(input)); + if (strchr("$`\"\\\n", next) != NULL) { + ch = i_getch(input); + if (ch != '\n') { + o_addqchr(dest, ch); + nommu_addchr(as_string, ch); + } } else { o_addqchr(dest, '\\'); + nommu_addchr(as_string, '\\'); } goto again; } @@ -5371,9 +5885,14 @@ static struct pipe *parse_stream(char **pstring, * found. When recursing, quote state is passed in via dest->o_escape. */ debug_printf_parse("parse_stream entered, end_trigger='%c'\n", - end_trigger ? : 'X'); + end_trigger ? end_trigger : 'X'); debug_enter(); + /* If very first arg is "" or '', dest.data may end up NULL. + * Preventing this: */ + o_addchr(&dest, '\0'); + dest.length = 0; + G.ifs = get_local_var_value("IFS"); if (G.ifs == NULL) G.ifs = " \t\n"; @@ -5410,10 +5929,17 @@ static struct pipe *parse_stream(char **pstring, if (heredoc_cnt) { syntax_error_unterm_str("here document"); - xfunc_die(); + goto parse_error; + } + /* end_trigger == '}' case errors out earlier, + * checking only ')' */ + if (end_trigger == ')') { + syntax_error_unterm_ch('('); /* exits */ + /* goto parse_error; */ } + if (done_word(&dest, &ctx)) { - xfunc_die(); + goto parse_error; } o_free(&dest); done_pipe(&ctx, PIPE_SEQ); @@ -5441,7 +5967,7 @@ static struct pipe *parse_stream(char **pstring, nommu_addchr(&ctx.as_string, ch); is_ifs = strchr(G.ifs, ch); is_special = strchr("<>;&|(){}#'" /* special outside of "str" */ - "\\$\"" USE_HUSH_TICK("`") /* always special */ + "\\$\"" IF_HUSH_TICK("`") /* always special */ , ch); if (!is_special && !is_ifs) { /* ordinary char */ @@ -5504,7 +6030,13 @@ static struct pipe *parse_stream(char **pstring, } if (end_trigger && end_trigger == ch - && (heredoc_cnt == 0 || end_trigger != ';') + && (ch != ';' || heredoc_cnt == 0) +#if ENABLE_HUSH_CASE + && (ch != ')' + || ctx.ctx_res_w != RES_MATCH + || (!dest.o_quoted && strcmp(dest.data, "esac") == 0) + ) +#endif ) { if (heredoc_cnt) { /* This is technically valid: @@ -5612,6 +6144,8 @@ static struct pipe *parse_stream(char **pstring, dest.o_assignment = NOT_ASSIGNMENT; } + /* Note: nommu_addchr(&ctx.as_string, ch) is already done */ + switch (ch) { case '#': if (dest.length == 0) { @@ -5632,13 +6166,22 @@ static struct pipe *parse_stream(char **pstring, syntax_error("\\"); xfunc_die(); } - o_addchr(&dest, '\\'); ch = i_getch(input); - nommu_addchr(&ctx.as_string, ch); - o_addchr(&dest, ch); - /* Example: echo Hello \2>file - * we need to know that word 2 is quoted */ - dest.o_quoted = 1; + if (ch != '\n') { + o_addchr(&dest, '\\'); + /*nommu_addchr(&ctx.as_string, '\\'); - already done */ + o_addchr(&dest, ch); + nommu_addchr(&ctx.as_string, ch); + /* Example: echo Hello \2>file + * we need to know that word 2 is quoted */ + dest.o_quoted = 1; + } +#if !BB_MMU + else { + /* It's "\". Remove trailing '\' from ctx.as_string */ + ctx.as_string.data[--ctx.as_string.length] = '\0'; + } +#endif break; case '$': if (handle_dollar(&ctx.as_string, &dest, input) != 0) { @@ -5658,10 +6201,7 @@ static struct pipe *parse_stream(char **pstring, nommu_addchr(&ctx.as_string, ch); if (ch == '\'') break; - if (dest.o_assignment == NOT_ASSIGNMENT) - o_addqchr(&dest, ch); - else - o_addchr(&dest, ch); + o_addqchr(&dest, ch); } break; case '"': @@ -5707,7 +6247,7 @@ static struct pipe *parse_stream(char **pstring, break; ch = i_getch(input); nommu_addchr(&ctx.as_string, ch); - if (ctx.ctx_res_w == RES_CASEI) { + if (ctx.ctx_res_w == RES_CASE_BODY) { ctx.ctx_dsemicolon = 1; ctx.ctx_res_w = RES_MATCH; break; @@ -5789,10 +6329,8 @@ static struct pipe *parse_stream(char **pstring, IF_HAS_KEYWORDS(struct parse_context *p2;) /* Clean up allocated tree. - * Samples for finding leaks on syntax error recovery path. - * Run them from interactive shell, watch pmap `pidof hush`. - * while if false; then false; fi do break; done - * (bash accepts it) + * Sample for finding leaks on syntax error recovery path. + * Run it from interactive shell, watch pmap `pidof hush`. * while if false; then false; fi; do break; fi * Samples to catch leaks at execution: * while if (true | {true;}); then echo ok; fi; do break; done @@ -5832,7 +6370,7 @@ static struct pipe *parse_stream(char **pstring, } /* Discard cached input, force prompt */ input->p = NULL; - USE_HUSH_INTERACTIVE(input->promptme = 1;) + IF_HUSH_INTERACTIVE(input->promptme = 1;) goto reset; } } @@ -5878,8 +6416,11 @@ static void block_signals(int second_time) unsigned mask; mask = (1 << SIGQUIT); - if (G_interactive_fd) + if (G_interactive_fd) { mask = (1 << SIGQUIT) | SPECIAL_INTERACTIVE_SIGS; + if (G_saved_tty_pgrp) /* we have ctty, job control sigs work */ + mask |= SPECIAL_JOB_SIGS; + } G.non_DFL_mask = mask; if (!second_time) @@ -5897,9 +6438,14 @@ static void block_signals(int second_time) second_time ? NULL : &G.inherited_set); /* POSIX allows shell to re-enable SIGCHLD * even if it was SIG_IGN on entry */ -// G.count_SIGCHLD++; /* ensure it is != G.handled_SIGCHLD */ +#if ENABLE_HUSH_FAST + G.count_SIGCHLD++; /* ensure it is != G.handled_SIGCHLD */ if (!second_time) - signal(SIGCHLD, SIG_DFL); // SIGCHLD_handler); + signal(SIGCHLD, SIGCHLD_handler); +#else + if (!second_time) + signal(SIGCHLD, SIG_DFL); +#endif } #if ENABLE_HUSH_JOB @@ -5963,6 +6509,7 @@ int hush_main(int argc, char **argv) }; int signal_mask_is_inited = 0; int opt; + unsigned builtin_argc; char **e; struct variable *cur_var; @@ -5992,20 +6539,56 @@ int hush_main(int argc, char **argv) } e++; } + /* reinstate HUSH_VERSION */ debug_printf_env("putenv '%s'\n", hush_version_str); - putenv((char *)hush_version_str); /* reinstate HUSH_VERSION */ + putenv((char *)hush_version_str); + + /* Export PWD */ + set_pwd_var(/*exp:*/ 1); + /* bash also exports SHLVL and _, + * and sets (but doesn't export) the following variables: + * BASH=/bin/bash + * BASH_VERSINFO=([0]="3" [1]="2" [2]="0" [3]="1" [4]="release" [5]="i386-pc-linux-gnu") + * BASH_VERSION='3.2.0(1)-release' + * HOSTTYPE=i386 + * MACHTYPE=i386-pc-linux-gnu + * OSTYPE=linux-gnu + * HOSTNAME= + * PPID= - we also do it elsewhere + * EUID= + * UID= + * GROUPS=() + * LINES= + * COLUMNS= + * BASH_ARGC=() + * BASH_ARGV=() + * BASH_LINENO=() + * BASH_SOURCE=() + * DIRSTACK=() + * PIPESTATUS=([0]="0") + * HISTFILE=//.bash_history + * HISTFILESIZE=500 + * HISTSIZE=500 + * MAILCHECK=60 + * PATH=/usr/gnu/bin:/usr/local/bin:/bin:/usr/bin:. + * SHELL=/bin/bash + * SHELLOPTS=braceexpand:emacs:hashall:histexpand:history:interactive-comments:monitor + * TERM=dumb + * OPTERR=1 + * OPTIND=1 + * IFS=$' \t\n' + * PS1='\s-\v\$ ' + * PS2='> ' + * PS4='+ ' + */ + #if ENABLE_FEATURE_EDITING G.line_input_state = new_line_input_t(FOR_SHELL); #endif 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(); - G.PS2 = "> "; -#endif + cmdedit_update_prompt(); if (setjmp(die_jmp)) { /* xfunc has failed! die die die */ @@ -6024,8 +6607,9 @@ int hush_main(int argc, char **argv) /* Parse options */ /* http://www.opengroup.org/onlinepubs/9699919799/utilities/sh.html */ + builtin_argc = 0; while (1) { - opt = getopt(argc, argv, "c:xins" + opt = getopt(argc, argv, "+c:xins" #if !BB_MMU "<:$:R:V:" # if ENABLE_HUSH_FUNCTIONS @@ -6037,15 +6621,42 @@ int hush_main(int argc, char **argv) break; switch (opt) { case 'c': - if (!G.root_pid) + /* Possibilities: + * sh ... -c 'script' + * sh ... -c 'script' ARG0 [ARG1...] + * On NOMMU, if builtin_argc != 0, + * sh ... -c 'builtin' [BARGV...] "" ARG0 [ARG1...] + * "" needs to be replaced with NULL + * and BARGV vector fed to builtin function. + * Note: this form never happens: + * sh ... -c 'builtin' [BARGV...] "" + */ + if (!G.root_pid) { G.root_pid = getpid(); + G.root_ppid = getppid(); + } G.global_argv = argv + optind; - if (!argv[optind]) { - /* -c 'script' (no params): prevent empty $0 */ - *--G.global_argv = argv[0]; - optind--; - } /* else -c 'script' PAR0 PAR1: $0 is PAR0 */ G.global_argc = argc - optind; + if (builtin_argc) { + /* -c 'builtin' [BARGV...] "" ARG0 [ARG1...] */ + const struct built_in_command *x; + + block_signals(0); /* 0: called 1st time */ + x = find_builtin(optarg); + if (x) { /* paranoia */ + G.global_argc -= builtin_argc; /* skip [BARGV...] "" */ + G.global_argv += builtin_argc; + G.global_argv[-1] = NULL; /* replace "" */ + G.last_exitcode = x->function(argv + optind - 1); + } + goto final_return; + } + if (!G.global_argv[0]) { + /* -c 'script' (no params): prevent empty $0 */ + G.global_argv--; /* points to argv[i] of 'script' */ + G.global_argv[0] = argv[0]; + G.global_argc--; + } /* else -c 'script' ARG0 [ARG1...]: $0 is ARG0 */ block_signals(0); /* 0: called 1st time */ parse_and_run_string(optarg); goto final_return; @@ -6065,9 +6676,13 @@ int hush_main(int argc, char **argv) case '$': G.root_pid = bb_strtou(optarg, &optarg, 16); optarg++; + G.root_ppid = bb_strtou(optarg, &optarg, 16); + optarg++; G.last_bg_pid = bb_strtou(optarg, &optarg, 16); optarg++; G.last_exitcode = bb_strtou(optarg, &optarg, 16); + optarg++; + builtin_argc = bb_strtou(optarg, &optarg, 16); # if ENABLE_HUSH_LOOPS optarg++; G.depth_of_loop = bb_strtou(optarg, &optarg, 16); @@ -6075,7 +6690,7 @@ int hush_main(int argc, char **argv) break; case 'R': case 'V': - set_local_var(xstrdup(optarg), 0, opt == 'R'); + set_local_var(xstrdup(optarg), /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ opt == 'R'); break; # if ENABLE_HUSH_FUNCTIONS case 'F': { @@ -6103,13 +6718,14 @@ int hush_main(int argc, char **argv) } } /* option parsing loop */ - if (!G.root_pid) + if (!G.root_pid) { G.root_pid = getpid(); + G.root_ppid = getppid(); + } /* If we are login shell... */ if (argv[0] && argv[0][0] == '-') { FILE *input; - /* TODO: what should argv be while sourcing /etc/profile? */ debug_printf("sourcing /etc/profile\n"); input = fopen_for_read("/etc/profile"); if (input != NULL) { @@ -6122,7 +6738,7 @@ int hush_main(int argc, char **argv) /* bash: after sourcing /etc/profile, * tries to source (in the given order): * ~/.bash_profile, ~/.bash_login, ~/.profile, - * stopping of first found. --noprofile turns this off. + * stopping on first found. --noprofile turns this off. * bash also sources ~/.bash_logout on exit. * If called as sh, skips .bash_XXX files. */ @@ -6153,8 +6769,8 @@ int hush_main(int argc, char **argv) * NB: don't forget to (re)run block_signals(0/1) as needed. */ - /* A shell is interactive if the '-i' flag was given, or if all of - * the following conditions are met: + /* 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 @@ -6163,54 +6779,57 @@ int hush_main(int argc, char **argv) */ #if ENABLE_HUSH_JOB if (isatty(STDIN_FILENO) && isatty(STDOUT_FILENO)) { - G.saved_tty_pgrp = tcgetpgrp(STDIN_FILENO); - debug_printf("saved_tty_pgrp:%d\n", G.saved_tty_pgrp); -//TODO: "interactive" and "have job control" are two different things. -//If tcgetpgrp fails here, "have job control" is false, but "interactive" -//should stay on! Currently, we mix these into one. - if (G.saved_tty_pgrp >= 0) { - /* try to dup stdin to high fd#, >= 255 */ - G_interactive_fd = fcntl(STDIN_FILENO, F_DUPFD, 255); + G_saved_tty_pgrp = tcgetpgrp(STDIN_FILENO); + debug_printf("saved_tty_pgrp:%d\n", G_saved_tty_pgrp); + if (G_saved_tty_pgrp < 0) + G_saved_tty_pgrp = 0; + + /* try to dup stdin 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) { - /* try to dup to any fd */ - G_interactive_fd = dup(STDIN_FILENO); - if (G_interactive_fd < 0) - /* give up */ - G_interactive_fd = 0; + /* give up */ + G_interactive_fd = 0; + G_saved_tty_pgrp = 0; } -// TODO: track & disallow any attempts of user -// to (inadvertently) close/redirect it } +// TODO: track & disallow any attempts of user +// to (inadvertently) close/redirect G_interactive_fd } debug_printf("interactive_fd:%d\n", G_interactive_fd); if (G_interactive_fd) { - pid_t shell_pgrp; - - /* We are indeed interactive shell, and we will perform - * job control. Setting up for that. */ - close_on_exec_on(G_interactive_fd); - /* If we were run as 'hush &', sleep until we are - * in the foreground (tty pgrp == our pgrp). - * If we get started under a job aware app (like bash), - * make sure we are now in charge so we don't fight over - * who gets the foreground */ - while (1) { - shell_pgrp = getpgrp(); - G.saved_tty_pgrp = tcgetpgrp(G_interactive_fd); - if (G.saved_tty_pgrp == shell_pgrp) - break; - /* send TTIN to ourself (should stop us) */ - kill(- shell_pgrp, SIGTTIN); + + if (G_saved_tty_pgrp) { + /* If we were run as 'hush &', sleep until we are + * in the foreground (tty pgrp == our pgrp). + * If we get started under a job aware app (like bash), + * make sure we are now in charge so we don't fight over + * who gets the foreground */ + while (1) { + pid_t shell_pgrp = getpgrp(); + G_saved_tty_pgrp = tcgetpgrp(G_interactive_fd); + if (G_saved_tty_pgrp == shell_pgrp) + break; + /* send TTIN to ourself (should stop us) */ + kill(- shell_pgrp, SIGTTIN); + } } + /* Block some signals */ block_signals(signal_mask_is_inited); - /* Set other signals to restore saved_tty_pgrp */ - set_fatal_handlers(); - /* 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()); + + if (G_saved_tty_pgrp) { + /* Set other signals to restore saved_tty_pgrp */ + set_fatal_handlers(); + /* Put ourselves in our own process group + * (bash, too, does this only if ctty is available) */ + bb_setpgrp(); /* is the same as setpgid(our_pid, our_pid); */ + /* Grab control of the terminal */ + tcsetpgrp(G_interactive_fd, getpid()); + } /* -1 is special - makes xfuncs longjmp, not exit * (we reset die_sleep = 0 whereever we [v]fork) */ enable_restore_tty_pgrp_on_exit(); /* sets die_sleep = -1 */ @@ -6247,8 +6866,13 @@ int hush_main(int argc, char **argv) */ if (!ENABLE_FEATURE_SH_EXTRA_QUIET && G_interactive_fd) { - printf("\n\n%s hush - the humble shell\n", bb_banner); - printf("Enter 'help' for a list of built-in commands.\n\n"); + /* note: ash and hush share this string */ + printf("\n\n%s %s\n" + IF_HUSH_HELP("Enter 'help' for a list of built-in commands.\n") + "\n", + bb_banner, + "hush - the humble shell" + ); } parse_and_run_file(stdin); @@ -6274,7 +6898,16 @@ int hush_main(int argc, char **argv) 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"); + bb_error_msg("lash is deprecated, please use hush instead"); + return hush_main(argc, argv); +} +#endif + +#if ENABLE_MSH +int msh_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int msh_main(int argc, char **argv) +{ + //bb_error_msg("msh is deprecated, please use hush instead"); return hush_main(argc, argv); } #endif @@ -6283,32 +6916,39 @@ int lash_main(int argc, char **argv) /* * Built-ins */ -static int builtin_true(char **argv UNUSED_PARAM) +static int FAST_FUNC builtin_true(char **argv UNUSED_PARAM) { return 0; } -static int builtin_test(char **argv) +static int run_applet_main(char **argv, int (*applet_main_func)(int argc, char **argv)) { int argc = 0; while (*argv) { argc++; argv++; } - return test_main(argc, argv - argc); + return applet_main_func(argc, argv - argc); } -static int builtin_echo(char **argv) +static int FAST_FUNC builtin_test(char **argv) { - int argc = 0; - while (*argv) { - argc++; - argv++; - } - return echo_main(argc, argv - argc); + return run_applet_main(argv, test_main); +} + +static int FAST_FUNC builtin_echo(char **argv) +{ + return run_applet_main(argv, echo_main); +} + +#if ENABLE_PRINTF +static int FAST_FUNC builtin_printf(char **argv) +{ + return run_applet_main(argv, printf_main); } +#endif -static int builtin_eval(char **argv) +static int FAST_FUNC builtin_eval(char **argv) { int rcode = EXIT_SUCCESS; @@ -6325,7 +6965,7 @@ static int builtin_eval(char **argv) return rcode; } -static int builtin_cd(char **argv) +static int FAST_FUNC builtin_cd(char **argv) { const char *newdir = argv[1]; if (newdir == NULL) { @@ -6333,32 +6973,40 @@ static int builtin_cd(char **argv) * bash says "bash: cd: HOME not set" and does nothing * (exitcode 1) */ - newdir = getenv("HOME") ? : "/"; + const char *home = get_local_var_value("HOME"); + newdir = home ? home : "/"; } if (chdir(newdir)) { /* Mimic bash message exactly */ bb_perror_msg("cd: %s", newdir); return EXIT_FAILURE; } - set_cwd(); + /* Read current dir (get_cwd(1) is inside) and set PWD. + * Note: do not enforce exporting. If PWD was unset or unexported, + * set it again, but do not export. bash does the same. + */ + set_pwd_var(/*exp:*/ 0); return EXIT_SUCCESS; } -static int builtin_exec(char **argv) +static int FAST_FUNC builtin_exec(char **argv) { if (*++argv == NULL) return EXIT_SUCCESS; /* bash does this */ - { -#if !BB_MMU - nommu_save_t dummy; -#endif - /* TODO: if exec fails, bash does NOT exit! We do... */ - pseudo_exec_argv(&dummy, argv, 0, NULL); - /* never returns */ - } + + /* Careful: we can end up here after [v]fork. Do not restore + * tty pgrp then, only top-level shell process does that */ + if (G_saved_tty_pgrp && getpid() == G.root_pid) + tcsetpgrp(G_interactive_fd, G_saved_tty_pgrp); + + /* TODO: if exec fails, bash does NOT exit! We do. + * We'll need to undo sigprocmask (it's inside execvp_or_die) + * and tcsetpgrp, and this is inherently racy. + */ + execvp_or_die(argv); } -static int builtin_exit(char **argv) +static int FAST_FUNC builtin_exit(char **argv) { debug_printf_exec("%s()\n", __func__); @@ -6386,17 +7034,16 @@ static int builtin_exit(char **argv) static void print_escaped(const char *s) { + if (*s == '\'') + goto squote; do { - if (*s != '\'') { - const char *p; - - p = strchrnul(s, '\''); - /* print 'xxxx', possibly just '' */ - printf("'%.*s'", (int)(p - s), s); - if (*p == '\0') - break; - s = p; - } + const char *p = strchrnul(s, '\''); + /* print 'xxxx', possibly just '' */ + printf("'%.*s'", (int)(p - s), s); + if (*p == '\0') + break; + s = p; + squote: /* s points to '; print "'''...'''" */ putchar('"'); do putchar('\''); while (*++s == '\''); @@ -6404,44 +7051,12 @@ static void print_escaped(const char *s) } while (*s); } -static int builtin_export(char **argv) -{ - unsigned opt_unexport; - - if (argv[1] == NULL) { - char **e = environ; - if (e) { - while (*e) { -#if 0 - puts(*e++); -#else - /* ash emits: export VAR='VAL' - * bash: declare -x VAR="VAL" - * we follow ash example */ - const char *s = *e++; - const char *p = strchr(s, '='); - - if (!p) /* wtf? take next variable */ - continue; - /* export var= */ - printf("export %.*s", (int)(p - s) + 1, s); - print_escaped(p + 1); - putchar('\n'); -#endif - } - /*fflush(stdout); - done after each builtin anyway */ - } - return EXIT_SUCCESS; - } - -#if ENABLE_HUSH_EXPORT_N - opt_unexport = getopt32(argv, "+n"); /* "+": stop at 1st non-option */ - argv += optind; -#else - opt_unexport = 0; - argv++; +#if !ENABLE_HUSH_LOCAL +#define helper_export_local(argv, exp, lvl) \ + helper_export_local(argv, exp) #endif - +static void helper_export_local(char **argv, int exp, int lvl) +{ do { char *name = *argv; @@ -6451,7 +7066,7 @@ static int builtin_export(char **argv) struct variable *var; var = get_local_var(name); - if (opt_unexport) { + if (exp == -1) { /* unexporting? */ /* export -n NAME (without =VALUE) */ if (var) { var->flg_export = 0; @@ -6460,33 +7075,90 @@ static int builtin_export(char **argv) } /* else: export -n NOT_EXISTING_VAR: no-op */ continue; } - /* export NAME (without =VALUE) */ - if (var) { - var->flg_export = 1; - debug_printf_env("%s: putenv '%s'\n", __func__, var->varstr); - putenv(var->varstr); - continue; + if (exp == 1) { /* exporting? */ + /* export NAME (without =VALUE) */ + if (var) { + var->flg_export = 1; + debug_printf_env("%s: putenv '%s'\n", __func__, var->varstr); + putenv(var->varstr); + continue; + } } /* Exporting non-existing variable. * bash does not put it in environment, * but remembers that it is exported, * and does put it in env when it is set later. * We just set it to "" and export. */ + /* Or, it's "local NAME" (without =VALUE). + * bash sets the value to "". */ name = xasprintf("%s=", name); } else { - /* (Un)exporting NAME=VALUE */ + /* (Un)exporting/making local NAME=VALUE */ name = xstrdup(name); } - set_local_var(name, - /*export:*/ (opt_unexport ? -1 : 1), - /*readonly:*/ 0 - ); + set_local_var(name, /*exp:*/ exp, /*lvl:*/ lvl, /*ro:*/ 0); } while (*++argv); +} + +static int FAST_FUNC builtin_export(char **argv) +{ + unsigned opt_unexport; + +#if ENABLE_HUSH_EXPORT_N + /* "!": do not abort on errors */ + opt_unexport = getopt32(argv, "!n"); + if (opt_unexport == (uint32_t)-1) + return EXIT_FAILURE; + argv += optind; +#else + opt_unexport = 0; + argv++; +#endif + if (argv[0] == NULL) { + char **e = environ; + if (e) { + while (*e) { +#if 0 + puts(*e++); +#else + /* ash emits: export VAR='VAL' + * bash: declare -x VAR="VAL" + * we follow ash example */ + const char *s = *e++; + const char *p = strchr(s, '='); + + if (!p) /* wtf? take next variable */ + continue; + /* export var= */ + printf("export %.*s", (int)(p - s) + 1, s); + print_escaped(p + 1); + putchar('\n'); +#endif + } + /*fflush(stdout); - done after each builtin anyway */ + } + return EXIT_SUCCESS; + } + + helper_export_local(argv, (opt_unexport ? -1 : 1), 0); + + return EXIT_SUCCESS; +} + +#if ENABLE_HUSH_LOCAL +static int FAST_FUNC builtin_local(char **argv) +{ + if (G.func_nest_level == 0) { + bb_error_msg("%s: not in a function", argv[0]); + return EXIT_FAILURE; /* bash compat */ + } + helper_export_local(argv, 0, G.func_nest_level); return EXIT_SUCCESS; } +#endif -static int builtin_trap(char **argv) +static int FAST_FUNC builtin_trap(char **argv) { int sig; char *new_cmd; @@ -6502,6 +7174,10 @@ static int builtin_trap(char **argv) if (G.traps[i]) { printf("trap -- "); print_escaped(G.traps[i]); + /* note: bash adds "SIG", but only if invoked + * as "bash". If called as "sh", or if set -o posix, + * then it prints short signal names. + * We are printing short names: */ printf(" %s\n", get_signame(i)); } } @@ -6575,15 +7251,49 @@ static int builtin_trap(char **argv) goto process_sig_list; } +/* http://www.opengroup.org/onlinepubs/9699919799/utilities/type.html */ +static int FAST_FUNC builtin_type(char **argv) +{ + int ret = EXIT_SUCCESS; + + while (*++argv) { + const char *type; + char *path = NULL; + + if (0) {} /* make conditional compile easier below */ + /*else if (find_alias(*argv)) + type = "an alias";*/ +#if ENABLE_HUSH_FUNCTIONS + else if (find_function(*argv)) + type = "a function"; +#endif + else if (find_builtin(*argv)) + type = "a shell builtin"; + else if ((path = find_in_path(*argv)) != NULL) + type = path; + else { + bb_error_msg("type: %s: not found", *argv); + ret = EXIT_FAILURE; + continue; + } + + printf("%s is %s\n", *argv, type); + free(path); + } + + return ret; +} + #if ENABLE_HUSH_JOB /* built-in 'fg' and 'bg' handler */ -static int builtin_fg_bg(char **argv) +static int FAST_FUNC 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) { @@ -6608,7 +7318,7 @@ static int builtin_fg_bg(char **argv) found: /* TODO: bash prints a string representation * of job being foregrounded (like "sleep 1 | cat") */ - if (argv[0][0] == 'f') { + if (argv[0][0] == 'f' && G_saved_tty_pgrp) { /* Put the job into the foreground. */ tcsetpgrp(G_interactive_fd, pi->pgrp); } @@ -6639,23 +7349,24 @@ static int builtin_fg_bg(char **argv) #endif #if ENABLE_HUSH_HELP -static int builtin_help(char **argv UNUSED_PARAM) +static int FAST_FUNC builtin_help(char **argv UNUSED_PARAM) { const struct built_in_command *x; - printf("\n" + printf( "Built-in commands:\n" "------------------\n"); - for (x = bltins; x != &bltins[ARRAY_SIZE(bltins)]; x++) { - printf("%s\t%s\n", x->cmd, x->descr); + for (x = bltins1; x != &bltins1[ARRAY_SIZE(bltins1)]; x++) { + if (x->descr) + printf("%s\t%s\n", x->cmd, x->descr); } - printf("\n\n"); + bb_putchar('\n'); return EXIT_SUCCESS; } #endif #if ENABLE_HUSH_JOB -static int builtin_jobs(char **argv UNUSED_PARAM) +static int FAST_FUNC builtin_jobs(char **argv UNUSED_PARAM) { struct pipe *job; const char *status_string; @@ -6673,11 +7384,15 @@ static int builtin_jobs(char **argv UNUSED_PARAM) #endif #if HUSH_DEBUG -static int builtin_memleak(char **argv UNUSED_PARAM) +static int FAST_FUNC builtin_memleak(char **argv UNUSED_PARAM) { void *p; unsigned long l; +# ifdef M_TRIM_THRESHOLD + /* Optional. Reduces probability of false positives */ + malloc_trim(0); +# endif /* Crude attempt to find where "free memory" starts, * sans fragmentation. */ p = malloc(240); @@ -6689,7 +7404,7 @@ static int builtin_memleak(char **argv UNUSED_PARAM) if (!G.memleak_value) G.memleak_value = l; - + l -= G.memleak_value; if ((long)l < 0) l = 0; @@ -6702,13 +7417,13 @@ static int builtin_memleak(char **argv UNUSED_PARAM) } #endif -static int builtin_pwd(char **argv UNUSED_PARAM) +static int FAST_FUNC builtin_pwd(char **argv UNUSED_PARAM) { - puts(set_cwd()); + puts(get_cwd(0)); return EXIT_SUCCESS; } -static int builtin_read(char **argv) +static int FAST_FUNC builtin_read(char **argv) { char *string; const char *name = "REPLY"; @@ -6727,7 +7442,7 @@ static int builtin_read(char **argv) //TODO: bash unbackslashes input, splits words and puts them in argv[i] string = xmalloc_reads(STDIN_FILENO, xasprintf("%s=", name), NULL); - return set_local_var(string, 0, 0); + return set_local_var(string, /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ 0); } /* http://www.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#set @@ -6751,7 +7466,7 @@ static int builtin_read(char **argv) * * So far, we only support "set -- [argument...]" and some of the short names. */ -static int builtin_set(char **argv) +static int FAST_FUNC builtin_set(char **argv) { int n; char **pp, **g_argv; @@ -6810,7 +7525,7 @@ static int builtin_set(char **argv) return EXIT_FAILURE; } -static int builtin_shift(char **argv) +static int FAST_FUNC builtin_shift(char **argv) { int n = 1; if (argv[1]) { @@ -6830,8 +7545,9 @@ static int builtin_shift(char **argv) return EXIT_FAILURE; } -static int builtin_source(char **argv) +static int FAST_FUNC builtin_source(char **argv) { + char *arg_path; FILE *input; save_arg_t sv; #if ENABLE_HUSH_FUNCTIONS @@ -6841,8 +7557,11 @@ static int builtin_source(char **argv) if (*++argv == NULL) return EXIT_FAILURE; -// TODO: search through $PATH is missing - input = fopen_or_warn(*argv, "r"); + if (strchr(*argv, '/') == NULL && (arg_path = find_in_path(*argv)) != NULL) { + input = fopen_for_read(arg_path); + free(arg_path); + } else + input = fopen_or_warn(*argv, "r"); if (!input) { /* bb_perror_msg("%s", *argv); - done by fopen_or_warn */ return EXIT_FAILURE; @@ -6867,7 +7586,7 @@ static int builtin_source(char **argv) return G.last_exitcode; } -static int builtin_umask(char **argv) +static int FAST_FUNC builtin_umask(char **argv) { int rc; mode_t mask; @@ -6899,39 +7618,25 @@ static int builtin_umask(char **argv) } /* http://www.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#unset */ -static int builtin_unset(char **argv) +static int FAST_FUNC builtin_unset(char **argv) { int ret; - char var; - char *arg; - - if (!*++argv) - return EXIT_SUCCESS; + unsigned opts; - var = 0; - while ((arg = *argv) != NULL && arg[0] == '-') { - arg++; - do { - switch (*arg) { - case 'v': - case 'f': - if (var == 0 || var == *arg) { - var = *arg; - break; - } - /* else: unset -vf, which is illegal. - * fall through */ - default: - bb_error_msg("unset: %s: invalid option", *argv); - return EXIT_FAILURE; - } - } while (*++arg); - argv++; + /* "!": do not abort on errors */ + /* "+": stop at 1st non-option */ + opts = getopt32(argv, "!+vf"); + if (opts == (unsigned)-1) + return EXIT_FAILURE; + if (opts == 3) { + bb_error_msg("unset: -v and -f are exclusive"); + return EXIT_FAILURE; } + argv += optind; ret = EXIT_SUCCESS; while (*argv) { - if (var != 'f') { + if (!(opts & 2)) { /* not -f */ if (unset_local_var(*argv)) { /* unset doesn't fail. * Error is when one tries to unset RO var. @@ -6950,7 +7655,7 @@ static int builtin_unset(char **argv) } /* http://www.opengroup.org/onlinepubs/9699919799/utilities/wait.html */ -static int builtin_wait(char **argv) +static int FAST_FUNC builtin_wait(char **argv) { int ret = EXIT_SUCCESS; int status, sig; @@ -7034,7 +7739,7 @@ static unsigned parse_numeric_argv1(char **argv, unsigned def, unsigned def_min) #endif #if ENABLE_HUSH_LOOPS -static int builtin_break(char **argv) +static int FAST_FUNC builtin_break(char **argv) { unsigned depth; if (G.depth_of_loop == 0) { @@ -7052,7 +7757,7 @@ static int builtin_break(char **argv) return EXIT_SUCCESS; } -static int builtin_continue(char **argv) +static int FAST_FUNC builtin_continue(char **argv) { G.flag_break_continue = 1; /* BC_CONTINUE = 2 = 1+1 */ return builtin_break(argv); @@ -7060,7 +7765,7 @@ static int builtin_continue(char **argv) #endif #if ENABLE_HUSH_FUNCTIONS -static int builtin_return(char **argv) +static int FAST_FUNC builtin_return(char **argv) { int rc;