* Licensed under GPLv2 or later, see file LICENSE in this source tree.
*/
//config:config ASH
-//config: bool "ash (77 kb)"
+//config: bool "ash (78 kb)"
//config: default y
//config: depends on !NOMMU
//config: help
#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_FEATURE_SH_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_EPOCH_VARS 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
+#define BASH_WAIT_N ENABLE_ASH_BASH_COMPAT
#if defined(__ANDROID_API__) && __ANDROID_API__ <= 24
/* Bionic at least up to version 24 has no glob() */
"e" "errexit",
"f" "noglob",
"I" "ignoreeof",
- "i" "interactive",
+/* The below allowed this invocation:
+ * ash -c 'set -i; echo $-; sleep 5; echo $-'
+ * to be ^C-ed and get to interactive ash prompt.
+ * bash does not support such "set -i".
+ * In our code, this is denoted by empty long name:
+ */
+ "i" "",
"m" "monitor",
"n" "noexec",
- "s" "stdin",
+/* Ditto: bash has no "set -s" */
+ "s" "",
+ "c" "",
"x" "xtrace",
"v" "verbose",
"C" "noclobber",
,"\0" "debug"
#endif
};
+//bash 4.4.23 also has these opts (with these defaults):
+//braceexpand on
+//emacs on
+//errtrace off
+//functrace off
+//hashall on
+//histexpand off
+//history on
+//interactive-comments on
+//keyword off
+//onecmd off
+//physical off
+//posix off
+//privileged off
#define optletters(n) optletters_optnames[n][0]
#define optnames(n) (optletters_optnames[n] + 1)
uint8_t exitstatus; /* exit status of last command */
uint8_t back_exitstatus;/* exit status of backquoted command */
smallint job_warning; /* user was warned about stopped jobs (can be 2, 1 or 0). */
+ int savestatus; /* exit status of last command outside traps */
int rootpid; /* pid of main shell */
/* shell level: 0 for the main shell, 1 for its children, and so on */
int shlvl;
#define mflag optlist[4]
#define nflag optlist[5]
#define sflag optlist[6]
-#define xflag optlist[7]
-#define vflag optlist[8]
-#define Cflag optlist[9]
-#define aflag optlist[10]
-#define bflag optlist[11]
-#define uflag optlist[12]
-#define viflag optlist[13]
+#define cflag optlist[7]
+#define xflag optlist[8]
+#define vflag optlist[9]
+#define Cflag optlist[10]
+#define aflag optlist[11]
+#define bflag optlist[12]
+#define uflag optlist[13]
+#define viflag optlist[14]
#if BASH_PIPEFAIL
-# define pipefail optlist[14]
+# define pipefail optlist[15]
#else
# define pipefail 0
#endif
#if DEBUG
-# define nolog optlist[14 + BASH_PIPEFAIL]
-# define debug optlist[15 + BASH_PIPEFAIL]
+# define nolog optlist[15 + BASH_PIPEFAIL]
+# define debug optlist[16 + BASH_PIPEFAIL]
#endif
/* trap handler commands */
#define exitstatus (G_misc.exitstatus )
#define back_exitstatus (G_misc.back_exitstatus )
#define job_warning (G_misc.job_warning)
+#define savestatus (G_misc.savestatus )
#define rootpid (G_misc.rootpid )
#define shlvl (G_misc.shlvl )
#define errlinno (G_misc.errlinno )
#define random_gen (G_misc.random_gen )
#define backgndpid (G_misc.backgndpid )
#define INIT_G_misc() do { \
- (*(struct globals_misc**)&ash_ptr_to_globals_misc) = xzalloc(sizeof(G_misc)); \
+ (*(struct globals_misc**)not_const_pp(&ash_ptr_to_globals_misc)) = xzalloc(sizeof(G_misc)); \
barrier(); \
+ savestatus = -1; \
curdir = nullstr; \
physdir = nullstr; \
trap_ptr = trap; \
ret = vsnprintf(outbuf, length, fmt, ap);
va_end(ap);
INT_ON;
- return ret;
+ return ret > (int)length ? length : ret;
}
static void
#define g_stacknleft (G_memstack.g_stacknleft)
#define stackbase (G_memstack.stackbase )
#define INIT_G_memstack() do { \
- (*(struct globals_memstack**)&ash_ptr_to_globals_memstack) = xzalloc(sizeof(G_memstack)); \
+ (*(struct globals_memstack**)not_const_pp(&ash_ptr_to_globals_memstack)) = xzalloc(sizeof(G_memstack)); \
barrier(); \
g_stackp = &stackbase; \
g_stacknxt = stackbase.space; \
#if ENABLE_ASH_RANDOM_SUPPORT
static void change_random(const char *) FAST_FUNC;
#endif
+#if BASH_EPOCH_VARS
+static void change_seconds(const char *) FAST_FUNC;
+static void change_realtime(const char *) FAST_FUNC;
+#endif
static const struct {
int flags;
#if ENABLE_ASH_RANDOM_SUPPORT
{ VSTRFIXED|VTEXTFIXED|VUNSET|VDYNAMIC, "RANDOM", change_random },
#endif
+#if BASH_EPOCH_VARS
+ { VSTRFIXED|VTEXTFIXED|VUNSET|VDYNAMIC, "EPOCHSECONDS", change_seconds },
+ { VSTRFIXED|VTEXTFIXED|VUNSET|VDYNAMIC, "EPOCHREALTIME", change_realtime },
+#endif
#if ENABLE_LOCALE_SUPPORT
{ VSTRFIXED|VTEXTFIXED|VUNSET, "LC_ALL" , change_lc_all },
{ VSTRFIXED|VTEXTFIXED|VUNSET, "LC_CTYPE" , change_lc_ctype },
#define linenovar (G_var.linenovar )
#define vifs varinit[0]
#if ENABLE_ASH_MAIL
-# define vmail (&vifs)[1]
-# define vmpath (&vmail)[1]
-# define vpath (&vmpath)[1]
-#else
-# define vpath (&vifs)[1]
-#endif
-#define vps1 (&vpath)[1]
-#define vps2 (&vps1)[1]
-#define vps4 (&vps2)[1]
+# define vmail varinit[1]
+# define vmpath varinit[2]
+#endif
+#define VAR_OFFSET1 (ENABLE_ASH_MAIL*2)
+#define vpath varinit[VAR_OFFSET1 + 1]
+#define vps1 varinit[VAR_OFFSET1 + 2]
+#define vps2 varinit[VAR_OFFSET1 + 3]
+#define vps4 varinit[VAR_OFFSET1 + 4]
#if ENABLE_ASH_GETOPTS
-# define voptind (&vps4)[1]
-# define vlineno (&voptind)[1]
-# if ENABLE_ASH_RANDOM_SUPPORT
-# define vrandom (&vlineno)[1]
-# endif
-#else
-# define vlineno (&vps4)[1]
-# if ENABLE_ASH_RANDOM_SUPPORT
-# define vrandom (&vlineno)[1]
-# endif
+# define voptind varinit[VAR_OFFSET1 + 5]
+#endif
+#define VAR_OFFSET2 (VAR_OFFSET1 + ENABLE_ASH_GETOPTS)
+#define vlineno varinit[VAR_OFFSET2 + 5]
+#if ENABLE_ASH_RANDOM_SUPPORT
+# define vrandom varinit[VAR_OFFSET2 + 6]
+#endif
+#define VAR_OFFSET3 (VAR_OFFSET2 + ENABLE_ASH_RANDOM_SUPPORT)
+#if BASH_EPOCH_VARS
+# define vepochs varinit[VAR_OFFSET3 + 6]
+# define vepochr varinit[VAR_OFFSET3 + 7]
#endif
#define INIT_G_var() do { \
unsigned i; \
- (*(struct globals_var**)&ash_ptr_to_globals_var) = xzalloc(sizeof(G_var)); \
+ (*(struct globals_var**)not_const_pp(&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; \
v = *findvar(hashvar(name), name);
if (v) {
-#if ENABLE_ASH_RANDOM_SUPPORT
+#if ENABLE_ASH_RANDOM_SUPPORT || BASH_EPOCH_VARS
/*
* Dynamic variables are implemented roughly the same way they are
* in bash. Namely, they're "special" so long as they aren't unset.
}
flags |= vp->flags & ~(VTEXTFIXED|VSTACK|VNOSAVE|VUNSET);
+#if ENABLE_ASH_RANDOM_SUPPORT || BASH_EPOCH_VARS
+ if (flags & VUNSET)
+ flags &= ~VDYNAMIC;
+#endif
} else {
/* variable s is not found */
if (flags & VNOSET)
}
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';
vp = setvareq(nameeq, flags | VNOSAVE);
INT_ON;
struct job {
struct procstat ps0; /* status of process */
- struct procstat *ps; /* status or processes when more than one */
+ struct procstat *ps; /* status of processes when more than one */
#if JOBS
int stopstatus; /* status of a stopped job */
#endif
#endif
static int
-sprint_status48(char *s, int status, int sigonly)
+sprint_status48(char *os, int status, int sigonly)
{
- int col;
+ char *s = os;
int st;
- col = 0;
if (!WIFEXITED(status)) {
#if JOBS
if (WIFSTOPPED(status))
}
st &= 0x7f;
//TODO: use bbox's get_signame? strsignal adds ~600 bytes to text+rodata
- col = fmtstr(s, 32, strsignal(st));
+ //s = stpncpy(s, strsignal(st), 32); //not all libc have stpncpy()
+ s += fmtstr(s, 32, strsignal(st));
if (WCOREDUMP(status)) {
- strcpy(s + col, " (core dumped)");
- col += sizeof(" (core dumped)")-1;
+ s = stpcpy(s, " (core dumped)");
}
} else if (!sigonly) {
st = WEXITSTATUS(status);
- col = fmtstr(s, 16, (st ? "Done(%d)" : "Done"), st);
+ s += fmtstr(s, 16, (st ? "Done(%d)" : "Done"), st);
}
out:
- return col;
+ return s - os;
}
static int
/* Children exist, but none are ready. Sleep until interesting signal */
#if 1
sigfillset(&mask);
- sigprocmask(SIG_SETMASK, &mask, &mask);
+ sigprocmask2(SIG_SETMASK, &mask); /* mask is updated */
while (!got_sigchld && !pending_sig)
sigsuspend(&mask);
sigprocmask(SIG_SETMASK, &mask, NULL);
#define DOWAIT_NONBLOCK 0
#define DOWAIT_BLOCK 1
#define DOWAIT_BLOCK_OR_SIG 2
+#if BASH_WAIT_N
+# define DOWAIT_JOBSTATUS 0x10 /* OR this to get job's exitstatus instead of pid */
+#endif
static int
dowait(int block, struct job *job)
int pid;
int status;
struct job *jp;
- struct job *thisjob = NULL;
+ struct job *thisjob;
+#if BASH_WAIT_N
+ bool want_jobexitstatus = (block & DOWAIT_JOBSTATUS);
+ block = (block & ~DOWAIT_JOBSTATUS);
+#endif
TRACE(("dowait(0x%x) called\n", block));
}
TRACE(("wait returns pid=%d, status=0x%x, errno=%d(%s)\n",
pid, status, errno, strerror(errno)));
+ thisjob = NULL;
if (pid <= 0)
goto out;
- thisjob = NULL;
for (jp = curjob; jp; jp = jp->prev_job) {
int jobstate;
struct procstat *ps;
out:
INT_ON;
+#if BASH_WAIT_N
+ if (want_jobexitstatus) {
+ pid = -1;
+ if (thisjob && thisjob->state == JOBDONE)
+ pid = thisjob->ps[thisjob->nprocs - 1].ps_status;
+ }
+#endif
if (thisjob && thisjob == job) {
char s[48 + 1];
int len;
struct job *job;
int retval;
struct job *jp;
-
+#if BASH_WAIT_N
+ int status;
+ char one = nextopt("n");
+#else
nextopt(nullstr);
+#endif
retval = 0;
argv = argptr;
- if (!*argv) {
- /* wait for all jobs */
+ if (!argv[0]) {
+ /* wait for all jobs / one job if -n */
for (;;) {
jp = curjob;
+#if BASH_WAIT_N
+ if (one && !jp)
+ /* exitcode of "wait -n" with nothing to wait for is 127, not 0 */
+ retval = 127;
+#endif
while (1) {
if (!jp) /* no running procs */
goto ret;
* with an exit status greater than 128, immediately after which
* the trap is executed."
*/
+#if BASH_WAIT_N
+ status = dowait(DOWAIT_BLOCK_OR_SIG | DOWAIT_JOBSTATUS, NULL);
+#else
dowait(DOWAIT_BLOCK_OR_SIG, NULL);
+#endif
/* if child sends us a signal *and immediately exits*,
* dowait() returns pid > 0. Check this case,
* not "if (dowait() < 0)"!
*/
if (pending_sig)
goto sigout;
+#if BASH_WAIT_N
+ if (one) {
+ /* wait -n waits for one _job_, not one _process_.
+ * date; sleep 3 & sleep 2 | sleep 1 & wait -n; date
+ * should wait for 2 seconds. Not 1 or 3.
+ */
+ if (status != -1 && !WIFSTOPPED(status)) {
+ retval = WEXITSTATUS(status);
+ if (WIFSIGNALED(status))
+ retval = WTERMSIG(status) + 128;
+ goto ret;
+ }
+ }
+#endif
}
}
#define EXP_CASE 0x10 /* keeps quotes around for CASE pattern */
#define EXP_VARTILDE2 0x20 /* expand tildes after colons only */
#define EXP_WORD 0x40 /* expand word in parameter expansion */
-#define EXP_QUOTED 0x80 /* expand word in double quotes */
+#define EXP_QUOTED 0x100 /* expand word in double quotes */
/*
* rmescape() flags
*/
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);
}
goto lose;
home = pw->pw_dir;
}
- if (!home || !*home)
+ if (!home)
goto lose;
*p = c;
strtodest(home, SQSYNTAX, quotes);
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 (;;) {
- /* Handle escaped slashes, e.g. "${v/\//_}" (they are CTLESC'ed by this point) */
if (*repl == '\0') {
repl = NULL;
break;
*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++;
* ash -c 'echo ${#1#}' name:'1=#'
*/
static NOINLINE ssize_t
-varvalue(char *name, int varflags, int flags, 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 '*': {
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;
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, "ed);
+ varlen = varvalue(var, varflags, flag, quoted);
if (varflags & VSNUL)
varlen--;
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;
}
/*
* 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;
#else
execve(cmd, argv, envp);
#endif
+
if (cmd != bb_busybox_exec_path && errno == ENOEXEC) {
/* Run "cmd" as a shell script:
* http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html
/* 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);
}
{
uint8_t *g;
int sig;
- uint8_t last_status;
+ int status, last_status;
if (!pending_sig)
return;
- last_status = exitstatus;
+ status = savestatus;
+ last_status = status;
+ if (status < 0) {
+ status = exitstatus;
+ savestatus = status;
+ }
pending_sig = 0;
barrier();
if (!p)
continue;
evalstring(p, 0);
+ exitstatus = status;
}
- exitstatus = last_status;
+
+ savestatus = last_status;
TRACE(("dotrap returns\n"));
}
{
int checkexit = 0;
int (*evalfn)(union node *, int);
+ struct stackmark smark;
int status = 0;
+ setstackmark(&smark);
+
if (n == NULL) {
TRACE(("evaltree(NULL) called\n"));
goto out;
if (flags & EV_EXIT)
raise_exception(EXEXIT);
+ popstackmark(&smark);
TRACE(("leaving evaltree (no interrupts)\n"));
return exitstatus;
}
struct arglist arglist;
union node *argp;
struct strlist *sp;
- 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;
for (argp = n->nfor.args; argp; argp = argp->narg.next) {
break;
}
loopnest--;
- popstackmark(&smark);
return status;
}
union node *cp;
union node *patp;
struct arglist arglist;
- 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;
expandarg(n->ncase.expr, &arglist, EXP_TILDE);
}
}
out:
- popstackmark(&smark);
-
return status;
}
return status;
}
+/* setinteractive needs this forward reference */
+#if EDITING_HAS_get_exe_name
+static const char *get_builtin_name(int i) FAST_FUNC;
+#endif
+
/*
* Controls whether the shell is interactive or not.
*/
setsignal(SIGINT);
setsignal(SIGQUIT);
setsignal(SIGTERM);
-#if !ENABLE_FEATURE_SH_EXTRA_QUIET
if (is_interactive > 1) {
+#if !ENABLE_FEATURE_SH_EXTRA_QUIET
/* Looks like they want an interactive shell */
static smallint did_banner;
);
did_banner = 1;
}
- }
#endif
+#if ENABLE_FEATURE_EDITING
+ if (!line_input_state) {
+ line_input_state = new_line_input_t(FOR_SHELL | WITH_PATH_LOOKUP);
+# if EDITING_HAS_get_exe_name
+ line_input_state->get_exe_name = get_builtin_name;
+# endif
+ }
+#endif
+ }
}
static void
setinteractive(iflag);
setjobctl(mflag);
#if ENABLE_FEATURE_EDITING_VI
- if (viflag)
- line_input_state->flags |= VI_MODE;
- else
- line_input_state->flags &= ~VI_MODE;
+ if (line_input_state) {
+ if (viflag)
+ line_input_state->flags |= VI_MODE;
+ else
+ line_input_state->flags &= ~VI_MODE;
+ }
#else
viflag = 0; /* forcibly keep the option off */
#endif
shellparam.optind = 1;
shellparam.optoff = -1;
#endif
- pushlocalvars();
evaltree(func->n.ndefun.body, flags & EV_TESTED);
- poplocalvars(0);
funcdone:
INT_OFF;
funcline = savefuncline;
#if ENABLE_ASH_GETOPTS
{ BUILTIN_REGULAR "getopts" , getoptscmd },
#endif
- { BUILTIN_NOSPEC "hash" , hashcmd },
+ { BUILTIN_REGULAR "hash" , hashcmd },
#if ENABLE_ASH_HELP
{ BUILTIN_NOSPEC "help" , helpcmd },
#endif
#if ENABLE_ASH_PRINTF
{ BUILTIN_REGULAR "printf" , printfcmd },
#endif
- { BUILTIN_NOSPEC "pwd" , pwdcmd },
+ { BUILTIN_REGULAR "pwd" , pwdcmd },
{ BUILTIN_REGULAR "read" , readcmd },
{ BUILTIN_SPEC_REG_ASSG "readonly", exportcmd },
{ BUILTIN_SPEC_REG "return" , returncmd },
{ BUILTIN_SPEC_REG "times" , timescmd },
{ BUILTIN_SPEC_REG "trap" , trapcmd },
{ BUILTIN_REGULAR "true" , truecmd },
- { BUILTIN_NOSPEC "type" , typecmd },
- { BUILTIN_NOSPEC "ulimit" , ulimitcmd },
+ { BUILTIN_REGULAR "type" , typecmd },
+ { BUILTIN_REGULAR "ulimit" , ulimitcmd },
{ BUILTIN_REGULAR "umask" , umaskcmd },
#if ENABLE_ASH_ALIAS
{ BUILTIN_REGULAR "unalias" , unaliascmd },
return bp;
}
+#if EDITING_HAS_get_exe_name
+static const char * FAST_FUNC
+get_builtin_name(int i)
+{
+ return /*i >= 0 &&*/ i < ARRAY_SIZE(builtintab) ? builtintab[i].name + 1 : NULL;
+}
+#endif
+
/*
* 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;
struct arglist varlist;
/* 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;
goto readstatus;
case CMDFUNCTION:
- poplocalvars(1);
/* See above for the rationale */
dowait(DOWAIT_NONBLOCK, NULL);
if (evalfun(cmdentry.u.func, argc, argv, flags))
if (cmd->ncmd.redirect)
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
*/
setvar0("_", lastarg);
}
- popstackmark(&smark);
return status;
}
else {
# if ENABLE_ASH_IDLE_TIMEOUT
int timeout = -1;
- if (iflag) {
- const char *tmout_var = lookupvar("TMOUT");
- if (tmout_var) {
- timeout = atoi(tmout_var) * 1000;
- if (timeout <= 0)
- timeout = -1;
- }
+ const char *tmout_var = lookupvar("TMOUT");
+ if (tmout_var) {
+ timeout = atoi(tmout_var) * 1000;
+ if (timeout <= 0)
+ timeout = -1;
}
line_input_state->timeout = timeout;
# endif
struct synstack *next;
};
+static int
+pgetc_top(struct synstack *stack)
+{
+ return stack->syntax == SQSYNTAX ? pgetc() : pgetc_eatbnl();
+}
+
static void
synstack_push(struct synstack **stack, struct synstack *next, int syntax)
{
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);
}
/*
return 1;
}
for (i = 0; i < NOPTS; i++) {
+ if (optnames(i)[0] == '\0')
+ continue;
if (val) {
out1fmt("%-16s%s\n", optnames(i), optlist[i] ? "on" : "off");
} else {
int i;
for (i = 0; i < NOPTS; i++) {
- if (optletters(i) == flag) {
+ if (optletters(i) == flag && optnames(i)[0] != '\0') {
optlist[i] = val;
return;
}
ash_msg_and_raise_error("illegal option %c%c", val ? '-' : '+', flag);
/* NOTREACHED */
}
+/* If login_sh is not NULL, we are called to parse command line opts,
+ * not "set -opts"
+ */
static int
-options(int cmdline, int *login_sh)
+options(int *login_sh)
{
char *p;
int val;
int c;
- if (cmdline)
+ if (login_sh)
minusc = NULL;
while ((p = *argptr) != NULL) {
c = *p++;
if (c == '-') {
val = 1;
if (p[0] == '\0' || LONE_DASH(p)) {
- if (!cmdline) {
+ if (!login_sh) {
/* "-" means turn off -x and -v */
if (p[0] == '\0')
xflag = vflag = 0;
}
/* first char was + or - */
while ((c = *p++) != '\0') {
- /* bash 3.2 indeed handles -c CMD and +c CMD the same */
- if (c == 'c' && cmdline) {
- minusc = p; /* command is after shell args */
- } else if (c == 'o') {
+ if (login_sh) {
+ /* bash 3.2 indeed handles -c CMD and +c CMD the same */
+ if (c == 'c') {
+ minusc = p; /* command is after shell args */
+ cflag = 1;
+ continue;
+ }
+ if (c == 's') { /* -s, +s */
+ sflag = 1;
+ continue;
+ }
+ if (c == 'i') { /* -i, +i */
+ iflag = 1;
+ continue;
+ }
+ if (c == 'l') {
+ *login_sh = 1; /* -l or +l == --login */
+ continue;
+ }
+ /* bash does not accept +-login, we also won't */
+ if (val && (c == '-')) { /* long options */
+ if (strcmp(p, "login") == 0) {
+ *login_sh = 1;
+ }
+ break;
+ }
+ }
+ if (c == 'o') {
if (plus_minus_o(*argptr, val)) {
/* it already printed err message */
return 1; /* error */
}
if (*argptr)
argptr++;
- } else if (cmdline && (c == 'l')) { /* -l or +l == --login */
- 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) {
- if (login_sh)
- *login_sh = 1;
- }
- break;
} else {
setoption(c, val);
}
return showvars(nullstr, 0, VUNSET);
INT_OFF;
- retval = options(/*cmdline:*/ 0, NULL);
+ retval = options(/*login_sh:*/ NULL);
if (retval == 0) { /* if no parse error... */
optschanged();
if (*argptr != NULL) {
}
#endif
+#if BASH_EPOCH_VARS
+static void FAST_FUNC
+change_epoch(struct var *vepoch, const char *fmt)
+{
+ struct timeval tv;
+ char buffer[sizeof("%lu.nnnnnn") + sizeof(long)*3];
+
+ gettimeofday(&tv, NULL);
+ sprintf(buffer, fmt, (unsigned long)tv.tv_sec, (unsigned)tv.tv_usec);
+ setvar(vepoch->var_text, buffer, VNOFUNC);
+ vepoch->flags &= ~VNOFUNC;
+}
+
+static void FAST_FUNC
+change_seconds(const char *value UNUSED_PARAM)
+{
+ change_epoch(&vepochs, "%lu");
+}
+
+static void FAST_FUNC
+change_realtime(const char *value UNUSED_PARAM)
+{
+ change_epoch(&vepochr, "%lu.%06u");
+}
+#endif
+
#if ENABLE_ASH_GETOPTS
static int
getopts(char *optstr, char *optvar, char **optfirst)
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);
}
*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;
CHECKSTRSPACE(4, out); /* permit 4 calls to USTPUTC */
switch (SIT(c, synstack->syntax)) {
case CNL: /* '\n' */
- if (synstack->syntax == BASESYNTAX)
+ if (synstack->syntax == BASESYNTAX
+ && !synstack->varnest
+ ) {
goto endword; /* exit outer loop */
+ }
USTPUTC(c, out);
nlprompt();
- c = pgetc();
+ c = pgetc_top(synstack);
goto loop; /* continue outer loop */
case CWORD:
USTPUTC(c, out);
USTPUTC(CTLESC, out);
USTPUTC('\\', out);
pungetc();
- } else if (c == '\n') {
- nlprompt();
} else {
if (pssyntax && c == '$') {
USTPUTC(CTLESC, out);
IF_ASH_ALIAS(if (c != PEOA))
USTPUTC(c, out);
}
- c = pgetc();
+ c = pgetc_top(synstack);
} /* for (;;) */
endword:
for (p = eofmark; STPUTC(c, out), *p; p++) {
if (c != *p)
goto more_heredoc;
-
+ /* FIXME: fails for backslash-newlined terminator:
+ * cat <<EOF
+ * ...
+ * EO\
+ * F
+ * (see heredoc_bkslash_newline2.tests)
+ */
c = pgetc_without_PEOA();
}
STPUTC(c, out);
c = pgetc_eatbnl();
} while (isdigit(c));
- } else {
+ } else if (c != '}') {
/* $[{[#]]<specialchar>[}] */
int cc = c;
}
USTPUTC(cc, out);
- }
+ } else
+ goto badsub;
if (c != '}' && subtype == VSLENGTH) {
/* ${#VAR didn't end with } */
heredoclist = NULL;
while (here) {
+ tokpushback = 0;
setprompt_if(needprompt, 2);
- readtoken1(pgetc(), here->here->type == NHERE ? SQSYNTAX : DQSYNTAX,
- here->eofmark, here->striptabs);
+ if (here->here->type == NHERE)
+ readtoken1(pgetc(), SQSYNTAX, here->eofmark, here->striptabs);
+ else
+ readtoken1(pgetc_eatbnl(), DQSYNTAX, here->eofmark, here->striptabs);
n = stzalloc(sizeof(struct narg));
n->narg.type = NARG;
/*n->narg.next = NULL; - stzalloc did it */
{
union node n;
int saveprompt;
+ struct parsefile *file_stop = g_parsefile;
+ volatile int saveint;
+ struct jmploc *volatile savehandler = exception_handler;
+ struct jmploc jmploc;
+ const char *volatile result;
+ int err;
/* XXX Fix (char *) cast. */
setinputstring((char *)ps);
saveprompt = doprompt;
doprompt = 0;
+ result = ps;
+
+ SAVE_INT(saveint);
+ err = setjmp(jmploc.loc);
+ if (err)
+ goto out;
/* 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 = &jmploc;
+ readtoken1(pgetc(), syntax_type, FAKEEOFMARK, 0);
n.narg.type = NARG;
n.narg.next = NULL;
n.narg.text = wordtext;
n.narg.backquote = backquotelist;
+ /* expandarg() might fail too:
+ * PS1='$((123+))'
+ */
expandarg(&n, NULL, EXP_QUOTED);
- return stackblock();
+ result = stackblock();
+
+out:
+ exception_handler = savehandler;
+ if (err && exception_type != EXERROR)
+ longjmp(exception_handler->loc, 1);
+ RESTORE_INT(saveint);
+
+ doprompt = saveprompt;
+ /* Try: PS1='`xxx(`' */
+ unwindfiles(file_stop);
+
+ return result;
}
static inline int
{
if (stoppedjobs())
return 0;
+
if (argv[1])
- exitstatus = number(argv[1]);
+ savestatus = number(argv[1]);
+
raise_exception(EXEXIT);
/* NOTREACHED */
}
static int FAST_FUNC
historycmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
{
- show_history(line_input_state);
+ if (line_input_state)
+ show_history(line_input_state);
return EXIT_SUCCESS;
}
#endif
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;
- char *opt_d = NULL; /* optimized out if !BASH */
- int read_flags = 0;
+ struct builtin_read_params params;
const char *r;
int i;
+ 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':
- opt_d = optionarg;
+ params.opt_d = optionarg;
break;
#endif
default:
}
}
+ 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,
- opt_d
- );
+ r = shell_builtin_read(¶ms);
INT_ON;
if ((uintptr_t)r == 1 && errno == EINTR) {
{
struct jmploc loc;
char *p;
- int status;
#if ENABLE_FEATURE_EDITING_SAVE_ON_EXIT
- save_history(line_input_state);
+ if (line_input_state)
+ save_history(line_input_state);
#endif
- status = exitstatus;
- TRACE(("pid %d, exitshell(%d)\n", getpid(), status));
- if (setjmp(loc.loc)) {
- if (exception_type == EXEXIT)
- status = exitstatus;
+ savestatus = exitstatus;
+ TRACE(("pid %d, exitshell(%d)\n", getpid(), savestatus));
+ if (setjmp(loc.loc))
goto out;
- }
exception_handler = &loc;
p = trap[0];
if (p) {
*/
setjobctl(0);
flush_stdout_stderr();
- _exit(status);
+ _exit(savestatus);
/* NOTREACHED */
}
}
}
+ setvareq((char*)defifsvar, VTEXTFIXED);
setvareq((char*)defoptindvar, VTEXTFIXED);
setvar0("PPID", utoa(getppid()));
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, &login_sh)) {
+ if (options(&login_sh)) {
/* it already printed err message */
raise_exception(EXERROR);
}
ash_msg_and_raise_error(bb_msg_requires_arg, "-c");
sflag = 1;
}
- if (iflag == 2 && sflag == 1 && isatty(0) && isatty(1))
+ if (iflag == 2 /* no explicit -i given */
+ && sflag == 1 /* -s given (or implied) */
+ && !minusc /* bash compat: ash -sc 'echo $-' is not interactive (dash is) */
+ && isatty(0) && isatty(1) /* we are on tty */
+ ) {
iflag = 1;
+ }
if (mflag == 2)
mflag = iflag;
for (i = 0; i < NOPTS; i++)
/* from eval.c: */
evalskip = 0;
loopnest = 0;
+ if (savestatus >= 0) {
+ exitstatus = savestatus;
+ savestatus = -1;
+ }
/* from expand.c: */
ifsfree();
* 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;
monitor(4, etext, profile_buf, sizeof(profile_buf), 50);
#endif
-#if ENABLE_FEATURE_EDITING
- line_input_state = new_line_input_t(FOR_SHELL | WITH_PATH_LOOKUP);
-#endif
state = 0;
if (setjmp(jmploc.loc)) {
smallint e;
init();
setstackmark(&smark);
+
+#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: "));
* Ensure we don't falsely claim that 0 (stdin)
* is one of stacked source fds.
* 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 save_fd_on_redirect()
- evalstring(minusc, sflag ? 0 : EV_EXIT);
+
+ // dash: evalstring(minusc, sflag ? 0 : EV_EXIT);
+ // The above makes
+ // ash -sc 'echo $-'
+ // continue reading input from stdin after running 'echo'.
+ // bash does not do this: it prints "hBcs" and exits.
+ evalstring(minusc, EV_EXIT);
}
if (sflag || minusc == NULL) {