why can't I pipe output from both execl calls?
Question
Possible duplicates:
- How to call execl() in C with the proper arguments?
- Grabbing output from exec
- Linux Pipes as Input and Output
- Using dup2 for piping
- Piping for input/output
I've been trying to learn piping in Linux using dup/dup2 and fork the last 3 days. I think I got the hang of it, but when I call two different programs from the child process, it seems that I am only capturing output from the first one called. I don't understand why that is and/or what I'm doing wrong. This is my primary question.
Edit: I think a possible solution is to fork another child and set up pipes with dup2, but I'm more just wondering why the code below doesn't work. What I mean is, I would expect to capture stderr from the first execl call and stdout from the second. This doesn't seem to be happening.
My second question is if I am opening and closing the pipes correctly. If not, I would like to know what I need to add/remove/change.
Here is my code:
#include <stdlib.h>
#include <iostream>
#include <time.h>
#include <sys/wait.h>
#define READ_END 0
#define WRITE_END 1
void parentProc(int* stdoutpipe, int* stderrpipe);
void childProc(int* stdoutpipe, int* stderrpipe);
int main(){
pid_t pid;
int status;
int stdoutpipe[2]; // pipe
int stderrpipe[2]; // pipe
// create a pipe
if (pipe(stdoutpipe) || pipe(stderrpipe)){
std::cerr << "Pipe failed." << std::endl;
return EXIT_FAILURE;
}
// fork a child
pid = fork();
if (pid < 0) {
std::cerr << "Fork failed." << std::endl;
return EXIT_FAILURE;
}
// child process
else if (pid == 0){
childProc(stdoutpipe, stderrpipe);
}
// parent process
else {
std::cout<< "waitpid: " << waitpid(pid, &status, 0)
<<'\n'<<std::endl;
parentProc(stdoutpipe, stderrpipe);
}
return 0;
}
void childProc(int* stdoutpipe, int* stderrpipe){
dup2(stdoutpipe[WRITE_END], STDOUT_FILENO);
close(stdoutpipe[READ_END]);
dup2(stderrpipe[WRITE_END], STDERR_FILENO);
close(stderrpipe[READ_END]);
execl("/bin/bash", "/bin/bash", "foo", NULL);
execl("/bin/ls", "ls", "-1", (char *)0);
// execl("/home/me/outerr", "outerr", "-1", (char *)0);
//char * msg = "Hello from stdout";
//std::cout << msg;
//msg = "Hello from stderr!";
//std::cerr << msg << std::endl;
// close write end now?
}
void parentProc(int* stdoutpipe, int* stderrpipe){
close(stdoutpipe[WRITE_END]);
close(stderrpipe[WRITE_END]);
char buffer[256];
char buffer2[256];
read(stdoutpipe[READ_END], buffer, sizeof(buffer));
std::cout << "stdout: " << buffer << std::endl;
read(stderrpipe[READ_END], buffer2, sizeof(buffer));
std::cout << "stderr: " << buffer2 << std::endl;
// close read end now?
}
When I run this, I get the following output:
yfp> g++ selectTest3.cpp; ./a.out
waitpid: 21423
stdout: hB�(6
stderr: foo: line 1: -bash:: command not found
The source code for the "outerr" binary (commented out above) is simply:
#include <iostream>
int main(){
std::cout << "Hello from stdout" << std::endl;
std::cerr << "Hello from stderr!" << std::endl;
return 0;
}
When I call "outerr," instead of ls or "foo" I get the following output, which I would expect:
yfp> g++ selectTest3.cpp; ./a.out
waitpid: 21439
stdout: Hello from stdout
stderr: Hello from stderr!
Solution
On execl
Once you successfully call execl
or any other function from the exec
family, the original process is completely overwritten by the new process. This implies that the new process never "returns" to the old one. If you have two execl
calls in a row, the only way the second one can be executed is if the first one fails.
In order to run two different commands in a row, you have to fork
one child to run the first command, wait
, fork
a second child to run the second command, then (optionally) wait
for the second child too.
On read
The read
system call does not append a terminating null, so in general you need to look at the return value, which tells you the number of bytes actually read. Then set the following character to null to get a C string, or use the range constructor for std::string
.
On pipes
Right now you are using waitpid
to wait until the child process has already finished, then reading from the pipes. The problem with this is that if the child process produces a lot of output, then it will block because the pipe gets full and the parent process is not reading from it. The result will be a deadlock, as the child waits for the parent to read, and the parent waits for the child to terminate.
What you should do is use select
to wait for input to arrive on either the child's stdout
or the child's stderr
. When input arrives, read it; this will allow the child to continue. When the child process dies, you'll know because you'll get end of file on both. Then you can safely call wait
or waitpid
.
OTHER TIPS
The exec
family of functions replace the current process image with a new process image. When you execute,
execl("/bin/bash", "/bin/bash", "foo", NULL);
the code from the current process is not executed any more. That's why you never see the result of executing
execl("/bin/ls", "ls", "-1", (char *)0);