*
* 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 <setjmp.h>
-#include <fnmatch.h>
-#include <sys/times.h>
-#include <sys/utsname.h> /* 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 <glob.h>
-#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
//config: shell (by Herbert Xu), which was created by porting the 'ash' shell
//config: (written by Kenneth Almquist) from NetBSD.
//config:
+//config:# ash options
+//config:# note: Don't remove !NOMMU part in the next line; it would break
+//config:# menuconfig's indenting.
+//config:if !NOMMU && (ASH || SH_IS_ASH || BASH_IS_ASH)
+//config:
//config:config ASH_OPTIMIZE_FOR_SIZE
//config: bool "Optimize for size instead of speed"
//config: default y
-//config: depends on ASH
-//config: help
-//config: Compile ash for reduced size at the price of speed.
+//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH
//config:
//config:config ASH_INTERNAL_GLOB
//config: bool "Use internal glob() implementation"
-//config: default n
-//config: depends on ASH
+//config: default y # Y is bigger, but because of uclibc glob() bug, let Y be default for now
+//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH
//config: help
//config: Do not use glob() function from libc, use internal implementation.
//config: Use this if you are getting "glob.h: No such file or directory"
//config: or similar build errors.
+//config: Note that as of now (2017-01), uclibc and musl glob() both have bugs
+//config: which would break ash if you select N here.
+//config:
+//config:config ASH_BASH_COMPAT
+//config: bool "bash-compatible extensions"
+//config: default y
+//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH
+//config:
+//config:config ASH_JOB_CONTROL
+//config: bool "Job control"
+//config: default y
+//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH
+//config:
+//config:config ASH_ALIAS
+//config: bool "Alias support"
+//config: default y
+//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH
//config:
//config:config ASH_RANDOM_SUPPORT
//config: bool "Pseudorandom generator and $RANDOM variable"
//config: default y
-//config: depends on ASH
+//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH
//config: help
//config: Enable pseudorandom generator and dynamic variable "$RANDOM".
//config: Each read of "$RANDOM" will generate a new pseudorandom value.
//config:config ASH_EXPAND_PRMT
//config: bool "Expand prompt string"
//config: default y
-//config: depends on ASH
+//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH
//config: help
-//config: "PS#" may contain volatile content, such as backquote commands.
+//config: $PS# may contain volatile content, such as backquote commands.
//config: This option recreates the prompt string from the environment
//config: variable each time it is displayed.
//config:
-//config:config ASH_BASH_COMPAT
-//config: bool "bash-compatible extensions"
-//config: default y
-//config: depends on ASH
-//config: help
-//config: Enable bash-compatible extensions.
-//config:
//config:config ASH_IDLE_TIMEOUT
-//config: bool "Idle timeout variable"
-//config: default n
-//config: depends on ASH
-//config: help
-//config: Enables bash-like auto-logout after $TMOUT seconds of idle time.
-//config:
-//config:config ASH_JOB_CONTROL
-//config: bool "Job control"
-//config: default y
-//config: depends on ASH
-//config: help
-//config: Enable job control in the ash shell.
-//config:
-//config:config ASH_ALIAS
-//config: bool "Alias support"
+//config: bool "Idle timeout variable $TMOUT"
//config: default y
-//config: depends on ASH
+//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH
//config: help
-//config: Enable alias support in the ash shell.
+//config: Enable bash-like auto-logout after $TMOUT seconds of idle time.
//config:
-//config:config ASH_GETOPTS
-//config: bool "Builtin getopt to parse positional parameters"
+//config:config ASH_MAIL
+//config: bool "Check for new mail in interactive shell"
//config: default y
-//config: depends on ASH
+//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH
//config: help
-//config: Enable support for getopts builtin in ash.
+//config: Enable "check for new mail" function:
+//config: if set, $MAIL file and $MAILPATH list of files
+//config: are checked for mtime changes, and "you have mail"
+//config: message is printed if change is detected.
//config:
-//config:config ASH_BUILTIN_ECHO
-//config: bool "Builtin version of 'echo'"
+//config:config ASH_ECHO
+//config: bool "echo builtin"
//config: default y
-//config: depends on ASH
-//config: help
-//config: Enable support for echo builtin in ash.
+//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH
//config:
-//config:config ASH_BUILTIN_PRINTF
-//config: bool "Builtin version of 'printf'"
+//config:config ASH_PRINTF
+//config: bool "printf builtin"
//config: default y
-//config: depends on ASH
-//config: help
-//config: Enable support for printf builtin in ash.
+//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH
//config:
-//config:config ASH_BUILTIN_TEST
-//config: bool "Builtin version of 'test'"
+//config:config ASH_TEST
+//config: bool "test builtin"
//config: default y
-//config: depends on ASH
-//config: help
-//config: Enable support for test builtin in ash.
+//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH
//config:
//config:config ASH_HELP
//config: bool "help builtin"
//config: default y
-//config: depends on ASH
-//config: help
-//config: Enable help builtin in ash.
+//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH
//config:
-//config:config ASH_CMDCMD
-//config: bool "'command' command to override shell builtins"
+//config:config ASH_GETOPTS
+//config: bool "getopts builtin"
//config: default y
-//config: depends on ASH
-//config: help
-//config: Enable support for the ash 'command' builtin, which allows
-//config: you to run the specified command with the specified arguments,
-//config: even when there is an ash builtin command with the same name.
+//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH
//config:
-//config:config ASH_MAIL
-//config: bool "Check for new mail on interactive shells"
-//config: default n
-//config: depends on ASH
+//config:config ASH_CMDCMD
+//config: bool "command builtin"
+//config: default y
+//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH
//config: help
-//config: Enable "check for new mail" function in the ash shell.
+//config: Enable support for the 'command' builtin, which allows
+//config: you to run the specified command or builtin,
+//config: even when there is a function with the same name.
//config:
+//config:endif # ash options
//applet:IF_ASH(APPLET(ash, BB_DIR_BIN, BB_SUID_DROP))
-//applet:IF_FEATURE_SH_IS_ASH(APPLET_ODDNAME(sh, ash, BB_DIR_BIN, BB_SUID_DROP, sh))
-//applet:IF_FEATURE_BASH_IS_ASH(APPLET_ODDNAME(bash, ash, BB_DIR_BIN, BB_SUID_DROP, bash))
+// APPLET_ODDNAME:name main location suid_type help
+//applet:IF_SH_IS_ASH( APPLET_ODDNAME(sh, ash, BB_DIR_BIN, BB_SUID_DROP, ash))
+//applet:IF_BASH_IS_ASH(APPLET_ODDNAME(bash, ash, BB_DIR_BIN, BB_SUID_DROP, ash))
//kbuild:lib-$(CONFIG_ASH) += ash.o ash_ptr_hack.o shell_common.o
+//kbuild:lib-$(CONFIG_SH_IS_ASH) += ash.o ash_ptr_hack.o shell_common.o
+//kbuild:lib-$(CONFIG_BASH_IS_ASH) += ash.o ash_ptr_hack.o shell_common.o
//kbuild:lib-$(CONFIG_ASH_RANDOM_SUPPORT) += random.o
+/*
+ * DEBUG=1 to compile in debugging ('set -o debug' turns on)
+ * DEBUG=2 to compile in and turn on debugging.
+ * When debugging is on ("set -o debug" was executed, or DEBUG=2),
+ * debugging info is written to ./trace, quit signal generates core dump.
+ */
+#define DEBUG 0
+/* Tweak debug output verbosity here */
+#define DEBUG_TIME 0
+#define DEBUG_PID 1
+#define DEBUG_SIG 1
+#define DEBUG_INTONOFF 0
+
+#define PROFILE 0
+
+#define JOBS ENABLE_ASH_JOB_CONTROL
+
+#include <setjmp.h>
+#include <fnmatch.h>
+#include <sys/times.h>
+#include <sys/utsname.h> /* for setting $HOSTNAME */
+#include "busybox.h" /* for applet_names */
+
+/* So far, all bash compat is controlled by one config option */
+/* Separate defines document which part of code implements what */
+/* function keyword */
+#define BASH_FUNCTION ENABLE_ASH_BASH_COMPAT
+#define IF_BASH_FUNCTION IF_ASH_BASH_COMPAT
+/* &>file */
+#define BASH_REDIR_OUTPUT ENABLE_ASH_BASH_COMPAT
+#define IF_BASH_REDIR_OUTPUT IF_ASH_BASH_COMPAT
+/* $'...' */
+#define BASH_DOLLAR_SQUOTE ENABLE_ASH_BASH_COMPAT
+#define IF_BASH_DOLLAR_SQUOTE IF_ASH_BASH_COMPAT
+#define BASH_PATTERN_SUBST ENABLE_ASH_BASH_COMPAT
+#define IF_BASH_PATTERN_SUBST IF_ASH_BASH_COMPAT
+#define BASH_SUBSTR ENABLE_ASH_BASH_COMPAT
+#define IF_BASH_SUBSTR IF_ASH_BASH_COMPAT
+/* [[ EXPR ]] */
+#define BASH_TEST2 (ENABLE_ASH_BASH_COMPAT * ENABLE_ASH_TEST)
+#define BASH_SOURCE ENABLE_ASH_BASH_COMPAT
+#define BASH_PIPEFAIL ENABLE_ASH_BASH_COMPAT
+#define BASH_HOSTNAME_VAR ENABLE_ASH_BASH_COMPAT
+#define BASH_SHLVL_VAR ENABLE_ASH_BASH_COMPAT
+
+#if defined(__ANDROID_API__) && __ANDROID_API__ <= 24
+/* Bionic at least up to version 24 has no glob() */
+# undef ENABLE_ASH_INTERNAL_GLOB
+# define ENABLE_ASH_INTERNAL_GLOB 1
+#endif
+
+#if !ENABLE_ASH_INTERNAL_GLOB && defined(__UCLIBC__)
+# error uClibc glob() is buggy, use ASH_INTERNAL_GLOB.
+# error The bug is: for "$PWD"/<pattern> 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 <pattern> 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 <glob.h>
+#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. */
"b" "notify",
"u" "nounset",
"\0" "vi"
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_PIPEFAIL
,"\0" "pipefail"
#endif
#if DEBUG
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 EXEXIT 4 /* exit the shell */
-#define EXSIG 5 /* trapped signal in wait(1) */
smallint isloginsh;
char nullstr[1]; /* zero length string */
#define bflag optlist[11]
#define uflag optlist[12]
#define viflag optlist[13]
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_PIPEFAIL
# define pipefail optlist[14]
#else
# define pipefail 0
#endif
#if DEBUG
-# define nolog optlist[14 + ENABLE_ASH_BASH_COMPAT]
-# define debug optlist[15 + ENABLE_ASH_BASH_COMPAT]
+# define nolog optlist[14 + BASH_PIPEFAIL]
+# define debug optlist[15 + BASH_PIPEFAIL]
#endif
/* trap handler commands */
#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 )
* much more efficient and portable. (But hacking the kernel is so much
* more fun than worrying about efficiency and portability. :-))
*/
-#define INT_OFF do { \
+#if DEBUG_INTONOFF
+# define INT_OFF do { \
+ TRACE(("%s:%d INT_OFF(%d)\n", __func__, __LINE__, suppress_int)); \
+ suppress_int++; \
+ barrier(); \
+} while (0)
+#else
+# define INT_OFF do { \
suppress_int++; \
barrier(); \
} while (0)
+#endif
/*
* Called to raise an exception. Since C doesn't include exceptions, we
static void
raise_interrupt(void)
{
- int ex_type;
-
pending_int = 0;
/* Signal is not automatically unmasked after it is raised,
* do it ourself - unmask all signals */
sigprocmask_allsigs(SIG_UNBLOCK);
/* pending_sig = 0; - now done in signal_handler() */
- ex_type = EXSIG;
- if (gotsig[SIGINT - 1] && !trap[SIGINT]) {
- if (!(rootshell && iflag)) {
- /* Kill ourself with SIGINT */
- signal(SIGINT, SIG_DFL);
- raise(SIGINT);
- }
- ex_type = EXINT;
+ if (!(rootshell && iflag)) {
+ /* Kill ourself with SIGINT */
+ signal(SIGINT, SIG_DFL);
+ raise(SIGINT);
}
/* bash: ^C even on empty command line sets $? */
exitstatus = SIGINT + 128;
- raise_exception(ex_type);
+ raise_exception(EXINT);
/* NOTREACHED */
}
#if DEBUG
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)
{
#define VSTRIMLEFT 0x8 /* ${var#pattern} */
#define VSTRIMLEFTMAX 0x9 /* ${var##pattern} */
#define VSLENGTH 0xa /* ${#var} */
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_SUBSTR
#define VSSUBSTR 0xc /* ${var:position:length} */
+#endif
+#if BASH_PATTERN_SUBST
#define VSREPLACE 0xd /* ${var/pattern/replacement} */
#define VSREPLACEALL 0xe /* ${var//pattern/replacement} */
#endif
#define NDEFUN 14
#define NARG 15
#define NTO 16
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_REDIR_OUTPUT
#define NTO2 17
#endif
#define NCLOBBER 18
{
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
case NTO: s = ">>"+1; dftfd = 1; break;
case NCLOBBER: s = ">|"; dftfd = 1; break;
case NAPPEND: s = ">>"; dftfd = 1; break;
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_REDIR_OUTPUT
case NTO2:
#endif
case NTOFD: s = ">&"; dftfd = 1; break;
{
#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);
{
va_list ap;
+ exitstatus = 2;
+
va_start(ap, msg);
ash_vmsg_and_raise(EXERROR, msg, ap);
/* NOTREACHED */
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
return NULL;
}
+#if ENABLE_UNICODE_SUPPORT
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.
if (flags & VNOSAVE)
free(s);
n = vp->var_text;
+ exitstatus = 1;
ash_msg_and_raise_error("%.*s: is read only", strchrnul(n, '=') - n, n);
}
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.
*/
}
#endif
-#if ENABLE_ASH_EXPAND_PRMT
/* expandstr() needs parsing machinery, so it is far away ahead... */
static const char *expandstr(const char *ps);
-#else
-#define expandstr(s) s
-#endif
static void
setprompt_if(smallint do_set, int whichprompt)
}
#if ENABLE_ASH_EXPAND_PRMT
pushstackmark(&smark, stackblocksize());
-#endif
putprompt(expandstr(prompt));
-#if ENABLE_ASH_EXPAND_PRMT
popstackmark(&smark);
+#else
+ putprompt(prompt);
#endif
}
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
if (!dest)
dest = nullstr;
if (*dest == '/')
- goto step7;
+ goto step6;
if (*dest == '.') {
c = dest[1];
dotdot:
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)) {
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:
#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))
# 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 */
{
int i;
- while ((i = nextopt("a")) != '\0') {
- if (i == 'a') {
- rmaliases();
- return 0;
- }
+ while (nextopt("a") != '\0') {
+ rmaliases();
+ return 0;
}
for (i = 0; *argptr; argptr++) {
if (unalias(*argptr)) {
#if JOBS
int stopstatus; /* status of a stopped job */
#endif
- uint32_t
- nprocs: 16, /* number of processes */
- state: 8,
+ unsigned nprocs; /* number of processes */
+
#define JOBRUNNING 0 /* at least one proc running */
#define JOBSTOPPED 1 /* all procs are stopped */
#define JOBDONE 2 /* all procs are completed */
+ unsigned
+ state: 8,
#if JOBS
sigint: 1, /* job was killed by SIGINT */
jobctl: 1, /* job running under job control */
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) {
raise_interrupt(); /* does not return */
}
pending_int = 1;
- } else {
- pending_sig = 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) {
#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
/* number of presumed living untracked jobs */
static int jobless; //4
+#if 0
+/* Bash has a feature: it restores termios after a successful wait for
+ * a foreground job which had at least one stopped or sigkilled member.
+ * The probable rationale is that SIGSTOP and SIGKILL can preclude task from
+ * properly restoring tty state. Should we do this too?
+ * A reproducer: ^Z an interactive python:
+ *
+ * # python
+ * Python 2.7.12 (...)
+ * >>> ^Z
+ * { python leaves tty in -icanon -echo state. We do survive that... }
+ * [1]+ Stopped python
+ * { ...however, next program (python #2) does not survive it well: }
+ * # python
+ * Python 2.7.12 (...)
+ * >>> Traceback (most recent call last):
+ * { above, I typed "qwerty<CR>", but -echo state is still in effect }
+ * File "<stdin>", line 1, in <module>
+ * NameError: name 'qwerty' is not defined
+ *
+ * The implementation below is modeled on bash code and seems to work.
+ * However, I'm not sure we should do this. For one: what if I'd fg
+ * the stopped python instead? It'll be confused by "restored" tty state.
+ */
+static struct termios shell_tty_info;
static void
-set_curjob(struct job *jp, unsigned mode)
+get_tty_state(void)
{
- struct job *jp1;
- struct job **jpp, **curp;
-
- /* first remove from list */
- jpp = curp = &curjob;
- while (1) {
- jp1 = *jpp;
- if (jp1 == jp)
- break;
- jpp = &jp1->prev_job;
- }
- *jpp = jp1->prev_job;
-
- /* Then re-insert in correct position */
- jpp = curp;
- switch (mode) {
- default:
+ if (rootshell && ttyfd >= 0)
+ tcgetattr(ttyfd, &shell_tty_info);
+}
+static void
+set_tty_state(void)
+{
+ /* if (rootshell) - caller ensures this */
+ if (ttyfd >= 0)
+ tcsetattr(ttyfd, TCSADRAIN, &shell_tty_info);
+}
+static int
+job_signal_status(struct job *jp)
+{
+ int status;
+ unsigned i;
+ struct procstat *ps = jp->ps;
+ for (i = 0; i < jp->nprocs; i++) {
+ status = ps[i].ps_status;
+ if (WIFSIGNALED(status) || WIFSTOPPED(status))
+ return status;
+ }
+ return 0;
+}
+static void
+restore_tty_if_stopped_or_signaled(struct job *jp)
+{
+//TODO: check what happens if we come from waitforjob() in expbackq()
+ if (rootshell) {
+ int s = job_signal_status(jp);
+ if (s) /* WIFSIGNALED(s) || WIFSTOPPED(s) */
+ set_tty_state();
+ }
+}
+#else
+# define get_tty_state() ((void)0)
+# define restore_tty_if_stopped_or_signaled(jp) ((void)0)
+#endif
+
+static void
+set_curjob(struct job *jp, unsigned mode)
+{
+ struct job *jp1;
+ struct job **jpp, **curp;
+
+ /* first remove from list */
+ jpp = curp = &curjob;
+ while (1) {
+ jp1 = *jpp;
+ if (jp1 == jp)
+ break;
+ jpp = &jp1->prev_job;
+ }
+ *jpp = jp1->prev_job;
+
+ /* Then re-insert in correct position */
+ jpp = curp;
+ switch (mode) {
+ default:
#if DEBUG
abort();
#endif
}
/* fd is a tty at this point */
fd = fcntl(fd, F_DUPFD, 10);
- if (ofd >= 0) /* if it is "/dev/tty", close. If 0/1/2, dont */
+ if (ofd >= 0) /* if it is "/dev/tty", close. If 0/1/2, don't */
close(ofd);
if (fd < 0)
goto out; /* F_DUPFD failed */
goto out;
jp->state = JOBRUNNING;
pgid = jp->ps[0].ps_pid;
- if (mode == FORK_FG)
+ if (mode == FORK_FG) {
+ get_tty_state();
xtcsetpgrp(ttyfd, pgid);
+ }
killpg(pgid, SIGCONT);
ps = jp->ps;
i = jp->nprocs;
}
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;
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)
int retval;
struct job *jp;
- if (pending_sig)
- raise_exception(EXSIG);
-
nextopt(nullstr);
retval = 0;
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;
}
}
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: ;
ret:
return retval;
+ sigout:
+ retval = 128 + pending_sig;
+ return retval;
}
static struct job *
memset(jp, 0, sizeof(*jp));
#if JOBS
/* jp->jobctl is a bitfield.
- * "jp->jobctl |= jobctl" likely to give awful code */
+ * "jp->jobctl |= doing_jobctl" likely to give awful code */
if (doing_jobctl)
jp->jobctl = 1;
#endif
static const char vstype[VSTYPE + 1][3] = {
"", "}", "-", "+", "?", "=",
"%", "%%", "#", "##"
- IF_ASH_BASH_COMPAT(, ":", "/", "//")
+ IF_BASH_SUBSTR(, ":")
+ IF_BASH_PATTERN_SUBST(, "/", "//")
};
const char *p, *str;
case CTLBACKQ:
str = "$(...)";
goto dostr;
-#if ENABLE_SH_MATH_SUPPORT
+#if ENABLE_FEATURE_SH_MATH
case CTLARI:
str = "$((";
goto dostr;
case NAPPEND:
p = ">>";
goto redir;
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_REDIR_OUTPUT
case NTO2:
#endif
case NTOFD:
{
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)
{
{
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++;
}
}
+/* jp and n are NULL when called by openhere() for heredoc support */
static int
forkshell(struct job *jp, union node *n, int mode)
{
#if JOBS
if (jp->jobctl) {
xtcsetpgrp(ttyfd, rootpid);
+ restore_tty_if_stopped_or_signaled(jp);
+
/*
* This is truly gross.
* If we're doing job control, then we did a TIOCSPGRP which
}
-/* ============ redir.c
- *
+/*
* Code for dealing with input/output redirection.
*/
goto ecreate;
break;
case NTO:
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_REDIR_OUTPUT
case NTO2:
#endif
/* Take care of noclobber mode. */
union node *tmp = redir;
do {
sv_pos++;
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_REDIR_OUTPUT
if (tmp->nfile.type == NTO2)
sv_pos++;
#endif
continue;
}
}
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_REDIR_OUTPUT
redirect_more:
#endif
if (need_to_remember(sv, fd)) {
/* 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) {
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 */
}
} else if (fd != newfd) { /* move newfd to fd */
dup2_or_raise(newfd, fd);
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_REDIR_OUTPUT
if (!(redir->nfile.type == NTO2 && fd == 2))
#endif
close(newfd);
}
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_REDIR_OUTPUT
if (redir->nfile.type == NTO2 && fd == 1) {
/* We already redirected it to fd 1, now copy it to 2 */
newfd = 1;
* 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)
{
/*
* 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"
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)
{
rmescapes(char *str, int flag)
{
static const char qchars[] ALIGN1 = {
- IF_ASH_BASH_COMPAT('/',) CTLESC, CTLQUOTEMARK, '\0' };
+ IF_BASH_PATTERN_SUBST('/',) CTLESC, CTLQUOTEMARK, '\0' };
char *p, *q, *r;
unsigned inquotes;
unsigned protect_against_glob;
unsigned globbing;
- IF_ASH_BASH_COMPAT(unsigned slash = flag & RMESCAPE_SLASH;)
+ IF_BASH_PATTERN_SUBST(unsigned slash = flag & RMESCAPE_SLASH;)
- p = strpbrk(str, qchars IF_ASH_BASH_COMPAT(+ !slash));
+ p = strpbrk(str, qchars IF_BASH_PATTERN_SUBST(+ !slash));
if (!p)
return str;
while (*p) {
if ((unsigned char)*p == CTLQUOTEMARK) {
// Note: both inquotes and protect_against_glob only affect whether
+// CTLESC,<ch> gets converted to <ch> or to \<ch>
inquotes = ~inquotes;
p++;
protect_against_glob = globbing;
ash_msg_and_raise_error("CTLESC at EOL (shouldn't happen)");
#endif
if (protect_against_glob) {
- *q++ = '\\';
+ /*
+ * We used to trust glob() and fnmatch() to eat
+ * superfluous escapes (\z where z has no
+ * special meaning anyway). But this causes
+ * bugs such as string of one greek letter rho
+ * (unicode-encoded as two bytes "cf,81")
+ * getting encoded as "cf,CTLESC,81"
+ * and here, converted to "cf,\,81" -
+ * which does not go well with some flavors
+ * of fnmatch() in unicode locales
+ * (for example, glibc <= 2.22).
+ *
+ * Lets add "\" only on the chars which need it.
+ * Testcases for less obvious chars are shown.
+ */
+ if (*p == '*'
+ || *p == '?'
+ || *p == '['
+ || *p == '\\' /* case '\' in \\ ) echo ok;; *) echo WRONG;; esac */
+ || *p == ']' /* case ']' in [a\]] ) echo ok;; *) echo WRONG;; esac */
+ || *p == '-' /* case '-' in [a\-c]) echo ok;; *) echo WRONG;; esac */
+ || *p == '!' /* case '!' in [\!] ) echo ok;; *) echo WRONG;; esac */
+ /* Some libc support [^negate], that's why "^" also needs love */
+ || *p == '^' /* case '^' in [\^] ) echo ok;; *) echo WRONG;; esac */
+ ) {
+ *q++ = '\\';
+ }
}
} else if (*p == '\\' && !inquotes) {
/* naked back slash */
protect_against_glob = 0;
goto copy;
}
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_PATTERN_SUBST
else if (*p == '/' && slash) {
/* stop handling globbing and mark location of slash */
globbing = slash = 0;
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;
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) {
* For now, preserve bash-like behavior, it seems to be somewhat more useful:
*/
eflag = 0;
+ ifsfree();
evaltree(n, EV_EXIT); /* actually evaltreenr... */
/* NOTREACHED */
}
+ /* parent */
close(pip[1]);
result->fd = pip[0];
result->jp = jp;
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.
* $@ like $* since no splitting will be performed.
*
* var_str_list (can be NULL) is a list of "VAR=val" strings which take precedence
- * over shell varables. Needed for "A=a B=$A; echo $B" case - we use it
+ * over shell variables. Needed for "A=a B=$A; echo $B" case - we use it
* for correct expansion of "B=$A" word.
*/
static void
CTLESC,
CTLVAR,
CTLBACKQ,
-#if ENABLE_SH_MATH_SUPPORT
+#if ENABLE_FEATURE_SH_MATH
CTLENDARI,
#endif
'\0'
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++;
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);
if (try2optimize) {
/* Maybe we can optimize this:
* if pattern ends with unescaped *, we can avoid checking
- * shorter strings: if "foo*" doesnt match "raw_value_of_v",
- * it wont match truncated "raw_value_of_" strings too.
+ * shorter strings: if "foo*" doesn't match "raw_value_of_v",
+ * it won't match truncated "raw_value_of_" strings too.
*/
unsigned plen = strlen(pattern);
/* Does it end with "*"? */
char *loc;
char *rmesc, *rmescend;
char *str;
- IF_ASH_BASH_COMPAT(char *repl = NULL;)
- IF_ASH_BASH_COMPAT(int pos, len, orig_len;)
+ IF_BASH_SUBSTR(int pos, len, orig_len;)
int amount, resetloc;
- IF_ASH_BASH_COMPAT(int workloc;)
+ IF_BASH_PATTERN_SUBST(int workloc;)
+ IF_BASH_PATTERN_SUBST(char *repl = NULL;)
int zero;
char *(*scan)(char*, char*, char*, char*, int, int);
varunset(p, varname, startp, varflags);
/* NOTREACHED */
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_SUBSTR
case VSSUBSTR:
//TODO: support more general format ${v:EXPR:EXPR},
// where EXPR follows $(()) rules
amount = loc - expdest;
STADJUST(amount, expdest);
return loc;
-#endif
+#endif /* BASH_SUBSTR */
}
resetloc = expdest - (char *)stackblock();
+#if BASH_PATTERN_SUBST
/* We'll comeback here if we grow the stack while handling
* a VSREPLACE or VSREPLACEALL, since our pointers into the
* stack will need rebasing, and we'll need to remove our work
* areas each time
*/
- IF_ASH_BASH_COMPAT(restart:)
+ restart:
+#endif
amount = expdest - ((char *)stackblock() + resetloc);
STADJUST(-amount, expdest);
* RMESCAPE_SLASH causes preglob to work differently on the pattern
* and string. It's only used on the first call.
*/
- preglob(str, IF_ASH_BASH_COMPAT(
+ preglob(str, IF_BASH_PATTERN_SUBST(
(subtype == VSREPLACE || subtype == VSREPLACEALL) && !repl ?
- RMESCAPE_SLASH :) 0);
+ RMESCAPE_SLASH : ) 0);
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_PATTERN_SUBST
workloc = expdest - (char *)stackblock();
if (subtype == VSREPLACE || subtype == VSREPLACEALL) {
char *idx, *end;
STADJUST(-amount, expdest);
return startp;
}
-#endif /* ENABLE_ASH_BASH_COMPAT */
+#endif /* BASH_PATTERN_SUBST */
subtype -= VSTRIMRIGHT;
#if DEBUG
}
if (subtype == VSASSIGN || subtype == VSQUESTION) {
- if (varlen >= 0)
- goto record;
-
- 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)
- varunset(p, var, 0, 0);
-
- if (subtype == VSLENGTH) {
- cvtnum(varlen > 0 ? varlen : 0);
- goto record;
- }
-
- if (subtype == VSNORMAL) {
- record:
- if (!easy)
- goto end;
- recordregion(startloc, expdest - (char *)stackblock(), quoted);
- goto end;
- }
-
-#if DEBUG
- switch (subtype) {
- case VSTRIMLEFT:
- case VSTRIMLEFTMAX:
- case VSTRIMRIGHT:
- case VSTRIMRIGHTMAX:
-#if ENABLE_ASH_BASH_COMPAT
- case VSSUBSTR:
- case VSREPLACE:
- case VSREPLACEALL:
-#endif
- break;
- default:
- abort();
- }
-#endif
-
- if (varlen >= 0) {
- /*
- * Terminate the string and start recording the pattern
- * right after it
- */
- STPUTC('\0', expdest);
- patloc = expdest - (char *)stackblock();
- if (NULL == subevalvar(p, /* varname: */ NULL, patloc, subtype,
- startloc, varflags, flag, var_str_list)) {
- int amount = expdest - (
- (char *)stackblock() + patloc - 1
- );
- STADJUST(-amount, expdest);
- }
- /* Remove any recorded regions beyond start of variable */
- removerecordregions(startloc);
- goto record;
- }
-
- end:
- if (subtype != VSNORMAL) { /* skip to end of alternative */
- int nesting = 1;
- for (;;) {
- unsigned char c = *p++;
- if (c == CTLESC)
- p++;
- else if (c == CTLBACKQ) {
- if (varlen >= 0)
- argbackq = argbackq->next;
- } else if (c == CTLVAR) {
- if ((*p++ & VSTYPE) != VSNORMAL)
- nesting++;
- } else if (c == CTLENDVAR) {
- if (--nesting == 0)
- break;
- }
- }
- }
- return p;
-}
-
-/*
- * Break the argument string into pieces based upon IFS and add the
- * strings to the argument list. The regions of the string to be
- * searched for IFS characters have been stored by recordregion.
- */
-static void
-ifsbreakup(char *string, struct arglist *arglist)
-{
- struct ifsregion *ifsp;
- struct strlist *sp;
- char *start;
- char *p;
- char *q;
- const char *ifs, *realifs;
- int ifsspc;
- int nulonly;
-
- start = string;
- if (ifslastp != NULL) {
- ifsspc = 0;
- nulonly = 0;
- realifs = ifsset() ? ifsval() : defifs;
- ifsp = &ifsfirst;
- do {
- p = string + ifsp->begoff;
- nulonly = ifsp->nulonly;
- ifs = nulonly ? nullstr : realifs;
- ifsspc = 0;
- while (p < string + ifsp->endoff) {
- q = p;
- if ((unsigned char)*p == CTLESC)
- p++;
- if (!strchr(ifs, *p)) {
- p++;
- continue;
- }
- if (!nulonly)
- ifsspc = (strchr(defifs, *p) != NULL);
- /* Ignore IFS whitespace at start */
- if (q == start && ifsspc) {
- p++;
- start = p;
- continue;
- }
- *q = '\0';
- sp = stzalloc(sizeof(*sp));
- sp->text = start;
- *arglist->lastp = sp;
- arglist->lastp = &sp->next;
- p++;
- if (!nulonly) {
- for (;;) {
- if (p >= string + ifsp->endoff) {
- break;
- }
- q = p;
- if ((unsigned char)*p == CTLESC)
- p++;
- if (strchr(ifs, *p) == NULL) {
- p = q;
- break;
- }
- if (strchr(defifs, *p) == NULL) {
- if (ifsspc) {
- p++;
- ifsspc = 0;
- } else {
- p = q;
- break;
- }
- } else
- p++;
- }
- }
- start = p;
- } /* while */
- ifsp = ifsp->next;
- } while (ifsp != NULL);
- if (nulonly)
- goto add;
+ if (varlen >= 0)
+ goto record;
+
+ 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 (!*start)
- return;
+ if (varlen < 0 && uflag)
+ varunset(p, var, 0, 0);
- add:
- sp = stzalloc(sizeof(*sp));
- sp->text = start;
- *arglist->lastp = sp;
- arglist->lastp = &sp->next;
-}
+ if (subtype == VSLENGTH) {
+ cvtnum(varlen > 0 ? varlen : 0);
+ goto record;
+ }
-static void
-ifsfree(void)
-{
- struct ifsregion *p;
+ if (subtype == VSNORMAL) {
+ record:
+ if (!easy)
+ goto end;
+ recordregion(startloc, expdest - (char *)stackblock(), quoted);
+ goto end;
+ }
- 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;
+#if DEBUG
+ switch (subtype) {
+ case VSTRIMLEFT:
+ case VSTRIMLEFTMAX:
+ case VSTRIMRIGHT:
+ case VSTRIMRIGHTMAX:
+#if BASH_SUBSTR
+ case VSSUBSTR:
+#endif
+#if BASH_PATTERN_SUBST
+ case VSREPLACE:
+ case VSREPLACEALL:
+#endif
+ break;
+ default:
+ abort();
+ }
+#endif
+
+ if (varlen >= 0) {
+ /*
+ * Terminate the string and start recording the pattern
+ * right after it
+ */
+ STPUTC('\0', expdest);
+ patloc = expdest - (char *)stackblock();
+ if (NULL == subevalvar(p, /* varname: */ NULL, patloc, subtype,
+ startloc, varflags, flag, var_str_list)) {
+ int amount = expdest - (
+ (char *)stackblock() + patloc - 1
+ );
+ STADJUST(-amount, expdest);
+ }
+ /* Remove any recorded regions beyond start of variable */
+ removerecordregions(startloc);
+ goto record;
+ }
+
+ end:
+ if (subtype != VSNORMAL) { /* skip to end of alternative */
+ int nesting = 1;
+ for (;;) {
+ unsigned char c = *p++;
+ if (c == CTLESC)
+ p++;
+ else if (c == CTLBACKQ) {
+ if (varlen >= 0)
+ argbackq = argbackq->next;
+ } else if (c == CTLVAR) {
+ if ((*p++ & VSTYPE) != VSNORMAL)
+ nesting++;
+ } else if (c == CTLENDVAR) {
+ if (--nesting == 0)
+ break;
+ }
+ }
+ }
+ return p;
}
/*
exparg.lastp = &sp->next;
}
+/* Avoid glob() (and thus, stat() et al) for words like "echo" */
+static int
+hasmeta(const char *p)
+{
+ static const char chars[] ALIGN1 = {
+ '*', '?', '[', '\\', CTLQUOTEMARK, CTLESC, 0
+ };
+
+ for (;;) {
+ p = strpbrk(p, chars);
+ if (!p)
+ break;
+ switch ((unsigned char) *p) {
+ case CTLQUOTEMARK:
+ for (;;) {
+ p++;
+ if (*p == CTLQUOTEMARK)
+ break;
+ if (*p == CTLESC)
+ p++;
+ if (*p == '\0') /* huh? */
+ return 0;
+ }
+ break;
+ case '\\':
+ case CTLESC:
+ p++;
+ if (*p == '\0')
+ return 0;
+ break;
+ case '[':
+ if (!strchr(p + 1, ']')) {
+ /* It's not a properly closed [] pattern,
+ * but other metas may follow. Continue checking.
+ * my[file* _is_ globbed by bash
+ * and matches filenames like "my[file1".
+ */
+ break;
+ }
+ /* fallthrough */
+ default:
+ /* case '*': */
+ /* case '?': */
+ return 1;
+ }
+ p++;
+ }
+
+ return 0;
+}
+
/* If we want to use glob() from libc... */
#if !ENABLE_ASH_INTERNAL_GLOB
if (fflag)
goto nometa;
+
+ if (!hasmeta(str->text))
+ goto nometa;
+
INT_OFF;
p = preglob(str->text, RMESCAPE_ALLOC | RMESCAPE_HEAP);
// GLOB_NOMAGIC (GNU): if no *?[ chars in pattern, return it even if no match
// Which means you need to unescape the string, right? Not so fast:
// if there _is_ a file named "file\?" (with backslash), it is returned
// as "file\?" too (whichever pattern you used to find it, say, "file*").
-// You DONT KNOW by looking at the result whether you need to unescape it.
+// You DON'T KNOW by looking at the result whether you need to unescape it.
//
// Worse, globbing of "file\?" in a directory with two files, "file?" and "file\?",
// returns "file\?" - which is WRONG: "file\?" pattern matches "file?" file.
static void
expandmeta(struct strlist *str /*, int flag*/)
{
- static const char metachars[] ALIGN1 = {
- '*', '?', '[', 0
- };
/* TODO - EXP_REDIR */
while (str) {
if (fflag)
goto nometa;
- if (!strpbrk(str->text, metachars))
+ if (!hasmeta(str->text))
goto nometa;
savelastp = exparg.lastp;
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));
*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();
}
/*
static int
patmatch(char *pattern, const char *string)
{
- return pmatch(preglob(pattern, 0), string);
+ char *p = preglob(pattern, 0);
+ //bb_error_msg("fnmatch(pattern:'%s',str:'%s')", p, string);
+ return pmatch(p, string);
}
/*
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;
#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
* 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;
}
}
/*
* Exec a program. Never returns. If you change this routine, you may
* have to change the find_command routine as well.
+ * argv[-1] must exist and be writable! See tryexec() for why.
*/
-static void shellexec(char **, const char *, int) NORETURN;
-static void
-shellexec(char **argv, const char *path, int idx)
+static void shellexec(char *prog, char **argv, const char *path, int idx) NORETURN;
+static void shellexec(char *prog, char **argv, const char *path, int idx)
{
char *cmdname;
int e;
int applet_no = -1; /* used only by FEATURE_SH_STANDALONE */
envp = listvars(VEXPORT, VUNSET, /*end:*/ NULL);
- if (strchr(argv[0], '/') != NULL
+ if (strchr(prog, '/') != NULL
#if ENABLE_FEATURE_SH_STANDALONE
- || (applet_no = find_applet_by_name(argv[0])) >= 0
+ || (applet_no = find_applet_by_name(prog)) >= 0
#endif
) {
- tryexec(IF_FEATURE_SH_STANDALONE(applet_no,) argv[0], argv, envp);
+ tryexec(IF_FEATURE_SH_STANDALONE(applet_no,) prog, argv, envp);
if (applet_no >= 0) {
/* We tried execing ourself, but it didn't work.
* Maybe /proc/self/exe doesn't exist?
} else {
try_PATH:
e = ENOENT;
- while ((cmdname = path_advance(&path, argv[0])) != NULL) {
+ while ((cmdname = path_advance(&path, prog)) != NULL) {
if (--idx < 0 && pathopt == NULL) {
tryexec(IF_FEATURE_SH_STANDALONE(-1,) cmdname, argv, envp);
if (errno != ENOENT && errno != ENOTDIR)
}
exitstatus = exerrno;
TRACE(("shellexec failed for %s, errno %d, suppress_int %d\n",
- argv[0], e, suppress_int));
- ash_msg_and_raise(EXEXIT, "%s: %s", argv[0], errmsg(e, "not found"));
+ prog, e, suppress_int));
+ ash_msg_and_raise(EXEXIT, "%s: %s", prog, errmsg(e, "not found"));
/* NOTREACHED */
}
TESAC,
TFI,
TFOR,
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_FUNCTION
TFUNCTION,
#endif
TIF,
/* 19 */ | (1u << TESAC)
/* 20 */ | (1u << TFI)
/* 21 */ | (0u << TFOR)
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_FUNCTION
/* 22 */ | (0u << TFUNCTION)
#endif
/* 23 */ | (0u << TIF)
"esac",
"fi",
"for",
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_FUNCTION
"function",
#endif
"if",
describe_command(char *command, const char *path, int describe_command_verbose)
{
struct cmdentry entry;
- struct tblentry *cmdp;
#if ENABLE_ASH_ALIAS
const struct alias *ap;
#endif
goto out;
}
#endif
- /* Then check if it is a tracked alias */
- cmdp = cmdlookup(command, 0);
- if (cmdp != NULL) {
- entry.cmdtype = cmdp->cmdtype;
- entry.u = cmdp->param;
- } else {
- /* Finally use brute force */
- find_command(command, &entry, DO_ABS, path);
- }
+ /* Brute force */
+ find_command(command, &entry, DO_ABS, path);
switch (entry.cmdtype) {
case CMDNORMAL: {
} while (--j >= 0);
}
if (describe_command_verbose) {
- out1fmt(" is%s %s",
- (cmdp ? " a tracked alias for" : nullstr), p
- );
+ out1fmt(" is %s", p);
} else {
out1str(p);
}
[NDEFUN ] = SHELL_ALIGN(sizeof(struct narg)),
[NARG ] = SHELL_ALIGN(sizeof(struct narg)),
[NTO ] = SHELL_ALIGN(sizeof(struct nfile)),
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_REDIR_OUTPUT
[NTO2 ] = SHELL_ALIGN(sizeof(struct nfile)),
#endif
[NCLOBBER ] = SHELL_ALIGN(sizeof(struct nfile)),
funcblocksize = calcsize(funcblocksize, n->narg.next);
break;
case NTO:
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_REDIR_OUTPUT
case NTO2:
#endif
case NCLOBBER:
new->narg.next = copynode(n->narg.next);
break;
case NTO:
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_REDIR_OUTPUT
case NTO2:
#endif
case NCLOBBER:
static int
evaltree(union node *n, int flags)
{
- struct jmploc *volatile savehandler = exception_handler;
- struct jmploc jmploc;
int checkexit = 0;
int (*evalfn)(union node *, int);
int status = 0;
- int int_level;
-
- SAVE_INT(int_level);
if (n == NULL) {
TRACE(("evaltree(NULL) called\n"));
- goto out1;
+ goto out;
}
TRACE(("evaltree(%p: %d, %d) called\n", n, n->type, flags));
dotrap();
- exception_handler = &jmploc;
- {
- int err = setjmp(jmploc.loc);
- if (err) {
- /* if it was a signal, check for trap handlers */
- if (exception_type == EXSIG) {
- TRACE(("exception %d (EXSIG) in evaltree, err=%d\n",
- exception_type, err));
- goto out;
- }
- /* continue on the way out */
- TRACE(("exception %d in evaltree, propagating err=%d\n",
- exception_type, err));
- exception_handler = savehandler;
- longjmp(exception_handler->loc, err);
- }
- }
-
switch (n->type) {
default:
#if DEBUG
exitstatus = status;
break;
}
-
out:
- exception_handler = savehandler;
-
- out1:
/* Order of checks below is important:
* signal handlers trigger before exit caused by "set -e".
*/
if (flags & EV_EXIT)
raise_exception(EXEXIT);
- RESTORE_INT(int_level);
TRACE(("leaving evaltree (no interrupts)\n"));
-
return exitstatus;
}
evalsubshell(union node *n, int flags)
{
struct job *jp;
- int backgnd = (n->type == NBACKGND);
+ int backgnd = (n->type == NBACKGND); /* FORK_BG(1) if yes, else FORK_FG(0) */
int status;
expredir(n->nredir.redirect);
if (!backgnd && (flags & EV_EXIT) && !may_have_traps)
goto nofork;
INT_OFF;
+ if (backgnd == FORK_FG)
+ get_tty_state();
jp = makejob(/*n,*/ 1);
if (forkshell(jp, n, backgnd) == 0) {
/* child */
evaltreenr(n->nredir.n, flags);
/* never returns */
}
+ /* parent */
status = 0;
- if (!backgnd)
+ if (backgnd == FORK_FG)
status = waitforjob(jp);
INT_ON;
return status;
case NFROMTO:
case NFROM:
case NTO:
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_REDIR_OUTPUT
case NTO2:
#endif
case NCLOBBER:
case NAPPEND:
expandarg(redir->nfile.fname, &fn, EXP_TILDE | EXP_REDIR);
TRACE(("expredir expanded to '%s'\n", fn.list->text));
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_REDIR_OUTPUT
store_expfname:
#endif
#if 0
expandarg(redir->ndup.vname, &fn, EXP_FULL | EXP_TILDE);
if (fn.list == NULL)
ash_msg_and_raise_error("redir error");
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_REDIR_OUTPUT
//FIXME: we used expandarg with different args!
if (!isdigit_str9(fn.list->text)) {
/* >&file, not >&fd */
pipelen++;
flags |= EV_EXIT;
INT_OFF;
+ if (n->npipe.pipe_backgnd == 0)
+ get_tty_state();
jp = makejob(/*n,*/ pipelen);
prevfd = -1;
for (lp = n->npipe.cmdlist; lp; lp = lp->next) {
}
}
if (forkshell(jp, lp->n, n->npipe.pipe_backgnd) == 0) {
+ /* child */
INT_ON;
if (pip[1] >= 0) {
close(pip[0]);
evaltreenr(lp->n, flags);
/* never returns */
}
+ /* parent */
if (prevfd >= 0)
close(prevfd);
prevfd = pip[0];
/* else:
* it's a duplicate "local VAR" declaration, do nothing
*/
- return;
+ goto ret;
}
lvp = lvp->next;
}
lvp->vp = vp;
lvp->next = localvars;
localvars = lvp;
+ ret:
INT_ON;
}
static int FAST_FUNC
execcmd(int argc UNUSED_PARAM, char **argv)
{
- if (argv[1]) {
+ optionarg = NULL;
+ while (nextopt("a:") != '\0')
+ /* nextopt() sets optionarg to "-a ARGV0" */;
+
+ argv = argptr;
+ if (argv[0]) {
+ char *prog;
+
iflag = 0; /* exit on error */
mflag = 0;
optschanged();
/*setsignal(SIGTSTP); - unnecessary because of mflag=0 */
/*setsignal(SIGTTOU); - unnecessary because of mflag=0 */
- shellexec(argv + 1, pathval(), 0);
+ prog = argv[0];
+ if (optionarg)
+ argv[0] = optionarg;
+ shellexec(prog, argv, pathval(), 0);
/* NOTREACHED */
}
return 0;
#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;
#define BUILTIN_SPEC_REG_ASSG "7"
/* Stubs for calling non-FAST_FUNC's */
-#if ENABLE_ASH_BUILTIN_ECHO
+#if ENABLE_ASH_ECHO
static int FAST_FUNC echocmd(int argc, char **argv) { return echo_main(argc, argv); }
#endif
-#if ENABLE_ASH_BUILTIN_PRINTF
+#if ENABLE_ASH_PRINTF
static int FAST_FUNC printfcmd(int argc, char **argv) { return printf_main(argc, argv); }
#endif
-#if ENABLE_ASH_BUILTIN_TEST
+#if ENABLE_ASH_TEST || BASH_TEST2
static int FAST_FUNC testcmd(int argc, char **argv) { return test_main(argc, argv); }
#endif
static const struct builtincmd builtintab[] = {
{ BUILTIN_SPEC_REG "." , dotcmd },
{ BUILTIN_SPEC_REG ":" , truecmd },
-#if ENABLE_ASH_BUILTIN_TEST
+#if ENABLE_ASH_TEST
{ BUILTIN_REGULAR "[" , testcmd },
-#if ENABLE_ASH_BASH_COMPAT
- { BUILTIN_REGULAR "[[" , testcmd },
#endif
+#if BASH_TEST2
+ { BUILTIN_REGULAR "[[" , testcmd },
#endif
#if ENABLE_ASH_ALIAS
{ BUILTIN_REG_ASSG "alias" , aliascmd },
{ BUILTIN_REGULAR "command" , commandcmd },
#endif
{ BUILTIN_SPEC_REG "continue", breakcmd },
-#if ENABLE_ASH_BUILTIN_ECHO
+#if ENABLE_ASH_ECHO
{ BUILTIN_REGULAR "echo" , echocmd },
#endif
{ BUILTIN_SPEC_REG "eval" , NULL }, /*evalcmd() has a differing prototype*/
{ BUILTIN_REGULAR "jobs" , jobscmd },
{ BUILTIN_REGULAR "kill" , killcmd },
#endif
-#if ENABLE_SH_MATH_SUPPORT
+#if ENABLE_FEATURE_SH_MATH
{ BUILTIN_NOSPEC "let" , letcmd },
#endif
{ BUILTIN_ASSIGN "local" , localcmd },
-#if ENABLE_ASH_BUILTIN_PRINTF
+#if ENABLE_ASH_PRINTF
{ BUILTIN_REGULAR "printf" , printfcmd },
#endif
{ BUILTIN_NOSPEC "pwd" , pwdcmd },
{ BUILTIN_SPEC_REG "return" , returncmd },
{ BUILTIN_SPEC_REG "set" , setcmd },
{ BUILTIN_SPEC_REG "shift" , shiftcmd },
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_SOURCE
{ BUILTIN_SPEC_REG "source" , dotcmd },
#endif
-#if ENABLE_ASH_BUILTIN_TEST
+#if ENABLE_ASH_TEST
{ BUILTIN_REGULAR "test" , testcmd },
#endif
{ BUILTIN_SPEC_REG "times" , timescmd },
/* Should match the above table! */
#define COMMANDCMD (builtintab + \
/* . : */ 2 + \
- /* [ */ 1 * ENABLE_ASH_BUILTIN_TEST + \
- /* [[ */ 1 * ENABLE_ASH_BUILTIN_TEST * ENABLE_ASH_BASH_COMPAT + \
+ /* [ */ 1 * ENABLE_ASH_TEST + \
+ /* [[ */ 1 * BASH_TEST2 + \
/* alias */ 1 * ENABLE_ASH_ALIAS + \
/* bg */ 1 * ENABLE_ASH_JOB_CONTROL + \
/* break cd cddir */ 3)
#define EVALCMD (COMMANDCMD + \
/* command */ 1 * ENABLE_ASH_CMDCMD + \
/* continue */ 1 + \
- /* echo */ 1 * ENABLE_ASH_BUILTIN_ECHO + \
+ /* echo */ 1 * ENABLE_ASH_ECHO + \
0)
#define EXECCMD (EVALCMD + \
/* eval */ 1)
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;
}
if (status) {
+ bail:
+ exitstatus = status;
+
/* We have a redirection error. */
if (spclbltin > 0)
raise_exception(EXERROR);
- bail:
- exitstatus = status;
+
goto out;
}
if (!(flags & EV_EXIT) || may_have_traps) {
/* No, forking off a child is necessary */
INT_OFF;
+ get_tty_state();
jp = makejob(/*cmd,*/ 1);
if (forkshell(jp, cmd, FORK_FG) != 0) {
/* parent */
/* fall through to exec'ing external program */
}
listsetvar(varlist.list, VEXPORT|VSTACK);
- shellexec(argv, path, cmdentry.u.index);
+ shellexec(argv[0], argv, path, cmdentry.u.index);
/* NOTREACHED */
} /* default */
case CMDBUILTIN:
dowait(DOWAIT_NONBLOCK, NULL);
if (evalbltin(cmdentry.u.cmd, argc, argv, flags)) {
- int exit_status;
- int i = exception_type;
- if (i == EXEXIT)
- goto raise;
- exit_status = 2;
- if (i == EXINT)
- exit_status = 128 + SIGINT;
- if (i == EXSIG)
- exit_status = 128 + pending_sig;
- exitstatus = exit_status;
- if (i == EXINT || spclbltin > 0) {
- raise:
- longjmp(exception_handler->loc, 1);
+ if (exception_type == EXERROR && spclbltin <= 0) {
+ FORCE_INT_ON;
+ goto readstatus;
}
- FORCE_INT_ON;
+ raise:
+ longjmp(exception_handler->loc, 1);
}
goto readstatus;
}
-/* ============ input.c
- *
+/*
* This implements the input routines used by the parser.
*/
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) {
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();
{
struct parsefile *pf = g_parsefile;
+ if (pf == &basepf)
+ return;
+
INT_OFF;
if (pf->pf_fd >= 0)
close(pf->pf_fd);
}
-/* ============ mail.c
- *
+/*
* Routines to check for mail.
*/
#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';
- 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;
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");
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);
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;
}
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 */
union node *vars, **vpp;
union node **rpp, *redir;
int savecheckkwd;
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_TEST2
smallint double_brackets_flag = 0;
- smallint function_flag = 0;
#endif
+ IF_BASH_FUNCTION(smallint function_flag = 0;)
args = NULL;
app = &args;
checkkwd = savecheckkwd;
t = readtoken();
switch (t) {
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_FUNCTION
case TFUNCTION:
if (peektoken() != TWORD)
raise_error_unexpected_syntax(TWORD);
function_flag = 1;
break;
+#endif
+#if BASH_TEST2
case TAND: /* "&&" */
case TOR: /* "||" */
if (!double_brackets_flag) {
n->type = NARG;
/*n->narg.next = NULL; - stzalloc did it */
n->narg.text = wordtext;
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_TEST2
if (strcmp("[[", wordtext) == 0)
double_brackets_flag = 1;
else if (strcmp("]]", wordtext) == 0)
app = &n->narg.next;
savecheckkwd = 0;
}
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_FUNCTION
if (function_flag) {
checkkwd = CHKNL | CHKKWD;
switch (peektoken()) {
parsefname(); /* read name of redirection file */
break;
case TLP:
- IF_ASH_BASH_COMPAT(do_func:)
+ IF_BASH_FUNCTION(do_func:)
if (args && app == &args->narg.next
&& !vars && !redir
) {
const char *name;
/* We have a function */
- if (IF_ASH_BASH_COMPAT(!function_flag &&) readtoken() != TRP)
+ if (IF_BASH_FUNCTION(!function_flag &&) readtoken() != TRP)
raise_error_unexpected_syntax(TRP);
name = n->narg.text;
if (!goodname(name)
n->narg.next = parse_command();
return n;
}
- IF_ASH_BASH_COMPAT(function_flag = 0;)
+ IF_BASH_FUNCTION(function_flag = 0;)
/* fall through */
default:
tokpushback = 1;
n1 = list(0);
t = TEND;
break;
- IF_ASH_BASH_COMPAT(case TFUNCTION:)
+ IF_BASH_FUNCTION(case TFUNCTION:)
case TWORD:
case TREDIR:
tokpushback = 1;
return n1;
}
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_DOLLAR_SQUOTE
static int
decode_dollar_squote(void)
{
smallint quotef;
smallint dblquote;
smallint oldstyle;
- smallint prevsyntax; /* syntax before arithmetic */
-#if ENABLE_ASH_EXPAND_PRMT
+ IF_FEATURE_SH_MATH(smallint prevsyntax;) /* syntax before arithmetic */
smallint pssyntax; /* we are expanding a prompt string */
-#endif
int varnest; /* levels of variables expansion */
- int arinest; /* levels of arithmetic expansion */
- int parenlevel; /* levels of parens in arithmetic */
+ IF_FEATURE_SH_MATH(int arinest;) /* levels of arithmetic expansion */
+ IF_FEATURE_SH_MATH(int parenlevel;) /* levels of parens in arithmetic */
int dqvarnest; /* levels of variables expansion within double quotes */
- IF_ASH_BASH_COMPAT(smallint bash_dollar_squote = 0;)
+ IF_BASH_DOLLAR_SQUOTE(smallint bash_dollar_squote = 0;)
startlinno = g_parsefile->linno;
bqlist = NULL;
quotef = 0;
- prevsyntax = 0;
-#if ENABLE_ASH_EXPAND_PRMT
+ IF_FEATURE_SH_MATH(prevsyntax = 0;)
pssyntax = (syntax == PSSYNTAX);
if (pssyntax)
syntax = DQSYNTAX;
-#endif
dblquote = (syntax == DQSYNTAX);
varnest = 0;
- arinest = 0;
- parenlevel = 0;
+ IF_FEATURE_SH_MATH(arinest = 0;)
+ IF_FEATURE_SH_MATH(parenlevel = 0;)
dqvarnest = 0;
STARTSTACKSTR(out);
USTPUTC(c, out);
break;
case CCTL:
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_DOLLAR_SQUOTE
if (c == '\\' && bash_dollar_squote) {
c = decode_dollar_squote();
if (c == '\0') {
} else if (c == '\n') {
nlprompt();
} else {
-#if ENABLE_ASH_EXPAND_PRMT
if (c == '$' && pssyntax) {
USTPUTC(CTLESC, out);
USTPUTC('\\', out);
}
-#endif
/* Backslash is retained if we are in "str" and next char isn't special */
if (dblquote
&& c != '\\'
dblquote = 1;
goto quotemark;
case CENDQUOTE:
- IF_ASH_BASH_COMPAT(bash_dollar_squote = 0;)
+ IF_BASH_DOLLAR_SQUOTE(bash_dollar_squote = 0;)
if (eofmark != NULL && varnest == 0) {
USTPUTC(c, out);
} else {
}
USTPUTC(c, out);
break;
-#if ENABLE_SH_MATH_SUPPORT
+#if ENABLE_FEATURE_SH_MATH
case CLP: /* '(' in arithmetic */
parenlevel++;
USTPUTC(c, out);
break;
default:
if (varnest == 0) {
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_REDIR_OUTPUT
if (c == '&') {
//Can't call pgetc_eatbnl() here, this requires three-deep pungetc()
if (pgetc() == '>')
} /* for (;;) */
endword:
-#if ENABLE_SH_MATH_SUPPORT
+#if ENABLE_FEATURE_SH_MATH
if (syntax == ARISYNTAX)
raise_error_syntax("missing '))'");
#endif
len = out - (char *)stackblock();
out = stackblock();
if (eofmark == NULL) {
- if ((c == '>' || c == '<' IF_ASH_BASH_COMPAT( || c == 0x100 + '>'))
+ if ((c == '>' || c == '<' IF_BASH_REDIR_OUTPUT( || c == 0x100 + '>'))
&& quotef == 0
) {
if (isdigit_str9(out)) {
pungetc();
}
}
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_REDIR_OUTPUT
else if (c == 0x100 + '>') { /* this flags &> redirection */
np->nfile.fd = 1;
pgetc(); /* this is '>', no need to check */
parsesub: {
unsigned char subtype;
int typeloc;
- int flags = 0;
c = pgetc_eatbnl();
if (c > 255 /* PEOA or PEOF */
|| (c != '(' && c != '{' && !is_name(c) && !is_special(c))
) {
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_DOLLAR_SQUOTE
if (syntax != DQSYNTAX && c == '\'')
bash_dollar_squote = 1;
else
} 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");
/* $VAR, $<specialchar>, ${...}, or PEOA/PEOF */
USTPUTC(CTLVAR, out);
typeloc = out - (char *)stackblock();
- USTPUTC(VSNORMAL, out);
+ STADJUST(1, out);
subtype = VSNORMAL;
if (c == '{') {
c = pgetc_eatbnl();
subtype = 0;
}
varname:
- if (c <= 255 /* not PEOA or PEOF */ && is_name(c)) {
+ 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 {
goto badsub;
}
- flags = 0;
if (subtype == 0) {
static const char types[] ALIGN1 = "}-+?=";
/* ${VAR...} but not $VAR or ${#VAR} */
switch (c) {
case ':':
c = pgetc_eatbnl();
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_SUBSTR
/* This check is only needed to not misinterpret
* ${VAR:-WORD}, ${VAR:+WORD}, ${VAR:=WORD}, ${VAR:?WORD}
* constructs.
break; /* "goto badsub" is bigger (!) */
}
#endif
- flags = VSNUL;
+ subtype = VSNUL;
/*FALLTHROUGH*/
default: {
const char *p = strchr(types, c);
if (p == NULL)
break;
- subtype = p - types + VSNORMAL;
+ subtype |= p - types + VSNORMAL;
break;
}
case '%':
subtype++;
break;
}
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_PATTERN_SUBST
case '/':
/* ${v/[/]pattern/repl} */
//TODO: encode pattern and repl separately.
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 parsebackq_newreturn;
}
-#if ENABLE_SH_MATH_SUPPORT
+#if ENABLE_FEATURE_SH_MATH
/*
* Parse an arithmetic expansion (indicate start of one and set state)
*/
p += xxreadtoken_doubles + 1;
} else {
pungetc();
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_REDIR_OUTPUT
if (c == '&' && cc == '>') /* &> */
break; /* return readtoken1(...) */
#endif
/*
* called by editline -- any expansions to the prompt should be added here.
*/
-#if ENABLE_ASH_EXPAND_PRMT
static const char *
expandstr(const char *ps)
{
expandarg(&n, NULL, EXP_QUOTED);
return stackblock();
}
-#endif
/*
* Execute a command or commands contained in a string.
static int
evalstring(char *s, int flags)
{
+ struct jmploc *volatile savehandler;
+ struct jmploc jmploc;
+ int ex;
+
union node *n;
struct stackmark smark;
int status;
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;
if (evalskip)
break;
}
+ out:
popstackmark(&smark);
popfile();
stunalloc(s);
+ exception_handler = savehandler;
+ if (ex)
+ longjmp(exception_handler->loc, ex);
+
return status;
}
if (strchr(name, '/'))
return name;
- /* IIRC standards do not say whether . is to be searched.
- * And it is even smaller this way, making it unconditional for now:
- */
- if (1) { /* ENABLE_ASH_BASH_COMPAT */
- fullname = name;
- goto try_cur_dir;
- }
-
while ((fullname = path_advance(&path, name)) != NULL) {
- try_cur_dir:
if ((stat(fullname, &statb) == 0) && S_ISREG(statb.st_mode)) {
/*
* Don't bother freeing here, since it will
int status = 0;
char *fullname;
char **argv;
+ char *args_need_save;
struct strlist *sp;
volatile struct shparam saveparam;
*/
fullname = find_dot_file(argv[0]);
argv++;
- if (argv[0]) { /* . FILE ARGS, ARGS exist */
+ args_need_save = argv[0];
+ if (args_need_save) { /* ". FILE ARGS", and ARGS are not empty */
int argc;
saveparam = shellparam;
shellparam.malloced = 0;
status = cmdloop(0);
popfile();
- if (argv[0]) {
+ if (args_need_save) {
freeparam(&shellparam);
shellparam = saveparam;
};
exitcode = 0;
while (*ap) {
signo = get_signum(*ap);
- if (signo < 0) {
+ if (signo < 0 || signo >= NSIG) {
/* Mimic bash message exactly */
ash_msg("%s: invalid signal specification", *ap);
exitcode = 1;
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);
}
flag_off = ~flag_off;
- /*if (opt_p_not_specified) - bash doesnt check this. Try "export -p NAME" */
+ /*if (opt_p_not_specified) - bash doesn't check this. Try "export -p NAME" */
{
aptr = argptr;
name = *aptr;
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.
/* "read -s" needs to save/restore termios, can't allow ^C
* to jump out of it.
*/
+ again:
INT_OFF;
r = shell_builtin_read(setvar0,
argptr,
);
INT_ON;
+ if ((uintptr_t)r == 1 && errno == EINTR) {
+ /* to get SIGCHLD: sleep 1 & read x; echo $x */
+ if (pending_sig == 0)
+ goto again;
+ }
+
if ((uintptr_t)r > 1)
ash_msg_and_raise_error(r);
/* we will never free this */
basepf.next_to_pgetc = basepf.buf = ckmalloc(IBUFSIZ);
- signal(SIGCHLD, SIG_DFL);
+ sigmode[SIGCHLD - 1] = S_DFL;
+ setsignal(SIGCHLD);
+
/* bash re-enables SIGHUP which is SIG_IGNed on entry.
* Try: "trap '' HUP; bash; echo RET" and type "kill -HUP $$"
*/
setvareq((char*)defoptindvar, VTEXTFIXED);
setvar0("PPID", utoa(getppid()));
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_SHLVL_VAR
p = lookupvar("SHLVL");
setvar("SHLVL", utoa((p ? atoi(p) : 0) + 1), VEXPORT);
+#endif
+#if BASH_HOSTNAME_VAR
if (!lookupvar("HOSTNAME")) {
struct utsname uts;
uname(&uts);
//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.
*/
#if DEBUG == 2
debug = 1;
#endif
- /* POSIX 1003.2: first arg after -c cmd is $0, remainder $1... */
+ /* POSIX 1003.2: first arg after "-c CMD" is $0, remainder $1... */
if (xminusc) {
minusc = *xargv++;
if (*xargv)
}
/*
- * Read /etc/profile or .profile.
+ * Read /etc/profile, ~/.profile, $ENV.
*/
static void
read_profile(const char *name)
{
+ name = expandstr(name);
if (setinputfile(name, INPUT_PUSH_FILE | INPUT_NOFILE_OK) < 0)
return;
cmdloop(0);
/*
* 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)
/* 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: */
while (redirlist)
popredir(/*drop:*/ 0, /*restore:*/ 0);
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;
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;
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;
#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) {
if (!hp) {
hp = lookupvar("HOME");
if (hp) {
+ INT_OFF;
hp = concat_path_file(hp, ".ash_history");
setvar0("HISTFILE", hp);
free((char*)hp);
+ INT_ON;
hp = lookupvar("HISTFILE");
}
}