Home > Blockchain >  How to get the error stream pipe of the child process?
How to get the error stream pipe of the child process?

Time:08-15

I'm trying to execute an external program via libpipeline , but I can't get the error stream for the child process. code show as below:

#include <pipeline.h>
#include <stdlib.h>
#include <stdio.h>
#include <pwd.h>
#include <unistd.h>
#include <string.h>

int main() {
    pipeline *cmd;
    const char *line;

    // get user info
    struct passwd *uinfo = getpwuid(geteuid());
    if (!uinfo) {
        perror(NULL);
        return EXIT_FAILURE;
    }
    printf("login shell: %s\n", uinfo->pw_shell);

    // Create the pipeline of the external application, NULL indicates the end of parameter input.
    cmd = pipeline_new_command_args(uinfo->pw_shell, "-c", "echo 'Hello World' 1>&2", NULL);
    pipeline_want_out(cmd, -1);
    pipeline_start(cmd);
    line = pipeline_peekline(cmd);
    if (!strstr(line, "coding: UTF-8")) printf("Unicode text follows:0\n");

    while ((line = pipeline_readline(cmd))) printf("stdout: %s", line);

    printf("exit code: %d\n", pipeline_wait(cmd));

    return EXIT_SUCCESS;
}

How can I read the error stream of the child process?

The environmental information is as follows:

operating system: Linux, 5.15.60-1-MANJARO

gcc version: 12.1.1 20220730 (GCC)

shell: zsh 5.9

CodePudding user response:

How can I read the error stream of the child process?

As far as I can determine, libpipeline does not perform any stderr redirection, except as requested on a per-command basis via function pipecmd_discard_err(), or as you manually inject with the help of pipecmd_pre_exec() or pipeline_install_post_fork(). There is no built-in facility for reading the standard error output of any of the commands in the pipeline.

But it looks like you indeed can use some of the aforementioned mechanisms to do what you ask. Supposing that you want to capture the stderr output of all of the commands in the pipeline, you should be able to do this:

  • write a function to redirect stderr to the write end of a pipe. Something like this, maybe:

    void redirect_stderr(void *pipe) {
        int *pipe_fds = pipe;
    
        // Hope that the following doesn't fail, because there aren't any
        // particularly good choices for how to handle that in this
        // context.
        dup2(pipe_fds[1], STDERR_FILENO);
        close(pipe_fds[1]);
    }
    
  • Before you set up the pipeline, create a pipe for stderr redirection:

    int err_pipe_fds[2];
    int status;
    status = pipe(pipe_fds);
    // handle any error ...
    
  • Use the above to set up a pre-exec handler on each command in your pipeline:

    void pipecmd_pre_exec(command1, redirect_stderr, NULL, err_pipe_fds);
    void pipecmd_pre_exec(command2, redirect_stderr, NULL, err_pipe_fds);
    // ...
    

    (A pipeline-wide post-fork handler is not a good fit for this, because those do not accept arguments.)

Then, once the pipeline is running, you should be able to read the error output of all the commands from the read end of the pipe (file descriptor err_pipe_fds[0]). If you prefer stream I/O for that, then use fdopen() to wrap the file descriptor in a stream.

HOWEVER, do note that you should set up a separate thread to consume the stderr data, and you must start that thread before starting the pipeline. Otherwise, there is a risk that the pipeline will deadlock on account of the stderr pipe's buffer filling.

CodePudding user response:

Here is a complete application using only the C library. I based off this tutorial

#include <iostream>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>

// Child process executes this 
void child( int out[2], int err[2] ) {
    // Duplicates stdout and stderr in the child, taking care to wait
    while ((dup2(out[1], STDOUT_FILENO) == -1) && (errno == EINTR)) {}
    while ((dup2(err[1], STDERR_FILENO) == -1) && (errno == EINTR)) {}
    // Child does not need to access any pipe ends as it is using 
    // stdout/stderr
    ::close(out[1]);  
    ::close(err[1]);
    ::close(out[0]);
    ::close(err[0]);
    // Print something nice to papa
    std::cerr << "Hello World" << std::endl;
}

// This is the parent executing
void parent( pid_t pid, int out[2], int err[2] ) {
    // Parent does not need the write end of the child pipes (only read)
    ::close(out[1]);
    ::close(err[1]);

    // Read stderr
    while ( true ) {
        char buf[4096];
        ssize_t nb = ::read( err[0], buf, sizeof(buf));
        if ( nb>0 ) {
            std::cerr << "Read " << nb << " bytes" << std::endl;
            std::cerr << "   [" << std::string(buf,nb) << "]" << std::endl;
        } else if ( nb==0 ) {
            // pipe broke or was signaled
            break;
        }
        else if ( nb<0 ) {
            std::cerr << "Error " << strerror(errno) << std::endl;
            break;
        }      
    }
    // wait for the child to end clean
    while( true ) {
        int pstatus = 0;
        pid_t res = ::waitpid(pid, &pstatus, 0);
        if ( res==-1 ) {
            perror("waitpid");
            exit(EXIT_FAILURE);
        }
        if ( res==pid ) {
            std::cout << "Process exited with status " 
                << WEXITSTATUS(pstatus) << std::endl;
            break;
        }
        if(WIFEXITED(pstatus)) break;
    }
}

int main() {
    // We dont want signals
    signal(SIGPIPE, SIG_IGN);

    // Create the pipe between child and parent
    int err[2];
    int out[2];
    ::pipe(err);
    ::pipe(out);

    // fork
    pid_t pid = fork();
    if ( pid==0 ) {
        child(out,err);
        return 69;
    }
    else {
        parent(pid, out, err);
        return 0;
    }
}

It produces

Program stdout
Process exited with status 69
Program stderr
Read 11 bytes
   [Hello World]
Read 1 bytes
   [
]

Complete code: https://godbolt.org/z/hjaf8fYE6

  • Related