Question

Possible duplicates:

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!
Was it helpful?

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);
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top