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

Jordan K. Hubbard jkh at queasyweasel.com
Tue Mar 2 01:07:01 PST 2004


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

#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
fwrite errno = 0, rc = 0

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:

--- 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;
                 return (EOF);
+       }

  #define        MIN(a, b) ((a) < (b) ? (a) : (b))
  #define        COPY(n)   (void)memcpy((void *)fp->_p, (void *)p, 
(size_t)(n))

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.   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)!

This is your libc.  This is your libc on SUSv2*.  Any questions?

* References:
http://www.opengroup.org/onlinepubs/007908799/xsh/fwrite.html
http://www.opengroup.org/onlinepubs/007908799/xsh/fputs.html

--
Jordan K. Hubbard
Engineering Manager, BSD technology group
Apple Computer


More information about the freebsd-hackers mailing list