* 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
#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;
volatile /*sig_atomic_t*/ smallint pending_int; /* 1 = got SIGINT */
volatile /*sig_atomic_t*/ smallint got_sigchld; /* 1 = got SIGCHLD */
volatile /*sig_atomic_t*/ smallint pending_sig; /* last pending signal */
- smallint exception_type; /* kind of exception (0..5) */
- /* exceptions */
+ smallint exception_type; /* kind of exception: */
#define EXINT 0 /* SIGINT received */
#define EXERROR 1 /* a generic error */
-#define EXEXIT 4 /* exit the shell */
+#define EXEND 3 /* exit the shell */
+#define EXEXIT 4 /* exit the shell via exitcmd */
char nullstr[1]; /* zero length string */
#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; \
* part of the block that has been used.
*/
static void
-growstackblock(void)
+growstackblock(size_t min)
{
size_t newlen;
newlen = g_stacknleft * 2;
if (newlen < g_stacknleft)
ash_msg_and_raise_error(bb_msg_memory_exhausted);
- if (newlen < 128)
- newlen += 128;
+ min = SHELL_ALIGN(min | 128);
+ if (newlen < min)
+ newlen += min;
if (g_stacknxt == g_stackp->space && g_stackp != &stackbase) {
struct stack_block *sp;
growstackstr(void)
{
size_t len = stackblocksize();
- growstackblock();
+ growstackblock(0);
return (char *)stackblock() + len;
}
+static char *
+growstackto(size_t len)
+{
+ if (stackblocksize() < len)
+ growstackblock(len);
+ return stackblock();
+}
+
/*
* Called from CHECKSTRSPACE.
*/
makestrspace(size_t newlen, char *p)
{
size_t len = p - g_stacknxt;
- size_t size;
-
- for (;;) {
- size_t nleft;
- size = stackblocksize();
- nleft = size - len;
- if (nleft >= newlen)
- break;
- growstackblock();
- }
- return (char *)stackblock() + len;
+ return growstackto(len + newlen) + len;
}
static char *
#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;
setvar(s, NULL, 0);
}
-/*
- * Process a linked list of variable assignments.
- */
-static void
-listsetvar(struct strlist *list_set_var, int flags)
-{
- struct strlist *lp = list_set_var;
-
- if (!lp)
- return;
- INT_OFF;
- do {
- setvareq(lp->text, flags);
- lp = lp->next;
- } while (lp);
- INT_ON;
-}
-
/*
* Generate a list of variables satisfying the given conditions.
*/
}
-/* ============ Path search helper
- *
+/* ============ Path search helper */
+static const char *
+legal_pathopt(const char *opt, const char *term, int magic)
+{
+ switch (magic) {
+ case 0:
+ opt = NULL;
+ break;
+
+ case 1:
+ opt = prefix(opt, "builtin") ?: prefix(opt, "func");
+ break;
+
+ default:
+ opt += strcspn(opt, term);
+ break;
+ }
+
+ if (opt && *opt == '%')
+ opt++;
+
+ return opt;
+}
+
+/*
* The variable path (passed by reference) should be set to the start
- * of the path before the first call; path_advance will update
- * this value as it proceeds. Successive calls to path_advance will return
+ * of the path before the first call; padvance will update
+ * this value as it proceeds. Successive calls to padvance will return
* the possible path expansions in sequence. If an option (indicated by
* a percent sign) appears in the path entry then the global variable
* pathopt will be set to point to it; otherwise pathopt will be set to
* NULL.
+ *
+ * If magic is 0 then pathopt recognition will be disabled. If magic is
+ * 1 we shall recognise %builtin/%func. Otherwise we shall accept any
+ * pathopt.
*/
-static const char *pathopt; /* set by path_advance */
+static const char *pathopt; /* set by padvance */
-static char *
-path_advance(const char **path, const char *name)
+static int
+padvance_magic(const char **path, const char *name, int magic)
{
+ const char *term = "%:";
+ const char *lpathopt;
const char *p;
char *q;
const char *start;
+ size_t qlen;
size_t len;
if (*path == NULL)
- return NULL;
+ return -1;
+
+ lpathopt = NULL;
start = *path;
- for (p = start; *p && *p != ':' && *p != '%'; p++)
- continue;
- len = p - start + strlen(name) + 2; /* "2" is for '/' and '\0' */
- while (stackblocksize() < len)
- growstackblock();
- q = stackblock();
- if (p != start) {
- q = mempcpy(q, start, p - start);
- *q++ = '/';
+
+ if (*start == '%' && (p = legal_pathopt(start + 1, term, magic))) {
+ lpathopt = start + 1;
+ start = p;
+ term = ":";
}
- strcpy(q, name);
- pathopt = NULL;
+
+ len = strcspn(start, term);
+ p = start + len;
+
if (*p == '%') {
- pathopt = ++p;
- while (*p && *p != ':')
- p++;
+ size_t extra = strchrnul(p, ':') - p;
+
+ if (legal_pathopt(p + 1, term, magic))
+ lpathopt = p + 1;
+ else
+ len += extra;
+
+ p += extra;
}
- if (*p == ':')
- *path = p + 1;
- else
- *path = NULL;
- return stalloc(len);
+
+ pathopt = lpathopt;
+ *path = *p == ':' ? p + 1 : NULL;
+
+ /* "2" is for '/' and '\0' */
+ qlen = len + strlen(name) + 2;
+ q = growstackto(qlen);
+
+ if (len) {
+ q = mempcpy(q, start, len);
+ *q++ = '/';
+ }
+ strcpy(q, name);
+
+ return qlen;
+}
+
+static int
+padvance(const char **path, const char *name)
+{
+ return padvance_magic(path, name, 1);
}
char c;
struct stat statb;
int flags;
+ int len;
flags = cdopt();
dest = *argptr;
if (!*dest)
dest = ".";
path = bltinlookup("CDPATH");
- while (path) {
- c = *path;
- p = path_advance(&path, dest);
+ while (p = path, (len = padvance(&path, dest)) >= 0) {
+ c = *p;
+ p = stalloc(len);
+
if (stat(p, &statb) >= 0 && S_ISDIR(statb.st_mode)) {
if (c && c != ':')
flags |= CD_PRINT;
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
static unsigned njobs; //4
/* current job */
static struct job *curjob; //lots
-/* number of presumed living untracked jobs */
-static int jobless; //4
#if 0
/* Bash has a feature: it restores termios after a successful wait for
#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);
- while (!got_sigchld && !pending_sig)
+ sigprocmask2(SIG_SETMASK, &mask); /* mask is updated */
+ while (!got_sigchld && !pending_sig) {
sigsuspend(&mask);
+ /* ^^^ add "sigdelset(&mask, SIGCHLD);" before sigsuspend
+ * to make sure SIGCHLD is not masked off?
+ * It was reported that this:
+ * fn() { : | return; }
+ * shopt -s lastpipe
+ * fn
+ * exec ash SCRIPT
+ * under bash 4.4.23 runs SCRIPT with SIGCHLD masked,
+ * making "wait" commands in SCRIPT block forever.
+ */
+ }
sigprocmask(SIG_SETMASK, &mask, NULL);
#else /* unsafe: a signal can set pending_sig after check, but before pause() */
while (!got_sigchld && !pending_sig)
#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)
+waitone(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;
goto out;
}
/* The process wasn't found in job list */
-#if JOBS
- if (!WIFSTOPPED(status))
- jobless--;
-#endif
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;
return pid;
}
+static int
+dowait(int block, struct job *jp)
+{
+ int pid = block == DOWAIT_NONBLOCK ? got_sigchld : 1;
+
+ while (jp ? jp->state == JOBRUNNING : pid > 0) {
+ if (!jp)
+ got_sigchld = 0;
+ pid = waitone(block, jp);
+ }
+
+ return pid;
+}
+
#if JOBS
static void
showjob(struct job *jp, int mode)
TRACE(("showjobs(0x%x) called\n", mode));
/* Handle all finished jobs */
- while (dowait(DOWAIT_NONBLOCK, NULL) > 0)
- continue;
+ dowait(DOWAIT_NONBLOCK, NULL);
for (jp = curjob; jp; jp = jp->prev_job) {
if (!(mode & SHOW_CHANGED) || jp->changed) {
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);
- /* if child sends us a signal *and immediately exits*,
- * dowait() returns pid > 0. Check this case,
- * not "if (dowait() < 0)"!
- */
+#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
}
}
job = getjob(*argv, 0);
}
/* loop until process terminated or stopped */
- while (job->state == JOBRUNNING) {
- dowait(DOWAIT_BLOCK_OR_SIG, NULL);
- if (pending_sig)
- goto sigout;
- }
+ dowait(DOWAIT_BLOCK_OR_SIG, NULL);
+ if (pending_sig)
+ goto sigout;
job->waited = 1;
retval = getstatus(job);
repeat: ;
str = "${";
goto dostr;
case CTLENDVAR:
- str = "\"}" + !(quoted & 1);
+ str = "\"}";
+ str += !(quoted & 1);
quoted >>= 1;
subtype = 0;
goto dostr;
#endif
for (jp = curjob; jp; jp = jp->prev_job)
freejob(jp);
- jobless = 0;
}
/* Called after fork(), in parent */
forkparent(struct job *jp, union node *n, int mode, pid_t pid)
{
TRACE(("In parent shell: child = %d\n", pid));
- if (!jp) {
- /* jp is NULL when called by openhere() for heredoc support */
- while (jobless && dowait(DOWAIT_NONBLOCK, NULL) > 0)
- continue;
- jobless++;
+ if (!jp) /* jp is NULL when called by openhere() for heredoc support */
return;
- }
#if JOBS
if (mode != FORK_NOJOB && jp->jobctl) {
int pgrp;
{
int st;
- TRACE(("waitforjob(%%%d) called\n", jobno(jp)));
+ TRACE(("waitforjob(%%%d) called\n", jp ? jobno(jp) : 0));
- INT_OFF;
- while (jp->state == JOBRUNNING) {
- /* In non-interactive shells, we _can_ get
- * a keyboard signal here and be EINTRed,
- * but we just loop back, waiting for command to complete.
- *
- * man bash:
- * "If bash is waiting for a command to complete and receives
- * a signal for which a trap has been set, the trap
- * will not be executed until the command completes."
- *
- * Reality is that even if trap is not set, bash
- * will not act on the signal until command completes.
- * Try this. sleep5intoff.c:
- * #include <signal.h>
- * #include <unistd.h>
- * int main() {
- * sigset_t set;
- * sigemptyset(&set);
- * sigaddset(&set, SIGINT);
- * sigaddset(&set, SIGQUIT);
- * sigprocmask(SIG_BLOCK, &set, NULL);
- * sleep(5);
- * return 0;
- * }
- * $ bash -c './sleep5intoff; echo hi'
- * ^C^C^C^C <--- pressing ^C once a second
- * $ _
- * $ bash -c './sleep5intoff; echo hi'
- * ^\^\^\^\hi <--- pressing ^\ (SIGQUIT)
- * $ _
- */
- dowait(DOWAIT_BLOCK, jp);
- }
- INT_ON;
+ /* In non-interactive shells, we _can_ get
+ * a keyboard signal here and be EINTRed, but we just loop
+ * inside dowait(), waiting for command to complete.
+ *
+ * man bash:
+ * "If bash is waiting for a command to complete and receives
+ * a signal for which a trap has been set, the trap
+ * will not be executed until the command completes."
+ *
+ * Reality is that even if trap is not set, bash
+ * will not act on the signal until command completes.
+ * Try this. sleep5intoff.c:
+ * #include <signal.h>
+ * #include <unistd.h>
+ * int main() {
+ * sigset_t set;
+ * sigemptyset(&set);
+ * sigaddset(&set, SIGINT);
+ * sigaddset(&set, SIGQUIT);
+ * sigprocmask(SIG_BLOCK, &set, NULL);
+ * sleep(5);
+ * return 0;
+ * }
+ * $ bash -c './sleep5intoff; echo hi'
+ * ^C^C^C^C <--- pressing ^C once a second
+ * $ _
+ * $ bash -c './sleep5intoff; echo hi'
+ * ^\^\^\^\hi <--- pressing ^\ (SIGQUIT)
+ * $ _
+ */
+ dowait(jp ? DOWAIT_BLOCK : DOWAIT_NONBLOCK, jp);
+ if (!jp)
+ return exitstatus;
st = getstatus(jp);
#if JOBS
#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 */
+#define EXP_KEEPNUL 0x200 /* do not skip NUL characters */
+
/*
* rmescape() flags
*/
/* Add CTLESC when necessary. */
#define QUOTES_ESC (EXP_FULL | EXP_CASE)
-/* Do not skip NUL characters. */
-#define QUOTES_KEEPNUL EXP_TILDE
/*
* Structure specifying which parts of the string should be searched
/* holds expanded arg list */
static struct arglist exparg;
-/*
- * Our own itoa().
- * cvtnum() is used even if math support is off (to prepare $? values and such).
- */
-static int
-cvtnum(arith_t num)
-{
- int len;
-
- /* 32-bit and wider ints require buffer size of bytes*3 (or less) */
- len = sizeof(arith_t) * 3;
- /* If narrower: worst case, 1-byte ints: need 5 bytes: "-127<NUL>" */
- if (sizeof(arith_t) < 4) len += 2;
-
- expdest = makestrspace(len, expdest);
- len = fmtstr(expdest, len, ARITH_FMT, num);
- STADJUST(len, expdest);
- return len;
-}
-
/*
* Break the argument string into pieces based upon IFS and add the
* strings to the argument list. The regions of the string to be
/*
* Put a string on the stack.
*/
-static void
-memtodest(const char *p, size_t len, int syntax, int quotes)
+static size_t
+memtodest(const char *p, size_t len, int flags)
{
+ int syntax = flags & EXP_QUOTED ? DQSYNTAX : BASESYNTAX;
char *q;
+ char *s;
if (!len)
- return;
+ return 0;
- q = makestrspace((quotes & QUOTES_ESC) ? len * 2 : len, expdest);
+ q = makestrspace(len * 2, expdest);
+ s = q;
do {
unsigned char c = *p++;
if (c) {
- if (quotes & QUOTES_ESC) {
+ if (flags & QUOTES_ESC) {
int n = SIT(c, syntax);
if (n == CCTL
- || (((quotes & EXP_FULL) || syntax != BASESYNTAX)
- && n == CBACK
- )
+ || ((flags & EXP_QUOTED) && n == CBACK)
) {
USTPUTC(CTLESC, q);
}
}
- } else if (!(quotes & QUOTES_KEEPNUL))
+ } else if (!(flags & EXP_KEEPNUL))
continue;
USTPUTC(c, q);
} while (--len);
expdest = q;
+ return q - s;
}
static size_t
-strtodest(const char *p, int syntax, int quotes)
+strtodest(const char *p, int flags)
{
size_t len = strlen(p);
- memtodest(p, len, syntax, quotes);
+ memtodest(p, len, flags);
return len;
}
+/*
+ * Our own itoa().
+ * cvtnum() is used even if math support is off (to prepare $? values and such).
+ */
+static int
+cvtnum(arith_t num, int flags)
+{
+ /* 32-bit and wider ints require buffer size of bytes*3 (or less) */
+ /* If narrower: worst case, 1-byte ints: need 5 bytes: "-127<NUL>" */
+ int len = (sizeof(arith_t) >= 4) ? sizeof(arith_t) * 3 : sizeof(arith_t) * 3 + 2;
+ char buf[len];
+
+ len = fmtstr(buf, len, ARITH_FMT, num);
+ return memtodest(buf, len, flags);
+}
+
/*
* Record the fact that we have to scan this region of the
* string for IFS characters.
}
static char *
-exptilde(char *startp, char *p, int flags)
+exptilde(char *startp, char *p, int flag)
{
unsigned char c;
char *name;
struct passwd *pw;
const char *home;
- int quotes = flags & QUOTES_ESC;
name = p + 1;
case CTLQUOTEMARK:
return startp;
case ':':
- if (flags & EXP_VARTILDE)
+ if (flag & EXP_VARTILDE)
goto done;
break;
case '/':
goto lose;
home = pw->pw_dir;
}
- if (!home || !*home)
+ if (!home)
goto lose;
*p = c;
- strtodest(home, SQSYNTAX, quotes);
+ strtodest(home, flag | EXP_QUOTED);
return p;
lose:
*p = c;
char *p;
char *dest;
int startloc;
- int syntax = flag & EXP_QUOTED ? DQSYNTAX : BASESYNTAX;
struct stackmark smark;
INT_OFF;
if (i == 0)
goto read;
for (;;) {
- memtodest(p, i, syntax, flag & QUOTES_ESC);
+ memtodest(p, i, flag);
read:
if (in.fd < 0)
break;
/* Eat all trailing newlines */
dest = expdest;
- for (; dest > (char *)stackblock() && dest[-1] == '\n';)
+ for (; dest > ((char *)stackblock() + startloc) && dest[-1] == '\n';)
STUNPUTC(dest);
expdest = dest;
if (flag & QUOTES_ESC)
rmescapes(p + 1, 0, NULL);
- len = cvtnum(ash_arith(p + 1));
+ len = cvtnum(ash_arith(p + 1), flag);
if (!(flag & EXP_QUOTED))
recordregion(begoff, begoff + len, 0);
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;
- int syntax;
+ flags |= EXP_KEEPNUL;
+ flags &= discard ? ~QUOTES_ESC : ~0;
sep = (flags & EXP_FULL) << CHAR_BIT;
- syntax = quoted ? DQSYNTAX : BASESYNTAX;
switch (*name) {
case '$':
if (num == 0)
return -1;
numvar:
- len = cvtnum(num);
+ len = cvtnum(num, flags);
goto check_1char_name;
case '-':
expdest = makestrspace(NOPTS, expdest);
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;
while ((p = *ap++) != NULL) {
- len += strtodest(p, syntax, quotes);
+ len += strtodest(p, flags);
if (*ap && sep) {
len++;
- memtodest(&sepc, 1, syntax, quotes);
+ memtodest(&sepc, 1, flags);
}
}
break;
if (!p)
return -1;
- len = strtodest(p, syntax, quotes);
+ len = strtodest(p, flags);
#if ENABLE_UNICODE_SUPPORT
if (subtype == VSLENGTH && len > 0) {
reinit_unicode_for_ash();
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--;
varunset(p, var, 0, 0);
if (subtype == VSLENGTH) {
- cvtnum(varlen > 0 ? varlen : 0);
+ cvtnum(varlen > 0 ? varlen : 0, flag);
goto record;
}
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;
}
goto record;
}
+ varlen = 0;
+
end:
if (subtype != VSNORMAL) { /* skip to end of alternative */
int nesting = 1;
/*
* 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;
#define DO_ABS 0x02 /* checks absolute paths */
#define DO_NOFUNC 0x04 /* don't return shell functions, for command */
#define DO_ALTPATH 0x08 /* using alternate path */
-#define DO_ALTBLTIN 0x20 /* %builtin in alt. path */
+#define DO_REGBLTIN 0x10 /* regular built-ins and functions only */
static void find_command(char *, struct cmdentry *, int, const char *);
#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
} else {
try_PATH:
e = ENOENT;
- while ((cmdname = path_advance(&path, prog)) != NULL) {
+ while (padvance(&path, argv[0]) >= 0) {
+ cmdname = stackblock();
if (--idx < 0 && pathopt == NULL) {
tryexec(IF_FEATURE_SH_STANDALONE(-1,) cmdname, argv, envp);
if (errno != ENOENT && errno != ENOTDIR)
e = errno;
}
- stunalloc(cmdname);
}
}
/* 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",
prog, e, suppress_int));
- ash_msg_and_raise(EXEXIT, "%s: %s", prog, errmsg(e, "not found"));
+ ash_msg_and_raise(EXEND, "%s: %s", prog, errmsg(e, "not found"));
/* NOTREACHED */
}
idx = cmdp->param.index;
path = pathval();
do {
- name = path_advance(&path, cmdp->cmdname);
- stunalloc(name);
+ padvance(&path, cmdp->cmdname);
} while (--idx >= 0);
+ name = stackblock();
out1fmt("%s%s\n", name, (cmdp->rehash ? "*" : nullstr));
}
/*
- * Clear out command entries. The argument specifies the first entry in
- * PATH which has changed.
+ * Clear out command entries.
*/
static void
-clearcmdentry(int firstchange)
+clearcmdentry(void)
{
struct tblentry **tblp;
struct tblentry **pp;
for (tblp = cmdtable; tblp < &cmdtable[CMDTABLESIZE]; tblp++) {
pp = tblp;
while ((cmdp = *pp) != NULL) {
- if ((cmdp->cmdtype == CMDNORMAL &&
- cmdp->param.index >= firstchange)
- || (cmdp->cmdtype == CMDBUILTIN &&
- builtinloc >= firstchange)
+ if (cmdp->cmdtype == CMDNORMAL
+ || (cmdp->cmdtype == CMDBUILTIN
+ && !IS_BUILTIN_REGULAR(cmdp->param.cmd)
+ && builtinloc > 0
+ )
) {
*pp = cmdp->next;
free(cmdp);
char *name;
if (nextopt("r") != '\0') {
- clearcmdentry(0);
+ clearcmdentry();
return 0;
}
cmdp = cmdlookup(name, 0);
if (cmdp != NULL
&& (cmdp->cmdtype == CMDNORMAL
- || (cmdp->cmdtype == CMDBUILTIN && builtinloc >= 0))
+ || (cmdp->cmdtype == CMDBUILTIN
+ && !IS_BUILTIN_REGULAR(cmdp->param.cmd)
+ && builtinloc > 0
+ )
+ )
) {
delete_cmd_entry();
}
* Called with interrupts off.
*/
static void FAST_FUNC
-changepath(const char *new)
+changepath(const char *newval)
{
- const char *old;
- int firstchange;
+ const char *new;
int idx;
- int idx_bltin;
+ int bltin;
- old = pathval();
- firstchange = 9999; /* assume no change */
+ new = newval;
idx = 0;
- idx_bltin = -1;
+ bltin = -1;
for (;;) {
- if (*old != *new) {
- firstchange = idx;
- if ((*old == '\0' && *new == ':')
- || (*old == ':' && *new == '\0')
- ) {
- firstchange++;
- }
- old = new; /* ignore subsequent differences */
+ if (*new == '%' && prefix(new + 1, "builtin")) {
+ bltin = idx;
+ break;
}
- if (*new == '\0')
+ new = strchr(new, ':');
+ if (!new)
break;
- if (*new == '%' && idx_bltin < 0 && prefix(new + 1, "builtin"))
- idx_bltin = idx;
- if (*new == ':')
- idx++;
+ idx++;
new++;
- old++;
}
- if (builtinloc < 0 && idx_bltin >= 0)
- builtinloc = idx_bltin; /* zap builtins */
- if (builtinloc >= 0 && idx_bltin < 0)
- firstchange = 0;
- clearcmdentry(firstchange);
- builtinloc = idx_bltin;
+ builtinloc = bltin;
+ clearcmdentry();
}
enum {
TEOF,
p = command;
} else {
do {
- p = path_advance(&path, command);
- stunalloc(p);
+ padvance(&path, command);
} while (--j >= 0);
+ p = stackblock();
}
if (describe_command_verbose) {
out1fmt(" is %s", p);
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);
}
}
#if ENABLE_ASH_CMDCMD
+static struct strlist *
+fill_arglist(struct arglist *arglist, union node **argpp)
+{
+ struct strlist **lastp = arglist->lastp;
+ union node *argp;
+
+ while ((argp = *argpp) != NULL) {
+ expandarg(argp, arglist, EXP_FULL | EXP_TILDE);
+ *argpp = argp->narg.next;
+ if (*lastp)
+ break;
+ }
+
+ return *lastp;
+}
+
/* Is it "command [-p] PROG ARGS" bltin, no other opts? Return ptr to "PROG" if yes */
-static char **
-parse_command_args(char **argv, const char **path)
+static int
+parse_command_args(struct arglist *arglist, union node **argpp, const char **path)
{
+ struct strlist *sp = arglist->list;
char *cp, c;
for (;;) {
- cp = *++argv;
- if (!cp)
- return NULL;
+ sp = sp->next ? sp->next : fill_arglist(arglist, argpp);
+ if (!sp)
+ return 0;
+ cp = sp->text;
if (*cp++ != '-')
break;
c = *cp++;
if (!c)
break;
if (c == '-' && !*cp) {
- if (!*++argv)
- return NULL;
+ if (!sp->next && !fill_arglist(arglist, argpp))
+ return 0;
+ sp = sp->next;
break;
}
do {
break;
default:
/* run 'typecmd' for other options */
- return NULL;
+ return 0;
}
c = *cp++;
} while (c);
}
- return argv;
+
+ arglist->list = sp;
+ return DO_NOFUNC;
}
static int FAST_FUNC
#define SKIPBREAK (1 << 0)
#define SKIPCONT (1 << 1)
#define SKIPFUNC (1 << 2)
+#define SKIPFUNCDEF (1 << 3)
static smallint evalskip; /* set to SKIPxxx if we are skipping commands */
static int skipcount; /* number of levels to skip */
static int loopnest; /* current loop nesting level */
{
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);
+ if (evalskip != SKIPFUNC)
+ 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;
dotrap();
if (checkexit & status)
- raise_exception(EXEXIT);
+ raise_exception(EXEND);
if (flags & EV_EXIT)
- raise_exception(EXEXIT);
+ raise_exception(EXEND);
+ 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;
}
case NFROMFD:
case NTOFD: /* >& */
if (redir->ndup.vname) {
- expandarg(redir->ndup.vname, &fn, EXP_FULL | EXP_TILDE);
+ expandarg(redir->ndup.vname, &fn, EXP_TILDE | EXP_REDIR);
if (fn.list == NULL)
ash_msg_and_raise_error("redir error");
#if BASH_REDIR_OUTPUT
-//FIXME: we used expandarg with different args!
if (!isdigit_str9(fn.list->text)) {
/* >&file, not >&fd */
if (redir->nfile.fd != 1) /* 123>&file - BAD */
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
* Create a new localvar environment.
*/
static struct localvar_list *
-pushlocalvars(void)
+pushlocalvars(int push)
{
struct localvar_list *ll;
+ struct localvar_list *top;
+
+ top = localvar_stack;
+ if (!push)
+ goto out;
INT_OFF;
ll = ckzalloc(sizeof(*ll));
/*ll->lv = NULL; - zalloc did it */
- ll->next = localvar_stack;
+ ll->next = top;
localvar_stack = ll;
INT_ON;
-
- return ll->next;
+ out:
+ return top;
}
static void
shellparam.optind = 1;
shellparam.optoff = -1;
#endif
- pushlocalvars();
evaltree(func->n.ndefun.body, flags & EV_TESTED);
- poplocalvars(0);
funcdone:
INT_OFF;
funcline = savefuncline;
shellparam = saveparam;
exception_handler = savehandler;
INT_ON;
- evalskip &= ~SKIPFUNC;
+ evalskip &= ~(SKIPFUNC | SKIPFUNCDEF);
return e;
}
* (options will be restored on return from the function).
*/
static void
-mklocal(char *name)
+mklocal(char *name, int flags)
{
struct localvar *lvp;
struct var **vpp;
if (vp == NULL) {
/* variable did not exist yet */
if (eq)
- vp = setvareq(name, VSTRFIXED);
+ vp = setvareq(name, VSTRFIXED | flags);
else
- vp = setvar(name, NULL, VSTRFIXED);
+ vp = setvar(name, NULL, VSTRFIXED | flags);
lvp->flags = VUNSET;
} else {
lvp->text = vp->var_text;
*/
vp->flags |= VSTRFIXED|VTEXTFIXED;
if (eq)
- setvareq(name, 0);
+ setvareq(name, flags);
else
/* "local VAR" unsets VAR: */
setvar0(name, NULL);
argv = argptr;
while ((name = *argv++) != NULL) {
- mklocal(name);
+ mklocal(name, 0);
}
return 0;
}
static int FAST_FUNC
returncmd(int argc UNUSED_PARAM, char **argv)
{
+ int skip;
+ int status;
+
/*
* If called outside a function, do what ksh does;
* skip the rest of the file.
*/
- evalskip = SKIPFUNC;
- return argv[1] ? number(argv[1]) : exitstatus;
-}
+ if (argv[1]) {
+ skip = SKIPFUNC;
+ status = number(argv[1]);
+ } else {
+ skip = SKIPFUNCDEF;
+ status = exitstatus;
+ }
+ evalskip = skip;
+
+ return status;
+}
/* Forward declarations for builtintab[] */
static int breakcmd(int, char **) FAST_FUNC;
#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)
{
evalcommand(union node *cmd, int flags)
{
static const struct builtincmd null_bltin = {
- "\0\0", bltincmd /* why three NULs? */
+ BUILTIN_REGULAR "", bltincmd
};
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;
char **argv;
int argc;
+ struct strlist *osp;
const struct strlist *sp;
struct cmdentry cmdentry;
struct job *jp;
char *lastarg;
const char *path;
int spclbltin;
+ int cmd_flag;
int status;
char **nargv;
smallint cmd_is_exec;
+ int vflags;
+ int vlocal;
errlinno = lineno = cmd->ncmd.linno;
if (funcline)
/* 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;
arglist.lastp = &arglist.list;
*arglist.lastp = NULL;
+ cmd_flag = 0;
+ cmd_is_exec = 0;
+ spclbltin = -1;
+ vflags = 0;
+ vlocal = 0;
+ path = NULL;
+
argc = 0;
- if (cmd->ncmd.args) {
- struct builtincmd *bcmd;
- smallint pseudovarflag;
+ argp = cmd->ncmd.args;
+ osp = fill_arglist(&arglist, &argp);
+ if (osp) {
+ int pseudovarflag = 0;
- bcmd = find_builtin(cmd->ncmd.args->narg.text);
- pseudovarflag = bcmd && IS_BUILTIN_ASSIGN(bcmd);
+ for (;;) {
+ find_command(arglist.list->text, &cmdentry,
+ cmd_flag | DO_REGBLTIN, pathval());
- for (argp = cmd->ncmd.args; argp; argp = argp->narg.next) {
- struct strlist **spp;
+ vlocal++;
- spp = arglist.lastp;
- if (pseudovarflag && isassignment(argp->narg.text))
- expandarg(argp, &arglist, EXP_VARTILDE);
- else
- expandarg(argp, &arglist, EXP_FULL | EXP_TILDE);
+ /* implement bltin and command here */
+ if (cmdentry.cmdtype != CMDBUILTIN)
+ break;
+
+ pseudovarflag = IS_BUILTIN_ASSIGN(cmdentry.u.cmd);
+ if (spclbltin < 0) {
+ spclbltin = IS_BUILTIN_SPECIAL(cmdentry.u.cmd);
+ vlocal = !spclbltin;
+ }
+ cmd_is_exec = cmdentry.u.cmd == EXECCMD;
+ if (cmdentry.u.cmd != COMMANDCMD)
+ break;
- for (sp = *spp; sp; sp = sp->next)
- argc++;
+ cmd_flag = parse_command_args(&arglist, &argp, &path);
+ if (!cmd_flag)
+ break;
}
+
+ for (; argp; argp = argp->narg.next)
+ expandarg(argp, &arglist,
+ pseudovarflag &&
+ isassignment(argp->narg.text) ?
+ EXP_VARTILDE : EXP_FULL | EXP_TILDE);
+
+ for (sp = arglist.list; sp; sp = sp->next)
+ argc++;
+
+ if (cmd_is_exec && argc > 1)
+ vflags = VEXPORT;
}
+ localvar_stop = pushlocalvars(vlocal);
+
/* Reserve one extra spot at the front for shellexec. */
nargv = stalloc(sizeof(char *) * (argc + 2));
argv = ++nargv;
}
status = redirectsafe(cmd->ncmd.redirect, REDIR_PUSH | REDIR_SAVEFD2);
- path = vpath.var_text;
+ if (status) {
+ bail:
+ exitstatus = status;
+
+ /* We have a redirection error. */
+ if (spclbltin > 0)
+ raise_exception(EXERROR);
+
+ goto out;
+ }
+
for (argp = cmd->ncmd.assign; argp; argp = argp->narg.next) {
struct strlist **spp;
- char *p;
spp = varlist.lastp;
expandarg(argp, &varlist, EXP_VARTILDE);
- mklocal((*spp)->text);
-
- /*
- * Modify the command lookup path, if a PATH= assignment
- * is present
- */
- p = (*spp)->text;
- if (varcmp(p, path) == 0)
- path = p;
+ if (vlocal)
+ mklocal((*spp)->text, VEXPORT);
+ else
+ setvareq((*spp)->text, vflags);
}
/* Print the command if xflag is set. */
safe_write(preverrout_fd, "\n", 1);
}
- cmd_is_exec = 0;
- spclbltin = -1;
-
/* Now locate the command. */
- if (argc) {
- int cmd_flag = DO_ERR;
-#if ENABLE_ASH_CMDCMD
- const char *oldpath = path + 5;
-#endif
- path += 5;
- for (;;) {
- find_command(argv[0], &cmdentry, cmd_flag, path);
- if (cmdentry.cmdtype == CMDUNKNOWN) {
- flush_stdout_stderr();
- status = 127;
- goto bail;
- }
-
- /* implement bltin and command here */
- if (cmdentry.cmdtype != CMDBUILTIN)
- break;
- if (spclbltin < 0)
- spclbltin = IS_BUILTIN_SPECIAL(cmdentry.u.cmd);
- if (cmdentry.u.cmd == EXECCMD)
- cmd_is_exec = 1;
-#if ENABLE_ASH_CMDCMD
- if (cmdentry.u.cmd == COMMANDCMD) {
- path = oldpath;
- nargv = parse_command_args(argv, &path);
- if (!nargv)
- break;
- /* It's "command [-p] PROG ARGS" (that is, no -Vv).
- * nargv => "PROG". path is updated if -p.
- */
- argc -= nargv - argv;
- argv = nargv;
- cmd_flag |= DO_NOFUNC;
- } else
-#endif
- break;
- }
+ if (cmdentry.cmdtype != CMDBUILTIN
+ || !(IS_BUILTIN_REGULAR(cmdentry.u.cmd))
+ ) {
+ path = path ? path : pathval();
+ find_command(argv[0], &cmdentry, cmd_flag | DO_ERR, path);
}
- if (status) {
- bail:
- exitstatus = status;
-
- /* We have a redirection error. */
- if (spclbltin > 0)
- raise_exception(EXERROR);
-
- goto out;
- }
+ jp = NULL;
/* Execute the command. */
switch (cmdentry.cmdtype) {
+ case CMDUNKNOWN:
+ status = 127;
+ flush_stdout_stderr();
+ goto bail;
+
default: {
#if ENABLE_FEATURE_SH_STANDALONE \
* 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);
+ exitstatus = run_nofork_applet(applet_no, argv);
environ = sv_environ;
/*
* Try enabling NOFORK for "yes" applet.
jp = makejob(/*cmd,*/ 1);
if (forkshell(jp, cmd, FORK_FG) != 0) {
/* parent */
- status = waitforjob(jp);
- INT_ON;
TRACE(("forked child exited with %d\n", status));
break;
}
FORCE_INT_ON;
/* fall through to exec'ing external program */
}
- listsetvar(varlist.list, VEXPORT|VSTACK);
shellexec(argv[0], argv, path, cmdentry.u.index);
/* NOTREACHED */
} /* default */
case CMDBUILTIN:
- if (spclbltin > 0 || argc == 0) {
- poplocalvars(1);
- if (cmd_is_exec && argc > 1)
- listsetvar(varlist.list, VEXPORT);
- }
-
- /* Tight loop with builtins only:
- * "while kill -0 $child; do true; done"
- * will never exit even if $child died, unless we do this
- * to reap the zombie and make kill detect that it's gone: */
- dowait(DOWAIT_NONBLOCK, NULL);
-
- if (evalbltin(cmdentry.u.cmd, argc, argv, flags)) {
- if (exception_type == EXERROR && spclbltin <= 0) {
- FORCE_INT_ON;
- goto readstatus;
- }
+ if (evalbltin(cmdentry.u.cmd, argc, argv, flags)
+ && !(exception_type == EXERROR && spclbltin <= 0)
+ ) {
raise:
longjmp(exception_handler->loc, 1);
}
- goto readstatus;
+ break;
case CMDFUNCTION:
- poplocalvars(1);
- /* See above for the rationale */
- dowait(DOWAIT_NONBLOCK, NULL);
if (evalfun(cmdentry.u.func, argc, argv, flags))
goto raise;
- readstatus:
- status = exitstatus;
break;
} /* switch */
+ status = waitforjob(jp);
+ FORCE_INT_ON;
+
out:
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);
}
/*
mpath = mpathset() ? mpathval() : mailval();
new_hash = 0;
for (;;) {
- p = path_advance(&mpath, nullstr);
- if (p == NULL)
+ int len;
+
+ len = padvance_magic(&mpath, nullstr, 2);
+ if (!len)
+ break;
+ p = stackblock();
break;
if (*p == '\0')
continue;
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)
n1 = NULL;
for (;;) {
- switch (peektoken()) {
+ switch (readtoken()) {
case TNL:
if (!(nlflag & 1))
break;
if (!n1 && (nlflag & 1))
n1 = NODE_EOF;
parseheredoc();
+ tokpushback++;
+ lasttoken = TEOF;
return n1;
}
+ tokpushback++;
checkkwd = CHKNL | CHKKWD | CHKALIAS;
if (nlflag == 2 && ((1 << peektoken()) & tokendlist))
return n1;
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);
}
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();
}
do {
STPUTC(c, out);
c = pgetc_eatbnl();
- } while (isdigit(c));
- } else {
+ } while (!subtype && isdigit(c));
+ } else if (c != '}') {
/* $[{[#]]<specialchar>[}] */
int cc = c;
}
USTPUTC(cc, out);
- }
+ } else
+ goto badsub;
if (c != '}' && subtype == VSLENGTH) {
/* ${#VAR didn't end with } */
union node *n;
char *str;
size_t savelen;
+ struct heredoc *saveheredoclist;
smallint saveprompt = 0;
str = NULL;
*nlpp = stzalloc(sizeof(**nlpp));
/* (*nlpp)->next = NULL; - stzalloc did it */
+ saveheredoclist = heredoclist;
+ heredoclist = NULL;
+
if (oldstyle) {
saveprompt = doprompt;
doprompt = 0;
if (oldstyle)
doprompt = saveprompt;
- else if (readtoken() != TRP)
- raise_error_unexpected_syntax(TRP);
+ else {
+ if (readtoken() != TRP)
+ raise_error_unexpected_syntax(TRP);
+ setinputstring(nullstr);
+ }
+
+ parseheredoc();
+ heredoclist = saveheredoclist;
(*nlpp)->n = n;
- if (oldstyle) {
- /*
- * Start reading from old file again, ignoring any pushed back
- * tokens left from the backquote parsing
- */
- popfile();
+ /* Start reading from old file again. */
+ popfile();
+ /* Ignore any pushed back tokens left from the backquote parsing. */
+ if (oldstyle)
tokpushback = 0;
- }
- while (stackblocksize() <= savelen)
- growstackblock();
- STARTSTACKSTR(out);
+ out = growstackto(savelen + 1);
if (str) {
memcpy(out, str, savelen);
STADJUST(savelen, out);
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 (!top || numeof >= 50)
break;
if (!stoppedjobs()) {
- if (!Iflag)
+ if (!Iflag) {
+ if (iflag) {
+ newline_and_flush(stderr);
+ }
break;
+ }
out2str("\nUse \"exit\" to leave shell.\n");
}
numeof++;
skip = evalskip;
if (skip) {
- evalskip &= ~SKIPFUNC;
+ evalskip &= ~(SKIPFUNC | SKIPFUNCDEF);
break;
}
}
* search for the file, which is necessary to find sub-commands.
*/
static char *
-find_dot_file(char *name)
+find_dot_file(char *basename)
{
char *fullname;
const char *path = pathval();
struct stat statb;
+ int len;
/* don't try this for absolute or relative paths */
- if (strchr(name, '/'))
- return name;
+ if (strchr(basename, '/'))
+ return basename;
- while ((fullname = path_advance(&path, name)) != NULL) {
- if ((stat(fullname, &statb) == 0) && S_ISREG(statb.st_mode)) {
- /*
- * Don't bother freeing here, since it will
- * be freed by the caller.
- */
- return fullname;
+ while ((len = padvance(&path, basename)) >= 0) {
+ fullname = stackblock();
+ if ((!pathopt || *pathopt == 'f')
+ && !stat(fullname, &statb) && S_ISREG(statb.st_mode)
+ ) {
+ /* This will be freed by the caller. */
+ return stalloc(len);
}
- if (fullname != name)
- stunalloc(fullname);
}
/* not found in PATH */
#if ENABLE_ASH_BASH_SOURCE_CURDIR
- return name;
+ return basename;
#else
- ash_msg_and_raise_error("%s: not found", name);
+ ash_msg_and_raise_error("%s: not found", basename);
/* NOTREACHED */
#endif
}
{
if (stoppedjobs())
return 0;
+
if (argv[1])
- exitstatus = number(argv[1]);
+ savestatus = number(argv[1]);
+
raise_exception(EXEXIT);
/* NOTREACHED */
}
int e;
int updatetbl;
struct builtincmd *bcmd;
+ int len;
/* If name contains a slash, don't use PATH or hash table */
if (strchr(name, '/') != NULL) {
/* #if ENABLE_FEATURE_SH_STANDALONE... moved after builtin check */
updatetbl = (path == pathval());
- if (!updatetbl) {
+ if (!updatetbl)
act |= DO_ALTPATH;
- if (strstr(path, "%builtin") != NULL)
- act |= DO_ALTBLTIN;
- }
/* If name is in the table, check answer will be ok */
cmdp = cmdlookup(name, 0);
abort();
#endif
case CMDNORMAL:
- bit = DO_ALTPATH;
+ bit = DO_ALTPATH | DO_REGBLTIN;
break;
case CMDFUNCTION:
bit = DO_NOFUNC;
break;
case CMDBUILTIN:
- bit = DO_ALTBLTIN;
+ bit = IS_BUILTIN_REGULAR(cmdp->param.cmd) ? 0 : DO_REGBLTIN;
break;
}
if (act & bit) {
+ if (act & bit & DO_REGBLTIN)
+ goto fail;
+
updatetbl = 0;
cmdp = NULL;
} else if (cmdp->rehash == 0)
if (bcmd) {
if (IS_BUILTIN_REGULAR(bcmd))
goto builtin_success;
- if (act & DO_ALTPATH) {
- if (!(act & DO_ALTBLTIN))
- goto builtin_success;
- } else if (builtinloc <= 0) {
+ if (act & DO_ALTPATH)
+ goto builtin_success;
+ if (builtinloc <= 0)
goto builtin_success;
- }
}
+ if (act & DO_REGBLTIN)
+ goto fail;
+
#if ENABLE_FEATURE_SH_STANDALONE
{
int applet_no = find_applet_by_name(name);
e = ENOENT;
idx = -1;
loop:
- while ((fullname = path_advance(&path, name)) != NULL) {
- stunalloc(fullname);
- /* NB: code below will still use fullname
- * despite it being "unallocated" */
+ while ((len = padvance(&path, name)) >= 0) {
+ const char *lpathopt = pathopt;
+
+ fullname = stackblock();
idx++;
- if (pathopt) {
- if (prefix(pathopt, "builtin")) {
+ if (lpathopt) {
+ if (*lpathopt == 'b') {
if (bcmd)
goto builtin_success;
continue;
- }
- if ((act & DO_NOFUNC)
- || !prefix(pathopt, "func")
- ) { /* ignore unimplemented options */
+ } else if (!(act & DO_NOFUNC)) {
+ /* handled below */
+ } else {
+ /* ignore unimplemented options */
continue;
}
}
e = EACCES; /* if we fail, this will be the error */
if (!S_ISREG(statb.st_mode))
continue;
- if (pathopt) { /* this is a %func directory */
- stalloc(strlen(fullname) + 1);
+ if (lpathopt) { /* this is a %func directory */
+ stalloc(len);
/* NB: stalloc will return space pointed by fullname
* (because we don't have any intervening allocations
* between stunalloc above and this stalloc) */
#endif
ash_msg("%s: %s", name, errmsg(e, "not found"));
}
+ fail:
entry->cmdtype = CMDUNKNOWN;
return;
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) {
/* ============ main() and helpers */
+/*
+ * This routine is called when an error or an interrupt occurs in an
+ * interactive shell and control is returned to the main command loop
+ * but prior to exitshell.
+ */
+static void
+exitreset(void)
+{
+ /* from eval.c: */
+ if (savestatus >= 0) {
+ if (exception_type == EXEXIT || evalskip == SKIPFUNCDEF)
+ exitstatus = savestatus;
+ savestatus = -1;
+ }
+ evalskip = 0;
+ loopnest = 0;
+
+ /* from expand.c: */
+ ifsfree();
+
+ /* from redir.c: */
+ unwindredir(NULL);
+}
+
+/*
+ * This routine is called when an error or an interrupt occurs in an
+ * interactive shell and control is returned to the main command loop.
+ * (In dash, this function is auto-generated by build machinery).
+ */
+static void
+reset(void)
+{
+ /* from input.c: */
+ g_parsefile->left_in_buffer = 0;
+ g_parsefile->left_in_line = 0; /* clear input buffer */
+ popallfiles();
+
+ /* from var.c: */
+ unwindlocalvars(NULL);
+}
+
/*
* Called to exit the shell.
*/
{
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) {
trap[0] = NULL;
evalskip = 0;
evalstring(p, 0);
+ evalskip = SKIPFUNCDEF;
/*free(p); - we'll exit soon */
}
out:
+ exitreset();
/* dash wraps setjobctl(0) in "if (setjmp(loc.loc) == 0) {...}".
* our setjobctl(0) does not panic if tcsetpgrp fails inside it.
*/
setjobctl(0);
flush_stdout_stderr();
- _exit(status);
+ _exit(exitstatus);
/* NOTREACHED */
}
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.
- * Try: "trap '' HUP; bash; echo RET" and type "kill -HUP $$"
- */
- signal(SIGHUP, SIG_DFL);
-
{
char **envp;
const char *p;
}
}
+ 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++)
popfile();
}
-/*
- * This routine is called when an error or an interrupt occurs in an
- * interactive shell and control is returned to the main command loop.
- * (In dash, this function is auto-generated by build machinery).
- */
-static void
-reset(void)
-{
- /* from eval.c: */
- evalskip = 0;
- loopnest = 0;
-
- /* from expand.c: */
- ifsfree();
-
- /* from input.c: */
- g_parsefile->left_in_buffer = 0;
- g_parsefile->left_in_line = 0; /* clear input buffer */
- popallfiles();
-
- /* from redir.c: */
- unwindredir(NULL);
-
- /* from var.c: */
- unwindlocalvars(NULL);
-}
-
#if PROFILE
static short profile_buf[16384];
extern int etext();
* 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;
smallint s;
- reset();
+ exitreset();
e = exception_type;
s = state;
- if (e == EXEXIT || s == 0 || iflag == 0 || shlvl) {
+ if (e == EXEND || e == EXEXIT || s == 0 || iflag == 0 || shlvl) {
exitshell();
}
+
+ reset();
+
if (e == EXINT) {
newline_and_flush(stderr);
}
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) {
}
#endif
state4: /* XXX ??? - why isn't this before the "if" statement */
+
+ /* Interactive bash re-enables SIGHUP which is SIG_IGNed on entry.
+ * Try:
+ * trap '' hup; bash; echo RET # type "kill -hup $$", see SIGHUP having effect
+ * trap '' hup; bash -c 'kill -hup $$; echo ALIVE' # here SIGHUP is SIG_IGNed
+ */
+ signal(SIGHUP, SIG_DFL);
+
cmdloop(1);
}
#if PROFILE