2

Programming Language: C and C++ mixed code

Context: There is program 'A' that takes user input from STDIN and outputs a response to STDOUT. The input and output of 'A' are unsuitable for my intended users. Unfortunately editing or augmenting 'A' directly is not an option.

Approach: I created program 'B' that forks a child process to launch 'A' and use pipes to intercept and translate input/output (i.e. take user input and adjust before send to STDIN of A via pipe, and conversely receive STDOUT of 'A' via pipe and adjust for users.)

Problem: When attempting to receive the output of 'A' the program seems to 'hang' mid-communication. My research has led me to believe that the issue could be 1 of 3 problems: *for clarity let Pipe 1 be data sent from parent(B) to STDIN of 'A', and Pipe 2 is used to send data from STDOUT of 'A' to parent.

  1. There is no EOF at the end of stream associated with Pipe 2. An indicative symptom of this is that Pipe 1 functions as expected, and is not affected by a similar problem probably because program 'A' uses cin to handle input and looks for '\n' not EOF. I tried adjusting my program to also read line by line but after the first line of the execution of 'A' (in addition to the self declaring line printed by the child process) it stopped receiving data.
  2. There is some sort of contention when child is writing to the pipe at the same time that parent is attempting to read from it.
  3. fgetc() may not be 'consuming' data in the Pipe 2 stream buffer and therefore the child process is blocking until this data is 'received'. This may be the problem because the input for 'A' (Pipe 1 data) is received flawlessly by the shell/OS (STDIN) and buffering is handled better/differently there on that end, whereas STDOUT receives the output of 'A' and delivers it to Pipe 2 and as a result all buffering responsibilities fall on the pipe mechanism.

Below are the codes for program B ("test.cpp") and a dummy mimicking program I am using for testing instead of A ("test2.cpp").

test.cpp

#include <cstdlib>
#include <cstdio>
#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

using namespace std;

int main()
{
pid_t pid;
int wpipe[2];//write to child pipe
int rpipe[2];//read from child pipe

//initiate pipes here
if(pipe(wpipe))
{
    cout << "write pipe creation failed"<<endl;
    return 1;
}
if(pipe(rpipe))
{
    cout << "read pipe creation failed"<<endl;
    return 1;
}

//fork child here
pid_t procID = vfork();//does not copy address space. better than fork? 

if(procID==0)
{//child
    //close unused pipe ends
    close(wpipe[1]);//read from wpipe
    close(rpipe[0]);//write to rpipe
    //handle stdin & stdout pipes
    dup2(wpipe[0],STDIN_FILENO);//reroute stdin to wpipe[0]
    dup2(rpipe[1],STDOUT_FILENO);//reroute stdout to rpipe[1]
    //close pipes since they are copied??
    close(wpipe[0]);
    close(rpipe[1]);
    //prepare array for execv
    char** args;
    args = new char*[2];
    args[0] = new char[8];
    strcpy(args[0],"test2");
    args[1] = (char*)0;

    cout << "I'm child. Relinquishing procID & memory image to test 2."<<endl;

    int test = execv(args[0],args); 
    if(test==-1)
        cout << "Error: execv failed."<<endl;
    delete[] args[0];
    delete[] args;
    exit(0);
}else if(procID>0)
{//parent
    int state,c;

    //close unused pipe ends
    close(wpipe[0]);//write to wpipe
    close(rpipe[1]);//read from rpipe

    //communicate with child
    FILE *wstream;
    wstream = fdopen(wpipe[1],"w");
    FILE *rstream;
    rstream = fdopen(rpipe[0],"r");

    //read from child
    c = fgetc(rstream);
    while(c!=EOF)
    {
        putchar(c);
        c = fgetc(rstream);
    }

    fprintf(wstream,"test1\n");

            c = fgetc(rstream);
    while(c!=EOF)
    {
        putchar(c);
        c = fgetc(rstream);
    }

    fprintf(wstream,"test2\n");

            c = fgetc(rstream);
    while(c!=EOF)
    {
        putchar(c);
        c = fgetc(rstream);
    }

    fclose(wstream);
    fclose(rstream);

    waitpid(procID,&state,0);
    cout << "I'm parent."<<endl;
}
else
{//failure to fork
    cout << "Fork failed" << endl;
    return 1;
}

return 0;
}

test2.cpp

#include <iostream>
#include <string>

using namespace std;

int main()
{
    string tmp = "";
    cout << "started" << endl;
    while(tmp=="")
        cin >> tmp;
    cout << tmp << endl;
    tmp="";
    while(tmp=="")
        cin >> tmp;
    cin >> tmp;
    cout << tmp << endl;

    cout << "exiting" << endl;

return 0;   
}

I apologize in advance for the lengthy question and monolithic code.

Deanie
  • 2,316
  • 2
  • 19
  • 35
Hooman
  • 31
  • 4
  • 4
    If you want a shortcut, use [pstreams](http://pstreams.sf.net/), or if you want to do it yourself I think the source code is a good example of doing it right (but then I'm biased as I wrote it ;-) – Jonathan Wakely Mar 28 '13 at 00:15
  • Thanks. I will definitely look into it :) – Hooman Mar 28 '13 at 00:30
  • Looks like a simple deadlock. Both processes are waiting indefinitely for input from the other one. – nwellnhof Mar 28 '13 at 00:32
  • @nwellnhof - At first I though that too because its a very likely scenario if there is no EOF in Pipe 2 (otherwise the parent will simply escape the read loops and keep going). I tested that by checking for '\n' instead of EOF (since the dummy 'A' always outputs `endl` with each `cout`) but it still failed to receive all the outputs of 'A'. On a related note is it wrong to assume that since rstream, is well...a stream, the only consistent thing it can have is an end signified by EOF? – Hooman Mar 28 '13 at 00:45

1 Answers1

0

You appear to be using three different types of io.

dup2 is level 2 io that operates on raw, low-level file descriptors; fgetc is level 3 io that operates on buffered c FILEs; iostream is C++ thing on a level above FILEs.

I would suggest:

  • Stick with one paradigm, and frankly going with 'c' will probably be easiest because there are lots of examples available,
  • Use a scripting language like Perl or Python: they're extremely good at this kind of thing.

Or you could see some of the answers to others who have asked this same question.

Community
  • 1
  • 1
kfsone
  • 23,617
  • 2
  • 42
  • 74