Search code examples
linuxepollptracewaitpid

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?


Solution

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