Search code examples
cunixfgetsio-buffering

Standard I/O stream -- fgets() buffering type


The book "advanced programming in unix environment" discussed pipe in chapter 15, it shows that we should pay attention to the buffering type when deal with standard I/O functions.

The buffering types for different opened standard I/O streams are (discussed in chapter 5 of the book):

  • standard error is unbuffered
  • streams connected to terminal devices is line-buffered
  • all other streams are fully-buffered

When parent/child connect to a pipe, the end (which should be a FILE * type object, according to the interface) that they used to communicate should be fully-buffered according to the rule list above (since it's a stream connected to pipe). But the behavior of sample code from that chapter seems to be something NOT fully-buffered.

Here is the sample code:

myuclc.c:

1   #include "apue.h"
2   #include <ctype.h>

3   int
4   main(void)
5   {
6       int     c;

7       while ((c = getchar()) != EOF) {
8           if (isupper(c))
9               c = tolower(c);
10          if (putchar(c) == EOF)
11              err_sys("output error");
12          if (c == '\n')
13              fflush(stdout);
14      }
15      exit(0);
16  }

popen1.c:

1   #include "apue.h"
2   #include <sys/wait.h>

3   int
4   main(void)
5   {
6       char    line[MAXLINE];
7       FILE    *fpin;
8
9       if ((fpin = popen("myuclc", "r")) == NULL)  // "myuclc" is executable file compile-link by "myuclc.c"
10          err_sys("popen error");
11      for ( ; ; ) {
12          fputs("prompt> ", stdout);
13          fflush(stdout);
14
15          if (fgets(line, MAXLINE, fpin) == NULL) /* read from pipe */
16              break;
17          if (fputs(line, stdout) == EOF)
18              err_sys("fputs error to pipe");
19      }
20      if (pclose(fpin) == -1)
21          err_sys("pclose error");
22      putchar('\n');
23      exit(0);
24  }

So my question is : fgets() in line 15 of popen1.c should be fully-buffered according to the buffering rules, why does it act like line-buffered or unbuffered:

Furthermore, I also tried to setvbuf() before fgets() to specifically set buffering type to _IOFBF (fully-buffered) of fpin, still not work.

prompt> abc
abc
prompt> ABC
abc
prompt> efg
efg
prompt> EFG
efg
prompt>

Solution

  • In myuclc.c you perform an explicit flush on every newline:

    12          if (c == '\n')
    13              fflush(stdout);
    

    This causes the stream to be manually line-buffered. Whenever you flush the pipe, the process on the other end will be unblocked and will read whatever was in the buffer at the time.

    The "buffering rules" talk about when this flushing happens automatically. Unbuffered streams are automatically flushed after every write command (fprintf, fputc, etc.). Line-buffered streams are automatically flushed whenever a newline character is written to the stream.

    And all streams are flushed when the buffer fills up, when the stream is closed, or when the writer performs an explicit flush