kyb
kyb

Reputation: 8141

Why poll() returns immediately on regular files and blocks on fifo?

I checked this code several times and cannot understand why does poll() return immediately?

Here file is opened for read and should wait for event. How to make it wait for input?

#include <iostream>

#include <poll.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>


using namespace std;

ssize_t read_out_to_the_end(int fd){
   char chunk[1024];
   ssize_t ret = 0, n;
   while((n = ::read(fd, chunk, sizeof chunk)) > 0){
      ret += n;
      cerr << "read chunk: " << n << " | ";
      cerr.write(chunk, n);
      cerr << endl;
   }
   if (n < 0) {
       cerr << "err in read" << endl;
   }
   else if (ret == 0){
      cerr << "nothing to read" << endl;
   }
   return ret;
}

int main() {
   int bininfd = open("bin-in", O_RDONLY | O_CREAT);//, 0644/*S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH*/);
   if (bininfd < 0) {
      perror("err in open(binin)");
      return -1;
   }

   struct pollfd pollfds[] = {
         {bininfd, POLLIN, 0},
   };
   auto&[pfd] = pollfds;

   while (1) {
      pfd.revents = 0;  // cleanup, shouldn't it be redundant
      int pollret = poll(pollfds, 1, -1);
      if (pollret > 0) {
         if (pfd.revents & POLLIN) {
            cerr << "(pfd.revents & POLLIN)" << endl;
            read_out_to_the_end(pfd.fd);
         }
      } else if (pollret == 0) {
         cerr << "poll timed out" << endl;
         continue;
      } else {
         cerr << "check for error" << endl;
         continue;
      }
   }
}

the output is

(pfd.revents & POLLIN)
nothing to read
(pfd.revents & POLLIN)
nothing to read
(pfd.revents & POLLIN)
nothing to read
(pfd.revents & POLLIN)
nothing to read
(pfd.revents & POLLIN)
............... etc ....................

live example

UPDATE:

  1. read_out_to_the_end() fixed. Thanks to @RemyLebeau
  2. it works (blocks) on fifos as I expect, but not on regular files. Why?

Upvotes: 0

Views: 1980

Answers (2)

user10678532
user10678532

Reputation:

poll() or select() never block on regular files. They always return a regular file as "ready". If you want to use poll() to do what tail -f does, you're on the wrong track.

Quoting from the SUSv4 standard:

The poll() function shall support regular files, terminal and pseudo-terminal devices, FIFOs, pipes, sockets and [OB XSR] STREAMS-based files. The behavior of poll() on elements of fds that refer to other types of file is unspecified.

Regular files shall always poll TRUE for reading and writing.

Since using poll() or select() on regular files is pretty much useless, newer interfaces have tried to remedy that. On BSD, you could use kqueue(2) with EVFILT_READ, and on Linux inotify(2) with IN_MODIFY. The newer epoll(7) interface on Linux will simply error out with EPERM if you try to watch a regular file.

Unfortunately, neither of those is standard.

Upvotes: 3

Remy Lebeau
Remy Lebeau

Reputation: 595991

read_out_to_the_end() has several issues:

  • ret is uninitialized.

  • The while loop is incrementing n when it should be assigning it instead. But then if the while loop hits the EOF, if( n == 0) will be true even if data was actually read before hitting EOF.

  • chunk may be null-terminated, but it may also receive nulls too, depending on the input data. So it should not be written to cerr (why not cout?) using operator<<, use cerr.write() instead so that you can pass it the actual number of bytes read.

Try this instead:

ssize_t read_out_to_the_end(int fd){
   char chunk[1024];
   ssize_t ret = 0, n;
   while((n = ::read(fd, chunk, sizeof chunk)) > 0){
      ret += n;
      cerr << "read chunk: " << n << " | ";
      cerr.write(chunk, n);
      cerr << endl;
   }
   if (n < 0) {
       cerr << "err in read" << endl;
   }
   else if (ret == 0){
      cerr << "nothing to read" << endl;
   }
   return ret;
}

int main() {
   int bininfd = open("bin-in", O_RDONLY | O_CREAT);//, 0644/*S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH*/);
   if (bininfd < 0) {
      perror("err in open(binin)");
      return -1;
   }

   pollfd pfd = {};
   pfd.fd = bininfd;
   pfd.events = POLLIN;

   while (true) {
      pfd.revents = 0;  // cleanup, shouldn't it be redundant
      int pollret = poll(&pfd, 1, -1);
      if (pollret > 0) {
         if (pfd.revents & POLLIN) {
            cerr << "(pfd.revents & POLLIN)" << endl;
            read_out_to_the_end(pfd.fd);
         }
      } else if (pollret == 0) {
         cerr << "poll timed out" << endl;
         continue;
      } else {
         cerr << "poll error " << errno << endl;
         break;
      }
   }
}

Also, on a side note, the open() documentation says:

The mode argument specifies the file mode bits be applied when a new file is created. This argument must be supplied when O_CREAT or O_TMPFILE is specified in flags; if neither O_CREAT nor O_TMPFILE is specified, then mode is ignored. The effective mode is modified by the process's umask in the usual way: in the absence of a default ACL, the mode of the created file is (mode & ~umask). Note that this mode applies only to future accesses of the newly created file; the open() call that creates a read-only file may well return a read/write file descriptor.

Upvotes: 1

Related Questions