ash: finally installed it as /bin/sh on my machine.

some breakage noticed, the most dire is mishandled ^C.
 fixing it.

function                                             old     new   delta
blocking_wait_with_raise_on_sig                        -      40     +40
waitforjob                                            85     100     +15
setsignal                                            280     278      -2
evalvar                                             1376    1374      -2
waitcmd                                              186     182      -4
dowait                                               350     316     -34
redirect                                            1231    1185     -46
------------------------------------------------------------------------------
(add/remove: 1/0 grow/shrink: 1/5 up/down: 55/-88)            Total: -33 bytes
This commit is contained in:
Denis Vlasenko 2008-12-03 10:36:26 +00:00
parent 0c68a874e7
commit f8535ccd65

View File

@ -201,14 +201,13 @@ struct globals_misc {
/* /*
* Sigmode records the current value of the signal handlers for the various * Sigmode records the current value of the signal handlers for the various
* modes. A value of zero means that the current handler is not known. * modes. A value of zero means that the current handler is not known.
* S_HARD_IGN indicates that the signal was ignored on entry to the shell, * S_HARD_IGN indicates that the signal was ignored on entry to the shell.
*/ */
char sigmode[NSIG - 1]; char sigmode[NSIG - 1];
#define S_DFL 1 /* default signal handling (SIG_DFL) */ #define S_DFL 1 /* default signal handling (SIG_DFL) */
#define S_CATCH 2 /* signal is caught */ #define S_CATCH 2 /* signal is caught */
#define S_IGN 3 /* signal is ignored (SIG_IGN) */ #define S_IGN 3 /* signal is ignored (SIG_IGN) */
#define S_HARD_IGN 4 /* signal is ignored permenantly */ #define S_HARD_IGN 4 /* signal is ignored permenantly */
#define S_RESET 5 /* temporary - to reset a hard ignored sig */
/* indicates specified signal received */ /* indicates specified signal received */
char gotsig[NSIG - 1]; /* offset by 1: "signal" 0 is meaningless */ char gotsig[NSIG - 1]; /* offset by 1: "signal" 0 is meaningless */
@ -368,7 +367,7 @@ force_int_on(void)
} while (0) } while (0)
/* /*
* Ignore a signal. Only one usage site - in forkchild() * Ignore a signal. Avoids unnecessary system calls.
*/ */
static void static void
ignoresig(int signo) ignoresig(int signo)
@ -3295,81 +3294,90 @@ static void setjobctl(int);
static void static void
setsignal(int signo) setsignal(int signo)
{ {
int action; char *t;
char *t, tsig; char cur_act, new_act;
struct sigaction act; struct sigaction act;
t = trap[signo]; t = trap[signo];
action = S_IGN; new_act = S_DFL;
if (t == NULL) if (t != NULL) { /* trap for this sig is set */
action = S_DFL; new_act = S_CATCH;
else if (*t != '\0') if (t[0] == '\0') /* trap is "": ignore this sig */
action = S_CATCH; new_act = S_IGN;
if (rootshell && action == S_DFL) { }
if (rootshell && new_act == S_DFL) {
switch (signo) { switch (signo) {
case SIGINT: case SIGINT:
if (iflag || minusc || sflag == 0) if (iflag || minusc || sflag == 0)
action = S_CATCH; new_act = S_CATCH;
break; break;
case SIGQUIT: case SIGQUIT:
#if DEBUG #if DEBUG
if (debug) if (debug)
break; break;
#endif #endif
/* FALLTHROUGH */ /* man bash:
* "In all cases, bash ignores SIGQUIT. Non-builtin
* commands run by bash have signal handlers
* set to the values inherited by the shell
* from its parent". */
new_act = S_IGN;
break;
case SIGTERM: case SIGTERM:
if (iflag) if (iflag)
action = S_IGN; new_act = S_IGN;
break; break;
#if JOBS #if JOBS
case SIGTSTP: case SIGTSTP:
case SIGTTOU: case SIGTTOU:
if (mflag) if (mflag)
action = S_IGN; new_act = S_IGN;
break; break;
#endif #endif
} }
} }
//TODO: if !rootshell, we reset SIGQUIT to DFL,
//whereas we have to restore it to what shell got on entry
//from the parent. See comment above
t = &sigmode[signo - 1]; t = &sigmode[signo - 1];
tsig = *t; cur_act = *t;
if (tsig == 0) { if (cur_act == 0) {
/* /* current setting is not yet known */
* current setting unknown if (sigaction(signo, NULL, &act)) {
*/ /* pretend it worked; maybe we should give a warning,
if (sigaction(signo, NULL, &act) == -1) { * but other shells don't. We don't alter sigmode,
/* * so we retry every time.
* Pretend it worked; maybe we should give a warning * btw, in Linux it never fails. --vda */
* here, but other shells don't. We don't alter
* sigmode, so that we retry every time.
*/
return; return;
} }
tsig = S_RESET; /* force to be set */
if (act.sa_handler == SIG_IGN) { if (act.sa_handler == SIG_IGN) {
tsig = S_HARD_IGN; cur_act = S_HARD_IGN;
if (mflag if (mflag
&& (signo == SIGTSTP || signo == SIGTTIN || signo == SIGTTOU) && (signo == SIGTSTP || signo == SIGTTIN || signo == SIGTTOU)
) { ) {
tsig = S_IGN; /* don't hard ignore these */ cur_act = S_IGN; /* don't hard ignore these */
} }
} }
} }
if (tsig == S_HARD_IGN || tsig == action) if (cur_act == S_HARD_IGN || cur_act == new_act)
return; return;
act.sa_handler = SIG_DFL; act.sa_handler = SIG_DFL;
switch (action) { switch (new_act) {
case S_CATCH: case S_CATCH:
act.sa_handler = onsig; act.sa_handler = onsig;
act.sa_flags = 0; /* matters only if !DFL and !IGN */
sigfillset(&act.sa_mask); /* ditto */
break; break;
case S_IGN: case S_IGN:
act.sa_handler = SIG_IGN; act.sa_handler = SIG_IGN;
break; break;
} }
*t = action;
act.sa_flags = 0;
sigfillset(&act.sa_mask);
sigaction_set(signo, &act); sigaction_set(signo, &act);
*t = new_act;
} }
/* mode flags for set_curjob */ /* mode flags for set_curjob */
@ -3790,15 +3798,9 @@ dowait(int wait_flags, struct job *job)
pid = waitpid(-1, &status, pid = waitpid(-1, &status,
(doing_jobctl ? (wait_flags | WUNTRACED) : wait_flags)); (doing_jobctl ? (wait_flags | WUNTRACED) : wait_flags));
TRACE(("wait returns pid=%d, status=0x%x\n", pid, status)); TRACE(("wait returns pid=%d, status=0x%x\n", pid, status));
if (pid <= 0)
if (pid <= 0) {
/* If we were doing blocking wait and (probably) got EINTR,
* check for pending sigs received while waiting.
* (NB: can be moved into callers if needed) */
if (wait_flags == DOWAIT_BLOCK && pendingsig)
raise_exception(EXSIG);
return pid; return pid;
}
INT_OFF; INT_OFF;
thisjob = NULL; thisjob = NULL;
for (jp = curjob; jp; jp = jp->prev_job) { for (jp = curjob; jp; jp = jp->prev_job) {
@ -3870,6 +3872,15 @@ dowait(int wait_flags, struct job *job)
return pid; return pid;
} }
static int
blocking_wait_with_raise_on_sig(struct job *job)
{
pid_t pid = dowait(DOWAIT_BLOCK, job);
if (pid <= 0 && pendingsig)
raise_exception(EXSIG);
return pid;
}
#if JOBS #if JOBS
static void static void
showjob(FILE *out, struct job *jp, int mode) showjob(FILE *out, struct job *jp, int mode)
@ -3949,7 +3960,7 @@ showjobs(FILE *out, int mode)
TRACE(("showjobs(%x) called\n", mode)); TRACE(("showjobs(%x) called\n", mode));
/* If not even one job changed, there is nothing to do */ /* Handle all finished jobs */
while (dowait(DOWAIT_NONBLOCK, NULL) > 0) while (dowait(DOWAIT_NONBLOCK, NULL) > 0)
continue; continue;
@ -4041,7 +4052,14 @@ waitcmd(int argc UNUSED_PARAM, char **argv)
jp->waited = 1; jp->waited = 1;
jp = jp->prev_job; jp = jp->prev_job;
} }
dowait(DOWAIT_BLOCK, NULL); /* 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."
* Do we do it that way? */
blocking_wait_with_raise_on_sig(NULL);
} }
} }
@ -4061,11 +4079,10 @@ waitcmd(int argc UNUSED_PARAM, char **argv)
job = getjob(*argv, 0); job = getjob(*argv, 0);
/* loop until process terminated or stopped */ /* loop until process terminated or stopped */
while (job->state == JOBRUNNING) while (job->state == JOBRUNNING)
dowait(DOWAIT_BLOCK, NULL); blocking_wait_with_raise_on_sig(NULL);
job->waited = 1; job->waited = 1;
retval = getstatus(job); retval = getstatus(job);
repeat: repeat: ;
;
} while (*++argv); } while (*++argv);
ret: ret:
@ -4492,6 +4509,10 @@ forkchild(struct job *jp, /*union node *n,*/ int mode)
oldlvl = shlvl; oldlvl = shlvl;
shlvl++; shlvl++;
/* man bash: "Non-builtin commands run by bash have signal handlers
* set to the values inherited by the shell from its parent".
* Do we do it correctly? */
closescript(); closescript();
clear_traps(); clear_traps();
#if JOBS #if JOBS
@ -4504,8 +4525,8 @@ forkchild(struct job *jp, /*union node *n,*/ int mode)
pgrp = getpid(); pgrp = getpid();
else else
pgrp = jp->ps[0].pid; pgrp = jp->ps[0].pid;
/* This can fail because we are doing it in the parent also */ /* this can fail because we are doing it in the parent also */
(void)setpgid(0, pgrp); setpgid(0, pgrp);
if (mode == FORK_FG) if (mode == FORK_FG)
xtcsetpgrp(ttyfd, pgrp); xtcsetpgrp(ttyfd, pgrp);
setsignal(SIGTSTP); setsignal(SIGTSTP);
@ -4513,6 +4534,8 @@ forkchild(struct job *jp, /*union node *n,*/ int mode)
} else } else
#endif #endif
if (mode == FORK_BG) { if (mode == FORK_BG) {
/* man bash: "When job control is not in effect,
* asynchronous commands ignore SIGINT and SIGQUIT" */
ignoresig(SIGINT); ignoresig(SIGINT);
ignoresig(SIGQUIT); ignoresig(SIGQUIT);
if (jp->nprocs == 0) { if (jp->nprocs == 0) {
@ -4521,10 +4544,18 @@ forkchild(struct job *jp, /*union node *n,*/ int mode)
ash_msg_and_raise_error("can't open %s", bb_dev_null); ash_msg_and_raise_error("can't open %s", bb_dev_null);
} }
} }
if (!oldlvl && iflag) { if (!oldlvl) {
setsignal(SIGINT); if (iflag) { /* why if iflag only? */
setsignal(SIGINT);
setsignal(SIGTERM);
}
/* man bash:
* "In all cases, bash ignores SIGQUIT. Non-builtin
* commands run by bash have signal handlers
* set to the values inherited by the shell
* from its parent".
* Take care of the second rule: */
setsignal(SIGQUIT); setsignal(SIGQUIT);
setsignal(SIGTERM);
} }
for (jp = curjob; jp; jp = jp->prev_job) for (jp = curjob; jp; jp = jp->prev_job)
freejob(jp); freejob(jp);
@ -4596,12 +4627,12 @@ forkshell(struct job *jp, union node *n, int mode)
/* /*
* Wait for job to finish. * Wait for job to finish.
* *
* Under job control we have the problem that while a child process is * Under job control we have the problem that while a child process
* running interrupts generated by the user are sent to the child but not * is running interrupts generated by the user are sent to the child
* to the shell. This means that an infinite loop started by an inter- * but not to the shell. This means that an infinite loop started by
* active user may be hard to kill. With job control turned off, an * an interactive user may be hard to kill. With job control turned off,
* interactive user may place an interactive program inside a loop. If * an interactive user may place an interactive program inside a loop.
* the interactive program catches interrupts, the user doesn't want * If the interactive program catches interrupts, the user doesn't want
* these interrupts to also abort the loop. The approach we take here * these interrupts to also abort the loop. The approach we take here
* is to have the shell ignore interrupt signals while waiting for a * is to have the shell ignore interrupt signals while waiting for a
* foreground process to terminate, and then send itself an interrupt * foreground process to terminate, and then send itself an interrupt
@ -4619,9 +4650,44 @@ waitforjob(struct job *jp)
int st; int st;
TRACE(("waitforjob(%%%d) called\n", jobno(jp))); TRACE(("waitforjob(%%%d) called\n", jobno(jp)));
INT_OFF;
while (jp->state == JOBRUNNING) { 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
* $ _
* TODO: we do not execute "echo hi" as bash does:
* $ bash -c './sleep5intoff; echo hi'
* ^\^\^\^\hi <--- pressing ^\ (SIGQUIT)
* $ _
*/
dowait(DOWAIT_BLOCK, jp); dowait(DOWAIT_BLOCK, jp);
} }
INT_ON;
st = getstatus(jp); st = getstatus(jp);
#if JOBS #if JOBS
if (jp->jobctl) { if (jp->jobctl) {
@ -4757,12 +4823,10 @@ openhere(union node *redir)
if (forkshell((struct job *)NULL, (union node *)NULL, FORK_NOJOB) == 0) { if (forkshell((struct job *)NULL, (union node *)NULL, FORK_NOJOB) == 0) {
/* child */ /* child */
close(pip[0]); close(pip[0]);
signal(SIGINT, SIG_IGN); ignoresig(SIGINT); //signal(SIGINT, SIG_IGN);
signal(SIGQUIT, SIG_IGN); ignoresig(SIGQUIT); //signal(SIGQUIT, SIG_IGN);
signal(SIGHUP, SIG_IGN); ignoresig(SIGHUP); //signal(SIGHUP, SIG_IGN);
#ifdef SIGTSTP ignoresig(SIGTSTP); //signal(SIGTSTP, SIG_IGN);
signal(SIGTSTP, SIG_IGN);
#endif
signal(SIGPIPE, SIG_DFL); signal(SIGPIPE, SIG_DFL);
if (redir->type == NHERE) if (redir->type == NHERE)
full_write(pip[1], redir->nhere.doc->narg.text, len); full_write(pip[1], redir->nhere.doc->narg.text, len);