Failing to read from std::cin after pipe() with dup2()

Viewed 62

I was trying to read output from a child process using a pipe(). I used dup2() to get the pipe's output through stdin, and cin.get(c) in a loop to get the child's output. The first time I do this, everything works fine. After that, however, every time I try to do this again, cin.get() returns EOF.

I thought that perhaps cin.clear() would help, since cin.get() is setting the failbit and eofbit when it reaches EOF... This does remove the flags, but the main problem remains: cin.get() does not read anything.

Here is a simplified case that shows the problem:

#include <iostream>
#include <unistd.h>

int main()
{
    int fd[2];
    pipe(fd);
    int pid = fork();

    //FIRST TIME
    if (pid == 0)
    {
        close(fd[0]);
        dup2(fd[1], 1);
        close(fd[1]);
        std::cout << "PROCESS 1 OUTPUT" << std::endl;
        exit(0);
    }

    wait(NULL);

    close(fd[1]);
    int original = dup(0);
    dup2(fd[0], 0);
    close(fd[0]);

    if (std::cin.fail())
        std::cout << "FAIL1" << std::endl;
    char c;
    std::string output1;
    while (std::cin.get(c))
    {
        output1 += c;
    }
    std::cin.clear(); // This helps with "FAIL"s, but doesn't fix the problem :(

    if (std::cin.fail())
        std::cout << "FAIL2" << std::endl;

    dup2(original, 0);
    close(original);

    std::cout << "output: " << output1 << std::endl;
    
    //SECOND TIME
    int fd2[2];
    pipe(fd2);
    int pid2 = fork();

    if (pid2 == 0)
    {
        close(fd2[0]);
        dup2(fd2[1], 1);
        close(fd2[1]);
        std::cout << "PROCESS 2 OUTPUT" << std::endl;
        exit(0);
    }

    wait(NULL);

    close(fd2[1]);
    int original2 = dup(0);
    dup2(fd2[0], 0);
    close(fd2[0]);

    if (std::cin.fail())
        std::cout << "FAIL3" << std::endl;

    char c2;
    std::string output2;
    while (std::cin.get(c2))
    {
        output2 += c2;
    }

    if (std::cin.fail())
        std::cout << "FAIL4" << std::endl;

    dup2(original2, 0);
    close(original2);

    std::cout << "output2: " << output2 << std::endl;

    return 0;
}

This program outputs the following:

output: PROCESS 1 OUTPUT

FAIL4
output2:

What I expected to get was the two outputs and no "FAIL". Do you have any idea why this happens?

I found an alternative solution using read() and reading straight from the pipe's fd[0], however I would love to understand why this is happening when I use cin.

2 Answers

I'm going to provide this answer in the hopes of encouraging someone who understands the quirks of C++ better than I to downvote and provide a more correct answer. The behavior you want seems to be available if you do:

  std::cin.clear(); 
  std::clearerr(stdin);

That cin.clear() does not also do clearerr(stdin) seems bizarre to me, and this is probably why I continue to steer clear of C++. Although, to be fair to C++, the thing that ought to be avoided here is manipulation of the underlying file descriptors of the standard file streams.

As William Pursell mentioned, using both clear() and clearerr() will solve your problem.

After reading around for a bit, I came to the conclusion that this is due to the fact that std::cin and stdin are two different things, and therefore you need to reset their error flags separately:

  • std::cin is an object of class istream, and its internal error state flags are set using std::cin.clear().
  • stdin is actually a FILE *, and its error flags and the EOF indicator are reset using std::clearerr(stdin).
  • std::cin is associated with the standard C input stream stdin (according to cppreference.com), but I guess you still need to reset both separately.

These StackOverflow questions and answers might also help:

Related