From f8535ccd65d21db9762be8715a6107889a50977f Mon Sep 17 00:00:00 2001 From: Denis Vlasenko Date: Wed, 3 Dec 2008 10:36:26 +0000 Subject: [PATCH] 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 --- shell/ash.c | 198 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 131 insertions(+), 67 deletions(-) diff --git a/shell/ash.c b/shell/ash.c index 9eff1b38e..913779ab4 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -201,14 +201,13 @@ struct globals_misc { /* * 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. - * 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]; -#define S_DFL 1 /* default signal handling (SIG_DFL) */ -#define S_CATCH 2 /* signal is caught */ -#define S_IGN 3 /* signal is ignored (SIG_IGN) */ +#define S_DFL 1 /* default signal handling (SIG_DFL) */ +#define S_CATCH 2 /* signal is caught */ +#define S_IGN 3 /* signal is ignored (SIG_IGN) */ #define S_HARD_IGN 4 /* signal is ignored permenantly */ -#define S_RESET 5 /* temporary - to reset a hard ignored sig */ /* indicates specified signal received */ char gotsig[NSIG - 1]; /* offset by 1: "signal" 0 is meaningless */ @@ -368,7 +367,7 @@ force_int_on(void) } while (0) /* - * Ignore a signal. Only one usage site - in forkchild() + * Ignore a signal. Avoids unnecessary system calls. */ static void ignoresig(int signo) @@ -3295,81 +3294,90 @@ static void setjobctl(int); static void setsignal(int signo) { - int action; - char *t, tsig; + char *t; + char cur_act, new_act; struct sigaction act; t = trap[signo]; - action = S_IGN; - if (t == NULL) - action = S_DFL; - else if (*t != '\0') - action = S_CATCH; - if (rootshell && action == S_DFL) { + new_act = S_DFL; + if (t != NULL) { /* trap for this sig is set */ + new_act = S_CATCH; + if (t[0] == '\0') /* trap is "": ignore this sig */ + new_act = S_IGN; + } + + if (rootshell && new_act == S_DFL) { switch (signo) { case SIGINT: if (iflag || minusc || sflag == 0) - action = S_CATCH; + new_act = S_CATCH; break; case SIGQUIT: #if DEBUG if (debug) break; #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: if (iflag) - action = S_IGN; + new_act = S_IGN; break; #if JOBS case SIGTSTP: case SIGTTOU: if (mflag) - action = S_IGN; + new_act = S_IGN; break; #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]; - tsig = *t; - if (tsig == 0) { - /* - * current setting unknown - */ - if (sigaction(signo, NULL, &act) == -1) { - /* - * Pretend it worked; maybe we should give a warning - * here, but other shells don't. We don't alter - * sigmode, so that we retry every time. - */ + cur_act = *t; + if (cur_act == 0) { + /* current setting is not yet known */ + if (sigaction(signo, NULL, &act)) { + /* pretend it worked; maybe we should give a warning, + * but other shells don't. We don't alter sigmode, + * so we retry every time. + * btw, in Linux it never fails. --vda */ return; } - tsig = S_RESET; /* force to be set */ if (act.sa_handler == SIG_IGN) { - tsig = S_HARD_IGN; + cur_act = S_HARD_IGN; if (mflag && (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; + act.sa_handler = SIG_DFL; - switch (action) { + switch (new_act) { case S_CATCH: act.sa_handler = onsig; + act.sa_flags = 0; /* matters only if !DFL and !IGN */ + sigfillset(&act.sa_mask); /* ditto */ break; case S_IGN: act.sa_handler = SIG_IGN; break; } - *t = action; - act.sa_flags = 0; - sigfillset(&act.sa_mask); sigaction_set(signo, &act); + + *t = new_act; } /* mode flags for set_curjob */ @@ -3790,15 +3798,9 @@ dowait(int wait_flags, struct job *job) pid = waitpid(-1, &status, (doing_jobctl ? (wait_flags | WUNTRACED) : wait_flags)); TRACE(("wait returns pid=%d, status=0x%x\n", pid, status)); - - 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); + if (pid <= 0) return pid; - } + INT_OFF; thisjob = NULL; for (jp = curjob; jp; jp = jp->prev_job) { @@ -3870,6 +3872,15 @@ dowait(int wait_flags, struct job *job) 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 static void showjob(FILE *out, struct job *jp, int mode) @@ -3949,7 +3960,7 @@ showjobs(FILE *out, int 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) continue; @@ -4041,7 +4052,14 @@ waitcmd(int argc UNUSED_PARAM, char **argv) jp->waited = 1; 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); /* loop until process terminated or stopped */ while (job->state == JOBRUNNING) - dowait(DOWAIT_BLOCK, NULL); + blocking_wait_with_raise_on_sig(NULL); job->waited = 1; retval = getstatus(job); - repeat: - ; + repeat: ; } while (*++argv); ret: @@ -4492,6 +4509,10 @@ forkchild(struct job *jp, /*union node *n,*/ int mode) oldlvl = 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(); clear_traps(); #if JOBS @@ -4504,8 +4525,8 @@ forkchild(struct job *jp, /*union node *n,*/ int mode) pgrp = getpid(); else pgrp = jp->ps[0].pid; - /* This can fail because we are doing it in the parent also */ - (void)setpgid(0, pgrp); + /* this can fail because we are doing it in the parent also */ + setpgid(0, pgrp); if (mode == FORK_FG) xtcsetpgrp(ttyfd, pgrp); setsignal(SIGTSTP); @@ -4513,6 +4534,8 @@ forkchild(struct job *jp, /*union node *n,*/ int mode) } else #endif if (mode == FORK_BG) { + /* man bash: "When job control is not in effect, + * asynchronous commands ignore SIGINT and SIGQUIT" */ ignoresig(SIGINT); ignoresig(SIGQUIT); 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); } } - if (!oldlvl && iflag) { - setsignal(SIGINT); + if (!oldlvl) { + 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(SIGTERM); } for (jp = curjob; jp; jp = jp->prev_job) freejob(jp); @@ -4596,12 +4627,12 @@ forkshell(struct job *jp, union node *n, int mode) /* * Wait for job to finish. * - * Under job control we have the problem that while a child process is - * running interrupts generated by the user are sent to the child but not - * to the shell. This means that an infinite loop started by an inter- - * active user may be hard to kill. With job control turned off, an - * interactive user may place an interactive program inside a loop. If - * the interactive program catches interrupts, the user doesn't want + * Under job control we have the problem that while a child process + * is running interrupts generated by the user are sent to the child + * but not to the shell. This means that an infinite loop started by + * an interactive user may be hard to kill. With job control turned off, + * an interactive user may place an interactive program inside a loop. + * If the interactive program catches interrupts, the user doesn't want * these interrupts to also abort the loop. The approach we take here * is to have the shell ignore interrupt signals while waiting for a * foreground process to terminate, and then send itself an interrupt @@ -4619,9 +4650,44 @@ waitforjob(struct job *jp) int st; TRACE(("waitforjob(%%%d) called\n", jobno(jp))); + + 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 + * #include + * 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); } + INT_ON; + st = getstatus(jp); #if JOBS if (jp->jobctl) { @@ -4757,12 +4823,10 @@ openhere(union node *redir) if (forkshell((struct job *)NULL, (union node *)NULL, FORK_NOJOB) == 0) { /* child */ close(pip[0]); - signal(SIGINT, SIG_IGN); - signal(SIGQUIT, SIG_IGN); - signal(SIGHUP, SIG_IGN); -#ifdef SIGTSTP - signal(SIGTSTP, SIG_IGN); -#endif + ignoresig(SIGINT); //signal(SIGINT, SIG_IGN); + ignoresig(SIGQUIT); //signal(SIGQUIT, SIG_IGN); + ignoresig(SIGHUP); //signal(SIGHUP, SIG_IGN); + ignoresig(SIGTSTP); //signal(SIGTSTP, SIG_IGN); signal(SIGPIPE, SIG_DFL); if (redir->type == NHERE) full_write(pip[1], redir->nhere.doc->narg.text, len); -- 2.25.1