Another conformance question... This time fputs().

Bruce Evans bde at zeta.org.au
Tue Mar 2 04:04:00 PST 2004


On Tue, 2 Mar 2004, Jordan K. Hubbard wrote:

> I submit for your consideration the following test program and the
> wonderful variety of test results it produces:

freebsd-standards is a better list for C and POSIX conformance.

> #include <stdio.h>
> #include <errno.h>
>
> int main(int argc, char *argv[])
> {
>   int rc;
>
>   FILE *fp=fopen("/dev/zero", "r");
>   rc = fputs("Hello world\n", fp);
>   printf("errno = %d, rc = %d\n", errno, rc);
>   errno = 0;
>   rc = fwrite("Hello world again\n", 1, 18, fp);
>   printf("fwrite errno = %d, rc = %d\n", errno, rc);
>   fclose(fp);
> }
>
> On Red Hat Linux 9.0, it outputs the following:
>
> errno = 9, rc = -1
> fwrite errno = 9, rc = 0
>
> Just to save you the grepping, errno #9 is EBADF, "bad file number".
> Now we KNOW that the mode on that fopen is (a) on a device which
> doesn't allow writing and (b) of the wrong open mode ("r" rather than
> "w"), but this discussion concerns "the right thing to do" when faced
> with just these sorts of bogus situations and one could probably argue
> that Linux returns the wrong errno here, but it does set errno.
>
> What does FreeBSD do?  It does this:
>
> errno = 0, rc = -1

Setting errno is a POSIX extension.  It is not required in C9[09].
FreeBSD apparently only implements the C requirement here.  FreeBSD's
man page doesn't claim to set errno for fputs() (it "may" set it,
and probably does set it if it calls write(2), but the library is
too smart here and doesn't call write(2)).

> fwrite errno = 0, rc = 0

Similarly, except the FreeBSD man page is imprecise to the point of
brokenness.  It says that errno is set if a write error occurred, but
this only happens if there was a write(2) and the library is too smart
here, so errno is whatever garbage was already in it.

fwrite() has interesting behaviour for short but non-null writes.
POSIX.1-1990 and POSIX.1-1996 spell out that such writes shall set the
error indicator on the stream but shall not change errno.  This
corresponds to write(2) not setting errno for short writes.  stdio
would have to try to write the residual bytes after a short write and
write() might then return -1 and set errno, and the standard is saying
that fwrite() shall not set errno in this case.  POSIX.1-2001 seems
to be missing this requirement.  The old standards say this in a poorly
worded section which I first read as saying that this happens for all
failing fwrite()s.

> Given that it's just not kosher to write on a read-only fp and get no
> error back at all, I would argue (though not passionately) for the
> following diff to libc:

The error is in the return code where it belongs (getting back fewer
elements than requested for fwrite() means an error).  Portable programs
wouldn't test errno.  However, POSIX requires setting errno.

>
> --- stdio/fvwrite.c     22 Mar 2002 21:53:04 -0000      1.15
> +++ stdio/fvwrite.c     2 Mar 2004 08:40:25 -0000
> @@ -43,6 +43,7 @@
>   #include <stdio.h>
>   #include <stdlib.h>
>   #include <string.h>
> +#include <errno.h>
>   #include "local.h"
>   #include "fvwrite.h"
>
> @@ -67,8 +68,10 @@
>          if ((len = uio->uio_resid) == 0)
>                  return (0);
>          /* make sure we can write */
> -       if (cantwrite(fp))
> +       if (cantwrite(fp)) {
> +               errno = EACCES;

Should be EBADF, as in Linux.

>                  return (EOF);
> +       }

Otherwise, I agree with this change except for its style bugs (unsorting
of the #includes and tab lossage).

> That gives us this behavior for our little test program:
>
> errno = 13, rc = -1
> fwrite errno = 13, rc = 0
>
> In both cases, we get EACCES for fputs() or fwrite() attempts on a
> read-only file pointer pointing to a read-only device, something we'd
> expect to get "permission denied" for I think.

No, EBADF is correct since it is what write(2) would return if it were
attempted.  This is a POSIX requirement on write(2) (EBADF means
"not a valid file descriptor open for writing" here, and there seems to
be no way to distinguish "not open" and "not open for writing"; EACCES
means "the write was on a socket and the calling process doesn't have
appropriate privilege").  POSIX also generally requires stdio errno's
to be the same as for the underliying functions (write() here).

> In the case where we
> open the fp for write access, the FreeBSD behavior is unchanged:
>
> errno = 19, rc = 0
> fwrite errno = 0, rc = 18
>
> Which gives us ENODEV for the fputs(3) and no error for the fwrite(3).
> I'm not sure why an error is returned at all in the fputs(3) case since
> it seems perfectly valid to write onto /dev/null and simply have the
> data be discarded, but that error is coming back from somewhere deeper
> of the bowels of stdio and has nothing to do with my proposed diff in
> any case.  Red Hat Linux, interestingly enough, returns errno 25 in
> this case (ENOTTY)!

errno is garbage after most C library calls (ones that aren't specified
to set it).  I think it is guaranteed to be unchanged by many stdio
calls in POSIX (fputs() and fwrite() but not fopen()).  Thus it has
the same near-garbage value that it had before the calls.  ENOTTY is
a common garbage value.  It is left by isatty() in stdio initialization
for a stream in many implementations.

ktracing shows that errno 19 is another FreeBSD bug.  It is returned
by ioctl(...TIOCGETA...) on /dev/zero, presumably as part of isatty()
(which is called by fputs()).  ENODEV means that the device doesn't
support any ioctls, but that is bogus since it supports some.  The
error should be ENOTTY as in Linux to indicate that the device doesn't
support the specific ioctl.  ENODEV is also not documented in the ioctl
man page.

Bruce


More information about the freebsd-arch mailing list