X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=shell%2Fash.c;h=efb4615db10cbf31ff80371089b5277776535411;hb=2b1559056cf32c42675ecd937796e1455bcb5c2c;hp=c8dd9a5fe3c9cbf289369c78102f923ef61d02df;hpb=08089c7c85a72d5eaf2bed22033098b263f58a3e;p=oweals%2Fbusybox.git diff --git a/shell/ash.c b/shell/ash.c index c8dd9a5fe..efb4615db 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,17 +26,22 @@ //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: depends on ASH || SH_IS_ASH || BASH_IS_ASH //config: help //config: Compile ash for reduced size at the price of speed. //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" @@ -115,7 +50,7 @@ //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,7 +61,7 @@ //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: This option recreates the prompt string from the environment @@ -135,70 +70,70 @@ //config:config ASH_BASH_COMPAT //config: bool "bash-compatible extensions" //config: default y -//config: depends on ASH +//config: depends on ASH || SH_IS_ASH || BASH_IS_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: depends on ASH || SH_IS_ASH || BASH_IS_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: depends on ASH || SH_IS_ASH || BASH_IS_ASH //config: help //config: Enable job control in the ash shell. //config: //config:config ASH_ALIAS //config: bool "Alias support" //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: //config:config ASH_GETOPTS //config: bool "Builtin getopt to parse positional parameters" //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: //config:config ASH_BUILTIN_ECHO //config: bool "Builtin version of 'echo'" //config: default y -//config: depends on ASH +//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH //config: help //config: Enable support for echo builtin in ash. //config: //config:config ASH_BUILTIN_PRINTF //config: bool "Builtin version of 'printf'" //config: default y -//config: depends on ASH +//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH //config: help //config: Enable support for printf builtin in ash. //config: //config:config ASH_BUILTIN_TEST //config: bool "Builtin version of 'test'" //config: default y -//config: depends on ASH +//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH //config: help //config: Enable support for test builtin in ash. //config: //config:config ASH_HELP //config: bool "help builtin" //config: default y -//config: depends on ASH +//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH //config: help //config: Enable help builtin in ash. //config: //config:config ASH_CMDCMD //config: bool "'command' command to override shell builtins" //config: default y -//config: depends on ASH +//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH //config: help //config: Enable support for the ash 'command' builtin, which allows //config: you to run the specified command with the specified arguments, @@ -206,19 +141,103 @@ //config: //config:config ASH_MAIL //config: bool "Check for new mail on interactive shells" -//config: default n -//config: depends on ASH +//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: +//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: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 +/* + * 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 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 */ + +#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. */ @@ -277,8 +296,10 @@ struct jmploc { }; struct globals_misc { - /* pid of main shell */ - int rootpid; + uint8_t exitstatus; /* exit status of last command */ + uint8_t back_exitstatus;/* exit status of backquoted command */ + smallint job_warning; /* user was warned about stopped jobs (can be 2, 1 or 0). */ + int rootpid; /* pid of main shell */ /* shell level: 0 for the main shell, 1 for its children, and so on */ int shlvl; #define rootshell (!shlvl) @@ -293,16 +314,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 */ @@ -355,10 +373,12 @@ struct globals_misc { random_t random_gen; #endif pid_t backgndpid; /* pid of last background process */ - smallint job_warning; /* user was warned about stopped jobs (can be 2, 1 or 0). */ }; extern struct globals_misc *const ash_ptr_to_globals_misc; #define G_misc (*ash_ptr_to_globals_misc) +#define exitstatus (G_misc.exitstatus ) +#define back_exitstatus (G_misc.back_exitstatus ) +#define job_warning (G_misc.job_warning) #define rootpid (G_misc.rootpid ) #define shlvl (G_misc.shlvl ) #define minusc (G_misc.minusc ) @@ -369,6 +389,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 ) @@ -380,7 +401,6 @@ extern struct globals_misc *const ash_ptr_to_globals_misc; #define trap_ptr (G_misc.trap_ptr ) #define random_gen (G_misc.random_gen ) #define backgndpid (G_misc.backgndpid ) -#define job_warning (G_misc.job_warning) #define INIT_G_misc() do { \ (*(struct globals_misc**)&ash_ptr_to_globals_misc) = xzalloc(sizeof(G_misc)); \ barrier(); \ @@ -409,12 +429,11 @@ static void trace_vprintf(const char *fmt, va_list va); /* ============ Utility functions */ -#define xbarrier() do { __asm__ __volatile__ ("": : :"memory"); } while (0) - #define is_name(c) ((c) == '_' || isalpha((unsigned char)(c))) #define is_in_name(c) ((c) == '_' || isalnum((unsigned char)(c))) -static int isdigit_str9(const char *str) +static int +isdigit_str9(const char *str) { int maxlen = 9 + 1; /* max 9 digits: 999999999 */ while (--maxlen && isdigit(*str)) @@ -422,7 +441,8 @@ static int isdigit_str9(const char *str) return (*str == '\0'); } -static const char *var_end(const char *var) +static const char * +var_end(const char *var) { while (*var) if (*var++ == '=') @@ -441,10 +461,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++; \ - xbarrier(); \ + barrier(); \ } while (0) +#endif /* * Called to raise an exception. Since C doesn't include exceptions, we @@ -471,7 +499,7 @@ raise_exception(int e) #endif /* - * Called from trap.c when a SIGINT is received. (If the user specifies + * Called when a SIGINT is received. (If the user specifies * that SIGINT is to be trapped or ignored using the trap builtin, then * this routine is not called.) Suppressint is nonzero when interrupts * are held using the INT_OFF macro. (The test for iflag is just @@ -481,24 +509,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); } - raise_exception(ex_type); + /* bash: ^C even on empty command line sets $? */ + exitstatus = SIGINT + 128; + raise_exception(EXINT); /* NOTREACHED */ } #if DEBUG @@ -511,16 +535,23 @@ raise_interrupt(void) static IF_ASH_OPTIMIZE_FOR_SIZE(inline) void int_on(void) { - xbarrier(); + barrier(); if (--suppress_int == 0 && pending_int) { 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) { - xbarrier(); + barrier(); suppress_int = 0; if (pending_int) raise_interrupt(); @@ -530,7 +561,7 @@ force_int_on(void) #define SAVE_INT(v) ((v) = suppress_int) #define RESTORE_INT(v) do { \ - xbarrier(); \ + barrier(); \ suppress_int = (v); \ if (suppress_int == 0 && pending_int) \ raise_interrupt(); \ @@ -847,13 +878,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 @@ -1217,7 +1243,6 @@ static struct parsefile *g_parsefile = &basepf; /* current input file */ static int startlinno; /* line # where last token started */ static char *commandname; /* currently executing command */ static struct strlist *cmdenviron; /* environment for builtin command */ -static uint8_t exitstatus; /* exit status of last command */ /* ============ Message printing */ @@ -1247,11 +1272,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); @@ -1267,6 +1291,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 */ @@ -1397,7 +1423,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; @@ -1406,7 +1431,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)); \ @@ -1415,7 +1439,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) @@ -1492,12 +1515,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 @@ -1603,10 +1624,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; } @@ -1956,7 +1973,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)]; @@ -1965,7 +1981,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 ) @@ -2026,7 +2041,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 @@ -2140,7 +2155,9 @@ lookupvar(const char *name) return NULL; } -static void reinit_unicode_for_ash(void) +#if ENABLE_UNICODE_SUPPORT +static void +reinit_unicode_for_ash(void) { /* Unicode support should be activated even if LANG is set * _during_ shell execution, not only if it was set when @@ -2155,6 +2172,9 @@ static void reinit_unicode_for_ash(void) reinit_unicode(s); } } +#else +# define reinit_unicode_for_ash() ((void)0) +#endif /* * Search the environment of a builtin command. @@ -2193,6 +2213,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); } @@ -2264,32 +2285,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. */ @@ -2624,7 +2619,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 @@ -2672,7 +2667,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: @@ -2689,13 +2684,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)) { @@ -2704,9 +2693,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: @@ -2761,7 +2756,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)) @@ -3142,7 +3137,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 */ @@ -3169,7 +3175,8 @@ static struct alias **atab; // [ATABSIZE]; static struct alias ** -__lookupalias(const char *name) { +__lookupalias(const char *name) +{ unsigned int hashval; struct alias **app; const char *p; @@ -3351,8 +3358,6 @@ unaliascmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) #endif /* ASH_ALIAS */ -/* ============ jobs.c */ - /* Mode argument to forkshell. Don't change FORK_FG or FORK_BG. */ #define FORK_FG 0 #define FORK_BG 1 @@ -3430,7 +3435,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) { @@ -3438,8 +3450,6 @@ signal_handler(int signo) raise_interrupt(); /* does not return */ } pending_int = 1; - } else { - pending_sig = signo; } } @@ -3497,6 +3507,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) { @@ -3546,10 +3559,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 @@ -3767,12 +3776,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, dont */ 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); @@ -3970,27 +3979,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; @@ -4062,15 +4132,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) @@ -4240,9 +4301,6 @@ waitcmd(int argc UNUSED_PARAM, char **argv) int retval; struct job *jp; - if (pending_sig) - raise_exception(EXSIG); - nextopt(nullstr); retval = 0; @@ -4259,21 +4317,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; } } @@ -4293,8 +4350,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: ; @@ -4302,6 +4362,9 @@ waitcmd(int argc UNUSED_PARAM, char **argv) ret: return retval; + sigout: + retval = 128 + pending_sig; + return retval; } static struct job * @@ -4439,7 +4502,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; @@ -4666,8 +4729,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 */ @@ -4696,25 +4758,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) { @@ -4846,6 +4909,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++; @@ -4879,6 +4943,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) { @@ -5008,8 +5073,7 @@ stoppedjobs(void) } -/* ============ redir.c - * +/* * Code for dealing with input/output redirection. */ @@ -5189,31 +5253,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); } @@ -5226,13 +5291,16 @@ 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) +static int +need_to_remember(struct redirtab *rp, int fd) { int i; @@ -5249,7 +5317,8 @@ static int need_to_remember(struct redirtab *rp, int fd) } /* "hidden" fd is a fd used to read scripts, or a copy of such */ -static int is_hidden_fd(struct redirtab *rp, int fd) +static int +is_hidden_fd(struct redirtab *rp, int fd) { int i; struct parsefile *pf; @@ -5303,7 +5372,6 @@ redirect(union node *redir, int flags) int newfd; int copied_fd2 = -1; - g_nullredirs++; if (!redir) { return; } @@ -5325,8 +5393,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; @@ -5367,11 +5433,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) { @@ -5386,6 +5452,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 */ @@ -5405,10 +5474,10 @@ 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); + dup2_or_raise(newfd, fd); #if ENABLE_ASH_BASH_COMPAT if (!(redir->nfile.type == NTO2 && fd == 2)) #endif @@ -5438,7 +5507,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; @@ -5454,13 +5523,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; } @@ -5469,20 +5537,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) { @@ -5511,7 +5565,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) { @@ -5539,6 +5593,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 */ @@ -5588,7 +5653,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" @@ -5604,6 +5669,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) { @@ -5729,14 +5907,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; @@ -5877,49 +6056,53 @@ struct backcmd { /* result of evalbackcmd */ }; /* These forward decls are needed to use "eval" code for backticks handling: */ -static uint8_t back_exitstatus; /* exit status of backquoted command */ #define EV_EXIT 01 /* exit after evaluating tree */ static 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)); @@ -5983,7 +6166,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. @@ -6065,7 +6248,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' @@ -6101,7 +6284,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++; @@ -6181,7 +6364,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); @@ -6326,7 +6509,6 @@ subevalvar(char *p, char *varname, int strloc, int subtype, char *str; IF_ASH_BASH_COMPAT(char *repl = NULL;) IF_ASH_BASH_COMPAT(int pos, len, orig_len;) - int saveherefd = herefd; int amount, resetloc; IF_ASH_BASH_COMPAT(int workloc;) int zero; @@ -6335,12 +6517,10 @@ subevalvar(char *p, char *varname, int strloc, int subtype, //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; @@ -6603,20 +6783,21 @@ subevalvar(char *p, char *varname, int strloc, int subtype, * ash -c 'echo ${#1#}' name:'1=#' */ static NOINLINE ssize_t -varvalue(char *name, int varflags, int flags, struct strlist *var_str_list) +varvalue(char *name, int varflags, int flags, struct strlist *var_str_list, int *quotedp) { const char *p; int num; int i; ssize_t len = 0; int sep; - int quoted = flags & EXP_QUOTED; + int quoted = *quotedp; int subtype = varflags & VSTYPE; int discard = subtype == VSPLUS || subtype == VSLENGTH; int quotes = (discard ? 0 : (flags & QUOTES_ESC)) | QUOTES_KEEPNUL; - int syntax = quoted ? DQSYNTAX : BASESYNTAX; + int syntax; - sep = quoted ? ((flags & EXP_FULL) << CHAR_BIT) : 0; + sep = (flags & EXP_FULL) << CHAR_BIT; + syntax = quoted ? DQSYNTAX : BASESYNTAX; switch (*name) { case '$': @@ -6650,21 +6831,21 @@ varvalue(char *name, int varflags, int flags, struct strlist *var_str_list) raise_error_syntax("bad substitution"); #endif break; - case '@': { + case '@': + if (quoted && sep) + goto param; + /* fall through */ + case '*': { char **ap; char sepc; - if (quoted && (flags & EXP_FULL)) { - /* note: this is not meant as PEOF value */ - sep = 1 << CHAR_BIT; - goto param; - } - /* fall through */ - case '*': - sep = ifsset() ? (unsigned char)(ifsval()[0]) : ' '; + if (quoted) + sep = 0; + sep |= ifsset() ? ifsval()[0] : ' '; param: - ap = shellparam.p; sepc = sep; + *quotedp = !sepc; + ap = shellparam.p; if (!ap) return -1; while ((p = *ap++) != NULL) { @@ -6676,7 +6857,7 @@ varvalue(char *name, int varflags, int flags, struct strlist *var_str_list) } } break; - } /* case '@' and '*' */ + } /* case '*' */ case '0': case '1': case '2': @@ -6748,7 +6929,7 @@ varvalue(char *name, int varflags, int flags, struct strlist *var_str_list) * input string. */ static char * -evalvar(char *p, int flags, struct strlist *var_str_list) +evalvar(char *p, int flag, struct strlist *var_str_list) { char varflags; char subtype; @@ -6761,14 +6942,18 @@ evalvar(char *p, int flags, struct strlist *var_str_list) varflags = (unsigned char) *p++; subtype = varflags & VSTYPE; - quoted = flags & EXP_QUOTED; + + if (!subtype) + raise_error_syntax("bad substitution"); + + quoted = flag & EXP_QUOTED; var = p; easy = (!quoted || (*var == '@' && shellparam.nparam)); startloc = expdest - (char *)stackblock(); p = strchr(p, '=') + 1; //TODO: use var_end(p)? again: - varlen = varvalue(var, varflags, flags, var_str_list); + varlen = varvalue(var, varflags, flag, var_str_list, "ed); if (varflags & VSNUL) varlen--; @@ -6782,36 +6967,27 @@ evalvar(char *p, int flags, struct strlist *var_str_list) if (varlen < 0) { argstr( p, - flags | EXP_TILDE | EXP_WORD, + flag | EXP_TILDE | EXP_WORD, var_str_list ); goto end; } - if (easy) - goto record; - goto end; + goto record; } if (subtype == VSASSIGN || subtype == VSQUESTION) { - if (varlen < 0) { - if (subevalvar(p, var, /* strloc: */ 0, - subtype, startloc, varflags, - /* quotes: */ flags & ~QUOTES_ESC, - var_str_list) - ) { - varflags &= ~VSNUL; - /* - * Remove any recorded regions beyond - * start of variable - */ - removerecordregions(startloc); - goto again; - } - goto end; - } - if (easy) + if (varlen >= 0) goto record; - goto end; + + subevalvar(p, var, 0, subtype, startloc, varflags, + flag & ~QUOTES_ESC, var_str_list); + varflags &= ~VSNUL; + /* + * Remove any recorded regions beyond + * start of variable + */ + removerecordregions(startloc); + goto again; } if (varlen < 0 && uflag) @@ -6823,8 +6999,10 @@ evalvar(char *p, int flags, struct strlist *var_str_list) } if (subtype == VSNORMAL) { - if (easy) - goto record; + record: + if (!easy) + goto end; + recordregion(startloc, expdest - (char *)stackblock(), quoted); goto end; } @@ -6853,148 +7031,37 @@ evalvar(char *p, int flags, struct strlist *var_str_list) STPUTC('\0', expdest); patloc = expdest - (char *)stackblock(); if (NULL == subevalvar(p, /* varname: */ NULL, patloc, subtype, - startloc, varflags, flags, var_str_list)) { + 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); - record: - recordregion(startloc, expdest - (char *)stackblock(), quoted); - } - - 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; -} - -static void -ifsfree(void) -{ - struct ifsregion *p; - - INT_OFF; - p = ifsfirst.next; - do { - struct ifsregion *ifsp; - ifsp = p->next; - free(p); - p = ifsp; - } while (p); - ifslastp = NULL; - ifsfirst.next = NULL; - INT_ON; + 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; } /* @@ -7036,29 +7103,59 @@ expandmeta(struct strlist *str /*, int flag*/) if (fflag) goto nometa; + + /* Avoid glob() (and thus, stat() et al) for words like "echo" */ + p = str->text; + while (*p) { + if (*p == '*') + goto need_glob; + if (*p == '?') + goto need_glob; + if (*p == '[') + goto need_glob; + p++; + } + goto nometa; + + need_glob: INT_OFF; p = preglob(str->text, RMESCAPE_ALLOC | RMESCAPE_HEAP); - /* - * GLOB_NOMAGIC (GNU): if no *?[ chars in pattern, return it even if no match - * TODO?: GLOB_NOCHECK: if no match, return unchanged pattern (sans \* escapes?) - */ - i = glob(p, GLOB_NOMAGIC, NULL, &pglob); +// GLOB_NOMAGIC (GNU): if no *?[ chars in pattern, return it even if no match +// GLOB_NOCHECK: if no match, return unchanged pattern (sans \* escapes?) +// +// glibc 2.24.90 glob(GLOB_NOMAGIC) does not remove backslashes used for escaping: +// if you pass it "file\?", it returns "file\?", not "file?", if no match. +// 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. +// +// Worse, globbing of "file\?" in a directory with two files, "file?" and "file\?", +// returns "file\?" - which is WRONG: "file\?" pattern matches "file?" file. +// Without GLOB_NOMAGIC, this works correctly ("file?" is returned as a match). +// With GLOB_NOMAGIC | GLOB_NOCHECK, this also works correctly. +// i = glob(p, GLOB_NOMAGIC | GLOB_NOCHECK, NULL, &pglob); +// i = glob(p, GLOB_NOMAGIC, NULL, &pglob); + i = glob(p, 0, NULL, &pglob); + //bb_error_msg("glob('%s'):%d '%s'...", p, i, pglob.gl_pathv ? pglob.gl_pathv[0] : "-"); if (p != str->text) free(p); switch (i) { case 0: +#if 0 // glibc 2.24.90 bug? Patterns like "*/file", when match, don't set GLOB_MAGCHAR /* GLOB_MAGCHAR is set if *?[ chars were seen (GNU) */ if (!(pglob.gl_flags & GLOB_MAGCHAR)) goto nometa2; +#endif addglob(&pglob); globfree(&pglob); INT_ON; break; case GLOB_NOMATCH: -nometa2: + //nometa2: globfree(&pglob); INT_ON; -nometa: + nometa: *exparg.lastp = str; rmescapes(str->text, 0); exparg.lastp = &str->next; @@ -7320,15 +7417,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)); @@ -7351,13 +7447,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(); } /* @@ -7366,7 +7463,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()); } @@ -7392,10 +7488,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; @@ -7496,13 +7592,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 @@ -7519,19 +7609,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; } } @@ -7539,6 +7623,7 @@ 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 @@ -7550,7 +7635,6 @@ 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 ENABLE_FEATURE_SH_STANDALONE @@ -7594,7 +7678,7 @@ 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")); + ash_msg_and_raise(EXEXIT, "%s: %s", argv[0], errmsg(e, "not found")); /* NOTREACHED */ } @@ -7866,49 +7950,86 @@ enum { }; typedef smallint token_id_t; -/* first char is indicating which tokens mark the end of a list */ +/* Nth bit indicates if token marks the end of a list */ +enum { + tokendlist = 0 + /* 0 */ | (1u << TEOF) + /* 1 */ | (0u << TNL) + /* 2 */ | (0u << TREDIR) + /* 3 */ | (0u << TWORD) + /* 4 */ | (0u << TSEMI) + /* 5 */ | (0u << TBACKGND) + /* 6 */ | (0u << TAND) + /* 7 */ | (0u << TOR) + /* 8 */ | (0u << TPIPE) + /* 9 */ | (0u << TLP) + /* 10 */ | (1u << TRP) + /* 11 */ | (1u << TENDCASE) + /* 12 */ | (1u << TENDBQUOTE) + /* 13 */ | (0u << TNOT) + /* 14 */ | (0u << TCASE) + /* 15 */ | (1u << TDO) + /* 16 */ | (1u << TDONE) + /* 17 */ | (1u << TELIF) + /* 18 */ | (1u << TELSE) + /* 19 */ | (1u << TESAC) + /* 20 */ | (1u << TFI) + /* 21 */ | (0u << TFOR) +#if ENABLE_ASH_BASH_COMPAT + /* 22 */ | (0u << TFUNCTION) +#endif + /* 23 */ | (0u << TIF) + /* 24 */ | (0u << TIN) + /* 25 */ | (1u << TTHEN) + /* 26 */ | (0u << TUNTIL) + /* 27 */ | (0u << TWHILE) + /* 28 */ | (0u << TBEGIN) + /* 29 */ | (1u << TEND) + , /* thus far 29 bits used */ +}; + static const char *const tokname_array[] = { - "\1end of file", - "\0newline", - "\0redirection", - "\0word", - "\0;", - "\0&", - "\0&&", - "\0||", - "\0|", - "\0(", - "\1)", - "\1;;", - "\1`", + "end of file", + "newline", + "redirection", + "word", + ";", + "&", + "&&", + "||", + "|", + "(", + ")", + ";;", + "`", #define KWDOFFSET 13 /* the following are keywords */ - "\0!", - "\0case", - "\1do", - "\1done", - "\1elif", - "\1else", - "\1esac", - "\1fi", - "\0for", + "!", + "case", + "do", + "done", + "elif", + "else", + "esac", + "fi", + "for", #if ENABLE_ASH_BASH_COMPAT - "\0function", -#endif - "\0if", - "\0in", - "\1then", - "\0until", - "\0while", - "\0{", - "\1}", + "function", +#endif + "if", + "in", + "then", + "until", + "while", + "{", + "}", }; /* Wrapper around strcmp for qsort/bsearch/... */ static int pstrcmp(const void *a, const void *b) { - return strcmp((char*) a, (*(char**) b) + 1); + return strcmp((char*)a, *(char**)b); } static const char *const * @@ -8109,12 +8230,10 @@ commandcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) #endif -/* ============ eval.c */ - -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 */ @@ -8152,71 +8271,72 @@ 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 @@ -8226,33 +8346,31 @@ calcsize(union node *n) 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 *); @@ -8376,15 +8494,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; } @@ -8392,14 +8508,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; } @@ -8407,14 +8523,13 @@ defun(char *name, union node *func) #define SKIPBREAK (1 << 0) #define SKIPCONT (1 << 1) #define SKIPFUNC (1 << 2) -#define SKIPEVAL (1 << 4) static smallint evalskip; /* set to SKIPxxx if we are skipping commands */ static int skipcount; /* number of levels to skip */ static int funcnest; /* depth of function calls */ static int loopnest; /* current loop nesting level */ /* Forward decl way out to parsing code - dotrap needs it */ -static int evalstring(char *s, int mask); +static int evalstring(char *s, int flags); /* Called to execute a trap. * Single callsite - at the end of evaltree(). @@ -8423,43 +8538,46 @@ static int evalstring(char *s, int mask); * Perhaps we should avoid entering new trap handlers * while we are executing a trap handler. [is it a TODO?] */ -static int +static void dotrap(void) { uint8_t *g; int sig; - uint8_t savestatus; + uint8_t last_status; + + if (!pending_sig) + return; - savestatus = exitstatus; + last_status = exitstatus; pending_sig = 0; - xbarrier(); + barrier(); TRACE(("dotrap entered\n")); for (sig = 1, g = gotsig; sig < NSIG; sig++, g++) { - char *t; + char *p; - if (*g == 0) + if (!*g) continue; - t = trap[sig]; + + if (evalskip) { + pending_sig = sig; + break; + } + + p = trap[sig]; /* non-trapped SIGINT is handled separately by raise_interrupt, * don't upset it by resetting gotsig[SIGINT-1] */ - if (sig == SIGINT && !t) + if (sig == SIGINT && !p) continue; - TRACE(("sig %d is active, will run handler '%s'\n", sig, t)); + TRACE(("sig %d is active, will run handler '%s'\n", sig, p)); *g = 0; - if (!t) + if (!p) continue; - evalstring(t, SKIPEVAL); - exitstatus = savestatus; - if (evalskip) { - TRACE(("dotrap returns %d\n", evalskip)); - return evalskip; - } + evalstring(p, 0); } - - TRACE(("dotrap returns 0\n")); - return 0; + exitstatus = last_status; + TRACE(("dotrap returns\n")); } /* forward declarations - evaluation is fairly recursive business... */ @@ -8470,7 +8588,7 @@ static int evalsubshell(union node *, int); static void expredir(union node *); static int evalpipe(union node *, int); static int evalcommand(union node *, int); -static int evalbltin(const struct builtincmd *, int, char **); +static int evalbltin(const struct builtincmd *, int, char **, int); static void prehash(union node *); /* @@ -8480,38 +8598,17 @@ 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)); - 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); - } - } + dotrap(); switch (n->type) { default: @@ -8529,7 +8626,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; @@ -8545,11 +8643,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; @@ -8571,7 +8667,7 @@ evaltree(union node *n, int flags) n->nbinary.ch1, (flags | ((is_or >> 1) - 1)) & EV_TESTED ); - if (!status == is_or || evalskip) + if ((!status) == is_or || evalskip) break; n = n->nbinary.ch2; evaln: @@ -8595,7 +8691,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. */ @@ -8604,27 +8700,18 @@ 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". */ - if (pending_sig && dotrap()) - goto exexit; - if (checkexit & status) - evalskip |= SKIPEVAL; + dotrap(); - if (flags & EV_EXIT) { - exexit: + if (checkexit & status) + raise_exception(EXEXIT); + if (flags & EV_EXIT) raise_exception(EXEXIT); - } - RESTORE_INT(int_level); TRACE(("leaving evaltree (no interrupts)\n")); - return exitstatus; } @@ -8633,37 +8720,51 @@ static #endif int evaltreenr(union node *, int) __attribute__ ((alias("evaltree"),__noreturn__)); +static int +skiploop(void) +{ + int skip = evalskip; + + switch (skip) { + case 0: + break; + case SKIPBREAK: + case SKIPCONT: + if (--skipcount <= 0) { + evalskip = 0; + break; + } + skip = SKIPBREAK; + break; + } + return skip; +} + static int evalloop(union node *n, int flags) { + int skip; int status; loopnest++; status = 0; flags &= EV_TESTED; - for (;;) { + do { int i; i = evaltree(n->nbinary.ch1, EV_TESTED); - if (evalskip) { - skipping: - if (evalskip == SKIPCONT && --skipcount <= 0) { - evalskip = 0; - continue; - } - if (evalskip == SKIPBREAK && --skipcount <= 0) - evalskip = 0; - break; - } + skip = skiploop(); + if (skip == SKIPFUNC) + status = i; + if (skip) + continue; if (n->type != NWHILE) i = !i; if (i != 0) break; status = evaltree(n->nbinary.ch2, flags); - if (evalskip) - goto skipping; - } - exitstatus = status; + skip = skiploop(); + } while (!(skip & ~SKIPCONT)); loopnest--; return status; @@ -8683,9 +8784,6 @@ evalfor(union node *n, int flags) arglist.lastp = &arglist.list; for (argp = n->nfor.args; argp; argp = argp->narg.next) { expandarg(argp, &arglist, EXP_FULL | EXP_TILDE); - /* XXX */ - if (evalskip) - goto out; } *arglist.lastp = NULL; @@ -8694,18 +8792,10 @@ evalfor(union node *n, int flags) for (sp = arglist.list; sp; sp = sp->next) { setvar0(n->nfor.var, sp->text); status = evaltree(n->nfor.body, flags); - if (evalskip) { - if (evalskip == SKIPCONT && --skipcount <= 0) { - evalskip = 0; - continue; - } - if (evalskip == SKIPBREAK && --skipcount <= 0) - evalskip = 0; + if (skiploop() & ~SKIPCONT) break; - } } loopnest--; - out: popstackmark(&smark); return status; @@ -8770,6 +8860,7 @@ evalsubshell(union node *n, int flags) evaltreenr(n->nredir.n, flags); /* never returns */ } + /* parent */ status = 0; if (!backgnd) status = waitforjob(jp); @@ -8875,6 +8966,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]); @@ -8890,6 +8982,7 @@ evalpipe(union node *n, int flags) evaltreenr(lp->n, flags); /* never returns */ } + /* parent */ if (prevfd >= 0) close(prevfd); prevfd = pip[0]; @@ -9020,7 +9113,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--; @@ -9063,7 +9156,7 @@ mklocal(char *name) /* else: * it's a duplicate "local VAR" declaration, do nothing */ - return; + goto ret; } lvp = lvp->next; } @@ -9102,6 +9195,7 @@ mklocal(char *name) lvp->vp = vp; lvp->next = localvars; localvars = lvp; + ret: INT_ON; } @@ -9177,7 +9271,7 @@ returncmd(int argc UNUSED_PARAM, char **argv) /* Forward declarations for builtintab[] */ static int breakcmd(int, char **) FAST_FUNC; static int dotcmd(int, char **) FAST_FUNC; -static int evalcmd(int, char **) FAST_FUNC; +static int evalcmd(int, char **, int) FAST_FUNC; static int exitcmd(int, char **) FAST_FUNC; static int exportcmd(int, char **) FAST_FUNC; #if ENABLE_ASH_GETOPTS @@ -9189,7 +9283,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; @@ -9227,9 +9321,9 @@ static const struct builtincmd builtintab[] = { { BUILTIN_SPEC_REG ":" , truecmd }, #if ENABLE_ASH_BUILTIN_TEST { BUILTIN_REGULAR "[" , testcmd }, -#if ENABLE_ASH_BASH_COMPAT +# if ENABLE_ASH_BASH_COMPAT { BUILTIN_REGULAR "[[" , testcmd }, -#endif +# endif #endif #if ENABLE_ASH_ALIAS { BUILTIN_REG_ASSG "alias" , aliascmd }, @@ -9247,7 +9341,7 @@ static const struct builtincmd builtintab[] = { #if ENABLE_ASH_BUILTIN_ECHO { BUILTIN_REGULAR "echo" , echocmd }, #endif - { BUILTIN_SPEC_REG "eval" , evalcmd }, + { BUILTIN_SPEC_REG "eval" , NULL }, /*evalcmd() has a differing prototype*/ { BUILTIN_SPEC_REG "exec" , execcmd }, { BUILTIN_SPEC_REG "exit" , exitcmd }, { BUILTIN_SPEC_REG_ASSG "export" , exportcmd }, @@ -9269,7 +9363,7 @@ 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 }, @@ -9320,6 +9414,11 @@ static const struct builtincmd builtintab[] = { /* * Search the table of builtin commands. */ +static int +pstrcmp1(const void *a, const void *b) +{ + return strcmp((char*)a, *(char**)b + 1); +} static struct builtincmd * find_builtin(const char *name) { @@ -9327,7 +9426,7 @@ find_builtin(const char *name) bp = bsearch( name, builtintab, ARRAY_SIZE(builtintab), sizeof(builtintab[0]), - pstrcmp + pstrcmp1 ); return bp; } @@ -9405,7 +9504,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; @@ -9499,11 +9600,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; } @@ -9522,7 +9625,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 @@ -9567,22 +9670,13 @@ evalcommand(union node *cmd, int flags) * to reap the zombie and make kill detect that it's gone: */ dowait(DOWAIT_NONBLOCK, NULL); - if (evalbltin(cmdentry.u.cmd, argc, argv)) { - int exit_status; - int i = exception_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 (evalbltin(cmdentry.u.cmd, argc, argv, flags)) { + if (exception_type == EXERROR && spclbltin <= 0) { + FORCE_INT_ON; + goto readstatus; } - FORCE_INT_ON; + raise: + longjmp(exception_handler->loc, 1); } goto readstatus; @@ -9598,7 +9692,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... @@ -9612,11 +9707,12 @@ evalcommand(union node *cmd, int flags) } static int -evalbltin(const struct builtincmd *cmd, int argc, char **argv) +evalbltin(const struct builtincmd *cmd, int argc, char **argv, int flags) { char *volatile savecmdname; struct jmploc *volatile savehandler; struct jmploc jmploc; + int status; int i; savecmdname = commandname; @@ -9628,10 +9724,14 @@ evalbltin(const struct builtincmd *cmd, int argc, char **argv) commandname = argv[0]; argptr = argv + 1; optptr = NULL; /* initialize nextopt */ - exitstatus = (*cmd->builtin)(argc, argv); + if (cmd == EVALCMD) + status = evalcmd(argc, argv, flags); + else + status = (*cmd->builtin)(argc, argv); flush_stdout_stderr(); + status |= ferror(stdout); + exitstatus = status; cmddone: - exitstatus |= ferror(stdout); clearerr(stdout); commandname = savecmdname; exception_handler = savehandler; @@ -9695,8 +9795,7 @@ breakcmd(int argc UNUSED_PARAM, char **argv) } -/* ============ input.c - * +/* * This implements the input routines used by the parser. */ @@ -9811,13 +9910,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) { @@ -9964,6 +10066,19 @@ preadbuffer(void) return (unsigned char)*g_parsefile->next_to_pgetc++; } +static void +nlprompt(void) +{ + g_parsefile->linno++; + setprompt_if(doprompt, 2); +} +static void +nlnoprompt(void) +{ + g_parsefile->linno++; + needprompt = doprompt; +} + static int pgetc(void) { @@ -9977,7 +10092,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(); @@ -10052,8 +10167,7 @@ pgetc_eatbnl(void) break; } - g_parsefile->linno++; - setprompt_if(doprompt, 2); + nlprompt(); } return c; @@ -10082,6 +10196,9 @@ popfile(void) { struct parsefile *pf = g_parsefile; + if (pf == &basepf) + return; + INT_OFF; if (pf->pf_fd >= 0) close(pf->pf_fd); @@ -10124,7 +10241,6 @@ closescript(void) static void setinputfd(int fd, int push) { - close_on_exec_on(fd); if (push) { pushfile(); g_parsefile->buf = NULL; @@ -10145,22 +10261,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; @@ -10183,8 +10296,7 @@ setinputstring(char *string) } -/* ============ mail.c - * +/* * Routines to check for mail. */ @@ -10502,25 +10614,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; @@ -10541,7 +10653,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"); @@ -10558,7 +10670,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); @@ -10570,23 +10682,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; } @@ -10605,20 +10714,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 */ @@ -10645,8 +10753,8 @@ static const char * tokname(char *buf, int tok) { if (tok < TSEMI) - return tokname_array[tok] + 1; - sprintf(buf, "\"%s\"", tokname_array[tok] + 1); + return tokname_array[tok]; + sprintf(buf, "\"%s\"", tokname_array[tok]); return buf; } @@ -10703,7 +10811,7 @@ list(int nlflag) } checkkwd = CHKNL | CHKKWD | CHKALIAS; - if (nlflag == 2 && tokname_array[peektoken()][0]) + if (nlflag == 2 && ((1 << peektoken()) & tokendlist)) return n1; nlflag |= 2; @@ -11085,7 +11193,7 @@ parse_command(void) n1->nbinary.ch1 = list(0); got = readtoken(); if (got != TDO) { - TRACE(("expecting DO got '%s' %s\n", tokname_array[got] + 1, + TRACE(("expecting DO got '%s' %s\n", tokname_array[got], got == TWORD ? wordtext : "")); raise_error_unexpected_syntax(TDO); } @@ -11231,7 +11339,8 @@ parse_command(void) } #if ENABLE_ASH_BASH_COMPAT -static int decode_dollar_squote(void) +static int +decode_dollar_squote(void) { static const char C_escapes[] ALIGN1 = "nrbtfav""x\\01234567"; int c, cnt; @@ -11305,13 +11414,13 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) smallint quotef; smallint dblquote; smallint oldstyle; - smallint prevsyntax; /* syntax before arithmetic */ + IF_FEATURE_SH_MATH(smallint prevsyntax;) /* syntax before arithmetic */ #if ENABLE_ASH_EXPAND_PRMT smallint pssyntax; /* we are expanding a prompt string */ #endif int varnest; /* levels of variables expansion */ - int arinest; /* levels of arithmetic expansion */ - int parenlevel; /* levels of parens in arithmetic */ + 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;) @@ -11319,7 +11428,7 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) startlinno = g_parsefile->linno; bqlist = NULL; quotef = 0; - prevsyntax = 0; + IF_FEATURE_SH_MATH(prevsyntax = 0;) #if ENABLE_ASH_EXPAND_PRMT pssyntax = (syntax == PSSYNTAX); if (pssyntax) @@ -11327,8 +11436,8 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) #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); @@ -11342,8 +11451,7 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) if (syntax == BASESYNTAX) goto endword; /* exit outer loop */ USTPUTC(c, out); - g_parsefile->linno++; - setprompt_if(doprompt, 2); + nlprompt(); c = pgetc(); goto loop; /* continue outer loop */ case CWORD: @@ -11377,7 +11485,7 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) USTPUTC('\\', out); pungetc(); } else if (c == '\n') { - setprompt_if(doprompt, 2); + nlprompt(); } else { #if ENABLE_ASH_EXPAND_PRMT if (c == '$' && pssyntax) { @@ -11436,7 +11544,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); @@ -11487,7 +11595,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 @@ -11542,14 +11650,19 @@ 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; - g_parsefile->linno++; - needprompt = doprompt; + nlnoprompt(); } else { pushstring(line, NULL); } @@ -11646,7 +11759,6 @@ parseredir: { parsesub: { unsigned char subtype; int typeloc; - int flags; c = pgetc_eatbnl(); if (c > 255 /* PEOA or PEOF */ @@ -11662,7 +11774,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"); @@ -11675,26 +11787,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 { @@ -11703,19 +11808,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} */ @@ -11731,16 +11848,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 '%': @@ -11749,7 +11866,7 @@ parsesub: { subtype = (c == '#' ? VSTRIMLEFT : VSTRIMRIGHT); c = pgetc_eatbnl(); if (c != cc) - goto do_pungetc; + goto badsub; subtype++; break; } @@ -11761,22 +11878,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; } @@ -11831,8 +11948,7 @@ parsebackq: { case '\\': pc = pgetc(); if (pc == '\n') { - g_parsefile->linno++; - setprompt_if(doprompt, 2); + nlprompt(); /* * If eating a newline, avoid putting * the newline into the new character @@ -11857,8 +11973,7 @@ parsebackq: { raise_error_syntax("EOF in backquote substitution"); case '\n': - g_parsefile->linno++; - needprompt = doprompt; + nlnoprompt(); break; default: @@ -11914,7 +12029,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) */ @@ -11990,16 +12105,14 @@ xxreadtoken(void) pungetc(); break; /* return readtoken1(...) */ } - startlinno = ++g_parsefile->linno; - setprompt_if(doprompt, 2); + nlprompt(); } else { const char *p; p = xxreadtoken_chars + sizeof(xxreadtoken_chars) - 1; if (c != PEOF) { if (c == '\n') { - g_parsefile->linno++; - needprompt = doprompt; + nlnoprompt(); } p = strchr(xxreadtoken_chars, c); @@ -12052,15 +12165,13 @@ xxreadtoken(void) continue; case '\\': if (pgetc() == '\n') { - startlinno = ++g_parsefile->linno; - setprompt_if(doprompt, 2); + nlprompt(); continue; } pungetc(); goto breakloop; case '\n': - g_parsefile->linno++; - needprompt = doprompt; + nlnoprompt(); RETURN(TNL); case PEOF: RETURN(TEOF); @@ -12131,7 +12242,7 @@ readtoken(void) pp = findkwd(wordtext); if (pp) { lasttoken = t = pp - tokname_array; - TRACE(("keyword '%s' recognized\n", tokname_array[t] + 1)); + TRACE(("keyword '%s' recognized\n", tokname_array[t])); goto out; } } @@ -12152,9 +12263,9 @@ readtoken(void) checkkwd = 0; #if DEBUG if (!alreadyseen) - TRACE(("token '%s' %s\n", tokname_array[t] + 1, t == TWORD ? wordtext : "")); + TRACE(("token '%s' %s\n", tokname_array[t], t == TWORD ? wordtext : "")); else - TRACE(("reread token '%s' %s\n", tokname_array[t] + 1, t == TWORD ? wordtext : "")); + TRACE(("reread token '%s' %s\n", tokname_array[t], t == TWORD ? wordtext : "")); #endif return t; } @@ -12220,11 +12331,17 @@ 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; @@ -12241,8 +12358,12 @@ expandstr(const char *ps) * Execute a command or commands contained in a string. */ static int -evalstring(char *s, int mask) +evalstring(char *s, int flags) { + struct jmploc *volatile savehandler; + struct jmploc jmploc; + int ex; + union node *n; struct stackmark smark; int status; @@ -12252,21 +12373,38 @@ evalstring(char *s, int mask) 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; - i = evaltree(n, 0); + i = evaltree(n, flags); if (n) status = i; popstackmark(&smark); if (evalskip) break; } + out: popstackmark(&smark); popfile(); stunalloc(s); - evalskip &= mask; + exception_handler = savehandler; + if (ex) + longjmp(exception_handler->loc, ex); + return status; } @@ -12274,7 +12412,7 @@ evalstring(char *s, int mask) * The eval command. */ static int FAST_FUNC -evalcmd(int argc UNUSED_PARAM, char **argv) +evalcmd(int argc UNUSED_PARAM, char **argv, int flags) { char *p; char *concat; @@ -12294,7 +12432,7 @@ evalcmd(int argc UNUSED_PARAM, char **argv) STPUTC('\0', concat); p = grabstackstr(concat); } - return evalstring(p, ~SKIPEVAL); + return evalstring(p, flags & EV_TESTED); } return 0; } @@ -12356,7 +12494,7 @@ cmdloop(int top) if (skip) { evalskip &= ~SKIPFUNC; - return skip & SKIPEVAL; + break; } } return status; @@ -12404,32 +12542,38 @@ find_dot_file(char *name) } static int FAST_FUNC -dotcmd(int argc, char **argv) +dotcmd(int argc_ UNUSED_PARAM, char **argv_ UNUSED_PARAM) { + /* "false; . empty_file; echo $?" should print 0, not 1: */ + int status = 0; char *fullname; + char **argv; struct strlist *sp; volatile struct shparam saveparam; for (sp = cmdenviron; sp; sp = sp->next) setvareq(ckstrdup(sp->text), VSTRFIXED | VTEXTFIXED); - if (!argv[1]) { + nextopt(nullstr); /* handle possible "--" */ + argv = argptr; + + if (!argv[0]) { /* bash says: "bash: .: filename argument required" */ return 2; /* bash compat */ } - /* "false; . empty_file; echo $?" should print 0, not 1: */ - exitstatus = 0; - /* This aborts if file isn't found, which is POSIXly correct. * bash returns exitcode 1 instead. */ - fullname = find_dot_file(argv[1]); - argv += 2; - argc -= 2; - if (argc) { /* argc > 0, argv[0] != NULL */ + fullname = find_dot_file(argv[0]); + argv++; + if (argv[0]) { /* . FILE ARGS, ARGS exist */ + int argc; saveparam = shellparam; shellparam.malloced = 0; + argc = 1; + while (argv[argc]) + argc++; shellparam.nparam = argc; shellparam.p = argv; }; @@ -12439,15 +12583,15 @@ dotcmd(int argc, char **argv) */ setinputfile(fullname, INPUT_PUSH_FILE); commandname = fullname; - cmdloop(0); + status = cmdloop(0); popfile(); - if (argc) { + if (argv[0]) { freeparam(&shellparam); shellparam = saveparam; }; - return exitstatus; + return status; } static int FAST_FUNC @@ -12669,8 +12813,6 @@ find_command(char *name, struct cmdentry *entry, int act, const char *path) } -/* ============ trap.c */ - /* * The trap builtin. */ @@ -12725,12 +12867,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); @@ -12776,7 +12919,7 @@ helpcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) } } # endif - out1fmt("\n\n"); + newline_and_flush(stdout); return EXIT_SUCCESS; } #endif @@ -12923,7 +13066,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. @@ -13096,15 +13239,10 @@ exitshell(void) #if ENABLE_FEATURE_EDITING_SAVE_ON_EXIT save_history(line_input_state); #endif - status = exitstatus; TRACE(("pid %d, exitshell(%d)\n", getpid(), status)); if (setjmp(loc.loc)) { if (exception_type == EXEXIT) -/* dash bug: it just does _exit(exitstatus) here - * but we have to do setjobctl(0) first! - * (bug is still not fixed in dash-0.5.3 - if you run dash - * under Midnight Commander, on exit from dash MC is backgrounded) */ status = exitstatus; goto out; } @@ -13112,12 +13250,16 @@ exitshell(void) p = trap[0]; if (p) { trap[0] = NULL; + evalskip = 0; evalstring(p, 0); - free(p); + /*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 */ } @@ -13125,18 +13267,17 @@ exitshell(void) static void init(void) { - /* from input.c: */ /* we will never free this */ basepf.next_to_pgetc = basepf.buf = ckmalloc(IBUFSIZ); - /* from trap.c: */ - 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 $$" */ signal(SIGHUP, SIG_DFL); - /* from var.c: */ { char **envp; const char *p; @@ -13180,15 +13321,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. */ @@ -13253,24 +13385,22 @@ procargs(char **argv) } /* - * Read /etc/profile or .profile. + * Read /etc/profile, ~/.profile, $ENV. */ static void read_profile(const char *name) { - int skip; - + name = expandstr(name); if (setinputfile(name, INPUT_PUSH_FILE | INPUT_NOFILE_OK) < 0) return; - skip = cmdloop(0); + cmdloop(0); popfile(); - if (skip) - exitshell(); } /* * This routine is called when an error or an interrupt occurs in an * interactive shell and control is returned to the main command loop. + * (In dash, this function is auto-generated by build machinery). */ static void reset(void) @@ -13278,15 +13408,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 @@ -13304,7 +13437,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; @@ -13333,8 +13465,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(); @@ -13354,16 +13484,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; @@ -13375,11 +13504,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; @@ -13389,11 +13515,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) {