#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 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
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);
if (flags & VNOSAVE)
free(s);
n = vp->var_text;
+ exitstatus = 1;
ash_msg_and_raise_error("%.*s: is read only", strchrnul(n, '=') - n, n);
}
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 0
-#define DOWAIT_BLOCK 1
-
#if JOBS
/* pgrp of shell on invocation */
static int initialpgrp; //references:2
return col;
}
+static int
+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 wait_flags;
int pid;
int status;
struct job *jp;
- struct job *thisjob;
+ struct job *thisjob = NULL;
TRACE(("dowait(0x%x) called\n", block));
- /* Do a wait system call. If job control is compiled in, we accept
- * stopped processes.
- * NB: _not_ safe_waitpid, we need to detect EINTR.
+ /* 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().
*/
- wait_flags = 0;
- if (block == DOWAIT_NONBLOCK)
- wait_flags = WNOHANG;
- if (doing_jobctl)
- wait_flags |= WUNTRACED;
- pid = waitpid(-1, &status, wait_flags);
+ 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;
* with an exit status greater than 128, immediately after which
* the trap is executed."
*/
- dowait(DOWAIT_BLOCK, NULL); ///DOWAIT_WAITCMD
+ 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)"!
}
/* loop until process terminated or stopped */
while (job->state == JOBRUNNING) {
- dowait(DOWAIT_BLOCK, NULL); ///DOWAIT_WAITCMD
+ dowait(DOWAIT_BLOCK_OR_SIG, NULL);
if (pending_sig)
goto sigout;
}
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
/* 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;
}
if (evalbltin(cmdentry.u.cmd, argc, argv, flags)) {
if (exception_type == EXERROR && spclbltin <= 0) {
FORCE_INT_ON;
- break;
+ goto readstatus;
}
raise:
longjmp(exception_handler->loc, 1);
{
struct parsefile *pf = g_parsefile;
+ if (pf == &basepf)
+ return;
+
INT_OFF;
if (pf->pf_fd >= 0)
close(pf->pf_fd);
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;
}
/* we will never free this */
basepf.next_to_pgetc = basepf.buf = ckmalloc(IBUFSIZ);
- 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 $$"
*/
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;