There's no portable way to turn a file descriptor read only or write only
serious but undoubtedly stupid question: why does writing to file descriptor 0 in Linux and OS X work?
My first impulse was to say 'lazy code that starts with a general read/write file descriptor and doesn't bother to make it read only when the fd becomes a new process's standard input', but I decided to check the manual pages first. Much to my surprise it turns out that in Unix there is no portable way to turn a read/write file descriptor into a read-only or write-only one.
In theory the obvious way to do this is with
O_WRONLY as applicable). In practice, this is
explicitly documented as not working on both Linux and FreeBSD; on
them you're not allowed to affect the file access mode, only things
O_NONBLOCK. It's not clear if this behavior is compliant
with the Single Unix Specification for
but either way it's how a very large number of real systems behave
in the field today so we're stuck with it.
This means that if you have, say, a shell, the shell cannot specifically restrict plain commands that it starts to have read-only standard input and write-only standard output and standard error. The best it can do is pass on its own stdin, stdout, and stderr, and if they were passed to the shell with full read/write permissions the shell has to pass them to your process with these permissions intact and so your process can write to fd 0. Only when the shell is making new file descriptors can it restrict them to be read only or write only, which means pipelines and file redirections.
Further, it turns out that in a fair number of cases it's natural
to start out with a single read/write file descriptor (and in a few
it's basically required). For one example, anything run on a
pseudo-tty that was set up through
openpty() will be this way,
openpty() API only gives you a single file descriptor for
the entire pty and obviously it has to be opened read/write. There
are any number of other cases, so I'm not going to try to run through
(At this point it may also have reached the point of backwards
compatibility, due to
ioctl() calls on terminals and ptys. I'm
honestly not sure of the rules for what terminal ioctls need read
and/or write permissions on the file descriptors, and I bet a
bunch of other people aren't either. In that sort of
environment, new programs that set up shells might be able to
restrict fds 0, 1, and 2 to their correct modes but don't dare do
so lest they break various shells and programs that have gotten
away with being casual and uncertain.)
PS: If you want to see how a shell or a command's descriptors are
set up, you can use
lsof. The letter after the file descriptor's
number will tell you if it's read, write, or
u for r/w.