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

Time:09-20

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.

CodePudding user response:

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:

CodePudding user response:

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.

  •  Tags:  
  • c
  • Related