4
votes

First of all sorry if my English won't be fluent and clear.

I'm working on understanding pipes and communication between processes. I have tried to implement two c programs, the one writes into a certain pipe from what he reads from the standard input and the other one waits until the pipe opens and reads from it and prints to the standard output until EOF.

Here is the code for the writer pipe:

  fd = open(filename, O_RDWR);
  if(fd == -1) print_error();

  while(fgets(buffer, BUFFER_SIZE, stdin) != NULL) {
    if(write(fd, buffer, BUFFER_SIZE) == -1) print_error();
  }

and here is the code for the reader pipe:

while(1) {
    if((fd = open(filename, O_RDWR)) == -1) {
      if(errno == ENOENT) sleep(1);
      else print_error();
    }
    else {
      while(read(fd, buffer, BUFFER_SIZE) != 0) {
        fprintf(stdout, "%s", buffer);
      }
    }
  }

The thing is that when I run those two programs concurrently the basic concept works, I write something to the standard input in the writer program and I see in the other terminal that the reader program prints it to the standard output. The problem is that when I send EOF by hitting CTRL + D for the writer program the reader program still waits for input, and I know for sure that it isn't because the while(1), I saw in the debugger that the read syscall is just waiting to input and didn't understand that we got EOF, the line : read(fd, buffer, BUFFER_SIZE) didn't evaluate even though there is no input.

I hope that I gave all the data needed to solve the problem, anyone have any ideas what gone wrong?

2

2 Answers

2
votes

If fgets() detects EOF it returns either what already is in its read buffer or just returns NULL.

In the latter case you want to notify the reading end that the transmission is over by for example just closing the pipe.

  int fd = open(filename, O_RDWR);
  if (fd == -1) 
  {
    perror("open() failed");
  }
  else
  {    
    while (fgets(buffer, BUFFER_SIZE, stdin) != NULL) 
    {
      if (write(fd, buffer, BUFFER_SIZE) == -1) 
      {
        perror("write() failed");
        break;
      }
    }
    if (ferror(stdin))
    {
       perror("fgets() failed");
    }

    close(fd); /* The other end's blocking call to read() would return 0. */
  }

In no case something like EOF is read. EOF is not a character but a state.

From man 3 read:

If some process has the pipe open for writing [...] read() shall block the calling thread until some data is written or the pipe is closed by all processes that had the pipe open for writing.


Also this code does not cover read() failing:

  while(read(fd, buffer, BUFFER_SIZE) != 0) {
    fprintf(stdout, "%s", buffer);
  }

It should look for example like this:

  ssize_t result;
  while (0 != (result = read(fd, buffer, BUFFER_SIZE))) 
  {
    if (0 > result)
    {
      if ((EINTR == errno) || (EAGAIN == errno)) 
      {
        continue;
      }

      perror("read() failed");
      break;
    }

    fprintf(stdout, "%s", buffer);
  }

Even more this loop ...

while(1) {
  if((fd = open(filename, O_RDWR)) == -1) {
    if(errno == ENOENT) sleep(1);
    else print_error();
  }
  ...
}

... misses to close() fd, before (re-)opening it.


If the reader does not write let it open the pipe by specifying O_RDONLY instead of RDWR.

1
votes

read can also return -1 on error. This might be the case if your writer process doesn't close his output stream properly. So you should definitively check for read <= 0.

But I hope that you are also aware that your surrounding while(1) loop will make it immediately enter the read call for a next round, so it might also be that you see just that.