socketsunixposixxsi

Can I test if a file descriptor is of the right type for read()?


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.


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.