Search code examples
linuxunixfile-descriptorerrnopidfd

How can I force a call to read(2) to return EINVAL?


Context

I'm writing code that emulates one aspect of pidfds on platforms that don't support them (old Linux, other Unix).

I'm doing this a) in order to test some pidfd-related code on very old platforms and b) as a personal challenge. Mostly as a personal challenge/for fun.

I explicitly am not trying to re-implement all pidfd functionality; I don't actually care about processes//proc/PIDs at all. Instead, I'm trying to emulate just one part of pidfds' features: the fact that a pidfd's file descriptor has three behaviours:

  1. If opened in blocking mode, block on read.
  2. If opened or fcntl'd to nonlocking mode, return EWOULDBLOCK on read.
  3. Explicitly return EINVAL on read.

That third part is the tricky part, and an unusual aspect of pidfds at this time (very little else intentionally returns EINVAL for read()).

Question

I want to induce that behaviour on some other kind of file descriptor (doesn't matter what kind). Specifically, I want a file descriptor that:

  • By default, obeys the usual O_NONBLOCK (or not) behaviour.
  • After I do something to it, all calls to read(2) that would not ordinarily return an error will instead return EINVAL, regardless of the parameters to read(2).

This turns out to be surprisingly tricky.

What I've tried

read(2)'s manual page for EINVAL says it's returned if:

fd is attached to an object which is unsuitable for reading; or the file was opened with the O_DIRECT flag, and either the address specified in buf, the value specified in count, or the file offset is not suitably aligned.

...or if an invalid buffer size is passed to read(2) on a timerfd.

Neither the timerfd case nor the O_DIRECT case satisfy my requirements, as they only return EINVAL if certain arguments are passed to read(2), and I want it to be returned in all non-erroring cases.

I've also tried signalfds (couldn't find a case that returned EINVAL on read), inotify FDs (same), and various permutations of forcibly close(2)d or shutdown(2) pipes, FIFOs, and anonymous sockets.

I'm not that well-versed in POSIX trivia, though, so it's entirely possible I missed something that allows a file descriptor type I've already experimented on to return EINVAL.

Bonus points if there's a solution that works on BSD/MacOS, but really anything is better than nothing, even if Linux-specific or kernel-version-specific.

I've tried some of the other tricks on this question, but they largely generate error codes other than EINVAL.


Solution

  • The O_DIRECT case can actually satisfy your requirement, because the file offset isn't one of the arguments to read. If you do int fd = open("/bin/sh", O_RDONLY|O_DIRECT); lseek(fd, 1, SEEK_SET);, then read(fd, buf, count); should fail with EINVAL no matter what buf and count are.