sh: Fix EINTR race condition in "wait" and "set -T" using sigsuspend().
When waiting for child processes using "wait" or if "set -T" is in effect, a signal interrupts the wait. Make sure there is no window where the signal handler may be invoked (setting a flag) just before going to sleep. There is a similar race condition in the shell language, but scripts can avoid it by exiting from the trap handler or enforcing synchronization using a fifo. If SIGCHLD is not trapped, a signal handler must be installed for it. Only install this handler for the duration of the wait to avoid triggering unexpected [EINTR] errors elsewhere. Note that for some reason only SIGINT and SIGQUIT interrupt a "wait" command. This remains the case.
This commit is contained in:
parent
767a02fb40
commit
1794251add
@ -87,6 +87,10 @@ int in_waitcmd = 0; /* are we in waitcmd()? */
|
|||||||
volatile sig_atomic_t breakwaitcmd = 0; /* should wait be terminated? */
|
volatile sig_atomic_t breakwaitcmd = 0; /* should wait be terminated? */
|
||||||
static int ttyfd = -1;
|
static int ttyfd = -1;
|
||||||
|
|
||||||
|
/* mode flags for dowait */
|
||||||
|
#define DOWAIT_BLOCK 0x1 /* wait until a child exits */
|
||||||
|
#define DOWAIT_SIG 0x2 /* if DOWAIT_BLOCK, abort on signals */
|
||||||
|
|
||||||
#if JOBS
|
#if JOBS
|
||||||
static void restartjob(struct job *);
|
static void restartjob(struct job *);
|
||||||
#endif
|
#endif
|
||||||
@ -518,7 +522,7 @@ waitcmd(int argc, char **argv)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} while (dowait(1, (struct job *)NULL) != -1);
|
} while (dowait(DOWAIT_BLOCK | DOWAIT_SIG, (struct job *)NULL) != -1);
|
||||||
in_waitcmd--;
|
in_waitcmd--;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@ -965,7 +969,7 @@ waitforjob(struct job *jp, int *origstatus)
|
|||||||
INTOFF;
|
INTOFF;
|
||||||
TRACE(("waitforjob(%%%td) called\n", jp - jobtab + 1));
|
TRACE(("waitforjob(%%%td) called\n", jp - jobtab + 1));
|
||||||
while (jp->state == 0)
|
while (jp->state == 0)
|
||||||
if (dowait(1, jp) == -1)
|
if (dowait(DOWAIT_BLOCK | (Tflag ? DOWAIT_SIG : 0), jp) == -1)
|
||||||
dotrap();
|
dotrap();
|
||||||
#if JOBS
|
#if JOBS
|
||||||
if (jp->jobctl) {
|
if (jp->jobctl) {
|
||||||
@ -1003,14 +1007,20 @@ waitforjob(struct job *jp, int *origstatus)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
dummy_handler(int sig)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Wait for a process to terminate.
|
* Wait for a process to terminate.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static pid_t
|
static pid_t
|
||||||
dowait(int block, struct job *job)
|
dowait(int mode, struct job *job)
|
||||||
{
|
{
|
||||||
|
struct sigaction sa, osa;
|
||||||
|
sigset_t mask, omask;
|
||||||
pid_t pid;
|
pid_t pid;
|
||||||
int status;
|
int status;
|
||||||
struct procstat *sp;
|
struct procstat *sp;
|
||||||
@ -1021,8 +1031,22 @@ dowait(int block, struct job *job)
|
|||||||
int sig;
|
int sig;
|
||||||
int coredump;
|
int coredump;
|
||||||
int wflags;
|
int wflags;
|
||||||
|
int restore_sigchld;
|
||||||
|
|
||||||
TRACE(("dowait(%d) called\n", block));
|
TRACE(("dowait(%d) called\n", block));
|
||||||
|
restore_sigchld = 0;
|
||||||
|
if ((mode & DOWAIT_SIG) != 0) {
|
||||||
|
sigfillset(&mask);
|
||||||
|
sigprocmask(SIG_BLOCK, &mask, &omask);
|
||||||
|
INTOFF;
|
||||||
|
if (!issigchldtrapped()) {
|
||||||
|
restore_sigchld = 1;
|
||||||
|
sa.sa_handler = dummy_handler;
|
||||||
|
sa.sa_flags = 0;
|
||||||
|
sigemptyset(&sa.sa_mask);
|
||||||
|
sigaction(SIGCHLD, &sa, &osa);
|
||||||
|
}
|
||||||
|
}
|
||||||
do {
|
do {
|
||||||
#if JOBS
|
#if JOBS
|
||||||
if (iflag)
|
if (iflag)
|
||||||
@ -1030,13 +1054,25 @@ dowait(int block, struct job *job)
|
|||||||
else
|
else
|
||||||
#endif
|
#endif
|
||||||
wflags = 0;
|
wflags = 0;
|
||||||
if (block == 0)
|
if ((mode & (DOWAIT_BLOCK | DOWAIT_SIG)) != DOWAIT_BLOCK)
|
||||||
wflags |= WNOHANG;
|
wflags |= WNOHANG;
|
||||||
pid = wait3(&status, wflags, (struct rusage *)NULL);
|
pid = wait3(&status, wflags, (struct rusage *)NULL);
|
||||||
TRACE(("wait returns %d, status=%d\n", (int)pid, status));
|
TRACE(("wait returns %d, status=%d\n", (int)pid, status));
|
||||||
|
if (pid == 0 && (mode & DOWAIT_SIG) != 0) {
|
||||||
|
sigsuspend(&omask);
|
||||||
|
pid = -1;
|
||||||
|
if (int_pending())
|
||||||
|
break;
|
||||||
|
}
|
||||||
} while (pid == -1 && errno == EINTR && breakwaitcmd == 0);
|
} while (pid == -1 && errno == EINTR && breakwaitcmd == 0);
|
||||||
if (pid == -1 && errno == ECHILD && job != NULL)
|
if (pid == -1 && errno == ECHILD && job != NULL)
|
||||||
job->state = JOBDONE;
|
job->state = JOBDONE;
|
||||||
|
if ((mode & DOWAIT_SIG) != 0) {
|
||||||
|
if (restore_sigchld)
|
||||||
|
sigaction(SIGCHLD, &osa, NULL);
|
||||||
|
sigprocmask(SIG_SETMASK, &omask, NULL);
|
||||||
|
INTON;
|
||||||
|
}
|
||||||
if (breakwaitcmd != 0) {
|
if (breakwaitcmd != 0) {
|
||||||
breakwaitcmd = 0;
|
breakwaitcmd = 0;
|
||||||
if (pid <= 0)
|
if (pid <= 0)
|
||||||
|
@ -368,6 +368,14 @@ ignoresig(int signo)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int
|
||||||
|
issigchldtrapped(void)
|
||||||
|
{
|
||||||
|
|
||||||
|
return (trap[SIGCHLD] != NULL && *trap[SIGCHLD] != '\0');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Signal handler.
|
* Signal handler.
|
||||||
*/
|
*/
|
||||||
|
@ -41,6 +41,7 @@ void clear_traps(void);
|
|||||||
int have_traps(void);
|
int have_traps(void);
|
||||||
void setsignal(int);
|
void setsignal(int);
|
||||||
void ignoresig(int);
|
void ignoresig(int);
|
||||||
|
int issigchldtrapped(void);
|
||||||
void onsig(int);
|
void onsig(int);
|
||||||
void dotrap(void);
|
void dotrap(void);
|
||||||
void setinteractive(int);
|
void setinteractive(int);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user