read() blocks process until the stream is closed

Hi @all,

i really stuck in programming a tool with bidirectional process communication (popen(cmd, "rw") ... something like that ;-)).

Here is the code:

  if(pipe(p_stdin) != 0 || pipe(p_stdout) != 0) {
    fprintf(stderr, "Aufruf von pipe() schlug fehl.\n");
    exit(1);
  }

  if((pid = fork()) < 0) {
    fprintf(stderr, "Aufruf von fork() schlug fehl.\n");
    exit(1);

  // kinderprozess
  } else if(pid == 0) {
    close(p_stdin[WRITE]);
    dup2(p_stdin[READ], READ);
    close(p_stdout[READ]);
    dup2(p_stdout[WRITE], WRITE);

    // factor aurufen
    execl("/bin/sh", "sh", "-c", "factor", NULL);
    perror("execl");
    exit(1);
  }

  close(p_stdin[READ]);
  close(p_stdout[WRITE]);

  // zahlen einlesen
  do {
    assert(fgets(buf, 128, stdin) != NULL);
    
    if((number = atoi(buf)) > 0) {
      // zahl an factor �bergeben
      buf[length = strlen(buf)] = '\n';
      assert(write(p_stdin[WRITE], buf, length + 1) != -1);

      // ergebniss auslesen
      close(p_stdin[WRITE]); // <- this musst be removed
      assert(read(p_stdout[READ], buf, 128) != -1);
      printf("Die Zufallszahl %d hat die Primfaktoren %s", number, buf + length + 1);
    }
  } while(number > 0);

intersting are only the last 10 lines. I try to write to factor, then read, than write again (...) until 0 is given. This wont work, because read(p_stdout[READ], ....) always blocks the programm, waiting for some better waeather are someso. I tried to use fsync() between write and read, but it didnt help. Only fclose() solve the problem, but if p_stdin[WRITE] is closed, i can only iterate the loop one times.

Can someone help me? Thx :slight_smile:

Hallo Jens,

Of course, we can help. It's just a matter how much you're willing to pay :smiley:
More seriously, I think your problem is simply related to the fact that stdout is line buffered. If you switch to unbuffered mode in the child, this may perhaps solve your problem:

 // kinderprozess
  } else if(pid == 0) {
    close(p_stdin[WRITE]);
    dup2(p_stdin[READ], READ);
    close(p_stdout[READ]);
    dup2(p_stdout[WRITE], WRITE);
    setvbuf(stdout, NULL, _IONBF, 0);
    ...

Another alternative would be that the factor program fflush each message it writes to stdout.

Besides that, a few comment to your code:

  • you may want to use the constant STDIN_FILENO and STDOUT_FILENO defined in <unistd.h> instead of your READ and WRITE constant.
  • Once you dup2, you may close the descriptor you have duplicated (p_stdin[READ] and p_stdout[WRITE])

Viel Gl�ck,
Lo�c.

Hi Loic,

thx for your tips - unfortunately "setvbuf(stdout, NULL, _IONBF, 0)" does not solve the problem (I think its the same like a explizit fsync() call). Any other ideas?

Greetz Jens

Hi Jens,

yeah, my bad. I prototyped using a parent/child... But setvbuf() settings are not preserved across an exec()! Besides that, I am not sure that it has to do with your specific problem... I need to dig further. Perhaps you could tell us what this 'factor' is? a shell script?

Bis Dann,
Lo�c.

Hi! "factor" is part of the gnu coreutils and calculates the prime factors fo a given number :slight_smile:

Hallo Jens,

so I have a good and a bad news. The good news: I found the reason for your problem. The stream stdout is normally "line-buffered", but become block-buffered (e.g. 8Kb) when stdout is not connected to a terminal (for instance, when stdout is redirected to a pipe or a file)... This can be simply seen if you invoke factor as follows

factor | more
123
456
(no output)
x
factor: `x' is not a valid positive integer
123: 3 41
456: 2 2 2 3 19

The 'x' causes a message on stderr stream, and stdout to be flushed.

There are solutions to this problem, but those are not necessarily straightforward. That's the bad news. Basically, you have to let the child process think that it is attached to a pseudo-terminal. On Linux, this can be achieved using forkpty(). More info on this technique can be found here: rmathew: Terminal Sickness

If you want to play with bidirectional pipes, I suggest you to have a binary that you control (e.g. a shell script, or a C-program from you) where you can flush the stdout stream.

Cheers,
Lo�c

Do you have gmp installed?
factor source is attached.... This would be the simple way to deal with your problem - as Loic suggested.