1

I'm new to programming, and I'm trying to write a c++ program for Linux which would create a child process, and this child process would execute an external program. The output of this program should be redirected to the main program and saved into a string variable, preserving all the spaces and new lines. I don't know how many lines/characters will the output contain.
This is the basic idea:

#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/wait.h>
 
int main()
{
    int pipeDescriptors[2];
    pipe(pipeDescriptors);
    pid_t pid = fork();
    if (pid == -1)
    {
        std::cerr << __LINE__ << ": fork() failed!\n" <<
        std::strerror(errno) << '\n';
        return 1;
    }
    else if (!pid)
    {
        // Child process
        close(pipeDescriptors[0]); // Not gonna read from here
        if (dup2(pipeDescriptors[1], STDOUT_FILENO) == -1) // Redirect output to the pipe
        {
            std::cerr << __LINE__ << ": dup2() failed!\n" <<
            std::strerror(errno) << '\n';
            return 1;
        }
        close(pipeDescriptors[1]); // Not needed anymore
        execlp("someExternalProgram", "someExternalProgram", NULL);
    }
    else
    {
        // Parent process
        close(pipeDescriptors[1]); // Not gonna write here
        pid_t stdIn = dup(STDIN_FILENO); // Save the standard input for further usage
        if (dup2(pipeDescriptors[0], STDIN_FILENO) == -1) // Redirect input to the pipe
        {
            std::cerr << __LINE__ << ": dup2() failed!\n" <<
            std::strerror(errno) << '\n';
            return 1;
        }
        close(pipeDescriptors[0]); // Not needed anymore
        int childExitCode;
        wait(&childExitCode);
        if (childExitCode == 0)
        {
            std::string childOutput;
            char c;
            while (std::cin.read(&c, sizeof(c)))
            {
                childOutput += c;
            }
            // Do something with childOutput...
        }
        if (dup2(stdIn, STDIN_FILENO) == -1) // Restore the standard input
        {
            std::cerr << __LINE__ << ": dup2() failed!\n" <<
            std::strerror(errno) << '\n';
            return 1;
        }
        // Some further code goes here...
    }
    return 0;
}

The problem with the above code is that when std::cin.get() function reads the last byte in the input stream, it doesn't actually "know" that this byte is the last one and tries to read further, which leads to set failbit and eofbit for std::cin so I cannot read from the standard input later anymore. std::cin.clear() resets those flags, but stdin still remains unusable.

If I could get the precise size in bytes of the stdin content without going beyond the last character in the stream, I would be able to use std::cin.read() for reading this exact amount of bytes into a string variable. But I guess there is no way to do that.
So how can I solve this problem? Should I use an intermediate file for writing the output of the child process into it and reading it later from the parent process?

Robert Columbia
  • 6,313
  • 15
  • 32
  • 40
Alexey104
  • 969
  • 1
  • 5
  • 17
  • 1
    No you don't know "the precise size in bytes" of `std::cin`. It could be completely empty. Or it could be the entire contents of "Harry Potter And The Deathly Hallows". You don't know until you read it. Your C++ textbook should have plenty of examples of reading files whose size is not known in advance, and the techniques for reading their entire contents, without knowing their size. Is there anything specific in your textbook that you don't understand, or is unclear to you? – Sam Varshavchik Oct 05 '20 at 11:03
  • 1
    This smells like an XY problem: you're asking us to solve a framing problem (X) because you're reusing stdin to read from the external process (Y). Why not solve Y instead by reading directly from the pipe and leaving stdin as is? [How to construct a c++ fstream from a POSIX file descriptor?](https://stackoverflow.com/questions/2746168/how-to-construct-a-c-fstream-from-a-posix-file-descriptor) – Botje Oct 05 '20 at 11:10
  • 1
    You can use https://man7.org/linux/man-pages/man2/ioctl.2.html to find the size of the stream in stdin. – anastaciu Oct 05 '20 at 11:46

1 Answers1

1

The child process writes into the pipe but the parent doesn't read the pipe until the child process terminates. If the child writes more than the pipe buffer size it blocks waiting for the parent to read the pipe, but the parent is blocked waiting for the child to terminate leading to a deadlock.

To avoid that, the parent process must keep reading the pipe until EOF and only then use wait to get the child process exit status.

E.g.:

// Read entire child output.
std::string child_stdout{std::istreambuf_iterator<char>{std::cin},
                         std::istreambuf_iterator<char>{}};
// Get the child exit status.
int childExitCode;
if(wait(&childExitCode))
    std::abort(); // wait failed.

You may also like to open a new istream from the pipe file descriptor to avoid messing up std::cin state.

Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271