pipeでforkしたプロセスとやり取りする


```cpp:a.cpp
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>

void dummy_handler(int) {
}

void set_sigaction(int sig) {
    struct sigaction sa;
    memset(&sa, 0, sizeof(struct sigaction));
    sa.sa_handler = &dummy_handler;
    sigemptyset(&sa.sa_mask);
    errno = 0;
    if (sigaction(sig, &sa, nullptr)) {
        printf("sigaction fail. %s\n", strerror(errno));
        exit(1);
    }
}

int main(int argc, char **argv) {
    
    set_sigaction(SIGHUP);
    set_sigaction(SIGINT);
    set_sigaction(SIGQUIT);
    set_sigaction(SIGTERM);
    
    sigset_t newss;
    sigemptyset(&newss);
    sigaddset(&newss, SIGCHLD);
    sigaddset(&newss, SIGUSR1);
    sigaddset(&newss, SIGALRM);
    sigaddset(&newss, SIGHUP);
    sigaddset(&newss, SIGINT);
    sigaddset(&newss, SIGQUIT);
    sigaddset(&newss, SIGTERM);
    sigprocmask(SIG_BLOCK, &newss, nullptr);
    
    int status = 0;
    int stdout_fd[2];
    int stderr_fd[2];
    
    if (pipe(stdout_fd) < 0) {
        printf("pipe fail. %s\n", strerror(errno));
        return 1;
    }
    if (pipe(stderr_fd) < 0) {
        printf("pipe fail. %s\n", strerror(errno));
        close(stdout_fd[0]);
        close(stdout_fd[1]);
        return 1;
    }
    
    pid_t pid = fork();
    if (pid < 0) {
        printf("fork fail. %s\n", strerror(errno));
        close(stdout_fd[0]);
        close(stdout_fd[1]);
        close(stderr_fd[0]);
        close(stderr_fd[1]);
        return 1;
    } else if (pid == 0) {
        // 子の読み込み側をクローズ。子は書くだけ。
        close(stdout_fd[0]);
        close(stderr_fd[0]);
        
        // 2本あるパイプのうち、どちらか（あるいは両方）が、STDOUTかSTDERRをすでに使っている場合、
        // 片方をdup2でつなぎなおすと、もう片方の親プロセスとつながっているパイプを閉じてしまう可能性がある。
        // なので、現状パイプでつながっている内容を、STDOUT、STDERR以外の値になるようにしてから、
        // 改めて、STDOUTとSTDERRにつなぎなおす。
        int basefd = stdout_fd[1] > stderr_fd[1] ? stdout_fd[1] : stderr_fd[1];
        int outfd = basefd + 3;
        int errfd = basefd + 4;
        dup2(stdout_fd[1], outfd); // outfdが開いていたとしても、一旦閉じてstdout_fd[1]の内容を指すようにopenする。
        dup2(stderr_fd[1], errfd); // errfdが開いていたとしても、一旦閉じてstderr_fd[1]の内容を指すようにopenする。
        close(stdout_fd[1]); // stdout_fd[1]は、これで不要。
        close(stderr_fd[1]); // stderr_fd[1]は、これで不要。
        dup2(outfd, 1); // 1が開いていたとしても、一旦閉じてoutfdの内容を指すようにopenする。
        dup2(errfd, 2); // 2が開いていたとしても、一旦閉じてerrfdの内容を指すようにopenする。
        close(outfd); // outfdは、これで不要。
        close(errfd); // errfdは、これで不要。
        
        // 上記のつなぎ変えの流れ
        // 子プロセス起動時に、stdout_fd[1]が1、stderr_fd[1]が3とした場合。
        // stdout_fd[1] = 1 -> outfd = 6 -> dup2(6, 1) ... 親のパイプ(stdout)を指すfdが、1 -> 6 -> 1というように変化する。
        // stderr_fd[1] = 3 -> errfd = 7 -> dup2(7, 2) ... 親のパイプ（stderr）を指すfdが、3 -> 7 -> 2というように変化する。
        // 親プロセス側で、fork前にfd6、fd7を何か開いていた場合でも、子プロセス側で閉じても親プロセス側に影響は無い。
        
        // 親から通知されるUSER1シグナルをチェック。USER1シグナルが来るまで待機。
        // 不測の事態が多発する場合、sigwaitでは無く、sigtimedwaitを使って一定時間で抜けたほうが良いかも知れない。
        sigset_t ss;
        sigemptyset(&ss);
        sigaddset(&ss, SIGUSR1);
        int sig;
        sigwait(&ss, &sig);
        
        // 適当な文字列を親プロセスに送る。
        for (int i = 0; i < 3; ++i) {
            for (int ii = 0; ii < 5; ++ii) {
                write(1, "abcabcabc\n", 10);
                write(2, "123123123\n", 10);
            }
        }
        
        // ここでexecしても、問題なく内容を受け取れる、はず。
        execlp("ps", "ps", "aux" , nullptr); 
        perror("execlp");
        _exit(1);
    }
    
    // 親の書き込み側をクローズ。親は読むだけ。
    close(stdout_fd[1]);
    close(stderr_fd[1]);
    
    // 親側準備完了。子に通知する。
    kill(pid, SIGUSR1);
    
    fd_set read_set_master;
    FD_ZERO(&read_set_master);
    FD_SET(stdout_fd[0], &read_set_master);
    FD_SET(stderr_fd[0], &read_set_master);
    struct timespec waitval;
    waitval.tv_sec = 10;
    waitval.tv_nsec = 0;
    fd_set read_set;
    int basefd = stdout_fd[0] > stderr_fd[0] ? stdout_fd[0] : stderr_fd[0];
    char buf[4096];
    
    std::string out;
    std::string err;
    
    // 終了用のシグナルを受けた場合は、pselectを抜けたい。
    // SIGCHILD、SIGUSER1、SIGALRMのブロックは継続しつつ、
    // SIGHUP、SIGINT、SIGQUIT、SIGTERMを受け付けたい。
    sigset_t ss;
    sigemptyset(&ss);
    sigaddset(&newss, SIGCHLD);
    sigaddset(&newss, SIGUSR1);
    sigaddset(&newss, SIGALRM);
    
    // 子からの応答を待つ。
    while (1) {
        memcpy(&read_set, &read_set_master, sizeof(read_set_master));
        int r = pselect(basefd + 1, &read_set, nullptr, nullptr, &waitval, &ss);
        if (r == 0) {
            printf("select timeout.\n");
            printf("kill. pid=%d, sig=%d\n", pid, SIGTERM);
            kill(pid, SIGTERM);
            break;
        } else if (r < 0) {
            printf("select fail. %s\n", strerror(errno));
            printf("kill. pid=%d, sig=%d\n", pid, SIGTERM);
            kill(pid, SIGTERM);
            break;
        }
        // 結構単純に、、、
        // stdout、stderrが両方0になったら、もう読むものなし、、で良さそう?
        bool end_flag = true;
        if (FD_ISSET(stdout_fd[0], &read_set)) {
            ssize_t ret = read(stdout_fd[0], buf, 4096);
            if(ret > 0) {
                end_flag = false;
                buf[ret] = '\0';
                out += buf;
            }
        }
        if (FD_ISSET(stderr_fd[0], &read_set)) {
            ssize_t ret = read(stderr_fd[0], buf, 4096);
            if(ret > 0) {
                end_flag = false;
                buf[ret] = '\0';
                err += buf;
            }
        }
        if (end_flag) {
            break;
        }
    }
    close(stdout_fd[0]);
    close(stderr_fd[0]);
    
    // 子の終了を待つ。
    int limit = 10;
    while (1) {
        pid_t p = waitpid(pid, &status, WNOHANG);
        if (p < 0) {
            status = -1;
            break;
        } else if (p == pid) {
            break;
        }
        if (limit-- == 0) {
            printf("kill. pid=%d, sig=%d\n", pid, SIGKILL);
            kill(pid, SIGKILL);
            continue;
        }
        sleep(1);
    }
    
    printf("%s\n", out.c_str());
    printf("%s\n", err.c_str());
    
    printf("out.size=%lu, err.size=%lu, status=%d\n", out.size(), err.size(), WIFEXITED(status) ? WEXITSTATUS(status) : -1);
    return 0;
}

```

stdoutとstderrの内容を得るために、
2本パイプを作って、良い感じにdupでつなぎかえる。

親側は、子が何か返してくるまでselectで停止する。（ノンブロッキングでループはしたくない）
無限に停止したくないので、timeoutで抜けられるようにする。
select中はシグナルを受け付けたいので、pselectで待つ。