Piping for input/output

41,846

Solution 1

Your primary problem is that you have the arguments to dup2() reversed. You need to use:

dup2(fd_p2c[0], 0);   // Duplicate read end of pipe to standard input
dup2(fd_pFc[1], 1);   // Duplicate write end of pipe to standard output

I got suckered into misreading what you wrote as OK until I put error checking on the set-up code and got unexpected values from the dup2() calls, which told me what the trouble was. When something goes wrong, insert the error checks you skimped on before.

You also did not ensure null termination of the data read from the child; this code does.

Working code (with diagnostics), using cat as the simplest possible 'other command':

#include <unistd.h>
#include <string>
#include <iostream>
using namespace std;

int main()
{
    int fd_p2c[2], fd_c2p[2], bytes_read;
    pid_t childpid;
    char readbuffer[80];
    string program_name = "/bin/cat";
    string gulp_command = "this is the command data sent to the child cat (kitten?)";
    string receive_output = "";

    if (pipe(fd_p2c) != 0 || pipe(fd_c2p) != 0)
    {
        cerr << "Failed to pipe\n";
        exit(1);
    }
    childpid = fork();

    if (childpid < 0)
    {
        cout << "Fork failed" << endl;
        exit(-1);
    }
    else if (childpid == 0)
    {
        if (dup2(fd_p2c[0], 0) != 0 ||
            close(fd_p2c[0]) != 0 ||
            close(fd_p2c[1]) != 0)
        {
            cerr << "Child: failed to set up standard input\n";
            exit(1);
        }
        if (dup2(fd_c2p[1], 1) != 1 ||
            close(fd_c2p[1]) != 0 ||
            close(fd_c2p[0]) != 0)
        {
            cerr << "Child: failed to set up standard output\n";
            exit(1);
        }

        execl(program_name.c_str(), program_name.c_str(), (char *) 0);
        cerr << "Failed to execute " << program_name << endl;
        exit(1);
    }
    else
    {
        close(fd_p2c[0]);
        close(fd_c2p[1]);

        cout << "Writing to child: <<" << gulp_command << ">>" << endl;
        int nbytes = gulp_command.length();
        if (write(fd_p2c[1], gulp_command.c_str(), nbytes) != nbytes)
        {
            cerr << "Parent: short write to child\n";
            exit(1);
        }
        close(fd_p2c[1]);

        while (1)
        {
            bytes_read = read(fd_c2p[0], readbuffer, sizeof(readbuffer)-1);

            if (bytes_read <= 0)
                break;

            readbuffer[bytes_read] = '\0';
            receive_output += readbuffer;
        }
        close(fd_c2p[0]);
        cout << "From child: <<" << receive_output << ">>" << endl;
    }
    return 0;
}

Sample output:

Writing to child: <<this is the command data sent to the child cat (kitten?)>>
From child: <<this is the command data sent to the child cat (kitten?)>>

Note that you will need to be careful to ensure you don't get deadlocked with your code. If you have a strictly synchronous protocol (so the parent writes a message and reads a response in lock-step), you should be fine, but if the parent is trying to write a message that's too big to fit in the pipe to the child while the child is trying to write a message that's too big to fit in the pipe back to the parent, then each will be blocked writing while waiting for the other to read.

Solution 2

It sounds like you're looking for coprocesses. You can program them in C/C++ but since they are already available in the (bash) shell, easier to use the shell, right?

First start the external program with the coproc builtin:

coproc external_program

The coproc starts the program in the background and stores the file descriptors to communicate with it in an array shell variable. Now you just need to start your program connecting it to those file descriptors:

your_program <&${COPROC[0]} >&${COPROC[1]}

Solution 3

#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <string.h>
#include <iostream>
using namespace std;
int main() {
    int i, status, len;
    char str[10];
    mknod("pipe", S_IFIFO | S_IRUSR | S_IWUSR, 0); //create named pipe
    pid_t pid = fork(); // create new process
    /* Process A */
    if (pid == 0) {
        int myPipe = open("pipe", O_WRONLY); // returns a file descriptor for the pipe
        cout << "\nThis is process A having PID= " << getpid(); //Get pid of process A
        cout << "\nEnter the string: ";
        cin >> str;
        len = strlen(str);
        write(myPipe, str, len); //Process A write to the named pipe
        cout << "Process A sent " << str;
        close(myPipe); //closes the file descriptor fields.
        }
    /* Process B */
        else {
        int myPipe = open("pipe", O_RDONLY); //Open the pipe and returns file descriptor
        char buffer[21];
        int pid_child;
        pid_child = wait(&status); //wait until any one child process terminates
        int length = read(myPipe, buffer, 20); //reads up to size bytes from pipe with descriptor fields, store results
    //  in buffer;
        cout<< "\n\nThis is process B having PID= " << getpid();//Get pid of process B
        buffer[length] = '\0';
        cout << "\nProcess B received " << buffer;
        i = 0;
        //Reverse the string
        for (length = length - 1; length >= 0; length--)
        str[i++] = buffer[length];
        str[i] = '\0';
        cout << "\nRevers of string is " << str;
        close(myPipe);
        }
    unlink("pipe");
return 0;
}
Share:
41,846
Eric Inclan
Author by

Eric Inclan

Updated on July 31, 2022

Comments

  • Eric Inclan
    Eric Inclan almost 2 years

    This question follows from my attempt to implement the instructions in:

    Linux Pipes as Input and Output

    How to send a simple string between two programs using pipes?

    http://tldp.org/LDP/lpg/node11.html

    My question is along the lines of the question in: Linux Pipes as Input and Output, but more specific.

    Essentially, I am trying to replace:

    /directory/program < input.txt > output.txt
    

    using pipes in C++ in order to avoid using the hard drive. Here's my code:

    //LET THE PLUMBING BEGIN 
    int fd_p2c[2], fd_pFc[2], bytes_read;
        // "p2c" = pipe_to_child, "pFc" = pipe_from_child (see above link)
    pid_t childpid;
    char readbuffer[80];
    string program_name;// <---- includes program name + full path
    string gulp_command;// <---- includes my line-by-line stdin for program execution
    string receive_output = "";
    
    pipe(fd_p2c);//create pipe-to-child
    pipe(fd_pFc);//create pipe-from-child
    childpid = fork();//create fork
    
    if (childpid < 0)
    {
        cout << "Fork failed" << endl;
        exit(-1);
    }
    else if (childpid == 0)
    {
        dup2(0,fd_p2c[0]);//close stdout & make read end of p2c into stdout
        close(fd_p2c[0]);//close read end of p2c
        close(fd_p2c[1]);//close write end of p2c
        dup2(1,fd_pFc[1]);//close stdin & make read end of pFc into stdin
        close(fd_pFc[1]);//close write end of pFc
        close(fd_pFc[0]);//close read end of pFc
    
        //Execute the required program
        execl(program_name.c_str(),program_name.c_str(),(char *) 0);
        exit(0);
    }
    else
    {
        close(fd_p2c[0]);//close read end of p2c
        close(fd_pFc[1]);//close write end of pFc
    
        //"Loop" - send all data to child on write end of p2c
        write(fd_p2c[1], gulp_command.c_str(), (strlen(gulp_command.c_str())));
        close(fd_p2c[1]);//close write end of p2c
    
        //Loop - receive all data to child on read end of pFc
        while (1)
        {        
            bytes_read = read(fd_pFc[0], readbuffer, sizeof(readbuffer));
    
            if (bytes_read <= 0)//if nothing read from buffer...
                break;//...break loop
    
            receive_output += readbuffer;//append data to string
        }
        close(fd_pFc[0]);//close read end of pFc
    }
    

    I am absolutely sure that the above strings are initialized properly. However, two things happen that don't make sense to me:

    (1) The program I am executing reports that the "input file is empty." Since I am not calling the program with "<" it should not be expecting an input file. Instead, it should be expecting keyboard input. Furthermore, it should be reading the text contained in "gulp_command."

    (2) The program's report (provided via standard output) appears in the terminal. This is odd because the purpose of this piping is to transfer stdout to my string "receive_output." But since it is appearing on screen, that indicates to me that the information is not being passed correctly through the pipe to the variable. If I implement the following at the end of the if statement,

    cout << receive_output << endl;
    

    I get nothing, as though the string is empty. I appreciate any help you can give me!

    EDIT: Clarification

    My program currently communicates with another program using text files. My program writes a text file (e.g. input.txt), which is read by the external program. That program then produces output.txt, which is read by my program. So it's something like this:

    my code -> input.txt -> program -> output.txt -> my code
    

    Therefore, my code currently uses,

    system("program < input.txt > output.txt");
    

    I want to replace this process using pipes. I want to pass my input as standard input to the program, and have my code read the standard output from that program into a string.