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

Reply via email to