X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=shell%2Fhush.c;h=a68986329c9c08dfbe2d387a8d68f35d429377c0;hb=1e660422b16510f8bcdb764d632bb1da391c4a35;hp=7683a3749eb61cf6a505d6bb47f85e48fc19a4ca;hpb=26ad94bedcc6a4aa3feb07ea032709bcd517ee46;p=oweals%2Fbusybox.git diff --git a/shell/hush.c b/shell/hush.c index 7683a3749..a68986329 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -41,15 +41,28 @@ * * TODOs: * grep for "TODO" and fix (some of them are easy) + * make complex ${var%...} constructs support optional + * make here documents optional * special variables (done: PWD, PPID, RANDOM) + * follow IFS rules more precisely, including update semantics * tilde expansion * aliases - * kill %jobspec - * follow IFS rules more precisely, including update semantics * builtins mandated by standards we don't support: - * [un]alias, command, fc, getopts, newgrp, readonly, times - * make complex ${var%...} constructs support optional - * make here documents optional + * [un]alias, command, fc, getopts, times: + * command -v CMD: print "/path/to/CMD" + * prints "CMD" for builtins + * prints "alias ALIAS='EXPANSION'" for aliases + * prints nothing and sets $? to 1 if not found + * command -V CMD: print "CMD is /path/CMD|a shell builtin|etc" + * command [-p] CMD: run CMD, even if a function CMD also exists + * (can use this to override standalone shell as well) + * -p: use default $PATH + * command BLTIN: disables special-ness (e.g. errors do not abort) + * getopts: getopt() for shells + * times: print getrusage(SELF/CHILDREN).ru_utime/ru_stime + * fc -l[nr] [BEG] [END]: list range of commands in history + * fc [-e EDITOR] [BEG] [END]: edit/rerun range of commands + * fc -s [PAT=REP] [CMD]: rerun CMD, replacing PAT with REP * * Bash compat TODO: * redirection of stdout+stderr: &> and >& @@ -65,8 +78,13 @@ * The EXPR is evaluated according to ARITHMETIC EVALUATION. * This is exactly equivalent to let "EXPR". * $[EXPR]: synonym for $((EXPR)) + * indirect expansion: ${!VAR} + * substring op on @: ${@:n:m} * * Won't do: + * Some builtins mandated by standards: + * newgrp [GRP]: not a builtin in bash but a suid binary + * which spawns a new shell with new group ID * In bash, export builtin is 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" @@ -82,35 +100,6 @@ * $ "export" i=`echo 'aaa bbb'`; echo "$i" * aaa */ -#if !(defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) \ - || defined(__APPLE__) \ - ) -# include /* for malloc_trim */ -#endif -#include -/* #include */ -#if ENABLE_HUSH_CASE -# include -#endif -#include /* for setting $HOSTNAME */ - -#include "busybox.h" /* for APPLET_IS_NOFORK/NOEXEC */ -#include "unicode.h" -#include "shell_common.h" -#include "math.h" -#include "match.h" -#if ENABLE_HUSH_RANDOM_SUPPORT -# include "random.h" -#else -# define CLEAR_RANDOM_T(rnd) ((void)0) -#endif -#ifndef F_DUPFD_CLOEXEC -# define F_DUPFD_CLOEXEC F_DUPFD -#endif -#ifndef PIPE_BUF -# define PIPE_BUF 4096 /* amount of buffering in a pipe */ -#endif - //config:config HUSH //config: bool "hush" //config: default y @@ -128,9 +117,7 @@ //config:config HUSH_BASH_COMPAT //config: bool "bash-compatible extensions" //config: default y -//config: depends on HUSH -//config: help -//config: Enable bash-compatible extensions. +//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH //config: //config:config HUSH_BRACE_EXPANSION //config: bool "Brace expansion" @@ -139,17 +126,10 @@ //config: help //config: Enable {abc,def} extension. //config: -//config:config HUSH_HELP -//config: bool "help builtin" -//config: default y -//config: depends on HUSH -//config: help -//config: Enable help builtin in hush. Code size + ~1 kbyte. -//config: //config:config HUSH_INTERACTIVE //config: bool "Interactive mode" //config: default y -//config: depends on HUSH +//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH //config: help //config: Enable interactive mode (prompt and command editing). //config: Without this, hush simply reads and executes commands @@ -160,8 +140,6 @@ //config: bool "Save command history to .hush_history" //config: default y //config: depends on HUSH_INTERACTIVE && FEATURE_EDITING_SAVEHISTORY -//config: help -//config: Enable history saving in hush. //config: //config:config HUSH_JOB //config: bool "Job control" @@ -175,42 +153,38 @@ //config: but no separate process group is formed. //config: //config:config HUSH_TICK -//config: bool "Process substitution" +//config: bool "Support process substitution" //config: default y -//config: depends on HUSH +//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH //config: help -//config: Enable process substitution `command` and $(command) in hush. +//config: Enable `command` and $(command). //config: //config:config HUSH_IF //config: bool "Support if/then/elif/else/fi" //config: default y -//config: depends on HUSH -//config: help -//config: Enable if/then/elif/else/fi in hush. +//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH //config: //config:config HUSH_LOOPS //config: bool "Support for, while and until loops" //config: default y -//config: depends on HUSH -//config: help -//config: Enable for, while and until loops in hush. +//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH //config: //config:config HUSH_CASE //config: bool "Support case ... esac statement" //config: default y -//config: depends on HUSH +//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH //config: help -//config: Enable case ... esac statement in hush. +400 bytes. +//config: Enable case ... esac statement. +400 bytes. //config: //config:config HUSH_FUNCTIONS //config: bool "Support funcname() { commands; } syntax" //config: default y -//config: depends on HUSH +//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH //config: help -//config: Enable support for shell functions in hush. +800 bytes. +//config: Enable support for shell functions. +800 bytes. //config: //config:config HUSH_LOCAL -//config: bool "Support local builtin" +//config: bool "local builtin" //config: default y //config: depends on HUSH_FUNCTIONS //config: help @@ -219,40 +193,115 @@ //config:config HUSH_RANDOM_SUPPORT //config: bool "Pseudorandom generator and $RANDOM variable" //config: default y -//config: depends on HUSH +//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH //config: help //config: Enable pseudorandom generator and dynamic variable "$RANDOM". //config: Each read of "$RANDOM" will generate a new pseudorandom value. //config: +//config:config HUSH_MODE_X +//config: bool "Support 'hush -x' option and 'set -x' command" +//config: default y +//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH +//config: help +//config: This instructs hush to print commands before execution. +//config: Adds ~300 bytes. +//config: +//config:config HUSH_ECHO +//config: bool "echo builtin" +//config: default y +//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH +//config: +//config:config HUSH_PRINTF +//config: bool "printf builtin" +//config: default y +//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH +//config: +//config:config HUSH_TEST +//config: bool "test builtin" +//config: default y +//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH +//config: +//config:config HUSH_HELP +//config: bool "help builtin" +//config: default y +//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH +//config: +//config:config HUSH_EXPORT +//config: bool "export builtin" +//config: default y +//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH +//config: //config:config HUSH_EXPORT_N //config: bool "Support 'export -n' option" //config: default y -//config: depends on HUSH +//config: depends on HUSH_EXPORT //config: help //config: export -n unexports variables. It is a bash extension. //config: -//config:config HUSH_MODE_X -//config: bool "Support 'hush -x' option and 'set -x' command" +//config:config HUSH_READONLY +//config: bool "readonly builtin" //config: default y -//config: depends on HUSH //config: help -//config: This instructs hush to print commands before execution. -//config: Adds ~300 bytes. +//config: Enable support for read-only variables. //config: -//config:config MSH -//config: bool "msh (deprecated: aliased to hush)" -//config: default n -//config: select HUSH -//config: help -//config: msh is deprecated and will be removed, please migrate to hush. +//config:config HUSH_KILL +//config: bool "kill builtin (supports kill %jobspec)" +//config: default y +//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH +//config: +//config:config HUSH_WAIT +//config: bool "wait builtin" +//config: default y +//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH +//config: +//config:config HUSH_TRAP +//config: bool "trap builtin" +//config: default y +//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH +//config: +//config:config HUSH_TYPE +//config: bool "type builtin" +//config: default y +//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH +//config: +//config:config HUSH_READ +//config: bool "read builtin" +//config: default y +//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH +//config: +//config:config HUSH_SET +//config: bool "set builtin" +//config: default y +//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH //config: +//config:config HUSH_UNSET +//config: bool "unset builtin" +//config: default y +//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH +//config: +//config:config HUSH_ULIMIT +//config: bool "ulimit builtin" +//config: default y +//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH +//config: +//config:config HUSH_UMASK +//config: bool "umask builtin" +//config: default y +//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH +//config: +//config:config HUSH_MEMLEAK +//config: bool "memleak builtin (debugging)" +//config: default n +//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH //applet:IF_HUSH(APPLET(hush, BB_DIR_BIN, BB_SUID_DROP)) -//applet:IF_MSH(APPLET(msh, BB_DIR_BIN, BB_SUID_DROP)) -//applet:IF_FEATURE_SH_IS_HUSH(APPLET_ODDNAME(sh, hush, BB_DIR_BIN, BB_SUID_DROP, sh)) -//applet:IF_FEATURE_BASH_IS_HUSH(APPLET_ODDNAME(bash, hush, BB_DIR_BIN, BB_SUID_DROP, bash)) +// APPLET_ODDNAME:name main location suid_type help +//applet:IF_SH_IS_HUSH( APPLET_ODDNAME(sh, hush, BB_DIR_BIN, BB_SUID_DROP, hush)) +//applet:IF_BASH_IS_HUSH(APPLET_ODDNAME(bash, hush, BB_DIR_BIN, BB_SUID_DROP, hush)) //kbuild:lib-$(CONFIG_HUSH) += hush.o match.o shell_common.o +//kbuild:lib-$(CONFIG_SH_IS_HUSH) += hush.o match.o shell_common.o +//kbuild:lib-$(CONFIG_BASH_IS_HUSH) += hush.o match.o shell_common.o //kbuild:lib-$(CONFIG_HUSH_RANDOM_SUPPORT) += random.o /* -i (interactive) and -s (read stdin) are also accepted, @@ -261,21 +310,47 @@ * therefore we don't show them either. */ //usage:#define hush_trivial_usage -//usage: "[-nxl] [-c 'SCRIPT' [ARG0 [ARGS]] / FILE [ARGS]]" +//usage: "[-enxl] [-c 'SCRIPT' [ARG0 [ARGS]] / FILE [ARGS]]" //usage:#define hush_full_usage "\n\n" //usage: "Unix shell interpreter" -//usage:#define msh_trivial_usage hush_trivial_usage -//usage:#define msh_full_usage hush_full_usage +#if !(defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) \ + || defined(__APPLE__) \ + ) +# include /* for malloc_trim */ +#endif +#include +/* #include */ +#if ENABLE_HUSH_CASE +# include +#endif +#include /* for setting $HOSTNAME */ + +#include "busybox.h" /* for APPLET_IS_NOFORK/NOEXEC */ +#include "unicode.h" +#include "shell_common.h" +#include "math.h" +#include "match.h" +#if ENABLE_HUSH_RANDOM_SUPPORT +# include "random.h" +#else +# define CLEAR_RANDOM_T(rnd) ((void)0) +#endif +#ifndef F_DUPFD_CLOEXEC +# define F_DUPFD_CLOEXEC F_DUPFD +#endif +#ifndef PIPE_BUF +# define PIPE_BUF 4096 /* amount of buffering in a pipe */ +#endif + -//usage:#if ENABLE_FEATURE_SH_IS_HUSH -//usage:# define sh_trivial_usage hush_trivial_usage -//usage:# define sh_full_usage hush_full_usage -//usage:#endif -//usage:#if ENABLE_FEATURE_BASH_IS_HUSH -//usage:# define bash_trivial_usage hush_trivial_usage -//usage:# define bash_full_usage hush_full_usage -//usage:#endif +/* So far, all bash compat is controlled by one config option */ +/* Separate defines document which part of code implements what */ +#define BASH_PATTERN_SUBST ENABLE_HUSH_BASH_COMPAT +#define BASH_SUBSTR ENABLE_HUSH_BASH_COMPAT +#define BASH_SOURCE ENABLE_HUSH_BASH_COMPAT +#define BASH_HOSTNAME_VAR ENABLE_HUSH_BASH_COMPAT +#define BASH_TEST2 (ENABLE_HUSH_BASH_COMPAT && ENABLE_HUSH_TEST) /* Build knobs */ @@ -352,18 +427,19 @@ #define debug_printf_expand(...) do {} while (0) #define debug_printf_varexp(...) do {} while (0) #define debug_printf_glob(...) do {} while (0) +#define debug_printf_redir(...) do {} while (0) #define debug_printf_list(...) do {} while (0) #define debug_printf_subst(...) do {} while (0) #define debug_printf_clean(...) do {} while (0) #define ERR_PTR ((void*)(long)1) -#define JOB_STATUS_FORMAT "[%d] %-22s %.40s\n" +#define JOB_STATUS_FORMAT "[%u] %-22s %.40s\n" #define _SPECIAL_VARS_STR "_*@$!?#" #define SPECIAL_VARS_STR ("_*@$!?#" + 1) #define NUMERIC_SPECVARS_STR ("_*@$!?#" + 3) -#if ENABLE_HUSH_BASH_COMPAT +#if BASH_PATTERN_SUBST /* Support / and // replace ops */ /* Note that // is stored as \ in "encoded" string representation */ # define VAR_ENCODED_SUBST_OPS "\\/%#:-=+?" @@ -469,11 +545,7 @@ typedef struct in_str { int peek_buf[2]; int last_char; FILE *file; - 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 */ @@ -528,7 +600,7 @@ struct command { smallint cmd_type; /* CMD_xxx */ #define CMD_NORMAL 0 #define CMD_SUBSHELL 1 -#if ENABLE_HUSH_BASH_COMPAT +#if BASH_TEST2 /* used for "[[ EXPR ]]" */ # define CMD_SINGLEWORD_NOGLOB 2 #endif @@ -578,7 +650,7 @@ struct pipe { int alive_cmds; /* number of commands running (not exited) */ int stopped_cmds; /* number of commands alive, but stopped */ #if ENABLE_HUSH_JOB - int jobid; /* job number */ + unsigned jobid; /* job number */ pid_t pgrp; /* process group ID for the job */ char *cmdtext; /* name of job */ #endif @@ -588,10 +660,10 @@ struct pipe { IF_HAS_KEYWORDS(smallint res_word;) /* needed for if, for, while, until... */ }; typedef enum pipe_style { - PIPE_SEQ = 1, - PIPE_AND = 2, - PIPE_OR = 3, - PIPE_BG = 4, + PIPE_SEQ = 0, + PIPE_AND = 1, + PIPE_OR = 2, + PIPE_BG = 3, } pipe_style; /* Is there anything in this pipe at all? */ #define IS_NULL_PIPE(pi) \ @@ -699,6 +771,7 @@ struct function { static const char o_opt_strings[] ALIGN1 = "pipefail\0" "noexec\0" + "errexit\0" #if ENABLE_HUSH_MODE_X "xtrace\0" #endif @@ -706,6 +779,7 @@ static const char o_opt_strings[] ALIGN1 = enum { OPT_O_PIPEFAIL, OPT_O_NOEXEC, + OPT_O_ERREXIT, #if ENABLE_HUSH_MODE_X OPT_O_XTRACE, #endif @@ -755,13 +829,32 @@ struct globals { #endif #if ENABLE_HUSH_JOB int run_list_level; - int last_jobid; + unsigned last_jobid; pid_t saved_tty_pgrp; struct pipe *job_list; # define G_saved_tty_pgrp (G.saved_tty_pgrp) #else # define G_saved_tty_pgrp 0 #endif + /* How deeply are we in context where "set -e" is ignored */ + int errexit_depth; + /* "set -e" rules (do we follow them correctly?): + * Exit if pipe, list, or compound command exits with a non-zero status. + * Shell does not exit if failed command is part of condition in + * if/while, part of && or || list except the last command, any command + * in a pipe but the last, or if the command's return value is being + * inverted with !. If a compound command other than a subshell returns a + * non-zero status because a command failed while -e was being ignored, the + * shell does not exit. A trap on ERR, if set, is executed before the shell + * exits [ERR is a bashism]. + * + * If a compound command or function executes in a context where -e is + * ignored, none of the commands executed within are affected by the -e + * setting. If a compound command or function sets -e while executing in a + * context where -e is ignored, that setting does not have any effect until + * the compound command or the command containing the function call completes. + */ + char o_opt[NUM_OPT_O]; #if ENABLE_HUSH_MODE_X # define G_x_mode (G.o_opt[OPT_O_XTRACE]) @@ -785,8 +878,14 @@ struct globals { smallint exiting; /* used to prevent EXIT trap recursion */ /* These four support $?, $#, and $1 */ smalluint last_exitcode; + smalluint last_bg_pid_exitcode; +#if ENABLE_HUSH_SET /* are global_argv and global_argv[1..n] malloced? (note: not [0]) */ smalluint global_args_malloced; +# define G_global_args_malloced (G.global_args_malloced) +#else +# define G_global_args_malloced 0 +#endif /* how many non-NULL argv's we have. NB: $# + 1 */ int global_argc; char **global_argv; @@ -825,14 +924,21 @@ struct globals { unsigned special_sig_mask; #if ENABLE_HUSH_JOB unsigned fatal_sig_mask; -# define G_fatal_sig_mask G.fatal_sig_mask +# define G_fatal_sig_mask (G.fatal_sig_mask) #else # define G_fatal_sig_mask 0 #endif +#if ENABLE_HUSH_TRAP char **traps; /* char *traps[NSIG] */ +# define G_traps G.traps +#else +# define G_traps ((char**)NULL) +#endif sigset_t pending_set; -#if HUSH_DEBUG +#if ENABLE_HUSH_MEMLEAK unsigned long memleak_value; +#endif +#if HUSH_DEBUG int debug_indent; #endif struct sigaction sa; @@ -854,11 +960,18 @@ struct globals { /* Function prototypes for builtins */ static int builtin_cd(char **argv) FAST_FUNC; +#if ENABLE_HUSH_ECHO static int builtin_echo(char **argv) FAST_FUNC; +#endif static int builtin_eval(char **argv) FAST_FUNC; static int builtin_exec(char **argv) FAST_FUNC; static int builtin_exit(char **argv) FAST_FUNC; +#if ENABLE_HUSH_EXPORT static int builtin_export(char **argv) FAST_FUNC; +#endif +#if ENABLE_HUSH_READONLY +static int builtin_readonly(char **argv) FAST_FUNC; +#endif #if ENABLE_HUSH_JOB static int builtin_fg_bg(char **argv) FAST_FUNC; static int builtin_jobs(char **argv) FAST_FUNC; @@ -872,24 +985,43 @@ static int builtin_history(char **argv) FAST_FUNC; #if ENABLE_HUSH_LOCAL static int builtin_local(char **argv) FAST_FUNC; #endif -#if HUSH_DEBUG +#if ENABLE_HUSH_MEMLEAK static int builtin_memleak(char **argv) FAST_FUNC; #endif -#if ENABLE_PRINTF +#if ENABLE_HUSH_PRINTF static int builtin_printf(char **argv) FAST_FUNC; #endif static int builtin_pwd(char **argv) FAST_FUNC; +#if ENABLE_HUSH_READ static int builtin_read(char **argv) FAST_FUNC; +#endif +#if ENABLE_HUSH_SET static int builtin_set(char **argv) FAST_FUNC; +#endif static int builtin_shift(char **argv) FAST_FUNC; static int builtin_source(char **argv) FAST_FUNC; +#if ENABLE_HUSH_TEST || BASH_TEST2 static int builtin_test(char **argv) FAST_FUNC; +#endif +#if ENABLE_HUSH_TRAP static int builtin_trap(char **argv) FAST_FUNC; +#endif +#if ENABLE_HUSH_TYPE static int builtin_type(char **argv) FAST_FUNC; +#endif static int builtin_true(char **argv) FAST_FUNC; +#if ENABLE_HUSH_UMASK static int builtin_umask(char **argv) FAST_FUNC; +#endif +#if ENABLE_HUSH_UNSET static int builtin_unset(char **argv) FAST_FUNC; +#endif +#if ENABLE_HUSH_KILL +static int builtin_kill(char **argv) FAST_FUNC; +#endif +#if ENABLE_HUSH_WAIT static int builtin_wait(char **argv) FAST_FUNC; +#endif #if ENABLE_HUSH_LOOPS static int builtin_break(char **argv) FAST_FUNC; static int builtin_continue(char **argv) FAST_FUNC; @@ -916,13 +1048,13 @@ struct built_in_command { }; static const struct built_in_command bltins1[] = { - BLTIN("." , builtin_source , "Run commands in a file"), + BLTIN("." , builtin_source , "Run commands in 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 job in background"), #endif #if ENABLE_HUSH_LOOPS - BLTIN("break" , builtin_break , "Exit from a loop"), + BLTIN("break" , builtin_break , "Exit loop"), #endif BLTIN("cd" , builtin_cd , "Change directory"), #if ENABLE_HUSH_LOOPS @@ -930,53 +1062,87 @@ static const struct built_in_command bltins1[] = { #endif 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("exit" , builtin_exit , NULL), +#if ENABLE_HUSH_EXPORT BLTIN("export" , builtin_export , "Set environment variables"), +#endif #if ENABLE_HUSH_JOB - BLTIN("fg" , builtin_fg_bg , "Bring job into the foreground"), + BLTIN("fg" , builtin_fg_bg , "Bring job into foreground"), #endif #if ENABLE_HUSH_HELP BLTIN("help" , builtin_help , NULL), #endif #if MAX_HISTORY && ENABLE_FEATURE_EDITING - BLTIN("history" , builtin_history , "Show command history"), + BLTIN("history" , builtin_history , "Show history"), #endif #if ENABLE_HUSH_JOB BLTIN("jobs" , builtin_jobs , "List jobs"), #endif +#if ENABLE_HUSH_KILL + BLTIN("kill" , builtin_kill , "Send signals to processes"), +#endif #if ENABLE_HUSH_LOCAL BLTIN("local" , builtin_local , "Set local variables"), #endif -#if HUSH_DEBUG +#if ENABLE_HUSH_MEMLEAK BLTIN("memleak" , builtin_memleak , NULL), #endif +#if ENABLE_HUSH_READ BLTIN("read" , builtin_read , "Input into variable"), +#endif +#if ENABLE_HUSH_READONLY + BLTIN("readonly" , builtin_readonly, "Make variables read-only"), +#endif #if ENABLE_HUSH_FUNCTIONS - BLTIN("return" , builtin_return , "Return from a function"), + BLTIN("return" , builtin_return , "Return from function"), +#endif +#if ENABLE_HUSH_SET + BLTIN("set" , builtin_set , "Set positional parameters"), #endif - BLTIN("set" , builtin_set , "Set/unset positional parameters"), BLTIN("shift" , builtin_shift , "Shift positional parameters"), -#if ENABLE_HUSH_BASH_COMPAT - BLTIN("source" , builtin_source , "Run commands in a file"), +#if BASH_SOURCE + BLTIN("source" , builtin_source , NULL), #endif +#if ENABLE_HUSH_TRAP BLTIN("trap" , builtin_trap , "Trap signals"), +#endif BLTIN("true" , builtin_true , NULL), +#if ENABLE_HUSH_TYPE BLTIN("type" , builtin_type , "Show command type"), - BLTIN("ulimit" , shell_builtin_ulimit , "Control resource limits"), +#endif +#if ENABLE_HUSH_ULIMIT + BLTIN("ulimit" , shell_builtin_ulimit, "Control resource limits"), +#endif +#if ENABLE_HUSH_UMASK BLTIN("umask" , builtin_umask , "Set file creation mask"), +#endif +#if ENABLE_HUSH_UNSET BLTIN("unset" , builtin_unset , "Unset variables"), +#endif +#if ENABLE_HUSH_WAIT BLTIN("wait" , builtin_wait , "Wait for process"), +#endif }; -/* For now, echo and test are unconditionally enabled. - * Maybe make it configurable? */ +/* These builtins won't be used if we are on NOMMU and need to re-exec + * (it's cheaper to run an external program in this case): + */ static const struct built_in_command bltins2[] = { +#if ENABLE_HUSH_TEST BLTIN("[" , builtin_test , NULL), +#endif +#if BASH_TEST2 + BLTIN("[[" , builtin_test , NULL), +#endif +#if ENABLE_HUSH_ECHO BLTIN("echo" , builtin_echo , NULL), -#if ENABLE_PRINTF +#endif +#if ENABLE_HUSH_PRINTF BLTIN("printf" , builtin_printf , NULL), #endif BLTIN("pwd" , builtin_pwd , NULL), +#if ENABLE_HUSH_TEST BLTIN("test" , builtin_test , NULL), +#endif }; @@ -1034,6 +1200,10 @@ static const struct built_in_command bltins2[] = { # define DEBUG_GLOB 0 #endif +#ifndef debug_printf_redir +# define debug_printf_redir(...) (indent(), fdprintf(2, __VA_ARGS__)) +#endif + #ifndef debug_printf_list # define debug_printf_list(...) (indent(), fdprintf(2, __VA_ARGS__)) #endif @@ -1152,6 +1322,9 @@ static void syntax_error_unexpected_ch(unsigned lineno UNUSED_PARAM, int ch) char msg[2]; msg[0] = ch; msg[1] = '\0'; +#if HUSH_DEBUG >= 2 + bb_error_msg("hush.c:%u", lineno); +#endif bb_error_msg("syntax error: unexpected %s", ch == EOF ? "EOF" : msg); } @@ -1266,12 +1439,30 @@ static void free_strings(char **strings) free(strings); } +static int fcntl_F_DUPFD(int fd, int avoid_fd) +{ + int newfd; + repeat: + newfd = fcntl(fd, F_DUPFD, avoid_fd + 1); + if (newfd < 0) { + if (errno == EBUSY) + goto repeat; + if (errno == EINTR) + goto repeat; + } + return newfd; +} -static int xdup_and_close(int fd, int F_DUPFD_maybe_CLOEXEC) +static int xdup_and_close(int fd, int F_DUPFD_maybe_CLOEXEC, int avoid_fd) { - /* We avoid taking stdio fds. Mimicking ash: use fds above 9 */ - int newfd = fcntl(fd, F_DUPFD_maybe_CLOEXEC, 10); + int newfd; + repeat: + newfd = fcntl(fd, F_DUPFD_maybe_CLOEXEC, avoid_fd + 1); if (newfd < 0) { + if (errno == EBUSY) + goto repeat; + if (errno == EINTR) + goto repeat; /* fd was not open? */ if (errno == EBADF) return fd; @@ -1309,13 +1500,14 @@ static void fclose_and_forget(FILE *fp) } fclose(fp); } -static int save_FILEs_on_redirect(int fd) +static int save_FILEs_on_redirect(int fd, int avoid_fd) { struct FILE_list *fl = G.FILE_list; while (fl) { if (fd == fl->fd) { /* We use it only on script files, they are all CLOEXEC */ - fl->fd = xdup_and_close(fd, F_DUPFD_CLOEXEC); + fl->fd = xdup_and_close(fd, F_DUPFD_CLOEXEC, avoid_fd); + debug_printf_redir("redirect_fd %d: matches a script fd, moving it to %d\n", fd, fl->fd); return 1; } fl = fl->next; @@ -1328,13 +1520,14 @@ static void restore_redirected_FILEs(void) while (fl) { int should_be = fileno(fl->fp); if (fl->fd != should_be) { + debug_printf_redir("restoring script fd from %d to %d\n", fl->fd, should_be); xmove_fd(fl->fd, should_be); fl->fd = should_be; } fl = fl->next; } } -#if ENABLE_FEATURE_SH_STANDALONE +#if ENABLE_FEATURE_SH_STANDALONE && BB_MMU static void close_all_FILE_list(void) { struct FILE_list *fl = G.FILE_list; @@ -1358,43 +1551,38 @@ typedef struct save_arg_t { char *sv_argv0; char **sv_g_argv; int sv_g_argc; - smallint sv_g_malloced; + IF_HUSH_SET(smallint sv_g_malloced;) } save_arg_t; static void save_and_replace_G_args(save_arg_t *sv, char **argv) { - int n; - sv->sv_argv0 = argv[0]; sv->sv_g_argv = G.global_argv; sv->sv_g_argc = G.global_argc; - sv->sv_g_malloced = G.global_args_malloced; + IF_HUSH_SET(sv->sv_g_malloced = G.global_args_malloced;) argv[0] = G.global_argv[0]; /* retain $0 */ G.global_argv = argv; - G.global_args_malloced = 0; + IF_HUSH_SET(G.global_args_malloced = 0;) - n = 1; - while (*++argv) - n++; - G.global_argc = n; + G.global_argc = 1 + string_array_len(argv + 1); } static void restore_G_args(save_arg_t *sv, char **argv) { - char **pp; - +#if ENABLE_HUSH_SET if (G.global_args_malloced) { /* someone ran "set -- arg1 arg2 ...", undo */ - pp = G.global_argv; + char **pp = G.global_argv; while (*++pp) /* note: does not free $0 */ free(*pp); free(G.global_argv); } +#endif argv[0] = sv->sv_argv0; G.global_argv = sv->sv_g_argv; G.global_argc = sv->sv_g_argc; - G.global_args_malloced = sv->sv_g_malloced; + IF_HUSH_SET(G.global_args_malloced = sv->sv_g_malloced;) } @@ -1578,9 +1766,8 @@ static sighandler_t install_sighandler(int sig, sighandler_t handler) } static void hush_exit(int exitcode) NORETURN; -static void fflush_and__exit(void) NORETURN; -static void restore_ttypgrp_and__exit(void) NORETURN; +static void restore_ttypgrp_and__exit(void) NORETURN; static void restore_ttypgrp_and__exit(void) { /* xfunc has failed! die die die */ @@ -1589,6 +1776,8 @@ static void restore_ttypgrp_and__exit(void) hush_exit(xfunc_error_retval); } +#if ENABLE_HUSH_JOB + /* Needed only on some libc: * It was observed that on exit(), fgetc'ed buffered data * gets "unwound" via lseek(fd, -NUM, SEEK_CUR). @@ -1602,14 +1791,13 @@ static void restore_ttypgrp_and__exit(void) * and in `cmd` handling. * If set as die_func(), this makes xfunc_die() exit via _exit(), not exit(): */ +static void fflush_and__exit(void) NORETURN; static void fflush_and__exit(void) { fflush_all(); _exit(xfunc_error_retval); } -#if ENABLE_HUSH_JOB - /* After [v]fork, in child: do not restore tty pgrp on xfunc death */ # define disable_restore_tty_pgrp_on_exit() (die_func = fflush_and__exit) /* After [v]fork, in parent: restore tty pgrp on xfunc death */ @@ -1682,13 +1870,13 @@ static void hush_exit(int exitcode) #endif fflush_all(); - if (G.exiting <= 0 && G.traps && G.traps[0] && G.traps[0][0]) { + if (G.exiting <= 0 && G_traps && G_traps[0] && G_traps[0][0]) { char *argv[3]; /* argv[0] is unused */ - argv[1] = G.traps[0]; + argv[1] = G_traps[0]; argv[2] = NULL; G.exiting = 1; /* prevent EXIT trap recursion */ - /* Note: G.traps[0] is not cleared! + /* Note: G_traps[0] is not cleared! * "trap" will still show it, if executed * in the handler */ builtin_eval(argv); @@ -1739,17 +1927,18 @@ static int check_and_run_traps(void) } while (sig < NSIG); break; got_sig: - if (G.traps && G.traps[sig]) { + if (G_traps && G_traps[sig]) { debug_printf_exec("%s: sig:%d handler:'%s'\n", __func__, sig, G.traps[sig]); - if (G.traps[sig][0]) { + if (G_traps[sig][0]) { /* We have user-defined handler */ smalluint save_rcode; char *argv[3]; /* argv[0] is unused */ - argv[1] = G.traps[sig]; + argv[1] = G_traps[sig]; argv[2] = NULL; save_rcode = G.last_exitcode; builtin_eval(argv); +//FIXME: shouldn't it be set to 128 + sig instead? G.last_exitcode = save_rcode; last_sig = sig; } /* else: "" trap, ignoring signal */ @@ -1759,8 +1948,6 @@ static int check_and_run_traps(void) switch (sig) { case SIGINT: debug_printf_exec("%s: sig:%d default SIGINT handler\n", __func__, sig); - /* Builtin was ^C'ed, make it look prettier: */ - bb_putchar('\n'); G.flag_SIGINT = 1; last_sig = sig; break; @@ -1786,7 +1973,7 @@ static int check_and_run_traps(void) 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); /* Note: - * We dont do 'last_sig = sig' here -> NOT returning this sig. + * We don't do 'last_sig = sig' here -> NOT returning this sig. * This simplifies wait builtin a bit. */ break; @@ -1795,7 +1982,7 @@ static int check_and_run_traps(void) debug_printf_exec("%s: sig:%d default handling is to ignore\n", __func__, sig); /* SIGTERM, SIGQUIT, SIGTTIN, SIGTTOU, SIGTSTP */ /* Note: - * We dont do 'last_sig = sig' here -> NOT returning this sig. + * We don't do 'last_sig = sig' here -> NOT returning this sig. * Example: wait is not interrupted by TERM * in interactive shell, because TERM is ignored. */ @@ -1876,19 +2063,10 @@ static const char* FAST_FUNC get_local_var_value(const char *name) * -1: clear export flag and unsetenv the variable * flg_read_only is set only when we handle -R var=val */ -#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 local_lvl, int flg_read_only) +static int set_local_var(char *str, + int flg_export UNUSED_PARAM, + int local_lvl UNUSED_PARAM, + int flg_read_only UNUSED_PARAM) { struct variable **var_pp; struct variable *cur; @@ -1912,9 +2090,7 @@ static int set_local_var(char *str, int flg_export, int local_lvl, int flg_read_ /* We found an existing var with this name */ if (cur->flg_read_only) { -#if !BB_MMU if (!flg_read_only) -#endif bb_error_msg("%s: readonly variable", str); free(str); return -1; @@ -1982,10 +2158,12 @@ static int set_local_var(char *str, int flg_export, int local_lvl, int flg_read_ set_str_and_exp: cur->varstr = str; -#if !BB_MMU - cur->flg_read_only = flg_read_only; -#endif exp: +#if !BB_MMU || ENABLE_HUSH_READONLY + if (flg_read_only != 0) { + cur->flg_read_only = flg_read_only; + } +#endif if (flg_export == 1) cur->flg_export = 1; if (name_len == 4 && cur->varstr[0] == 'P' && cur->varstr[1] == 'S') @@ -2043,10 +2221,12 @@ static int unset_local_var_len(const char *name, int name_len) return EXIT_SUCCESS; } +#if ENABLE_HUSH_UNSET static int unset_local_var(const char *name) { return unset_local_var_len(name, strlen(name)); } +#endif static void unset_vars(char **strings) { @@ -2063,11 +2243,13 @@ static void unset_vars(char **strings) free(strings); } +#if BASH_HOSTNAME_VAR || ENABLE_FEATURE_SH_MATH || ENABLE_HUSH_READ static void FAST_FUNC set_local_var_from_halves(const char *name, const char *val) { char *var = xasprintf("%s=%s", name, val); set_local_var(var, /*flags:*/ 0, /*lvl:*/ 0, /*ro:*/ 0); } +#endif /* @@ -2154,7 +2336,7 @@ static void reinit_unicode_for_hush(void) * AT\ * H\ * \ - * It excercises a lot of corner cases. + * It exercises a lot of corner cases. */ static void cmdedit_update_prompt(void) { @@ -2196,18 +2378,30 @@ static int get_user_input(struct in_str *i) prompt_str = setup_prompt_string(i->promptmode); # if ENABLE_FEATURE_EDITING - do { + for (;;) { reinit_unicode_for_hush(); - G.flag_SIGINT = 0; + if (G.flag_SIGINT) { + /* There was ^C'ed, make it look prettier: */ + bb_putchar('\n'); + G.flag_SIGINT = 0; + } /* buglet: SIGINT will not make new prompt to appear _at once_, - * only after . (^C will work) */ + * only after . (^C works immediately) */ r = read_line_input(G.line_input_state, prompt_str, G.user_input_buf, CONFIG_FEATURE_EDITING_MAX_LEN-1, /*timeout*/ -1 ); - /* catch *SIGINT* etc (^C is handled by read_line_input) */ + /* read_line_input intercepts ^C, "convert" it to SIGINT */ + if (r == 0) { + write(STDOUT_FILENO, "^C", 2); + raise(SIGINT); + } check_and_run_traps(); - } while (r == 0 || G.flag_SIGINT); /* repeat if ^C or SIGINT */ + if (r != 0 && !G.flag_SIGINT) + break; + /* ^C or SIGINT: repeat */ + G.last_exitcode = 128 + SIGINT; + } if (r < 0) { /* EOF/error detected */ i->p = NULL; @@ -2217,7 +2411,7 @@ static int get_user_input(struct in_str *i) i->p = G.user_input_buf; return (unsigned char)*i->p++; # else - do { + for (;;) { G.flag_SIGINT = 0; if (i->last_char == '\0' || i->last_char == '\n') { /* Why check_and_run_traps here? Try this interactively: @@ -2229,8 +2423,18 @@ static int get_user_input(struct in_str *i) fputs(prompt_str, stdout); } fflush_all(); +//FIXME: here ^C or SIGINT will have effect only after r = fgetc(i->file); - } while (G.flag_SIGINT || r == '\0'); + /* In !ENABLE_FEATURE_EDITING we don't use read_line_input, + * no ^C masking happens during fgetc, no special code for ^C: + * it generates SIGINT as usual. + */ + check_and_run_traps(); + if (G.flag_SIGINT) + G.last_exitcode = 128 + SIGINT; + if (r != '\0') + break; + } return r; # endif } @@ -2259,10 +2463,23 @@ static inline int fgetc_interactive(struct in_str *i) } #endif /* INTERACTIVE */ -static int FAST_FUNC file_get(struct in_str *i) +static int i_getch(struct in_str *i) { int ch; + if (!i->file) { + /* string-based in_str */ + ch = (unsigned char)*i->p; + if (ch != '\0') { + i->p++; + i->last_char = ch; + return ch; + } + return EOF; + } + + /* FILE-based in_str */ + #if ENABLE_FEATURE_EDITING /* This can be stdin, check line editing char[] buffer */ if (i->p && *i->p != '\0') { @@ -2288,10 +2505,18 @@ static int FAST_FUNC file_get(struct in_str *i) return ch; } -static int FAST_FUNC file_peek(struct in_str *i) +static int i_peek(struct in_str *i) { int ch; + if (!i->file) { + /* string-based in_str */ + /* Doesn't report EOF on NUL. None of the callers care. */ + return (unsigned char)*i->p; + } + + /* FILE-based in_str */ + #if ENABLE_FEATURE_EDITING && ENABLE_HUSH_INTERACTIVE /* This can be stdin, check line editing char[] buffer */ if (i->p && *i->p != '\0') @@ -2318,23 +2543,6 @@ static int FAST_FUNC file_peek(struct in_str *i) return ch; } -static int FAST_FUNC static_get(struct in_str *i) -{ - int ch = (unsigned char)*i->p; - if (ch != '\0') { - i->p++; - i->last_char = ch; - return ch; - } - return EOF; -} - -static int FAST_FUNC static_peek(struct in_str *i) -{ - /* Doesn't report EOF on NUL. None of the callers care. */ - return (unsigned char)*i->p; -} - /* Only ever called if i_peek() was called, and did not return EOF. * IOW: we know the previous peek saw an ordinary char, not EOF, not NUL, * not end-of-line. Therefore we never need to read a new editing line here. @@ -2370,8 +2578,6 @@ static int i_peek2(struct in_str *i) static void setup_file_in_str(struct in_str *i, FILE *f) { memset(i, 0, sizeof(*i)); - i->get = file_get; - i->peek = file_peek; /* i->promptmode = 0; - PS1 (memset did it) */ i->file = f; /* i->p = NULL; */ @@ -2380,9 +2586,8 @@ static void setup_file_in_str(struct in_str *i, FILE *f) static void setup_string_in_str(struct in_str *i, const char *s) { memset(i, 0, sizeof(*i)); - i->get = static_get; - i->peek = static_peek; /* i->promptmode = 0; - PS1 (memset did it) */ + /*i->file = NULL */; i->p = s; } @@ -3139,7 +3344,6 @@ static struct pipe *new_pipe(void) { struct pipe *pi; pi = xzalloc(sizeof(struct pipe)); - /*pi->followup = 0; - deliberately invalid value */ /*pi->res_word = RES_NONE; - RES_NONE is 0 anyway */ return pi; } @@ -3191,12 +3395,49 @@ static void done_pipe(struct parse_context *ctx, pipe_style type) debug_printf_parse("done_pipe entered, followup %d\n", type); /* Close previous command */ not_null = done_command(ctx); - ctx->pipe->followup = type; #if HAS_KEYWORDS ctx->pipe->pi_inverted = ctx->ctx_inverted; ctx->ctx_inverted = 0; ctx->pipe->res_word = ctx->ctx_res_w; #endif + if (type == PIPE_BG && ctx->list_head != ctx->pipe) { + /* Necessary since && and || have precedence over &: + * "cmd1 && cmd2 &" must spawn both cmds, not only cmd2, + * in a backgrounded subshell. + */ + struct pipe *pi; + struct command *command; + + /* Is this actually this construct, all pipes end with && or ||? */ + pi = ctx->list_head; + while (pi != ctx->pipe) { + if (pi->followup != PIPE_AND && pi->followup != PIPE_OR) + goto no_conv; + pi = pi->next; + } + + debug_printf_parse("BG with more than one pipe, converting to { p1 &&...pN; } &\n"); + pi->followup = PIPE_SEQ; /* close pN _not_ with "&"! */ + pi = xzalloc(sizeof(*pi)); + pi->followup = PIPE_BG; + pi->num_cmds = 1; + pi->cmds = xzalloc(sizeof(pi->cmds[0])); + command = &pi->cmds[0]; + if (CMD_NORMAL != 0) /* "if xzalloc didn't do that already" */ + command->cmd_type = CMD_NORMAL; + command->group = ctx->list_head; +#if !BB_MMU + command->group_as_string = xstrndup( + ctx->as_string.data, + ctx->as_string.length - 1 /* do not copy last char, "&" */ + ); +#endif + /* Replace all pipes in ctx with one newly created */ + ctx->list_head = ctx->pipe = pi; + } else { + no_conv: + ctx->pipe->followup = type; + } /* Without this check, even just on command line generates * tree of three NOPs (!). Which is harmless but annoying. @@ -3366,9 +3607,8 @@ static int reserved_word(o_string *word, struct parse_context *ctx) if (r->flag & FLAG_START) { struct parse_context *old; - old = xmalloc(sizeof(*old)); + old = xmemdup(ctx, sizeof(*ctx)); debug_printf_parse("push stack %p\n", old); - *old = *ctx; /* physical copy */ initialize_context(ctx); ctx->stack = old; } else if (/*ctx->ctx_res_w == RES_NONE ||*/ !(ctx->old_flag & (1 << r->res))) { @@ -3498,7 +3738,7 @@ static int done_word(o_string *word, struct parse_context *ctx) (ctx->ctx_res_w == RES_SNTX)); return (ctx->ctx_res_w == RES_SNTX); } -# if ENABLE_HUSH_BASH_COMPAT +# if BASH_TEST2 if (strcmp(word->data, "[[") == 0) { command->cmd_type = CMD_SINGLEWORD_NOGLOB; } @@ -3994,7 +4234,7 @@ static int i_peek_and_eat_bkslash_nl(struct in_str *input) } } -#if ENABLE_HUSH_TICK || ENABLE_SH_MATH_SUPPORT || ENABLE_HUSH_DOLLAR_OPS +#if ENABLE_HUSH_TICK || ENABLE_FEATURE_SH_MATH || ENABLE_HUSH_DOLLAR_OPS /* Subroutines for copying $(...) and `...` things */ static int add_till_backquote(o_string *dest, struct in_str *input, int in_dquote); /* '...' */ @@ -4096,7 +4336,7 @@ static int add_till_closing_bracket(o_string *dest, struct in_str *input, unsign { int ch; char dbl = end_ch & DOUBLE_CLOSE_CHAR_FLAG; -# if ENABLE_HUSH_BASH_COMPAT +# if BASH_SUBSTR || BASH_PATTERN_SUBST char end_char2 = end_ch >> 8; # endif end_ch &= (DOUBLE_CLOSE_CHAR_FLAG - 1); @@ -4107,7 +4347,11 @@ static int add_till_closing_bracket(o_string *dest, struct in_str *input, unsign syntax_error_unterm_ch(end_ch); return 0; } - if (ch == end_ch IF_HUSH_BASH_COMPAT( || ch == end_char2)) { + if (ch == end_ch +# if BASH_SUBSTR || BASH_PATTERN_SUBST + || ch == end_char2 +# endif + ) { if (!dbl) break; /* we look for closing )) of $((EXPR)) */ @@ -4162,7 +4406,7 @@ static int add_till_closing_bracket(o_string *dest, struct in_str *input, unsign } return ch; } -#endif /* ENABLE_HUSH_TICK || ENABLE_SH_MATH_SUPPORT || ENABLE_HUSH_DOLLAR_OPS */ +#endif /* ENABLE_HUSH_TICK || ENABLE_FEATURE_SH_MATH || ENABLE_HUSH_DOLLAR_OPS */ /* Return code: 0 for OK, 1 for syntax error */ #if BB_MMU @@ -4260,14 +4504,14 @@ static int parse_dollar(o_string *as_string, /* Eat everything until closing '}' (or ':') */ end_ch = '}'; - if (ENABLE_HUSH_BASH_COMPAT + if (BASH_SUBSTR && ch == ':' && !strchr(MINUS_PLUS_EQUAL_QUESTION, i_peek(input)) ) { /* It's ${var:N[:M]} thing */ end_ch = '}' * 0x100 + ':'; } - if (ENABLE_HUSH_BASH_COMPAT + if (BASH_PATTERN_SUBST && ch == '/' ) { /* It's ${var/[/]pattern[/repl]} thing */ @@ -4294,7 +4538,9 @@ static int parse_dollar(o_string *as_string, o_addchr(as_string, last_ch); } - if (ENABLE_HUSH_BASH_COMPAT && (end_ch & 0xff00)) { + if ((BASH_SUBSTR || BASH_PATTERN_SUBST) + && (end_ch & 0xff00) + ) { /* close the first block: */ o_addchr(dest, SPECIAL_VAR_SYMBOL); /* while parsing N from ${var:N[:M]} @@ -4305,7 +4551,7 @@ static int parse_dollar(o_string *as_string, goto again; } /* got '}' */ - if (end_ch == '}' * 0x100 + ':') { + if (BASH_SUBSTR && end_ch == '}' * 0x100 + ':') { /* it's ${var:N} - emulate :999999999 */ o_addstr(dest, "999999999"); } /* else: it's ${var/[/]pattern} */ @@ -4316,13 +4562,13 @@ static int parse_dollar(o_string *as_string, o_addchr(dest, SPECIAL_VAR_SYMBOL); break; } -#if ENABLE_SH_MATH_SUPPORT || ENABLE_HUSH_TICK +#if ENABLE_FEATURE_SH_MATH || ENABLE_HUSH_TICK case '(': { unsigned pos; ch = i_getch(input); nommu_addchr(as_string, ch); -# if ENABLE_SH_MATH_SUPPORT +# if ENABLE_FEATURE_SH_MATH if (i_peek_and_eat_bkslash_nl(input) == '(') { ch = i_getch(input); nommu_addchr(as_string, ch); @@ -4380,7 +4626,7 @@ static int parse_dollar(o_string *as_string, } #if BB_MMU -# if ENABLE_HUSH_BASH_COMPAT +# if BASH_PATTERN_SUBST #define encode_string(as_string, dest, input, dquote_end, process_bkslash) \ encode_string(dest, input, dquote_end, process_bkslash) # else @@ -4392,7 +4638,7 @@ static int parse_dollar(o_string *as_string, #else /* !MMU */ -# if ENABLE_HUSH_BASH_COMPAT +# if BASH_PATTERN_SUBST /* all parameters are needed, no macro tricks */ # else #define encode_string(as_string, dest, input, dquote_end, process_bkslash) \ @@ -4405,7 +4651,7 @@ static int encode_string(o_string *as_string, int dquote_end, int process_bkslash) { -#if !ENABLE_HUSH_BASH_COMPAT +#if !BASH_PATTERN_SUBST const int process_bkslash = 1; #endif int ch; @@ -4633,7 +4879,9 @@ static struct pipe *parse_stream(char **pstring, * Really, ask yourself, why * "cmd && " doesn't start * cmd but waits for more input? - * No reason...) + * The only reason is that it might be + * a "cmd1 && cmd2 &" construct, + * cmd1 may need to run in BG). */ struct pipe *pi = ctx.list_head; if (pi->num_cmds != 0 /* check #1 */ @@ -4991,7 +5239,8 @@ static struct pipe *parse_stream(char **pstring, * if we see {, we call parse_group(..., end_trigger='}') * and it will match } earlier (not here). */ syntax_error_unexpected_ch(ch); - goto parse_error; + G.last_exitcode = 2; + goto parse_error2; default: if (HUSH_DEBUG) bb_error_msg_and_die("BUG: unexpected %c\n", ch); @@ -4999,6 +5248,8 @@ static struct pipe *parse_stream(char **pstring, } /* while (1) */ parse_error: + G.last_exitcode = 1; + parse_error2: { struct parse_context *pctx; IF_HAS_KEYWORDS(struct parse_context *p2;) @@ -5032,7 +5283,6 @@ static struct pipe *parse_stream(char **pstring, } while (HAS_KEYWORDS && pctx); o_free(&dest); - G.last_exitcode = 1; #if !BB_MMU if (pstring) *pstring = NULL; @@ -5046,7 +5296,7 @@ static struct pipe *parse_stream(char **pstring, /*** Execution routines ***/ /* Expansion can recurse, need forward decls: */ -#if !ENABLE_HUSH_BASH_COMPAT +#if !BASH_PATTERN_SUBST && !ENABLE_HUSH_CASE /* only ${var/pattern/repl} (its pattern part) needs additional mode */ #define expand_string_to_string(str, do_unbackslash) \ expand_string_to_string(str) @@ -5079,7 +5329,7 @@ static void o_addblock_duplicate_backslash(o_string *o, const char *str, int len /* And now we want to add { or } and continue: * o_addchr(o, c); * continue; - * luckily, just falling throught achieves this. + * luckily, just falling through achieves this. */ } #endif @@ -5167,13 +5417,16 @@ static int expand_on_ifs(int *ended_with_ifs, o_string *output, int n, const cha * Returns malloced string. * As an optimization, we return NULL if expansion is not needed. */ -#if !ENABLE_HUSH_BASH_COMPAT +#if !BASH_PATTERN_SUBST /* only ${var/pattern/repl} (its pattern part) needs additional mode */ #define encode_then_expand_string(str, process_bkslash, do_unbackslash) \ encode_then_expand_string(str) #endif static char *encode_then_expand_string(const char *str, int process_bkslash, int do_unbackslash) { +#if !BASH_PATTERN_SUBST + const int do_unbackslash = 1; +#endif char *exp_str; struct in_str input; o_string dest = NULL_O_STRING; @@ -5200,7 +5453,7 @@ static char *encode_then_expand_string(const char *str, int process_bkslash, int return exp_str; } -#if ENABLE_SH_MATH_SUPPORT +#if ENABLE_FEATURE_SH_MATH static arith_t expand_and_evaluate_arith(const char *arg, const char **errmsg_p) { arith_state_t math_state; @@ -5221,7 +5474,7 @@ static arith_t expand_and_evaluate_arith(const char *arg, const char **errmsg_p) } #endif -#if ENABLE_HUSH_BASH_COMPAT +#if BASH_PATTERN_SUBST /* ${var/[/]pattern[/repl]} helpers */ static char *strstr_pattern(char *val, const char *pattern, int *size) { @@ -5273,7 +5526,7 @@ static char *replace_pattern(char *val, const char *pattern, const char *repl, c debug_printf_varexp("result:'%s'\n", result); return result; } -#endif +#endif /* BASH_PATTERN_SUBST */ /* Helper: * Handles varname... construct. @@ -5321,7 +5574,7 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha if (exp_op == ':') { exp_op = *exp_word++; //TODO: try ${var:} and ${var:bogus} in non-bash config - if (ENABLE_HUSH_BASH_COMPAT + if (BASH_SUBSTR && (!exp_op || !strchr(MINUS_PLUS_EQUAL_QUESTION, exp_op)) ) { /* oops... it's ${var:N[:M]}, not ${var:?xxx} or some such */ @@ -5403,7 +5656,7 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha } } } -#if ENABLE_HUSH_BASH_COMPAT +#if BASH_PATTERN_SUBST else if (exp_op == '/' || exp_op == '\\') { /* It's ${var/[/]pattern[/repl]} thing. * Note that in encoded form it has TWO parts: @@ -5450,9 +5703,9 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha free(repl); } } -#endif +#endif /* BASH_PATTERN_SUBST */ else if (exp_op == ':') { -#if ENABLE_HUSH_BASH_COMPAT && ENABLE_SH_MATH_SUPPORT +#if BASH_SUBSTR && ENABLE_FEATURE_SH_MATH /* It's ${var:N[:M]} bashism. * Note that in encoded form it has TWO parts: * var:NM @@ -5472,27 +5725,34 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha if (errmsg) goto arith_err; debug_printf_varexp("len:'%s'=%lld\n", exp_word, (long long)len); - if (len >= 0) { /* bash compat: len < 0 is illegal */ - if (beg < 0) /* bash compat */ - beg = 0; - debug_printf_varexp("from val:'%s'\n", val); - if (len == 0 || !val || beg >= strlen(val)) { + if (beg < 0) { + /* negative beg counts from the end */ + beg = (arith_t)strlen(val) + beg; + if (beg < 0) /* ${v: -999999} is "" */ + beg = len = 0; + } + debug_printf_varexp("from val:'%s'\n", val); + if (len < 0) { + /* in bash, len=-n means strlen()-n */ + len = (arith_t)strlen(val) - beg + len; + if (len < 0) /* bash compat */ + die_if_script("%s: substring expression < 0", var); + } + if (len <= 0 || !val || beg >= strlen(val)) { arith_err: - val = NULL; - } else { - /* Paranoia. What if user entered 9999999999999 - * which fits in arith_t but not int? */ - if (len >= INT_MAX) - len = INT_MAX; - val = to_be_freed = xstrndup(val + beg, len); - } - debug_printf_varexp("val:'%s'\n", val); - } else -#endif - { - die_if_script("malformed ${%s:...}", var); val = NULL; + } else { + /* Paranoia. What if user entered 9999999999999 + * which fits in arith_t but not int? */ + if (len >= INT_MAX) + len = INT_MAX; + val = to_be_freed = xstrndup(val + beg, len); } + debug_printf_varexp("val:'%s'\n", val); +#else /* not (HUSH_SUBSTR_EXPANSION && FEATURE_SH_MATH) */ + die_if_script("malformed ${%s:...}", var); + val = NULL; +#endif } else { /* one of "-=+?" */ /* Standard-mandated substitution ops: * ${var?word} - indicate error if unset @@ -5587,7 +5847,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg) #if ENABLE_HUSH_TICK o_string subst_result = NULL_O_STRING; #endif -#if ENABLE_SH_MATH_SUPPORT +#if ENABLE_FEATURE_SH_MATH char arith_buf[sizeof(arith_t)*3 + 2]; #endif @@ -5674,14 +5934,14 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg) arg++; /* Can't just stuff it into output o_string, * expanded result may need to be globbed - * and $IFS-splitted */ + * and $IFS-split */ debug_printf_subst("SUBST '%s' first_ch %x\n", arg, first_ch); G.last_exitcode = process_command_subs(&subst_result, arg); debug_printf_subst("SUBST RES:%d '%s'\n", G.last_exitcode, subst_result.data); val = subst_result.data; goto store_val; #endif -#if ENABLE_SH_MATH_SUPPORT +#if ENABLE_FEATURE_SH_MATH case '+': { /* +cmd */ arith_t res; @@ -5778,7 +6038,7 @@ static char **expand_strvec_to_strvec(char **argv) return expand_variables(argv, EXP_FLAG_GLOB | EXP_FLAG_ESC_GLOB_CHARS); } -#if ENABLE_HUSH_BASH_COMPAT +#if BASH_TEST2 static char **expand_strvec_to_strvec_singleword_noglob(char **argv) { return expand_variables(argv, EXP_FLAG_SINGLEWORD); @@ -5793,7 +6053,7 @@ static char **expand_strvec_to_strvec_singleword_noglob(char **argv) */ static char *expand_string_to_string(const char *str, int do_unbackslash) { -#if !ENABLE_HUSH_BASH_COMPAT +#if !BASH_PATTERN_SUBST && !ENABLE_HUSH_CASE const int do_unbackslash = 1; #endif char *argv[2], **list; @@ -5826,7 +6086,7 @@ static char *expand_string_to_string(const char *str, int do_unbackslash) return (char*)list; } -/* Used for "eval" builtin */ +/* Used for "eval" builtin and case string */ static char* expand_strvec_to_string(char **argv) { char **list; @@ -5871,13 +6131,15 @@ static void switch_off_special_sigs(unsigned mask) sig++; if (!(mask & 1)) continue; - if (G.traps) { - if (G.traps[sig] && !G.traps[sig][0]) +#if ENABLE_HUSH_TRAP + if (G_traps) { + if (G_traps[sig] && !G_traps[sig][0]) /* trap is '', has to remain SIG_IGN */ continue; - free(G.traps[sig]); - G.traps[sig] = NULL; + free(G_traps[sig]); + G_traps[sig] = NULL; } +#endif /* We are here only if no trap or trap was not '' */ install_sighandler(sig, SIG_DFL); } @@ -5894,7 +6156,7 @@ 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). */ - unsigned sig; + IF_HUSH_TRAP(unsigned sig;) unsigned mask; /* Child shells are not interactive. @@ -5903,35 +6165,37 @@ static void reset_traps_to_defaults(void) * Same goes for SIGTERM, SIGHUP, SIGINT. */ mask = (G.special_sig_mask & SPECIAL_INTERACTIVE_SIGS) | G_fatal_sig_mask; - if (!G.traps && !mask) + if (!G_traps && !mask) return; /* already no traps and no special sigs */ /* Switch off special sigs */ switch_off_special_sigs(mask); -#if ENABLE_HUSH_JOB +# if ENABLE_HUSH_JOB G_fatal_sig_mask = 0; -#endif +# endif G.special_sig_mask &= ~SPECIAL_INTERACTIVE_SIGS; /* SIGQUIT,SIGCHLD and maybe SPECIAL_JOBSTOP_SIGS * remain set in G.special_sig_mask */ - if (!G.traps) +# if ENABLE_HUSH_TRAP + if (!G_traps) return; /* Reset all sigs to default except ones with empty traps */ for (sig = 0; sig < NSIG; sig++) { - if (!G.traps[sig]) + if (!G_traps[sig]) continue; /* no trap: nothing to do */ - if (!G.traps[sig][0]) + if (!G_traps[sig][0]) continue; /* empty trap: has to remain SIG_IGN */ /* sig has non-empty trap, reset it: */ - free(G.traps[sig]); - G.traps[sig] = NULL; + free(G_traps[sig]); + G_traps[sig] = NULL; /* There is no signal for trap 0 (EXIT) */ if (sig == 0) continue; install_sighandler(sig, pick_sighandler(sig)); } +# endif } #else /* !BB_MMU */ @@ -5971,10 +6235,10 @@ static void re_execute_shell(char ***to_free, const char *s, cnt++; empty_trap_mask = 0; - if (G.traps) { + if (G_traps) { int sig; for (sig = 1; sig < NSIG; sig++) { - if (G.traps[sig] && !G.traps[sig][0]) + if (G_traps[sig] && !G_traps[sig][0]) empty_trap_mask |= 1LL << sig; } } @@ -6167,6 +6431,7 @@ static FILE *generate_stream_from_string(const char *s, pid_t *pid_p) xmove_fd(channel[1], 1); /* Prevent it from trying to handle ctrl-z etc */ IF_HUSH_JOB(G.run_list_level = 1;) +# if ENABLE_HUSH_TRAP /* Awful hack for `trap` or $(trap). * * http://www.opengroup.org/onlinepubs/009695399/utilities/trap.html @@ -6210,6 +6475,7 @@ static FILE *generate_stream_from_string(const char *s, pid_t *pid_p) fflush_all(); /* important */ _exit(0); } +# endif # if BB_MMU reset_traps_to_defaults(); parse_and_run_string(s); @@ -6362,77 +6628,108 @@ static void setup_heredoc(struct redir_struct *redir) wait(NULL); /* wait till child has died */ } -/* fd: redirect wants this fd to be used (e.g. 3>file). - * Move all conflicting internally used fds, - * and remember them so that we can restore them later. - */ -static int save_fds_on_redirect(int fd, int squirrel[3]) -{ - if (squirrel) { - /* Handle redirects of fds 0,1,2 */ +struct squirrel { + int orig_fd; + int moved_to; + /* moved_to = n: fd was moved to n; restore back to orig_fd after redir */ + /* moved_to = -1: fd was opened by redirect; close orig_fd after redir */ +}; - /* If we collide with an already moved stdio fd... */ - if (fd == squirrel[0]) { - squirrel[0] = xdup_and_close(squirrel[0], F_DUPFD); - return 1; - } - if (fd == squirrel[1]) { - squirrel[1] = xdup_and_close(squirrel[1], F_DUPFD); - return 1; - } - if (fd == squirrel[2]) { - squirrel[2] = xdup_and_close(squirrel[2], F_DUPFD); - return 1; - } - /* If we are about to redirect stdio fd, and did not yet move it... */ - if (fd <= 2 && squirrel[fd] < 0) { - /* We avoid taking stdio fds */ - squirrel[fd] = fcntl(fd, F_DUPFD, 10); - if (squirrel[fd] < 0 && errno != EBADF) +static struct squirrel *add_squirrel(struct squirrel *sq, int fd, int avoid_fd) +{ + int i = 0; + + if (sq) while (sq[i].orig_fd >= 0) { + /* If we collide with an already moved fd... */ + if (fd == sq[i].moved_to) { + sq[i].moved_to = fcntl_F_DUPFD(sq[i].moved_to, avoid_fd); + debug_printf_redir("redirect_fd %d: already busy, moving to %d\n", fd, sq[i].moved_to); + if (sq[i].moved_to < 0) /* what? */ xfunc_die(); - return 0; /* "we did not close fd" */ + return sq; + } + if (fd == sq[i].orig_fd) { + /* Example: echo Hello >/dev/null 1>&2 */ + debug_printf_redir("redirect_fd %d: already moved\n", fd); + return sq; } + i++; } + sq = xrealloc(sq, (i + 2) * sizeof(sq[0])); + sq[i].orig_fd = fd; + /* If this fd is open, we move and remember it; if it's closed, moved_to = -1 */ + sq[i].moved_to = fcntl_F_DUPFD(fd, avoid_fd); + debug_printf_redir("redirect_fd %d: previous fd is moved to %d (-1 if it was closed)\n", fd, sq[i].moved_to); + if (sq[i].moved_to < 0 && errno != EBADF) + xfunc_die(); + sq[i+1].orig_fd = -1; /* end marker */ + return sq; +} + +/* fd: redirect wants this fd to be used (e.g. 3>file). + * Move all conflicting internally used fds, + * and remember them so that we can restore them later. + */ +static int save_fds_on_redirect(int fd, int avoid_fd, struct squirrel **sqp) +{ + if (avoid_fd < 9) /* the important case here is that it can be -1 */ + avoid_fd = 9; + #if ENABLE_HUSH_INTERACTIVE if (fd != 0 && fd == G.interactive_fd) { - G.interactive_fd = xdup_and_close(G.interactive_fd, F_DUPFD_CLOEXEC); - return 1; + G.interactive_fd = xdup_and_close(G.interactive_fd, F_DUPFD_CLOEXEC, avoid_fd); + debug_printf_redir("redirect_fd %d: matches interactive_fd, moving it to %d\n", fd, G.interactive_fd); + return 1; /* "we closed fd" */ } #endif - /* Are we called from setup_redirects(squirrel==NULL)? Two cases: * (1) Redirect in a forked child. No need to save FILEs' fds, * we aren't going to use them anymore, ok to trash. - * (2) "exec 3>FILE". Bummer. We can save FILEs' fds, - * but how are we doing to use them? + * (2) "exec 3>FILE". Bummer. We can save script FILEs' fds, + * but how are we doing to restore them? * "fileno(fd) = new_fd" can't be done. */ - if (!squirrel) + if (!sqp) return 0; - return save_FILEs_on_redirect(fd); + /* If this one of script's fds? */ + if (save_FILEs_on_redirect(fd, avoid_fd)) + return 1; /* yes. "we closed fd" */ + + /* Check whether it collides with any open fds (e.g. stdio), save fds as needed */ + *sqp = add_squirrel(*sqp, fd, avoid_fd); + return 0; /* "we did not close fd" */ } -static void restore_redirects(int squirrel[3]) +static void restore_redirects(struct squirrel *sq) { - int i, fd; - for (i = 0; i <= 2; i++) { - fd = squirrel[i]; - if (fd != -1) { - /* We simply die on error */ - xmove_fd(fd, i); + + if (sq) { + int i = 0; + while (sq[i].orig_fd >= 0) { + if (sq[i].moved_to >= 0) { + /* We simply die on error */ + debug_printf_redir("restoring redirected fd from %d to %d\n", sq[i].moved_to, sq[i].orig_fd); + xmove_fd(sq[i].moved_to, sq[i].orig_fd); + } else { + /* cmd1 9>FILE; cmd2_should_see_fd9_closed */ + debug_printf_redir("restoring redirected fd %d: closing it\n", sq[i].orig_fd); + close(sq[i].orig_fd); + } + i++; } + free(sq); } - /* Moved G.interactive_fd stays on new fd, not doing anything for it */ + /* If moved, G.interactive_fd stays on new fd, not restoring it */ restore_redirected_FILEs(); } /* squirrel != NULL means we squirrel away copies of stdin, stdout, * and stderr if they are redirected. */ -static int setup_redirects(struct command *prog, int squirrel[]) +static int setup_redirects(struct command *prog, struct squirrel **sqp) { int openfd, mode; struct redir_struct *redir; @@ -6440,7 +6737,7 @@ 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, squirrel); + save_fds_on_redirect(redir->rd_fd, /*avoid:*/ 0, sqp); /* for REDIRECT_HEREDOC2, rd_filename holds _contents_ * of the heredoc */ debug_printf_parse("set heredoc '%s'\n", @@ -6479,7 +6776,7 @@ static int setup_redirects(struct command *prog, int squirrel[]) } if (openfd != redir->rd_fd) { - int closed = save_fds_on_redirect(redir->rd_fd, squirrel); + int closed = save_fds_on_redirect(redir->rd_fd, /*avoid:*/ openfd, sqp); if (openfd == REDIRFD_CLOSE) { /* "rd_fd >&-" means "close me" */ if (!closed) { @@ -6618,6 +6915,7 @@ static struct function *new_function(char *name) return funcp; } +# if ENABLE_HUSH_UNSET static void unset_func(const char *name) { struct function **funcpp = find_function_slot(name); @@ -6633,13 +6931,14 @@ static void unset_func(const char *name) if (funcp->body) { free_pipe_list(funcp->body); free(funcp->name); -# if !BB_MMU +# if !BB_MMU free(funcp->body_as_string); -# endif +# endif } free(funcp); } } +# endif # if BB_MMU #define exec_function(to_free, funcp, argv) \ @@ -6653,13 +6952,11 @@ static void exec_function(char ***to_free, char **argv) { # if BB_MMU - int n = 1; + int n; argv[0] = G.global_argv[0]; G.global_argv = argv; - while (*++argv) - n++; - G.global_argc = n; + G.global_argc = n = 1 + string_array_len(argv + 1); /* On MMU, funcp->body is always non-NULL */ n = run_list(funcp->body); fflush_all(); @@ -6907,7 +7204,7 @@ static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save, /* Do not leak open fds from opened script files etc */ close_all_FILE_list(); debug_printf_exec("running applet '%s'\n", argv[0]); - run_applet_no_and_exit(a, argv); + run_applet_no_and_exit(a, argv[0], argv); } # endif /* Re-exec ourselves */ @@ -6983,12 +7280,12 @@ static const char *get_cmdtext(struct pipe *pi) * On subsequent bg argv is trashed, but we won't use it */ if (pi->cmdtext) return pi->cmdtext; + argv = pi->cmds[0].argv; - if (!argv || !argv[0]) { + if (!argv) { pi->cmdtext = xzalloc(1); return pi->cmdtext; } - len = 0; do { len += strlen(*argv) + 1; @@ -6997,33 +7294,61 @@ static const char *get_cmdtext(struct pipe *pi) pi->cmdtext = p; argv = pi->cmds[0].argv; do { - len = strlen(*argv); - memcpy(p, *argv, len); - p += len; + p = stpcpy(p, *argv); *p++ = ' '; } while (*++argv); p[-1] = '\0'; return pi->cmdtext; } -static void insert_bg_job(struct pipe *pi) +static void remove_job_from_table(struct pipe *pi) +{ + struct pipe *prev_pipe; + + if (pi == G.job_list) { + G.job_list = pi->next; + } else { + prev_pipe = G.job_list; + while (prev_pipe->next != pi) + prev_pipe = prev_pipe->next; + prev_pipe->next = pi->next; + } + G.last_jobid = 0; + if (G.job_list) + G.last_jobid = G.job_list->jobid; +} + +static void delete_finished_job(struct pipe *pi) +{ + remove_job_from_table(pi); + free_pipe(pi); +} + +static void clean_up_last_dead_job(void) +{ + if (G.job_list && !G.job_list->alive_cmds) + delete_finished_job(G.job_list); +} + +static void insert_job_into_table(struct pipe *pi) { struct pipe *job, **jobp; int i; - /* Linear search for the ID of the job to use */ - pi->jobid = 1; - for (job = G.job_list; job; job = job->next) - if (job->jobid >= pi->jobid) - pi->jobid = job->jobid + 1; + clean_up_last_dead_job(); - /* Add job to the list of running jobs */ + /* Find the end of the list, and find next job ID to use */ + i = 0; jobp = &G.job_list; - while ((job = *jobp) != NULL) + while ((job = *jobp) != NULL) { + if (job->jobid > i) + i = job->jobid; jobp = &job->next; - job = *jobp = xmalloc(sizeof(*job)); + } + pi->jobid = i + 1; - *job = *pi; /* physical copy */ + /* Create a new job struct at the end */ + job = *jobp = xmemdup(pi, sizeof(*pi)); job->next = NULL; job->cmds = xzalloc(sizeof(pi->cmds[0]) * pi->num_cmds); /* Cannot copy entire pi->cmds[] vector! This causes double frees */ @@ -7034,34 +7359,9 @@ static void insert_bg_job(struct pipe *pi) job->cmdtext = xstrdup(get_cmdtext(pi)); if (G_interactive_fd) - printf("[%d] %d %s\n", job->jobid, job->cmds[0].pid, job->cmdtext); + printf("[%u] %u %s\n", job->jobid, (unsigned)job->cmds[0].pid, job->cmdtext); G.last_jobid = job->jobid; } - -static void remove_bg_job(struct pipe *pi) -{ - struct pipe *prev_pipe; - - if (pi == G.job_list) { - G.job_list = pi->next; - } else { - prev_pipe = G.job_list; - while (prev_pipe->next != pi) - prev_pipe = prev_pipe->next; - prev_pipe->next = pi->next; - } - if (G.job_list) - G.last_jobid = G.job_list->jobid; - else - G.last_jobid = 0; -} - -/* Remove a backgrounded job */ -static void delete_finished_bg_job(struct pipe *pi) -{ - remove_bg_job(pi); - free_pipe(pi); -} #endif /* JOB */ static int job_exited_or_stopped(struct pipe *pi) @@ -7148,7 +7448,7 @@ static int process_wait_result(struct pipe *fg_pipe, pid_t childpid, int status) if (G_interactive_fd) { #if ENABLE_HUSH_JOB if (fg_pipe->alive_cmds != 0) - insert_bg_job(fg_pipe); + insert_job_into_table(fg_pipe); #endif return rcode; } @@ -7158,7 +7458,7 @@ static int process_wait_result(struct pipe *fg_pipe, pid_t childpid, int status) /* There are still running processes in the fg_pipe */ return -1; } - /* It wasnt in fg_pipe, look for process in bg pipes */ + /* It wasn't in fg_pipe, look for process in bg pipes */ } #if ENABLE_HUSH_JOB @@ -7177,16 +7477,31 @@ static int process_wait_result(struct pipe *fg_pipe, pid_t childpid, int status) found_pi_and_prognum: if (dead) { /* child exited */ - pi->cmds[i].pid = 0; - pi->cmds[i].cmd_exitcode = WEXITSTATUS(status); + int rcode = WEXITSTATUS(status); if (WIFSIGNALED(status)) - pi->cmds[i].cmd_exitcode = 128 + WTERMSIG(status); + rcode = 128 + WTERMSIG(status); + pi->cmds[i].cmd_exitcode = rcode; + if (G.last_bg_pid == pi->cmds[i].pid) + G.last_bg_pid_exitcode = rcode; + pi->cmds[i].pid = 0; pi->alive_cmds--; if (!pi->alive_cmds) { - if (G_interactive_fd) + if (G_interactive_fd) { printf(JOB_STATUS_FORMAT, pi->jobid, "Done", pi->cmdtext); - delete_finished_bg_job(pi); + delete_finished_job(pi); + } else { +/* + * bash deletes finished jobs from job table only in interactive mode, + * after "jobs" cmd, or if pid of a new process matches one of the old ones + * (see cleanup_dead_jobs(), delete_old_job(), J_NOTIFIED in bash source). + * Testcase script: "(exit 3) & sleep 1; wait %1; echo $?" prints 3 in bash. + * We only retain one "dead" job, if it's the single job on the list. + * This covers most of real-world scenarios where this is useful. + */ + if (pi != G.job_list) + delete_finished_job(pi); + } } } else { /* child stopped */ @@ -7343,14 +7658,14 @@ static int checkjobs_and_fg_shell(struct pipe *fg_pipe) static int redirect_and_varexp_helper(char ***new_env_p, struct variable **old_vars_p, struct command *command, - int squirrel[3], + struct squirrel **sqp, char **argv_expanded) { /* setup_redirects acts on file descriptors, not FILEs. * This is perfect for work that comes after exec(). * Is it really safe for inline use? Experimentally, * things seem to work. */ - int rcode = setup_redirects(command, squirrel); + int rcode = setup_redirects(command, sqp); if (rcode == 0) { char **new_env = expand_assignments(command->argv, command->assignment_cnt); *new_env_p = new_env; @@ -7370,8 +7685,7 @@ static NOINLINE int run_pipe(struct pipe *pi) struct command *command; char **argv_expanded; char **argv; - /* it is not always needed, but we aim to smaller code */ - int squirrel[] = { -1, -1, -1 }; + struct squirrel *squirrel = NULL; int rcode; debug_printf_exec("run_pipe start: members:%d\n", pi->num_cmds); @@ -7428,7 +7742,7 @@ static NOINLINE int run_pipe(struct pipe *pi) /* { list } */ debug_printf("non-subshell group\n"); rcode = 1; /* exitcode if redir failed */ - if (setup_redirects(command, squirrel) == 0) { + if (setup_redirects(command, &squirrel) == 0) { debug_printf_exec(": run_list\n"); rcode = run_list(command->group) & 0xff; } @@ -7455,7 +7769,7 @@ static NOINLINE int run_pipe(struct pipe *pi) /* Ensure redirects take effect (that is, create files). * Try "a=t >file" */ #if 0 /* A few cases in testsuite fail with this code. FIXME */ - rcode = redirect_and_varexp_helper(&new_env, /*old_vars:*/ NULL, command, squirrel, /*argv_expanded:*/ NULL); + rcode = redirect_and_varexp_helper(&new_env, /*old_vars:*/ NULL, command, &squirrel, /*argv_expanded:*/ NULL); /* Set shell variables */ if (new_env) { argv = new_env; @@ -7477,7 +7791,7 @@ static NOINLINE int run_pipe(struct pipe *pi) #else /* Older, bigger, but more correct code */ - rcode = setup_redirects(command, squirrel); + rcode = setup_redirects(command, &squirrel); restore_redirects(squirrel); /* Set shell variables */ if (G_x_mode) @@ -7509,7 +7823,7 @@ static NOINLINE int run_pipe(struct pipe *pi) } /* Expand the rest into (possibly) many strings each */ -#if ENABLE_HUSH_BASH_COMPAT +#if BASH_TEST2 if (command->cmd_type == CMD_SINGLEWORD_NOGLOB) { argv_expanded = expand_strvec_to_strvec_singleword_noglob(argv + command->assignment_cnt); } else @@ -7540,7 +7854,7 @@ static NOINLINE int run_pipe(struct pipe *pi) goto clean_up_and_ret1; } } - rcode = redirect_and_varexp_helper(&new_env, &old_vars, command, squirrel, argv_expanded); + rcode = redirect_and_varexp_helper(&new_env, &old_vars, command, &squirrel, argv_expanded); if (rcode == 0) { if (!funcp) { debug_printf_exec(": builtin '%s' '%s'...\n", @@ -7581,7 +7895,7 @@ static NOINLINE int run_pipe(struct pipe *pi) if (ENABLE_FEATURE_SH_NOFORK) { int n = find_applet_by_name(argv_expanded[0]); if (n >= 0 && APPLET_IS_NOFORK(n)) { - rcode = redirect_and_varexp_helper(&new_env, &old_vars, command, squirrel, argv_expanded); + rcode = redirect_and_varexp_helper(&new_env, &old_vars, command, &squirrel, argv_expanded); if (rcode == 0) { debug_printf_exec(": run_nofork_applet '%s' '%s'...\n", argv_expanded[0], argv_expanded[1]); @@ -7805,6 +8119,9 @@ static int run_list(struct pipe *pi) /* Go through list of pipes, (maybe) executing them. */ for (; pi; pi = IF_HUSH_LOOPS(rword == RES_DONE ? loop_top : ) pi->next) { + int r; + int sv_errexit_depth; + if (G.flag_SIGINT) break; if (G_flag_return_in_progress == 1) @@ -7813,6 +8130,13 @@ static int run_list(struct pipe *pi) IF_HAS_KEYWORDS(rword = pi->res_word;) debug_printf_exec(": rword=%d cond_code=%d last_rword=%d\n", rword, cond_code, last_rword); + + sv_errexit_depth = G.errexit_depth; + if (IF_HAS_KEYWORDS(rword == RES_IF || rword == RES_ELIF ||) + pi->followup != PIPE_SEQ + ) { + G.errexit_depth++; + } #if ENABLE_HUSH_LOOPS if ((rword == RES_WHILE || rword == RES_UNTIL || rword == RES_FOR) && loop_top == NULL /* avoid bumping G.depth_of_loop twice */ @@ -7904,6 +8228,7 @@ static int run_list(struct pipe *pi) if (rword == RES_CASE) { debug_printf_exec("CASE cond_code:%d\n", cond_code); case_word = expand_strvec_to_string(pi->cmds->argv); + unbackslash(case_word); continue; } if (rword == RES_MATCH) { @@ -7915,9 +8240,10 @@ static int run_list(struct pipe *pi) /* all prev words didn't match, does this one match? */ argv = pi->cmds->argv; while (*argv) { - char *pattern = expand_string_to_string(*argv, /*unbackslash:*/ 1); + char *pattern = expand_string_to_string(*argv, /*unbackslash:*/ 0); /* TODO: which FNM_xxx flags to use? */ cond_code = (fnmatch(pattern, case_word, /*flags:*/ 0) != 0); + debug_printf_exec("fnmatch(pattern:'%s',str:'%s'):%d\n", pattern, case_word, cond_code); free(pattern); if (cond_code == 0) { /* match! we will execute this branch */ free(case_word); @@ -7955,75 +8281,82 @@ static int run_list(struct pipe *pi) * after run_pipe to collect any background children, * even if list execution is to be stopped. */ debug_printf_exec(": run_pipe with %d members\n", pi->num_cmds); - { - int r; -#if ENABLE_HUSH_LOOPS - G.flag_break_continue = 0; -#endif - rcode = r = run_pipe(pi); /* NB: rcode is a smallint */ - if (r != -1) { - /* We ran a builtin, function, or group. - * rcode is already known - * and we don't need to wait for anything. */ - G.last_exitcode = rcode; - debug_printf_exec(": builtin/func exitcode %d\n", rcode); - check_and_run_traps(); #if ENABLE_HUSH_LOOPS - /* Was it "break" or "continue"? */ - if (G.flag_break_continue) { - smallint fbc = G.flag_break_continue; - /* We might fall into outer *loop*, - * don't want to break it too */ - if (loop_top) { - G.depth_break_continue--; - if (G.depth_break_continue == 0) - G.flag_break_continue = 0; - /* else: e.g. "continue 2" should *break* once, *then* continue */ - } /* else: "while... do... { we are here (innermost list is not a loop!) };...done" */ - if (G.depth_break_continue != 0 || fbc == BC_BREAK) { - checkjobs(NULL, 0 /*(no pid to wait for)*/); - break; - } - /* "continue": simulate end of loop */ - rword = RES_DONE; - continue; - } + G.flag_break_continue = 0; #endif - if (G_flag_return_in_progress == 1) { + rcode = r = run_pipe(pi); /* NB: rcode is a smalluint, r is int */ + if (r != -1) { + /* We ran a builtin, function, or group. + * rcode is already known + * and we don't need to wait for anything. */ + debug_printf_exec(": builtin/func exitcode %d\n", rcode); + G.last_exitcode = rcode; + check_and_run_traps(); +#if ENABLE_HUSH_LOOPS + /* Was it "break" or "continue"? */ + if (G.flag_break_continue) { + smallint fbc = G.flag_break_continue; + /* We might fall into outer *loop*, + * don't want to break it too */ + if (loop_top) { + G.depth_break_continue--; + if (G.depth_break_continue == 0) + G.flag_break_continue = 0; + /* else: e.g. "continue 2" should *break* once, *then* continue */ + } /* else: "while... do... { we are here (innermost list is not a loop!) };...done" */ + if (G.depth_break_continue != 0 || fbc == BC_BREAK) { checkjobs(NULL, 0 /*(no pid to wait for)*/); break; } - } else if (pi->followup == PIPE_BG) { - /* What does bash do with attempts to background builtins? */ - /* even bash 3.2 doesn't do that well with nested bg: - * try "{ { sleep 10; echo DEEP; } & echo HERE; } &". - * I'm NOT treating inner &'s as jobs */ - check_and_run_traps(); -#if ENABLE_HUSH_JOB - if (G.run_list_level == 1) - insert_bg_job(pi); + /* "continue": simulate end of loop */ + rword = RES_DONE; + continue; + } #endif - /* Last command's pid goes to $! */ - G.last_bg_pid = pi->cmds[pi->num_cmds - 1].pid; - G.last_exitcode = rcode = EXIT_SUCCESS; - debug_printf_exec(": cmd&: exitcode EXIT_SUCCESS\n"); - } else { + if (G_flag_return_in_progress == 1) { + checkjobs(NULL, 0 /*(no pid to wait for)*/); + break; + } + } else if (pi->followup == PIPE_BG) { + /* What does bash do with attempts to background builtins? */ + /* even bash 3.2 doesn't do that well with nested bg: + * try "{ { sleep 10; echo DEEP; } & echo HERE; } &". + * I'm NOT treating inner &'s as jobs */ #if ENABLE_HUSH_JOB - if (G.run_list_level == 1 && G_interactive_fd) { - /* Waits for completion, then fg's main shell */ - rcode = checkjobs_and_fg_shell(pi); - debug_printf_exec(": checkjobs_and_fg_shell exitcode %d\n", rcode); - check_and_run_traps(); - } else -#endif - { /* This one just waits for completion */ - rcode = checkjobs(pi, 0 /*(no pid to wait for)*/); - debug_printf_exec(": checkjobs exitcode %d\n", rcode); - check_and_run_traps(); - } - G.last_exitcode = rcode; + if (G.run_list_level == 1) + insert_job_into_table(pi); +#endif + /* Last command's pid goes to $! */ + G.last_bg_pid = pi->cmds[pi->num_cmds - 1].pid; + G.last_bg_pid_exitcode = 0; + debug_printf_exec(": cmd&: exitcode EXIT_SUCCESS\n"); +/* Check pi->pi_inverted? "! sleep 1 & echo $?": bash says 1. dash and ash says 0 */ + rcode = EXIT_SUCCESS; + goto check_traps; + } else { +#if ENABLE_HUSH_JOB + if (G.run_list_level == 1 && G_interactive_fd) { + /* Waits for completion, then fg's main shell */ + rcode = checkjobs_and_fg_shell(pi); + debug_printf_exec(": checkjobs_and_fg_shell exitcode %d\n", rcode); + goto check_traps; } +#endif + /* This one just waits for completion */ + rcode = checkjobs(pi, 0 /*(no pid to wait for)*/); + debug_printf_exec(": checkjobs exitcode %d\n", rcode); + check_traps: + G.last_exitcode = rcode; + check_and_run_traps(); + } + + /* Handle "set -e" */ + if (rcode != 0 && G.o_opt[OPT_O_ERREXIT]) { + debug_printf_exec("ERREXIT:1 errexit_depth:%d\n", G.errexit_depth); + if (G.errexit_depth == 0) + hush_exit(rcode); } + G.errexit_depth = sv_errexit_depth; /* Analyze how result affects subsequent commands */ #if ENABLE_HUSH_IF @@ -8109,10 +8442,12 @@ static void install_sighandlers(unsigned mask) if (old_handler == SIG_IGN) { /* oops... restore back to IGN, and record this fact */ install_sighandler(sig, old_handler); - if (!G.traps) - G.traps = xzalloc(sizeof(G.traps[0]) * NSIG); - free(G.traps[sig]); - G.traps[sig] = xzalloc(1); /* == xstrdup(""); */ +#if ENABLE_HUSH_TRAP + if (!G_traps) + G_traps = xzalloc(sizeof(G_traps[0]) * NSIG); + free(G_traps[sig]); + G_traps[sig] = xzalloc(1); /* == xstrdup(""); */ +#endif } } } @@ -8146,11 +8481,11 @@ static void install_fatal_sighandlers(void) /* We will restore tty pgrp on these signals */ mask = 0 - + (1 << SIGILL ) * HUSH_DEBUG - + (1 << SIGFPE ) * HUSH_DEBUG + /*+ (1 << SIGILL ) * HUSH_DEBUG*/ + /*+ (1 << SIGFPE ) * HUSH_DEBUG*/ + (1 << SIGBUS ) * HUSH_DEBUG + (1 << SIGSEGV) * HUSH_DEBUG - + (1 << SIGTRAP) * HUSH_DEBUG + /*+ (1 << SIGTRAP) * HUSH_DEBUG*/ + (1 << SIGABRT) /* bash 3.2 seems to handle these just like 'fatal' ones */ + (1 << SIGPIPE) @@ -8202,6 +8537,9 @@ static int set_mode(int state, char mode, const char *o_opt) G.o_opt[idx] = state; break; } + case 'e': + G.o_opt[OPT_O_ERREXIT] = state; + break; default: return EXIT_FAILURE; } @@ -8263,7 +8601,7 @@ int hush_main(int argc, char **argv) /* Export PWD */ set_pwd_var(/*exp:*/ 1); -#if ENABLE_HUSH_BASH_COMPAT +#if BASH_HOSTNAME_VAR /* Set (but not export) HOSTNAME unless already set */ if (!get_local_var_value("HOSTNAME")) { struct utsname uts; @@ -8328,7 +8666,7 @@ int hush_main(int argc, char **argv) flags = (argv[0] && argv[0][0] == '-') ? OPT_login : 0; builtin_argc = 0; while (1) { - opt = getopt(argc, argv, "+c:xinsl" + opt = getopt(argc, argv, "+c:exinsl" #if !BB_MMU "<:$:R:V:" # if ENABLE_HUSH_FUNCTIONS @@ -8411,15 +8749,17 @@ int hush_main(int argc, char **argv) optarg++; empty_trap_mask = bb_strtoull(optarg, &optarg, 16); if (empty_trap_mask != 0) { - int sig; + IF_HUSH_TRAP(int sig;) install_special_sighandlers(); - G.traps = xzalloc(sizeof(G.traps[0]) * NSIG); +# if ENABLE_HUSH_TRAP + G_traps = xzalloc(sizeof(G_traps[0]) * NSIG); for (sig = 1; sig < NSIG; sig++) { if (empty_trap_mask & (1LL << sig)) { - G.traps[sig] = xzalloc(1); /* == xstrdup(""); */ + G_traps[sig] = xzalloc(1); /* == xstrdup(""); */ install_sighandler(sig, SIG_IGN); } } +# endif } # if ENABLE_HUSH_LOOPS optarg++; @@ -8444,6 +8784,7 @@ int hush_main(int argc, char **argv) #endif case 'n': case 'x': + case 'e': if (set_mode(1, opt, NULL) == 0) /* no error */ break; default: @@ -8530,7 +8871,7 @@ int hush_main(int argc, char **argv) G_saved_tty_pgrp = 0; /* try to dup stdin to high fd#, >= 255 */ - G_interactive_fd = fcntl(STDIN_FILENO, F_DUPFD, 255); + G_interactive_fd = fcntl_F_DUPFD(STDIN_FILENO, 254); if (G_interactive_fd < 0) { /* try to dup to any fd */ G_interactive_fd = dup(STDIN_FILENO); @@ -8603,7 +8944,7 @@ int hush_main(int argc, char **argv) #elif ENABLE_HUSH_INTERACTIVE /* No job control compiled in, only prompt/line editing */ if (isatty(STDIN_FILENO) && isatty(STDOUT_FILENO)) { - G_interactive_fd = fcntl(STDIN_FILENO, F_DUPFD, 255); + G_interactive_fd = fcntl_F_DUPFD(STDIN_FILENO, 254); if (G_interactive_fd < 0) { /* try to dup to any fd */ G_interactive_fd = dup(STDIN_FILENO); @@ -8642,16 +8983,6 @@ int hush_main(int argc, char **argv) } -#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 - - /* * Built-ins */ @@ -8660,33 +8991,56 @@ static int FAST_FUNC builtin_true(char **argv UNUSED_PARAM) return 0; } +#if ENABLE_HUSH_TEST || ENABLE_HUSH_ECHO || ENABLE_HUSH_PRINTF || ENABLE_HUSH_KILL static int run_applet_main(char **argv, int (*applet_main_func)(int argc, char **argv)) { - int argc = 0; - while (*argv) { - argc++; - argv++; - } - return applet_main_func(argc, argv - argc); + int argc = string_array_len(argv); + return applet_main_func(argc, argv); } - +#endif +#if ENABLE_HUSH_TEST || BASH_TEST2 static int FAST_FUNC builtin_test(char **argv) { return run_applet_main(argv, test_main); } - +#endif +#if ENABLE_HUSH_ECHO static int FAST_FUNC builtin_echo(char **argv) { return run_applet_main(argv, echo_main); } - -#if ENABLE_PRINTF +#endif +#if ENABLE_HUSH_PRINTF static int FAST_FUNC builtin_printf(char **argv) { return run_applet_main(argv, printf_main); } #endif +#if ENABLE_HUSH_HELP +static int FAST_FUNC builtin_help(char **argv UNUSED_PARAM) +{ + const struct built_in_command *x; + + printf( + "Built-in commands:\n" + "------------------\n"); + for (x = bltins1; x != &bltins1[ARRAY_SIZE(bltins1)]; x++) { + if (x->b_descr) + printf("%-10s%s\n", x->b_cmd, x->b_descr); + } + return EXIT_SUCCESS; +} +#endif + +#if MAX_HISTORY && ENABLE_FEATURE_EDITING +static int FAST_FUNC builtin_history(char **argv UNUSED_PARAM) +{ + show_history(G.line_input_state); + return EXIT_SUCCESS; +} +#endif + static char **skip_dash_dash(char **argv) { argv++; @@ -8695,24 +9049,6 @@ static char **skip_dash_dash(char **argv) return argv; } -static int FAST_FUNC builtin_eval(char **argv) -{ - int rcode = EXIT_SUCCESS; - - argv = skip_dash_dash(argv); - if (*argv) { - char *str = expand_strvec_to_string(argv); - /* bash: - * eval "echo Hi; done" ("done" is syntax error): - * "echo Hi" will not execute too. - */ - parse_and_run_string(str); - free(str); - rcode = G.last_exitcode; - } - return rcode; -} - static int FAST_FUNC builtin_cd(char **argv) { const char *newdir; @@ -8740,6 +9076,30 @@ static int FAST_FUNC builtin_cd(char **argv) return EXIT_SUCCESS; } +static int FAST_FUNC builtin_pwd(char **argv UNUSED_PARAM) +{ + puts(get_cwd(0)); + return EXIT_SUCCESS; +} + +static int FAST_FUNC builtin_eval(char **argv) +{ + int rcode = EXIT_SUCCESS; + + argv = skip_dash_dash(argv); + if (*argv) { + char *str = expand_strvec_to_string(argv); + /* bash: + * eval "echo Hi; done" ("done" is syntax error): + * "echo Hi" will not execute too. + */ + parse_and_run_string(str); + free(str); + rcode = G.last_exitcode; + } + return rcode; +} + static int FAST_FUNC builtin_exec(char **argv) { argv = skip_dash_dash(argv); @@ -8785,49 +9145,195 @@ static int FAST_FUNC builtin_exit(char **argv) hush_exit(xatoi(argv[0]) & 0xff); } -static void print_escaped(const char *s) +#if ENABLE_HUSH_TYPE +/* http://www.opengroup.org/onlinepubs/9699919799/utilities/type.html */ +static int FAST_FUNC builtin_type(char **argv) { - if (*s == '\'') - goto squote; - do { - 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 == '\''); - putchar('"'); - } while (*s); -} + int ret = EXIT_SUCCESS; -#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; - char *name_end = strchrnul(name, '='); + while (*++argv) { + const char *type; + char *path = NULL; - /* So far we do not check that name is valid (TODO?) */ + 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; + } - if (*name_end == '\0') { - struct variable *var, **vpp; + printf("%s is %s\n", *argv, type); + free(path); + } - vpp = get_ptr_to_local_var(name, name_end - name); - var = vpp ? *vpp : NULL; + return ret; +} +#endif - if (exp == -1) { /* unexporting? */ - /* export -n NAME (without =VALUE) */ - if (var) { - var->flg_export = 0; - debug_printf_env("%s: unsetenv '%s'\n", __func__, name); - unsetenv(name); +#if ENABLE_HUSH_READ +/* Interruptibility of read builtin in bash + * (tested on bash-4.2.8 by sending signals (not by ^C)): + * + * Empty trap makes read ignore corresponding signal, for any signal. + * + * SIGINT: + * - terminates non-interactive shell; + * - interrupts read in interactive shell; + * if it has non-empty trap: + * - executes trap and returns to command prompt in interactive shell; + * - executes trap and returns to read in non-interactive shell; + * SIGTERM: + * - is ignored (does not interrupt) read in interactive shell; + * - terminates non-interactive shell; + * if it has non-empty trap: + * - executes trap and returns to read; + * SIGHUP: + * - terminates shell (regardless of interactivity); + * if it has non-empty trap: + * - executes trap and returns to read; + * SIGCHLD from children: + * - does not interrupt read regardless of interactivity: + * try: sleep 1 & read x; echo $x + */ +static int FAST_FUNC builtin_read(char **argv) +{ + const char *r; + char *opt_n = NULL; + char *opt_p = NULL; + char *opt_t = NULL; + char *opt_u = NULL; + const char *ifs; + int read_flags; + + /* "!": do not abort on errors. + * Option string must start with "sr" to match BUILTIN_READ_xxx + */ + read_flags = getopt32(argv, "!srn:p:t:u:", &opt_n, &opt_p, &opt_t, &opt_u); + if (read_flags == (uint32_t)-1) + return EXIT_FAILURE; + argv += optind; + ifs = get_local_var_value("IFS"); /* can be NULL */ + + again: + r = shell_builtin_read(set_local_var_from_halves, + argv, + ifs, + read_flags, + opt_n, + opt_p, + opt_t, + opt_u + ); + + if ((uintptr_t)r == 1 && errno == EINTR) { + unsigned sig = check_and_run_traps(); + if (sig != SIGINT) + goto again; + } + + if ((uintptr_t)r > 1) { + bb_error_msg("%s", r); + r = (char*)(uintptr_t)1; + } + + return (uintptr_t)r; +} +#endif + +#if ENABLE_HUSH_UMASK +static int FAST_FUNC builtin_umask(char **argv) +{ + int rc; + mode_t mask; + + rc = 1; + mask = umask(0); + argv = skip_dash_dash(argv); + if (argv[0]) { + mode_t old_mask = mask; + + /* numeric umasks are taken as-is */ + /* symbolic umasks are inverted: "umask a=rx" calls umask(222) */ + if (!isdigit(argv[0][0])) + mask ^= 0777; + mask = bb_parse_mode(argv[0], mask); + if (!isdigit(argv[0][0])) + mask ^= 0777; + if ((unsigned)mask > 0777) { + mask = old_mask; + /* bash messages: + * bash: umask: 'q': invalid symbolic mode operator + * bash: umask: 999: octal number out of range + */ + bb_error_msg("%s: invalid mode '%s'", "umask", argv[0]); + rc = 0; + } + } else { + /* Mimic bash */ + printf("%04o\n", (unsigned) mask); + /* fall through and restore mask which we set to 0 */ + } + umask(mask); + + return !rc; /* rc != 0 - success */ +} +#endif + +#if ENABLE_HUSH_EXPORT || ENABLE_HUSH_TRAP +static void print_escaped(const char *s) +{ + if (*s == '\'') + goto squote; + do { + 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 == '\''); + putchar('"'); + } while (*s); +} +#endif + +#if ENABLE_HUSH_EXPORT || ENABLE_HUSH_LOCAL || ENABLE_HUSH_READONLY +static int helper_export_local(char **argv, + int exp UNUSED_PARAM, + int ro UNUSED_PARAM, + int lvl UNUSED_PARAM) +{ + do { + char *name = *argv; + char *name_end = strchrnul(name, '='); + + /* So far we do not check that name is valid (TODO?) */ + + if (*name_end == '\0') { + struct variable *var, **vpp; + + vpp = get_ptr_to_local_var(name, name_end - name); + var = vpp ? *vpp : NULL; + + if (exp == -1) { /* unexporting? */ + /* export -n NAME (without =VALUE) */ + if (var) { + var->flg_export = 0; + debug_printf_env("%s: unsetenv '%s'\n", __func__, name); + unsetenv(name); } /* else: export -n NOT_EXISTING_VAR: no-op */ continue; } @@ -8840,30 +9346,39 @@ static void helper_export_local(char **argv, int exp, int lvl) continue; } } -#if ENABLE_HUSH_LOCAL - if (exp == 0 /* local? */ +# if ENABLE_HUSH_LOCAL + if (exp == 0 && ro == 0 /* local? */ && var && var->func_nest_level == lvl ) { /* "local x=abc; ...; local x" - ignore second local decl */ continue; } -#endif +# endif /* 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. */ + * We just set it to "" and export. + */ /* Or, it's "local NAME" (without =VALUE). - * bash sets the value to "". */ + * bash sets the value to "". + */ + /* Or, it's "readonly NAME" (without =VALUE). + * bash remembers NAME and disallows its creation + * in the future. + */ name = xasprintf("%s=", name); } else { /* (Un)exporting/making local NAME=VALUE */ name = xstrdup(name); } - set_local_var(name, /*exp:*/ exp, /*lvl:*/ lvl, /*ro:*/ 0); + set_local_var(name, /*exp:*/ exp, /*lvl:*/ lvl, /*ro:*/ ro); } while (*++argv); + return EXIT_SUCCESS; } +#endif +#if ENABLE_HUSH_EXPORT static int FAST_FUNC builtin_export(char **argv) { unsigned opt_unexport; @@ -8905,10 +9420,9 @@ static int FAST_FUNC builtin_export(char **argv) return EXIT_SUCCESS; } - helper_export_local(argv, (opt_unexport ? -1 : 1), 0); - - return EXIT_SUCCESS; + return helper_export_local(argv, /*exp:*/ (opt_unexport ? -1 : 1), /*ro:*/ 0, /*lvl:*/ 0); } +#endif #if ENABLE_HUSH_LOCAL static int FAST_FUNC builtin_local(char **argv) @@ -8917,11 +9431,33 @@ static int FAST_FUNC builtin_local(char **argv) 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; + argv++; + return helper_export_local(argv, /*exp:*/ 0, /*ro:*/ 0, /*lvl:*/ G.func_nest_level); +} +#endif + +#if ENABLE_HUSH_READONLY +static int FAST_FUNC builtin_readonly(char **argv) +{ + if (*++argv == NULL) { + /* bash: readonly [-p]: list all readonly VARs + * (-p has no effect in bash) + */ + struct variable *e; + for (e = G.top_var; e; e = e->next) { + if (e->flg_read_only) { +//TODO: quote value: readonly VAR='VAL' + printf("readonly %s\n", e->varstr); + } + } + return EXIT_SUCCESS; + } + return helper_export_local(argv, /*exp:*/ 0, /*ro:*/ 1, /*lvl:*/ 0); } #endif + +#if ENABLE_HUSH_UNSET /* http://www.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#unset */ static int FAST_FUNC builtin_unset(char **argv) { @@ -8949,16 +9485,18 @@ static int FAST_FUNC builtin_unset(char **argv) ret = EXIT_FAILURE; } } -#if ENABLE_HUSH_FUNCTIONS +# if ENABLE_HUSH_FUNCTIONS else { unset_func(*argv); } -#endif +# endif argv++; } return ret; } +#endif +#if ENABLE_HUSH_SET /* http://www.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#set * built-in 'set' handler * SUSv3 says: @@ -9029,10 +9567,7 @@ static int FAST_FUNC builtin_set(char **argv) /* This realloc's G.global_argv */ G.global_argv = pp = add_strings_to_strings(g_argv, argv, /*dup:*/ 1); - n = 1; - while (*++pp) - n++; - G.global_argc = n; + G.global_argc = 1 + string_array_len(pp + 1); return EXIT_SUCCESS; @@ -9041,16 +9576,28 @@ static int FAST_FUNC builtin_set(char **argv) bb_error_msg("set: %s: invalid option", arg); return EXIT_FAILURE; } +#endif static int FAST_FUNC builtin_shift(char **argv) { int n = 1; argv = skip_dash_dash(argv); if (argv[0]) { - n = atoi(argv[0]); + n = bb_strtou(argv[0], NULL, 10); + if (errno || n < 0) { + /* shared string with ash.c */ + bb_error_msg("Illegal number: %s", argv[0]); + /* + * ash aborts in this case. + * bash prints error message and set $? to 1. + * Interestingly, for "shift 99999" bash does not + * print error message, but does set $? to 1 + * (and does no shifting at all). + */ + } } if (n >= 0 && n < G.global_argc) { - if (G.global_args_malloced) { + if (G_global_args_malloced) { int m = 1; while (m <= n) free(G.global_argv[m++]); @@ -9063,87 +9610,78 @@ static int FAST_FUNC builtin_shift(char **argv) return EXIT_FAILURE; } -/* Interruptibility of read builtin in bash - * (tested on bash-4.2.8 by sending signals (not by ^C)): - * - * Empty trap makes read ignore corresponding signal, for any signal. - * - * SIGINT: - * - terminates non-interactive shell; - * - interrupts read in interactive shell; - * if it has non-empty trap: - * - executes trap and returns to command prompt in interactive shell; - * - executes trap and returns to read in non-interactive shell; - * SIGTERM: - * - is ignored (does not interrupt) read in interactive shell; - * - terminates non-interactive shell; - * if it has non-empty trap: - * - executes trap and returns to read; - * SIGHUP: - * - terminates shell (regardless of interactivity); - * if it has non-empty trap: - * - executes trap and returns to read; - */ -static int FAST_FUNC builtin_read(char **argv) +static int FAST_FUNC builtin_source(char **argv) { - const char *r; - char *opt_n = NULL; - char *opt_p = NULL; - char *opt_t = NULL; - char *opt_u = NULL; - const char *ifs; - int read_flags; + char *arg_path, *filename; + FILE *input; + save_arg_t sv; + char *args_need_save; +#if ENABLE_HUSH_FUNCTIONS + smallint sv_flg; +#endif - /* "!": do not abort on errors. - * Option string must start with "sr" to match BUILTIN_READ_xxx - */ - read_flags = getopt32(argv, "!srn:p:t:u:", &opt_n, &opt_p, &opt_t, &opt_u); - if (read_flags == (uint32_t)-1) + argv = skip_dash_dash(argv); + filename = argv[0]; + if (!filename) { + /* bash says: "bash: .: filename argument required" */ + return 2; /* bash compat */ + } + arg_path = NULL; + if (!strchr(filename, '/')) { + arg_path = find_in_path(filename); + if (arg_path) + filename = arg_path; + } + input = remember_FILE(fopen_or_warn(filename, "r")); + free(arg_path); + if (!input) { + /* bb_perror_msg("%s", *argv); - done by fopen_or_warn */ + /* POSIX: non-interactive shell should abort here, + * not merely fail. So far no one complained :) + */ return EXIT_FAILURE; - argv += optind; - ifs = get_local_var_value("IFS"); /* can be NULL */ + } - again: - r = shell_builtin_read(set_local_var_from_halves, - argv, - ifs, - read_flags, - opt_n, - opt_p, - opt_t, - opt_u - ); +#if ENABLE_HUSH_FUNCTIONS + sv_flg = G_flag_return_in_progress; + /* "we are inside sourced file, ok to use return" */ + G_flag_return_in_progress = -1; +#endif + args_need_save = argv[1]; /* used as a boolean variable */ + if (args_need_save) + save_and_replace_G_args(&sv, argv); - if ((uintptr_t)r == 1 && errno == EINTR) { - unsigned sig = check_and_run_traps(); - if (sig && sig != SIGINT) - goto again; - } + /* "false; . ./empty_line; echo Zero:$?" should print 0 */ + G.last_exitcode = 0; + parse_and_run_file(input); + fclose_and_forget(input); - if ((uintptr_t)r > 1) { - bb_error_msg("%s", r); - r = (char*)(uintptr_t)1; - } + if (args_need_save) /* can't use argv[1] instead: "shift" can mangle it */ + restore_G_args(&sv, argv); +#if ENABLE_HUSH_FUNCTIONS + G_flag_return_in_progress = sv_flg; +#endif - return (uintptr_t)r; + return G.last_exitcode; } +#if ENABLE_HUSH_TRAP static int FAST_FUNC builtin_trap(char **argv) { int sig; char *new_cmd; - if (!G.traps) - G.traps = xzalloc(sizeof(G.traps[0]) * NSIG); + if (!G_traps) + G_traps = xzalloc(sizeof(G_traps[0]) * NSIG); argv++; if (!*argv) { int i; /* No args: print all trapped */ for (i = 0; i < NSIG; ++i) { - if (G.traps[i]) { + if (G_traps[i]) { printf("trap -- "); - print_escaped(G.traps[i]); + 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. @@ -9169,15 +9707,15 @@ static int FAST_FUNC builtin_trap(char **argv) if (sig < 0 || sig >= NSIG) { ret = EXIT_FAILURE; /* Mimic bash message exactly */ - bb_perror_msg("trap: %s: invalid signal specification", argv[-1]); + bb_error_msg("trap: %s: invalid signal specification", argv[-1]); continue; } - free(G.traps[sig]); - G.traps[sig] = xstrdup(new_cmd); + free(G_traps[sig]); + G_traps[sig] = xstrdup(new_cmd); debug_printf("trap: setting SIG%s (%i) to '%s'\n", - get_signame(sig), sig, G.traps[sig]); + get_signame(sig), sig, G_traps[sig]); /* There is no signal for 0 (EXIT) */ if (sig == 0) @@ -9217,57 +9755,55 @@ static int FAST_FUNC builtin_trap(char **argv) argv++; goto process_sig_list; } +#endif -/* http://www.opengroup.org/onlinepubs/9699919799/utilities/type.html */ -static int FAST_FUNC builtin_type(char **argv) +#if ENABLE_HUSH_JOB +static struct pipe *parse_jobspec(const char *str) { - int ret = EXIT_SUCCESS; - - while (*++argv) { - const char *type; - char *path = NULL; + struct pipe *pi; + unsigned jobnum; - 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; + if (sscanf(str, "%%%u", &jobnum) != 1) { + if (str[0] != '%' + || (str[1] != '%' && str[1] != '+' && str[1] != '\0') + ) { + bb_error_msg("bad argument '%s'", str); + return NULL; + } + /* It is "%%", "%+" or "%" - current job */ + jobnum = G.last_jobid; + if (jobnum == 0) { + bb_error_msg("no current job"); + return NULL; + } + } + for (pi = G.job_list; pi; pi = pi->next) { + if (pi->jobid == jobnum) { + return pi; } - - printf("%s is %s\n", *argv, type); - free(path); } + bb_error_msg("%u: no such job", jobnum); + return NULL; +} + +static int FAST_FUNC builtin_jobs(char **argv UNUSED_PARAM) +{ + struct pipe *job; + const char *status_string; + + checkjobs(NULL, 0 /*(no pid to wait for)*/); + for (job = G.job_list; job; job = job->next) { + if (job->alive_cmds == job->stopped_cmds) + status_string = "Stopped"; + else + status_string = "Running"; - return ret; -} + printf(JOB_STATUS_FORMAT, job->jobid, status_string, job->cmdtext); + } -#if ENABLE_HUSH_JOB -static struct pipe *parse_jobspec(const char *str) -{ - struct pipe *pi; - int jobnum; + clean_up_last_dead_job(); - if (sscanf(str, "%%%d", &jobnum) != 1) { - bb_error_msg("bad argument '%s'", str); - return NULL; - } - for (pi = G.job_list; pi; pi = pi->next) { - if (pi->jobid == jobnum) { - return pi; - } - } - bb_error_msg("%d: no such job", jobnum); - return NULL; + return EXIT_SUCCESS; } /* built-in 'fg' and 'bg' handler */ @@ -9311,202 +9847,95 @@ static int FAST_FUNC builtin_fg_bg(char **argv) i = kill(- pi->pgrp, SIGCONT); if (i < 0) { if (errno == ESRCH) { - delete_finished_bg_job(pi); + delete_finished_job(pi); return EXIT_SUCCESS; } bb_perror_msg("kill (SIGCONT)"); } if (argv[0][0] == 'f') { - remove_bg_job(pi); + remove_job_from_table(pi); /* FG job shouldn't be in job table */ return checkjobs_and_fg_shell(pi); } return EXIT_SUCCESS; } #endif -#if ENABLE_HUSH_HELP -static int FAST_FUNC builtin_help(char **argv UNUSED_PARAM) -{ - const struct built_in_command *x; - - printf( - "Built-in commands:\n" - "------------------\n"); - for (x = bltins1; x != &bltins1[ARRAY_SIZE(bltins1)]; x++) { - if (x->b_descr) - printf("%-10s%s\n", x->b_cmd, x->b_descr); - } - return EXIT_SUCCESS; -} -#endif - -#if MAX_HISTORY && ENABLE_FEATURE_EDITING -static int FAST_FUNC builtin_history(char **argv UNUSED_PARAM) +#if ENABLE_HUSH_KILL +static int FAST_FUNC builtin_kill(char **argv) { - show_history(G.line_input_state); - return EXIT_SUCCESS; -} -#endif + int ret = 0; -#if ENABLE_HUSH_JOB -static int FAST_FUNC builtin_jobs(char **argv UNUSED_PARAM) -{ - struct pipe *job; - const char *status_string; +# if ENABLE_HUSH_JOB + if (argv[1] && strcmp(argv[1], "-l") != 0) { + int i = 1; - checkjobs(NULL, 0 /*(no pid to wait for)*/); - for (job = G.job_list; job; job = job->next) { - if (job->alive_cmds == job->stopped_cmds) - status_string = "Stopped"; - else - status_string = "Running"; + do { + struct pipe *pi; + char *dst; + int j, n; - printf(JOB_STATUS_FORMAT, job->jobid, status_string, job->cmdtext); + if (argv[i][0] != '%') + continue; + /* + * "kill %N" - job kill + * Converting to pgrp / pid kill + */ + pi = parse_jobspec(argv[i]); + if (!pi) { + /* Eat bad jobspec */ + j = i; + do { + j++; + argv[j - 1] = argv[j]; + } while (argv[j]); + ret = 1; + i--; + continue; + } + /* + * In jobs started under job control, we signal + * entire process group by kill -PGRP_ID. + * This happens, f.e., in interactive shell. + * + * Otherwise, we signal each child via + * kill PID1 PID2 PID3. + * Testcases: + * sh -c 'sleep 1|sleep 1 & kill %1' + * sh -c 'true|sleep 2 & sleep 1; kill %1' + * sh -c 'true|sleep 1 & sleep 2; kill %1' + */ + n = G_interactive_fd ? 1 : pi->num_cmds; + dst = alloca(n * sizeof(int)*4); + argv[i] = dst; + if (G_interactive_fd) + dst += sprintf(dst, " -%u", (int)pi->pgrp); + else for (j = 0; j < n; j++) { + struct command *cmd = &pi->cmds[j]; + /* Skip exited members of the job */ + if (cmd->pid == 0) + continue; + /* + * kill_main has matching code to expect + * leading space. Needed to not confuse + * negative pids with "kill -SIGNAL_NO" syntax + */ + dst += sprintf(dst, " %u", (int)cmd->pid); + } + *dst = '\0'; + } while (argv[++i]); } - return EXIT_SUCCESS; -} -#endif - -#if HUSH_DEBUG -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); - l = (unsigned long)p; - free(p); - p = malloc(3400); - if (l < (unsigned long)p) l = (unsigned long)p; - free(p); - -# if 0 /* debug */ - { - struct mallinfo mi = mallinfo(); - printf("top alloc:0x%lx malloced:%d+%d=%d\n", l, - mi.arena, mi.hblkhd, mi.arena + mi.hblkhd); + if (argv[1] || ret == 0) { + ret = run_applet_main(argv, kill_main); } -# endif - - if (!G.memleak_value) - G.memleak_value = l; - - l -= G.memleak_value; - if ((long)l < 0) - l = 0; - l /= 1024; - if (l > 127) - l = 127; - - /* Exitcode is "how many kilobytes we leaked since 1st call" */ - return l; -} -#endif - -static int FAST_FUNC builtin_pwd(char **argv UNUSED_PARAM) -{ - puts(get_cwd(0)); - return EXIT_SUCCESS; + /* else: ret = 1, "kill %bad_jobspec" case */ + return ret; } - -static int FAST_FUNC builtin_source(char **argv) -{ - char *arg_path, *filename; - FILE *input; - save_arg_t sv; -#if ENABLE_HUSH_FUNCTIONS - smallint sv_flg; -#endif - - argv = skip_dash_dash(argv); - filename = argv[0]; - if (!filename) { - /* bash says: "bash: .: filename argument required" */ - return 2; /* bash compat */ - } - arg_path = NULL; - if (!strchr(filename, '/')) { - arg_path = find_in_path(filename); - if (arg_path) - filename = arg_path; - } - input = remember_FILE(fopen_or_warn(filename, "r")); - free(arg_path); - if (!input) { - /* bb_perror_msg("%s", *argv); - done by fopen_or_warn */ - /* POSIX: non-interactive shell should abort here, - * not merely fail. So far no one complained :) - */ - return EXIT_FAILURE; - } - -#if ENABLE_HUSH_FUNCTIONS - sv_flg = G_flag_return_in_progress; - /* "we are inside sourced file, ok to use return" */ - G_flag_return_in_progress = -1; -#endif - if (argv[1]) - save_and_replace_G_args(&sv, argv); - - /* "false; . ./empty_line; echo Zero:$?" should print 0 */ - G.last_exitcode = 0; - parse_and_run_file(input); - fclose_and_forget(input); - - if (argv[1]) - restore_G_args(&sv, argv); -#if ENABLE_HUSH_FUNCTIONS - G_flag_return_in_progress = sv_flg; #endif - return G.last_exitcode; -} - -static int FAST_FUNC builtin_umask(char **argv) -{ - int rc; - mode_t mask; - - rc = 1; - mask = umask(0); - argv = skip_dash_dash(argv); - if (argv[0]) { - mode_t old_mask = mask; - - /* numeric umasks are taken as-is */ - /* symbolic umasks are inverted: "umask a=rx" calls umask(222) */ - if (!isdigit(argv[0][0])) - mask ^= 0777; - mask = bb_parse_mode(argv[0], mask); - if (!isdigit(argv[0][0])) - mask ^= 0777; - if ((unsigned)mask > 0777) { - mask = old_mask; - /* bash messages: - * bash: umask: 'q': invalid symbolic mode operator - * bash: umask: 999: octal number out of range - */ - bb_error_msg("%s: invalid mode '%s'", "umask", argv[0]); - rc = 0; - } - } else { - /* Mimic bash */ - printf("%04o\n", (unsigned) mask); - /* fall through and restore mask which we set to 0 */ - } - umask(mask); - - return !rc; /* rc != 0 - success */ -} - +#if ENABLE_HUSH_WAIT /* http://www.opengroup.org/onlinepubs/9699919799/utilities/wait.html */ #if !ENABLE_HUSH_JOB # define wait_for_child_or_signal(pipe,pid) wait_for_child_or_signal(pid) @@ -9518,6 +9947,9 @@ static int wait_for_child_or_signal(struct pipe *waitfor_pipe, pid_t waitfor_pid int sig; sigset_t oldset; + if (!sigisemptyset(&G.pending_set)) + goto check_sig; + /* waitpid is not interruptible by SA_RESTARTed * signals which we use. Thus, this ugly dance: */ @@ -9532,7 +9964,6 @@ static int wait_for_child_or_signal(struct pipe *waitfor_pipe, pid_t waitfor_pid if (!sigisemptyset(&G.pending_set)) { /* Crap! we raced with some signal! */ - // sig = 0; goto restore; } @@ -9567,13 +9998,10 @@ static int wait_for_child_or_signal(struct pipe *waitfor_pipe, pid_t waitfor_pid sigsuspend(&oldset); restore: sigprocmask(SIG_SETMASK, &oldset, NULL); - + check_sig: /* So, did we get a signal? */ - //if (sig > 0) - // raise(sig); /* run handler */ sig = check_and_run_traps(); if (sig /*&& sig != SIGCHLD - always true */) { - /* see note 2 */ ret = 128 + sig; break; } @@ -9586,7 +10014,6 @@ static int FAST_FUNC builtin_wait(char **argv) { int ret; int status; - struct pipe *wait_pipe = NULL; argv = skip_dash_dash(argv); if (argv[0] == NULL) { @@ -9614,11 +10041,20 @@ static int FAST_FUNC builtin_wait(char **argv) if (errno || pid <= 0) { #if ENABLE_HUSH_JOB if (argv[0][0] == '%') { + struct pipe *wait_pipe; + ret = 127; /* bash compat for bad jobspecs */ wait_pipe = parse_jobspec(*argv); if (wait_pipe) { - pid = - wait_pipe->pgrp; - goto do_wait; + ret = job_exited_or_stopped(wait_pipe); + if (ret < 0) { + ret = wait_for_child_or_signal(wait_pipe, 0); + } else { + /* waiting on "last dead job" removes it */ + clean_up_last_dead_job(); + } } + /* else: parse_jobspec() already emitted error msg */ + continue; } #endif /* mimic bash message */ @@ -9626,19 +10062,20 @@ static int FAST_FUNC builtin_wait(char **argv) ret = EXIT_FAILURE; continue; /* bash checks all argv[] */ } - IF_HUSH_JOB(do_wait:) + /* Do we have such child? */ ret = waitpid(pid, &status, WNOHANG); if (ret < 0) { /* No */ + ret = 127; if (errno == ECHILD) { - if (G.last_bg_pid > 0 && pid == G.last_bg_pid) { + if (pid == G.last_bg_pid) { /* "wait $!" but last bg task has already exited. Try: * (sleep 1; exit 3) & sleep 2; echo $?; wait $!; echo $? * In bash it prints exitcode 0, then 3. * In dash, it is 127. */ - /* ret = G.last_bg_pid_exitstatus - FIXME */ + ret = G.last_bg_pid_exitcode; } else { /* Example: "wait 1". mimic bash message */ bb_error_msg("wait: pid %d is not a child of this shell", (int)pid); @@ -9647,30 +10084,23 @@ static int FAST_FUNC builtin_wait(char **argv) /* ??? */ bb_perror_msg("wait %s", *argv); } - ret = 127; continue; /* bash checks all argv[] */ } if (ret == 0) { /* Yes, and it still runs */ - ret = wait_for_child_or_signal(wait_pipe, wait_pipe ? 0 : pid); + ret = wait_for_child_or_signal(NULL, pid); } else { /* Yes, and it just exited */ - process_wait_result(NULL, ret, status); + process_wait_result(NULL, pid, status); ret = WEXITSTATUS(status); if (WIFSIGNALED(status)) ret = 128 + WTERMSIG(status); -#if ENABLE_HUSH_JOB - if (wait_pipe) { - ret = job_exited_or_stopped(wait_pipe); - if (ret < 0) - goto do_wait; - } -#endif } } while (*++argv); return ret; } +#endif #if ENABLE_HUSH_LOOPS || ENABLE_HUSH_FUNCTIONS static unsigned parse_numeric_argv1(char **argv, unsigned def, unsigned def_min) @@ -9737,3 +10167,46 @@ static int FAST_FUNC builtin_return(char **argv) return rc; } #endif + +#if ENABLE_HUSH_MEMLEAK +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); + l = (unsigned long)p; + free(p); + p = malloc(3400); + if (l < (unsigned long)p) l = (unsigned long)p; + free(p); + + +# if 0 /* debug */ + { + struct mallinfo mi = mallinfo(); + printf("top alloc:0x%lx malloced:%d+%d=%d\n", l, + mi.arena, mi.hblkhd, mi.arena + mi.hblkhd); + } +# endif + + if (!G.memleak_value) + G.memleak_value = l; + + l -= G.memleak_value; + if ((long)l < 0) + l = 0; + l /= 1024; + if (l > 127) + l = 127; + + /* Exitcode is "how many kilobytes we leaked since 1st call" */ + return l; +} +#endif