What is going on in Unix with errno's limited nature
If you read manual pages, such as Linux's errno(3), you'll soon discover an important and peculiar seeming limitation of looking at errno. To quote the Linux version:
The value in errno is significant only when the return value of the call indicated an error (i.e., -1 from most system calls; -1 or NULL from most library functions); a function that succeeds is allowed to change errno. The value of errno is never set to zero by any system call or library function.
This is also more or less what POSIX says in errno, although in standards language that's less clear. All of this is a sign of what has traditionally been going on behind the scenes in Unix.
The classical Unix approach to kernel system calls doesn't return
multiple values, for example the regular return value and errno.
Instead, Unix kernels have traditionally returned either a success
value or the errno value along with an indication of failure, telling
them apart in various ways (such as the PDP-11 return method). At the C library level, the simple approach taken
in early Unix was that system call wrappers only bothered to set
the C level errno
if the kernel signaled an error. See, for
example, the V7 libc/crt/cerror.s
combined with libc/sys/dup.s,
where the dup() wrapper only jumps to cerror
and sets errno
if
the kernel signals an error. The system call wrappers could all
have explicitly set errno
to 0 on success, but they didn't.
The next issue is that various C library calls may make a number
of system calls themselves, some of which may fail without the
library call itself failing. The classical case is stdio checking
to see whether stdout is connected to a terminal and so should be
line buffered, which was traditionally implemented by trying to do
a terminal-only ioctl() to the file descriptors, which would fail
with ENOTTY on non-terminal file descriptors. Even if stdio did a
successful write() rather than only buffering your output, the
write() system call wrapper wouldn't change the existing ENOTTY
errno
value from the failed ioctl(). So you can have a fwrite()
(or printf() or puts() or other stdio call) that succeeds while
'setting' errno
to some value such as ENOTTY.
When ANSI C and POSIX came along, they inherited this existing
situation and there wasn't much they could do about it (POSIX
was mostly documenting existing practice). I believe
that they also wanted to allow a situation where POSIX functions
were implemented on top of whatever oddball system calls you wanted
to have your library code do, even if they set errno
. So the only
thing POSIX could really require was the traditional Unix behavior
that if something failed and it was documented to set errno on
failure, you could then look at errno
and have it be meaningful.
(This was what existing Unixes were already mostly doing and specifying it put minimal constraints on any new POSIX environments, including POSIX environments on top of other operating systems.)
(This elaborates on a Fediverse post of mine, and you can run into this in non-C languages that have true multi-value returns under the right circumstances.)
|
|