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