//config: default y
//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH
//config:
+//config:config ASH_BASH_SOURCE_CURDIR
+//config: bool "'source' and '.' builtins search current directory after $PATH"
+//config: default n # do not encourage non-standard behavior
+//config: depends on ASH_BASH_COMPAT
+//config: help
+//config: This is not compliant with standards. Avoid if possible.
+//config:
+//config:config ASH_BASH_NOT_FOUND_HOOK
+//config: bool "command_not_found_handle hook support"
+//config: default y
+//config: depends on ASH_BASH_COMPAT
+//config: help
+//config: Enable support for the 'command_not_found_handle' hook function,
+//config: from GNU bash, which allows for alternative command not found
+//config: handling.
+//config:
//config:config ASH_JOB_CONTROL
//config: bool "Job control"
//config: default y
//config: you to run the specified command or builtin,
//config: even when there is a function with the same name.
//config:
+//config:config ASH_EMBEDDED_SCRIPTS
+//config: bool "Embed scripts in the binary"
+//config: default y
+//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH
+//config: help
+//config: Allow scripts to be compressed and embedded in the busybox
+//config: binary. The scripts should be placed in the 'embed' directory
+//config: at build time. Like applets, scripts can be run as
+//config: 'busybox SCRIPT ...' or by linking their name to the binary.
+//config:
+//config: This also allows applets to be implemented as scripts: place
+//config: the script in 'applets_sh' and a stub C file containing
+//config: configuration in the appropriate subsystem directory.
+//config:
//config:endif # ash options
//applet:IF_ASH(APPLET(ash, BB_DIR_BIN, BB_SUID_DROP))
#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 ENABLE_ASH_EMBEDDED_SCRIPTS
+# include "embedded_scripts.h"
+#else
+# define NUM_SCRIPTS 0
+#endif
/* So far, all bash compat is controlled by one config option */
/* Separate defines document which part of code implements what */
#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 ]] */
+/* BASH_TEST2: [[ EXPR ]]
+ * Status of [[ support:
+ * We replace && and || with -a and -o
+ * TODO:
+ * singleword+noglob expansion:
+ * v='a b'; [[ $v = 'a b' ]]; echo 0:$?
+ * [[ /bin/n* ]]; echo 0:$?
+ * -a/-o are not AND/OR ops! (they are just strings)
+ * quoting needs to be considered (-f is an operator, "-f" and ""-f are not; etc)
+ * = is glob match operator, not equality operator: STR = GLOB
+ * (in GLOB, quoting is significant on char-by-char basis: a*cd"*")
+ * == same as =
+ * add =~ regex match operator: STR =~ REGEX
+ */
#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
+#define BASH_XTRACEFD ENABLE_ASH_BASH_COMPAT
+#define BASH_READ_D ENABLE_ASH_BASH_COMPAT
+#define IF_BASH_READ_D IF_ASH_BASH_COMPAT
#if defined(__ANDROID_API__) && __ANDROID_API__ <= 24
/* Bionic at least up to version 24 has no glob() */
# define IF_NOT_FEATURE_SH_STANDALONE(...) __VA_ARGS__
#endif
+#ifndef F_DUPFD_CLOEXEC
+# define F_DUPFD_CLOEXEC F_DUPFD
+#endif
+#ifndef O_CLOEXEC
+# define O_CLOEXEC 0
+#endif
#ifndef PIPE_BUF
# define PIPE_BUF 4096 /* amount of buffering in a pipe */
#endif
# error "Do not even bother, ash will not run on NOMMU machine"
#endif
+/* We use a trick to have more optimized code (fewer pointer reloads):
+ * ash.c: extern struct globals *const ash_ptr_to_globals;
+ * ash_ptr_hack.c: struct globals *ash_ptr_to_globals;
+ * This way, compiler in ash.c knows the pointer can not change.
+ *
+ * However, this may break on weird arches or toolchains. In this case,
+ * set "-DBB_GLOBAL_CONST=''" in CONFIG_EXTRA_CFLAGS to disable
+ * this optimization.
+ */
+#ifndef BB_GLOBAL_CONST
+# define BB_GLOBAL_CONST const
+#endif
+
/* ============ Hash table sizes. Configurable. */
/* shell level: 0 for the main shell, 1 for its children, and so on */
int shlvl;
#define rootshell (!shlvl)
+ int errlinno;
+
char *minusc; /* argument to -c option */
char *curdir; // = nullstr; /* current working directory */
#define EXERROR 1 /* a generic error */
#define EXEXIT 4 /* exit the shell */
- smallint isloginsh;
char nullstr[1]; /* zero length string */
char optlist[NOPTS];
#define S_DFL 1 /* default signal handling (SIG_DFL) */
#define S_CATCH 2 /* signal is caught */
#define S_IGN 3 /* signal is ignored (SIG_IGN) */
-#define S_HARD_IGN 4 /* signal is ignored permanently */
+#define S_HARD_IGN 4 /* signal is ignored permanently (it was SIG_IGN on entry to shell) */
/* indicates specified signal received */
uint8_t gotsig[NSIG - 1]; /* offset by 1: "signal" 0 is meaningless */
#endif
pid_t backgndpid; /* pid of last background process */
};
-extern struct globals_misc *const ash_ptr_to_globals_misc;
+extern struct globals_misc *BB_GLOBAL_CONST ash_ptr_to_globals_misc;
#define G_misc (*ash_ptr_to_globals_misc)
#define exitstatus (G_misc.exitstatus )
#define back_exitstatus (G_misc.back_exitstatus )
#define job_warning (G_misc.job_warning)
#define rootpid (G_misc.rootpid )
#define shlvl (G_misc.shlvl )
+#define errlinno (G_misc.errlinno )
#define minusc (G_misc.minusc )
#define curdir (G_misc.curdir )
#define physdir (G_misc.physdir )
#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 )
#define optlist (G_misc.optlist )
#define sigmode (G_misc.sigmode )
struct ncmd {
smallint type; /* Nxxxx */
+ int linno;
union node *assign;
union node *args;
union node *redirect;
struct nredir {
smallint type;
+ int linno;
union node *n;
union node *redirect;
};
struct nfor {
smallint type;
+ int linno;
union node *args;
union node *body;
char *var;
struct ncase {
smallint type;
+ int linno;
union node *expr;
union node *cases;
};
union node *body;
};
+struct ndefun {
+ smallint type;
+ int linno;
+ char *text;
+ union node *body;
+};
+
struct narg {
smallint type;
union node *next;
struct nfor nfor;
struct ncase ncase;
struct nclist nclist;
+ struct ndefun ndefun;
struct narg narg;
struct nfile nfile;
struct ndup ndup;
int unget;
};
+/*
+ * The parsefile structure pointed to by the global variable parsefile
+ * contains information about the current file being read.
+ */
struct parsefile {
struct parsefile *prev; /* preceding file on stack */
int linno; /* current line */
static struct parsefile basepf; /* top level input file */
static struct parsefile *g_parsefile = &basepf; /* current input file */
-static int startlinno; /* line # where last token started */
static char *commandname; /* currently executing command */
if (strcmp(arg0, commandname))
fprintf(stderr, "%s: ", commandname);
if (!iflag || g_parsefile->pf_fd > 0)
- fprintf(stderr, "line %d: ", startlinno);
+ fprintf(stderr, "line %d: ", errlinno);
}
vfprintf(stderr, msg, ap);
newline_and_flush(stderr);
va_end(ap);
}
+/*
+ * 'fmt' must be a string literal.
+ */
+#define ash_msg_and_raise_perror(fmt, ...) ash_msg_and_raise_error(fmt ": "STRERROR_FMT, ##__VA_ARGS__ STRERROR_ERRNO)
+
static void raise_error_syntax(const char *) NORETURN;
static void
raise_error_syntax(const char *msg)
{
+ errlinno = g_parsefile->linno;
ash_msg_and_raise_error("syntax error: %s", msg);
/* NOTREACHED */
}
size_t g_stacknleft; // = MINSIZE;
struct stack_block stackbase;
};
-extern struct globals_memstack *const ash_ptr_to_globals_memstack;
+extern struct globals_memstack *BB_GLOBAL_CONST ash_ptr_to_globals_memstack;
#define G_memstack (*ash_ptr_to_globals_memstack)
#define g_stackp (G_memstack.g_stackp )
#define g_stacknxt (G_memstack.g_stacknxt )
return memcpy(stalloc(len), p, len);
}
-static inline void
+static ALWAYS_INLINE void
grabstackblock(size_t len)
{
stalloc(len);
/* ============ Shell variables */
-/*
- * The parsefile structure pointed to by the global variable parsefile
- * contains information about the current file being read.
- */
struct shparam {
int nparam; /* # of positional parameters (without $0) */
#if ENABLE_ASH_GETOPTS
#if ENABLE_ASH_GETOPTS
{ VSTRFIXED|VTEXTFIXED , defoptindvar, getoptsreset },
#endif
+ { VSTRFIXED|VTEXTFIXED , NULL /* inited to linenovar */, NULL },
#if ENABLE_ASH_RANDOM_SUPPORT
{ VSTRFIXED|VTEXTFIXED|VUNSET|VDYNAMIC, "RANDOM", change_random },
#endif
struct globals_var {
struct shparam shellparam; /* $@ current positional parameters */
struct redirtab *redirlist;
- int preverrout_fd; /* save fd2 before print debug if xflag is set. */
+ int preverrout_fd; /* stderr fd: usually 2, unless redirect moved it */
struct var *vartab[VTABSIZE];
struct var varinit[ARRAY_SIZE(varinit_data)];
+ int lineno;
+ char linenovar[sizeof("LINENO=") + sizeof(int)*3];
};
-extern struct globals_var *const ash_ptr_to_globals_var;
+extern struct globals_var *BB_GLOBAL_CONST ash_ptr_to_globals_var;
#define G_var (*ash_ptr_to_globals_var)
#define shellparam (G_var.shellparam )
//#define redirlist (G_var.redirlist )
#define preverrout_fd (G_var.preverrout_fd)
#define vartab (G_var.vartab )
#define varinit (G_var.varinit )
-#define INIT_G_var() do { \
- unsigned i; \
- (*(struct globals_var**)&ash_ptr_to_globals_var) = xzalloc(sizeof(G_var)); \
- barrier(); \
- for (i = 0; i < ARRAY_SIZE(varinit_data); i++) { \
- varinit[i].flags = varinit_data[i].flags; \
- varinit[i].var_text = varinit_data[i].var_text; \
- varinit[i].var_func = varinit_data[i].var_func; \
- } \
-} while (0)
-
+#define lineno (G_var.lineno )
+#define linenovar (G_var.linenovar )
#define vifs varinit[0]
#if ENABLE_ASH_MAIL
# define vmail (&vifs)[1]
#define vps4 (&vps2)[1]
#if ENABLE_ASH_GETOPTS
# define voptind (&vps4)[1]
+# define vlineno (&voptind)[1]
# if ENABLE_ASH_RANDOM_SUPPORT
-# define vrandom (&voptind)[1]
+# define vrandom (&vlineno)[1]
# endif
#else
+# define vlineno (&vps4)[1]
# if ENABLE_ASH_RANDOM_SUPPORT
-# define vrandom (&vps4)[1]
+# define vrandom (&vlineno)[1]
# endif
#endif
+#define INIT_G_var() do { \
+ unsigned i; \
+ (*(struct globals_var**)&ash_ptr_to_globals_var) = xzalloc(sizeof(G_var)); \
+ barrier(); \
+ for (i = 0; i < ARRAY_SIZE(varinit_data); i++) { \
+ varinit[i].flags = varinit_data[i].flags; \
+ varinit[i].var_text = varinit_data[i].var_text; \
+ varinit[i].var_func = varinit_data[i].var_func; \
+ } \
+ strcpy(linenovar, "LINENO="); \
+ vlineno.var_text = linenovar; \
+} while (0)
/*
* The following macros access the values of the above variables.
static void FAST_FUNC
getoptsreset(const char *value)
{
- shellparam.optind = number(value) ?: 1;
+ shellparam.optind = 1;
+ if (is_number(value))
+ shellparam.optind = number(value) ?: 1;
shellparam.optoff = -1;
}
#endif
if (v->flags & VDYNAMIC)
v->var_func(NULL);
#endif
- if (!(v->flags & VUNSET))
+ if (!(v->flags & VUNSET)) {
+ if (v == &vlineno && v->var_text == linenovar) {
+ fmtstr(linenovar+7, sizeof(linenovar)-7, "%d", lineno);
+ }
return var_end(v->var_text);
+ }
}
return NULL;
}
/*
* Search the environment of a builtin command.
*/
-static inline const char *
+static ALWAYS_INLINE const char *
bltinlookup(const char *name)
{
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));
out_free:
if ((flags & (VTEXTFIXED|VSTACK|VNOSAVE)) == VNOSAVE)
free(s);
- return;
+ 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));
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);
+ nameeq = ckzalloc(namelen + vallen + 2);
p = mempcpy(nameeq, name, namelen);
if (val) {
*p++ = '=';
- p = mempcpy(p, val, vallen);
+ memcpy(p, val, vallen);
}
- *p = '\0';
- setvareq(nameeq, flags | VNOSAVE);
+ vp = setvareq(nameeq, flags | VNOSAVE);
INT_ON;
+
+ return vp;
}
static void FAST_FUNC
/*
* Generate a list of variables satisfying the given conditions.
*/
+#if !ENABLE_FEATURE_SH_NOFORK
+# define listvars(on, off, lp, end) listvars(on, off, end)
+#endif
static char **
-listvars(int on, int off, char ***end)
+listvars(int on, int off, struct strlist *lp, char ***end)
{
struct var **vpp;
struct var *vp;
do {
for (vp = *vpp; vp; vp = vp->next) {
if ((vp->flags & mask) == on) {
+#if ENABLE_FEATURE_SH_NOFORK
+ /* If variable with the same name is both
+ * exported and temporarily set for a command:
+ * export ZVAR=5
+ * ZVAR=6 printenv
+ * then "ZVAR=6" will be both in vartab and
+ * lp lists. Do not pass it twice to printenv.
+ */
+ struct strlist *lp1 = lp;
+ while (lp1) {
+ if (strcmp(lp1->text, vp->var_text) == 0)
+ goto skip;
+ lp1 = lp1->next;
+ }
+#endif
if (ep == stackstrend())
ep = growstackstr();
*ep++ = (char*)vp->var_text;
+#if ENABLE_FEATURE_SH_NOFORK
+ skip: ;
+#endif
}
}
} while (++vpp < vartab + VTABSIZE);
+
+#if ENABLE_FEATURE_SH_NOFORK
+ while (lp) {
+ if (ep == stackstrend())
+ ep = growstackstr();
+ *ep++ = lp->text;
+ lp = lp->next;
+ }
+#endif
+
if (ep == stackstrend())
ep = growstackstr();
if (end)
#endif
/* expandstr() needs parsing machinery, so it is far away ahead... */
-static const char *expandstr(const char *ps);
+static const char *expandstr(const char *ps, int syntax_type);
+/* Values for syntax param */
+#define BASESYNTAX 0 /* not in quotes */
+#define DQSYNTAX 1 /* in double quotes */
+#define SQSYNTAX 2 /* in single quotes */
+#define ARISYNTAX 3 /* in arithmetic */
+#if ENABLE_ASH_EXPAND_PRMT
+# define PSSYNTAX 4 /* prompt. never passed to SIT() */
+#endif
+/* PSSYNTAX expansion is identical to DQSYNTAX, except keeping '\$' as '\$' */
+/*
+ * called by editline -- any expansions to the prompt should be added here.
+ */
static void
setprompt_if(smallint do_set, int whichprompt)
{
}
#if ENABLE_ASH_EXPAND_PRMT
pushstackmark(&smark, stackblocksize());
- putprompt(expandstr(prompt));
+ putprompt(expandstr(prompt, PSSYNTAX));
popstackmark(&smark);
#else
putprompt(prompt);
goto docd;
err:
- ash_msg_and_raise_error("can't cd to %s", dest);
+ ash_msg_and_raise_perror("can't cd to %s", dest);
/* NOTREACHED */
out:
if (flags & CD_PRINT)
/* c in SIT(c, syntax) must be an *unsigned char* or PEOA or PEOF,
* caller must ensure proper cast on it if c is *char_ptr!
*/
-/* Values for syntax param */
-#define BASESYNTAX 0 /* not in quotes */
-#define DQSYNTAX 1 /* in double quotes */
-#define SQSYNTAX 2 /* in single quotes */
-#define ARISYNTAX 3 /* in arithmetic */
-#define PSSYNTAX 4 /* prompt. never passed to SIT() */
-
#if USE_SIT_FUNCTION
static int
#endif
}
}
-//TODO: if !rootshell, we reset SIGQUIT to DFL,
-//whereas we have to restore it to what shell got on entry
-//from the parent. See comment above
+ /* if !rootshell, we reset SIGQUIT to DFL,
+ * whereas we have to restore it to what shell got on entry.
+ * This is handled by the fact that if signal was IGNored on entry,
+ * then cur_act is S_HARD_IGN and we never change its sigaction
+ * (see code below).
+ */
if (signo == SIGCHLD)
new_act = S_CATCH;
cur_act = S_IGN; /* don't hard ignore these */
}
}
+ if (act.sa_handler == SIG_DFL && new_act == S_DFL) {
+ /* installing SIG_DFL over SIG_DFL is a no-op */
+ /* saves one sigaction call in each "sh -c SCRIPT" invocation */
+ *t = S_DFL;
+ return;
+ }
}
if (cur_act == S_HARD_IGN || cur_act == new_act)
return;
+ *t = new_act;
+
act.sa_handler = SIG_DFL;
switch (new_act) {
case S_CATCH:
act.sa_handler = SIG_IGN;
break;
}
-
/* flags and mask matter only if !DFL and !IGN, but we do it
* for all cases for more deterministic behavior:
*/
- act.sa_flags = 0;
+ act.sa_flags = 0; //TODO: why not SA_RESTART?
sigfillset(&act.sa_mask);
sigaction_set(signo, &act);
-
- *t = new_act;
}
/* mode flags for set_curjob */
xtcsetpgrp(int fd, pid_t pgrp)
{
if (tcsetpgrp(fd, pgrp))
- ash_msg_and_raise_error("can't set tty process group (%m)");
+ ash_msg_and_raise_perror("can't set tty process group");
}
/*
goto out;
}
/* fd is a tty at this point */
- fd = fcntl(fd, F_DUPFD, 10);
+ fd = fcntl(fd, F_DUPFD_CLOEXEC, 10);
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 */
- close_on_exec_on(fd);
+ if (F_DUPFD_CLOEXEC == F_DUPFD) /* if old libc (w/o F_DUPFD_CLOEXEC) */
+ close_on_exec_on(fd);
while (1) { /* while we are in the background */
pgrp = tcgetpgrp(fd);
if (pgrp < 0) {
/* These can only happen inside quotes */
cc[0] = c;
str = cc;
+//FIXME:
+// $ true $$ &
+// $ <cr>
+// [1]+ Done true ${\$} <<=== BUG: ${\$} is not a valid way to write $$ (${$} would be ok)
c = '\\';
break;
default:
p = "; done";
goto dodo;
case NDEFUN:
- cmdputs(n->narg.text);
+ cmdputs(n->ndefun.text);
p = "() { ... }";
goto dotail2;
case NCMD:
cmdputs(utoa(n->nfile.fd));
cmdputs(p);
if (n->type == NTOFD || n->type == NFROMFD) {
- cmdputs(utoa(n->ndup.dupfd));
+ if (n->ndup.dupfd >= 0)
+ cmdputs(utoa(n->ndup.dupfd));
+ else
+ cmdputs("-");
break;
}
n = n->nfile.fname;
if (jp->nprocs == 0) {
close(0);
if (open(bb_dev_null, O_RDONLY) != 0)
- ash_msg_and_raise_error("can't open '%s'", bb_dev_null);
+ ash_msg_and_raise_perror("can't open '%s'", bb_dev_null);
}
}
if (oldlvl == 0) {
TRACE(("Fork failed, errno=%d", errno));
if (jp)
freejob(jp);
- ash_msg_and_raise_error("can't fork");
+ ash_msg_and_raise_perror("can't fork");
}
if (pid == 0) {
CLEAR_RANDOM_T(&random_gen); /* or else $RANDOM repeats in child */
#undef EMPTY
#undef CLOSED
#define EMPTY -2 /* marks an unused slot in redirtab */
-#define CLOSED -3 /* marks a slot of previously-closed fd */
+#define CLOSED -1 /* marks a slot of previously-closed fd */
/*
* Handle here documents. Normally we fork off a process to write the
size_t len = 0;
if (pipe(pip) < 0)
- ash_msg_and_raise_error("pipe call failed");
+ ash_msg_and_raise_perror("can't create pipe");
if (redir->type == NHERE) {
len = strlen(redir->nhere.doc->narg.text);
if (len <= PIPE_BUF) {
f = open(fname, O_WRONLY, 0666);
if (f < 0)
goto ecreate;
- if (fstat(f, &sb) < 0 && S_ISREG(sb.st_mode)) {
+ if (!fstat(f, &sb) && S_ISREG(sb.st_mode)) {
close(f);
errno = EEXIST;
goto ecreate;
int newfd;
int err;
- newfd = fcntl(from, F_DUPFD, 10);
+ newfd = fcntl(from, F_DUPFD_CLOEXEC, 10);
err = newfd < 0 ? errno : 0;
if (err != EBADF) {
if (err)
- ash_msg_and_raise_error("%d: %m", from);
+ ash_msg_and_raise_perror("%d", from);
close(from);
- fcntl(newfd, F_SETFD, FD_CLOEXEC);
+ if (F_DUPFD_CLOEXEC == F_DUPFD)
+ close_on_exec_on(newfd);
}
return newfd;
newfd = (from != to) ? dup2(from, to) : to;
if (newfd < 0) {
/* Happens when source fd is not open: try "echo >&99" */
- ash_msg_and_raise_error("%d: %m", from);
+ ash_msg_and_raise_perror("%d", from);
}
return newfd;
}
+static int
+dup_CLOEXEC(int fd, int avoid_fd)
+{
+ int newfd;
+ repeat:
+ newfd = fcntl(fd, F_DUPFD_CLOEXEC, avoid_fd + 1);
+ if (newfd >= 0) {
+ if (F_DUPFD_CLOEXEC == F_DUPFD) /* if old libc (w/o F_DUPFD_CLOEXEC) */
+ close_on_exec_on(newfd);
+ } else { /* newfd < 0 */
+ if (errno == EBUSY)
+ goto repeat;
+ if (errno == EINTR)
+ goto repeat;
+ }
+ return newfd;
+}
+static int
+xdup_CLOEXEC_and_close(int fd, int avoid_fd)
+{
+ int newfd;
+ repeat:
+ newfd = fcntl(fd, F_DUPFD_CLOEXEC, avoid_fd + 1);
+ if (newfd < 0) {
+ if (errno == EBUSY)
+ goto repeat;
+ if (errno == EINTR)
+ goto repeat;
+ /* fd was not open? */
+ if (errno == EBADF)
+ return fd;
+ ash_msg_and_raise_perror("%d", newfd);
+ }
+ if (F_DUPFD_CLOEXEC == F_DUPFD)
+ close_on_exec_on(newfd);
+ close(fd);
+ return newfd;
+}
/* Struct def and variable are moved down to the first usage site */
-struct two_fd_t {
- int orig, copy;
+struct squirrel {
+ int orig_fd;
+ int moved_to;
};
struct redirtab {
struct redirtab *next;
int pair_count;
- struct two_fd_t two_fd[];
+ struct squirrel two_fd[];
};
#define redirlist (G_var.redirlist)
-enum {
- COPYFD_RESTORE = (int)~(INT_MAX),
-};
-static int
-need_to_remember(struct redirtab *rp, int fd)
+static void
+add_squirrel_closed(struct redirtab *sq, int fd)
{
int i;
- if (!rp) /* remembering was not requested */
- return 0;
+ if (!sq)
+ return;
- for (i = 0; i < rp->pair_count; i++) {
- if (rp->two_fd[i].orig == fd) {
- /* already remembered */
- return 0;
+ for (i = 0; sq->two_fd[i].orig_fd != EMPTY; i++) {
+ /* If we collide with an already moved fd... */
+ if (fd == sq->two_fd[i].orig_fd) {
+ /* Examples:
+ * "echo 3>FILE 3>&- 3>FILE"
+ * "echo 3>&- 3>FILE"
+ * No need for last redirect to insert
+ * another "need to close 3" indicator.
+ */
+ TRACE(("redirect_fd %d: already moved or closed\n", fd));
+ return;
}
}
- return 1;
+ TRACE(("redirect_fd %d: previous fd was closed\n", fd));
+ sq->two_fd[i].orig_fd = fd;
+ sq->two_fd[i].moved_to = CLOSED;
}
-/* "hidden" fd is a fd used to read scripts, or a copy of such */
static int
-is_hidden_fd(struct redirtab *rp, int fd)
+save_fd_on_redirect(int fd, int avoid_fd, struct redirtab *sq)
{
- int i;
- struct parsefile *pf;
+ int i, new_fd;
+
+ if (avoid_fd < 9) /* the important case here is that it can be -1 */
+ avoid_fd = 9;
- if (fd == -1)
+#if JOBS
+ if (fd == ttyfd) {
+ /* Testcase: "ls -l /proc/$$/fd 10>&-" should work */
+ ttyfd = xdup_CLOEXEC_and_close(ttyfd, avoid_fd);
+ TRACE(("redirect_fd %d: matches ttyfd, moving it to %d\n", fd, ttyfd));
+ return 1; /* "we closed fd" */
+ }
+#endif
+ /* Are we called from redirect(0)? E.g. redirect
+ * in a forked child. No need to save fds,
+ * we aren't going to use them anymore, ok to trash.
+ */
+ if (!sq)
return 0;
- /* Check open scripts' fds */
- pf = g_parsefile;
- while (pf) {
- /* We skip pf_fd == 0 case because of the following case:
- * $ ash # running ash interactively
- * $ . ./script.sh
- * and in script.sh: "exec 9>&0".
- * Even though top-level pf_fd _is_ 0,
- * it's still ok to use it: "read" builtin uses it,
- * why should we cripple "exec" builtin?
- */
- if (pf->pf_fd > 0 && fd == pf->pf_fd) {
- return 1;
+
+ /* If this one of script's fds? */
+ if (fd != 0) {
+ struct parsefile *pf = g_parsefile;
+ while (pf) {
+ /* We skip fd == 0 case because of the following:
+ * $ ash # running ash interactively
+ * $ . ./script.sh
+ * and in script.sh: "exec 9>&0".
+ * Even though top-level pf_fd _is_ 0,
+ * it's still ok to use it: "read" builtin uses it,
+ * why should we cripple "exec" builtin?
+ */
+ if (fd == pf->pf_fd) {
+ pf->pf_fd = xdup_CLOEXEC_and_close(fd, avoid_fd);
+ return 1; /* "we closed fd" */
+ }
+ pf = pf->prev;
}
- pf = pf->prev;
}
- if (!rp)
- return 0;
- /* Check saved fds of redirects */
- fd |= COPYFD_RESTORE;
- for (i = 0; i < rp->pair_count; i++) {
- if (rp->two_fd[i].copy == fd) {
- return 1;
+ /* Check whether it collides with any open fds (e.g. stdio), save fds as needed */
+
+ /* First: do we collide with some already moved fds? */
+ for (i = 0; sq->two_fd[i].orig_fd != EMPTY; i++) {
+ /* If we collide with an already moved fd... */
+ if (fd == sq->two_fd[i].moved_to) {
+ new_fd = dup_CLOEXEC(fd, avoid_fd);
+ sq->two_fd[i].moved_to = new_fd;
+ TRACE(("redirect_fd %d: already busy, moving to %d\n", fd, new_fd));
+ if (new_fd < 0) /* what? */
+ xfunc_die();
+ return 0; /* "we did not close fd" */
+ }
+ if (fd == sq->two_fd[i].orig_fd) {
+ /* Example: echo Hello >/dev/null 1>&2 */
+ TRACE(("redirect_fd %d: already moved\n", fd));
+ return 0; /* "we did not close fd" */
}
}
+
+ /* If this fd is open, we move and remember it; if it's closed, new_fd = CLOSED (-1) */
+ new_fd = dup_CLOEXEC(fd, avoid_fd);
+ TRACE(("redirect_fd %d: previous fd is moved to %d (-1 if it was closed)\n", fd, new_fd));
+ if (new_fd < 0) {
+ if (errno != EBADF)
+ xfunc_die();
+ /* new_fd = CLOSED; - already is -1 */
+ }
+ sq->two_fd[i].moved_to = new_fd;
+ sq->two_fd[i].orig_fd = fd;
+
+ /* if we move stderr, let "set -x" code know */
+ if (fd == preverrout_fd)
+ preverrout_fd = new_fd;
+
+ return 0; /* "we did not close fd" */
+}
+
+static int
+internally_opened_fd(int fd, struct redirtab *sq)
+{
+ int i;
+#if JOBS
+ if (fd == ttyfd)
+ return 1;
+#endif
+ /* If this one of script's fds? */
+ if (fd != 0) {
+ struct parsefile *pf = g_parsefile;
+ while (pf) {
+ if (fd == pf->pf_fd)
+ return 1;
+ pf = pf->prev;
+ }
+ }
+
+ if (sq) for (i = 0; i < sq->pair_count && sq->two_fd[i].orig_fd != EMPTY; i++) {
+ if (fd == sq->two_fd[i].moved_to)
+ return 1;
+ }
return 0;
}
*/
/* flags passed to redirect */
#define REDIR_PUSH 01 /* save previous values of file descriptors */
-#define REDIR_SAVEFD2 03 /* set preverrout */
static void
redirect(union node *redir, int flags)
{
struct redirtab *sv;
- int sv_pos;
- int i;
- int fd;
- int newfd;
- int copied_fd2 = -1;
- if (!redir) {
+ if (!redir)
return;
- }
sv = NULL;
- sv_pos = 0;
INT_OFF;
- if (flags & REDIR_PUSH) {
- union node *tmp = redir;
- do {
- sv_pos++;
-#if BASH_REDIR_OUTPUT
- if (tmp->nfile.type == NTO2)
- sv_pos++;
-#endif
- tmp = tmp->nfile.next;
- } while (tmp);
- sv = ckmalloc(sizeof(*sv) + sv_pos * sizeof(sv->two_fd[0]));
- sv->next = redirlist;
- sv->pair_count = sv_pos;
- redirlist = sv;
- while (sv_pos > 0) {
- sv_pos--;
- sv->two_fd[sv_pos].orig = sv->two_fd[sv_pos].copy = EMPTY;
- }
- }
-
+ if (flags & REDIR_PUSH)
+ sv = redirlist;
do {
- int right_fd = -1;
+ int fd;
+ int newfd;
+ int close_fd;
+ int closed;
+
fd = redir->nfile.fd;
if (redir->nfile.type == NTOFD || redir->nfile.type == NFROMFD) {
- right_fd = redir->ndup.dupfd;
- //bb_error_msg("doing %d > %d", fd, right_fd);
- /* redirect from/to same file descriptor? */
- if (right_fd == fd)
- continue;
- /* "echo >&10" and 10 is a fd opened to a sh script? */
- if (is_hidden_fd(sv, right_fd)) {
- errno = EBADF; /* as if it is closed */
- ash_msg_and_raise_error("%d: %m", right_fd);
- }
- newfd = -1;
+ //bb_error_msg("doing %d > %d", fd, newfd);
+ newfd = redir->ndup.dupfd;
+ close_fd = -1;
} else {
newfd = openredirect(redir); /* always >= 0 */
if (fd == newfd) {
- /* Descriptor wasn't open before redirect.
- * Mark it for close in the future */
- if (need_to_remember(sv, fd)) {
- goto remember_to_close;
- }
+ /* open() gave us precisely the fd we wanted.
+ * This means that this fd was not busy
+ * (not opened to anywhere).
+ * Remember to close it on restore:
+ */
+ add_squirrel_closed(sv, fd);
continue;
}
+ close_fd = newfd;
}
-#if BASH_REDIR_OUTPUT
- redirect_more:
-#endif
- if (need_to_remember(sv, fd)) {
- /* Copy old descriptor */
- /* 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);
-#endif
- if (i == -1) {
- i = errno;
- if (i != EBADF) {
- /* Strange error (e.g. "too many files" EMFILE?) */
- if (newfd >= 0)
- close(newfd);
- errno = i;
- ash_msg_and_raise_error("%d: %m", fd);
- /* NOTREACHED */
- }
- /* EBADF: it is not open - good, remember to close it */
- 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 */
- if (is_hidden_fd(sv, fd))
- i |= COPYFD_RESTORE;
+
+ if (fd == newfd)
+ continue;
+
+ /* if "N>FILE": move newfd to fd */
+ /* if "N>&M": dup newfd to fd */
+ /* if "N>&-": close fd (newfd is -1) */
+
+ IF_BASH_REDIR_OUTPUT(redirect_more:)
+
+ closed = save_fd_on_redirect(fd, /*avoid:*/ newfd, sv);
+ if (newfd == -1) {
+ /* "N>&-" means "close me" */
+ if (!closed) {
+ /* ^^^ optimization: saving may already
+ * have closed it. If not... */
+ close(fd);
}
- if (fd == 2)
- copied_fd2 = i;
- sv->two_fd[sv_pos].orig = fd;
- sv->two_fd[sv_pos].copy = i;
- sv_pos++;
- }
- if (newfd < 0) {
- /* NTOFD/NFROMFD: copy redir->ndup.dupfd to fd */
- if (redir->ndup.dupfd < 0) { /* "fd>&-" */
- /* Don't want to trigger debugging */
- if (fd != -1)
- close(fd);
- } else {
- dup2_or_raise(redir->ndup.dupfd, fd);
+ } else {
+ /* if newfd is a script fd or saved fd, simulate EBADF */
+ if (internally_opened_fd(newfd, sv)) {
+ errno = EBADF;
+ ash_msg_and_raise_perror("%d", newfd);
}
- } else if (fd != newfd) { /* move newfd to fd */
dup2_or_raise(newfd, fd);
+ if (close_fd >= 0) /* "N>FILE" or ">&FILE" or heredoc? */
+ close(close_fd);
#if BASH_REDIR_OUTPUT
- if (!(redir->nfile.type == NTO2 && fd == 2))
+ if (redir->nfile.type == NTO2 && fd == 1) {
+ /* ">&FILE". we already redirected to 1, now copy 1 to 2 */
+ fd = 2;
+ newfd = 1;
+ close_fd = -1;
+ goto redirect_more;
+ }
#endif
- close(newfd);
}
+ } while ((redir = redir->nfile.next) != NULL);
+ INT_ON;
+
+//dash:#define REDIR_SAVEFD2 03 /* set preverrout */
+#define REDIR_SAVEFD2 0
+ // dash has a bug: since REDIR_SAVEFD2=3 and REDIR_PUSH=1, this test
+ // triggers for pure REDIR_PUSH too. Thus, this is done almost always,
+ // not only for calls with flags containing REDIR_SAVEFD2.
+ // We do this unconditionally (see save_fd_on_redirect()).
+ //if ((flags & REDIR_SAVEFD2) && copied_fd2 >= 0)
+ // preverrout_fd = copied_fd2;
+}
+
+static int
+redirectsafe(union node *redir, int flags)
+{
+ int err;
+ volatile int saveint;
+ struct jmploc *volatile savehandler = exception_handler;
+ struct jmploc jmploc;
+
+ SAVE_INT(saveint);
+ /* "echo 9>/dev/null; echo >&9; echo result: $?" - result should be 1, not 2! */
+ err = setjmp(jmploc.loc); /* was = setjmp(jmploc.loc) * 2; */
+ if (!err) {
+ exception_handler = &jmploc;
+ redirect(redir, flags);
+ }
+ exception_handler = savehandler;
+ if (err && exception_type != EXERROR)
+ longjmp(exception_handler->loc, 1);
+ RESTORE_INT(saveint);
+ return err;
+}
+
+static struct redirtab*
+pushredir(union node *redir)
+{
+ struct redirtab *sv;
+ int i;
+
+ if (!redir)
+ return redirlist;
+
+ i = 0;
+ do {
+ i++;
#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;
- fd = 2;
- goto redirect_more;
- }
+ if (redir->nfile.type == NTO2)
+ i++;
#endif
- } while ((redir = redir->nfile.next) != NULL);
+ redir = redir->nfile.next;
+ } while (redir);
- INT_ON;
- if ((flags & REDIR_SAVEFD2) && copied_fd2 >= 0)
- preverrout_fd = copied_fd2;
+ sv = ckzalloc(sizeof(*sv) + i * sizeof(sv->two_fd[0]));
+ sv->pair_count = i;
+ while (--i >= 0)
+ sv->two_fd[i].orig_fd = sv->two_fd[i].moved_to = EMPTY;
+ sv->next = redirlist;
+ redirlist = sv;
+ return sv->next;
}
/*
* Undo the effects of the last redirection.
*/
static void
-popredir(int drop, int restore)
+popredir(int drop)
{
struct redirtab *rp;
int i;
INT_OFF;
rp = redirlist;
for (i = 0; i < rp->pair_count; i++) {
- int fd = rp->two_fd[i].orig;
- int copy = rp->two_fd[i].copy;
+ int fd = rp->two_fd[i].orig_fd;
+ int copy = rp->two_fd[i].moved_to;
if (copy == CLOSED) {
if (!drop)
close(fd);
continue;
}
if (copy != EMPTY) {
- if (!drop || (restore && (copy & COPYFD_RESTORE))) {
- copy &= ~COPYFD_RESTORE;
+ if (!drop) {
/*close(fd);*/
dup2_or_raise(copy, fd);
}
- close(copy & ~COPYFD_RESTORE);
+ close(copy);
}
}
redirlist = rp->next;
INT_ON;
}
-/*
- * Undo all redirections. Called on error or interrupt.
- */
-
-static int
-redirectsafe(union node *redir, int flags)
+static void
+unwindredir(struct redirtab *stop)
{
- int err;
- volatile int saveint;
- struct jmploc *volatile savehandler = exception_handler;
- struct jmploc jmploc;
-
- SAVE_INT(saveint);
- /* "echo 9>/dev/null; echo >&9; echo result: $?" - result should be 1, not 2! */
- err = setjmp(jmploc.loc); // huh?? was = setjmp(jmploc.loc) * 2;
- if (!err) {
- exception_handler = &jmploc;
- redirect(redir, flags);
- }
- exception_handler = savehandler;
- if (err && exception_type != EXERROR)
- longjmp(exception_handler->loc, 1);
- RESTORE_INT(saveint);
- return err;
+ while (redirlist != stop)
+ popredir(/*drop:*/ 0);
}
return result;
}
#endif
+#if BASH_SUBSTR
+# if ENABLE_FEATURE_SH_MATH
+static int substr_atoi(const char *s)
+{
+ arith_t t = ash_arith(s);
+ if (sizeof(t) > sizeof(int)) {
+ /* clamp very large or very large negative nums for ${v:N:M}:
+ * else "${v:0:0x100000001}" would work as "${v:0:1}"
+ */
+ if (t > INT_MAX)
+ t = INT_MAX;
+ if (t < INT_MIN)
+ t = INT_MIN;
+ }
+ return t;
+}
+# else
+# define substr_atoi(s) number(s)
+# endif
+#endif
/*
* expandarg flags
* performs globbing, and thus diverges from what we do).
*/
#define EXP_CASE 0x10 /* keeps quotes around for CASE pattern */
-#define EXP_QPAT 0x20 /* pattern in quoted parameter expansion */
-#define EXP_VARTILDE2 0x40 /* expand tildes after colons only */
-#define EXP_WORD 0x80 /* expand word in parameter expansion */
+#define EXP_VARTILDE2 0x20 /* expand tildes after colons only */
+#define EXP_WORD 0x40 /* expand word in parameter expansion */
#define EXP_QUOTED 0x100 /* expand word in double quotes */
/*
* rmescape() flags
#define RMESCAPE_GLOB 0x2 /* Add backslashes for glob */
#define RMESCAPE_GROW 0x8 /* Grow strings instead of stalloc */
#define RMESCAPE_HEAP 0x10 /* Malloc strings instead of stalloc */
-#define RMESCAPE_SLASH 0x20 /* Stop globbing after slash */
/* Add CTLESC when necessary. */
-#define QUOTES_ESC (EXP_FULL | EXP_CASE | EXP_QPAT)
+#define QUOTES_ESC (EXP_FULL | EXP_CASE)
/* Do not skip NUL characters. */
#define QUOTES_KEEPNUL EXP_TILDE
realifs = ifsset() ? ifsval() : defifs;
ifsp = &ifsfirst;
do {
+ int afternul;
+
p = string + ifsp->begoff;
+ afternul = nulonly;
nulonly = ifsp->nulonly;
ifs = nulonly ? nullstr : realifs;
ifsspc = 0;
p++;
continue;
}
- if (!nulonly)
+ if (!(afternul || nulonly))
ifsspc = (strchr(defifs, *p) != NULL);
/* Ignore IFS whitespace at start */
if (q == start && ifsspc) {
/*
* Remove any CTLESC characters from a string.
*/
+#if !BASH_PATTERN_SUBST
+#define rmescapes(str, flag, slash_position) \
+ rmescapes(str, flag)
+#endif
static char *
-rmescapes(char *str, int flag)
+rmescapes(char *str, int flag, int *slash_position)
{
static const char qchars[] ALIGN1 = {
IF_BASH_PATTERN_SUBST('/',) CTLESC, CTLQUOTEMARK, '\0' };
char *p, *q, *r;
- unsigned inquotes;
unsigned protect_against_glob;
unsigned globbing;
- IF_BASH_PATTERN_SUBST(unsigned slash = flag & RMESCAPE_SLASH;)
- p = strpbrk(str, qchars IF_BASH_PATTERN_SUBST(+ !slash));
+ p = strpbrk(str, qchars IF_BASH_PATTERN_SUBST(+ !slash_position));
if (!p)
return str;
}
}
- inquotes = 0;
globbing = flag & RMESCAPE_GLOB;
protect_against_glob = globbing;
while (*p) {
if ((unsigned char)*p == CTLQUOTEMARK) {
-// Note: both inquotes and protect_against_glob only affect whether
+// Note: protect_against_glob only affect whether
// CTLESC,<ch> gets converted to <ch> or to \<ch>
- inquotes = ~inquotes;
p++;
protect_against_glob = globbing;
continue;
}
+ if (*p == '\\') {
+ /* naked back slash */
+ protect_against_glob = 0;
+ goto copy;
+ }
if ((unsigned char)*p == CTLESC) {
p++;
#if DEBUG
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 */
+ || *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 */
+ || *p == '^' /* case '^' in [\^] ) echo ok;; *) echo WRONG;; esac */
) {
*q++ = '\\';
}
}
- } else if (*p == '\\' && !inquotes) {
- /* naked back slash */
- protect_against_glob = 0;
- goto copy;
}
#if BASH_PATTERN_SUBST
- else if (*p == '/' && slash) {
- /* stop handling globbing and mark location of slash */
- globbing = slash = 0;
- *p = CTLESC;
+ else if (slash_position && p == str + *slash_position) {
+ /* stop handling globbing */
+ globbing = 0;
+ *slash_position = q - r;
+ slash_position = NULL;
}
#endif
protect_against_glob = globbing;
static char *
preglob(const char *pattern, int flag)
{
- return rmescapes((char *)pattern, flag | RMESCAPE_GLOB);
+ return rmescapes((char *)pattern, flag | RMESCAPE_GLOB, NULL);
}
/*
if (quotes & QUOTES_ESC) {
int n = SIT(c, syntax);
if (n == CCTL
- || (((quotes & EXP_FULL) || syntax != BASESYNTAX)
- && n == CBACK
- )
+ || (syntax != BASESYNTAX && n == CBACK)
) {
USTPUTC(CTLESC, q);
}
#define EV_TESTED 02 /* exit status is checked; ignore -e flag */
static int evaltree(union node *, int);
+/* An evaltree() which is known to never return.
+ * Used to use an alias:
+ * static int evaltreenr(union node *, int) __attribute__((alias("evaltree"),__noreturn__));
+ * but clang was reported to "transfer" noreturn-ness to evaltree() as well.
+ */
+static ALWAYS_INLINE NORETURN void
+evaltreenr(union node *n, int flags)
+{
+ evaltree(n, flags);
+ bb_unreachable(abort());
+ /* NOTREACHED */
+}
+
static void FAST_FUNC
evalbackcmd(union node *n, struct backcmd *result)
{
}
if (pipe(pip) < 0)
- ash_msg_and_raise_error("pipe call failed");
+ ash_msg_and_raise_perror("can't create pipe");
jp = makejob(/*n,*/ 1);
if (forkshell(jp, n, FORK_NOJOB) == 0) {
/* child */
*/
eflag = 0;
ifsfree();
- evaltree(n, EV_EXIT); /* actually evaltreenr... */
+ evaltreenr(n, EV_EXIT);
/* NOTREACHED */
}
/* parent */
expdest = p;
if (flag & QUOTES_ESC)
- rmescapes(p + 1, 0);
+ rmescapes(p + 1, 0, NULL);
len = cvtnum(ash_arith(p + 1));
#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 variables. 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 = {
'=',
case CTLENDVAR: /* ??? */
goto breakloop;
case CTLQUOTEMARK:
- 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;
+ if (!inquotes && !memcmp(p, dolatstr + 1, DOLATSTRLEN - 1)) {
+ p = evalvar(p + 1, flags | EXP_QUOTED) + 1;
goto start;
}
+ inquotes ^= EXP_QUOTED;
addquote:
if (flags & QUOTES_ESC) {
p--;
case CTLESC:
startloc++;
length++;
-
- /*
- * Quoted parameter expansion pattern: remove quote
- * unless inside inner quotes or we have a literal
- * backslash.
- */
- if (((flags | inquotes) & (EXP_QPAT | EXP_QUOTED)) ==
- EXP_QPAT && *p != '\\')
- break;
-
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:
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 *rmesc, *rmescend;
char *str;
int amount, resetloc;
+ int argstr_flags;
IF_BASH_PATTERN_SUBST(int workloc;)
- IF_BASH_PATTERN_SUBST(char *repl = NULL;)
+ IF_BASH_PATTERN_SUBST(int slash_pos;)
+ IF_BASH_PATTERN_SUBST(char *repl;)
int zero;
char *(*scan)(char*, char*, char*, char*, int, int);
//bb_error_msg("subevalvar(p:'%s',varname:'%s',strloc:%d,subtype:%d,startloc:%d,varflags:%x,quotes:%d)",
// p, varname, strloc, subtype, startloc, varflags, quotes);
- argstr(p, EXP_TILDE | (subtype != VSASSIGN && subtype != VSQUESTION ?
- (flag & (EXP_QUOTED | EXP_QPAT) ? EXP_QPAT : EXP_CASE) : 0),
- var_str_list);
+#if BASH_PATTERN_SUBST
+ /* For "${v/pattern/repl}", we must find the delimiter _before_
+ * argstr() call expands possible variable references in pattern:
+ * think about "v=a; a=a/; echo ${v/$a/r}" case.
+ */
+ repl = NULL;
+ if (subtype == VSREPLACE || subtype == VSREPLACEALL) {
+ /* Find '/' and replace with NUL */
+ repl = p;
+ /* The pattern can't be empty.
+ * IOW: if the first char after "${v//" is a slash,
+ * it does not terminate the pattern - it's the first char of the pattern:
+ * v=/dev/ram; echo ${v////-} prints -dev-ram (pattern is "/")
+ * v=/dev/ram; echo ${v///r/-} prints /dev-am (pattern is "/r")
+ */
+ if (*repl == '/')
+ repl++;
+ for (;;) {
+ if (*repl == '\0') {
+ repl = NULL;
+ break;
+ }
+ if (*repl == '/') {
+ *repl = '\0';
+ break;
+ }
+ /* Handle escaped slashes, e.g. "${v/\//_}" (they are CTLESC'ed by this point) */
+ if ((unsigned char)*repl == CTLESC && repl[1])
+ repl++;
+ repl++;
+ }
+ }
+#endif
+ argstr_flags = EXP_TILDE;
+ if (subtype != VSASSIGN
+ && subtype != VSQUESTION
+#if BASH_SUBSTR
+ && subtype != VSSUBSTR
+#endif
+ ) {
+ /* EXP_CASE keeps CTLESC's */
+ argstr_flags = EXP_TILDE | EXP_CASE;
+ }
+ argstr(p, argstr_flags);
+ //bb_error_msg("str0:'%s'", (char *)stackblock() + strloc);
+#if BASH_PATTERN_SUBST
+ slash_pos = -1;
+ if (repl) {
+ slash_pos = expdest - ((char *)stackblock() + strloc);
+ STPUTC('/', expdest);
+ //bb_error_msg("repl+1:'%s'", repl + 1);
+ argstr(repl + 1, EXP_TILDE); /* EXP_TILDE: echo "${v/x/~}" expands ~ ! */
+ *repl = '/';
+ }
+#endif
STPUTC('\0', expdest);
argbackq = saveargbackq;
startp = (char *)stackblock() + startloc;
+ //bb_error_msg("str1:'%s'", (char *)stackblock() + strloc);
switch (subtype) {
case VSASSIGN:
loc = str = stackblock() + strloc;
-# if !ENABLE_FEATURE_SH_MATH
-# define ash_arith number
-# endif
/* Read POS in ${var:POS:LEN} */
colon = strchr(loc, ':');
if (colon) *colon = '\0';
- pos = ash_arith(loc);
+ pos = substr_atoi(loc);
if (colon) *colon = ':';
/* Read LEN in ${var:POS:LEN} */
/* *loc != '\0', guaranteed by parser */
if (quotes) {
char *ptr;
-
/* Adjust the length by the number of escapes */
for (ptr = startp; ptr < (str - 1); ptr++) {
if ((unsigned char)*ptr == CTLESC) {
orig_len = len;
if (*loc++ == ':') {
/* ${var::LEN} */
- len = ash_arith(loc);
+ len = substr_atoi(loc);
} else {
/* Skip POS in ${var:POS:LEN} */
len = orig_len;
- while (*loc && *loc != ':') {
+ while (*loc && *loc != ':')
loc++;
- }
- if (*loc++ == ':') {
- len = ash_arith(loc);
- }
+ if (*loc++ == ':')
+ len = substr_atoi(loc);
}
-# undef ash_arith
-
if (pos < 0) {
/* ${VAR:$((-n)):l} starts n chars from the end */
pos = orig_len + pos;
resetloc = expdest - (char *)stackblock();
#if BASH_PATTERN_SUBST
+ repl = NULL;
+
/* 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
rmesc = startp;
rmescend = (char *)stackblock() + strloc;
+ //bb_error_msg("str7:'%s'", rmescend);
if (quotes) {
- rmesc = rmescapes(startp, RMESCAPE_ALLOC | RMESCAPE_GROW);
+//TODO: how to handle slash_pos here if string changes (shortens?)
+ rmesc = rmescapes(startp, RMESCAPE_ALLOC | RMESCAPE_GROW, NULL);
if (rmesc != startp) {
rmescend = expdest;
startp = (char *)stackblock() + startloc;
* The result is a_\_z_c (not a\_\_z_c)!
*
* The search pattern and replace string treat backslashes differently!
- * RMESCAPE_SLASH causes preglob to work differently on the pattern
+ * "&slash_pos" causes rmescapes() to work differently on the pattern
* and string. It's only used on the first call.
*/
- preglob(str, IF_BASH_PATTERN_SUBST(
- (subtype == VSREPLACE || subtype == VSREPLACEALL) && !repl ?
- RMESCAPE_SLASH : ) 0);
+ //bb_error_msg("str8:'%s' slash_pos:%d", str, slash_pos);
+ rmescapes(str, RMESCAPE_GLOB,
+ repl ? NULL : (slash_pos < 0 ? NULL : &slash_pos)
+ );
#if BASH_PATTERN_SUBST
workloc = expdest - (char *)stackblock();
char *idx, *end;
if (!repl) {
- repl = strchr(str, CTLESC);
- if (repl)
+ //bb_error_msg("str9:'%s' slash_pos:%d", str, slash_pos);
+ repl = nullstr;
+ if (slash_pos >= 0) {
+ repl = str + slash_pos;
*repl++ = '\0';
- else
- repl = nullstr;
+ }
}
//bb_error_msg("str:'%s' repl:'%s'", str, repl);
* 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 quoted)
{
const char *p;
int num;
int i;
ssize_t len = 0;
int sep;
- int quoted = *quotedp;
int subtype = varflags & VSTYPE;
int discard = subtype == VSPLUS || subtype == VSLENGTH;
int quotes = (discard ? 0 : (flags & QUOTES_ESC)) | QUOTES_KEEPNUL;
case '-':
expdest = makestrspace(NOPTS, expdest);
for (i = NOPTS - 1; i >= 0; i--) {
- if (optlist[i]) {
+ if (optlist[i] && optletters(i)) {
USTPUTC(optletters(i), expdest);
len++;
}
case '*': {
char **ap;
char sepc;
+ char c;
- if (quoted)
- sep = 0;
- sep |= ifsset() ? ifsval()[0] : ' ';
+ /* We will set c to 0 or ~0 depending on whether
+ * we're doing field splitting. We won't do field
+ * splitting if either we're quoted or sep is zero.
+ *
+ * Instead of testing (quoted || !sep) the following
+ * trick optimises away any branches by using the
+ * fact that EXP_QUOTED (which is the only bit that
+ * can be set in quoted) is the same as EXP_FULL <<
+ * CHAR_BIT (which is the only bit that can be set
+ * in sep).
+ */
+#if EXP_QUOTED >> CHAR_BIT != EXP_FULL
+#error The following two lines expect EXP_QUOTED == EXP_FULL << CHAR_BIT
+#endif
+ c = !((quoted | ~sep) & EXP_QUOTED) - 1;
+ sep &= ~quoted;
+ sep |= ifsset() ? (unsigned char)(c & ifsval()[0]) : ' ';
param:
sepc = sep;
- *quotedp = !sepc;
ap = shellparam.p;
if (!ap)
return -1;
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;
int quoted;
- char easy;
char *var;
int patloc;
int startloc;
quoted = flag & EXP_QUOTED;
var = p;
- easy = (!quoted || (*var == '@' && shellparam.nparam));
startloc = expdest - (char *)stackblock();
p = strchr(p, '=') + 1; //TODO: use var_end(p)?
again:
- varlen = varvalue(var, varflags, flag, var_str_list, "ed);
+ varlen = varvalue(var, varflags, flag, quoted);
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
if (subtype == VSNORMAL) {
record:
- if (!easy)
- goto end;
+ if (quoted) {
+ quoted = *var == '@' && shellparam.nparam;
+ if (!quoted)
+ goto end;
+ }
recordregion(startloc, expdest - (char *)stackblock(), quoted);
goto end;
}
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
);
p = strpbrk(p, chars);
if (!p)
break;
- switch ((unsigned char) *p) {
+ switch ((unsigned char)*p) {
case CTLQUOTEMARK:
for (;;) {
p++;
- if (*p == CTLQUOTEMARK)
+ if ((unsigned char)*p == CTLQUOTEMARK)
break;
- if (*p == CTLESC)
+ if ((unsigned char)*p == CTLESC)
p++;
if (*p == '\0') /* huh? */
return 0;
INT_ON;
nometa:
*exparg.lastp = str;
- rmescapes(str->text, 0);
+ rmescapes(str->text, 0, NULL);
exparg.lastp = &str->next;
break;
default: /* GLOB_NOSPACE */
/*
* Do metacharacter (i.e. *, ?, [...]) expansion.
*/
+typedef struct exp_t {
+ char *dir;
+ unsigned dir_max;
+} exp_t;
static void
-expmeta(char *expdir, char *enddir, char *name)
+expmeta(exp_t *exp, char *name, unsigned name_len, unsigned expdir_len)
{
+#define expdir exp->dir
+#define expdir_max exp->dir_max
+ char *enddir = expdir + expdir_len;
char *p;
const char *cp;
char *start;
}
}
} else {
- if (*p == '\\')
+ if (*p == '\\' && p[1])
esc++;
if (p[esc] == '/') {
if (metaflag)
}
}
if (metaflag == 0) { /* we've reached the end of the file name */
- if (enddir != expdir)
- metaflag++;
+ if (!expdir_len)
+ return;
p = name;
do {
- if (*p == '\\')
+ if (*p == '\\' && p[1])
p++;
*enddir++ = *p;
} while (*p++);
- if (metaflag == 0 || lstat(expdir, &statb) >= 0)
+ if (lstat(expdir, &statb) == 0)
addfname(expdir);
return;
}
if (name < start) {
p = name;
do {
- if (*p == '\\')
+ if (*p == '\\' && p[1])
p++;
*enddir++ = *p++;
} while (p < start);
}
- if (enddir == expdir) {
+ *enddir = '\0';
+ cp = expdir;
+ expdir_len = enddir - cp;
+ if (!expdir_len)
cp = ".";
- } else if (enddir == expdir + 1 && *expdir == '/') {
- cp = "/";
- } else {
- cp = expdir;
- enddir[-1] = '\0';
- }
dirp = opendir(cp);
if (dirp == NULL)
return;
- if (enddir != expdir)
- enddir[-1] = '/';
if (*endname == 0) {
atend = 1;
} else {
*endname = '\0';
endname += esc + 1;
}
+ name_len -= endname - name;
matchdot = 0;
p = start;
if (*p == '\\')
strcpy(enddir, dp->d_name);
addfname(expdir);
} else {
- for (p = enddir, cp = dp->d_name; (*p++ = *cp++) != '\0';)
- continue;
- p[-1] = '/';
- expmeta(expdir, p, endname);
+ unsigned offset;
+ unsigned len;
+
+ p = stpcpy(enddir, dp->d_name);
+ *p = '/';
+
+ offset = p - expdir + 1;
+ len = offset + name_len + NAME_MAX;
+ if (len > expdir_max) {
+ len += PATH_MAX;
+ expdir = ckrealloc(expdir, len);
+ expdir_max = len;
+ }
+
+ expmeta(exp, endname, name_len, offset);
+ enddir = expdir + expdir_len;
}
}
}
closedir(dirp);
if (!atend)
endname[-esc - 1] = esc ? '\\' : '/';
+#undef expdir
+#undef expdir_max
}
static struct strlist *
/* TODO - EXP_REDIR */
while (str) {
- char *expdir;
+ exp_t exp;
struct strlist **savelastp;
struct strlist *sp;
char *p;
+ unsigned len;
if (fflag)
goto nometa;
INT_OFF;
p = preglob(str->text, RMESCAPE_ALLOC | RMESCAPE_HEAP);
- {
- int i = strlen(str->text);
-//BUGGY estimation of how long expanded name can be
- expdir = ckmalloc(i < 2048 ? 2048 : i+1);
- }
- expmeta(expdir, expdir, p);
- free(expdir);
+ len = strlen(p);
+ exp.dir_max = len + PATH_MAX;
+ exp.dir = ckmalloc(exp.dir_max);
+
+ expmeta(&exp, p, len, 0);
+ free(exp.dir);
if (p != str->text)
free(p);
INT_ON;
*/
nometa:
*exparg.lastp = str;
- rmescapes(str->text, 0);
+ rmescapes(str->text, 0, NULL);
exparg.lastp = &str->next;
} else {
*exparg.lastp = NULL;
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) {
patmatch(char *pattern, const char *string)
{
char *p = preglob(pattern, 0);
- //bb_error_msg("fnmatch(pattern:'%s',str:'%s')", p, string);
- return pmatch(p, string);
+ int r = pmatch(p, string);
+ //bb_error_msg("!fnmatch(pattern:'%s',str:'%s',0):%d", p, string, r);
+ return r;
}
/*
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);
static void
-tryexec(IF_FEATURE_SH_STANDALONE(int applet_no,) char *cmd, char **argv, char **envp)
+tryexec(IF_FEATURE_SH_STANDALONE(int applet_no,) const char *cmd, char **argv, char **envp)
{
#if ENABLE_FEATURE_SH_STANDALONE
if (applet_no >= 0) {
clearenv();
while (*envp)
putenv(*envp++);
- popredir(/*drop:*/ 1, /*restore:*/ 0);
- run_applet_no_and_exit(applet_no, cmd, argv);
+ popredir(/*drop:*/ 1);
+ run_noexec_applet_and_exit(applet_no, cmd, argv);
}
/* re-exec ourselves with the new arguments */
execve(bb_busybox_exec_path, argv, envp);
#else
execve(cmd, argv, envp);
#endif
- if (cmd != (char*) bb_busybox_exec_path && errno == ENOEXEC) {
+
+ if (cmd != 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.
*/
- argv[0] = cmd;
- cmd = (char*) bb_busybox_exec_path;
+ argv[0] = (char*) cmd;
+ cmd = bb_busybox_exec_path;
/* NB: this is only possible because all callers of shellexec()
* ensure that the argv[-1] slot exists!
*/
int exerrno;
int applet_no = -1; /* used only by FEATURE_SH_STANDALONE */
- envp = listvars(VEXPORT, VUNSET, /*end:*/ NULL);
+ envp = listvars(VEXPORT, VUNSET, /*strlist:*/ NULL, /*end:*/ NULL);
if (strchr(prog, '/') != NULL
#if ENABLE_FEATURE_SH_STANDALONE
|| (applet_no = find_applet_by_name(prog)) >= 0
/* Map to POSIX errors */
switch (e) {
- case EACCES:
+ default:
exerrno = 126;
break;
+ case ELOOP:
+ case ENAMETOOLONG:
case ENOENT:
+ case ENOTDIR:
exerrno = 127;
break;
- default:
- exerrno = 2;
- break;
}
exitstatus = exerrno;
TRACE(("shellexec failed for %s, errno %d, suppress_int %d\n",
case CMDFUNCTION:
if (describe_command_verbose) {
- out1str(" is a shell function");
+ /*out1str(" is a shell function");*/
+ out1str(" is a function"); /* bash says this */
} else {
out1str(command);
}
funcblocksize = calcsize(funcblocksize, n->nclist.next);
break;
case NDEFUN:
+ funcblocksize = calcsize(funcblocksize, n->ndefun.body);
+ funcblocksize += SHELL_ALIGN(strlen(n->ndefun.text) + 1);
+ break;
case NARG:
funcblocksize = sizenodelist(funcblocksize, n->narg.backquote);
funcblocksize += SHELL_ALIGN(strlen(n->narg.text) + 1); /* was funcstringsize += ... */
new->ncmd.redirect = copynode(n->ncmd.redirect);
new->ncmd.args = copynode(n->ncmd.args);
new->ncmd.assign = copynode(n->ncmd.assign);
+ new->ncmd.linno = n->ncmd.linno;
break;
case NPIPE:
new->npipe.cmdlist = copynodelist(n->npipe.cmdlist);
case NSUBSHELL:
new->nredir.redirect = copynode(n->nredir.redirect);
new->nredir.n = copynode(n->nredir.n);
+ new->nredir.linno = n->nredir.linno;
break;
case NAND:
case NOR:
new->nfor.var = nodeckstrdup(n->nfor.var);
new->nfor.body = copynode(n->nfor.body);
new->nfor.args = copynode(n->nfor.args);
+ new->nfor.linno = n->nfor.linno;
break;
case NCASE:
new->ncase.cases = copynode(n->ncase.cases);
new->ncase.expr = copynode(n->ncase.expr);
+ new->ncase.linno = n->ncase.linno;
break;
case NCLIST:
new->nclist.body = copynode(n->nclist.body);
new->nclist.next = copynode(n->nclist.next);
break;
case NDEFUN:
+ new->ndefun.body = copynode(n->ndefun.body);
+ new->ndefun.text = nodeckstrdup(n->ndefun.text);
+ new->ndefun.linno = n->ndefun.linno;
+ break;
case NARG:
new->narg.backquote = copynodelist(n->narg.backquote);
new->narg.text = nodeckstrdup(n->narg.text);
INT_OFF;
entry.cmdtype = CMDFUNCTION;
entry.u.func = copyfunc(func);
- addcmdentry(func->narg.text, &entry);
+ addcmdentry(func->ndefun.text, &entry);
INT_ON;
}
#define SKIPFUNC (1 << 2)
static smallint evalskip; /* set to SKIPxxx if we are skipping commands */
static int skipcount; /* number of levels to skip */
-static int funcnest; /* depth of function calls */
static int loopnest; /* current loop nesting level */
+static int funcline; /* starting line number of current function, or 0 if not in a function */
/* Forward decl way out to parsing code - dotrap needs it */
static int evalstring(char *s, int flags);
status = !evaltree(n->nnot.com, EV_TESTED);
goto setstatus;
case NREDIR:
+ errlinno = lineno = n->nredir.linno;
+ if (funcline)
+ lineno -= funcline - 1;
expredir(n->nredir.redirect);
+ pushredir(n->nredir.redirect);
status = redirectsafe(n->nredir.redirect, REDIR_PUSH);
if (!status) {
status = evaltree(n->nredir.n, flags & EV_TESTED);
}
if (n->nredir.redirect)
- popredir(/*drop:*/ 0, /*restore:*/ 0 /* not sure */);
+ popredir(/*drop:*/ 0);
goto setstatus;
case NCMD:
evalfn = evalcommand;
return exitstatus;
}
-#if !defined(__alpha__) || (defined(__GNUC__) && __GNUC__ >= 3)
-static
-#endif
-int evaltreenr(union node *, int) __attribute__ ((alias("evaltree"),__noreturn__));
-
static int
skiploop(void)
{
struct stackmark smark;
int status = 0;
+ errlinno = lineno = n->ncase.linno;
+ if (funcline)
+ lineno -= funcline - 1;
+
setstackmark(&smark);
arglist.list = NULL;
arglist.lastp = &arglist.list;
struct stackmark smark;
int status = 0;
+ errlinno = lineno = n->ncase.linno;
+ if (funcline)
+ lineno -= funcline - 1;
+
setstackmark(&smark);
arglist.list = NULL;
arglist.lastp = &arglist.list;
int backgnd = (n->type == NBACKGND); /* FORK_BG(1) if yes, else FORK_FG(0) */
int status;
+ errlinno = lineno = n->nredir.linno;
+ if (funcline)
+ lineno -= funcline - 1;
+
expredir(n->nredir.redirect);
if (!backgnd && (flags & EV_EXIT) && !may_have_traps)
goto nofork;
if (lp->next) {
if (pipe(pip) < 0) {
close(prevfd);
- ash_msg_and_raise_error("pipe call failed");
+ ash_msg_and_raise_perror("can't create pipe");
}
}
if (forkshell(jp, lp->n, n->npipe.pipe_backgnd) == 0) {
struct jmploc *volatile savehandler;
struct jmploc jmploc;
int e;
+ int savefuncline;
saveparam = shellparam;
+ savefuncline = funcline;
savehandler = exception_handler;
e = setjmp(jmploc.loc);
if (e) {
exception_handler = &jmploc;
shellparam.malloced = 0;
func->count++;
- funcnest++;
+ funcline = func->n.ndefun.linno;
INT_ON;
shellparam.nparam = argc - 1;
shellparam.p = argv + 1;
shellparam.optind = 1;
shellparam.optoff = -1;
#endif
- pushlocalvars();
- evaltree(func->n.narg.next, flags & EV_TESTED);
- poplocalvars(0);
+ evaltree(func->n.ndefun.body, flags & EV_TESTED);
funcdone:
INT_OFF;
- funcnest--;
+ funcline = savefuncline;
freefunc(func);
freeparam(&shellparam);
shellparam = saveparam;
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;
/*
* Execute a simple command.
*/
+static void unwindfiles(struct parsefile *stop);
static int
isassignment(const char *p)
{
"\0\0", bltincmd /* why three NULs? */
};
struct localvar_list *localvar_stop;
+ struct parsefile *file_stop;
+ struct redirtab *redir_stop;
struct stackmark smark;
union node *argp;
struct arglist arglist;
int spclbltin;
int status;
char **nargv;
- struct builtincmd *bcmd;
smallint cmd_is_exec;
- smallint pseudovarflag = 0;
+
+ errlinno = lineno = cmd->ncmd.linno;
+ if (funcline)
+ lineno -= funcline - 1;
/* First expand the arguments. */
TRACE(("evalcommand(0x%lx, %d) called\n", (long)cmd, flags));
setstackmark(&smark);
localvar_stop = pushlocalvars();
+ file_stop = g_parsefile;
back_exitstatus = 0;
cmdentry.cmdtype = CMDBUILTIN;
argc = 0;
if (cmd->ncmd.args) {
+ struct builtincmd *bcmd;
+ smallint pseudovarflag;
+
bcmd = find_builtin(cmd->ncmd.args->narg.text);
pseudovarflag = bcmd && IS_BUILTIN_ASSIGN(bcmd);
- }
- for (argp = cmd->ncmd.args; argp; argp = argp->narg.next) {
- struct strlist **spp;
+ for (argp = cmd->ncmd.args; argp; argp = argp->narg.next) {
+ struct strlist **spp;
- spp = arglist.lastp;
- if (pseudovarflag && isassignment(argp->narg.text))
- expandarg(argp, &arglist, EXP_VARTILDE);
- else
- expandarg(argp, &arglist, EXP_FULL | EXP_TILDE);
+ spp = arglist.lastp;
+ if (pseudovarflag && isassignment(argp->narg.text))
+ expandarg(argp, &arglist, EXP_VARTILDE);
+ else
+ expandarg(argp, &arglist, EXP_FULL | EXP_TILDE);
- for (sp = *spp; sp; sp = sp->next)
- argc++;
+ for (sp = *spp; sp; sp = sp->next)
+ argc++;
+ }
}
/* Reserve one extra spot at the front for shellexec. */
*nargv = NULL;
lastarg = NULL;
- if (iflag && funcnest == 0 && argc > 0)
+ if (iflag && funcline == 0 && argc > 0)
lastarg = nargv[-1];
- preverrout_fd = 2;
expredir(cmd->ncmd.redirect);
+ redir_stop = pushredir(cmd->ncmd.redirect);
+ preverrout_fd = 2;
+ if (BASH_XTRACEFD && xflag) {
+ /* NB: bash closes fd == $BASH_XTRACEFD when it is changed.
+ * we do not emulate this. We only use its value.
+ */
+ const char *xtracefd = lookupvar("BASH_XTRACEFD");
+ if (xtracefd && is_number(xtracefd))
+ preverrout_fd = atoi(xtracefd);
+
+ }
status = redirectsafe(cmd->ncmd.redirect, REDIR_PUSH | REDIR_SAVEFD2);
path = vpath.var_text;
if (xflag) {
const char *pfx = "";
- fdprintf(preverrout_fd, "%s", expandstr(ps4val()));
+ fdprintf(preverrout_fd, "%s", expandstr(ps4val(), DQSYNTAX));
sp = varlist.list;
while (sp) {
switch (cmdentry.cmdtype) {
default: {
-#if ENABLE_FEATURE_SH_NOFORK
+#if ENABLE_FEATURE_SH_STANDALONE \
+ && ENABLE_FEATURE_SH_NOFORK \
+ && NUM_APPLETS > 1
/* (1) BUG: if variables are set, we need to fork, or save/restore them
* around run_nofork_applet() call.
* (2) Should this check also be done in forkshell()?
/* find_command() encodes applet_no as (-2 - applet_no) */
int applet_no = (- cmdentry.u.index - 2);
if (applet_no >= 0 && APPLET_IS_NOFORK(applet_no)) {
- listsetvar(varlist.list, VEXPORT|VSTACK);
- /* run <applet>_main() */
+ char **sv_environ;
+
+ INT_OFF;
+ sv_environ = environ;
+ environ = listvars(VEXPORT, VUNSET, varlist.list, /*end:*/ NULL);
+ /*
+ * Run <applet>_main().
+ * Signals (^C) can't interrupt here.
+ * Otherwise we can mangle stdio or malloc internal state.
+ * This makes applets which can run for a long time
+ * and/or wait for user input ineligible for NOFORK:
+ * for example, "yes" or "rm" (rm -i waits for input).
+ */
status = run_nofork_applet(applet_no, argv);
+ environ = sv_environ;
+ /*
+ * Try enabling NOFORK for "yes" applet.
+ * ^C _will_ stop it (write returns EINTR),
+ * but this causes stdout FILE to be stuck
+ * and needing clearerr(). What if other applets
+ * also can get EINTRs? Do we need to switch
+ * our signals to SA_RESTART?
+ */
+ /*clearerr(stdout);*/
+ INT_ON;
break;
}
#endif
- /* Can we avoid forking off? For example, very last command
+ /* Can we avoid forking? For example, very last command
* in a script or a subshell does not need forking,
* we can just exec it.
*/
goto readstatus;
case CMDFUNCTION:
- 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);
+ popredir(/*drop:*/ cmd_is_exec);
+ unwindredir(redir_stop);
+ unwindfiles(file_stop);
unwindlocalvars(localvar_stop);
if (lastarg) {
/* dsl: I think this is intended to be used to support
#define CHKALIAS 0x1
#define CHKKWD 0x2
#define CHKNL 0x4
+#define CHKEOFMARK 0x8
/*
* Push a string back onto the input at this current parsefile level.
if (!iflag || g_parsefile->pf_fd != STDIN_FILENO)
nr = nonblock_immune_read(g_parsefile->pf_fd, buf, IBUFSIZ - 1);
else {
- int timeout = -1;
# if ENABLE_ASH_IDLE_TIMEOUT
+ int timeout = -1;
if (iflag) {
const char *tmout_var = lookupvar("TMOUT");
if (tmout_var) {
timeout = -1;
}
}
+ line_input_state->timeout = timeout;
# endif
# if ENABLE_FEATURE_TAB_COMPLETION
line_input_state->path_lookup = pathval();
# endif
reinit_unicode_for_ash();
- nr = read_line_input(line_input_state, cmdedit_prompt, buf, IBUFSIZ, timeout);
+ nr = read_line_input(line_input_state, cmdedit_prompt, buf, IBUFSIZ);
if (nr == 0) {
/* ^C pressed, "convert" to SIGINT */
write(STDOUT_FILENO, "^C", 2);
# define pgetc_without_PEOA() pgetc()
#endif
-/*
- * Read a line from the script.
- */
-static char *
-pfgets(char *line, int len)
-{
- char *p = line;
- int nleft = len;
- int c;
-
- while (--nleft > 0) {
- c = pgetc_without_PEOA();
- if (c == PEOF) {
- if (p == line)
- return NULL;
- break;
- }
- *p++ = c;
- if (c == '\n')
- break;
- }
- *p = '\0';
- return line;
-}
-
/*
* Undo a call to pgetc. Only two characters may be pushed back.
* PEOF may be pushed back.
return c;
}
+struct synstack {
+ smalluint syntax;
+ uint8_t innerdq :1;
+ uint8_t varpushed :1;
+ uint8_t dblquote :1;
+ int varnest; /* levels of variables expansion */
+ int dqvarnest; /* levels of variables expansion within double quotes */
+ int parenlevel; /* levels of parens in arithmetic */
+ struct synstack *prev;
+ struct synstack *next;
+};
+
+static void
+synstack_push(struct synstack **stack, struct synstack *next, int syntax)
+{
+ memset(next, 0, sizeof(*next));
+ next->syntax = syntax;
+ next->next = *stack;
+ (*stack)->prev = next;
+ *stack = next;
+}
+
+static ALWAYS_INLINE void
+synstack_pop(struct synstack **stack)
+{
+ *stack = (*stack)->next;
+}
+
/*
* To handle the "." command, a stack of input files is used. Pushfile
* adds a new entry to the stack and popfile restores the previous level.
INT_ON;
}
+static void
+unwindfiles(struct parsefile *stop)
+{
+ while (g_parsefile != stop)
+ popfile();
+}
+
/*
* Return to top level.
*/
static void
popallfiles(void)
{
- while (g_parsefile != &basepf)
- popfile();
+ unwindfiles(&basepf);
}
/*
int fd;
INT_OFF;
- fd = open(fname, O_RDONLY);
+ fd = open(fname, O_RDONLY | O_CLOEXEC);
if (fd < 0) {
if (flags & INPUT_NOFILE_OK)
goto out;
exitstatus = 127;
- ash_msg_and_raise_error("can't open '%s'", fname);
+ ash_msg_and_raise_perror("can't open '%s'", fname);
}
if (fd < 10)
fd = savefd(fd);
- else
+ else if (O_CLOEXEC == 0) /* old libc */
close_on_exec_on(fd);
+
setinputfd(fd, flags & INPUT_PUSH_FILE);
out:
INT_ON;
/* NOTREACHED */
}
static int
-options(int cmdline)
+options(int cmdline, int *login_sh)
{
char *p;
int val;
if (*argptr)
argptr++;
} else if (cmdline && (c == 'l')) { /* -l or +l == --login */
- isloginsh = 1;
+ if (login_sh)
+ *login_sh = 1;
/* bash does not accept +-login, we also won't */
} else if (cmdline && val && (c == '-')) { /* long options */
- if (strcmp(p, "login") == 0)
- isloginsh = 1;
+ if (strcmp(p, "login") == 0) {
+ if (login_sh)
+ *login_sh = 1;
+ }
break;
} else {
setoption(c, val);
if (argv[1])
n = number(argv[1]);
if (n > shellparam.nparam)
- n = 0; /* bash compat, was = shellparam.nparam; */
+ return 1;
INT_OFF;
shellparam.nparam -= n;
for (ap1 = shellparam.p; --n >= 0; ap1++) {
const char *sep;
char **ep, **epend;
- ep = listvars(on, off, &epend);
+ ep = listvars(on, off, /*strlist:*/ NULL, &epend);
qsort(ep, epend - ep, sizeof(char *), vpcmp);
sep = *sep_prefix ? " " : sep_prefix;
const char *p;
const char *q;
- p = strchrnul(*ep, '=');
+ p = endofname(*ep);
+/* Used to have simple "p = strchrnul(*ep, '=')" here instead, but this
+ * makes "export -p" to have output not suitable for "eval":
+ * import os
+ * os.environ["test-test"]="test"
+ * if os.fork() == 0:
+ * os.execv("ash", [ 'ash', '-c', 'eval $(export -p); echo OK' ]) # fixes this
+ * os.execv("ash", [ 'ash', '-c', 'env | grep test-test' ])
+ */
q = nullstr;
- if (*p)
+ if (*p == '=')
q = single_quote(++p);
out1fmt("%s%s%.*s%s\n", sep_prefix, sep, (int)(p - *ep), *ep, q);
}
return showvars(nullstr, 0, VUNSET);
INT_OFF;
- retval = options(/*cmdline:*/ 0);
+ retval = options(/*cmdline:*/ 0, NULL);
if (retval == 0) { /* if no parse error... */
optschanged();
if (*argptr != NULL) {
p = *optnext;
if (p == NULL || *p != '-' || *++p == '\0') {
atend:
+ unsetvar("OPTARG");
p = NULL;
done = 1;
goto out;
c = *p++;
for (q = optstr; *q != c;) {
if (*q == '\0') {
- if (optstr[0] == ':') {
+ /* OPTERR is a bashism */
+ const char *cp = lookupvar("OPTERR");
+ if ((cp && LONE_CHAR(cp, '0'))
+ || (optstr[0] == ':')
+ ) {
sbuf[0] = c;
/*sbuf[1] = '\0'; - already is */
setvar0("OPTARG", sbuf);
if (*++q == ':') {
if (*p == '\0' && (p = *optnext) == NULL) {
- if (optstr[0] == ':') {
+ /* OPTERR is a bashism */
+ const char *cp = lookupvar("OPTERR");
+ if ((cp && LONE_CHAR(cp, '0'))
+ || (optstr[0] == ':')
+ ) {
sbuf[0] = c;
/*sbuf[1] = '\0'; - already is */
setvar0("OPTARG", sbuf);
/* NOTREACHED */
}
-#define EOFMARKLEN 79
-
/* parsing is heavily cross-recursive, need these forward decls */
static union node *andor(void);
static union node *pipeline(void);
}
}
-/*
- * Returns true if the text contains nothing to expand (no dollar signs
- * or backquotes).
- */
-static int
-noexpand(const char *text)
-{
- unsigned char c;
-
- while ((c = *text++) != '\0') {
- if (c == CTLQUOTEMARK)
- continue;
- if (c == CTLESC)
- text++;
- else if (SIT(c, BASESYNTAX) == CCTL)
- return 0;
- }
- return 1;
-}
-
static void
parsefname(void)
{
union node *n = redirnode;
+ if (n->type == NHERE)
+ checkkwd = CHKEOFMARK;
if (readtoken() != TWORD)
raise_error_unexpected_syntax(-1);
if (n->type == NHERE) {
struct heredoc *here = heredoc;
struct heredoc *p;
- int i;
if (quoteflag == 0)
n->type = NXHERE;
TRACE(("Here document %d\n", n->type));
- if (!noexpand(wordtext) || (i = strlen(wordtext)) == 0 || i > EOFMARKLEN)
- raise_error_syntax("illegal eof marker for << redirection");
- rmescapes(wordtext, 0);
+ rmescapes(wordtext, 0, NULL);
here->eofmark = wordtext;
here->next = NULL;
if (heredoclist == NULL)
union node *vars, **vpp;
union node **rpp, *redir;
int savecheckkwd;
+ int savelinno;
#if BASH_TEST2
smallint double_brackets_flag = 0;
#endif
rpp = &redir;
savecheckkwd = CHKALIAS;
+ savelinno = g_parsefile->linno;
for (;;) {
int t;
checkkwd = savecheckkwd;
case TLP:
function_flag = 0;
break;
+# if BASH_TEST2
case TWORD:
if (strcmp("[[", wordtext) == 0)
goto do_func;
/* fall through */
+# endif
default:
raise_error_unexpected_syntax(-1);
}
}
n->type = NDEFUN;
checkkwd = CHKNL | CHKKWD | CHKALIAS;
- n->narg.next = parse_command();
+ n->ndefun.text = n->narg.text;
+ n->ndefun.linno = g_parsefile->linno;
+ n->ndefun.body = parse_command();
return n;
}
IF_BASH_FUNCTION(function_flag = 0;)
*vpp = NULL;
*rpp = NULL;
n = stzalloc(sizeof(struct ncmd));
- n->type = NCMD;
+ if (NCMD != 0)
+ n->type = NCMD;
+ n->ncmd.linno = savelinno;
n->ncmd.args = args;
n->ncmd.assign = vars;
n->ncmd.redirect = redir;
union node *redir, **rpp;
union node **rpp2;
int t;
+ int savelinno;
redir = NULL;
rpp2 = &redir;
+ savelinno = g_parsefile->linno;
+
switch (readtoken()) {
default:
raise_error_unexpected_syntax(-1);
raise_error_syntax("bad for loop variable");
n1 = stzalloc(sizeof(struct nfor));
n1->type = NFOR;
+ n1->nfor.linno = savelinno;
n1->nfor.var = wordtext;
checkkwd = CHKNL | CHKKWD | CHKALIAS;
if (readtoken() == TIN) {
case TCASE:
n1 = stzalloc(sizeof(struct ncase));
n1->type = NCASE;
+ n1->ncase.linno = savelinno;
if (readtoken() != TWORD)
raise_error_unexpected_syntax(TWORD);
n1->ncase.expr = n2 = stzalloc(sizeof(struct narg));
case TLP:
n1 = stzalloc(sizeof(struct nredir));
n1->type = NSUBSHELL;
+ n1->nredir.linno = savelinno;
n1->nredir.n = list(0);
/*n1->nredir.redirect = NULL; - stzalloc did it */
t = TRP;
if (n1->type != NSUBSHELL) {
n2 = stzalloc(sizeof(struct nredir));
n2->type = NREDIR;
+ n2->nredir.linno = savelinno;
n2->nredir.n = n1;
n1 = n2;
}
}
#endif
+/* Used by expandstr to get here-doc like behaviour. */
+#define FAKEEOFMARK ((char*)(uintptr_t)1)
+
+static ALWAYS_INLINE int
+realeofmark(const char *eofmark)
+{
+ return eofmark && eofmark != FAKEEOFMARK;
+}
+
/*
* If eofmark is NULL, read a word or a redirection symbol. If eofmark
* is not NULL, read a here document. In the latter case, eofmark is the
/* c parameter is an unsigned char or PEOF or PEOA */
char *out;
size_t len;
- char line[EOFMARKLEN + 1];
struct nodelist *bqlist;
smallint quotef;
- smallint dblquote;
smallint oldstyle;
- IF_FEATURE_SH_MATH(smallint prevsyntax;) /* syntax before arithmetic */
smallint pssyntax; /* we are expanding a prompt string */
- int varnest; /* levels of variables expansion */
- 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_BASH_DOLLAR_SQUOTE(smallint bash_dollar_squote = 0;)
+ /* syntax stack */
+ struct synstack synbase = { };
+ struct synstack *synstack = &synbase;
- startlinno = g_parsefile->linno;
- bqlist = NULL;
- quotef = 0;
- IF_FEATURE_SH_MATH(prevsyntax = 0;)
+#if ENABLE_ASH_EXPAND_PRMT
pssyntax = (syntax == PSSYNTAX);
if (pssyntax)
syntax = DQSYNTAX;
- dblquote = (syntax == DQSYNTAX);
- varnest = 0;
- IF_FEATURE_SH_MATH(arinest = 0;)
- IF_FEATURE_SH_MATH(parenlevel = 0;)
- dqvarnest = 0;
+#else
+ pssyntax = 0; /* constant */
+#endif
+ synstack->syntax = syntax;
+
+ if (syntax == DQSYNTAX)
+ synstack->dblquote = 1;
+ quotef = 0;
+ bqlist = NULL;
STARTSTACKSTR(out);
loop:
CHECKEND(); /* set c to PEOF if at end of here document */
for (;;) { /* until end of line or end of word */
CHECKSTRSPACE(4, out); /* permit 4 calls to USTPUTC */
- switch (SIT(c, syntax)) {
+ switch (SIT(c, synstack->syntax)) {
case CNL: /* '\n' */
- if (syntax == BASESYNTAX)
+ if (synstack->syntax == BASESYNTAX
+ && !synstack->varnest
+ ) {
goto endword; /* exit outer loop */
+ }
USTPUTC(c, out);
nlprompt();
c = pgetc();
if (c & 0x100) {
/* Unknown escape. Encode as '\z' */
c = (unsigned char)c;
- if (eofmark == NULL || dblquote)
+ if (eofmark == NULL || synstack->dblquote)
USTPUTC(CTLESC, out);
USTPUTC('\\', out);
}
}
#endif
- if (eofmark == NULL || dblquote)
+ if (!eofmark || synstack->dblquote || synstack->varnest)
USTPUTC(CTLESC, out);
USTPUTC(c, out);
break;
} else if (c == '\n') {
nlprompt();
} else {
- if (c == '$' && pssyntax) {
+ if (pssyntax && c == '$') {
USTPUTC(CTLESC, out);
USTPUTC('\\', out);
}
- /* Backslash is retained if we are in "str" and next char isn't special */
- if (dblquote
+ /* Backslash is retained if we are in "str"
+ * and next char isn't dquote-special.
+ */
+ if (synstack->dblquote
&& c != '\\'
&& c != '`'
&& c != '$'
- && (c != '"' || eofmark != NULL)
+ && (c != '"' || (eofmark != NULL && !synstack->varnest))
+ && (c != '}' || !synstack->varnest)
) {
+ USTPUTC(CTLESC, out); /* protect '\' from glob */
USTPUTC('\\', out);
}
USTPUTC(CTLESC, out);
}
break;
case CSQUOTE:
- syntax = SQSYNTAX;
+ synstack->syntax = SQSYNTAX;
quotemark:
if (eofmark == NULL) {
USTPUTC(CTLQUOTEMARK, out);
}
break;
case CDQUOTE:
- syntax = DQSYNTAX;
- dblquote = 1;
+ synstack->syntax = DQSYNTAX;
+ synstack->dblquote = 1;
+ toggledq:
+ if (synstack->varnest)
+ synstack->innerdq ^= 1;
goto quotemark;
case CENDQUOTE:
IF_BASH_DOLLAR_SQUOTE(bash_dollar_squote = 0;)
- if (eofmark != NULL && varnest == 0) {
+ if (eofmark != NULL && synstack->varnest == 0) {
USTPUTC(c, out);
- } else {
- if (dqvarnest == 0) {
- syntax = BASESYNTAX;
- dblquote = 0;
- }
- quotef = 1;
- goto quotemark;
+ break;
}
- break;
+
+ if (synstack->dqvarnest == 0) {
+ synstack->syntax = BASESYNTAX;
+ synstack->dblquote = 0;
+ }
+
+ quotef = 1;
+
+ if (c == '"')
+ goto toggledq;
+
+ goto quotemark;
case CVAR: /* '$' */
PARSESUB(); /* parse substitution */
break;
case CENDVAR: /* '}' */
- if (varnest > 0) {
- varnest--;
- if (dqvarnest > 0) {
- dqvarnest--;
- }
+ if (!synstack->innerdq && synstack->varnest > 0) {
+ if (!--synstack->varnest && synstack->varpushed)
+ synstack_pop(&synstack);
+ else if (synstack->dqvarnest > 0)
+ synstack->dqvarnest--;
c = CTLENDVAR;
}
USTPUTC(c, out);
break;
#if ENABLE_FEATURE_SH_MATH
case CLP: /* '(' in arithmetic */
- parenlevel++;
+ synstack->parenlevel++;
USTPUTC(c, out);
break;
case CRP: /* ')' in arithmetic */
- if (parenlevel > 0) {
- parenlevel--;
+ if (synstack->parenlevel > 0) {
+ synstack->parenlevel--;
} else {
if (pgetc_eatbnl() == ')') {
c = CTLENDARI;
- if (--arinest == 0) {
- syntax = prevsyntax;
- }
+ synstack_pop(&synstack);
} else {
/*
* unbalanced parens
break;
#endif
case CBQUOTE: /* '`' */
+ if (checkkwd & CHKEOFMARK) {
+ quotef = 1;
+ USTPUTC('`', out);
+ break;
+ }
+
PARSEBACKQOLD();
break;
case CENDFILE:
case CIGN:
break;
default:
- if (varnest == 0) {
+ if (synstack->varnest == 0) {
#if BASH_REDIR_OUTPUT
if (c == '&') {
//Can't call pgetc_eatbnl() here, this requires three-deep pungetc()
endword:
#if ENABLE_FEATURE_SH_MATH
- if (syntax == ARISYNTAX)
+ if (synstack->syntax == ARISYNTAX)
raise_error_syntax("missing '))'");
#endif
- if (syntax != BASESYNTAX && eofmark == NULL)
+ if (synstack->syntax != BASESYNTAX && eofmark == NULL)
raise_error_syntax("unterminated quoted string");
- if (varnest != 0) {
- startlinno = g_parsefile->linno;
+ if (synstack->varnest != 0) {
/* { */
raise_error_syntax("missing '}'");
}
* we are at the end of the here document, this routine sets the c to PEOF.
*/
checkend: {
- if (eofmark) {
+ if (realeofmark(eofmark)) {
+ int markloc;
+ char *p;
+
#if ENABLE_ASH_ALIAS
if (c == PEOA)
c = pgetc_without_PEOA();
c = pgetc_without_PEOA();
}
}
- if (c == *eofmark) {
- if (pfgets(line, sizeof(line)) != NULL) {
- char *p, *q;
- int cc;
-
- p = line;
- for (q = eofmark + 1;; p++, q++) {
- cc = *p;
- if (cc == '\n')
- cc = 0;
- if (!*q || cc != *q)
- break;
- }
- if (cc == *q) {
- c = PEOF;
- nlnoprompt();
- } else {
- pushstring(line, NULL);
+
+ markloc = out - (char *)stackblock();
+ for (p = eofmark; STPUTC(c, out), *p; p++) {
+ if (c != *p)
+ goto more_heredoc;
+
+ c = pgetc_without_PEOA();
+ }
+
+ if (c == '\n' || c == PEOF) {
+ c = PEOF;
+ g_parsefile->linno++;
+ needprompt = doprompt;
+ } else {
+ int len_here;
+
+ more_heredoc:
+ p = (char *)stackblock() + markloc + 1;
+ len_here = out - p;
+
+ if (len_here) {
+ len_here -= (c >= PEOF);
+ c = p[-1];
+
+ if (len_here) {
+ char *str;
+
+ str = alloca(len_here + 1);
+ *(char *)mempcpy(str, p, len_here) = '\0';
+
+ pushstring(str, NULL);
}
}
}
+
+ STADJUST((char *)stackblock() + markloc - out, out);
}
goto checkend_return;
}
np = stzalloc(sizeof(struct nfile));
if (c == '>') {
np->nfile.fd = 1;
- c = pgetc();
+ c = pgetc_eatbnl();
if (c == '>')
np->type = NAPPEND;
else if (c == '|')
#endif
else { /* c == '<' */
/*np->nfile.fd = 0; - stzalloc did it */
- c = pgetc();
+ c = pgetc_eatbnl();
switch (c) {
case '<':
if (sizeof(struct nfile) != sizeof(struct nhere)) {
np->type = NHERE;
heredoc = stzalloc(sizeof(struct heredoc));
heredoc->here = np;
- c = pgetc();
+ c = pgetc_eatbnl();
if (c == '-') {
heredoc->striptabs = 1;
} else {
int typeloc;
c = pgetc_eatbnl();
- if (c > 255 /* PEOA or PEOF */
+ if ((checkkwd & CHKEOFMARK)
+ || c > 255 /* PEOA or PEOF */
|| (c != '(' && c != '{' && !is_name(c) && !is_special(c))
) {
#if BASH_DOLLAR_SQUOTE
- if (syntax != DQSYNTAX && c == '\'')
+ if (synstack->syntax != DQSYNTAX && c == '\'')
bash_dollar_squote = 1;
else
#endif
}
} else {
/* $VAR, $<specialchar>, ${...}, or PEOA/PEOF */
+ smalluint newsyn = synstack->syntax;
+
USTPUTC(CTLVAR, out);
typeloc = out - (char *)stackblock();
STADJUST(1, out);
STPUTC(c, out);
c = pgetc_eatbnl();
} while (isdigit(c));
- } else if (is_special(c)) {
+ } else if (c != '}') {
/* $[{[#]]<specialchar>[}] */
int cc = c;
cc = '#';
}
}
+
+ if (!is_special(cc)) {
+ if (subtype == VSLENGTH)
+ subtype = 0;
+ goto badsub;
+ }
+
USTPUTC(cc, out);
- } else {
+ } else
goto badsub;
- }
+
if (c != '}' && subtype == VSLENGTH) {
/* ${#VAR didn't end with } */
goto badsub;
static const char types[] ALIGN1 = "}-+?=";
/* ${VAR...} but not $VAR or ${#VAR} */
/* c == first char after VAR */
+ int cc = c;
+
switch (c) {
case ':':
c = pgetc_eatbnl();
break;
}
case '%':
- case '#': {
- int cc = c;
+ case '#':
subtype = (c == '#' ? VSTRIMLEFT : VSTRIMRIGHT);
c = pgetc_eatbnl();
- if (c != cc)
- goto badsub;
- subtype++;
+ if (c == cc)
+ subtype++;
+ else
+ pungetc();
+
+ newsyn = BASESYNTAX;
break;
- }
#if BASH_PATTERN_SUBST
case '/':
/* ${v/[/]pattern/repl} */
//TODO: encode pattern and repl separately.
-// Currently ${v/$var_with_slash/repl} is horribly broken
+// Currently cases like: v=1;echo ${v/$((1/1))/ONE}
+// are broken (should print "ONE")
subtype = VSREPLACE;
+ newsyn = BASESYNTAX;
c = pgetc_eatbnl();
if (c != '/')
goto badsub;
badsub:
pungetc();
}
+
+ if (newsyn == ARISYNTAX)
+ newsyn = DQSYNTAX;
+
+ if ((newsyn != synstack->syntax || synstack->innerdq)
+ && subtype != VSNORMAL
+ ) {
+ synstack_push(&synstack,
+ synstack->prev ?: alloca(sizeof(*synstack)),
+ newsyn);
+
+ synstack->varpushed = 1;
+ synstack->dblquote = newsyn != BASESYNTAX;
+ }
+
((unsigned char *)stackblock())[typeloc] = subtype;
if (subtype != VSNORMAL) {
- varnest++;
- if (dblquote)
- dqvarnest++;
+ synstack->varnest++;
+ if (synstack->dblquote)
+ synstack->dqvarnest++;
}
STPUTC('=', out);
}
int pc;
setprompt_if(needprompt, 2);
- pc = pgetc();
+ pc = pgetc_eatbnl();
switch (pc) {
case '`':
goto done;
case '\\':
- pc = pgetc();
- if (pc == '\n') {
- nlprompt();
- /*
- * If eating a newline, avoid putting
- * the newline into the new character
- * stream (via the STPUTC after the
- * switch).
- */
- continue;
- }
+ pc = pgetc(); /* or pgetc_eatbnl()? why (example)? */
if (pc != '\\' && pc != '`' && pc != '$'
- && (!dblquote || pc != '"')
+ && (!synstack->dblquote || pc != '"')
) {
STPUTC('\\', pout);
}
case PEOF:
IF_ASH_ALIAS(case PEOA:)
- startlinno = g_parsefile->linno;
raise_error_syntax("EOF in backquote substitution");
case '\n':
* Parse an arithmetic expansion (indicate start of one and set state)
*/
parsearith: {
- if (++arinest == 1) {
- prevsyntax = syntax;
- syntax = ARISYNTAX;
- }
+
+ synstack_push(&synstack,
+ synstack->prev ?: alloca(sizeof(*synstack)),
+ ARISYNTAX);
+ synstack->dblquote = 1;
USTPUTC(CTLARI, out);
goto parsearith_return;
}
* quoted.
* If the token is TREDIR, then we set redirnode to a structure containing
* the redirection.
- * In all cases, the variable startlinno is set to the number of the line
- * on which the token starts.
*
* [Change comment: here documents and internal procedures]
* [Readtoken shouldn't have any arguments. Perhaps we should make the
return lasttoken;
}
setprompt_if(needprompt, 2);
- startlinno = g_parsefile->linno;
for (;;) { /* until token or start of word found */
- c = pgetc();
+ c = pgetc_eatbnl();
if (c == ' ' || c == '\t' IF_ASH_ALIAS( || c == PEOA))
continue;
continue;
pungetc();
} else if (c == '\\') {
- if (pgetc() != '\n') {
- pungetc();
- break; /* return readtoken1(...) */
- }
- nlprompt();
+ break; /* return readtoken1(...) */
} else {
const char *p;
break; /* return readtoken1(...) */
if ((int)(p - xxreadtoken_chars) >= xxreadtoken_singles) {
- int cc = pgetc();
+ int cc = pgetc_eatbnl();
if (cc == c) { /* double occurrence? */
p += xxreadtoken_doubles + 1;
} else {
return lasttoken;
}
setprompt_if(needprompt, 2);
- startlinno = g_parsefile->linno;
for (;;) { /* until token or start of word found */
- c = pgetc();
+ c = pgetc_eatbnl();
switch (c) {
case ' ': case '\t':
IF_ASH_ALIAS(case PEOA:)
continue;
pungetc();
continue;
- case '\\':
- if (pgetc() == '\n') {
- nlprompt();
- continue;
- }
- pungetc();
- goto breakloop;
case '\n':
nlnoprompt();
RETURN(TNL);
case PEOF:
RETURN(TEOF);
case '&':
- if (pgetc() == '&')
+ if (pgetc_eatbnl() == '&')
RETURN(TAND);
pungetc();
RETURN(TBACKGND);
case '|':
- if (pgetc() == '|')
+ if (pgetc_eatbnl() == '|')
RETURN(TOR);
pungetc();
RETURN(TPIPE);
case ';':
- if (pgetc() == ';')
+ if (pgetc_eatbnl() == ';')
RETURN(TENDCASE);
pungetc();
RETURN(TSEMI);
RETURN(TLP);
case ')':
RETURN(TRP);
- default:
- goto breakloop;
}
+ break;
}
- breakloop:
return readtoken1(c, BASESYNTAX, (char *)NULL, 0);
#undef RETURN
}
heredoclist = NULL;
while (here) {
+ tokpushback = 0;
setprompt_if(needprompt, 2);
readtoken1(pgetc(), here->here->type == NHERE ? SQSYNTAX : DQSYNTAX,
here->eofmark, here->striptabs);
}
-/*
- * called by editline -- any expansions to the prompt should be added here.
- */
static const char *
-expandstr(const char *ps)
+expandstr(const char *ps, int syntax_type)
{
union node n;
int saveprompt;
- /* XXX Fix (char *) cast. It _is_ a bug. ps is variable's value,
- * and token processing _can_ alter it (delete NULs etc). */
+ /* XXX Fix (char *) cast. */
setinputstring((char *)ps);
saveprompt = doprompt;
doprompt = 0;
- readtoken1(pgetc(), PSSYNTAX, nullstr, 0);
+
+ /* readtoken1() might die horribly.
+ * Try a prompt with syntactically wrong command:
+ * PS1='$(date "+%H:%M:%S) > '
+ */
+ {
+ volatile int saveint;
+ struct jmploc *volatile savehandler = exception_handler;
+ struct jmploc jmploc;
+ SAVE_INT(saveint);
+ if (setjmp(jmploc.loc) == 0) {
+ exception_handler = &jmploc;
+ readtoken1(pgetc(), syntax_type, FAKEEOFMARK, 0);
+ }
+ exception_handler = savehandler;
+ RESTORE_INT(saveint);
+ }
+
doprompt = saveprompt;
popfile();
exception_handler = savehandler;
if (ex)
- longjmp(exception_handler->loc, ex);
+ longjmp(exception_handler->loc, ex);
return status;
}
if (fullname != name)
stunalloc(fullname);
}
+ /* not found in PATH */
- /* not found in the PATH */
+#if ENABLE_ASH_BASH_SOURCE_CURDIR
+ return name;
+#else
ash_msg_and_raise_error("%s: not found", name);
/* NOTREACHED */
+#endif
}
static int FAST_FUNC
/* We failed. If there was an entry for this command, delete it */
if (cmdp && updatetbl)
delete_cmd_entry();
- if (act & DO_ERR)
+ if (act & DO_ERR) {
+#if ENABLE_ASH_BASH_NOT_FOUND_HOOK
+ struct tblentry *hookp = cmdlookup("command_not_found_handle", 0);
+ if (hookp && hookp->cmdtype == CMDFUNCTION) {
+ char *argv[3];
+ argv[0] = (char*) "command_not_found_handle";
+ argv[1] = name;
+ argv[2] = NULL;
+ evalfun(hookp->param.func, 2, argv, 0);
+ entry->cmdtype = CMDUNKNOWN;
+ return;
+ }
+#endif
ash_msg("%s: %s", name, errmsg(e, "not found"));
+ }
entry->cmdtype = CMDUNKNOWN;
return;
static int FAST_FUNC
timescmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
{
- unsigned long clk_tck, s, t;
+ unsigned clk_tck;
const unsigned char *p;
struct tms buf;
clk_tck = bb_clk_tck();
- times(&buf);
+ times(&buf);
p = timescmd_str;
do {
+ unsigned sec, frac;
+ unsigned long t;
t = *(clock_t *)(((char *) &buf) + p[1]);
- s = t / clk_tck;
- t = t % clk_tck;
- out1fmt("%lum%lu.%03lus%c",
- s / 60, s % 60,
- (t * 1000) / clk_tck,
+ sec = t / clk_tck;
+ frac = t % clk_tck;
+ out1fmt("%um%u.%03us%c",
+ sec / 60, sec % 60,
+ (frac * 1000) / clk_tck,
p[0]);
p += 2;
} while (*p);
* -p PROMPT Display PROMPT on stderr (if input is from tty)
* -t SECONDS Timeout after SECONDS (tty or pipe only)
* -u FD Read from given FD instead of fd 0
+ * -d DELIM End on DELIM char, not newline
* This uses unbuffered input, which may be avoidable in some cases.
* TODO: bash also has:
* -a ARRAY Read into array[0],[1],etc
- * -d DELIM End on DELIM char, not newline
* -e Use line editing (tty only)
*/
static int FAST_FUNC
readcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
{
- char *opt_n = NULL;
- char *opt_p = NULL;
- char *opt_t = NULL;
- char *opt_u = NULL;
- int read_flags = 0;
+ struct builtin_read_params params;
const char *r;
int i;
- while ((i = nextopt("p:u:rt:n:s")) != '\0') {
+ memset(¶ms, 0, sizeof(params));
+
+ while ((i = nextopt("p:u:rt:n:sd:")) != '\0') {
switch (i) {
case 'p':
- opt_p = optionarg;
+ params.opt_p = optionarg;
break;
case 'n':
- opt_n = optionarg;
+ params.opt_n = optionarg;
break;
case 's':
- read_flags |= BUILTIN_READ_SILENT;
+ params.read_flags |= BUILTIN_READ_SILENT;
break;
case 't':
- opt_t = optionarg;
+ params.opt_t = optionarg;
break;
case 'r':
- read_flags |= BUILTIN_READ_RAW;
+ params.read_flags |= BUILTIN_READ_RAW;
break;
case 'u':
- opt_u = optionarg;
+ params.opt_u = optionarg;
+ break;
+#if BASH_READ_D
+ case 'd':
+ params.opt_d = optionarg;
break;
+#endif
default:
break;
}
}
+ params.argv = argptr;
+ params.setvar = setvar0;
+ params.ifs = bltinlookup("IFS"); /* can be NULL */
+
/* "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,
- bltinlookup("IFS"), /* can be NULL */
- read_flags,
- opt_n,
- opt_p,
- opt_t,
- opt_u
- );
+ r = shell_builtin_read(¶ms);
INT_ON;
if ((uintptr_t)r == 1 && errno == EINTR) {
- /* to get SIGCHLD: sleep 1 & read x; echo $x */
+ /* To get SIGCHLD: sleep 1 & read x; echo $x
+ * Correct behavior is to not exit "read"
+ */
if (pending_sig == 0)
goto again;
}
}
} else {
char *modestr = *argptr;
- /* numeric umasks are taken as-is */
- /* symbolic umasks are inverted: "umask a=rx" calls umask(222) */
+ /* numeric umasks are taken as-is */
+ /* symbolic umasks are inverted: "umask a=rx" calls umask(222) */
if (!isdigit(modestr[0]))
mask ^= 0777;
mask = bb_parse_mode(modestr, mask);
/* NOTREACHED */
}
-static void
+/* Don't inline: conserve stack of caller from having our locals too */
+static NOINLINE void
init(void)
{
/* we will never free this */
basepf.next_to_pgetc = basepf.buf = ckmalloc(IBUFSIZ);
+ basepf.linno = 1;
- sigmode[SIGCHLD - 1] = S_DFL;
+ sigmode[SIGCHLD - 1] = S_DFL; /* ensure we install handler even if it is SIG_IGNed */
setsignal(SIGCHLD);
/* bash re-enables SIGHUP which is SIG_IGNed on entry.
{
char **envp;
const char *p;
- struct stat st1, st2;
initvar();
for (envp = environ; envp && *envp; envp++) {
- p = endofname(*envp);
- if (p != *envp && *p == '=') {
+/* Used to have
+ * p = endofname(*envp);
+ * if (p != *envp && *p == '=') {
+ * here to weed out badly-named variables, but this breaks
+ * scenarios where people do want them passed to children:
+ * import os
+ * os.environ["test-test"]="test"
+ * if os.fork() == 0:
+ * os.execv("ash", [ 'ash', '-c', 'eval $(export -p); echo OK' ]) # fixes this
+ * os.execv("ash", [ 'ash', '-c', 'env | grep test-test' ]) # breaks this
+ */
+ if (strchr(*envp, '=')) {
setvareq(*envp, VEXPORT|VTEXTFIXED);
}
}
+ setvareq((char*)defifsvar, VTEXTFIXED);
setvareq((char*)defoptindvar, VTEXTFIXED);
setvar0("PPID", utoa(getppid()));
#endif
p = lookupvar("PWD");
if (p) {
+ struct stat st1, st2;
if (p[0] != '/' || stat(p, &st1) || stat(".", &st2)
|| st1.st_dev != st2.st_dev || st1.st_ino != st2.st_ino
) {
//usage:#define ash_trivial_usage
-//usage: "[-/+OPTIONS] [-/+o OPT]... [-c 'SCRIPT' [ARG0 [ARGS]] / FILE [ARGS]]"
+//usage: "[-/+OPTIONS] [-/+o OPT]... [-c 'SCRIPT' [ARG0 [ARGS]] / FILE [ARGS] / -s [ARGS]]"
//usage:#define ash_full_usage "\n\n"
//usage: "Unix shell interpreter"
/*
* Process the shell command line arguments.
*/
-static void
+static int
procargs(char **argv)
{
int i;
const char *xminusc;
char **xargv;
+ int login_sh;
xargv = argv;
+ login_sh = xargv[0] && xargv[0][0] == '-';
+#if NUM_SCRIPTS > 0
+ if (minusc)
+ goto setarg0;
+#endif
arg0 = xargv[0];
/* if (xargv[0]) - mmm, this is always true! */
xargv++;
+ argptr = xargv;
for (i = 0; i < NOPTS; i++)
optlist[i] = 2;
- argptr = xargv;
- if (options(/*cmdline:*/ 1)) {
+ if (options(/*cmdline:*/ 1, &login_sh)) {
/* it already printed err message */
raise_exception(EXERROR);
}
xargv++;
}
optschanged();
+
+ return login_sh;
}
/*
static void
read_profile(const char *name)
{
- name = expandstr(name);
+ name = expandstr(name, DQSYNTAX);
if (setinputfile(name, INPUT_PUSH_FILE | INPUT_NOFILE_OK) < 0)
return;
cmdloop(0);
popallfiles();
/* from redir.c: */
- while (redirlist)
- popredir(/*drop:*/ 0, /*restore:*/ 0);
+ unwindredir(NULL);
/* from var.c: */
unwindlocalvars(NULL);
* is used to figure out how far we had gotten.
*/
int ash_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+#if NUM_SCRIPTS > 0
+int ash_main(int argc, char **argv)
+#else
int ash_main(int argc UNUSED_PARAM, char **argv)
+#endif
+/* note: 'argc' is used only if embedded scripts are enabled */
{
volatile smallint state;
struct jmploc jmploc;
struct stackmark smark;
+ int login_sh;
/* Initialize global data */
INIT_G_misc();
init();
setstackmark(&smark);
- procargs(argv);
+
+#if NUM_SCRIPTS > 0
+ if (argc < 0)
+ /* Non-NULL minusc tells procargs that an embedded script is being run */
+ minusc = get_script_content(-argc - 1);
+#endif
+ login_sh = procargs(argv);
#if DEBUG
TRACE(("Shell args: "));
trace_puts_args(argv);
#endif
- if (argv[0] && argv[0][0] == '-')
- isloginsh = 1;
- if (isloginsh) {
+ if (login_sh) {
const char *hp;
state = 1;
* Testcase: ash -c 'exec 1>&0' must not complain. */
// 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"
+ // in save_fd_on_redirect()
evalstring(minusc, sflag ? 0 : EV_EXIT);
}
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
- * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE