In Linux, one can wait on any FD using select
, poll
or epoll
.
It is also possible to wait for child-processes to change state using wait
, waitpid
or waitid
.
However, I can't figure a way to combine these operations, i.e., to block the calling process until either some FD becomes ready or a child process changes state.
I can use polling, by repeatedly calling non-blocking epoll
then waitid
, but that is wasteful.
It is possible to create a pidfd
for a child process (which is accepted by epoll
), but pidfd
only supports waiting for child termination, while I wish to wait for any state change (specifically, for ptrace stops).
Is this not possible in Linux?
You can wait for any child status change with signalfd() and make dummy read, then get actual status with waitpid():
sigset_t mask, old_set;
sigemptyset(&mask);
sigaddset(&mask, SIGCHLD);
sigprocmask(SIG_BLOCK, &mask, &old_set);
int sigfd = signalfd(-1, &mask, SFD_CLOEXEC);
if (sigfd == -1) {
perror("signalfd");
return 1;
}
for (int i = 0; i < 10; ++i) {
pid_t pid = fork();
if (pid == -1) {
perror("fork");
}
if (pid == 0) {
// Child process: restore blocked signals before exec() etc
sigprocmask(SIG_SETMASK, &old_set, NULL);
sleep(i % 3);
switch (i % 3) {
case 0:
raise(SIGSTOP);
break;
case 1:
raise(SIGABRT);
break;
}
exit(i);
}
printf("Spawned child %i with pid %u\n", i, pid);
}
for (;;) {
struct pollfd fds[] = {
{ .fd = STDIN_FILENO, .events = POLL_IN },
{ .fd = sigfd, .events = POLL_IN }
};
if (poll(fds, sizeof(fds)/sizeof(*fds), -1) == -1) {
perror("poll");
break;
}
if (fds[0].revents & POLL_IN) {
char buf[4096];
int ret = read(STDIN_FILENO, buf, sizeof(buf));
printf("Data from stdin: ");
fflush(stdout);
write(STDOUT_FILENO, buf, ret);
}
if (fds[1].revents & POLL_IN)
{
struct signalfd_siginfo fdsi;
read(sigfd, &fdsi, sizeof(fdsi));
for (;;) {
int status;
pid_t pid = waitpid(-1, &status, WNOHANG | WUNTRACED | WCONTINUED);
if (pid == -1) {
if (errno != ECHILD) {
perror("waitpid");
}
break;
}
if (pid == 0) {
break;
}
printf("Child %u ", pid);
if (WIFEXITED(status)) {
printf("exited with status %i\n", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
printf("terminated by signal %i\n", WTERMSIG(status));
} else if (WIFSTOPPED(status)) {
printf("stopped by signal %i\n", WSTOPSIG(status));
} else if (WIFCONTINUED(status)) {
printf("continued\n");
} else {
printf("status unknown\n");
}
}
}
}
close(sigfd);