Question

There are plenty of fds that can't be read from (for example, a listening socket). How do I test whether a read(2) on the fd will return EINVAL, without risking taking data out?

Things that don't work:

  1. We could do a read() with a zero-byte buffer passed. This is ruled out though:

    Implementations are allowed, but not required, to perform error checking for read() requests of zero bytes. [From POSIX 1003.1-2008]

  2. We might be tempted to call select() on the descriptor. Unfortunately, select() has very overloaded semantics for the readable set, so will tell that an fd is "readable" when in fact it's an error to call read() on it (for example, a listening socket will be marked "readable" but needs accept(), not read(), and there are other non-portable examples like event or kqueue fds).

  3. (Sort of works) Read manpages for every platform you compile on test the fd with specific system calls to produce a function that looks roughly like:

    int isReadable(int fd)
    { return isActiveSocket(fd) || isFifo(fd) || isRegFile(fd) ||
               isEventFd(fd) || ... /* more pain */ }
    
  4. (Sort of works) Note that read() itself doesn't necessarily give you a nice answer as to whether the fd was the right type for the system call! Surprisingly, EINVAL is unspecified in POSIX for read() (except on STREAMS), but is given to you on linux ("EINVAL: fd is attached to an object which is unsuitable for reading") and rather mysteriously on BSD ("EINVAL: The pointer associated with [the fd] was negative.").

Scenarios

Someone launches your application, and you want to know whether the fd with value 0 is bogus (eg a listening socket) or whether it's ever going to possible to read from it. You'd like to do without trying an actual read() a) because that blocks, b) because after taking the data out you can't stuff it back in.

Was it helpful?

Solution

No. You quoted the relevant part of the specification yourself.

Indeed, a read may fail at any time for any number of reasons. Testing for "will read succeed", followed by a read, merely introduces a race condition - the situation may change in between the two calls.

You need to write your application in such a way that a failed read is handled appropriately. If you do that, you won't usually need to care about testing beforehand, and can simply use select to determine when data is (probably) available.

OTHER TIPS

Is there a reason you cannot use fcntl with the F_GETXFL option?

int isread(int fd)
{
   int o_accmode=0;
   int rc=fcntl(fd, F_GETXFL, &o_accmode);
   if(rc == -1 )
       return rc;
   rc=(o_accmode & O_ACCMODE);
   return (rc==O_RDONLY || rc==RDWR);
}

Probably the simplest is to use select:

int
is_valid_descriptor( int fd )
{
  fd_set rd;
  struct timeval t = { 0, 0 };
  FD_ZERO( &rd );
  FD_SET( fd, &rd );
  return -1 != select( fd + 1, &rd, NULL, NULL, &t );
}

Note that this does not indicate whether or not a read will block (it's not clear what you mean by "succeed").

Assuming you're okay with calling recv() instead of read() (they are typically interchangeable for sockets), you can supply MSG_PEEK in the flags argument and the recv() call will act as usual except that it won't actually remove any bytes from the socket's internal buffer. You might be able to use that to catch errors without altering the socket's internal state.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top