#define DEBUG_TIME 0
#define DEBUG_PID 1
#define DEBUG_SIG 1
+#define DEBUG_INTONOFF 0
#define PROFILE 0
volatile int suppress_int; /* counter */
volatile /*sig_atomic_t*/ smallint pending_int; /* 1 = got SIGINT */
- /* last pending signal */
- volatile /*sig_atomic_t*/ smallint pending_sig;
+ volatile /*sig_atomic_t*/ smallint got_sigchld; /* 1 = got SIGCHLD */
+ volatile /*sig_atomic_t*/ smallint pending_sig; /* last pending signal */
smallint exception_type; /* kind of exception (0..5) */
/* exceptions */
#define EXINT 0 /* SIGINT received */
#define EXERROR 1 /* a generic error */
-#define EXSHELLPROC 2 /* execute a shell procedure */
-#define EXEXEC 3 /* command execution failed */
#define EXEXIT 4 /* exit the shell */
-#define EXSIG 5 /* trapped signal in wait(1) */
smallint isloginsh;
char nullstr[1]; /* zero length string */
#define exception_type (G_misc.exception_type )
#define suppress_int (G_misc.suppress_int )
#define pending_int (G_misc.pending_int )
+#define got_sigchld (G_misc.got_sigchld )
#define pending_sig (G_misc.pending_sig )
#define isloginsh (G_misc.isloginsh )
#define nullstr (G_misc.nullstr )
* much more efficient and portable. (But hacking the kernel is so much
* more fun than worrying about efficiency and portability. :-))
*/
-#define INT_OFF do { \
+#if DEBUG_INTONOFF
+# define INT_OFF do { \
+ TRACE(("%s:%d INT_OFF(%d)\n", __func__, __LINE__, suppress_int)); \
suppress_int++; \
barrier(); \
} while (0)
+#else
+# define INT_OFF do { \
+ suppress_int++; \
+ barrier(); \
+} while (0)
+#endif
/*
* Called to raise an exception. Since C doesn't include exceptions, we
#endif
/*
- * Called from trap.c when a SIGINT is received. (If the user specifies
+ * Called when a SIGINT is received. (If the user specifies
* that SIGINT is to be trapped or ignored using the trap builtin, then
* this routine is not called.) Suppressint is nonzero when interrupts
* are held using the INT_OFF macro. (The test for iflag is just
static void
raise_interrupt(void)
{
- int ex_type;
-
pending_int = 0;
/* Signal is not automatically unmasked after it is raised,
* do it ourself - unmask all signals */
sigprocmask_allsigs(SIG_UNBLOCK);
/* pending_sig = 0; - now done in signal_handler() */
- ex_type = EXSIG;
- if (gotsig[SIGINT - 1] && !trap[SIGINT]) {
- if (!(rootshell && iflag)) {
- /* Kill ourself with SIGINT */
- signal(SIGINT, SIG_DFL);
- raise(SIGINT);
- }
- ex_type = EXINT;
+ if (!(rootshell && iflag)) {
+ /* Kill ourself with SIGINT */
+ signal(SIGINT, SIG_DFL);
+ raise(SIGINT);
}
/* bash: ^C even on empty command line sets $? */
exitstatus = SIGINT + 128;
- raise_exception(ex_type);
+ raise_exception(EXINT);
/* NOTREACHED */
}
#if DEBUG
raise_interrupt();
}
}
-#define INT_ON int_on()
+#if DEBUG_INTONOFF
+# define INT_ON do { \
+ TRACE(("%s:%d INT_ON(%d)\n", __func__, __LINE__, suppress_int-1)); \
+ int_on(); \
+} while (0)
+#else
+# define INT_ON int_on()
+#endif
static IF_ASH_OPTIMIZE_FOR_SIZE(inline) void
force_int_on(void)
{
{
if (debug != 1)
return;
- if (DEBUG_TIME)
- fprintf(tracefile, "%u ", (int) time(NULL));
- if (DEBUG_PID)
- fprintf(tracefile, "[%u] ", (int) getpid());
- if (DEBUG_SIG)
- fprintf(tracefile, "pending s:%d i:%d(supp:%d) ", pending_sig, pending_int, suppress_int);
vfprintf(tracefile, fmt, va);
+ fprintf(tracefile, "\n");
}
static void
{
#if DEBUG
if (msg) {
- TRACE(("ash_vmsg_and_raise(%d, \"", cond));
+ TRACE(("ash_vmsg_and_raise(%d):", cond));
TRACEV((msg, ap));
- TRACE(("\") pid=%d\n", getpid()));
} else
- TRACE(("ash_vmsg_and_raise(%d, NULL) pid=%d\n", cond, getpid()));
+ TRACE(("ash_vmsg_and_raise(%d):NULL\n", cond));
if (msg)
#endif
ash_vmsg(msg, ap);
char *g_stacknxt; // = stackbase.space;
char *sstrend; // = stackbase.space + MINSIZE;
size_t g_stacknleft; // = MINSIZE;
- int herefd; // = -1;
struct stack_block stackbase;
};
extern struct globals_memstack *const ash_ptr_to_globals_memstack;
#define g_stacknxt (G_memstack.g_stacknxt )
#define sstrend (G_memstack.sstrend )
#define g_stacknleft (G_memstack.g_stacknleft)
-#define herefd (G_memstack.herefd )
#define stackbase (G_memstack.stackbase )
#define INIT_G_memstack() do { \
(*(struct globals_memstack**)&ash_ptr_to_globals_memstack) = xzalloc(sizeof(G_memstack)); \
g_stacknxt = stackbase.space; \
g_stacknleft = MINSIZE; \
sstrend = stackbase.space + MINSIZE; \
- herefd = -1; \
} while (0)
return memcpy(stalloc(len), p, len);
}
-static void
+static inline void
grabstackblock(size_t len)
{
- len = SHELL_ALIGN(len);
- g_stacknxt += len;
- g_stacknleft -= len;
+ stalloc(len);
}
static void
growstackstr(void)
{
size_t len = stackblocksize();
- if (herefd >= 0 && len >= 1024) {
- full_write(herefd, stackblock(), len);
- return stackblock();
- }
growstackblock();
return (char *)stackblock() + len;
}
struct globals_var {
struct shparam shellparam; /* $@ current positional parameters */
struct redirtab *redirlist;
- int g_nullredirs;
int preverrout_fd; /* save fd2 before print debug if xflag is set. */
struct var *vartab[VTABSIZE];
struct var varinit[ARRAY_SIZE(varinit_data)];
#define G_var (*ash_ptr_to_globals_var)
#define shellparam (G_var.shellparam )
//#define redirlist (G_var.redirlist )
-#define g_nullredirs (G_var.g_nullredirs )
#define preverrout_fd (G_var.preverrout_fd)
#define vartab (G_var.vartab )
#define varinit (G_var.varinit )
static void FAST_FUNC
getoptsreset(const char *value)
{
- shellparam.optind = number(value);
+ shellparam.optind = number(value) ?: 1;
shellparam.optoff = -1;
}
#endif
if (flags & VNOSAVE)
free(s);
n = vp->var_text;
+ exitstatus = 1;
ash_msg_and_raise_error("%.*s: is read only", strchrnul(n, '=') - n, n);
}
setvar(name, val, 0);
}
-#if ENABLE_ASH_GETOPTS
-/*
- * Safe version of setvar, returns 1 on success 0 on failure.
- */
-static int
-setvarsafe(const char *name, const char *val, int flags)
-{
- int err;
- volatile int saveint;
- struct jmploc *volatile savehandler = exception_handler;
- struct jmploc jmploc;
-
- SAVE_INT(saveint);
- if (setjmp(jmploc.loc))
- err = 1;
- else {
- exception_handler = &jmploc;
- setvar(name, val, flags);
- err = 0;
- }
- exception_handler = savehandler;
- RESTORE_INT(saveint);
- return err;
-}
-#endif
-
/*
* Unset the specified variable.
*/
static void hashcd(void);
/*
- * Actually do the chdir. We also call hashcd to let the routines in exec.c
+ * Actually do the chdir. We also call hashcd to let other routines
* know that the current directory has changed.
*/
static int
if (!dest)
dest = nullstr;
if (*dest == '/')
- goto step7;
+ goto step6;
if (*dest == '.') {
c = dest[1];
dotdot:
if (!*dest)
dest = ".";
path = bltinlookup("CDPATH");
- if (!path) {
- step6:
- step7:
- p = dest;
- goto docd;
- }
- do {
+ while (path) {
c = *path;
p = path_advance(&path, dest);
if (stat(p, &statb) >= 0 && S_ISDIR(statb.st_mode)) {
docd:
if (!docd(p, flags))
goto out;
- break;
+ goto err;
}
- } while (path);
+ }
+
+ step6:
+ p = dest;
+ goto docd;
+
+ err:
ash_msg_and_raise_error("can't cd to %s", dest);
/* NOTREACHED */
out:
#endif /* ASH_ALIAS */
-/* ============ jobs.c */
-
/* Mode argument to forkshell. Don't change FORK_FG or FORK_BG. */
#define FORK_FG 0
#define FORK_BG 1
static void
signal_handler(int signo)
{
+ if (signo == SIGCHLD) {
+ got_sigchld = 1;
+ if (!trap[SIGCHLD])
+ return;
+ }
+
gotsig[signo - 1] = 1;
+ pending_sig = signo;
if (signo == SIGINT && !trap[SIGINT]) {
if (!suppress_int) {
raise_interrupt(); /* does not return */
}
pending_int = 1;
- } else {
- pending_sig = signo;
}
}
//whereas we have to restore it to what shell got on entry
//from the parent. See comment above
+ if (signo == SIGCHLD)
+ new_act = S_CATCH;
+
t = &sigmode[signo - 1];
cur_act = *t;
if (cur_act == 0) {
#define CUR_RUNNING 1
#define CUR_STOPPED 0
-/* mode flags for dowait */
-#define DOWAIT_NONBLOCK WNOHANG
-#define DOWAIT_BLOCK 0
-
#if JOBS
/* pgrp of shell on invocation */
static int initialpgrp; //references:2
if (--fd < 0)
goto out;
}
+ /* fd is a tty at this point */
fd = fcntl(fd, F_DUPFD, 10);
- if (ofd >= 0)
+ if (ofd >= 0) /* if it is "/dev/tty", close. If 0/1/2, dont */
close(ofd);
if (fd < 0)
- goto out;
- /* fd is a tty at this point */
+ goto out; /* F_DUPFD failed */
close_on_exec_on(fd);
while (1) { /* while we are in the background */
pgrp = tcgetpgrp(fd);
}
static int
-dowait(int wait_flags, struct job *job)
+wait_block_or_sig(int *status)
+{
+ int pid;
+
+ do {
+ sigset_t mask;
+
+ /* Poll all children for changes in their state */
+ got_sigchld = 0;
+ /* if job control is active, accept stopped processes too */
+ pid = waitpid(-1, status, doing_jobctl ? (WNOHANG|WUNTRACED) : WNOHANG);
+ if (pid != 0)
+ break; /* Error (e.g. EINTR, ECHILD) or pid */
+
+ /* Children exist, but none are ready. Sleep until interesting signal */
+#if 1
+ sigfillset(&mask);
+ sigprocmask(SIG_SETMASK, &mask, &mask);
+ while (!got_sigchld && !pending_sig)
+ sigsuspend(&mask);
+ sigprocmask(SIG_SETMASK, &mask, NULL);
+#else /* unsafe: a signal can set pending_sig after check, but before pause() */
+ while (!got_sigchld && !pending_sig)
+ pause();
+#endif
+
+ /* If it was SIGCHLD, poll children again */
+ } while (got_sigchld);
+
+ return pid;
+}
+
+#define DOWAIT_NONBLOCK 0
+#define DOWAIT_BLOCK 1
+#define DOWAIT_BLOCK_OR_SIG 2
+
+static int
+dowait(int block, struct job *job)
{
int pid;
int status;
struct job *jp;
- struct job *thisjob;
+ struct job *thisjob = NULL;
- TRACE(("dowait(0x%x) called\n", wait_flags));
+ TRACE(("dowait(0x%x) called\n", block));
- /* Do a wait system call. If job control is compiled in, we accept
- * stopped processes. wait_flags may have WNOHANG, preventing blocking.
- * NB: _not_ safe_waitpid, we need to detect EINTR */
- if (doing_jobctl)
- wait_flags |= WUNTRACED;
- pid = waitpid(-1, &status, wait_flags);
+ /* It's wrong to call waitpid() outside of INT_OFF region:
+ * signal can arrive just after syscall return and handler can
+ * longjmp away, losing stop/exit notification processing.
+ * Thus, for "jobs" builtin, and for waiting for a fg job,
+ * we call waitpid() (blocking or non-blocking) inside INT_OFF.
+ *
+ * However, for "wait" builtin it is wrong to simply call waitpid()
+ * in INT_OFF region: "wait" needs to wait for any running job
+ * to change state, but should exit on any trap too.
+ * In INT_OFF region, a signal just before syscall entry can set
+ * pending_sig variables, but we can't check them, and we would
+ * either enter a sleeping waitpid() (BUG), or need to busy-loop.
+ *
+ * Because of this, we run inside INT_OFF, but use a special routine
+ * which combines waitpid() and sigsuspend().
+ * This is the reason why we need to have a handler for SIGCHLD:
+ * SIG_DFL handler does not wake sigsuspend().
+ */
+ INT_OFF;
+ if (block == DOWAIT_BLOCK_OR_SIG) {
+ pid = wait_block_or_sig(&status);
+ } else {
+ int wait_flags = 0;
+ if (block == DOWAIT_NONBLOCK)
+ wait_flags = WNOHANG;
+ /* if job control is active, accept stopped processes too */
+ if (doing_jobctl)
+ wait_flags |= WUNTRACED;
+ /* NB: _not_ safe_waitpid, we need to detect EINTR */
+ pid = waitpid(-1, &status, wait_flags);
+ }
TRACE(("wait returns pid=%d, status=0x%x, errno=%d(%s)\n",
pid, status, errno, strerror(errno)));
if (pid <= 0)
- return pid;
+ goto out;
- INT_OFF;
thisjob = NULL;
for (jp = curjob; jp; jp = jp->prev_job) {
int jobstate;
return pid;
}
-static int
-blocking_wait_with_raise_on_sig(void)
-{
- pid_t pid = dowait(DOWAIT_BLOCK, NULL);
- if (pid <= 0 && pending_sig)
- raise_exception(EXSIG);
- return pid;
-}
-
#if JOBS
static void
showjob(struct job *jp, int mode)
int retval;
struct job *jp;
- if (pending_sig)
- raise_exception(EXSIG);
-
nextopt(nullstr);
retval = 0;
jp->waited = 1;
jp = jp->prev_job;
}
- blocking_wait_with_raise_on_sig();
/* man bash:
* "When bash is waiting for an asynchronous command via
* the wait builtin, the reception of a signal for which a trap
* has been set will cause the wait builtin to return immediately
* with an exit status greater than 128, immediately after which
* the trap is executed."
- *
- * blocking_wait_with_raise_on_sig raises signal handlers
- * if it gets no pid (pid < 0). However,
- * if child sends us a signal *and immediately exits*,
- * blocking_wait_with_raise_on_sig gets pid > 0
- * and does not handle pending_sig. Check this case: */
+ */
+ dowait(DOWAIT_BLOCK_OR_SIG, NULL);
+ /* if child sends us a signal *and immediately exits*,
+ * dowait() returns pid > 0. Check this case,
+ * not "if (dowait() < 0)"!
+ */
if (pending_sig)
- raise_exception(EXSIG);
+ goto sigout;
}
}
job = getjob(*argv, 0);
}
/* loop until process terminated or stopped */
- while (job->state == JOBRUNNING)
- blocking_wait_with_raise_on_sig();
+ while (job->state == JOBRUNNING) {
+ dowait(DOWAIT_BLOCK_OR_SIG, NULL);
+ if (pending_sig)
+ goto sigout;
+ }
job->waited = 1;
retval = getstatus(job);
repeat: ;
ret:
return retval;
+ sigout:
+ retval = 128 + pending_sig;
+ return retval;
}
static struct job *
STARTSTACKSTR(cmdnextc);
cmdtxt(n);
name = stackblock();
- TRACE(("commandtext: name %p, end %p\n\t\"%s\"\n",
- name, cmdnextc, cmdnextc));
+ TRACE(("commandtext: name %p, end %p\n", name, cmdnextc));
return ckstrdup(name);
}
#endif /* JOBS */
{
char **tp;
+ INT_OFF;
for (tp = trap; tp < &trap[NSIG]; tp++) {
if (*tp && **tp) { /* trap not NULL or "" (SIG_IGN) */
- INT_OFF;
if (trap_ptr == trap)
free(*tp);
/* else: it "belongs" to trap_ptr vector, don't free */
*tp = NULL;
if ((tp - trap) != 0)
setsignal(tp - trap);
- INT_ON;
}
}
may_have_traps = 0;
+ INT_ON;
}
/* Lives far away from here, needed for forkchild */
static void closescript(void);
/* Called after fork(), in child */
+/* jp and n are NULL when called by openhere() for heredoc support */
static NOINLINE void
forkchild(struct job *jp, union node *n, int mode)
{
{
TRACE(("In parent shell: child = %d\n", pid));
if (!jp) {
+ /* jp is NULL when called by openhere() for heredoc support */
while (jobless && dowait(DOWAIT_NONBLOCK, NULL) > 0)
continue;
jobless++;
}
}
+/* jp and n are NULL when called by openhere() for heredoc support */
static int
forkshell(struct job *jp, union node *n, int mode)
{
}
-/* ============ redir.c
- *
+/*
* Code for dealing with input/output redirection.
*/
}
/*
- * Copy a file descriptor to be >= to. Returns -1
- * if the source file descriptor is closed, EMPTY if there are no unused
- * file descriptors left.
+ * Copy a file descriptor to be >= 10. Throws exception on error.
*/
-/* 0x800..00: bit to set in "to" to request dup2 instead of fcntl(F_DUPFD).
- * old code was doing close(to) prior to copyfd() to achieve the same */
-enum {
- COPYFD_EXACT = (int)~(INT_MAX),
- COPYFD_RESTORE = (int)((unsigned)COPYFD_EXACT >> 1),
-};
static int
-copyfd(int from, int to)
+savefd(int from)
{
int newfd;
+ int err;
- if (to & COPYFD_EXACT) {
- to &= ~COPYFD_EXACT;
- /*if (from != to)*/
- newfd = dup2(from, to);
- } else {
- newfd = fcntl(from, F_DUPFD, to);
+ newfd = fcntl(from, F_DUPFD, 10);
+ err = newfd < 0 ? errno : 0;
+ if (err != EBADF) {
+ if (err)
+ ash_msg_and_raise_error("%d: %m", from);
+ close(from);
+ fcntl(newfd, F_SETFD, FD_CLOEXEC);
}
+
+ return newfd;
+}
+static int
+dup2_or_raise(int from, int to)
+{
+ int newfd;
+
+ newfd = (from != to) ? dup2(from, to) : to;
if (newfd < 0) {
- if (errno == EMFILE)
- return EMPTY;
/* Happens when source fd is not open: try "echo >&99" */
ash_msg_and_raise_error("%d: %m", from);
}
};
struct redirtab {
struct redirtab *next;
- int nullredirs;
int pair_count;
struct two_fd_t two_fd[];
};
#define redirlist (G_var.redirlist)
+enum {
+ COPYFD_RESTORE = (int)~(INT_MAX),
+};
static int
need_to_remember(struct redirtab *rp, int fd)
int newfd;
int copied_fd2 = -1;
- g_nullredirs++;
if (!redir) {
return;
}
sv->next = redirlist;
sv->pair_count = sv_pos;
redirlist = sv;
- sv->nullredirs = g_nullredirs - 1;
- g_nullredirs = 0;
while (sv_pos > 0) {
sv_pos--;
sv->two_fd[sv_pos].orig = sv->two_fd[sv_pos].copy = EMPTY;
if (fd != -1)
close(fd);
} else {
- copyfd(redir->ndup.dupfd, fd | COPYFD_EXACT);
+ dup2_or_raise(redir->ndup.dupfd, fd);
}
} else if (fd != newfd) { /* move newfd to fd */
- copyfd(newfd, fd | COPYFD_EXACT);
+ dup2_or_raise(newfd, fd);
#if ENABLE_ASH_BASH_COMPAT
if (!(redir->nfile.type == NTO2 && fd == 2))
#endif
struct redirtab *rp;
int i;
- if (--g_nullredirs >= 0 || redirlist == NULL)
+ if (redirlist == NULL)
return;
INT_OFF;
rp = redirlist;
if (!drop || (restore && (copy & COPYFD_RESTORE))) {
copy &= ~COPYFD_RESTORE;
/*close(fd);*/
- copyfd(copy, fd | COPYFD_EXACT);
+ dup2_or_raise(copy, fd);
}
close(copy & ~COPYFD_RESTORE);
}
}
redirlist = rp->next;
- g_nullredirs = rp->nullredirs;
free(rp);
INT_ON;
}
* Undo all redirections. Called on error or interrupt.
*/
-/*
- * Discard all saved file descriptors.
- */
-static void
-clearredir(int drop)
-{
- for (;;) {
- g_nullredirs = 0;
- if (!redirlist)
- break;
- popredir(drop, /*restore:*/ 0);
- }
-}
-
static int
redirectsafe(union node *redir, int flags)
{
#define EXP_TILDE 0x2 /* do normal tilde expansion */
#define EXP_VARTILDE 0x4 /* expand tildes in an assignment */
#define EXP_REDIR 0x8 /* file glob for a redirection (1 match only) */
+/* ^^^^^^^^^^^^^^ this is meant to support constructs such as "cmd >file*.txt"
+ * POSIX says for this case:
+ * Pathname expansion shall not be performed on the word by a
+ * non-interactive shell; an interactive shell may perform it, but shall
+ * do so only when the expansion would result in one word.
+ * Currently, our code complies to the above rule by never globbing
+ * redirection filenames.
+ * Bash performs globbing, unless it is non-interactive and in POSIX mode.
+ * (this means that on a typical Linux distro, bash almost always
+ * performs globbing, and thus diverges from what we do).
+ */
#define EXP_CASE 0x10 /* keeps quotes around for CASE pattern */
#define EXP_QPAT 0x20 /* pattern in quoted parameter expansion */
#define EXP_VARTILDE2 0x40 /* expand tildes after colons only */
return len;
}
+/*
+ * Break the argument string into pieces based upon IFS and add the
+ * strings to the argument list. The regions of the string to be
+ * searched for IFS characters have been stored by recordregion.
+ */
+static void
+ifsbreakup(char *string, struct arglist *arglist)
+{
+ struct ifsregion *ifsp;
+ struct strlist *sp;
+ char *start;
+ char *p;
+ char *q;
+ const char *ifs, *realifs;
+ int ifsspc;
+ int nulonly;
+
+ start = string;
+ if (ifslastp != NULL) {
+ ifsspc = 0;
+ nulonly = 0;
+ realifs = ifsset() ? ifsval() : defifs;
+ ifsp = &ifsfirst;
+ do {
+ p = string + ifsp->begoff;
+ nulonly = ifsp->nulonly;
+ ifs = nulonly ? nullstr : realifs;
+ ifsspc = 0;
+ while (p < string + ifsp->endoff) {
+ q = p;
+ if ((unsigned char)*p == CTLESC)
+ p++;
+ if (!strchr(ifs, *p)) {
+ p++;
+ continue;
+ }
+ if (!nulonly)
+ ifsspc = (strchr(defifs, *p) != NULL);
+ /* Ignore IFS whitespace at start */
+ if (q == start && ifsspc) {
+ p++;
+ start = p;
+ continue;
+ }
+ *q = '\0';
+ sp = stzalloc(sizeof(*sp));
+ sp->text = start;
+ *arglist->lastp = sp;
+ arglist->lastp = &sp->next;
+ p++;
+ if (!nulonly) {
+ for (;;) {
+ if (p >= string + ifsp->endoff) {
+ break;
+ }
+ q = p;
+ if ((unsigned char)*p == CTLESC)
+ p++;
+ if (strchr(ifs, *p) == NULL) {
+ p = q;
+ break;
+ }
+ if (strchr(defifs, *p) == NULL) {
+ if (ifsspc) {
+ p++;
+ ifsspc = 0;
+ } else {
+ p = q;
+ break;
+ }
+ } else
+ p++;
+ }
+ }
+ start = p;
+ } /* while */
+ ifsp = ifsp->next;
+ } while (ifsp != NULL);
+ if (nulonly)
+ goto add;
+ }
+
+ if (!*start)
+ return;
+
+ add:
+ sp = stzalloc(sizeof(*sp));
+ sp->text = start;
+ *arglist->lastp = sp;
+ arglist->lastp = &sp->next;
+}
+
+static void
+ifsfree(void)
+{
+ struct ifsregion *p = ifsfirst.next;
+
+ if (!p)
+ goto out;
+
+ INT_OFF;
+ do {
+ struct ifsregion *ifsp;
+ ifsp = p->next;
+ free(p);
+ p = ifsp;
+ } while (p);
+ ifsfirst.next = NULL;
+ INT_ON;
+ out:
+ ifslastp = NULL;
+}
+
static size_t
esclen(const char *start, const char *p)
{
static void FAST_FUNC
evalbackcmd(union node *n, struct backcmd *result)
{
- int saveherefd;
+ int pip[2];
+ struct job *jp;
result->fd = -1;
result->buf = NULL;
result->nleft = 0;
result->jp = NULL;
- if (n == NULL)
+ if (n == NULL) {
goto out;
+ }
- saveherefd = herefd;
- herefd = -1;
-
- {
- int pip[2];
- struct job *jp;
-
- if (pipe(pip) < 0)
- ash_msg_and_raise_error("pipe call failed");
- jp = makejob(/*n,*/ 1);
- if (forkshell(jp, n, FORK_NOJOB) == 0) {
- FORCE_INT_ON;
- close(pip[0]);
- if (pip[1] != 1) {
- /*close(1);*/
- copyfd(pip[1], 1 | COPYFD_EXACT);
- close(pip[1]);
- }
- eflag = 0;
- evaltree(n, EV_EXIT); /* actually evaltreenr... */
- /* NOTREACHED */
+ if (pipe(pip) < 0)
+ ash_msg_and_raise_error("pipe call failed");
+ jp = makejob(/*n,*/ 1);
+ if (forkshell(jp, n, FORK_NOJOB) == 0) {
+ /* child */
+ FORCE_INT_ON;
+ close(pip[0]);
+ if (pip[1] != 1) {
+ /*close(1);*/
+ dup2_or_raise(pip[1], 1);
+ close(pip[1]);
}
- close(pip[1]);
- result->fd = pip[0];
- result->jp = jp;
+/* TODO: eflag clearing makes the following not abort:
+ * ash -c 'set -e; z=$(false;echo foo); echo $z'
+ * which is what bash does (unless it is in POSIX mode).
+ * dash deleted "eflag = 0" line in the commit
+ * Date: Mon, 28 Jun 2010 17:11:58 +1000
+ * [EVAL] Don't clear eflag in evalbackcmd
+ * For now, preserve bash-like behavior, it seems to be somewhat more useful:
+ */
+ eflag = 0;
+ ifsfree();
+ evaltree(n, EV_EXIT); /* actually evaltreenr... */
+ /* NOTREACHED */
}
- herefd = saveherefd;
+ /* parent */
+ close(pip[1]);
+ result->fd = pip[0];
+ result->jp = jp;
+
out:
TRACE(("evalbackcmd done: fd=%d buf=0x%x nleft=%d jp=0x%x\n",
result->fd, result->buf, result->nleft, result->jp));
char *str;
IF_ASH_BASH_COMPAT(char *repl = NULL;)
IF_ASH_BASH_COMPAT(int pos, len, orig_len;)
- int saveherefd = herefd;
int amount, resetloc;
IF_ASH_BASH_COMPAT(int workloc;)
int zero;
//bb_error_msg("subevalvar(p:'%s',varname:'%s',strloc:%d,subtype:%d,startloc:%d,varflags:%x,quotes:%d)",
// p, varname, strloc, subtype, startloc, varflags, quotes);
- herefd = -1;
argstr(p, EXP_TILDE | (subtype != VSASSIGN && subtype != VSQUESTION ?
(flag & (EXP_QUOTED | EXP_QPAT) ? EXP_QPAT : EXP_CASE) : 0),
var_str_list);
STPUTC('\0', expdest);
- herefd = saveherefd;
argbackq = saveargbackq;
startp = (char *)stackblock() + startloc;
varflags = (unsigned char) *p++;
subtype = varflags & VSTYPE;
+
+ if (!subtype)
+ raise_error_syntax("bad substitution");
+
quoted = flag & EXP_QUOTED;
var = p;
easy = (!quoted || (*var == '@' && shellparam.nparam));
return p;
}
-/*
- * Break the argument string into pieces based upon IFS and add the
- * strings to the argument list. The regions of the string to be
- * searched for IFS characters have been stored by recordregion.
- */
-static void
-ifsbreakup(char *string, struct arglist *arglist)
-{
- struct ifsregion *ifsp;
- struct strlist *sp;
- char *start;
- char *p;
- char *q;
- const char *ifs, *realifs;
- int ifsspc;
- int nulonly;
-
- start = string;
- if (ifslastp != NULL) {
- ifsspc = 0;
- nulonly = 0;
- realifs = ifsset() ? ifsval() : defifs;
- ifsp = &ifsfirst;
- do {
- p = string + ifsp->begoff;
- nulonly = ifsp->nulonly;
- ifs = nulonly ? nullstr : realifs;
- ifsspc = 0;
- while (p < string + ifsp->endoff) {
- q = p;
- if ((unsigned char)*p == CTLESC)
- p++;
- if (!strchr(ifs, *p)) {
- p++;
- continue;
- }
- if (!nulonly)
- ifsspc = (strchr(defifs, *p) != NULL);
- /* Ignore IFS whitespace at start */
- if (q == start && ifsspc) {
- p++;
- start = p;
- continue;
- }
- *q = '\0';
- sp = stzalloc(sizeof(*sp));
- sp->text = start;
- *arglist->lastp = sp;
- arglist->lastp = &sp->next;
- p++;
- if (!nulonly) {
- for (;;) {
- if (p >= string + ifsp->endoff) {
- break;
- }
- q = p;
- if ((unsigned char)*p == CTLESC)
- p++;
- if (strchr(ifs, *p) == NULL) {
- p = q;
- break;
- }
- if (strchr(defifs, *p) == NULL) {
- if (ifsspc) {
- p++;
- ifsspc = 0;
- } else {
- p = q;
- break;
- }
- } else
- p++;
- }
- }
- start = p;
- } /* while */
- ifsp = ifsp->next;
- } while (ifsp != NULL);
- if (nulonly)
- goto add;
- }
-
- if (!*start)
- return;
-
- add:
- sp = stzalloc(sizeof(*sp));
- sp->text = start;
- *arglist->lastp = sp;
- arglist->lastp = &sp->next;
-}
-
-static void
-ifsfree(void)
-{
- struct ifsregion *p;
-
- INT_OFF;
- p = ifsfirst.next;
- do {
- struct ifsregion *ifsp;
- ifsp = p->next;
- free(p);
- p = ifsp;
- } while (p);
- ifslastp = NULL;
- ifsfirst.next = NULL;
- INT_ON;
-}
-
/*
* Add a file name to the list.
*/
if (fflag)
goto nometa;
+
+ /* Avoid glob() (and thus, stat() et al) for words like "echo" */
+ p = str->text;
+ while (*p) {
+ if (*p == '*')
+ goto need_glob;
+ if (*p == '?')
+ goto need_glob;
+ if (*p == '[')
+ goto need_glob;
+ p++;
+ }
+ goto nometa;
+
+ need_glob:
INT_OFF;
p = preglob(str->text, RMESCAPE_ALLOC | RMESCAPE_HEAP);
// GLOB_NOMAGIC (GNU): if no *?[ chars in pattern, return it even if no match
argbackq = arg->narg.backquote;
STARTSTACKSTR(expdest);
- ifsfirst.next = NULL;
- ifslastp = NULL;
TRACE(("expandarg: argstr('%s',flags:%x)\n", arg->narg.text, flag));
argstr(arg->narg.text, flag,
/* var_str_list: */ arglist ? arglist->list : NULL);
p = _STPUTC('\0', expdest);
expdest = p - 1;
if (arglist == NULL) {
- return; /* here document expanded */
+ /* here document expanded */
+ goto out;
}
p = grabstackstr(p);
TRACE(("expandarg: p:'%s'\n", p));
*exparg.lastp = sp;
exparg.lastp = &sp->next;
}
- if (ifsfirst.next)
- ifsfree();
*exparg.lastp = NULL;
if (exparg.list) {
*arglist->lastp = exparg.list;
arglist->lastp = exparg.lastp;
}
+
+ out:
+ ifsfree();
}
/*
static void
expandhere(union node *arg, int fd)
{
- herefd = fd;
expandarg(arg, (struct arglist *)NULL, EXP_QUOTED);
full_write(fd, stackblock(), expdest - (char *)stackblock());
}
setstackmark(&smark);
argbackq = pattern->narg.backquote;
STARTSTACKSTR(expdest);
- ifslastp = NULL;
argstr(pattern->narg.text, EXP_TILDE | EXP_CASE,
/* var_str_list: */ NULL);
STACKSTRNUL(expdest);
+ ifsfree();
result = patmatch(stackblock(), val);
popstackmark(&smark);
return result;
#else
execve(cmd, argv, envp);
#endif
- if (cmd == (char*) bb_busybox_exec_path) {
- /* We already visited ENOEXEC branch below, don't do it again */
-//TODO: try execve(initial_argv0_of_shell, argv, envp) before giving up?
- free(argv);
- return;
- }
- if (errno == ENOEXEC) {
+ if (cmd != (char*) bb_busybox_exec_path && errno == ENOEXEC) {
/* Run "cmd" as a shell script:
* http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html
* "If the execve() function fails with ENOEXEC, the shell
* message and exit code 126. For one, this prevents attempts
* to interpret foreign ELF binaries as shell scripts.
*/
- char **ap;
- char **new;
-
- for (ap = argv; *ap; ap++)
- continue;
- new = ckmalloc((ap - argv + 2) * sizeof(new[0]));
- new[0] = (char*) "ash";
- new[1] = cmd;
- ap = new + 2;
- while ((*ap++ = *++argv) != NULL)
- continue;
+ argv[0] = cmd;
cmd = (char*) bb_busybox_exec_path;
- argv = new;
+ /* NB: this is only possible because all callers of shellexec()
+ * ensure that the argv[-1] slot exists!
+ */
+ argv--;
+ argv[0] = (char*) "ash";
goto repeat;
}
}
/*
* Exec a program. Never returns. If you change this routine, you may
* have to change the find_command routine as well.
+ * argv[-1] must exist and be writable! See tryexec() for why.
*/
static void shellexec(char **, const char *, int) NORETURN;
static void
int exerrno;
int applet_no = -1; /* used only by FEATURE_SH_STANDALONE */
- clearredir(/*drop:*/ 1);
envp = listvars(VEXPORT, VUNSET, /*end:*/ NULL);
if (strchr(argv[0], '/') != NULL
#if ENABLE_FEATURE_SH_STANDALONE
exitstatus = exerrno;
TRACE(("shellexec failed for %s, errno %d, suppress_int %d\n",
argv[0], e, suppress_int));
- ash_msg_and_raise(EXEXEC, "%s: %s", argv[0], errmsg(e, "not found"));
+ ash_msg_and_raise(EXEXIT, "%s: %s", argv[0], errmsg(e, "not found"));
/* NOTREACHED */
}
};
typedef smallint token_id_t;
-/* first char is indicating which tokens mark the end of a list */
+/* Nth bit indicates if token marks the end of a list */
+enum {
+ tokendlist = 0
+ /* 0 */ | (1u << TEOF)
+ /* 1 */ | (0u << TNL)
+ /* 2 */ | (0u << TREDIR)
+ /* 3 */ | (0u << TWORD)
+ /* 4 */ | (0u << TSEMI)
+ /* 5 */ | (0u << TBACKGND)
+ /* 6 */ | (0u << TAND)
+ /* 7 */ | (0u << TOR)
+ /* 8 */ | (0u << TPIPE)
+ /* 9 */ | (0u << TLP)
+ /* 10 */ | (1u << TRP)
+ /* 11 */ | (1u << TENDCASE)
+ /* 12 */ | (1u << TENDBQUOTE)
+ /* 13 */ | (0u << TNOT)
+ /* 14 */ | (0u << TCASE)
+ /* 15 */ | (1u << TDO)
+ /* 16 */ | (1u << TDONE)
+ /* 17 */ | (1u << TELIF)
+ /* 18 */ | (1u << TELSE)
+ /* 19 */ | (1u << TESAC)
+ /* 20 */ | (1u << TFI)
+ /* 21 */ | (0u << TFOR)
+#if ENABLE_ASH_BASH_COMPAT
+ /* 22 */ | (0u << TFUNCTION)
+#endif
+ /* 23 */ | (0u << TIF)
+ /* 24 */ | (0u << TIN)
+ /* 25 */ | (1u << TTHEN)
+ /* 26 */ | (0u << TUNTIL)
+ /* 27 */ | (0u << TWHILE)
+ /* 28 */ | (0u << TBEGIN)
+ /* 29 */ | (1u << TEND)
+ , /* thus far 29 bits used */
+};
+
static const char *const tokname_array[] = {
- "\1end of file",
- "\0newline",
- "\0redirection",
- "\0word",
- "\0;",
- "\0&",
- "\0&&",
- "\0||",
- "\0|",
- "\0(",
- "\1)",
- "\1;;",
- "\1`",
+ "end of file",
+ "newline",
+ "redirection",
+ "word",
+ ";",
+ "&",
+ "&&",
+ "||",
+ "|",
+ "(",
+ ")",
+ ";;",
+ "`",
#define KWDOFFSET 13
/* the following are keywords */
- "\0!",
- "\0case",
- "\1do",
- "\1done",
- "\1elif",
- "\1else",
- "\1esac",
- "\1fi",
- "\0for",
+ "!",
+ "case",
+ "do",
+ "done",
+ "elif",
+ "else",
+ "esac",
+ "fi",
+ "for",
#if ENABLE_ASH_BASH_COMPAT
- "\0function",
-#endif
- "\0if",
- "\0in",
- "\1then",
- "\0until",
- "\0while",
- "\0{",
- "\1}",
+ "function",
+#endif
+ "if",
+ "in",
+ "then",
+ "until",
+ "while",
+ "{",
+ "}",
};
/* Wrapper around strcmp for qsort/bsearch/... */
static int
pstrcmp(const void *a, const void *b)
{
- return strcmp((char*) a, (*(char**) b) + 1);
+ return strcmp((char*)a, *(char**)b);
}
static const char *const *
#endif
-/* ============ eval.c */
-
-static int funcblocksize; /* size of structures in function */
-static int funcstringsize; /* size of strings in node */
+/*static int funcblocksize; // size of structures in function */
+/*static int funcstringsize; // size of strings in node */
static void *funcblock; /* block to allocate function from */
-static char *funcstring; /* block to allocate strings from */
+static char *funcstring_end; /* end of block to allocate strings from */
/* flags in argument to evaltree */
#define EV_EXIT 01 /* exit after evaluating tree */
[NNOT ] = SHELL_ALIGN(sizeof(struct nnot)),
};
-static void calcsize(union node *n);
+static int calcsize(int funcblocksize, union node *n);
-static void
-sizenodelist(struct nodelist *lp)
+static int
+sizenodelist(int funcblocksize, struct nodelist *lp)
{
while (lp) {
funcblocksize += SHELL_ALIGN(sizeof(struct nodelist));
- calcsize(lp->n);
+ funcblocksize = calcsize(funcblocksize, lp->n);
lp = lp->next;
}
+ return funcblocksize;
}
-static void
-calcsize(union node *n)
+static int
+calcsize(int funcblocksize, union node *n)
{
if (n == NULL)
- return;
+ return funcblocksize;
funcblocksize += nodesize[n->type];
switch (n->type) {
case NCMD:
- calcsize(n->ncmd.redirect);
- calcsize(n->ncmd.args);
- calcsize(n->ncmd.assign);
+ funcblocksize = calcsize(funcblocksize, n->ncmd.redirect);
+ funcblocksize = calcsize(funcblocksize, n->ncmd.args);
+ funcblocksize = calcsize(funcblocksize, n->ncmd.assign);
break;
case NPIPE:
- sizenodelist(n->npipe.cmdlist);
+ funcblocksize = sizenodelist(funcblocksize, n->npipe.cmdlist);
break;
case NREDIR:
case NBACKGND:
case NSUBSHELL:
- calcsize(n->nredir.redirect);
- calcsize(n->nredir.n);
+ funcblocksize = calcsize(funcblocksize, n->nredir.redirect);
+ funcblocksize = calcsize(funcblocksize, n->nredir.n);
break;
case NAND:
case NOR:
case NSEMI:
case NWHILE:
case NUNTIL:
- calcsize(n->nbinary.ch2);
- calcsize(n->nbinary.ch1);
+ funcblocksize = calcsize(funcblocksize, n->nbinary.ch2);
+ funcblocksize = calcsize(funcblocksize, n->nbinary.ch1);
break;
case NIF:
- calcsize(n->nif.elsepart);
- calcsize(n->nif.ifpart);
- calcsize(n->nif.test);
+ funcblocksize = calcsize(funcblocksize, n->nif.elsepart);
+ funcblocksize = calcsize(funcblocksize, n->nif.ifpart);
+ funcblocksize = calcsize(funcblocksize, n->nif.test);
break;
case NFOR:
- funcstringsize += strlen(n->nfor.var) + 1;
- calcsize(n->nfor.body);
- calcsize(n->nfor.args);
+ funcblocksize += SHELL_ALIGN(strlen(n->nfor.var) + 1); /* was funcstringsize += ... */
+ funcblocksize = calcsize(funcblocksize, n->nfor.body);
+ funcblocksize = calcsize(funcblocksize, n->nfor.args);
break;
case NCASE:
- calcsize(n->ncase.cases);
- calcsize(n->ncase.expr);
+ funcblocksize = calcsize(funcblocksize, n->ncase.cases);
+ funcblocksize = calcsize(funcblocksize, n->ncase.expr);
break;
case NCLIST:
- calcsize(n->nclist.body);
- calcsize(n->nclist.pattern);
- calcsize(n->nclist.next);
+ funcblocksize = calcsize(funcblocksize, n->nclist.body);
+ funcblocksize = calcsize(funcblocksize, n->nclist.pattern);
+ funcblocksize = calcsize(funcblocksize, n->nclist.next);
break;
case NDEFUN:
case NARG:
- sizenodelist(n->narg.backquote);
- funcstringsize += strlen(n->narg.text) + 1;
- calcsize(n->narg.next);
+ funcblocksize = sizenodelist(funcblocksize, n->narg.backquote);
+ funcblocksize += SHELL_ALIGN(strlen(n->narg.text) + 1); /* was funcstringsize += ... */
+ funcblocksize = calcsize(funcblocksize, n->narg.next);
break;
case NTO:
#if ENABLE_ASH_BASH_COMPAT
case NFROM:
case NFROMTO:
case NAPPEND:
- calcsize(n->nfile.fname);
- calcsize(n->nfile.next);
+ funcblocksize = calcsize(funcblocksize, n->nfile.fname);
+ funcblocksize = calcsize(funcblocksize, n->nfile.next);
break;
case NTOFD:
case NFROMFD:
- calcsize(n->ndup.vname);
- calcsize(n->ndup.next);
+ funcblocksize = calcsize(funcblocksize, n->ndup.vname);
+ funcblocksize = calcsize(funcblocksize, n->ndup.next);
break;
case NHERE:
case NXHERE:
- calcsize(n->nhere.doc);
- calcsize(n->nhere.next);
+ funcblocksize = calcsize(funcblocksize, n->nhere.doc);
+ funcblocksize = calcsize(funcblocksize, n->nhere.next);
break;
case NNOT:
- calcsize(n->nnot.com);
+ funcblocksize = calcsize(funcblocksize, n->nnot.com);
break;
};
+ return funcblocksize;
}
static char *
nodeckstrdup(char *s)
{
- char *rtn = funcstring;
-
- strcpy(funcstring, s);
- funcstring += strlen(s) + 1;
- return rtn;
+ funcstring_end -= SHELL_ALIGN(strlen(s) + 1);
+ return strcpy(funcstring_end, s);
}
static union node *copynode(union node *);
struct funcnode *f;
size_t blocksize;
- funcblocksize = offsetof(struct funcnode, n);
- funcstringsize = 0;
- calcsize(n);
- blocksize = funcblocksize;
- f = ckmalloc(blocksize + funcstringsize);
+ /*funcstringsize = 0;*/
+ blocksize = offsetof(struct funcnode, n) + calcsize(0, n);
+ f = ckzalloc(blocksize /* + funcstringsize */);
funcblock = (char *) f + offsetof(struct funcnode, n);
- funcstring = (char *) f + blocksize;
+ funcstring_end = (char *) f + blocksize;
copynode(n);
- f->count = 0;
+ /* f->count = 0; - ckzalloc did it */
return f;
}
* Define a shell function.
*/
static void
-defun(char *name, union node *func)
+defun(union node *func)
{
struct cmdentry entry;
INT_OFF;
entry.cmdtype = CMDFUNCTION;
entry.u.func = copyfunc(func);
- addcmdentry(name, &entry);
+ addcmdentry(func->narg.text, &entry);
INT_ON;
}
static int
evaltree(union node *n, int flags)
{
- struct jmploc *volatile savehandler = exception_handler;
- struct jmploc jmploc;
int checkexit = 0;
int (*evalfn)(union node *, int);
int status = 0;
- int int_level;
-
- SAVE_INT(int_level);
if (n == NULL) {
TRACE(("evaltree(NULL) called\n"));
- goto out1;
+ goto out;
}
TRACE(("evaltree(%p: %d, %d) called\n", n, n->type, flags));
dotrap();
- exception_handler = &jmploc;
- {
- int err = setjmp(jmploc.loc);
- if (err) {
- /* if it was a signal, check for trap handlers */
- if (exception_type == EXSIG) {
- TRACE(("exception %d (EXSIG) in evaltree, err=%d\n",
- exception_type, err));
- goto out;
- }
- /* continue on the way out */
- TRACE(("exception %d in evaltree, propagating err=%d\n",
- exception_type, err));
- exception_handler = savehandler;
- longjmp(exception_handler->loc, err);
- }
- }
-
switch (n->type) {
default:
#if DEBUG
if (!status) {
status = evaltree(n->nredir.n, flags & EV_TESTED);
}
- popredir(/*drop:*/ 0, /*restore:*/ 0 /* not sure */);
+ if (n->nredir.redirect)
+ popredir(/*drop:*/ 0, /*restore:*/ 0 /* not sure */);
goto setstatus;
case NCMD:
evalfn = evalcommand;
evalfn = evalloop;
goto calleval;
case NSUBSHELL:
- evalfn = evalsubshell;
- goto checkexit;
case NBACKGND:
evalfn = evalsubshell;
- goto calleval;
+ goto checkexit;
case NPIPE:
evalfn = evalpipe;
goto checkexit;
status = 0;
goto setstatus;
case NDEFUN:
- defun(n->narg.text, n->narg.next);
+ defun(n);
/* Not necessary. To test it:
* "false; f() { qwerty; }; echo $?" should print 0.
*/
exitstatus = status;
break;
}
-
out:
- exception_handler = savehandler;
-
- out1:
/* Order of checks below is important:
* signal handlers trigger before exit caused by "set -e".
*/
if (flags & EV_EXIT)
raise_exception(EXEXIT);
- RESTORE_INT(int_level);
TRACE(("leaving evaltree (no interrupts)\n"));
-
return exitstatus;
}
evaltreenr(n->nredir.n, flags);
/* never returns */
}
+ /* parent */
status = 0;
if (!backgnd)
status = waitforjob(jp);
}
}
if (forkshell(jp, lp->n, n->npipe.pipe_backgnd) == 0) {
+ /* child */
INT_ON;
if (pip[1] >= 0) {
close(pip[0]);
evaltreenr(lp->n, flags);
/* never returns */
}
+ /* parent */
if (prevfd >= 0)
close(prevfd);
prevfd = pip[0];
shellparam.optind = 1;
shellparam.optoff = -1;
#endif
- evaltree(&func->n, flags & EV_TESTED);
+ evaltree(func->n.narg.next, flags & EV_TESTED);
funcdone:
INT_OFF;
funcnest--;
/* else:
* it's a duplicate "local VAR" declaration, do nothing
*/
- return;
+ goto ret;
}
lvp = lvp->next;
}
lvp->vp = vp;
lvp->next = localvars;
localvars = lvp;
+ ret:
INT_ON;
}
/*
* Search the table of builtin commands.
*/
+static int
+pstrcmp1(const void *a, const void *b)
+{
+ return strcmp((char*)a, *(char**)b + 1);
+}
static struct builtincmd *
find_builtin(const char *name)
{
bp = bsearch(
name, builtintab, ARRAY_SIZE(builtintab), sizeof(builtintab[0]),
- pstrcmp
+ pstrcmp1
);
return bp;
}
argc++;
}
- argv = nargv = stalloc(sizeof(char *) * (argc + 1));
+ /* Reserve one extra spot at the front for shellexec. */
+ nargv = stalloc(sizeof(char *) * (argc + 2));
+ argv = ++nargv;
for (sp = arglist.list; sp; sp = sp->next) {
TRACE(("evalcommand arg: %s\n", sp->text));
*nargv++ = sp->text;
if (applet_no >= 0 && APPLET_IS_NOFORK(applet_no)) {
listsetvar(varlist.list, VEXPORT|VSTACK);
/* run <applet>_main() */
- exitstatus = run_nofork_applet(applet_no, argv);
+ status = run_nofork_applet(applet_no, argv);
break;
}
#endif
dowait(DOWAIT_NONBLOCK, NULL);
if (evalbltin(cmdentry.u.cmd, argc, argv, flags)) {
- int exit_status;
- int i = exception_type;
- if (i == EXEXIT || i == EXEXEC)
- goto raise;
- exit_status = 2;
- if (i == EXINT)
- exit_status = 128 + SIGINT;
- if (i == EXSIG)
- exit_status = 128 + pending_sig;
- exitstatus = exit_status;
- if (i == EXINT || spclbltin > 0) {
- raise:
- longjmp(exception_handler->loc, 1);
+ if (exception_type == EXERROR && spclbltin <= 0) {
+ FORCE_INT_ON;
+ goto readstatus;
}
- FORCE_INT_ON;
+ raise:
+ longjmp(exception_handler->loc, 1);
}
goto readstatus;
} /* switch */
out:
- popredir(/*drop:*/ cmd_is_exec, /*restore:*/ cmd_is_exec);
+ if (cmd->ncmd.redirect)
+ popredir(/*drop:*/ cmd_is_exec, /*restore:*/ cmd_is_exec);
if (lastarg) {
/* dsl: I think this is intended to be used to support
* '_' in 'vi' command mode during line editing...
}
-/* ============ input.c
- *
+/*
* This implements the input routines used by the parser.
*/
return (unsigned char)*g_parsefile->next_to_pgetc++;
}
+static void
+nlprompt(void)
+{
+ g_parsefile->linno++;
+ setprompt_if(doprompt, 2);
+}
+static void
+nlnoprompt(void)
+{
+ g_parsefile->linno++;
+ needprompt = doprompt;
+}
+
static int
pgetc(void)
{
break;
}
- g_parsefile->linno++;
- setprompt_if(doprompt, 2);
+ nlprompt();
}
return c;
{
struct parsefile *pf = g_parsefile;
+ if (pf == &basepf)
+ return;
+
INT_OFF;
if (pf->pf_fd >= 0)
close(pf->pf_fd);
static void
setinputfd(int fd, int push)
{
- close_on_exec_on(fd);
if (push) {
pushfile();
g_parsefile->buf = NULL;
setinputfile(const char *fname, int flags)
{
int fd;
- int fd2;
INT_OFF;
fd = open(fname, O_RDONLY);
if (fd < 0) {
if (flags & INPUT_NOFILE_OK)
goto out;
+ exitstatus = 127;
ash_msg_and_raise_error("can't open '%s'", fname);
}
- if (fd < 10) {
- fd2 = copyfd(fd, 10);
- close(fd);
- if (fd2 < 0)
- ash_msg_and_raise_error("out of file descriptors");
- fd = fd2;
- }
+ if (fd < 10)
+ fd = savefd(fd);
+ else
+ close_on_exec_on(fd);
setinputfd(fd, flags & INPUT_PUSH_FILE);
out:
INT_ON;
}
-/* ============ mail.c
- *
+/*
* Routines to check for mail.
*/
#if ENABLE_ASH_GETOPTS
static int
-getopts(char *optstr, char *optvar, char **optfirst, int *param_optind, int *optoff)
+getopts(char *optstr, char *optvar, char **optfirst)
{
char *p, *q;
char c = '?';
int done = 0;
- int err = 0;
char sbuf[2];
char **optnext;
+ int ind = shellparam.optind;
+ int off = shellparam.optoff;
sbuf[1] = '\0';
- if (*param_optind < 1)
- return 1;
- optnext = optfirst + *param_optind - 1;
+ shellparam.optind = -1;
+ optnext = optfirst + ind - 1;
- if (*param_optind <= 1 || *optoff < 0 || (int)strlen(optnext[-1]) < *optoff)
+ if (ind <= 1 || off < 0 || (int)strlen(optnext[-1]) < off)
p = NULL;
else
- p = optnext[-1] + *optoff;
+ p = optnext[-1] + off;
if (p == NULL || *p == '\0') {
/* Current word is done, advance */
p = *optnext;
if (optstr[0] == ':') {
sbuf[0] = c;
/*sbuf[1] = '\0'; - already is */
- err |= setvarsafe("OPTARG", sbuf, 0);
+ setvar0("OPTARG", sbuf);
} else {
fprintf(stderr, "Illegal option -%c\n", c);
unsetvar("OPTARG");
if (optstr[0] == ':') {
sbuf[0] = c;
/*sbuf[1] = '\0'; - already is */
- err |= setvarsafe("OPTARG", sbuf, 0);
+ setvar0("OPTARG", sbuf);
c = ':';
} else {
fprintf(stderr, "No arg for -%c option\n", c);
if (p == *optnext)
optnext++;
- err |= setvarsafe("OPTARG", p, 0);
+ setvar0("OPTARG", p);
p = NULL;
} else
- err |= setvarsafe("OPTARG", nullstr, 0);
+ setvar0("OPTARG", nullstr);
out:
- *optoff = p ? p - *(optnext - 1) : -1;
- *param_optind = optnext - optfirst + 1;
- err |= setvarsafe("OPTIND", itoa(*param_optind), VNOFUNC);
+ ind = optnext - optfirst + 1;
+ setvar("OPTIND", itoa(ind), VNOFUNC);
sbuf[0] = c;
/*sbuf[1] = '\0'; - already is */
- err |= setvarsafe(optvar, sbuf, 0);
- if (err) {
- *param_optind = 1;
- *optoff = -1;
- flush_stdout_stderr();
- raise_exception(EXERROR);
- }
+ setvar0(optvar, sbuf);
+
+ shellparam.optoff = p ? p - *(optnext - 1) : -1;
+ shellparam.optind = ind;
+
return done;
}
ash_msg_and_raise_error("usage: getopts optstring var [arg]");
if (argc == 3) {
optbase = shellparam.p;
- if (shellparam.optind > shellparam.nparam + 1) {
+ if ((unsigned)shellparam.optind > shellparam.nparam + 1) {
shellparam.optind = 1;
shellparam.optoff = -1;
}
} else {
optbase = &argv[3];
- if (shellparam.optind > argc - 2) {
+ if ((unsigned)shellparam.optind > argc - 2) {
shellparam.optind = 1;
shellparam.optoff = -1;
}
}
- return getopts(argv[1], argv[2], optbase, &shellparam.optind,
- &shellparam.optoff);
+ return getopts(argv[1], argv[2], optbase);
}
#endif /* ASH_GETOPTS */
tokname(char *buf, int tok)
{
if (tok < TSEMI)
- return tokname_array[tok] + 1;
- sprintf(buf, "\"%s\"", tokname_array[tok] + 1);
+ return tokname_array[tok];
+ sprintf(buf, "\"%s\"", tokname_array[tok]);
return buf;
}
}
checkkwd = CHKNL | CHKKWD | CHKALIAS;
- if (nlflag == 2 && tokname_array[peektoken()][0])
+ if (nlflag == 2 && ((1 << peektoken()) & tokendlist))
return n1;
nlflag |= 2;
n1->nbinary.ch1 = list(0);
got = readtoken();
if (got != TDO) {
- TRACE(("expecting DO got '%s' %s\n", tokname_array[got] + 1,
+ TRACE(("expecting DO got '%s' %s\n", tokname_array[got],
got == TWORD ? wordtext : ""));
raise_error_unexpected_syntax(TDO);
}
if (syntax == BASESYNTAX)
goto endword; /* exit outer loop */
USTPUTC(c, out);
- g_parsefile->linno++;
- setprompt_if(doprompt, 2);
+ nlprompt();
c = pgetc();
goto loop; /* continue outer loop */
case CWORD:
USTPUTC('\\', out);
pungetc();
} else if (c == '\n') {
- setprompt_if(doprompt, 2);
+ nlprompt();
} else {
#if ENABLE_ASH_EXPAND_PRMT
if (c == '$' && pssyntax) {
if (c == *eofmark) {
if (pfgets(line, sizeof(line)) != NULL) {
char *p, *q;
+ int cc;
p = line;
- for (q = eofmark + 1; *q && *p == *q; p++, q++)
- continue;
- if (*p == '\n' && *q == '\0') {
+ for (q = eofmark + 1;; p++, q++) {
+ cc = *p;
+ if (cc == '\n')
+ cc = 0;
+ if (!*q || cc != *q)
+ break;
+ }
+ if (cc == *q) {
c = PEOF;
- g_parsefile->linno++;
- needprompt = doprompt;
+ nlnoprompt();
} else {
pushstring(line, NULL);
}
parsesub: {
unsigned char subtype;
int typeloc;
- int flags;
c = pgetc_eatbnl();
if (c > 255 /* PEOA or PEOF */
/* $VAR, $<specialchar>, ${...}, or PEOA/PEOF */
USTPUTC(CTLVAR, out);
typeloc = out - (char *)stackblock();
- USTPUTC(VSNORMAL, out);
+ STADJUST(1, out);
subtype = VSNORMAL;
if (c == '{') {
c = pgetc_eatbnl();
- if (c == '#') {
- c = pgetc_eatbnl();
- if (c == '}')
- c = '#'; /* ${#} - same as $# */
- else
- subtype = VSLENGTH; /* ${#VAR} */
- } else {
- subtype = 0;
- }
+ subtype = 0;
}
- if (c <= 255 /* not PEOA or PEOF */ && is_name(c)) {
+ varname:
+ if (is_name(c)) {
/* $[{[#]]NAME[}] */
do {
STPUTC(c, out);
c = pgetc_eatbnl();
- } while (c <= 255 /* not PEOA or PEOF */ && is_in_name(c));
+ } while (is_in_name(c));
} else if (isdigit(c)) {
/* $[{[#]]NUM[}] */
do {
} while (isdigit(c));
} else if (is_special(c)) {
/* $[{[#]]<specialchar>[}] */
- USTPUTC(c, out);
+ int cc = c;
+
c = pgetc_eatbnl();
+ if (!subtype && cc == '#') {
+ subtype = VSLENGTH;
+ if (c == '_' || isalnum(c))
+ goto varname;
+ cc = c;
+ c = pgetc_eatbnl();
+ if (cc == '}' || c != '}') {
+ pungetc();
+ subtype = 0;
+ c = cc;
+ cc = '#';
+ }
+ }
+ USTPUTC(cc, out);
} else {
- badsub:
- raise_error_syntax("bad substitution");
+ goto badsub;
}
if (c != '}' && subtype == VSLENGTH) {
/* ${#VAR didn't end with } */
goto badsub;
}
- STPUTC('=', out);
- flags = 0;
if (subtype == 0) {
static const char types[] ALIGN1 = "}-+?=";
/* ${VAR...} but not $VAR or ${#VAR} */
if (!strchr(types, c)) {
subtype = VSSUBSTR;
pungetc();
- break; /* "goto do_pungetc" is bigger (!) */
+ break; /* "goto badsub" is bigger (!) */
}
#endif
- flags = VSNUL;
+ subtype = VSNUL;
/*FALLTHROUGH*/
default: {
const char *p = strchr(types, c);
if (p == NULL)
- goto badsub;
- subtype = p - types + VSNORMAL;
+ break;
+ subtype |= p - types + VSNORMAL;
break;
}
case '%':
subtype = (c == '#' ? VSTRIMLEFT : VSTRIMRIGHT);
c = pgetc_eatbnl();
if (c != cc)
- goto do_pungetc;
+ goto badsub;
subtype++;
break;
}
subtype = VSREPLACE;
c = pgetc_eatbnl();
if (c != '/')
- goto do_pungetc;
+ goto badsub;
subtype++; /* VSREPLACEALL */
break;
#endif
}
} else {
- do_pungetc:
+ badsub:
pungetc();
}
- ((unsigned char *)stackblock())[typeloc] = subtype | flags;
+ ((unsigned char *)stackblock())[typeloc] = subtype;
if (subtype != VSNORMAL) {
varnest++;
- if (dblquote) {
+ if (dblquote)
dqvarnest++;
- }
}
+ STPUTC('=', out);
}
goto parsesub_return;
}
case '\\':
pc = pgetc();
if (pc == '\n') {
- g_parsefile->linno++;
- setprompt_if(doprompt, 2);
+ nlprompt();
/*
* If eating a newline, avoid putting
* the newline into the new character
raise_error_syntax("EOF in backquote substitution");
case '\n':
- g_parsefile->linno++;
- needprompt = doprompt;
+ nlnoprompt();
break;
default:
pungetc();
break; /* return readtoken1(...) */
}
- startlinno = ++g_parsefile->linno;
- setprompt_if(doprompt, 2);
+ nlprompt();
} else {
const char *p;
p = xxreadtoken_chars + sizeof(xxreadtoken_chars) - 1;
if (c != PEOF) {
if (c == '\n') {
- g_parsefile->linno++;
- needprompt = doprompt;
+ nlnoprompt();
}
p = strchr(xxreadtoken_chars, c);
continue;
case '\\':
if (pgetc() == '\n') {
- startlinno = ++g_parsefile->linno;
- setprompt_if(doprompt, 2);
+ nlprompt();
continue;
}
pungetc();
goto breakloop;
case '\n':
- g_parsefile->linno++;
- needprompt = doprompt;
+ nlnoprompt();
RETURN(TNL);
case PEOF:
RETURN(TEOF);
pp = findkwd(wordtext);
if (pp) {
lasttoken = t = pp - tokname_array;
- TRACE(("keyword '%s' recognized\n", tokname_array[t] + 1));
+ TRACE(("keyword '%s' recognized\n", tokname_array[t]));
goto out;
}
}
checkkwd = 0;
#if DEBUG
if (!alreadyseen)
- TRACE(("token '%s' %s\n", tokname_array[t] + 1, t == TWORD ? wordtext : ""));
+ TRACE(("token '%s' %s\n", tokname_array[t], t == TWORD ? wordtext : ""));
else
- TRACE(("reread token '%s' %s\n", tokname_array[t] + 1, t == TWORD ? wordtext : ""));
+ TRACE(("reread token '%s' %s\n", tokname_array[t], t == TWORD ? wordtext : ""));
#endif
return t;
}
expandstr(const char *ps)
{
union node n;
+ int saveprompt;
/* XXX Fix (char *) cast. It _is_ a bug. ps is variable's value,
* and token processing _can_ alter it (delete NULs etc). */
setinputstring((char *)ps);
+
+ saveprompt = doprompt;
+ doprompt = 0;
readtoken1(pgetc(), PSSYNTAX, nullstr, 0);
+ doprompt = saveprompt;
+
popfile();
n.narg.type = NARG;
static int
evalstring(char *s, int flags)
{
+ struct jmploc *volatile savehandler;
+ struct jmploc jmploc;
+ int ex;
+
union node *n;
struct stackmark smark;
int status;
setstackmark(&smark);
status = 0;
+ /* On exception inside execution loop, we must popfile().
+ * Try interactively:
+ * readonly a=a
+ * command eval "a=b" # throws "is read only" error
+ * "command BLTIN" is not supposed to abort (even in non-interactive use).
+ * But if we skip popfile(), we hit EOF in eval's string, and exit.
+ */
+ savehandler = exception_handler;
+ ex = setjmp(jmploc.loc);
+ if (ex)
+ goto out;
+ exception_handler = &jmploc;
+
while ((n = parsecmd(0)) != NODE_EOF) {
int i;
if (evalskip)
break;
}
+ out:
popstackmark(&smark);
popfile();
stunalloc(s);
+ exception_handler = savehandler;
+ if (ex)
+ longjmp(exception_handler->loc, ex);
+
return status;
}
}
-/* ============ trap.c */
-
/*
* The trap builtin.
*/
if (action) {
if (LONE_DASH(action))
action = NULL;
- else
+ else {
+ if (action[0]) /* not NULL and not "" and not "-" */
+ may_have_traps = 1;
action = ckstrdup(action);
+ }
}
free(trap[signo]);
- if (action)
- may_have_traps = 1;
trap[signo] = action;
if (signo != 0)
setsignal(signo);
}
}
# endif
- out1fmt("\n\n");
+ newline_and_flush(stdout);
return EXIT_SUCCESS;
}
#endif
#if ENABLE_FEATURE_EDITING_SAVE_ON_EXIT
save_history(line_input_state);
#endif
-
status = exitstatus;
TRACE(("pid %d, exitshell(%d)\n", getpid(), status));
if (setjmp(loc.loc)) {
if (exception_type == EXEXIT)
-/* dash bug: it just does _exit(exitstatus) here
- * but we have to do setjobctl(0) first!
- * (bug is still not fixed in dash-0.5.3 - if you run dash
- * under Midnight Commander, on exit from dash MC is backgrounded) */
status = exitstatus;
goto out;
}
trap[0] = NULL;
evalskip = 0;
evalstring(p, 0);
- free(p);
+ /*free(p); - we'll exit soon */
}
- flush_stdout_stderr();
out:
+ /* 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);
/* NOTREACHED */
}
static void
init(void)
{
- /* from input.c: */
/* we will never free this */
basepf.next_to_pgetc = basepf.buf = ckmalloc(IBUFSIZ);
- /* from trap.c: */
- signal(SIGCHLD, SIG_DFL);
+ sigmode[SIGCHLD - 1] = S_DFL;
+ setsignal(SIGCHLD);
+
/* bash re-enables SIGHUP which is SIG_IGNed on entry.
* Try: "trap '' HUP; bash; echo RET" and type "kill -HUP $$"
*/
signal(SIGHUP, SIG_DFL);
- /* from var.c: */
{
char **envp;
const char *p;
}
/*
- * Read /etc/profile or .profile.
+ * Read /etc/profile, ~/.profile, $ENV.
*/
static void
read_profile(const char *name)
{
+ name = expandstr(name);
if (setinputfile(name, INPUT_PUSH_FILE | INPUT_NOFILE_OK) < 0)
return;
cmdloop(0);
/*
* This routine is called when an error or an interrupt occurs in an
* interactive shell and control is returned to the main command loop.
+ * (In dash, this function is auto-generated by build machinery).
*/
static void
reset(void)
/* from eval.c: */
evalskip = 0;
loopnest = 0;
+
+ /* from expand.c: */
+ ifsfree();
+
/* from input.c: */
g_parsefile->left_in_buffer = 0;
g_parsefile->left_in_line = 0; /* clear input buffer */
popallfiles();
- /* from parser.c: */
- tokpushback = 0;
- checkkwd = 0;
+
/* from redir.c: */
- clearredir(/*drop:*/ 0);
+ while (redirlist)
+ popredir(/*drop:*/ 0, /*restore:*/ 0);
}
#if PROFILE
int ash_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int ash_main(int argc UNUSED_PARAM, char **argv)
{
- const char *shinit;
volatile smallint state;
struct jmploc jmploc;
struct stackmark smark;
reset();
e = exception_type;
- if (e == EXERROR)
- exitstatus = 2;
s = state;
if (e == EXEXIT || s == 0 || iflag == 0 || shlvl) {
exitshell();
goto state4;
}
exception_handler = &jmploc;
-#if DEBUG
- opentrace();
- TRACE(("Shell args: "));
- trace_puts_args(argv);
-#endif
rootpid = getpid();
init();
setstackmark(&smark);
procargs(argv);
+#if DEBUG
+ TRACE(("Shell args: "));
+ trace_puts_args(argv);
+#endif
if (argv[0] && argv[0][0] == '-')
isloginsh = 1;
state1:
state = 2;
hp = lookupvar("HOME");
- if (hp) {
- hp = concat_path_file(hp, ".profile");
- read_profile(hp);
- free((char*)hp);
- }
+ if (hp)
+ read_profile("$HOME/.profile");
}
state2:
state = 3;
#endif
iflag
) {
- shinit = lookupvar("ENV");
- if (shinit != NULL && *shinit != '\0') {
+ const char *shinit = lookupvar("ENV");
+ if (shinit != NULL && *shinit != '\0')
read_profile(shinit);
- }
}
+ popstackmark(&smark);
state3:
state = 4;
if (minusc) {