6
votes

i have a very simple c/c++ program that forks a child process to execute another program, and then sends some data to that child program, and waits for the response.

the child program reads from stdin and waits for EOF before it continues.

my problem is, the child program receives the initial input from the pipe writing, but it never sees an EOF (even though I close the pipe), so it waits forever.

i'm not sure why closing the pipe doesn't imply the EOF for the child's stdin?

here's the code:

http://gist.github.com/621210

3
I updated my answer. And as I think about this question, should this have the "homework" tag? It seems really basic.Zan Lynx
i assure you this has nothing to do with homework. i've been out of school for years. i'm not a c/c++ coder but i'm building a proof-of-concept system for a demo i'm doing and i've come back to code i've not written like in over 10 years.Kyle Simpson

3 Answers

8
votes

The most common reason for this is that you aren't closing the write end of the pipe, so the EOF never gets sent. The common example is when you have code that looks like:

int fds[2];
pipe(fds);  // open a pipe
if (fork()) {
    // parent process
    write(fds[1], ...  // write data
    close(fds[1]); // close it
} else {
    // child process
    while (read(fds[0], ....) > 0) {
        // read until EOF

The problem here is that the write end of the pipe never gets closed -- the parent process closes it, but the child still has the write descriptor open. So the child never sees an EOF on the read descriptor.

The very first thing you need to do after forking the child is to close(fds[1]);, closing its copy of the write descriptor. That way when the parent closes the last remaining reference to the write end of the pipe, the child will see EOF on the read end.

Edit

looking at the link you added, this is precisely the problem -- the child still has the write end of the pipe open on its stdout. Don't dup the write end to stdout in the child, just close it. Send stdout somewhere else (a logfile, or /dev/null)

Edit

for bi-drectional communication, you'll need two pipes:

int tochild[2], fromchild[2];
pipe(tochild); pipe(fromchild);
if (fork()) {
    close(tochild[0]);
    close(fromchild[1]);
    //write to tochild[1] and read from fromchild[0]
} else {
    dup2(tochild[0], 0);
    dup2(fromchild[1], 1);
    close(tochild[0]); close(tochild[1]);
    close(fromchild[0]); close(fromchild[1]);
    exec(...
}

You need to be very careful writing data in the parent, however -- if there's a lot of data to be sent to the child, you can't send all of it before reading the child's output or you might deadlock (both pipes fill up and the parent blocks trying to write more data for the child while the child blocks trying to output). You need to use poll or select to tell when there's data to read or space to write, and you may want to put the pipes (the parent ends at least) in non-blocking mode.

3
votes

Updated with what I think is the problem: You are reading into a character and checking that character for EOF. That is not how the read() system call works. It will return a 0 when at EOF. It does not write an EOF into the buffer.

Also I see you are reading one character at a time. This is an awful way to read data. It is several thousand times slower than reading a large buffer, say 4 or 8 kB.

I believe you also have a common mistake here. You are not checking the return value of write().

The write system call is not guaranteed to write all the data before returning. It might write 4000 bytes and return. It will return the number of bytes written. It is then your responsibility to update your buffer pointer and call write again.

Or it might return an error code and it is important for you to check for that.

0
votes

This is the proof of concept I wrote for you:

Fork exec double pipe by dino ciuffetti