*
* Licensed under GPLv2 or later, see file LICENSE in this source tree.
*/
+//config:config ASH
+//config: bool "ash (77 kb)"
+//config: default y
+//config: depends on !NOMMU
+//config: help
+//config: The most complete and most pedantically correct shell included with
+//config: busybox. This shell is actually a derivative of the Debian 'dash'
+//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 || SH_IS_ASH || BASH_IS_ASH
+//config:
+//config:config ASH_INTERNAL_GLOB
+//config: bool "Use internal glob() implementation"
+//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 || 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: You can reset the generator by using a specified start value.
+//config: After "unset RANDOM" the generator will switch off and this
+//config: variable will no longer have special treatment.
+//config:
+//config:config ASH_EXPAND_PRMT
+//config: bool "Expand prompt string"
+//config: default y
+//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
+//config: variable each time it is displayed.
+//config:
+//config:config ASH_IDLE_TIMEOUT
+//config: bool "Idle timeout variable $TMOUT"
+//config: default y
+//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH
+//config: help
+//config: Enable bash-like auto-logout after $TMOUT seconds of idle time.
+//config:
+//config:config ASH_MAIL
+//config: bool "Check for new mail in interactive shell"
+//config: default y
+//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH
+//config: help
+//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_ECHO
+//config: bool "echo builtin"
+//config: default y
+//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH
+//config:
+//config:config ASH_PRINTF
+//config: bool "printf builtin"
+//config: default y
+//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH
+//config:
+//config:config ASH_TEST
+//config: bool "test builtin"
+//config: default y
+//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 || SH_IS_ASH || BASH_IS_ASH
+//config:
+//config:config ASH_GETOPTS
+//config: bool "getopts builtin"
+//config: default y
+//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH
+//config:
+//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 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_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
/*
- * 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.
+ * 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
#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_SH_MATH_SUPPORT
+#if ENABLE_FEATURE_SH_MATH
# include "math.h"
+#else
+typedef long arith_t;
+# define ARITH_FMT "%ld"
#endif
#if ENABLE_ASH_RANDOM_SUPPORT
# include "random.h"
# error "Do not even bother, ash will not run on NOMMU machine"
#endif
-//config:config ASH
-//config: bool "ash"
-//config: default y
-//config: depends on !NOMMU
-//config: help
-//config: Tha 'ash' shell adds about 60k in the default configuration and is
-//config: the most complete and most pedantically correct shell included with
-//config: busybox. This shell is actually a derivative of the Debian 'dash'
-//config: shell (by Herbert Xu), which was created by porting the 'ash' shell
-//config: (written by Kenneth Almquist) from NetBSD.
-//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:
-//config:config ASH_INTERNAL_GLOB
-//config: bool "Use internal glob() implementation"
-//config: default n
-//config: depends on 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:
-//config:config ASH_RANDOM_SUPPORT
-//config: bool "Pseudorandom generator and $RANDOM variable"
-//config: default y
-//config: depends on ASH
-//config: help
-//config: Enable pseudorandom generator and dynamic variable "$RANDOM".
-//config: Each read of "$RANDOM" will generate a new pseudorandom value.
-//config: You can reset the generator by using a specified start value.
-//config: After "unset RANDOM" the generator will switch off and this
-//config: variable will no longer have special treatment.
-//config:
-//config:config ASH_EXPAND_PRMT
-//config: bool "Expand prompt string"
-//config: default y
-//config: depends on ASH
-//config: help
-//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: default y
-//config: depends on 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: 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: 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: 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: 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: 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: 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:
-//config:config ASH_MAIL
-//config: bool "Check for new mail on interactive shells"
-//config: default n
-//config: depends on ASH
-//config: help
-//config: Enable "check for new mail" function in the ash shell.
-//config:
-
-//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))
-
-//kbuild:lib-$(CONFIG_ASH) += ash.o ash_ptr_hack.o shell_common.o
-//kbuild:lib-$(CONFIG_ASH_RANDOM_SUPPORT) += random.o
-
/* ============ Hash table sizes. Configurable. */
"b" "notify",
"u" "nounset",
"\0" "vi"
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_PIPEFAIL
,"\0" "pipefail"
#endif
#if DEBUG
#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 */
* 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
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)
{
va_list ap;
int ret;
- va_start(ap, fmt);
INT_OFF;
+ va_start(ap, fmt);
ret = vsnprintf(outbuf, length, fmt, ap);
va_end(ap);
INT_ON;
#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
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;
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 */
/* ============ Message printing */
{
va_list ap;
+ exitstatus = 2;
+
va_start(ap, msg);
ash_vmsg_and_raise(EXERROR, msg, ap);
/* NOTREACHED */
stack_nputstr(const char *s, size_t n, char *p)
{
p = makestrspace(n, p);
- p = (char *)memcpy(p, s, n) + n;
+ p = (char *)mempcpy(p, s, n);
return p;
}
}
/*
- * Produce a possibly single quoted string suitable as input to the shell.
+ * Produce a single quoted string suitable as input to the shell.
* The return string is allocated on the stack.
*/
static char *
q = p = makestrspace(len + 3, p);
*q++ = '\'';
- q = (char *)memcpy(q, s, len) + len;
+ q = (char *)mempcpy(q, s, len);
*q++ = '\'';
s += len;
q = p = makestrspace(len + 3, p);
*q++ = '"';
- q = (char *)memcpy(q, s - len, len) + len;
+ q = (char *)mempcpy(q, s - len, len);
*q++ = '"';
STADJUST(q - p, p);
return stackblock();
}
+/*
+ * Produce a possibly single quoted string suitable as input to the shell.
+ * If quoting was done, the return string is allocated on the stack,
+ * otherwise a pointer to the original string is returned.
+ */
+static const char *
+maybe_single_quote(const char *s)
+{
+ const char *p = s;
+
+ while (*p) {
+ /* Assuming ACSII */
+ /* quote ctrl_chars space !"#$%&'()* */
+ if (*p < '+')
+ goto need_quoting;
+ /* quote ;<=>? */
+ if (*p >= ';' && *p <= '?')
+ goto need_quoting;
+ /* quote `[\ */
+ if (*p == '`')
+ goto need_quoting;
+ if (*p == '[')
+ goto need_quoting;
+ if (*p == '\\')
+ goto need_quoting;
+ /* quote {|}~ DEL and high bytes */
+ if (*p > 'z')
+ goto need_quoting;
+ /* Not quoting these: +,-./ 0-9 :@ A-Z ]^_ a-z */
+ /* TODO: maybe avoid quoting % */
+ p++;
+ }
+ return s;
+
+ need_quoting:
+ return single_quote(s);
+}
+
/* ============ nextopt */
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.
*/
-static const char *
+static ALWAYS_INLINE const char *
bltinlookup(const char *name)
{
- struct strlist *sp;
-
- for (sp = cmdenviron; sp; sp = sp->next) {
- if (varcmp(sp->text, name) == 0)
- return var_end(sp->text);
- }
return lookupvar(name);
}
* will go away.
* Called with interrupts off.
*/
-static void
+static struct var *
setvareq(char *s, int flags)
{
struct var *vp, **vpp;
vpp = hashvar(s);
flags |= (VEXPORT & (((unsigned) (1 - aflag)) - 1));
- vp = *findvar(vpp, s);
+ vpp = findvar(vpp, s);
+ vp = *vpp;
if (vp) {
if ((vp->flags & (VREADONLY|VDYNAMIC)) == VREADONLY) {
const char *n;
}
if (flags & VNOSET)
- return;
+ goto out;
if (vp->var_func && !(flags & VNOFUNC))
vp->var_func(var_end(s));
if (!(vp->flags & (VTEXTFIXED|VSTACK)))
free((char*)vp->var_text);
+ if (((flags & (VEXPORT|VREADONLY|VSTRFIXED|VUNSET)) | (vp->flags & VSTRFIXED)) == VUNSET) {
+ *vpp = vp->next;
+ free(vp);
+ out_free:
+ if ((flags & (VTEXTFIXED|VSTACK|VNOSAVE)) == VNOSAVE)
+ free(s);
+ goto out;
+ }
+
flags |= vp->flags & ~(VTEXTFIXED|VSTACK|VNOSAVE|VUNSET);
} else {
/* variable s is not found */
if (flags & VNOSET)
- return;
+ goto out;
+ if ((flags & (VEXPORT|VREADONLY|VSTRFIXED|VUNSET)) == VUNSET)
+ goto out_free;
vp = ckzalloc(sizeof(*vp));
vp->next = *vpp;
/*vp->func = NULL; - ckzalloc did it */
s = ckstrdup(s);
vp->var_text = s;
vp->flags = flags;
+
+ out:
+ return vp;
}
/*
* Set the value of a variable. The flags argument is ored with the
* flags of the variable. If val is NULL, the variable is unset.
*/
-static void
+static struct var *
setvar(const char *name, const char *val, int flags)
{
const char *q;
char *nameeq;
size_t namelen;
size_t vallen;
+ struct var *vp;
q = endofname(name);
p = strchrnul(q, '=');
INT_OFF;
nameeq = ckmalloc(namelen + vallen + 2);
- p = memcpy(nameeq, name, namelen) + namelen;
+ p = mempcpy(nameeq, name, namelen);
if (val) {
*p++ = '=';
- p = memcpy(p, val, vallen) + vallen;
+ p = mempcpy(p, val, vallen);
}
*p = '\0';
- setvareq(nameeq, flags | VNOSAVE);
+ vp = setvareq(nameeq, flags | VNOSAVE);
INT_ON;
+
+ return vp;
}
static void FAST_FUNC
/*
* Unset the specified variable.
*/
-static int
+static void
unsetvar(const char *s)
{
- struct var **vpp;
- struct var *vp;
- int retval;
-
- vpp = findvar(hashvar(s), s);
- vp = *vpp;
- retval = 2;
- if (vp) {
- int flags = vp->flags;
-
- retval = 1;
- if (flags & VREADONLY)
- goto out;
-#if ENABLE_ASH_RANDOM_SUPPORT
- vp->flags &= ~VDYNAMIC;
-#endif
- if (flags & VUNSET)
- goto ok;
- if ((flags & VSTRFIXED) == 0) {
- INT_OFF;
- if ((flags & (VTEXTFIXED|VSTACK)) == 0)
- free((char*)vp->var_text);
- *vpp = vp->next;
- free(vp);
- INT_ON;
- } else {
- setvar0(s, NULL);
- vp->flags &= ~VEXPORT;
- }
- ok:
- retval = 0;
- }
- out:
- return retval;
+ setvar(s, NULL, 0);
}
/*
growstackblock();
q = stackblock();
if (p != start) {
- memcpy(q, start, p - start);
- q += p - start;
+ q = mempcpy(q, start, p - start);
*q++ = '/';
}
strcpy(q, name);
}
#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
}
#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 */
/* 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
+get_tty_state(void)
+{
+ 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)
{
}
/* 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;
col = 0;
if (!WIFEXITED(status)) {
- if (JOBS && WIFSTOPPED(status))
+#if JOBS
+ if (WIFSTOPPED(status))
st = WSTOPSIG(status);
else
+#endif
st = WTERMSIG(status);
if (sigonly) {
if (st == SIGINT || st == SIGPIPE)
goto out;
- if (JOBS && WIFSTOPPED(status))
+#if JOBS
+ if (WIFSTOPPED(status))
goto out;
+#endif
}
st &= 0x7f;
//TODO: use bbox's get_signame? strsignal adds ~600 bytes to text+rodata
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 */
break; /* Error (e.g. EINTR, ECHILD) or pid */
/* Children exist, but none are ready. Sleep until interesting signal */
-#if 0 /* dash does this */
- sigset_t mask;
+#if 1
sigfillset(&mask);
sigprocmask(SIG_SETMASK, &mask, &mask);
while (!got_sigchld && !pending_sig)
sigsuspend(&mask);
sigprocmask(SIG_SETMASK, &mask, NULL);
-#else
+#else /* unsafe: a signal can set pending_sig after check, but before pause() */
while (!got_sigchld && !pending_sig)
pause();
#endif
* 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 pause().
+ * which combines waitpid() and sigsuspend().
* This is the reason why we need to have a handler for SIGCHLD:
- * SIG_DFL handler does not wake pause().
+ * SIG_DFL handler does not wake sigsuspend().
*/
INT_OFF;
if (block == DOWAIT_BLOCK_OR_SIG) {
goto out;
}
/* The process wasn't found in job list */
- if (JOBS && !WIFSTOPPED(status))
+#if JOBS
+ if (!WIFSTOPPED(status))
jobless--;
+#endif
out:
INT_ON;
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:
#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
#define EMPTY -2 /* marks an unused slot in redirtab */
#define CLOSED -3 /* marks a slot of previously-closed fd */
-/*
- * Open a file in noclobber mode.
- * The code was copied from bash.
- */
-static int
-noclobberopen(const char *fname)
-{
- int r, fd;
- struct stat finfo, finfo2;
-
- /*
- * If the file exists and is a regular file, return an error
- * immediately.
- */
- r = stat(fname, &finfo);
- if (r == 0 && S_ISREG(finfo.st_mode)) {
- errno = EEXIST;
- return -1;
- }
-
- /*
- * If the file was not present (r != 0), make sure we open it
- * exclusively so that if it is created before we open it, our open
- * will fail. Make sure that we do not truncate an existing file.
- * Note that we don't turn on O_EXCL unless the stat failed -- if the
- * file was not a regular file, we leave O_EXCL off.
- */
- if (r != 0)
- return open(fname, O_WRONLY|O_CREAT|O_EXCL, 0666);
- fd = open(fname, O_WRONLY|O_CREAT, 0666);
-
- /* If the open failed, return the file descriptor right away. */
- if (fd < 0)
- return fd;
-
- /*
- * OK, the open succeeded, but the file may have been changed from a
- * non-regular file to a regular file between the stat and the open.
- * We are assuming that the O_EXCL open handles the case where FILENAME
- * did not exist and is symlinked to an existing file between the stat
- * and open.
- */
-
- /*
- * If we can open it and fstat the file descriptor, and neither check
- * revealed that it was a regular file, and the file has not been
- * replaced, return the file descriptor.
- */
- if (fstat(fd, &finfo2) == 0
- && !S_ISREG(finfo2.st_mode)
- && finfo.st_dev == finfo2.st_dev
- && finfo.st_ino == finfo2.st_ino
- ) {
- return fd;
- }
-
- /* The file has been replaced. badness. */
- close(fd);
- errno = EEXIST;
- return -1;
-}
-
/*
* Handle here documents. Normally we fork off a process to write the
* data to a pipe. If the document is short, we can stuff the data in
static int
openredirect(union node *redir)
{
+ struct stat sb;
char *fname;
int f;
goto ecreate;
break;
case NTO:
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_REDIR_OUTPUT
case NTO2:
#endif
/* Take care of noclobber mode. */
if (Cflag) {
- f = noclobberopen(fname);
- if (f < 0)
+ if (stat(fname, &sb) < 0) {
+ f = open(fname, O_WRONLY|O_CREAT|O_EXCL, 0666);
+ if (f < 0)
+ goto ecreate;
+ } else if (!S_ISREG(sb.st_mode)) {
+ f = open(fname, O_WRONLY, 0666);
+ if (f < 0)
+ goto ecreate;
+ if (fstat(f, &sb) < 0 && S_ISREG(sb.st_mode)) {
+ close(f);
+ errno = EEXIST;
+ goto ecreate;
+ }
+ } else {
+ errno = EEXIST;
goto ecreate;
+ }
break;
}
/* FALLTHROUGH */
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)
{
#define RMESCAPE_SLASH 0x20 /* Stop globbing after slash */
/* Add CTLESC when necessary. */
-#define QUOTES_ESC (EXP_FULL | EXP_CASE | EXP_QPAT | EXP_REDIR)
+#define QUOTES_ESC (EXP_FULL | EXP_CASE | EXP_QPAT)
/* Do not skip NUL characters. */
#define QUOTES_KEEPNUL EXP_TILDE
/*
* Our own itoa().
+ * cvtnum() is used even if math support is off (to prepare $? values and such).
*/
-#if !ENABLE_SH_MATH_SUPPORT
-/* cvtnum() is used even if math support is off (to prepare $? values and such) */
-typedef long arith_t;
-# define ARITH_FMT "%ld"
-#endif
static int
cvtnum(arith_t num)
{
int len;
- expdest = makestrspace(sizeof(arith_t)*3 + 2, expdest);
- len = fmtstr(expdest, sizeof(arith_t)*3 + 2, ARITH_FMT, num);
+ /* 32-bit and wider ints require buffer size of bytes*3 (or less) */
+ len = sizeof(arith_t) * 3;
+ /* If narrower: worst case, 1-byte ints: need 5 bytes: "-127<NUL>" */
+ if (sizeof(arith_t) < 4) len += 2;
+
+ expdest = makestrspace(len, expdest);
+ len = fmtstr(expdest, len, ARITH_FMT, num);
STADJUST(len, expdest);
return len;
}
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;
}
q = r;
if (len > 0) {
- q = (char *)memcpy(q, str, len) + len;
+ q = (char *)mempcpy(q, str, len);
}
}
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;
};
/* These forward decls are needed to use "eval" code for backticks handling: */
-#define EV_EXIT 01 /* exit after evaluating tree */
+/* flags in argument to evaltree */
+#define EV_EXIT 01 /* exit after evaluating tree */
+#define EV_TESTED 02 /* exit status is checked; ignore -e flag */
static int evaltree(union node *, int);
static void FAST_FUNC
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.
#endif
/* argstr needs it */
-static char *evalvar(char *p, int flags, struct strlist *var_str_list);
+static char *evalvar(char *p, int flags);
/*
* Perform variable and command substitution. If EXP_FULL is set, output CTLESC
* characters to allow for further processing. Otherwise treat
* $@ 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
- * for correct expansion of "B=$A" word.
*/
static void
-argstr(char *p, int flags, struct strlist *var_str_list)
+argstr(char *p, int flags)
{
static const char spclchars[] ALIGN1 = {
'=',
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++;
inquotes ^= EXP_QUOTED;
/* "$@" syntax adherence hack */
if (inquotes && !memcmp(p, dolatstr + 1, DOLATSTRLEN - 1)) {
- p = evalvar(p + 1, flags | inquotes, /* var_str_list: */ NULL) + 1;
+ p = evalvar(p + 1, flags | inquotes) + 1;
goto start;
}
addquote:
goto addquote;
case CTLVAR:
TRACE(("argstr: evalvar('%s')\n", p));
- p = evalvar(p, flags | inquotes, var_str_list);
+ p = evalvar(p, flags | inquotes);
TRACE(("argstr: evalvar:'%s'\n", (char *)stackblock()));
goto start;
case CTLBACKQ:
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 "*"? */
static const char *
subevalvar(char *p, char *varname, int strloc, int subtype,
- int startloc, int varflags, int flag, struct strlist *var_str_list)
+ int startloc, int varflags, int flag)
{
struct nodelist *saveargbackq = argbackq;
int quotes = flag & QUOTES_ESC;
char *loc;
char *rmesc, *rmescend;
char *str;
- IF_ASH_BASH_COMPAT(char *repl = NULL;)
- IF_ASH_BASH_COMPAT(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);
// p, varname, strloc, subtype, startloc, varflags, quotes);
argstr(p, EXP_TILDE | (subtype != VSASSIGN && subtype != VSQUESTION ?
- (flag & (EXP_QUOTED | EXP_QPAT) ? EXP_QPAT : EXP_CASE) : 0),
- var_str_list);
+ (flag & (EXP_QUOTED | EXP_QPAT) ? EXP_QPAT : EXP_CASE) : 0)
+ );
STPUTC('\0', expdest);
argbackq = saveargbackq;
startp = (char *)stackblock() + startloc;
varunset(p, varname, startp, varflags);
/* NOTREACHED */
-#if ENABLE_ASH_BASH_COMPAT
- case VSSUBSTR:
-//TODO: support more general format ${v:EXPR:EXPR},
-// where EXPR follows $(()) rules
+#if BASH_SUBSTR
+ case VSSUBSTR: {
+ int pos, len, orig_len;
+ char *colon;
+
loc = str = stackblock() + strloc;
+
+# if !ENABLE_FEATURE_SH_MATH
+# define ash_arith number
+# endif
/* Read POS in ${var:POS:LEN} */
- pos = atoi(loc); /* number(loc) errors out on "1:4" */
- len = str - startp - 1;
+ colon = strchr(loc, ':');
+ if (colon) *colon = '\0';
+ pos = ash_arith(loc);
+ if (colon) *colon = ':';
+ /* Read LEN in ${var:POS:LEN} */
+ len = str - startp - 1;
/* *loc != '\0', guaranteed by parser */
if (quotes) {
char *ptr;
}
}
orig_len = len;
-
if (*loc++ == ':') {
/* ${var::LEN} */
- len = number(loc);
+ len = ash_arith(loc);
} else {
/* Skip POS in ${var:POS:LEN} */
len = orig_len;
while (*loc && *loc != ':') {
- /* TODO?
- * bash complains on: var=qwe; echo ${var:1a:123}
- if (!isdigit(*loc))
- ash_msg_and_raise_error(msg_illnum, str);
- */
loc++;
}
if (*loc++ == ':') {
- len = number(loc);
+ len = ash_arith(loc);
}
}
+# undef ash_arith
+
if (pos < 0) {
/* ${VAR:$((-n)):l} starts n chars from the end */
pos = orig_len + pos;
if ((unsigned)pos >= orig_len) {
/* apart from obvious ${VAR:999999:l},
* covers ${VAR:$((-9999999)):l} - result is ""
- * (bash-compat)
+ * (bash compat)
*/
pos = 0;
len = 0;
}
- if (len > (orig_len - pos))
+ if (len < 0) {
+ /* ${VAR:N:-M} sets LEN to strlen()-M */
+ len = (orig_len - pos) + len;
+ }
+ if ((unsigned)len > (orig_len - pos))
len = orig_len - pos;
for (str = startp; pos; str++, pos--) {
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) {
+ int len;
char *idx, *end;
if (!repl) {
STADJUST(-amount, expdest);
return startp;
}
-#endif /* ENABLE_ASH_BASH_COMPAT */
+#endif /* BASH_PATTERN_SUBST */
subtype -= VSTRIMRIGHT;
#if DEBUG
* ash -c 'echo ${#1#}' name:'1=#'
*/
static NOINLINE ssize_t
-varvalue(char *name, int varflags, int flags, struct strlist *var_str_list, int *quotedp)
+varvalue(char *name, int varflags, int flags, int *quotedp)
{
const char *p;
int num;
goto value;
default:
/* NB: name has form "VAR=..." */
-
- /* "A=a B=$A" case: var_str_list is a list of "A=a" strings
- * which should be considered before we check variables. */
- if (var_str_list) {
- unsigned name_len = (strchrnul(name, '=') - name) + 1;
- p = NULL;
- do {
- char *str, *eq;
- str = var_str_list->text;
- eq = strchr(str, '=');
- if (!eq) /* stop at first non-assignment */
- break;
- eq++;
- if (name_len == (unsigned)(eq - str)
- && strncmp(str, name, name_len) == 0
- ) {
- p = eq;
- /* goto value; - WRONG! */
- /* think "A=1 A=2 B=$A" */
- }
- var_str_list = var_str_list->next;
- } while (var_str_list);
- if (p)
- goto value;
- }
p = lookupvar(name);
value:
if (!p)
* input string.
*/
static char *
-evalvar(char *p, int flag, struct strlist *var_str_list)
+evalvar(char *p, int flag)
{
char varflags;
char subtype;
p = strchr(p, '=') + 1; //TODO: use var_end(p)?
again:
- varlen = varvalue(var, varflags, flag, var_str_list, "ed);
+ varlen = varvalue(var, varflags, flag, "ed);
if (varflags & VSNUL)
varlen--;
if (varlen < 0) {
argstr(
p,
- flag | EXP_TILDE | EXP_WORD,
- var_str_list
+ flag | EXP_TILDE | EXP_WORD
);
goto end;
}
goto record;
subevalvar(p, var, 0, subtype, startloc, varflags,
- flag & ~QUOTES_ESC, var_str_list);
+ flag & ~QUOTES_ESC);
varflags &= ~VSNUL;
/*
* Remove any recorded regions beyond
case VSTRIMLEFTMAX:
case VSTRIMRIGHT:
case VSTRIMRIGHTMAX:
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_SUBSTR
case VSSUBSTR:
+#endif
+#if BASH_PATTERN_SUBST
case VSREPLACE:
case VSREPLACEALL:
#endif
STPUTC('\0', expdest);
patloc = expdest - (char *)stackblock();
if (NULL == subevalvar(p, /* varname: */ NULL, patloc, subtype,
- startloc, varflags, flag, var_str_list)) {
+ startloc, varflags, flag)) {
int amount = expdest - (
(char *)stackblock() + patloc - 1
);
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);
TRACE(("expandarg: argstr('%s',flags:%x)\n", arg->narg.text, flag));
- argstr(arg->narg.text, flag,
- /* var_str_list: */ arglist ? arglist->list : NULL);
+ argstr(arg->narg.text, flag);
p = _STPUTC('\0', expdest);
expdest = p - 1;
if (arglist == NULL) {
exparg.lastp = &exparg.list;
expandmeta(exparg.list /*, flag*/);
} else {
- if (flag & EXP_REDIR) { /*XXX - for now, just remove escapes */
- rmescapes(p, 0);
- TRACE(("expandarg: rmescapes:'%s'\n", p));
- }
sp = stzalloc(sizeof(*sp));
sp->text = p;
*exparg.lastp = sp;
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);
- argstr(pattern->narg.text, EXP_TILDE | EXP_CASE,
- /* var_str_list: */ NULL);
+ argstr(pattern->narg.text, EXP_TILDE | EXP_CASE);
STACKSTRNUL(expdest);
ifsfree();
result = patmatch(stackblock(), val);
clearenv();
while (*envp)
putenv(*envp++);
- run_applet_no_and_exit(applet_no, argv);
+ popredir(/*drop:*/ 1, /*restore:*/ 0);
+ run_applet_no_and_exit(applet_no, cmd, argv);
}
/* re-exec ourselves with the new arguments */
execve(bb_busybox_exec_path, argv, envp);
* 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);
}
static void *funcblock; /* block to allocate function 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 */
-#define EV_TESTED 02 /* exit status is checked; ignore -e flag */
-
static const uint8_t nodesize[N_NUMBER] ALIGN1 = {
[NCMD ] = SHELL_ALIGN(sizeof(struct ncmd)),
[NPIPE ] = SHELL_ALIGN(sizeof(struct npipe)),
[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:
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 */
}
/* 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) {
#endif
}
-static struct localvar *localvars;
+struct localvar_list {
+ struct localvar_list *next;
+ struct localvar *lv;
+};
+
+static struct localvar_list *localvar_stack;
/*
* Called after a function returns.
* Interrupts must be off.
*/
static void
-poplocalvars(void)
+poplocalvars(int keep)
{
- struct localvar *lvp;
+ struct localvar_list *ll;
+ struct localvar *lvp, *next;
struct var *vp;
- while ((lvp = localvars) != NULL) {
- localvars = lvp->next;
+ INT_OFF;
+ ll = localvar_stack;
+ localvar_stack = ll->next;
+
+ next = ll->lv;
+ free(ll);
+
+ while ((lvp = next) != NULL) {
+ next = lvp->next;
vp = lvp->vp;
TRACE(("poplocalvar %s\n", vp ? vp->var_text : "-"));
- if (vp == NULL) { /* $- saved */
+ if (keep) {
+ int bits = VSTRFIXED;
+
+ if (lvp->flags != VUNSET) {
+ if (vp->var_text == lvp->text)
+ bits |= VTEXTFIXED;
+ else if (!(lvp->flags & (VTEXTFIXED|VSTACK)))
+ free((char*)lvp->text);
+ }
+
+ vp->flags &= ~bits;
+ vp->flags |= (lvp->flags & bits);
+
+ if ((vp->flags &
+ (VEXPORT|VREADONLY|VSTRFIXED|VUNSET)) == VUNSET)
+ unsetvar(vp->var_text);
+ } else if (vp == NULL) { /* $- saved */
memcpy(optlist, lvp->text, sizeof(optlist));
free((char*)lvp->text);
optschanged();
- } else if ((lvp->flags & (VUNSET|VSTRFIXED)) == VUNSET) {
+ } else if (lvp->flags == VUNSET) {
+ vp->flags &= ~(VSTRFIXED|VREADONLY);
unsetvar(vp->var_text);
} else {
if (vp->var_func)
}
free(lvp);
}
+ INT_ON;
+}
+
+/*
+ * Create a new localvar environment.
+ */
+static struct localvar_list *
+pushlocalvars(void)
+{
+ struct localvar_list *ll;
+
+ INT_OFF;
+ ll = ckzalloc(sizeof(*ll));
+ /*ll->lv = NULL; - zalloc did it */
+ ll->next = localvar_stack;
+ localvar_stack = ll;
+ INT_ON;
+
+ return ll->next;
+}
+
+static void
+unwindlocalvars(struct localvar_list *stop)
+{
+ while (localvar_stack != stop)
+ poplocalvars(0);
}
static int
evalfun(struct funcnode *func, int argc, char **argv, int flags)
{
volatile struct shparam saveparam;
- struct localvar *volatile savelocalvars;
struct jmploc *volatile savehandler;
struct jmploc jmploc;
int e;
saveparam = shellparam;
- savelocalvars = localvars;
savehandler = exception_handler;
e = setjmp(jmploc.loc);
if (e) {
}
INT_OFF;
exception_handler = &jmploc;
- localvars = NULL;
shellparam.malloced = 0;
func->count++;
funcnest++;
shellparam.optind = 1;
shellparam.optoff = -1;
#endif
+ pushlocalvars();
evaltree(func->n.narg.next, flags & EV_TESTED);
+ poplocalvars(0);
funcdone:
INT_OFF;
funcnest--;
freefunc(func);
- poplocalvars();
- localvars = savelocalvars;
freeparam(&shellparam);
shellparam = saveparam;
exception_handler = savehandler;
* x=0; f() { local x=1; echo $x; local x; echo $x; }; f; echo $x
* x=0; f() { local x=1; echo $x; local x=2; echo $x; }; f; echo $x
*/
- lvp = localvars;
+ lvp = localvar_stack->lv;
while (lvp) {
if (lvp->vp && varcmp(lvp->vp->var_text, name) == 0) {
if (eq)
/* else:
* it's a duplicate "local VAR" declaration, do nothing
*/
- return;
+ goto ret;
}
lvp = lvp->next;
}
if (vp == NULL) {
/* variable did not exist yet */
if (eq)
- setvareq(name, VSTRFIXED);
+ vp = setvareq(name, VSTRFIXED);
else
- setvar(name, NULL, VSTRFIXED);
- vp = *vpp; /* the new variable */
+ vp = setvar(name, NULL, VSTRFIXED);
lvp->flags = VUNSET;
} else {
lvp->text = vp->var_text;
}
}
lvp->vp = vp;
- lvp->next = localvars;
- localvars = lvp;
+ lvp->next = localvar_stack->lv;
+ localvar_stack->lv = lvp;
+ ret:
INT_ON;
}
{
char *name;
- if (!funcnest)
+ if (!localvar_stack)
ash_msg_and_raise_error("not in a function");
argv = argptr;
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
+ { BUILTIN_SPEC_REG_ASSG "local" , localcmd },
+#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)
static const struct builtincmd null_bltin = {
"\0\0", bltincmd /* why three NULs? */
};
+ struct localvar_list *localvar_stop;
struct stackmark smark;
union node *argp;
struct arglist arglist;
/* First expand the arguments. */
TRACE(("evalcommand(0x%lx, %d) called\n", (long)cmd, flags));
setstackmark(&smark);
+ localvar_stop = pushlocalvars();
back_exitstatus = 0;
cmdentry.cmdtype = CMDBUILTIN;
spp = varlist.lastp;
expandarg(argp, &varlist, EXP_VARTILDE);
+ mklocal((*spp)->text);
+
/*
* Modify the command lookup path, if a PATH= assignment
* is present
/* Print the command if xflag is set. */
if (xflag) {
- int n;
- const char *p = " %s" + 1;
+ const char *pfx = "";
+
+ fdprintf(preverrout_fd, "%s", expandstr(ps4val()));
- fdprintf(preverrout_fd, p, expandstr(ps4val()));
sp = varlist.list;
- for (n = 0; n < 2; n++) {
- while (sp) {
- fdprintf(preverrout_fd, p, sp->text);
- sp = sp->next;
- p = " %s";
- }
- sp = arglist.list;
+ while (sp) {
+ char *varval = sp->text;
+ char *eq = strchrnul(varval, '=');
+ if (*eq)
+ eq++;
+ fdprintf(preverrout_fd, "%s%.*s%s",
+ pfx,
+ (int)(eq - varval), varval,
+ maybe_single_quote(eq)
+ );
+ sp = sp->next;
+ pfx = " ";
+ }
+
+ sp = arglist.list;
+ while (sp) {
+ fdprintf(preverrout_fd, "%s%s",
+ pfx,
+ /* always quote if matches reserved word: */
+ findkwd(sp->text)
+ ? single_quote(sp->text)
+ : maybe_single_quote(sp->text)
+ );
+ sp = sp->next;
+ pfx = " ";
}
safe_write(preverrout_fd, "\n", 1);
}
}
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:
- cmdenviron = varlist.list;
- if (cmdenviron) {
- struct strlist *list = cmdenviron;
- int i = VNOSET;
- if (spclbltin > 0 || argc == 0) {
- i = 0;
- if (cmd_is_exec && argc > 1)
- i = VEXPORT;
- }
- listsetvar(list, i);
+ if (spclbltin > 0 || argc == 0) {
+ poplocalvars(1);
+ if (cmd_is_exec && argc > 1)
+ listsetvar(varlist.list, VEXPORT);
}
+
/* Tight loop with builtins only:
* "while kill -0 $child; do true; done"
* will never exit even if $child died, unless we do this
goto readstatus;
case CMDFUNCTION:
- listsetvar(varlist.list, 0);
+ poplocalvars(1);
/* See above for the rationale */
dowait(DOWAIT_NONBLOCK, NULL);
if (evalfun(cmdentry.u.func, argc, argv, flags))
out:
if (cmd->ncmd.redirect)
popredir(/*drop:*/ cmd_is_exec, /*restore:*/ cmd_is_exec);
+ unwindlocalvars(localvar_stop);
if (lastarg) {
/* dsl: I think this is intended to be used to support
* '_' in 'vi' command mode during line editing...
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();
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 */
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");
+ raise_error_syntax("support for $((arith)) is disabled");
#endif
} else {
pungetc();
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.
subtype++;
break;
}
-#if ENABLE_ASH_BASH_COMPAT
+#if BASH_PATTERN_SUBST
case '/':
/* ${v/[/]pattern/repl} */
//TODO: encode pattern and repl separately.
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
+
+static inline int
+parser_eof(void)
+{
+ return tokpushback && lasttoken == TEOF;
+}
/*
* Execute a command or commands contained in a string.
while ((n = parsecmd(0)) != NODE_EOF) {
int i;
- i = evaltree(n, flags);
+ i = evaltree(n, flags & ~(parser_eof() ? 0 : EV_EXIT));
if (n)
status = i;
popstackmark(&smark);
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;
- struct strlist *sp;
+ char *args_need_save;
volatile struct shparam saveparam;
- for (sp = cmdenviron; sp; sp = sp->next)
- setvareq(ckstrdup(sp->text), VSTRFIXED | VTEXTFIXED);
+//???
+// struct strlist *sp;
+// for (sp = cmdenviron; sp; sp = sp->next)
+// setvareq(ckstrdup(sp->text), VSTRFIXED | VTEXTFIXED);
nextopt(nullstr); /* handle possible "--" */
argv = argptr;
*/
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;
};
return 0;
}
+ /* Why the second check?
+ * "trap NUM [sig2]..." is the same as "trap - NUM [sig2]..."
+ * In this case, NUM is signal no, not an action.
+ */
action = NULL;
- if (ap[1])
+ if (ap[1] && !is_number(ap[0]))
action = *ap++;
+
exitcode = 0;
while (*ap) {
signo = get_signum(*ap);
}
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;
char **ap;
int i;
int flag = 0;
- int ret = 0;
while ((i = nextopt("vf")) != 0) {
flag = i;
for (ap = argptr; *ap; ap++) {
if (flag != 'f') {
- i = unsetvar(*ap);
- ret |= i;
- if (!(i & 2))
- continue;
+ unsetvar(*ap);
+ continue;
}
if (flag != 'v')
unsetfunc(*ap);
}
- return ret & 1;
+ return 0;
}
static const unsigned char timescmd_str[] ALIGN1 = {
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);
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)
/* from redir.c: */
while (redirlist)
popredir(/*drop:*/ 0, /*restore:*/ 0);
+
+ /* from var.c: */
+ unwindlocalvars(NULL);
}
#if PROFILE
// if (!sflag) g_parsefile->pf_fd = -1;
// ^^ not necessary since now we special-case fd 0
// in is_hidden_fd() to not be considered "hidden fd"
- evalstring(minusc, 0);
+ evalstring(minusc, sflag ? 0 : EV_EXIT);
}
if (sflag || minusc == NULL) {
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");
}
}