7

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.

Community
  • 1
  • 1
Eric Inclan
  • 327
  • 1
  • 5
  • 14
  • Your starting proposition is not clear. You state you want to replace `/directory/program output.txt` with something using pipes in order to avoid file system access. But you 'need' multiple processes to make using pipes sensible. (You _can_ use pipes in a single process, but it doesn't make sense usually.) So, you might have `/directory/program1 output.txt`; that makes sense (and you might previously have used `/directory/program1 intermediate.txt; /directory/program2 output.txt`). Please clarify your intent. – Jonathan Leffler Jul 07 '13 at 02:07
  • Good point. I edited the question. – Eric Inclan Jul 07 '13 at 02:24
  • As an extra clarification, my goal is basically the same as the question in: stackoverflow.com/questions/1734932/… which you answered previously (great answer by the way). – Eric Inclan Jul 07 '13 at 13:09
  • @EricInclan how would we run this code if I want to do the same thing but for two children processes? meaning sending and receiving strings between two child processes ignoring the parent. – Mohsin Sep 09 '16 at 15:33
  • @Mohsin I have never attempted that before. Unfortunately I cannot give you guidance at this time. – Eric Inclan Mar 03 '17 at 01:38

3 Answers3

8

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.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • 1
    This worked beautifully. Thank you for your help, and for the heads up about deadlocked code. I don't think I will have that issue with this program, but it's good to know. – Eric Inclan Jul 07 '13 at 18:56
  • how would we run this code if I want to do the same thing but for two children processes? meaning sending and receiving strings between two child processes ignoring the parent. – Mohsin Sep 09 '16 at 15:37
  • 1
    @Mohsin: it depends in part on how the children will communicate, and on what the parent process will do after starting the two children. There are multiple ways to deal with it. One option is to simply have the parent fork before doing any of the code above, and the parent process from that fork can exit or wait or continue with other work, while the child process handles the code as above. Alternatively, the parent process sets up the two pipes, forks twice, and each child sorts out its own plumbing while the parent closes the four file descriptors for the two pipes and goes its own way. – Jonathan Leffler Sep 09 '16 at 16:01
  • Ya I did exactly the second method that you suggested, I just simply make a pipe and call two forks one for child_a other for child_b, if the pid_child_a is zero it writes to the pipe else if the pid_child_b is zero it reads and prints the value from buffer, the problem is when I run it the first time it runs fine but the second time I run it prints nothing. Sometimes when I clean the project or do some editing again gives correct output but then again starts to print nothing the second and so on. – Mohsin Sep 09 '16 at 16:26
  • and also if I run it in debug mode it gives correct output everytime – Mohsin Sep 09 '16 at 16:31
  • @Mohsin: I can't debug your code from the vague description in a comment. If you can make it into an MCVE ([MCVE]), then you can ask a question about it here on SO — otherwise see my profile, but it should still be as near an MCVE as possible. I'm not sure why you'd be getting the erratic behaviour if you ensure every variable is appropriately initialized and you ensure each system call succeeds. As a first step in debugging, do make sure you are checking your system calls. – Jonathan Leffler Sep 09 '16 at 16:45
1

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]}
Joni
  • 108,737
  • 14
  • 143
  • 193
  • In my case, my program repeatedly calls external_program (thousands of times), each time with different input. This seems like a one-shot bash script that is executed at launch, right? I don't know much about file descriptors, but if I were to use this, does this mean I have to write to a file on the drive, or can the file descriptor point to a string in my code? – Eric Inclan Jul 07 '13 at 13:18
  • 1
    The file descriptors are connected to pipes in this case, not files. Also, you can run a bash script thousands of times, too. – Joni Jul 07 '13 at 15:25
1
#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;
}