shapaz
shapaz

Reputation: 43

How can I wait for both a file-descriptor and child state change simultanously?

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?

Upvotes: 1

Views: 1088

Answers (1)

dimich
dimich

Reputation: 1918

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);

Upvotes: 4

Related Questions