X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;ds=inline;f=shell%2Fash.c;h=b7635a82332c3f108becf4a83b8f5dcb57dbaa54;hb=a107ef2a6ace98c51473dc3153564a44b260bc6f;hp=9a4448f187ac192ca0137aab2172acf814cb9052;hpb=f37e1155aabde6bd95d267a8aec347cedccb8bc3;p=oweals%2Fbusybox.git diff --git a/shell/ash.c b/shell/ash.c index 9a4448f18..b7635a823 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -15,76 +15,6 @@ * * Licensed under GPLv2 or later, see file LICENSE in this source tree. */ - -/* - * The following should be set to reflect the type of system you have: - * JOBS -> 1 if you have Berkeley job control, 0 otherwise. - * define SYSV if you are running under System V. - * define DEBUG=1 to compile in debugging ('set -o debug' to turn on) - * define DEBUG=2 to compile in and turn on debugging. - * - * When debugging is on (DEBUG is 1 and "set -o debug" was executed), - * debugging info will be written to ./trace and a quit signal - * will generate a core dump. - */ -#define DEBUG 0 -/* Tweak debug output verbosity here */ -#define DEBUG_TIME 0 -#define DEBUG_PID 1 -#define DEBUG_SIG 1 - -#define PROFILE 0 - -#define JOBS ENABLE_ASH_JOB_CONTROL - -#include -#include -#include -#include /* for setting $HOSTNAME */ - -#include "busybox.h" /* for applet_names */ - -#if defined(__ANDROID_API__) && __ANDROID_API__ <= 24 -/* Bionic at least up to version 24 has no glob() */ -# undef ENABLE_ASH_INTERNAL_GLOB -# define ENABLE_ASH_INTERNAL_GLOB 1 -#endif - -#if !ENABLE_ASH_INTERNAL_GLOB -# include -#endif - -#include "unicode.h" -#include "shell_common.h" -#if ENABLE_SH_MATH_SUPPORT -# include "math.h" -#endif -#if ENABLE_ASH_RANDOM_SUPPORT -# include "random.h" -#else -# define CLEAR_RANDOM_T(rnd) ((void)0) -#endif - -#include "NUM_APPLETS.h" -#if NUM_APPLETS == 1 -/* STANDALONE does not make sense, and won't compile */ -# undef CONFIG_FEATURE_SH_STANDALONE -# undef ENABLE_FEATURE_SH_STANDALONE -# undef IF_FEATURE_SH_STANDALONE -# undef IF_NOT_FEATURE_SH_STANDALONE -# define ENABLE_FEATURE_SH_STANDALONE 0 -# define IF_FEATURE_SH_STANDALONE(...) -# define IF_NOT_FEATURE_SH_STANDALONE(...) __VA_ARGS__ -#endif - -#ifndef PIPE_BUF -# define PIPE_BUF 4096 /* amount of buffering in a pipe */ -#endif - -#if !BB_MMU -# error "Do not even bother, ash will not run on NOMMU machine" -#endif - //config:config ASH //config: bool "ash" //config: default y @@ -96,26 +26,46 @@ //config: shell (by Herbert Xu), which was created by porting the 'ash' shell //config: (written by Kenneth Almquist) from NetBSD. //config: +//config:# ash options +//config:# note: Don't remove !NOMMU part in the next line; it would break +//config:# menuconfig's indenting. +//config:if !NOMMU && (ASH || SH_IS_ASH || BASH_IS_ASH) +//config: //config:config ASH_OPTIMIZE_FOR_SIZE //config: bool "Optimize for size instead of speed" //config: default y -//config: depends on ASH -//config: help -//config: Compile ash for reduced size at the price of speed. +//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH //config: //config:config ASH_INTERNAL_GLOB //config: bool "Use internal glob() implementation" -//config: default n -//config: depends on ASH +//config: default y # Y is bigger, but because of uclibc glob() bug, let Y be default for now +//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH //config: help //config: Do not use glob() function from libc, use internal implementation. //config: Use this if you are getting "glob.h: No such file or directory" //config: or similar build errors. +//config: Note that as of now (2017-01), uclibc and musl glob() both have bugs +//config: which would break ash if you select N here. +//config: +//config:config ASH_BASH_COMPAT +//config: bool "bash-compatible extensions" +//config: default y +//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH +//config: +//config:config ASH_JOB_CONTROL +//config: bool "Job control" +//config: default y +//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH +//config: +//config:config ASH_ALIAS +//config: bool "Alias support" +//config: default y +//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH //config: //config:config ASH_RANDOM_SUPPORT //config: bool "Pseudorandom generator and $RANDOM variable" //config: default y -//config: depends on ASH +//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH //config: help //config: Enable pseudorandom generator and dynamic variable "$RANDOM". //config: Each read of "$RANDOM" will generate a new pseudorandom value. @@ -126,99 +76,172 @@ //config:config ASH_EXPAND_PRMT //config: bool "Expand prompt string" //config: default y -//config: depends on ASH +//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH //config: help -//config: "PS#" may contain volatile content, such as backquote commands. +//config: $PS# may contain volatile content, such as backquote commands. //config: This option recreates the prompt string from the environment //config: variable each time it is displayed. //config: -//config:config ASH_BASH_COMPAT -//config: bool "bash-compatible extensions" -//config: default y -//config: depends on ASH -//config: help -//config: Enable bash-compatible extensions. -//config: //config:config ASH_IDLE_TIMEOUT -//config: bool "Idle timeout variable" -//config: default n -//config: depends on ASH -//config: help -//config: Enables bash-like auto-logout after $TMOUT seconds of idle time. -//config: -//config:config ASH_JOB_CONTROL -//config: bool "Job control" -//config: default y -//config: depends on ASH -//config: help -//config: Enable job control in the ash shell. -//config: -//config:config ASH_ALIAS -//config: bool "Alias support" +//config: bool "Idle timeout variable $TMOUT" //config: default y -//config: depends on ASH +//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH //config: help -//config: Enable alias support in the ash shell. +//config: Enable bash-like auto-logout after $TMOUT seconds of idle time. //config: -//config:config ASH_GETOPTS -//config: bool "Builtin getopt to parse positional parameters" +//config:config ASH_MAIL +//config: bool "Check for new mail in interactive shell" //config: default y -//config: depends on ASH +//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH //config: help -//config: Enable support for getopts builtin in ash. +//config: Enable "check for new mail" function: +//config: if set, $MAIL file and $MAILPATH list of files +//config: are checked for mtime changes, and "you have mail" +//config: message is printed if change is detected. //config: -//config:config ASH_BUILTIN_ECHO -//config: bool "Builtin version of 'echo'" +//config:config ASH_ECHO +//config: bool "echo builtin" //config: default y -//config: depends on ASH -//config: help -//config: Enable support for echo builtin in ash. +//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH //config: -//config:config ASH_BUILTIN_PRINTF -//config: bool "Builtin version of 'printf'" +//config:config ASH_PRINTF +//config: bool "printf builtin" //config: default y -//config: depends on ASH -//config: help -//config: Enable support for printf builtin in ash. +//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH //config: -//config:config ASH_BUILTIN_TEST -//config: bool "Builtin version of 'test'" +//config:config ASH_TEST +//config: bool "test builtin" //config: default y -//config: depends on ASH -//config: help -//config: Enable support for test builtin in ash. +//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH //config: //config:config ASH_HELP //config: bool "help builtin" //config: default y -//config: depends on ASH -//config: help -//config: Enable help builtin in ash. +//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH //config: -//config:config ASH_CMDCMD -//config: bool "'command' command to override shell builtins" +//config:config ASH_GETOPTS +//config: bool "getopts builtin" //config: default y -//config: depends on ASH -//config: help -//config: Enable support for the ash 'command' builtin, which allows -//config: you to run the specified command with the specified arguments, -//config: even when there is an ash builtin command with the same name. +//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH //config: -//config:config ASH_MAIL -//config: bool "Check for new mail on interactive shells" -//config: default n -//config: depends on ASH +//config:config ASH_CMDCMD +//config: bool "command builtin" +//config: default y +//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH //config: help -//config: Enable "check for new mail" function in the ash shell. +//config: Enable support for the 'command' builtin, which allows +//config: you to run the specified command or builtin, +//config: even when there is a function with the same name. //config: +//config:endif # ash options //applet:IF_ASH(APPLET(ash, BB_DIR_BIN, BB_SUID_DROP)) -//applet:IF_FEATURE_SH_IS_ASH(APPLET_ODDNAME(sh, ash, BB_DIR_BIN, BB_SUID_DROP, sh)) -//applet:IF_FEATURE_BASH_IS_ASH(APPLET_ODDNAME(bash, ash, BB_DIR_BIN, BB_SUID_DROP, bash)) +// APPLET_ODDNAME:name main location suid_type help +//applet:IF_SH_IS_ASH( APPLET_ODDNAME(sh, ash, BB_DIR_BIN, BB_SUID_DROP, ash)) +//applet:IF_BASH_IS_ASH(APPLET_ODDNAME(bash, ash, BB_DIR_BIN, BB_SUID_DROP, ash)) //kbuild:lib-$(CONFIG_ASH) += ash.o ash_ptr_hack.o shell_common.o +//kbuild:lib-$(CONFIG_SH_IS_ASH) += ash.o ash_ptr_hack.o shell_common.o +//kbuild:lib-$(CONFIG_BASH_IS_ASH) += ash.o ash_ptr_hack.o shell_common.o //kbuild:lib-$(CONFIG_ASH_RANDOM_SUPPORT) += random.o +/* + * DEBUG=1 to compile in debugging ('set -o debug' turns on) + * DEBUG=2 to compile in and turn on debugging. + * When debugging is on ("set -o debug" was executed, or DEBUG=2), + * debugging info is written to ./trace, quit signal generates core dump. + */ +#define DEBUG 0 +/* Tweak debug output verbosity here */ +#define DEBUG_TIME 0 +#define DEBUG_PID 1 +#define DEBUG_SIG 1 +#define DEBUG_INTONOFF 0 + +#define PROFILE 0 + +#define JOBS ENABLE_ASH_JOB_CONTROL + +#include +#include +#include +#include /* for setting $HOSTNAME */ +#include "busybox.h" /* for applet_names */ + +/* So far, all bash compat is controlled by one config option */ +/* Separate defines document which part of code implements what */ +/* function keyword */ +#define BASH_FUNCTION ENABLE_ASH_BASH_COMPAT +#define IF_BASH_FUNCTION IF_ASH_BASH_COMPAT +/* &>file */ +#define BASH_REDIR_OUTPUT ENABLE_ASH_BASH_COMPAT +#define IF_BASH_REDIR_OUTPUT IF_ASH_BASH_COMPAT +/* $'...' */ +#define BASH_DOLLAR_SQUOTE ENABLE_ASH_BASH_COMPAT +#define IF_BASH_DOLLAR_SQUOTE IF_ASH_BASH_COMPAT +#define BASH_PATTERN_SUBST ENABLE_ASH_BASH_COMPAT +#define IF_BASH_PATTERN_SUBST IF_ASH_BASH_COMPAT +#define BASH_SUBSTR ENABLE_ASH_BASH_COMPAT +#define IF_BASH_SUBSTR IF_ASH_BASH_COMPAT +/* [[ EXPR ]] */ +#define BASH_TEST2 (ENABLE_ASH_BASH_COMPAT * ENABLE_ASH_TEST) +#define BASH_SOURCE ENABLE_ASH_BASH_COMPAT +#define BASH_PIPEFAIL ENABLE_ASH_BASH_COMPAT +#define BASH_HOSTNAME_VAR ENABLE_ASH_BASH_COMPAT +#define BASH_SHLVL_VAR ENABLE_ASH_BASH_COMPAT + +#if defined(__ANDROID_API__) && __ANDROID_API__ <= 24 +/* Bionic at least up to version 24 has no glob() */ +# undef ENABLE_ASH_INTERNAL_GLOB +# define ENABLE_ASH_INTERNAL_GLOB 1 +#endif + +#if !ENABLE_ASH_INTERNAL_GLOB && defined(__UCLIBC__) +# error uClibc glob() is buggy, use ASH_INTERNAL_GLOB. +# error The bug is: for "$PWD"/ ash will escape e.g. dashes in "$PWD" +# error with backslash, even ones which do not need to be: "/a-b" -> "/a\-b" +# error glob() should unbackslash them and match. uClibc does not unbackslash, +# error fails to match dirname, subsequently not expanding in it. +// Testcase: +// if (glob("/etc/polkit\\-1", 0, NULL, &pglob)) - this returns 0 on uclibc, no bug +// if (glob("/etc/polkit\\-1/*", 0, NULL, &pglob)) printf("uclibc bug!\n"); +#endif + +#if !ENABLE_ASH_INTERNAL_GLOB +# include +#endif + +#include "unicode.h" +#include "shell_common.h" +#if ENABLE_FEATURE_SH_MATH +# include "math.h" +#endif +#if ENABLE_ASH_RANDOM_SUPPORT +# include "random.h" +#else +# define CLEAR_RANDOM_T(rnd) ((void)0) +#endif + +#include "NUM_APPLETS.h" +#if NUM_APPLETS == 1 +/* STANDALONE does not make sense, and won't compile */ +# undef CONFIG_FEATURE_SH_STANDALONE +# undef ENABLE_FEATURE_SH_STANDALONE +# undef IF_FEATURE_SH_STANDALONE +# undef IF_NOT_FEATURE_SH_STANDALONE +# define ENABLE_FEATURE_SH_STANDALONE 0 +# define IF_FEATURE_SH_STANDALONE(...) +# define IF_NOT_FEATURE_SH_STANDALONE(...) __VA_ARGS__ +#endif + +#ifndef PIPE_BUF +# define PIPE_BUF 4096 /* amount of buffering in a pipe */ +#endif + +#if !BB_MMU +# error "Do not even bother, ash will not run on NOMMU machine" +#endif + /* ============ Hash table sizes. Configurable. */ @@ -244,7 +267,7 @@ static const char *const optletters_optnames[] = { "b" "notify", "u" "nounset", "\0" "vi" -#if ENABLE_ASH_BASH_COMPAT +#if BASH_PIPEFAIL ,"\0" "pipefail" #endif #if DEBUG @@ -295,16 +318,13 @@ struct globals_misc { volatile int suppress_int; /* counter */ volatile /*sig_atomic_t*/ smallint pending_int; /* 1 = got SIGINT */ - /* last pending signal */ - volatile /*sig_atomic_t*/ smallint pending_sig; + volatile /*sig_atomic_t*/ smallint got_sigchld; /* 1 = got SIGCHLD */ + volatile /*sig_atomic_t*/ smallint pending_sig; /* last pending signal */ smallint exception_type; /* kind of exception (0..5) */ /* exceptions */ #define EXINT 0 /* SIGINT received */ #define EXERROR 1 /* a generic error */ -#define EXSHELLPROC 2 /* execute a shell procedure */ -#define EXEXEC 3 /* command execution failed */ #define EXEXIT 4 /* exit the shell */ -#define EXSIG 5 /* trapped signal in wait(1) */ smallint isloginsh; char nullstr[1]; /* zero length string */ @@ -324,14 +344,14 @@ struct globals_misc { #define bflag optlist[11] #define uflag optlist[12] #define viflag optlist[13] -#if ENABLE_ASH_BASH_COMPAT +#if BASH_PIPEFAIL # define pipefail optlist[14] #else # define pipefail 0 #endif #if DEBUG -# define nolog optlist[14 + ENABLE_ASH_BASH_COMPAT] -# define debug optlist[15 + ENABLE_ASH_BASH_COMPAT] +# define nolog optlist[14 + BASH_PIPEFAIL] +# define debug optlist[15 + BASH_PIPEFAIL] #endif /* trap handler commands */ @@ -373,6 +393,7 @@ extern struct globals_misc *const ash_ptr_to_globals_misc; #define exception_type (G_misc.exception_type ) #define suppress_int (G_misc.suppress_int ) #define pending_int (G_misc.pending_int ) +#define got_sigchld (G_misc.got_sigchld ) #define pending_sig (G_misc.pending_sig ) #define isloginsh (G_misc.isloginsh ) #define nullstr (G_misc.nullstr ) @@ -444,10 +465,18 @@ static void exitshell(void) NORETURN; * much more efficient and portable. (But hacking the kernel is so much * more fun than worrying about efficiency and portability. :-)) */ -#define INT_OFF do { \ +#if DEBUG_INTONOFF +# define INT_OFF do { \ + TRACE(("%s:%d INT_OFF(%d)\n", __func__, __LINE__, suppress_int)); \ + suppress_int++; \ + barrier(); \ +} while (0) +#else +# define INT_OFF do { \ suppress_int++; \ barrier(); \ } while (0) +#endif /* * Called to raise an exception. Since C doesn't include exceptions, we @@ -484,26 +513,20 @@ static void raise_interrupt(void) NORETURN; static void raise_interrupt(void) { - int ex_type; - pending_int = 0; /* Signal is not automatically unmasked after it is raised, * do it ourself - unmask all signals */ sigprocmask_allsigs(SIG_UNBLOCK); /* pending_sig = 0; - now done in signal_handler() */ - ex_type = EXSIG; - if (gotsig[SIGINT - 1] && !trap[SIGINT]) { - if (!(rootshell && iflag)) { - /* Kill ourself with SIGINT */ - signal(SIGINT, SIG_DFL); - raise(SIGINT); - } - ex_type = EXINT; + if (!(rootshell && iflag)) { + /* Kill ourself with SIGINT */ + signal(SIGINT, SIG_DFL); + raise(SIGINT); } /* bash: ^C even on empty command line sets $? */ exitstatus = SIGINT + 128; - raise_exception(ex_type); + raise_exception(EXINT); /* NOTREACHED */ } #if DEBUG @@ -521,7 +544,14 @@ int_on(void) raise_interrupt(); } } -#define INT_ON int_on() +#if DEBUG_INTONOFF +# define INT_ON do { \ + TRACE(("%s:%d INT_ON(%d)\n", __func__, __LINE__, suppress_int-1)); \ + int_on(); \ +} while (0) +#else +# define INT_ON int_on() +#endif static IF_ASH_OPTIMIZE_FOR_SIZE(inline) void force_int_on(void) { @@ -642,8 +672,10 @@ out2str(const char *p) #define VSTRIMLEFT 0x8 /* ${var#pattern} */ #define VSTRIMLEFTMAX 0x9 /* ${var##pattern} */ #define VSLENGTH 0xa /* ${#var} */ -#if ENABLE_ASH_BASH_COMPAT +#if BASH_SUBSTR #define VSSUBSTR 0xc /* ${var:position:length} */ +#endif +#if BASH_PATTERN_SUBST #define VSREPLACE 0xd /* ${var/pattern/replacement} */ #define VSREPLACEALL 0xe /* ${var//pattern/replacement} */ #endif @@ -670,7 +702,7 @@ static const char dolatstr[] ALIGN1 = { #define NDEFUN 14 #define NARG 15 #define NTO 16 -#if ENABLE_ASH_BASH_COMPAT +#if BASH_REDIR_OUTPUT #define NTO2 17 #endif #define NCLOBBER 18 @@ -852,13 +884,8 @@ trace_vprintf(const char *fmt, va_list va) { if (debug != 1) return; - if (DEBUG_TIME) - fprintf(tracefile, "%u ", (int) time(NULL)); - if (DEBUG_PID) - fprintf(tracefile, "[%u] ", (int) getpid()); - if (DEBUG_SIG) - fprintf(tracefile, "pending s:%d i:%d(supp:%d) ", pending_sig, pending_int, suppress_int); vfprintf(tracefile, fmt, va); + fprintf(tracefile, "\n"); } static void @@ -1085,7 +1112,7 @@ shcmd(union node *cmd, FILE *fp) case NTO: s = ">>"+1; dftfd = 1; break; case NCLOBBER: s = ">|"; dftfd = 1; break; case NAPPEND: s = ">>"; dftfd = 1; break; -#if ENABLE_ASH_BASH_COMPAT +#if BASH_REDIR_OUTPUT case NTO2: #endif case NTOFD: s = ">&"; dftfd = 1; break; @@ -1251,11 +1278,10 @@ ash_vmsg_and_raise(int cond, const char *msg, va_list ap) { #if DEBUG if (msg) { - TRACE(("ash_vmsg_and_raise(%d, \"", cond)); + TRACE(("ash_vmsg_and_raise(%d):", cond)); TRACEV((msg, ap)); - TRACE(("\") pid=%d\n", getpid())); } else - TRACE(("ash_vmsg_and_raise(%d, NULL) pid=%d\n", cond, getpid())); + TRACE(("ash_vmsg_and_raise(%d):NULL\n", cond)); if (msg) #endif ash_vmsg(msg, ap); @@ -1271,6 +1297,8 @@ ash_msg_and_raise_error(const char *msg, ...) { va_list ap; + exitstatus = 2; + va_start(ap, msg); ash_vmsg_and_raise(EXERROR, msg, ap); /* NOTREACHED */ @@ -1401,7 +1429,6 @@ struct globals_memstack { char *g_stacknxt; // = stackbase.space; char *sstrend; // = stackbase.space + MINSIZE; size_t g_stacknleft; // = MINSIZE; - int herefd; // = -1; struct stack_block stackbase; }; extern struct globals_memstack *const ash_ptr_to_globals_memstack; @@ -1410,7 +1437,6 @@ extern struct globals_memstack *const ash_ptr_to_globals_memstack; #define g_stacknxt (G_memstack.g_stacknxt ) #define sstrend (G_memstack.sstrend ) #define g_stacknleft (G_memstack.g_stacknleft) -#define herefd (G_memstack.herefd ) #define stackbase (G_memstack.stackbase ) #define INIT_G_memstack() do { \ (*(struct globals_memstack**)&ash_ptr_to_globals_memstack) = xzalloc(sizeof(G_memstack)); \ @@ -1419,7 +1445,6 @@ extern struct globals_memstack *const ash_ptr_to_globals_memstack; g_stacknxt = stackbase.space; \ g_stacknleft = MINSIZE; \ sstrend = stackbase.space + MINSIZE; \ - herefd = -1; \ } while (0) @@ -1496,12 +1521,10 @@ sstrdup(const char *p) return memcpy(stalloc(len), p, len); } -static void +static inline void grabstackblock(size_t len) { - len = SHELL_ALIGN(len); - g_stacknxt += len; - g_stacknleft -= len; + stalloc(len); } static void @@ -1607,10 +1630,6 @@ static void * growstackstr(void) { size_t len = stackblocksize(); - if (herefd >= 0 && len >= 1024) { - full_write(herefd, stackblock(), len); - return stackblock(); - } growstackblock(); return (char *)stackblock() + len; } @@ -1960,7 +1979,6 @@ struct redirtab; struct globals_var { struct shparam shellparam; /* $@ current positional parameters */ struct redirtab *redirlist; - int g_nullredirs; int preverrout_fd; /* save fd2 before print debug if xflag is set. */ struct var *vartab[VTABSIZE]; struct var varinit[ARRAY_SIZE(varinit_data)]; @@ -1969,7 +1987,6 @@ extern struct globals_var *const ash_ptr_to_globals_var; #define G_var (*ash_ptr_to_globals_var) #define shellparam (G_var.shellparam ) //#define redirlist (G_var.redirlist ) -#define g_nullredirs (G_var.g_nullredirs ) #define preverrout_fd (G_var.preverrout_fd) #define vartab (G_var.vartab ) #define varinit (G_var.varinit ) @@ -2030,7 +2047,7 @@ extern struct globals_var *const ash_ptr_to_globals_var; static void FAST_FUNC getoptsreset(const char *value) { - shellparam.optind = number(value); + shellparam.optind = number(value) ?: 1; shellparam.optoff = -1; } #endif @@ -2144,6 +2161,7 @@ lookupvar(const char *name) return NULL; } +#if ENABLE_UNICODE_SUPPORT static void reinit_unicode_for_ash(void) { @@ -2160,6 +2178,9 @@ reinit_unicode_for_ash(void) reinit_unicode(s); } } +#else +# define reinit_unicode_for_ash() ((void)0) +#endif /* * Search the environment of a builtin command. @@ -2198,6 +2219,7 @@ setvareq(char *s, int flags) if (flags & VNOSAVE) free(s); n = vp->var_text; + exitstatus = 1; ash_msg_and_raise_error("%.*s: is read only", strchrnul(n, '=') - n, n); } @@ -2269,32 +2291,6 @@ setvar0(const char *name, const char *val) setvar(name, val, 0); } -#if ENABLE_ASH_GETOPTS -/* - * Safe version of setvar, returns 1 on success 0 on failure. - */ -static int -setvarsafe(const char *name, const char *val, int flags) -{ - int err; - volatile int saveint; - struct jmploc *volatile savehandler = exception_handler; - struct jmploc jmploc; - - SAVE_INT(saveint); - if (setjmp(jmploc.loc)) - err = 1; - else { - exception_handler = &jmploc; - setvar(name, val, flags); - err = 0; - } - exception_handler = savehandler; - RESTORE_INT(saveint); - return err; -} -#endif - /* * Unset the specified variable. */ @@ -2462,12 +2458,8 @@ putprompt(const char *s) } #endif -#if ENABLE_ASH_EXPAND_PRMT /* expandstr() needs parsing machinery, so it is far away ahead... */ static const char *expandstr(const char *ps); -#else -#define expandstr(s) s -#endif static void setprompt_if(smallint do_set, int whichprompt) @@ -2492,10 +2484,10 @@ setprompt_if(smallint do_set, int whichprompt) } #if ENABLE_ASH_EXPAND_PRMT pushstackmark(&smark, stackblocksize()); -#endif putprompt(expandstr(prompt)); -#if ENABLE_ASH_EXPAND_PRMT popstackmark(&smark); +#else + putprompt(prompt); #endif } @@ -2629,7 +2621,7 @@ setpwd(const char *val, int setold) static void hashcd(void); /* - * Actually do the chdir. We also call hashcd to let the routines in exec.c + * Actually do the chdir. We also call hashcd to let other routines * know that the current directory has changed. */ static int @@ -2677,7 +2669,7 @@ cdcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) if (!dest) dest = nullstr; if (*dest == '/') - goto step7; + goto step6; if (*dest == '.') { c = dest[1]; dotdot: @@ -2694,13 +2686,7 @@ cdcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) if (!*dest) dest = "."; path = bltinlookup("CDPATH"); - if (!path) { - step6: - step7: - p = dest; - goto docd; - } - do { + while (path) { c = *path; p = path_advance(&path, dest); if (stat(p, &statb) >= 0 && S_ISDIR(statb.st_mode)) { @@ -2709,9 +2695,15 @@ cdcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) docd: if (!docd(p, flags)) goto out; - break; + goto err; } - } while (path); + } + + step6: + p = dest; + goto docd; + + err: ash_msg_and_raise_error("can't cd to %s", dest); /* NOTREACHED */ out: @@ -2766,7 +2758,7 @@ pwdcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) #define USE_SIT_FUNCTION ENABLE_ASH_OPTIMIZE_FOR_SIZE -#if ENABLE_SH_MATH_SUPPORT +#if ENABLE_FEATURE_SH_MATH # define SIT_ITEM(a,b,c,d) (a | (b << 4) | (c << 8) | (d << 12)) #else # define SIT_ITEM(a,b,c,d) (a | (b << 4) | (c << 8)) @@ -3147,7 +3139,18 @@ static const uint8_t syntax_index_table[] ALIGN1 = { # endif }; +#if 1 # define SIT(c, syntax) ((S_I_T[syntax_index_table[c]] >> ((syntax)*4)) & 0xf) +#else /* debug version, caught one signed char bug */ +# define SIT(c, syntax) \ + ({ \ + if ((c) < 0 || (c) > (PEOF + ENABLE_ASH_ALIAS)) \ + bb_error_msg_and_die("line:%d c:%d", __LINE__, (c)); \ + if ((syntax) < 0 || (syntax) > (2 + ENABLE_FEATURE_SH_MATH)) \ + bb_error_msg_and_die("line:%d c:%d", __LINE__, (c)); \ + ((S_I_T[syntax_index_table[c]] >> ((syntax)*4)) & 0xf); \ + }) +#endif #endif /* !USE_SIT_FUNCTION */ @@ -3338,11 +3341,9 @@ unaliascmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) { int i; - while ((i = nextopt("a")) != '\0') { - if (i == 'a') { - rmaliases(); - return 0; - } + while (nextopt("a") != '\0') { + rmaliases(); + return 0; } for (i = 0; *argptr; argptr++) { if (unalias(*argptr)) { @@ -3386,12 +3387,13 @@ struct job { #if JOBS int stopstatus; /* status of a stopped job */ #endif - uint32_t - nprocs: 16, /* number of processes */ - state: 8, + unsigned nprocs; /* number of processes */ + #define JOBRUNNING 0 /* at least one proc running */ #define JOBSTOPPED 1 /* all procs are stopped */ #define JOBDONE 2 /* all procs are completed */ + unsigned + state: 8, #if JOBS sigint: 1, /* job was killed by SIGINT */ jobctl: 1, /* job running under job control */ @@ -3434,7 +3436,14 @@ ignoresig(int signo) static void signal_handler(int signo) { + if (signo == SIGCHLD) { + got_sigchld = 1; + if (!trap[SIGCHLD]) + return; + } + gotsig[signo - 1] = 1; + pending_sig = signo; if (signo == SIGINT && !trap[SIGINT]) { if (!suppress_int) { @@ -3442,8 +3451,6 @@ signal_handler(int signo) raise_interrupt(); /* does not return */ } pending_int = 1; - } else { - pending_sig = signo; } } @@ -3501,6 +3508,9 @@ setsignal(int signo) //whereas we have to restore it to what shell got on entry //from the parent. See comment above + if (signo == SIGCHLD) + new_act = S_CATCH; + t = &sigmode[signo - 1]; cur_act = *t; if (cur_act == 0) { @@ -3550,10 +3560,6 @@ setsignal(int signo) #define CUR_RUNNING 1 #define CUR_STOPPED 0 -/* mode flags for dowait */ -#define DOWAIT_NONBLOCK WNOHANG -#define DOWAIT_BLOCK 0 - #if JOBS /* pgrp of shell on invocation */ static int initialpgrp; //references:2 @@ -3568,25 +3574,91 @@ static struct job *curjob; //lots /* number of presumed living untracked jobs */ static int jobless; //4 +#if 0 +/* Bash has a feature: it restores termios after a successful wait for + * a foreground job which had at least one stopped or sigkilled member. + * The probable rationale is that SIGSTOP and SIGKILL can preclude task from + * properly restoring tty state. Should we do this too? + * A reproducer: ^Z an interactive python: + * + * # python + * Python 2.7.12 (...) + * >>> ^Z + * { python leaves tty in -icanon -echo state. We do survive that... } + * [1]+ Stopped python + * { ...however, next program (python #2) does not survive it well: } + * # python + * Python 2.7.12 (...) + * >>> Traceback (most recent call last): + * { above, I typed "qwerty", but -echo state is still in effect } + * File "", line 1, in + * NameError: name 'qwerty' is not defined + * + * The implementation below is modeled on bash code and seems to work. + * However, I'm not sure we should do this. For one: what if I'd fg + * the stopped python instead? It'll be confused by "restored" tty state. + */ +static struct termios shell_tty_info; static void -set_curjob(struct job *jp, unsigned mode) +get_tty_state(void) { - struct job *jp1; - struct job **jpp, **curp; - - /* first remove from list */ - jpp = curp = &curjob; - while (1) { - jp1 = *jpp; - if (jp1 == jp) - break; - jpp = &jp1->prev_job; - } - *jpp = jp1->prev_job; - - /* Then re-insert in correct position */ - jpp = curp; - switch (mode) { + if (rootshell && ttyfd >= 0) + tcgetattr(ttyfd, &shell_tty_info); +} +static void +set_tty_state(void) +{ + /* if (rootshell) - caller ensures this */ + if (ttyfd >= 0) + tcsetattr(ttyfd, TCSADRAIN, &shell_tty_info); +} +static int +job_signal_status(struct job *jp) +{ + int status; + unsigned i; + struct procstat *ps = jp->ps; + for (i = 0; i < jp->nprocs; i++) { + status = ps[i].ps_status; + if (WIFSIGNALED(status) || WIFSTOPPED(status)) + return status; + } + return 0; +} +static void +restore_tty_if_stopped_or_signaled(struct job *jp) +{ +//TODO: check what happens if we come from waitforjob() in expbackq() + if (rootshell) { + int s = job_signal_status(jp); + if (s) /* WIFSIGNALED(s) || WIFSTOPPED(s) */ + set_tty_state(); + } +} +#else +# define get_tty_state() ((void)0) +# define restore_tty_if_stopped_or_signaled(jp) ((void)0) +#endif + +static void +set_curjob(struct job *jp, unsigned mode) +{ + struct job *jp1; + struct job **jpp, **curp; + + /* first remove from list */ + jpp = curp = &curjob; + while (1) { + jp1 = *jpp; + if (jp1 == jp) + break; + jpp = &jp1->prev_job; + } + *jpp = jp1->prev_job; + + /* Then re-insert in correct position */ + jpp = curp; + switch (mode) { default: #if DEBUG abort(); @@ -3771,12 +3843,12 @@ setjobctl(int on) if (--fd < 0) goto out; } + /* fd is a tty at this point */ fd = fcntl(fd, F_DUPFD, 10); - if (ofd >= 0) + if (ofd >= 0) /* if it is "/dev/tty", close. If 0/1/2, don't */ close(ofd); if (fd < 0) - goto out; - /* fd is a tty at this point */ + goto out; /* F_DUPFD failed */ close_on_exec_on(fd); while (1) { /* while we are in the background */ pgrp = tcgetpgrp(fd); @@ -3899,8 +3971,10 @@ restartjob(struct job *jp, int mode) goto out; jp->state = JOBRUNNING; pgid = jp->ps[0].ps_pid; - if (mode == FORK_FG) + if (mode == FORK_FG) { + get_tty_state(); xtcsetpgrp(ttyfd, pgid); + } killpg(pgid, SIGCONT); ps = jp->ps; i = jp->nprocs; @@ -3974,27 +4048,88 @@ sprint_status48(char *s, int status, int sigonly) } static int -dowait(int wait_flags, struct job *job) +wait_block_or_sig(int *status) +{ + int pid; + + do { + sigset_t mask; + + /* Poll all children for changes in their state */ + got_sigchld = 0; + /* if job control is active, accept stopped processes too */ + pid = waitpid(-1, status, doing_jobctl ? (WNOHANG|WUNTRACED) : WNOHANG); + if (pid != 0) + break; /* Error (e.g. EINTR, ECHILD) or pid */ + + /* Children exist, but none are ready. Sleep until interesting signal */ +#if 1 + sigfillset(&mask); + sigprocmask(SIG_SETMASK, &mask, &mask); + while (!got_sigchld && !pending_sig) + sigsuspend(&mask); + sigprocmask(SIG_SETMASK, &mask, NULL); +#else /* unsafe: a signal can set pending_sig after check, but before pause() */ + while (!got_sigchld && !pending_sig) + pause(); +#endif + + /* If it was SIGCHLD, poll children again */ + } while (got_sigchld); + + return pid; +} + +#define DOWAIT_NONBLOCK 0 +#define DOWAIT_BLOCK 1 +#define DOWAIT_BLOCK_OR_SIG 2 + +static int +dowait(int block, struct job *job) { int pid; int status; struct job *jp; - struct job *thisjob; + struct job *thisjob = NULL; - TRACE(("dowait(0x%x) called\n", wait_flags)); + TRACE(("dowait(0x%x) called\n", block)); - /* Do a wait system call. If job control is compiled in, we accept - * stopped processes. wait_flags may have WNOHANG, preventing blocking. - * NB: _not_ safe_waitpid, we need to detect EINTR */ - if (doing_jobctl) - wait_flags |= WUNTRACED; - pid = waitpid(-1, &status, wait_flags); + /* It's wrong to call waitpid() outside of INT_OFF region: + * signal can arrive just after syscall return and handler can + * longjmp away, losing stop/exit notification processing. + * Thus, for "jobs" builtin, and for waiting for a fg job, + * we call waitpid() (blocking or non-blocking) inside INT_OFF. + * + * However, for "wait" builtin it is wrong to simply call waitpid() + * in INT_OFF region: "wait" needs to wait for any running job + * to change state, but should exit on any trap too. + * In INT_OFF region, a signal just before syscall entry can set + * pending_sig variables, but we can't check them, and we would + * either enter a sleeping waitpid() (BUG), or need to busy-loop. + * + * Because of this, we run inside INT_OFF, but use a special routine + * which combines waitpid() and sigsuspend(). + * This is the reason why we need to have a handler for SIGCHLD: + * SIG_DFL handler does not wake sigsuspend(). + */ + INT_OFF; + if (block == DOWAIT_BLOCK_OR_SIG) { + pid = wait_block_or_sig(&status); + } else { + int wait_flags = 0; + if (block == DOWAIT_NONBLOCK) + wait_flags = WNOHANG; + /* if job control is active, accept stopped processes too */ + if (doing_jobctl) + wait_flags |= WUNTRACED; + /* NB: _not_ safe_waitpid, we need to detect EINTR */ + pid = waitpid(-1, &status, wait_flags); + } TRACE(("wait returns pid=%d, status=0x%x, errno=%d(%s)\n", pid, status, errno, strerror(errno))); if (pid <= 0) - return pid; + goto out; - INT_OFF; thisjob = NULL; for (jp = curjob; jp; jp = jp->prev_job) { int jobstate; @@ -4066,15 +4201,6 @@ dowait(int wait_flags, struct job *job) return pid; } -static int -blocking_wait_with_raise_on_sig(void) -{ - pid_t pid = dowait(DOWAIT_BLOCK, NULL); - if (pid <= 0 && pending_sig) - raise_exception(EXSIG); - return pid; -} - #if JOBS static void showjob(struct job *jp, int mode) @@ -4244,9 +4370,6 @@ waitcmd(int argc UNUSED_PARAM, char **argv) int retval; struct job *jp; - if (pending_sig) - raise_exception(EXSIG); - nextopt(nullstr); retval = 0; @@ -4263,21 +4386,20 @@ waitcmd(int argc UNUSED_PARAM, char **argv) jp->waited = 1; jp = jp->prev_job; } - blocking_wait_with_raise_on_sig(); /* man bash: * "When bash is waiting for an asynchronous command via * the wait builtin, the reception of a signal for which a trap * has been set will cause the wait builtin to return immediately * with an exit status greater than 128, immediately after which * the trap is executed." - * - * blocking_wait_with_raise_on_sig raises signal handlers - * if it gets no pid (pid < 0). However, - * if child sends us a signal *and immediately exits*, - * blocking_wait_with_raise_on_sig gets pid > 0 - * and does not handle pending_sig. Check this case: */ + */ + dowait(DOWAIT_BLOCK_OR_SIG, NULL); + /* if child sends us a signal *and immediately exits*, + * dowait() returns pid > 0. Check this case, + * not "if (dowait() < 0)"! + */ if (pending_sig) - raise_exception(EXSIG); + goto sigout; } } @@ -4297,8 +4419,11 @@ waitcmd(int argc UNUSED_PARAM, char **argv) job = getjob(*argv, 0); } /* loop until process terminated or stopped */ - while (job->state == JOBRUNNING) - blocking_wait_with_raise_on_sig(); + while (job->state == JOBRUNNING) { + dowait(DOWAIT_BLOCK_OR_SIG, NULL); + if (pending_sig) + goto sigout; + } job->waited = 1; retval = getstatus(job); repeat: ; @@ -4306,6 +4431,9 @@ waitcmd(int argc UNUSED_PARAM, char **argv) ret: return retval; + sigout: + retval = 128 + pending_sig; + return retval; } static struct job * @@ -4380,7 +4508,7 @@ makejob(/*union node *node,*/ int nprocs) memset(jp, 0, sizeof(*jp)); #if JOBS /* jp->jobctl is a bitfield. - * "jp->jobctl |= jobctl" likely to give awful code */ + * "jp->jobctl |= doing_jobctl" likely to give awful code */ if (doing_jobctl) jp->jobctl = 1; #endif @@ -4409,7 +4537,8 @@ cmdputs(const char *s) static const char vstype[VSTYPE + 1][3] = { "", "}", "-", "+", "?", "=", "%", "%%", "#", "##" - IF_ASH_BASH_COMPAT(, ":", "/", "//") + IF_BASH_SUBSTR(, ":") + IF_BASH_PATTERN_SUBST(, "/", "//") }; const char *p, *str; @@ -4443,7 +4572,7 @@ cmdputs(const char *s) case CTLBACKQ: str = "$(...)"; goto dostr; -#if ENABLE_SH_MATH_SUPPORT +#if ENABLE_FEATURE_SH_MATH case CTLARI: str = "$(("; goto dostr; @@ -4636,7 +4765,7 @@ cmdtxt(union node *n) case NAPPEND: p = ">>"; goto redir; -#if ENABLE_ASH_BASH_COMPAT +#if BASH_REDIR_OUTPUT case NTO2: #endif case NTOFD: @@ -4670,8 +4799,7 @@ commandtext(union node *n) STARTSTACKSTR(cmdnextc); cmdtxt(n); name = stackblock(); - TRACE(("commandtext: name %p, end %p\n\t\"%s\"\n", - name, cmdnextc, cmdnextc)); + TRACE(("commandtext: name %p, end %p\n", name, cmdnextc)); return ckstrdup(name); } #endif /* JOBS */ @@ -4700,25 +4828,26 @@ clear_traps(void) { char **tp; + INT_OFF; for (tp = trap; tp < &trap[NSIG]; tp++) { if (*tp && **tp) { /* trap not NULL or "" (SIG_IGN) */ - INT_OFF; if (trap_ptr == trap) free(*tp); /* else: it "belongs" to trap_ptr vector, don't free */ *tp = NULL; if ((tp - trap) != 0) setsignal(tp - trap); - INT_ON; } } may_have_traps = 0; + INT_ON; } /* Lives far away from here, needed for forkchild */ static void closescript(void); /* Called after fork(), in child */ +/* jp and n are NULL when called by openhere() for heredoc support */ static NOINLINE void forkchild(struct job *jp, union node *n, int mode) { @@ -4850,6 +4979,7 @@ forkparent(struct job *jp, union node *n, int mode, pid_t pid) { TRACE(("In parent shell: child = %d\n", pid)); if (!jp) { + /* jp is NULL when called by openhere() for heredoc support */ while (jobless && dowait(DOWAIT_NONBLOCK, NULL) > 0) continue; jobless++; @@ -4883,6 +5013,7 @@ forkparent(struct job *jp, union node *n, int mode, pid_t pid) } } +/* jp and n are NULL when called by openhere() for heredoc support */ static int forkshell(struct job *jp, union node *n, int mode) { @@ -4972,6 +5103,8 @@ waitforjob(struct job *jp) #if JOBS if (jp->jobctl) { xtcsetpgrp(ttyfd, rootpid); + restore_tty_if_stopped_or_signaled(jp); + /* * This is truly gross. * If we're doing job control, then we did a TIOCSPGRP which @@ -5012,8 +5145,7 @@ stoppedjobs(void) } -/* ============ redir.c - * +/* * Code for dealing with input/output redirection. */ @@ -5162,7 +5294,7 @@ openredirect(union node *redir) goto ecreate; break; case NTO: -#if ENABLE_ASH_BASH_COMPAT +#if BASH_REDIR_OUTPUT case NTO2: #endif /* Take care of noclobber mode. */ @@ -5193,31 +5325,32 @@ openredirect(union node *redir) } /* - * Copy a file descriptor to be >= to. Returns -1 - * if the source file descriptor is closed, EMPTY if there are no unused - * file descriptors left. + * Copy a file descriptor to be >= 10. Throws exception on error. */ -/* 0x800..00: bit to set in "to" to request dup2 instead of fcntl(F_DUPFD). - * old code was doing close(to) prior to copyfd() to achieve the same */ -enum { - COPYFD_EXACT = (int)~(INT_MAX), - COPYFD_RESTORE = (int)((unsigned)COPYFD_EXACT >> 1), -}; static int -copyfd(int from, int to) +savefd(int from) { int newfd; + int err; - if (to & COPYFD_EXACT) { - to &= ~COPYFD_EXACT; - /*if (from != to)*/ - newfd = dup2(from, to); - } else { - newfd = fcntl(from, F_DUPFD, to); + newfd = fcntl(from, F_DUPFD, 10); + err = newfd < 0 ? errno : 0; + if (err != EBADF) { + if (err) + ash_msg_and_raise_error("%d: %m", from); + close(from); + fcntl(newfd, F_SETFD, FD_CLOEXEC); } + + return newfd; +} +static int +dup2_or_raise(int from, int to) +{ + int newfd; + + newfd = (from != to) ? dup2(from, to) : to; if (newfd < 0) { - if (errno == EMFILE) - return EMPTY; /* Happens when source fd is not open: try "echo >&99" */ ash_msg_and_raise_error("%d: %m", from); } @@ -5230,11 +5363,13 @@ struct two_fd_t { }; struct redirtab { struct redirtab *next; - int nullredirs; int pair_count; struct two_fd_t two_fd[]; }; #define redirlist (G_var.redirlist) +enum { + COPYFD_RESTORE = (int)~(INT_MAX), +}; static int need_to_remember(struct redirtab *rp, int fd) @@ -5309,7 +5444,6 @@ redirect(union node *redir, int flags) int newfd; int copied_fd2 = -1; - g_nullredirs++; if (!redir) { return; } @@ -5321,7 +5455,7 @@ redirect(union node *redir, int flags) union node *tmp = redir; do { sv_pos++; -#if ENABLE_ASH_BASH_COMPAT +#if BASH_REDIR_OUTPUT if (tmp->nfile.type == NTO2) sv_pos++; #endif @@ -5331,8 +5465,6 @@ redirect(union node *redir, int flags) sv->next = redirlist; sv->pair_count = sv_pos; redirlist = sv; - sv->nullredirs = g_nullredirs - 1; - g_nullredirs = 0; while (sv_pos > 0) { sv_pos--; sv->two_fd[sv_pos].orig = sv->two_fd[sv_pos].copy = EMPTY; @@ -5365,7 +5497,7 @@ redirect(union node *redir, int flags) continue; } } -#if ENABLE_ASH_BASH_COMPAT +#if BASH_REDIR_OUTPUT redirect_more: #endif if (need_to_remember(sv, fd)) { @@ -5373,11 +5505,11 @@ redirect(union node *redir, int flags) /* Careful to not accidentally "save" * to the same fd as right side fd in N>&M */ int minfd = right_fd < 10 ? 10 : right_fd + 1; +#if defined(F_DUPFD_CLOEXEC) + i = fcntl(fd, F_DUPFD_CLOEXEC, minfd); +#else i = fcntl(fd, F_DUPFD, minfd); -/* You'd expect copy to be CLOEXECed. Currently these extra "saved" fds - * are closed in popredir() in the child, preventing them from leaking - * into child. (popredir() also cleans up the mess in case of failures) - */ +#endif if (i == -1) { i = errno; if (i != EBADF) { @@ -5392,6 +5524,9 @@ redirect(union node *redir, int flags) remember_to_close: i = CLOSED; } else { /* fd is open, save its copy */ +#if !defined(F_DUPFD_CLOEXEC) + fcntl(i, F_SETFD, FD_CLOEXEC); +#endif /* "exec fd>&-" should not close fds * which point to script file(s). * Force them to be restored afterwards */ @@ -5411,16 +5546,16 @@ redirect(union node *redir, int flags) if (fd != -1) close(fd); } else { - copyfd(redir->ndup.dupfd, fd | COPYFD_EXACT); + dup2_or_raise(redir->ndup.dupfd, fd); } } else if (fd != newfd) { /* move newfd to fd */ - copyfd(newfd, fd | COPYFD_EXACT); -#if ENABLE_ASH_BASH_COMPAT + dup2_or_raise(newfd, fd); +#if BASH_REDIR_OUTPUT if (!(redir->nfile.type == NTO2 && fd == 2)) #endif close(newfd); } -#if ENABLE_ASH_BASH_COMPAT +#if BASH_REDIR_OUTPUT if (redir->nfile.type == NTO2 && fd == 1) { /* We already redirected it to fd 1, now copy it to 2 */ newfd = 1; @@ -5444,7 +5579,7 @@ popredir(int drop, int restore) struct redirtab *rp; int i; - if (--g_nullredirs >= 0 || redirlist == NULL) + if (redirlist == NULL) return; INT_OFF; rp = redirlist; @@ -5460,13 +5595,12 @@ popredir(int drop, int restore) if (!drop || (restore && (copy & COPYFD_RESTORE))) { copy &= ~COPYFD_RESTORE; /*close(fd);*/ - copyfd(copy, fd | COPYFD_EXACT); + dup2_or_raise(copy, fd); } close(copy & ~COPYFD_RESTORE); } } redirlist = rp->next; - g_nullredirs = rp->nullredirs; free(rp); INT_ON; } @@ -5475,20 +5609,6 @@ popredir(int drop, int restore) * Undo all redirections. Called on error or interrupt. */ -/* - * Discard all saved file descriptors. - */ -static void -clearredir(int drop) -{ - for (;;) { - g_nullredirs = 0; - if (!redirlist) - break; - popredir(drop, /*restore:*/ 0); - } -} - static int redirectsafe(union node *redir, int flags) { @@ -5517,7 +5637,7 @@ redirectsafe(union node *redir, int flags) * We have to deal with backquotes, shell variables, and file metacharacters. */ -#if ENABLE_SH_MATH_SUPPORT +#if ENABLE_FEATURE_SH_MATH static arith_t ash_arith(const char *s) { @@ -5545,6 +5665,17 @@ ash_arith(const char *s) #define EXP_TILDE 0x2 /* do normal tilde expansion */ #define EXP_VARTILDE 0x4 /* expand tildes in an assignment */ #define EXP_REDIR 0x8 /* file glob for a redirection (1 match only) */ +/* ^^^^^^^^^^^^^^ this is meant to support constructs such as "cmd >file*.txt" + * POSIX says for this case: + * Pathname expansion shall not be performed on the word by a + * non-interactive shell; an interactive shell may perform it, but shall + * do so only when the expansion would result in one word. + * Currently, our code complies to the above rule by never globbing + * redirection filenames. + * Bash performs globbing, unless it is non-interactive and in POSIX mode. + * (this means that on a typical Linux distro, bash almost always + * performs globbing, and thus diverges from what we do). + */ #define EXP_CASE 0x10 /* keeps quotes around for CASE pattern */ #define EXP_QPAT 0x20 /* pattern in quoted parameter expansion */ #define EXP_VARTILDE2 0x40 /* expand tildes after colons only */ @@ -5594,7 +5725,7 @@ static struct arglist exparg; /* * Our own itoa(). */ -#if !ENABLE_SH_MATH_SUPPORT +#if !ENABLE_FEATURE_SH_MATH /* cvtnum() is used even if math support is off (to prepare $? values and such) */ typedef long arith_t; # define ARITH_FMT "%ld" @@ -5610,6 +5741,119 @@ cvtnum(arith_t num) return len; } +/* + * Break the argument string into pieces based upon IFS and add the + * strings to the argument list. The regions of the string to be + * searched for IFS characters have been stored by recordregion. + */ +static void +ifsbreakup(char *string, struct arglist *arglist) +{ + struct ifsregion *ifsp; + struct strlist *sp; + char *start; + char *p; + char *q; + const char *ifs, *realifs; + int ifsspc; + int nulonly; + + start = string; + if (ifslastp != NULL) { + ifsspc = 0; + nulonly = 0; + realifs = ifsset() ? ifsval() : defifs; + ifsp = &ifsfirst; + do { + p = string + ifsp->begoff; + nulonly = ifsp->nulonly; + ifs = nulonly ? nullstr : realifs; + ifsspc = 0; + while (p < string + ifsp->endoff) { + q = p; + if ((unsigned char)*p == CTLESC) + p++; + if (!strchr(ifs, *p)) { + p++; + continue; + } + if (!nulonly) + ifsspc = (strchr(defifs, *p) != NULL); + /* Ignore IFS whitespace at start */ + if (q == start && ifsspc) { + p++; + start = p; + continue; + } + *q = '\0'; + sp = stzalloc(sizeof(*sp)); + sp->text = start; + *arglist->lastp = sp; + arglist->lastp = &sp->next; + p++; + if (!nulonly) { + for (;;) { + if (p >= string + ifsp->endoff) { + break; + } + q = p; + if ((unsigned char)*p == CTLESC) + p++; + if (strchr(ifs, *p) == NULL) { + p = q; + break; + } + if (strchr(defifs, *p) == NULL) { + if (ifsspc) { + p++; + ifsspc = 0; + } else { + p = q; + break; + } + } else + p++; + } + } + start = p; + } /* while */ + ifsp = ifsp->next; + } while (ifsp != NULL); + if (nulonly) + goto add; + } + + if (!*start) + return; + + add: + sp = stzalloc(sizeof(*sp)); + sp->text = start; + *arglist->lastp = sp; + arglist->lastp = &sp->next; +} + +static void +ifsfree(void) +{ + struct ifsregion *p = ifsfirst.next; + + if (!p) + goto out; + + INT_OFF; + do { + struct ifsregion *ifsp; + ifsp = p->next; + free(p); + p = ifsp; + } while (p); + ifsfirst.next = NULL; + INT_ON; + out: + ifslastp = NULL; +} + static size_t esclen(const char *start, const char *p) { @@ -5628,15 +5872,15 @@ static char * rmescapes(char *str, int flag) { static const char qchars[] ALIGN1 = { - IF_ASH_BASH_COMPAT('/',) CTLESC, CTLQUOTEMARK, '\0' }; + IF_BASH_PATTERN_SUBST('/',) CTLESC, CTLQUOTEMARK, '\0' }; char *p, *q, *r; unsigned inquotes; unsigned protect_against_glob; unsigned globbing; - IF_ASH_BASH_COMPAT(unsigned slash = flag & RMESCAPE_SLASH;) + IF_BASH_PATTERN_SUBST(unsigned slash = flag & RMESCAPE_SLASH;) - p = strpbrk(str, qchars IF_ASH_BASH_COMPAT(+ !slash)); + p = strpbrk(str, qchars IF_BASH_PATTERN_SUBST(+ !slash)); if (!p) return str; @@ -5669,6 +5913,7 @@ rmescapes(char *str, int flag) while (*p) { if ((unsigned char)*p == CTLQUOTEMARK) { // Note: both inquotes and protect_against_glob only affect whether +// CTLESC, gets converted to or to \ inquotes = ~inquotes; p++; protect_against_glob = globbing; @@ -5681,14 +5926,40 @@ rmescapes(char *str, int flag) ash_msg_and_raise_error("CTLESC at EOL (shouldn't happen)"); #endif if (protect_against_glob) { - *q++ = '\\'; + /* + * We used to trust glob() and fnmatch() to eat + * superfluous escapes (\z where z has no + * special meaning anyway). But this causes + * bugs such as string of one greek letter rho + * (unicode-encoded as two bytes "cf,81") + * getting encoded as "cf,CTLESC,81" + * and here, converted to "cf,\,81" - + * which does not go well with some flavors + * of fnmatch() in unicode locales + * (for example, glibc <= 2.22). + * + * Lets add "\" only on the chars which need it. + * Testcases for less obvious chars are shown. + */ + if (*p == '*' + || *p == '?' + || *p == '[' + || *p == '\\' /* case '\' in \\ ) echo ok;; *) echo WRONG;; esac */ + || *p == ']' /* case ']' in [a\]] ) echo ok;; *) echo WRONG;; esac */ + || *p == '-' /* case '-' in [a\-c]) echo ok;; *) echo WRONG;; esac */ + || *p == '!' /* case '!' in [\!] ) echo ok;; *) echo WRONG;; esac */ + /* Some libc support [^negate], that's why "^" also needs love */ + || *p == '^' /* case '^' in [\^] ) echo ok;; *) echo WRONG;; esac */ + ) { + *q++ = '\\'; + } } } else if (*p == '\\' && !inquotes) { /* naked back slash */ protect_against_glob = 0; goto copy; } -#if ENABLE_ASH_BASH_COMPAT +#if BASH_PATTERN_SUBST else if (*p == '/' && slash) { /* stop handling globbing and mark location of slash */ globbing = slash = 0; @@ -5735,14 +6006,15 @@ memtodest(const char *p, size_t len, int syntax, int quotes) do { unsigned char c = *p++; if (c) { - int n = SIT(c, syntax); - if ((quotes & QUOTES_ESC) - && ((n == CCTL) - || (((quotes & EXP_FULL) || syntax != BASESYNTAX) - && n == CBACK) - ) - ) { - USTPUTC(CTLESC, q); + if (quotes & QUOTES_ESC) { + int n = SIT(c, syntax); + if (n == CCTL + || (((quotes & EXP_FULL) || syntax != BASESYNTAX) + && n == CBACK + ) + ) { + USTPUTC(CTLESC, q); + } } } else if (!(quotes & QUOTES_KEEPNUL)) continue; @@ -5889,42 +6161,47 @@ static int evaltree(union node *, int); static void FAST_FUNC evalbackcmd(union node *n, struct backcmd *result) { - int saveherefd; + int pip[2]; + struct job *jp; result->fd = -1; result->buf = NULL; result->nleft = 0; result->jp = NULL; - if (n == NULL) + if (n == NULL) { goto out; + } - saveherefd = herefd; - herefd = -1; - - { - int pip[2]; - struct job *jp; - - if (pipe(pip) < 0) - ash_msg_and_raise_error("pipe call failed"); - jp = makejob(/*n,*/ 1); - if (forkshell(jp, n, FORK_NOJOB) == 0) { - FORCE_INT_ON; - close(pip[0]); - if (pip[1] != 1) { - /*close(1);*/ - copyfd(pip[1], 1 | COPYFD_EXACT); - close(pip[1]); - } - eflag = 0; - evaltree(n, EV_EXIT); /* actually evaltreenr... */ - /* NOTREACHED */ + if (pipe(pip) < 0) + ash_msg_and_raise_error("pipe call failed"); + jp = makejob(/*n,*/ 1); + if (forkshell(jp, n, FORK_NOJOB) == 0) { + /* child */ + FORCE_INT_ON; + close(pip[0]); + if (pip[1] != 1) { + /*close(1);*/ + dup2_or_raise(pip[1], 1); + close(pip[1]); } - close(pip[1]); - result->fd = pip[0]; - result->jp = jp; +/* TODO: eflag clearing makes the following not abort: + * ash -c 'set -e; z=$(false;echo foo); echo $z' + * which is what bash does (unless it is in POSIX mode). + * dash deleted "eflag = 0" line in the commit + * Date: Mon, 28 Jun 2010 17:11:58 +1000 + * [EVAL] Don't clear eflag in evalbackcmd + * For now, preserve bash-like behavior, it seems to be somewhat more useful: + */ + eflag = 0; + ifsfree(); + evaltree(n, EV_EXIT); /* actually evaltreenr... */ + /* NOTREACHED */ } - herefd = saveherefd; + /* parent */ + close(pip[1]); + result->fd = pip[0]; + result->jp = jp; + out: TRACE(("evalbackcmd done: fd=%d buf=0x%x nleft=%d jp=0x%x\n", result->fd, result->buf, result->nleft, result->jp)); @@ -5988,7 +6265,7 @@ expbackq(union node *cmd, int flag) stackblock() + startloc)); } -#if ENABLE_SH_MATH_SUPPORT +#if ENABLE_FEATURE_SH_MATH /* * Expand arithmetic expression. Backup to start of expression, * evaluate, place result in (backed up) result, adjust string position. @@ -6056,7 +6333,7 @@ static char *evalvar(char *p, int flags, struct strlist *var_str_list); * $@ like $* since no splitting will be performed. * * var_str_list (can be NULL) is a list of "VAR=val" strings which take precedence - * over shell varables. Needed for "A=a B=$A; echo $B" case - we use it + * over shell variables. Needed for "A=a B=$A; echo $B" case - we use it * for correct expansion of "B=$A" word. */ static void @@ -6070,7 +6347,7 @@ argstr(char *p, int flags, struct strlist *var_str_list) CTLESC, CTLVAR, CTLBACKQ, -#if ENABLE_SH_MATH_SUPPORT +#if ENABLE_FEATURE_SH_MATH CTLENDARI, #endif '\0' @@ -6106,7 +6383,7 @@ argstr(char *p, int flags, struct strlist *var_str_list) c = p[length]; if (c) { if (!(c & 0x80) - IF_SH_MATH_SUPPORT(|| c == CTLENDARI) + IF_FEATURE_SH_MATH(|| c == CTLENDARI) ) { /* c == '=' || c == ':' || c == CTLENDARI */ length++; @@ -6186,7 +6463,7 @@ argstr(char *p, int flags, struct strlist *var_str_list) expbackq(argbackq->n, flags | inquotes); argbackq = argbackq->next; goto start; -#if ENABLE_SH_MATH_SUPPORT +#if ENABLE_FEATURE_SH_MATH case CTLENDARI: p--; expari(flags | inquotes); @@ -6266,8 +6543,8 @@ scanright(char *startp, char *rmesc, char *rmescend, if (try2optimize) { /* Maybe we can optimize this: * if pattern ends with unescaped *, we can avoid checking - * shorter strings: if "foo*" doesnt match "raw_value_of_v", - * it wont match truncated "raw_value_of_" strings too. + * shorter strings: if "foo*" doesn't match "raw_value_of_v", + * it won't match truncated "raw_value_of_" strings too. */ unsigned plen = strlen(pattern); /* Does it end with "*"? */ @@ -6329,23 +6606,20 @@ subevalvar(char *p, char *varname, int strloc, int subtype, char *loc; char *rmesc, *rmescend; char *str; - IF_ASH_BASH_COMPAT(char *repl = NULL;) - IF_ASH_BASH_COMPAT(int pos, len, orig_len;) - int saveherefd = herefd; + IF_BASH_SUBSTR(int pos, len, orig_len;) int amount, resetloc; - IF_ASH_BASH_COMPAT(int workloc;) + IF_BASH_PATTERN_SUBST(int workloc;) + IF_BASH_PATTERN_SUBST(char *repl = NULL;) int zero; char *(*scan)(char*, char*, char*, char*, int, int); //bb_error_msg("subevalvar(p:'%s',varname:'%s',strloc:%d,subtype:%d,startloc:%d,varflags:%x,quotes:%d)", // p, varname, strloc, subtype, startloc, varflags, quotes); - herefd = -1; argstr(p, EXP_TILDE | (subtype != VSASSIGN && subtype != VSQUESTION ? (flag & (EXP_QUOTED | EXP_QPAT) ? EXP_QPAT : EXP_CASE) : 0), var_str_list); STPUTC('\0', expdest); - herefd = saveherefd; argbackq = saveargbackq; startp = (char *)stackblock() + startloc; @@ -6360,7 +6634,7 @@ subevalvar(char *p, char *varname, int strloc, int subtype, varunset(p, varname, startp, varflags); /* NOTREACHED */ -#if ENABLE_ASH_BASH_COMPAT +#if BASH_SUBSTR case VSSUBSTR: //TODO: support more general format ${v:EXPR:EXPR}, // where EXPR follows $(()) rules @@ -6429,17 +6703,19 @@ subevalvar(char *p, char *varname, int strloc, int subtype, amount = loc - expdest; STADJUST(amount, expdest); return loc; -#endif +#endif /* BASH_SUBSTR */ } resetloc = expdest - (char *)stackblock(); +#if BASH_PATTERN_SUBST /* We'll comeback here if we grow the stack while handling * a VSREPLACE or VSREPLACEALL, since our pointers into the * stack will need rebasing, and we'll need to remove our work * areas each time */ - IF_ASH_BASH_COMPAT(restart:) + restart: +#endif amount = expdest - ((char *)stackblock() + resetloc); STADJUST(-amount, expdest); @@ -6464,11 +6740,11 @@ subevalvar(char *p, char *varname, int strloc, int subtype, * RMESCAPE_SLASH causes preglob to work differently on the pattern * and string. It's only used on the first call. */ - preglob(str, IF_ASH_BASH_COMPAT( + preglob(str, IF_BASH_PATTERN_SUBST( (subtype == VSREPLACE || subtype == VSREPLACEALL) && !repl ? - RMESCAPE_SLASH :) 0); + RMESCAPE_SLASH : ) 0); -#if ENABLE_ASH_BASH_COMPAT +#if BASH_PATTERN_SUBST workloc = expdest - (char *)stackblock(); if (subtype == VSREPLACE || subtype == VSREPLACEALL) { char *idx, *end; @@ -6569,7 +6845,7 @@ subevalvar(char *p, char *varname, int strloc, int subtype, STADJUST(-amount, expdest); return startp; } -#endif /* ENABLE_ASH_BASH_COMPAT */ +#endif /* BASH_PATTERN_SUBST */ subtype -= VSTRIMRIGHT; #if DEBUG @@ -6767,6 +7043,10 @@ evalvar(char *p, int flag, struct strlist *var_str_list) varflags = (unsigned char) *p++; subtype = varflags & VSTYPE; + + if (!subtype) + raise_error_syntax("bad substitution"); + quoted = flag & EXP_QUOTED; var = p; easy = (!quoted || (*var == '@' && shellparam.nparam)); @@ -6833,166 +7113,58 @@ evalvar(char *p, int flag, struct strlist *var_str_list) case VSTRIMLEFTMAX: case VSTRIMRIGHT: case VSTRIMRIGHTMAX: -#if ENABLE_ASH_BASH_COMPAT - case VSSUBSTR: - case VSREPLACE: - case VSREPLACEALL: -#endif - break; - default: - abort(); - } -#endif - - if (varlen >= 0) { - /* - * Terminate the string and start recording the pattern - * right after it - */ - STPUTC('\0', expdest); - patloc = expdest - (char *)stackblock(); - if (NULL == subevalvar(p, /* varname: */ NULL, patloc, subtype, - startloc, varflags, flag, var_str_list)) { - int amount = expdest - ( - (char *)stackblock() + patloc - 1 - ); - STADJUST(-amount, expdest); - } - /* Remove any recorded regions beyond start of variable */ - removerecordregions(startloc); - goto record; - } - - end: - if (subtype != VSNORMAL) { /* skip to end of alternative */ - int nesting = 1; - for (;;) { - unsigned char c = *p++; - if (c == CTLESC) - p++; - else if (c == CTLBACKQ) { - if (varlen >= 0) - argbackq = argbackq->next; - } else if (c == CTLVAR) { - if ((*p++ & VSTYPE) != VSNORMAL) - nesting++; - } else if (c == CTLENDVAR) { - if (--nesting == 0) - break; - } - } - } - return p; -} - -/* - * Break the argument string into pieces based upon IFS and add the - * strings to the argument list. The regions of the string to be - * searched for IFS characters have been stored by recordregion. - */ -static void -ifsbreakup(char *string, struct arglist *arglist) -{ - struct ifsregion *ifsp; - struct strlist *sp; - char *start; - char *p; - char *q; - const char *ifs, *realifs; - int ifsspc; - int nulonly; - - start = string; - if (ifslastp != NULL) { - ifsspc = 0; - nulonly = 0; - realifs = ifsset() ? ifsval() : defifs; - ifsp = &ifsfirst; - do { - p = string + ifsp->begoff; - nulonly = ifsp->nulonly; - ifs = nulonly ? nullstr : realifs; - ifsspc = 0; - while (p < string + ifsp->endoff) { - q = p; - if ((unsigned char)*p == CTLESC) - p++; - if (!strchr(ifs, *p)) { - p++; - continue; - } - if (!nulonly) - ifsspc = (strchr(defifs, *p) != NULL); - /* Ignore IFS whitespace at start */ - if (q == start && ifsspc) { - p++; - start = p; - continue; - } - *q = '\0'; - sp = stzalloc(sizeof(*sp)); - sp->text = start; - *arglist->lastp = sp; - arglist->lastp = &sp->next; - p++; - if (!nulonly) { - for (;;) { - if (p >= string + ifsp->endoff) { - break; - } - q = p; - if ((unsigned char)*p == CTLESC) - p++; - if (strchr(ifs, *p) == NULL) { - p = q; - break; - } - if (strchr(defifs, *p) == NULL) { - if (ifsspc) { - p++; - ifsspc = 0; - } else { - p = q; - break; - } - } else - p++; - } - } - start = p; - } /* while */ - ifsp = ifsp->next; - } while (ifsp != NULL); - if (nulonly) - goto add; - } - - if (!*start) - return; - - add: - sp = stzalloc(sizeof(*sp)); - sp->text = start; - *arglist->lastp = sp; - arglist->lastp = &sp->next; -} +#if BASH_SUBSTR + case VSSUBSTR: +#endif +#if BASH_PATTERN_SUBST + case VSREPLACE: + case VSREPLACEALL: +#endif + break; + default: + abort(); + } +#endif -static void -ifsfree(void) -{ - struct ifsregion *p; + if (varlen >= 0) { + /* + * Terminate the string and start recording the pattern + * right after it + */ + STPUTC('\0', expdest); + patloc = expdest - (char *)stackblock(); + if (NULL == subevalvar(p, /* varname: */ NULL, patloc, subtype, + startloc, varflags, flag, var_str_list)) { + int amount = expdest - ( + (char *)stackblock() + patloc - 1 + ); + STADJUST(-amount, expdest); + } + /* Remove any recorded regions beyond start of variable */ + removerecordregions(startloc); + goto record; + } - INT_OFF; - p = ifsfirst.next; - do { - struct ifsregion *ifsp; - ifsp = p->next; - free(p); - p = ifsp; - } while (p); - ifslastp = NULL; - ifsfirst.next = NULL; - INT_ON; + end: + if (subtype != VSNORMAL) { /* skip to end of alternative */ + int nesting = 1; + for (;;) { + unsigned char c = *p++; + if (c == CTLESC) + p++; + else if (c == CTLBACKQ) { + if (varlen >= 0) + argbackq = argbackq->next; + } else if (c == CTLVAR) { + if ((*p++ & VSTYPE) != VSNORMAL) + nesting++; + } else if (c == CTLENDVAR) { + if (--nesting == 0) + break; + } + } + } + return p; } /* @@ -7009,6 +7181,57 @@ addfname(const char *name) exparg.lastp = &sp->next; } +/* Avoid glob() (and thus, stat() et al) for words like "echo" */ +static int +hasmeta(const char *p) +{ + static const char chars[] ALIGN1 = { + '*', '?', '[', '\\', CTLQUOTEMARK, CTLESC, 0 + }; + + for (;;) { + p = strpbrk(p, chars); + if (!p) + break; + switch ((unsigned char) *p) { + case CTLQUOTEMARK: + for (;;) { + p++; + if (*p == CTLQUOTEMARK) + break; + if (*p == CTLESC) + p++; + if (*p == '\0') /* huh? */ + return 0; + } + break; + case '\\': + case CTLESC: + p++; + if (*p == '\0') + return 0; + break; + case '[': + if (!strchr(p + 1, ']')) { + /* It's not a properly closed [] pattern, + * but other metas may follow. Continue checking. + * my[file* _is_ globbed by bash + * and matches filenames like "my[file1". + */ + break; + } + /* fallthrough */ + default: + /* case '*': */ + /* case '?': */ + return 1; + } + p++; + } + + return 0; +} + /* If we want to use glob() from libc... */ #if !ENABLE_ASH_INTERNAL_GLOB @@ -7034,6 +7257,10 @@ expandmeta(struct strlist *str /*, int flag*/) if (fflag) goto nometa; + + if (!hasmeta(str->text)) + goto nometa; + INT_OFF; p = preglob(str->text, RMESCAPE_ALLOC | RMESCAPE_HEAP); // GLOB_NOMAGIC (GNU): if no *?[ chars in pattern, return it even if no match @@ -7044,7 +7271,7 @@ expandmeta(struct strlist *str /*, int flag*/) // Which means you need to unescape the string, right? Not so fast: // if there _is_ a file named "file\?" (with backslash), it is returned // as "file\?" too (whichever pattern you used to find it, say, "file*"). -// You DONT KNOW by looking at the result whether you need to unescape it. +// You DON'T KNOW by looking at the result whether you need to unescape it. // // Worse, globbing of "file\?" in a directory with two files, "file?" and "file\?", // returns "file\?" - which is WRONG: "file\?" pattern matches "file?" file. @@ -7270,9 +7497,6 @@ expsort(struct strlist *str) static void expandmeta(struct strlist *str /*, int flag*/) { - static const char metachars[] ALIGN1 = { - '*', '?', '[', 0 - }; /* TODO - EXP_REDIR */ while (str) { @@ -7283,7 +7507,7 @@ expandmeta(struct strlist *str /*, int flag*/) if (fflag) goto nometa; - if (!strpbrk(str->text, metachars)) + if (!hasmeta(str->text)) goto nometa; savelastp = exparg.lastp; @@ -7333,15 +7557,14 @@ expandarg(union node *arg, struct arglist *arglist, int flag) argbackq = arg->narg.backquote; STARTSTACKSTR(expdest); - ifsfirst.next = NULL; - ifslastp = NULL; TRACE(("expandarg: argstr('%s',flags:%x)\n", arg->narg.text, flag)); argstr(arg->narg.text, flag, /* var_str_list: */ arglist ? arglist->list : NULL); p = _STPUTC('\0', expdest); expdest = p - 1; if (arglist == NULL) { - return; /* here document expanded */ + /* here document expanded */ + goto out; } p = grabstackstr(p); TRACE(("expandarg: p:'%s'\n", p)); @@ -7364,13 +7587,14 @@ expandarg(union node *arg, struct arglist *arglist, int flag) *exparg.lastp = sp; exparg.lastp = &sp->next; } - if (ifsfirst.next) - ifsfree(); *exparg.lastp = NULL; if (exparg.list) { *arglist->lastp = exparg.list; arglist->lastp = exparg.lastp; } + + out: + ifsfree(); } /* @@ -7379,7 +7603,6 @@ expandarg(union node *arg, struct arglist *arglist, int flag) static void expandhere(union node *arg, int fd) { - herefd = fd; expandarg(arg, (struct arglist *)NULL, EXP_QUOTED); full_write(fd, stackblock(), expdest - (char *)stackblock()); } @@ -7390,7 +7613,9 @@ expandhere(union node *arg, int fd) static int patmatch(char *pattern, const char *string) { - return pmatch(preglob(pattern, 0), string); + char *p = preglob(pattern, 0); + //bb_error_msg("fnmatch(pattern:'%s',str:'%s')", p, string); + return pmatch(p, string); } /* @@ -7405,10 +7630,10 @@ casematch(union node *pattern, char *val) setstackmark(&smark); argbackq = pattern->narg.backquote; STARTSTACKSTR(expdest); - ifslastp = NULL; argstr(pattern->narg.text, EXP_TILDE | EXP_CASE, /* var_str_list: */ NULL); STACKSTRNUL(expdest); + ifsfree(); result = patmatch(stackblock(), val); popstackmark(&smark); return result; @@ -7509,13 +7734,7 @@ tryexec(IF_FEATURE_SH_STANDALONE(int applet_no,) char *cmd, char **argv, char ** #else execve(cmd, argv, envp); #endif - if (cmd == (char*) bb_busybox_exec_path) { - /* We already visited ENOEXEC branch below, don't do it again */ -//TODO: try execve(initial_argv0_of_shell, argv, envp) before giving up? - free(argv); - return; - } - if (errno == ENOEXEC) { + if (cmd != (char*) bb_busybox_exec_path && errno == ENOEXEC) { /* Run "cmd" as a shell script: * http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html * "If the execve() function fails with ENOEXEC, the shell @@ -7532,19 +7751,13 @@ tryexec(IF_FEATURE_SH_STANDALONE(int applet_no,) char *cmd, char **argv, char ** * message and exit code 126. For one, this prevents attempts * to interpret foreign ELF binaries as shell scripts. */ - char **ap; - char **new; - - for (ap = argv; *ap; ap++) - continue; - new = ckmalloc((ap - argv + 2) * sizeof(new[0])); - new[0] = (char*) "ash"; - new[1] = cmd; - ap = new + 2; - while ((*ap++ = *++argv) != NULL) - continue; + argv[0] = cmd; cmd = (char*) bb_busybox_exec_path; - argv = new; + /* NB: this is only possible because all callers of shellexec() + * ensure that the argv[-1] slot exists! + */ + argv--; + argv[0] = (char*) "ash"; goto repeat; } } @@ -7552,10 +7765,10 @@ tryexec(IF_FEATURE_SH_STANDALONE(int applet_no,) char *cmd, char **argv, char ** /* * Exec a program. Never returns. If you change this routine, you may * have to change the find_command routine as well. + * argv[-1] must exist and be writable! See tryexec() for why. */ -static void shellexec(char **, const char *, int) NORETURN; -static void -shellexec(char **argv, const char *path, int idx) +static void shellexec(char *prog, char **argv, const char *path, int idx) NORETURN; +static void shellexec(char *prog, char **argv, const char *path, int idx) { char *cmdname; int e; @@ -7563,14 +7776,13 @@ shellexec(char **argv, const char *path, int idx) int exerrno; int applet_no = -1; /* used only by FEATURE_SH_STANDALONE */ - clearredir(/*drop:*/ 1); envp = listvars(VEXPORT, VUNSET, /*end:*/ NULL); - if (strchr(argv[0], '/') != NULL + if (strchr(prog, '/') != NULL #if ENABLE_FEATURE_SH_STANDALONE - || (applet_no = find_applet_by_name(argv[0])) >= 0 + || (applet_no = find_applet_by_name(prog)) >= 0 #endif ) { - tryexec(IF_FEATURE_SH_STANDALONE(applet_no,) argv[0], argv, envp); + tryexec(IF_FEATURE_SH_STANDALONE(applet_no,) prog, argv, envp); if (applet_no >= 0) { /* We tried execing ourself, but it didn't work. * Maybe /proc/self/exe doesn't exist? @@ -7582,7 +7794,7 @@ shellexec(char **argv, const char *path, int idx) } else { try_PATH: e = ENOENT; - while ((cmdname = path_advance(&path, argv[0])) != NULL) { + while ((cmdname = path_advance(&path, prog)) != NULL) { if (--idx < 0 && pathopt == NULL) { tryexec(IF_FEATURE_SH_STANDALONE(-1,) cmdname, argv, envp); if (errno != ENOENT && errno != ENOTDIR) @@ -7606,8 +7818,8 @@ shellexec(char **argv, const char *path, int idx) } exitstatus = exerrno; TRACE(("shellexec failed for %s, errno %d, suppress_int %d\n", - argv[0], e, suppress_int)); - ash_msg_and_raise(EXEXEC, "%s: %s", argv[0], errmsg(e, "not found")); + prog, e, suppress_int)); + ash_msg_and_raise(EXEXIT, "%s: %s", prog, errmsg(e, "not found")); /* NOTREACHED */ } @@ -7866,7 +8078,7 @@ enum { TESAC, TFI, TFOR, -#if ENABLE_ASH_BASH_COMPAT +#if BASH_FUNCTION TFUNCTION, #endif TIF, @@ -7904,7 +8116,7 @@ enum { /* 19 */ | (1u << TESAC) /* 20 */ | (1u << TFI) /* 21 */ | (0u << TFOR) -#if ENABLE_ASH_BASH_COMPAT +#if BASH_FUNCTION /* 22 */ | (0u << TFUNCTION) #endif /* 23 */ | (0u << TIF) @@ -7942,7 +8154,7 @@ static const char *const tokname_array[] = { "esac", "fi", "for", -#if ENABLE_ASH_BASH_COMPAT +#if BASH_FUNCTION "function", #endif "if", @@ -7976,7 +8188,6 @@ static int describe_command(char *command, const char *path, int describe_command_verbose) { struct cmdentry entry; - struct tblentry *cmdp; #if ENABLE_ASH_ALIAS const struct alias *ap; #endif @@ -8006,15 +8217,8 @@ describe_command(char *command, const char *path, int describe_command_verbose) goto out; } #endif - /* Then check if it is a tracked alias */ - cmdp = cmdlookup(command, 0); - if (cmdp != NULL) { - entry.cmdtype = cmdp->cmdtype; - entry.u = cmdp->param; - } else { - /* Finally use brute force */ - find_command(command, &entry, DO_ABS, path); - } + /* Brute force */ + find_command(command, &entry, DO_ABS, path); switch (entry.cmdtype) { case CMDNORMAL: { @@ -8029,9 +8233,7 @@ describe_command(char *command, const char *path, int describe_command_verbose) } while (--j >= 0); } if (describe_command_verbose) { - out1fmt(" is%s %s", - (cmdp ? " a tracked alias for" : nullstr), p - ); + out1fmt(" is %s", p); } else { out1str(p); } @@ -8159,10 +8361,10 @@ commandcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) #endif -static int funcblocksize; /* size of structures in function */ -static int funcstringsize; /* size of strings in node */ +/*static int funcblocksize; // size of structures in function */ +/*static int funcstringsize; // size of strings in node */ static void *funcblock; /* block to allocate function from */ -static char *funcstring; /* block to allocate strings from */ +static char *funcstring_end; /* end of block to allocate strings from */ /* flags in argument to evaltree */ #define EV_EXIT 01 /* exit after evaluating tree */ @@ -8186,7 +8388,7 @@ static const uint8_t nodesize[N_NUMBER] ALIGN1 = { [NDEFUN ] = SHELL_ALIGN(sizeof(struct narg)), [NARG ] = SHELL_ALIGN(sizeof(struct narg)), [NTO ] = SHELL_ALIGN(sizeof(struct nfile)), -#if ENABLE_ASH_BASH_COMPAT +#if BASH_REDIR_OUTPUT [NTO2 ] = SHELL_ALIGN(sizeof(struct nfile)), #endif [NCLOBBER ] = SHELL_ALIGN(sizeof(struct nfile)), @@ -8200,107 +8402,106 @@ static const uint8_t nodesize[N_NUMBER] ALIGN1 = { [NNOT ] = SHELL_ALIGN(sizeof(struct nnot)), }; -static void calcsize(union node *n); +static int calcsize(int funcblocksize, union node *n); -static void -sizenodelist(struct nodelist *lp) +static int +sizenodelist(int funcblocksize, struct nodelist *lp) { while (lp) { funcblocksize += SHELL_ALIGN(sizeof(struct nodelist)); - calcsize(lp->n); + funcblocksize = calcsize(funcblocksize, lp->n); lp = lp->next; } + return funcblocksize; } -static void -calcsize(union node *n) +static int +calcsize(int funcblocksize, union node *n) { if (n == NULL) - return; + return funcblocksize; funcblocksize += nodesize[n->type]; switch (n->type) { case NCMD: - calcsize(n->ncmd.redirect); - calcsize(n->ncmd.args); - calcsize(n->ncmd.assign); + funcblocksize = calcsize(funcblocksize, n->ncmd.redirect); + funcblocksize = calcsize(funcblocksize, n->ncmd.args); + funcblocksize = calcsize(funcblocksize, n->ncmd.assign); break; case NPIPE: - sizenodelist(n->npipe.cmdlist); + funcblocksize = sizenodelist(funcblocksize, n->npipe.cmdlist); break; case NREDIR: case NBACKGND: case NSUBSHELL: - calcsize(n->nredir.redirect); - calcsize(n->nredir.n); + funcblocksize = calcsize(funcblocksize, n->nredir.redirect); + funcblocksize = calcsize(funcblocksize, n->nredir.n); break; case NAND: case NOR: case NSEMI: case NWHILE: case NUNTIL: - calcsize(n->nbinary.ch2); - calcsize(n->nbinary.ch1); + funcblocksize = calcsize(funcblocksize, n->nbinary.ch2); + funcblocksize = calcsize(funcblocksize, n->nbinary.ch1); break; case NIF: - calcsize(n->nif.elsepart); - calcsize(n->nif.ifpart); - calcsize(n->nif.test); + funcblocksize = calcsize(funcblocksize, n->nif.elsepart); + funcblocksize = calcsize(funcblocksize, n->nif.ifpart); + funcblocksize = calcsize(funcblocksize, n->nif.test); break; case NFOR: - funcstringsize += strlen(n->nfor.var) + 1; - calcsize(n->nfor.body); - calcsize(n->nfor.args); + funcblocksize += SHELL_ALIGN(strlen(n->nfor.var) + 1); /* was funcstringsize += ... */ + funcblocksize = calcsize(funcblocksize, n->nfor.body); + funcblocksize = calcsize(funcblocksize, n->nfor.args); break; case NCASE: - calcsize(n->ncase.cases); - calcsize(n->ncase.expr); + funcblocksize = calcsize(funcblocksize, n->ncase.cases); + funcblocksize = calcsize(funcblocksize, n->ncase.expr); break; case NCLIST: - calcsize(n->nclist.body); - calcsize(n->nclist.pattern); - calcsize(n->nclist.next); + funcblocksize = calcsize(funcblocksize, n->nclist.body); + funcblocksize = calcsize(funcblocksize, n->nclist.pattern); + funcblocksize = calcsize(funcblocksize, n->nclist.next); break; case NDEFUN: case NARG: - sizenodelist(n->narg.backquote); - funcstringsize += strlen(n->narg.text) + 1; - calcsize(n->narg.next); + funcblocksize = sizenodelist(funcblocksize, n->narg.backquote); + funcblocksize += SHELL_ALIGN(strlen(n->narg.text) + 1); /* was funcstringsize += ... */ + funcblocksize = calcsize(funcblocksize, n->narg.next); break; case NTO: -#if ENABLE_ASH_BASH_COMPAT +#if BASH_REDIR_OUTPUT case NTO2: #endif case NCLOBBER: case NFROM: case NFROMTO: case NAPPEND: - calcsize(n->nfile.fname); - calcsize(n->nfile.next); + funcblocksize = calcsize(funcblocksize, n->nfile.fname); + funcblocksize = calcsize(funcblocksize, n->nfile.next); break; case NTOFD: case NFROMFD: - calcsize(n->ndup.vname); - calcsize(n->ndup.next); + funcblocksize = calcsize(funcblocksize, n->ndup.vname); + funcblocksize = calcsize(funcblocksize, n->ndup.next); break; case NHERE: case NXHERE: - calcsize(n->nhere.doc); - calcsize(n->nhere.next); + funcblocksize = calcsize(funcblocksize, n->nhere.doc); + funcblocksize = calcsize(funcblocksize, n->nhere.next); break; case NNOT: - calcsize(n->nnot.com); + funcblocksize = calcsize(funcblocksize, n->nnot.com); break; }; + return funcblocksize; } static char * nodeckstrdup(char *s) { - char *rtn = funcstring; - - strcpy(funcstring, s); - funcstring += strlen(s) + 1; - return rtn; + funcstring_end -= SHELL_ALIGN(strlen(s) + 1); + return strcpy(funcstring_end, s); } static union node *copynode(union node *); @@ -8383,7 +8584,7 @@ copynode(union node *n) new->narg.next = copynode(n->narg.next); break; case NTO: -#if ENABLE_ASH_BASH_COMPAT +#if BASH_REDIR_OUTPUT case NTO2: #endif case NCLOBBER: @@ -8424,15 +8625,13 @@ copyfunc(union node *n) struct funcnode *f; size_t blocksize; - funcblocksize = offsetof(struct funcnode, n); - funcstringsize = 0; - calcsize(n); - blocksize = funcblocksize; - f = ckmalloc(blocksize + funcstringsize); + /*funcstringsize = 0;*/ + blocksize = offsetof(struct funcnode, n) + calcsize(0, n); + f = ckzalloc(blocksize /* + funcstringsize */); funcblock = (char *) f + offsetof(struct funcnode, n); - funcstring = (char *) f + blocksize; + funcstring_end = (char *) f + blocksize; copynode(n); - f->count = 0; + /* f->count = 0; - ckzalloc did it */ return f; } @@ -8440,14 +8639,14 @@ copyfunc(union node *n) * Define a shell function. */ static void -defun(char *name, union node *func) +defun(union node *func) { struct cmdentry entry; INT_OFF; entry.cmdtype = CMDFUNCTION; entry.u.func = copyfunc(func); - addcmdentry(name, &entry); + addcmdentry(func->narg.text, &entry); INT_ON; } @@ -8530,41 +8729,18 @@ static void prehash(union node *); static int evaltree(union node *n, int flags) { - struct jmploc *volatile savehandler = exception_handler; - struct jmploc jmploc; int checkexit = 0; int (*evalfn)(union node *, int); int status = 0; - int int_level; - - SAVE_INT(int_level); if (n == NULL) { TRACE(("evaltree(NULL) called\n")); - goto out1; + goto out; } TRACE(("evaltree(%p: %d, %d) called\n", n, n->type, flags)); dotrap(); - exception_handler = &jmploc; - { - int err = setjmp(jmploc.loc); - if (err) { - /* if it was a signal, check for trap handlers */ - if (exception_type == EXSIG) { - TRACE(("exception %d (EXSIG) in evaltree, err=%d\n", - exception_type, err)); - goto out; - } - /* continue on the way out */ - TRACE(("exception %d in evaltree, propagating err=%d\n", - exception_type, err)); - exception_handler = savehandler; - longjmp(exception_handler->loc, err); - } - } - switch (n->type) { default: #if DEBUG @@ -8581,7 +8757,8 @@ evaltree(union node *n, int flags) if (!status) { status = evaltree(n->nredir.n, flags & EV_TESTED); } - popredir(/*drop:*/ 0, /*restore:*/ 0 /* not sure */); + if (n->nredir.redirect) + popredir(/*drop:*/ 0, /*restore:*/ 0 /* not sure */); goto setstatus; case NCMD: evalfn = evalcommand; @@ -8597,11 +8774,9 @@ evaltree(union node *n, int flags) evalfn = evalloop; goto calleval; case NSUBSHELL: - evalfn = evalsubshell; - goto checkexit; case NBACKGND: evalfn = evalsubshell; - goto calleval; + goto checkexit; case NPIPE: evalfn = evalpipe; goto checkexit; @@ -8647,7 +8822,7 @@ evaltree(union node *n, int flags) status = 0; goto setstatus; case NDEFUN: - defun(n->narg.text, n->narg.next); + defun(n); /* Not necessary. To test it: * "false; f() { qwerty; }; echo $?" should print 0. */ @@ -8656,11 +8831,7 @@ evaltree(union node *n, int flags) exitstatus = status; break; } - out: - exception_handler = savehandler; - - out1: /* Order of checks below is important: * signal handlers trigger before exit caused by "set -e". */ @@ -8671,9 +8842,7 @@ evaltree(union node *n, int flags) if (flags & EV_EXIT) raise_exception(EXEXIT); - RESTORE_INT(int_level); TRACE(("leaving evaltree (no interrupts)\n")); - return exitstatus; } @@ -8803,13 +8972,15 @@ static int evalsubshell(union node *n, int flags) { struct job *jp; - int backgnd = (n->type == NBACKGND); + int backgnd = (n->type == NBACKGND); /* FORK_BG(1) if yes, else FORK_FG(0) */ int status; expredir(n->nredir.redirect); if (!backgnd && (flags & EV_EXIT) && !may_have_traps) goto nofork; INT_OFF; + if (backgnd == FORK_FG) + get_tty_state(); jp = makejob(/*n,*/ 1); if (forkshell(jp, n, backgnd) == 0) { /* child */ @@ -8822,8 +8993,9 @@ evalsubshell(union node *n, int flags) evaltreenr(n->nredir.n, flags); /* never returns */ } + /* parent */ status = 0; - if (!backgnd) + if (backgnd == FORK_FG) status = waitforjob(jp); INT_ON; return status; @@ -8847,14 +9019,14 @@ expredir(union node *n) case NFROMTO: case NFROM: case NTO: -#if ENABLE_ASH_BASH_COMPAT +#if BASH_REDIR_OUTPUT case NTO2: #endif case NCLOBBER: case NAPPEND: expandarg(redir->nfile.fname, &fn, EXP_TILDE | EXP_REDIR); TRACE(("expredir expanded to '%s'\n", fn.list->text)); -#if ENABLE_ASH_BASH_COMPAT +#if BASH_REDIR_OUTPUT store_expfname: #endif #if 0 @@ -8876,7 +9048,7 @@ expredir(union node *n) expandarg(redir->ndup.vname, &fn, EXP_FULL | EXP_TILDE); if (fn.list == NULL) ash_msg_and_raise_error("redir error"); -#if ENABLE_ASH_BASH_COMPAT +#if BASH_REDIR_OUTPUT //FIXME: we used expandarg with different args! if (!isdigit_str9(fn.list->text)) { /* >&file, not >&fd */ @@ -8915,6 +9087,8 @@ evalpipe(union node *n, int flags) pipelen++; flags |= EV_EXIT; INT_OFF; + if (n->npipe.pipe_backgnd == 0) + get_tty_state(); jp = makejob(/*n,*/ pipelen); prevfd = -1; for (lp = n->npipe.cmdlist; lp; lp = lp->next) { @@ -8927,6 +9101,7 @@ evalpipe(union node *n, int flags) } } if (forkshell(jp, lp->n, n->npipe.pipe_backgnd) == 0) { + /* child */ INT_ON; if (pip[1] >= 0) { close(pip[0]); @@ -8942,6 +9117,7 @@ evalpipe(union node *n, int flags) evaltreenr(lp->n, flags); /* never returns */ } + /* parent */ if (prevfd >= 0) close(prevfd); prevfd = pip[0]; @@ -9072,7 +9248,7 @@ evalfun(struct funcnode *func, int argc, char **argv, int flags) shellparam.optind = 1; shellparam.optoff = -1; #endif - evaltree(&func->n, flags & EV_TESTED); + evaltree(func->n.narg.next, flags & EV_TESTED); funcdone: INT_OFF; funcnest--; @@ -9115,7 +9291,7 @@ mklocal(char *name) /* else: * it's a duplicate "local VAR" declaration, do nothing */ - return; + goto ret; } lvp = lvp->next; } @@ -9154,6 +9330,7 @@ mklocal(char *name) lvp->vp = vp; lvp->next = localvars; localvars = lvp; + ret: INT_ON; } @@ -9190,7 +9367,14 @@ truecmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) static int FAST_FUNC execcmd(int argc UNUSED_PARAM, char **argv) { - if (argv[1]) { + optionarg = NULL; + while (nextopt("a:") != '\0') + /* nextopt() sets optionarg to "-a ARGV0" */; + + argv = argptr; + if (argv[0]) { + char *prog; + iflag = 0; /* exit on error */ mflag = 0; optschanged(); @@ -9206,7 +9390,10 @@ execcmd(int argc UNUSED_PARAM, char **argv) /*setsignal(SIGTSTP); - unnecessary because of mflag=0 */ /*setsignal(SIGTTOU); - unnecessary because of mflag=0 */ - shellexec(argv + 1, pathval(), 0); + prog = argv[0]; + if (optionarg) + argv[0] = optionarg; + shellexec(prog, argv, pathval(), 0); /* NOTREACHED */ } return 0; @@ -9241,7 +9428,7 @@ static int helpcmd(int, char **) FAST_FUNC; #if MAX_HISTORY static int historycmd(int, char **) FAST_FUNC; #endif -#if ENABLE_SH_MATH_SUPPORT +#if ENABLE_FEATURE_SH_MATH static int letcmd(int, char **) FAST_FUNC; #endif static int readcmd(int, char **) FAST_FUNC; @@ -9263,13 +9450,13 @@ static int ulimitcmd(int, char **) FAST_FUNC; #define BUILTIN_SPEC_REG_ASSG "7" /* Stubs for calling non-FAST_FUNC's */ -#if ENABLE_ASH_BUILTIN_ECHO +#if ENABLE_ASH_ECHO static int FAST_FUNC echocmd(int argc, char **argv) { return echo_main(argc, argv); } #endif -#if ENABLE_ASH_BUILTIN_PRINTF +#if ENABLE_ASH_PRINTF static int FAST_FUNC printfcmd(int argc, char **argv) { return printf_main(argc, argv); } #endif -#if ENABLE_ASH_BUILTIN_TEST +#if ENABLE_ASH_TEST || BASH_TEST2 static int FAST_FUNC testcmd(int argc, char **argv) { return test_main(argc, argv); } #endif @@ -9277,11 +9464,11 @@ static int FAST_FUNC testcmd(int argc, char **argv) { return test_main(argc, a static const struct builtincmd builtintab[] = { { BUILTIN_SPEC_REG "." , dotcmd }, { BUILTIN_SPEC_REG ":" , truecmd }, -#if ENABLE_ASH_BUILTIN_TEST +#if ENABLE_ASH_TEST { BUILTIN_REGULAR "[" , testcmd }, -#if ENABLE_ASH_BASH_COMPAT - { BUILTIN_REGULAR "[[" , testcmd }, #endif +#if BASH_TEST2 + { BUILTIN_REGULAR "[[" , testcmd }, #endif #if ENABLE_ASH_ALIAS { BUILTIN_REG_ASSG "alias" , aliascmd }, @@ -9296,7 +9483,7 @@ static const struct builtincmd builtintab[] = { { BUILTIN_REGULAR "command" , commandcmd }, #endif { BUILTIN_SPEC_REG "continue", breakcmd }, -#if ENABLE_ASH_BUILTIN_ECHO +#if ENABLE_ASH_ECHO { BUILTIN_REGULAR "echo" , echocmd }, #endif { BUILTIN_SPEC_REG "eval" , NULL }, /*evalcmd() has a differing prototype*/ @@ -9321,11 +9508,11 @@ static const struct builtincmd builtintab[] = { { BUILTIN_REGULAR "jobs" , jobscmd }, { BUILTIN_REGULAR "kill" , killcmd }, #endif -#if ENABLE_SH_MATH_SUPPORT +#if ENABLE_FEATURE_SH_MATH { BUILTIN_NOSPEC "let" , letcmd }, #endif { BUILTIN_ASSIGN "local" , localcmd }, -#if ENABLE_ASH_BUILTIN_PRINTF +#if ENABLE_ASH_PRINTF { BUILTIN_REGULAR "printf" , printfcmd }, #endif { BUILTIN_NOSPEC "pwd" , pwdcmd }, @@ -9334,10 +9521,10 @@ static const struct builtincmd builtintab[] = { { BUILTIN_SPEC_REG "return" , returncmd }, { BUILTIN_SPEC_REG "set" , setcmd }, { BUILTIN_SPEC_REG "shift" , shiftcmd }, -#if ENABLE_ASH_BASH_COMPAT +#if BASH_SOURCE { BUILTIN_SPEC_REG "source" , dotcmd }, #endif -#if ENABLE_ASH_BUILTIN_TEST +#if ENABLE_ASH_TEST { BUILTIN_REGULAR "test" , testcmd }, #endif { BUILTIN_SPEC_REG "times" , timescmd }, @@ -9356,15 +9543,15 @@ static const struct builtincmd builtintab[] = { /* Should match the above table! */ #define COMMANDCMD (builtintab + \ /* . : */ 2 + \ - /* [ */ 1 * ENABLE_ASH_BUILTIN_TEST + \ - /* [[ */ 1 * ENABLE_ASH_BUILTIN_TEST * ENABLE_ASH_BASH_COMPAT + \ + /* [ */ 1 * ENABLE_ASH_TEST + \ + /* [[ */ 1 * BASH_TEST2 + \ /* alias */ 1 * ENABLE_ASH_ALIAS + \ /* bg */ 1 * ENABLE_ASH_JOB_CONTROL + \ /* break cd cddir */ 3) #define EVALCMD (COMMANDCMD + \ /* command */ 1 * ENABLE_ASH_CMDCMD + \ /* continue */ 1 + \ - /* echo */ 1 * ENABLE_ASH_BUILTIN_ECHO + \ + /* echo */ 1 * ENABLE_ASH_ECHO + \ 0) #define EXECCMD (EVALCMD + \ /* eval */ 1) @@ -9462,7 +9649,9 @@ evalcommand(union node *cmd, int flags) argc++; } - argv = nargv = stalloc(sizeof(char *) * (argc + 1)); + /* Reserve one extra spot at the front for shellexec. */ + nargv = stalloc(sizeof(char *) * (argc + 2)); + argv = ++nargv; for (sp = arglist.list; sp; sp = sp->next) { TRACE(("evalcommand arg: %s\n", sp->text)); *nargv++ = sp->text; @@ -9556,11 +9745,13 @@ evalcommand(union node *cmd, int flags) } if (status) { + bail: + exitstatus = status; + /* We have a redirection error. */ if (spclbltin > 0) raise_exception(EXERROR); - bail: - exitstatus = status; + goto out; } @@ -9579,7 +9770,7 @@ evalcommand(union node *cmd, int flags) if (applet_no >= 0 && APPLET_IS_NOFORK(applet_no)) { listsetvar(varlist.list, VEXPORT|VSTACK); /* run _main() */ - exitstatus = run_nofork_applet(applet_no, argv); + status = run_nofork_applet(applet_no, argv); break; } #endif @@ -9590,6 +9781,7 @@ evalcommand(union node *cmd, int flags) if (!(flags & EV_EXIT) || may_have_traps) { /* No, forking off a child is necessary */ INT_OFF; + get_tty_state(); jp = makejob(/*cmd,*/ 1); if (forkshell(jp, cmd, FORK_FG) != 0) { /* parent */ @@ -9603,7 +9795,7 @@ evalcommand(union node *cmd, int flags) /* fall through to exec'ing external program */ } listsetvar(varlist.list, VEXPORT|VSTACK); - shellexec(argv, path, cmdentry.u.index); + shellexec(argv[0], argv, path, cmdentry.u.index); /* NOTREACHED */ } /* default */ case CMDBUILTIN: @@ -9625,21 +9817,12 @@ evalcommand(union node *cmd, int flags) dowait(DOWAIT_NONBLOCK, NULL); if (evalbltin(cmdentry.u.cmd, argc, argv, flags)) { - int exit_status; - int i = exception_type; - if (i == EXEXIT || i == EXEXEC) - goto raise; - exit_status = 2; - if (i == EXINT) - exit_status = 128 + SIGINT; - if (i == EXSIG) - exit_status = 128 + pending_sig; - exitstatus = exit_status; - if (i == EXINT || spclbltin > 0) { - raise: - longjmp(exception_handler->loc, 1); + if (exception_type == EXERROR && spclbltin <= 0) { + FORCE_INT_ON; + goto readstatus; } - FORCE_INT_ON; + raise: + longjmp(exception_handler->loc, 1); } goto readstatus; @@ -9655,7 +9838,8 @@ evalcommand(union node *cmd, int flags) } /* switch */ out: - popredir(/*drop:*/ cmd_is_exec, /*restore:*/ cmd_is_exec); + if (cmd->ncmd.redirect) + popredir(/*drop:*/ cmd_is_exec, /*restore:*/ cmd_is_exec); if (lastarg) { /* dsl: I think this is intended to be used to support * '_' in 'vi' command mode during line editing... @@ -9757,8 +9941,7 @@ breakcmd(int argc UNUSED_PARAM, char **argv) } -/* ============ input.c - * +/* * This implements the input routines used by the parser. */ @@ -9873,13 +10056,16 @@ preadfd(void) reinit_unicode_for_ash(); nr = read_line_input(line_input_state, cmdedit_prompt, buf, IBUFSIZ, timeout); if (nr == 0) { - /* Ctrl+C pressed */ + /* ^C pressed, "convert" to SIGINT */ + write(STDOUT_FILENO, "^C", 2); if (trap[SIGINT]) { buf[0] = '\n'; buf[1] = '\0'; raise(SIGINT); return 1; } + exitstatus = 128 + SIGINT; + bb_putchar('\n'); goto retry; } if (nr < 0) { @@ -10052,7 +10238,7 @@ pgetc(void) return g_parsefile->lastc[--g_parsefile->unget]; if (--g_parsefile->left_in_line >= 0) - c = (signed char)*g_parsefile->next_to_pgetc++; + c = (unsigned char)*g_parsefile->next_to_pgetc++; else c = preadbuffer(); @@ -10156,6 +10342,9 @@ popfile(void) { struct parsefile *pf = g_parsefile; + if (pf == &basepf) + return; + INT_OFF; if (pf->pf_fd >= 0) close(pf->pf_fd); @@ -10198,7 +10387,6 @@ closescript(void) static void setinputfd(int fd, int push) { - close_on_exec_on(fd); if (push) { pushfile(); g_parsefile->buf = NULL; @@ -10219,22 +10407,19 @@ static int setinputfile(const char *fname, int flags) { int fd; - int fd2; INT_OFF; fd = open(fname, O_RDONLY); if (fd < 0) { if (flags & INPUT_NOFILE_OK) goto out; + exitstatus = 127; ash_msg_and_raise_error("can't open '%s'", fname); } - if (fd < 10) { - fd2 = copyfd(fd, 10); - close(fd); - if (fd2 < 0) - ash_msg_and_raise_error("out of file descriptors"); - fd = fd2; - } + if (fd < 10) + fd = savefd(fd); + else + close_on_exec_on(fd); setinputfd(fd, flags & INPUT_PUSH_FILE); out: INT_ON; @@ -10257,8 +10442,7 @@ setinputstring(char *string) } -/* ============ mail.c - * +/* * Routines to check for mail. */ @@ -10576,25 +10760,25 @@ change_random(const char *value) #if ENABLE_ASH_GETOPTS static int -getopts(char *optstr, char *optvar, char **optfirst, int *param_optind, int *optoff) +getopts(char *optstr, char *optvar, char **optfirst) { char *p, *q; char c = '?'; int done = 0; - int err = 0; char sbuf[2]; char **optnext; + int ind = shellparam.optind; + int off = shellparam.optoff; sbuf[1] = '\0'; - if (*param_optind < 1) - return 1; - optnext = optfirst + *param_optind - 1; + shellparam.optind = -1; + optnext = optfirst + ind - 1; - if (*param_optind <= 1 || *optoff < 0 || (int)strlen(optnext[-1]) < *optoff) + if (ind <= 1 || off < 0 || (int)strlen(optnext[-1]) < off) p = NULL; else - p = optnext[-1] + *optoff; + p = optnext[-1] + off; if (p == NULL || *p == '\0') { /* Current word is done, advance */ p = *optnext; @@ -10615,7 +10799,7 @@ getopts(char *optstr, char *optvar, char **optfirst, int *param_optind, int *opt if (optstr[0] == ':') { sbuf[0] = c; /*sbuf[1] = '\0'; - already is */ - err |= setvarsafe("OPTARG", sbuf, 0); + setvar0("OPTARG", sbuf); } else { fprintf(stderr, "Illegal option -%c\n", c); unsetvar("OPTARG"); @@ -10632,7 +10816,7 @@ getopts(char *optstr, char *optvar, char **optfirst, int *param_optind, int *opt if (optstr[0] == ':') { sbuf[0] = c; /*sbuf[1] = '\0'; - already is */ - err |= setvarsafe("OPTARG", sbuf, 0); + setvar0("OPTARG", sbuf); c = ':'; } else { fprintf(stderr, "No arg for -%c option\n", c); @@ -10644,23 +10828,20 @@ getopts(char *optstr, char *optvar, char **optfirst, int *param_optind, int *opt if (p == *optnext) optnext++; - err |= setvarsafe("OPTARG", p, 0); + setvar0("OPTARG", p); p = NULL; } else - err |= setvarsafe("OPTARG", nullstr, 0); + setvar0("OPTARG", nullstr); out: - *optoff = p ? p - *(optnext - 1) : -1; - *param_optind = optnext - optfirst + 1; - err |= setvarsafe("OPTIND", itoa(*param_optind), VNOFUNC); + ind = optnext - optfirst + 1; + setvar("OPTIND", itoa(ind), VNOFUNC); sbuf[0] = c; /*sbuf[1] = '\0'; - already is */ - err |= setvarsafe(optvar, sbuf, 0); - if (err) { - *param_optind = 1; - *optoff = -1; - flush_stdout_stderr(); - raise_exception(EXERROR); - } + setvar0(optvar, sbuf); + + shellparam.optoff = p ? p - *(optnext - 1) : -1; + shellparam.optind = ind; + return done; } @@ -10679,20 +10860,19 @@ getoptscmd(int argc, char **argv) ash_msg_and_raise_error("usage: getopts optstring var [arg]"); if (argc == 3) { optbase = shellparam.p; - if (shellparam.optind > shellparam.nparam + 1) { + if ((unsigned)shellparam.optind > shellparam.nparam + 1) { shellparam.optind = 1; shellparam.optoff = -1; } } else { optbase = &argv[3]; - if (shellparam.optind > argc - 2) { + if ((unsigned)shellparam.optind > argc - 2) { shellparam.optind = 1; shellparam.optoff = -1; } } - return getopts(argv[1], argv[2], optbase, &shellparam.optind, - &shellparam.optoff); + return getopts(argv[1], argv[2], optbase); } #endif /* ASH_GETOPTS */ @@ -10987,10 +11167,10 @@ simplecmd(void) union node *vars, **vpp; union node **rpp, *redir; int savecheckkwd; -#if ENABLE_ASH_BASH_COMPAT +#if BASH_TEST2 smallint double_brackets_flag = 0; - smallint function_flag = 0; #endif + IF_BASH_FUNCTION(smallint function_flag = 0;) args = NULL; app = &args; @@ -11005,12 +11185,14 @@ simplecmd(void) checkkwd = savecheckkwd; t = readtoken(); switch (t) { -#if ENABLE_ASH_BASH_COMPAT +#if BASH_FUNCTION case TFUNCTION: if (peektoken() != TWORD) raise_error_unexpected_syntax(TWORD); function_flag = 1; break; +#endif +#if BASH_TEST2 case TAND: /* "&&" */ case TOR: /* "||" */ if (!double_brackets_flag) { @@ -11024,7 +11206,7 @@ simplecmd(void) n->type = NARG; /*n->narg.next = NULL; - stzalloc did it */ n->narg.text = wordtext; -#if ENABLE_ASH_BASH_COMPAT +#if BASH_TEST2 if (strcmp("[[", wordtext) == 0) double_brackets_flag = 1; else if (strcmp("]]", wordtext) == 0) @@ -11039,7 +11221,7 @@ simplecmd(void) app = &n->narg.next; savecheckkwd = 0; } -#if ENABLE_ASH_BASH_COMPAT +#if BASH_FUNCTION if (function_flag) { checkkwd = CHKNL | CHKKWD; switch (peektoken()) { @@ -11069,7 +11251,7 @@ simplecmd(void) parsefname(); /* read name of redirection file */ break; case TLP: - IF_ASH_BASH_COMPAT(do_func:) + IF_BASH_FUNCTION(do_func:) if (args && app == &args->narg.next && !vars && !redir ) { @@ -11077,7 +11259,7 @@ simplecmd(void) const char *name; /* We have a function */ - if (IF_ASH_BASH_COMPAT(!function_flag &&) readtoken() != TRP) + if (IF_BASH_FUNCTION(!function_flag &&) readtoken() != TRP) raise_error_unexpected_syntax(TRP); name = n->narg.text; if (!goodname(name) @@ -11090,7 +11272,7 @@ simplecmd(void) n->narg.next = parse_command(); return n; } - IF_ASH_BASH_COMPAT(function_flag = 0;) + IF_BASH_FUNCTION(function_flag = 0;) /* fall through */ default: tokpushback = 1; @@ -11271,7 +11453,7 @@ parse_command(void) n1 = list(0); t = TEND; break; - IF_ASH_BASH_COMPAT(case TFUNCTION:) + IF_BASH_FUNCTION(case TFUNCTION:) case TWORD: case TREDIR: tokpushback = 1; @@ -11304,7 +11486,7 @@ parse_command(void) return n1; } -#if ENABLE_ASH_BASH_COMPAT +#if BASH_DOLLAR_SQUOTE static int decode_dollar_squote(void) { @@ -11380,30 +11562,26 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) smallint quotef; smallint dblquote; smallint oldstyle; - smallint prevsyntax; /* syntax before arithmetic */ -#if ENABLE_ASH_EXPAND_PRMT + IF_FEATURE_SH_MATH(smallint prevsyntax;) /* syntax before arithmetic */ smallint pssyntax; /* we are expanding a prompt string */ -#endif int varnest; /* levels of variables expansion */ - int arinest; /* levels of arithmetic expansion */ - int parenlevel; /* levels of parens in arithmetic */ + IF_FEATURE_SH_MATH(int arinest;) /* levels of arithmetic expansion */ + IF_FEATURE_SH_MATH(int parenlevel;) /* levels of parens in arithmetic */ int dqvarnest; /* levels of variables expansion within double quotes */ - IF_ASH_BASH_COMPAT(smallint bash_dollar_squote = 0;) + IF_BASH_DOLLAR_SQUOTE(smallint bash_dollar_squote = 0;) startlinno = g_parsefile->linno; bqlist = NULL; quotef = 0; - prevsyntax = 0; -#if ENABLE_ASH_EXPAND_PRMT + IF_FEATURE_SH_MATH(prevsyntax = 0;) pssyntax = (syntax == PSSYNTAX); if (pssyntax) syntax = DQSYNTAX; -#endif dblquote = (syntax == DQSYNTAX); varnest = 0; - arinest = 0; - parenlevel = 0; + IF_FEATURE_SH_MATH(arinest = 0;) + IF_FEATURE_SH_MATH(parenlevel = 0;) dqvarnest = 0; STARTSTACKSTR(out); @@ -11424,7 +11602,7 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) USTPUTC(c, out); break; case CCTL: -#if ENABLE_ASH_BASH_COMPAT +#if BASH_DOLLAR_SQUOTE if (c == '\\' && bash_dollar_squote) { c = decode_dollar_squote(); if (c == '\0') { @@ -11453,12 +11631,10 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) } else if (c == '\n') { nlprompt(); } else { -#if ENABLE_ASH_EXPAND_PRMT if (c == '$' && pssyntax) { USTPUTC(CTLESC, out); USTPUTC('\\', out); } -#endif /* Backslash is retained if we are in "str" and next char isn't special */ if (dblquote && c != '\\' @@ -11485,7 +11661,7 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) dblquote = 1; goto quotemark; case CENDQUOTE: - IF_ASH_BASH_COMPAT(bash_dollar_squote = 0;) + IF_BASH_DOLLAR_SQUOTE(bash_dollar_squote = 0;) if (eofmark != NULL && varnest == 0) { USTPUTC(c, out); } else { @@ -11510,7 +11686,7 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) } USTPUTC(c, out); break; -#if ENABLE_SH_MATH_SUPPORT +#if ENABLE_FEATURE_SH_MATH case CLP: /* '(' in arithmetic */ parenlevel++; USTPUTC(c, out); @@ -11544,7 +11720,7 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) break; default: if (varnest == 0) { -#if ENABLE_ASH_BASH_COMPAT +#if BASH_REDIR_OUTPUT if (c == '&') { //Can't call pgetc_eatbnl() here, this requires three-deep pungetc() if (pgetc() == '>') @@ -11561,7 +11737,7 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) } /* for (;;) */ endword: -#if ENABLE_SH_MATH_SUPPORT +#if ENABLE_FEATURE_SH_MATH if (syntax == ARISYNTAX) raise_error_syntax("missing '))'"); #endif @@ -11576,7 +11752,7 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) len = out - (char *)stackblock(); out = stackblock(); if (eofmark == NULL) { - if ((c == '>' || c == '<' IF_ASH_BASH_COMPAT( || c == 0x100 + '>')) + if ((c == '>' || c == '<' IF_BASH_REDIR_OUTPUT( || c == 0x100 + '>')) && quotef == 0 ) { if (isdigit_str9(out)) { @@ -11616,11 +11792,17 @@ checkend: { if (c == *eofmark) { if (pfgets(line, sizeof(line)) != NULL) { char *p, *q; + int cc; p = line; - for (q = eofmark + 1; *q && *p == *q; p++, q++) - continue; - if (*p == '\n' && *q == '\0') { + for (q = eofmark + 1;; p++, q++) { + cc = *p; + if (cc == '\n') + cc = 0; + if (!*q || cc != *q) + break; + } + if (cc == *q) { c = PEOF; nlnoprompt(); } else { @@ -11658,7 +11840,7 @@ parseredir: { pungetc(); } } -#if ENABLE_ASH_BASH_COMPAT +#if BASH_REDIR_OUTPUT else if (c == 0x100 + '>') { /* this flags &> redirection */ np->nfile.fd = 1; pgetc(); /* this is '>', no need to check */ @@ -11719,13 +11901,12 @@ parseredir: { parsesub: { unsigned char subtype; int typeloc; - int flags; c = pgetc_eatbnl(); if (c > 255 /* PEOA or PEOF */ || (c != '(' && c != '{' && !is_name(c) && !is_special(c)) ) { -#if ENABLE_ASH_BASH_COMPAT +#if BASH_DOLLAR_SQUOTE if (syntax != DQSYNTAX && c == '\'') bash_dollar_squote = 1; else @@ -11735,7 +11916,7 @@ parsesub: { } else if (c == '(') { /* $(command) or $((arith)) */ if (pgetc_eatbnl() == '(') { -#if ENABLE_SH_MATH_SUPPORT +#if ENABLE_FEATURE_SH_MATH PARSEARITH(); #else raise_error_syntax("you disabled math support for $((arith)) syntax"); @@ -11748,26 +11929,19 @@ parsesub: { /* $VAR, $, ${...}, or PEOA/PEOF */ USTPUTC(CTLVAR, out); typeloc = out - (char *)stackblock(); - USTPUTC(VSNORMAL, out); + STADJUST(1, out); subtype = VSNORMAL; if (c == '{') { c = pgetc_eatbnl(); - if (c == '#') { - c = pgetc_eatbnl(); - if (c == '}') - c = '#'; /* ${#} - same as $# */ - else - subtype = VSLENGTH; /* ${#VAR} */ - } else { - subtype = 0; - } + subtype = 0; } - if (c <= 255 /* not PEOA or PEOF */ && is_name(c)) { + varname: + if (is_name(c)) { /* $[{[#]]NAME[}] */ do { STPUTC(c, out); c = pgetc_eatbnl(); - } while (c <= 255 /* not PEOA or PEOF */ && is_in_name(c)); + } while (is_in_name(c)); } else if (isdigit(c)) { /* $[{[#]]NUM[}] */ do { @@ -11776,19 +11950,31 @@ parsesub: { } while (isdigit(c)); } else if (is_special(c)) { /* $[{[#]][}] */ - USTPUTC(c, out); + int cc = c; + c = pgetc_eatbnl(); + if (!subtype && cc == '#') { + subtype = VSLENGTH; + if (c == '_' || isalnum(c)) + goto varname; + cc = c; + c = pgetc_eatbnl(); + if (cc == '}' || c != '}') { + pungetc(); + subtype = 0; + c = cc; + cc = '#'; + } + } + USTPUTC(cc, out); } else { - badsub: - raise_error_syntax("bad substitution"); + goto badsub; } if (c != '}' && subtype == VSLENGTH) { /* ${#VAR didn't end with } */ goto badsub; } - STPUTC('=', out); - flags = 0; if (subtype == 0) { static const char types[] ALIGN1 = "}-+?="; /* ${VAR...} but not $VAR or ${#VAR} */ @@ -11796,7 +11982,7 @@ parsesub: { switch (c) { case ':': c = pgetc_eatbnl(); -#if ENABLE_ASH_BASH_COMPAT +#if BASH_SUBSTR /* This check is only needed to not misinterpret * ${VAR:-WORD}, ${VAR:+WORD}, ${VAR:=WORD}, ${VAR:?WORD} * constructs. @@ -11804,16 +11990,16 @@ parsesub: { if (!strchr(types, c)) { subtype = VSSUBSTR; pungetc(); - break; /* "goto do_pungetc" is bigger (!) */ + break; /* "goto badsub" is bigger (!) */ } #endif - flags = VSNUL; + subtype = VSNUL; /*FALLTHROUGH*/ default: { const char *p = strchr(types, c); if (p == NULL) - goto badsub; - subtype = p - types + VSNORMAL; + break; + subtype |= p - types + VSNORMAL; break; } case '%': @@ -11822,11 +12008,11 @@ parsesub: { subtype = (c == '#' ? VSTRIMLEFT : VSTRIMRIGHT); c = pgetc_eatbnl(); if (c != cc) - goto do_pungetc; + goto badsub; subtype++; break; } -#if ENABLE_ASH_BASH_COMPAT +#if BASH_PATTERN_SUBST case '/': /* ${v/[/]pattern/repl} */ //TODO: encode pattern and repl separately. @@ -11834,22 +12020,22 @@ parsesub: { subtype = VSREPLACE; c = pgetc_eatbnl(); if (c != '/') - goto do_pungetc; + goto badsub; subtype++; /* VSREPLACEALL */ break; #endif } } else { - do_pungetc: + badsub: pungetc(); } - ((unsigned char *)stackblock())[typeloc] = subtype | flags; + ((unsigned char *)stackblock())[typeloc] = subtype; if (subtype != VSNORMAL) { varnest++; - if (dblquote) { + if (dblquote) dqvarnest++; - } } + STPUTC('=', out); } goto parsesub_return; } @@ -11985,7 +12171,7 @@ parsebackq: { goto parsebackq_newreturn; } -#if ENABLE_SH_MATH_SUPPORT +#if ENABLE_FEATURE_SH_MATH /* * Parse an arithmetic expansion (indicate start of one and set state) */ @@ -12081,7 +12267,7 @@ xxreadtoken(void) p += xxreadtoken_doubles + 1; } else { pungetc(); -#if ENABLE_ASH_BASH_COMPAT +#if BASH_REDIR_OUTPUT if (c == '&' && cc == '>') /* &> */ break; /* return readtoken1(...) */ #endif @@ -12282,16 +12468,21 @@ parseheredoc(void) /* * called by editline -- any expansions to the prompt should be added here. */ -#if ENABLE_ASH_EXPAND_PRMT static const char * expandstr(const char *ps) { union node n; + int saveprompt; /* XXX Fix (char *) cast. It _is_ a bug. ps is variable's value, * and token processing _can_ alter it (delete NULs etc). */ setinputstring((char *)ps); + + saveprompt = doprompt; + doprompt = 0; readtoken1(pgetc(), PSSYNTAX, nullstr, 0); + doprompt = saveprompt; + popfile(); n.narg.type = NARG; @@ -12302,7 +12493,6 @@ expandstr(const char *ps) expandarg(&n, NULL, EXP_QUOTED); return stackblock(); } -#endif /* * Execute a command or commands contained in a string. @@ -12310,6 +12500,10 @@ expandstr(const char *ps) static int evalstring(char *s, int flags) { + struct jmploc *volatile savehandler; + struct jmploc jmploc; + int ex; + union node *n; struct stackmark smark; int status; @@ -12319,6 +12513,19 @@ evalstring(char *s, int flags) setstackmark(&smark); status = 0; + /* On exception inside execution loop, we must popfile(). + * Try interactively: + * readonly a=a + * command eval "a=b" # throws "is read only" error + * "command BLTIN" is not supposed to abort (even in non-interactive use). + * But if we skip popfile(), we hit EOF in eval's string, and exit. + */ + savehandler = exception_handler; + ex = setjmp(jmploc.loc); + if (ex) + goto out; + exception_handler = &jmploc; + while ((n = parsecmd(0)) != NODE_EOF) { int i; @@ -12329,10 +12536,15 @@ evalstring(char *s, int flags) if (evalskip) break; } + out: popstackmark(&smark); popfile(); stunalloc(s); + exception_handler = savehandler; + if (ex) + longjmp(exception_handler->loc, ex); + return status; } @@ -12443,16 +12655,7 @@ find_dot_file(char *name) if (strchr(name, '/')) return name; - /* IIRC standards do not say whether . is to be searched. - * And it is even smaller this way, making it unconditional for now: - */ - if (1) { /* ENABLE_ASH_BASH_COMPAT */ - fullname = name; - goto try_cur_dir; - } - while ((fullname = path_advance(&path, name)) != NULL) { - try_cur_dir: if ((stat(fullname, &statb) == 0) && S_ISREG(statb.st_mode)) { /* * Don't bother freeing here, since it will @@ -12476,6 +12679,7 @@ dotcmd(int argc_ UNUSED_PARAM, char **argv_ UNUSED_PARAM) int status = 0; char *fullname; char **argv; + char *args_need_save; struct strlist *sp; volatile struct shparam saveparam; @@ -12495,7 +12699,8 @@ dotcmd(int argc_ UNUSED_PARAM, char **argv_ UNUSED_PARAM) */ fullname = find_dot_file(argv[0]); argv++; - if (argv[0]) { /* . FILE ARGS, ARGS exist */ + args_need_save = argv[0]; + if (args_need_save) { /* ". FILE ARGS", and ARGS are not empty */ int argc; saveparam = shellparam; shellparam.malloced = 0; @@ -12514,7 +12719,7 @@ dotcmd(int argc_ UNUSED_PARAM, char **argv_ UNUSED_PARAM) status = cmdloop(0); popfile(); - if (argv[0]) { + if (args_need_save) { freeparam(&shellparam); shellparam = saveparam; }; @@ -12785,7 +12990,7 @@ trapcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) exitcode = 0; while (*ap) { signo = get_signum(*ap); - if (signo < 0) { + if (signo < 0 || signo >= NSIG) { /* Mimic bash message exactly */ ash_msg("%s: invalid signal specification", *ap); exitcode = 1; @@ -12795,12 +13000,13 @@ trapcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) if (action) { if (LONE_DASH(action)) action = NULL; - else + else { + if (action[0]) /* not NULL and not "" and not "-" */ + may_have_traps = 1; action = ckstrdup(action); + } } free(trap[signo]); - if (action) - may_have_traps = 1; trap[signo] = action; if (signo != 0) setsignal(signo); @@ -12889,7 +13095,7 @@ exportcmd(int argc UNUSED_PARAM, char **argv) } flag_off = ~flag_off; - /*if (opt_p_not_specified) - bash doesnt check this. Try "export -p NAME" */ + /*if (opt_p_not_specified) - bash doesn't check this. Try "export -p NAME" */ { aptr = argptr; name = *aptr; @@ -12993,7 +13199,7 @@ timescmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) return 0; } -#if ENABLE_SH_MATH_SUPPORT +#if ENABLE_FEATURE_SH_MATH /* * The let builtin. Partially stolen from GNU Bash, the Bourne Again SHell. * Copyright (C) 1987, 1989, 1991 Free Software Foundation, Inc. @@ -13069,6 +13275,7 @@ readcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) /* "read -s" needs to save/restore termios, can't allow ^C * to jump out of it. */ + again: INT_OFF; r = shell_builtin_read(setvar0, argptr, @@ -13081,6 +13288,12 @@ readcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) ); INT_ON; + if ((uintptr_t)r == 1 && errno == EINTR) { + /* to get SIGCHLD: sleep 1 & read x; echo $x */ + if (pending_sig == 0) + goto again; + } + if ((uintptr_t)r > 1) ash_msg_and_raise_error(r); @@ -13181,12 +13394,12 @@ exitshell(void) evalstring(p, 0); /*free(p); - we'll exit soon */ } - flush_stdout_stderr(); out: /* dash wraps setjobctl(0) in "if (setjmp(loc.loc) == 0) {...}". * our setjobctl(0) does not panic if tcsetpgrp fails inside it. */ setjobctl(0); + flush_stdout_stderr(); _exit(status); /* NOTREACHED */ } @@ -13197,7 +13410,9 @@ init(void) /* we will never free this */ basepf.next_to_pgetc = basepf.buf = ckmalloc(IBUFSIZ); - signal(SIGCHLD, SIG_DFL); + sigmode[SIGCHLD - 1] = S_DFL; + setsignal(SIGCHLD); + /* bash re-enables SIGHUP which is SIG_IGNed on entry. * Try: "trap '' HUP; bash; echo RET" and type "kill -HUP $$" */ @@ -13219,9 +13434,11 @@ init(void) setvareq((char*)defoptindvar, VTEXTFIXED); setvar0("PPID", utoa(getppid())); -#if ENABLE_ASH_BASH_COMPAT +#if BASH_SHLVL_VAR p = lookupvar("SHLVL"); setvar("SHLVL", utoa((p ? atoi(p) : 0) + 1), VEXPORT); +#endif +#if BASH_HOSTNAME_VAR if (!lookupvar("HOSTNAME")) { struct utsname uts; uname(&uts); @@ -13246,15 +13463,6 @@ init(void) //usage:#define ash_full_usage "\n\n" //usage: "Unix shell interpreter" -//usage:#if ENABLE_FEATURE_SH_IS_ASH -//usage:# define sh_trivial_usage ash_trivial_usage -//usage:# define sh_full_usage ash_full_usage -//usage:#endif -//usage:#if ENABLE_FEATURE_BASH_IS_ASH -//usage:# define bash_trivial_usage ash_trivial_usage -//usage:# define bash_full_usage ash_full_usage -//usage:#endif - /* * Process the shell command line arguments. */ @@ -13293,7 +13501,7 @@ procargs(char **argv) #if DEBUG == 2 debug = 1; #endif - /* POSIX 1003.2: first arg after -c cmd is $0, remainder $1... */ + /* POSIX 1003.2: first arg after "-c CMD" is $0, remainder $1... */ if (xminusc) { minusc = *xargv++; if (*xargv) @@ -13319,11 +13527,12 @@ procargs(char **argv) } /* - * Read /etc/profile or .profile. + * Read /etc/profile, ~/.profile, $ENV. */ static void read_profile(const char *name) { + name = expandstr(name); if (setinputfile(name, INPUT_PUSH_FILE | INPUT_NOFILE_OK) < 0) return; cmdloop(0); @@ -13333,6 +13542,7 @@ read_profile(const char *name) /* * This routine is called when an error or an interrupt occurs in an * interactive shell and control is returned to the main command loop. + * (In dash, this function is auto-generated by build machinery). */ static void reset(void) @@ -13340,15 +13550,18 @@ reset(void) /* from eval.c: */ evalskip = 0; loopnest = 0; + + /* from expand.c: */ + ifsfree(); + /* from input.c: */ g_parsefile->left_in_buffer = 0; g_parsefile->left_in_line = 0; /* clear input buffer */ popallfiles(); - /* from parser.c: */ - tokpushback = 0; - checkkwd = 0; + /* from redir.c: */ - clearredir(/*drop:*/ 0); + while (redirlist) + popredir(/*drop:*/ 0, /*restore:*/ 0); } #if PROFILE @@ -13366,7 +13579,6 @@ extern int etext(); int ash_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; int ash_main(int argc UNUSED_PARAM, char **argv) { - const char *shinit; volatile smallint state; struct jmploc jmploc; struct stackmark smark; @@ -13395,8 +13607,6 @@ int ash_main(int argc UNUSED_PARAM, char **argv) reset(); e = exception_type; - if (e == EXERROR) - exitstatus = 2; s = state; if (e == EXEXIT || s == 0 || iflag == 0 || shlvl) { exitshell(); @@ -13416,16 +13626,15 @@ int ash_main(int argc UNUSED_PARAM, char **argv) goto state4; } exception_handler = &jmploc; -#if DEBUG - opentrace(); - TRACE(("Shell args: ")); - trace_puts_args(argv); -#endif rootpid = getpid(); init(); setstackmark(&smark); procargs(argv); +#if DEBUG + TRACE(("Shell args: ")); + trace_puts_args(argv); +#endif if (argv[0] && argv[0][0] == '-') isloginsh = 1; @@ -13437,11 +13646,8 @@ int ash_main(int argc UNUSED_PARAM, char **argv) state1: state = 2; hp = lookupvar("HOME"); - if (hp) { - hp = concat_path_file(hp, ".profile"); - read_profile(hp); - free((char*)hp); - } + if (hp) + read_profile("$HOME/.profile"); } state2: state = 3; @@ -13451,11 +13657,11 @@ int ash_main(int argc UNUSED_PARAM, char **argv) #endif iflag ) { - shinit = lookupvar("ENV"); - if (shinit != NULL && *shinit != '\0') { + const char *shinit = lookupvar("ENV"); + if (shinit != NULL && *shinit != '\0') read_profile(shinit); - } } + popstackmark(&smark); state3: state = 4; if (minusc) { @@ -13476,9 +13682,11 @@ int ash_main(int argc UNUSED_PARAM, char **argv) if (!hp) { hp = lookupvar("HOME"); if (hp) { + INT_OFF; hp = concat_path_file(hp, ".ash_history"); setvar0("HISTFILE", hp); free((char*)hp); + INT_ON; hp = lookupvar("HISTFILE"); } }