| Summary: | Non-standard behavior of fread(3) | ||||||
|---|---|---|---|---|---|---|---|
| Product: | Base System | Reporter: | tobez <tobez> | ||||
| Component: | bin | Assignee: | rnordier | ||||
| Status: | Closed FIXED | ||||||
| Severity: | Affects Only Me | ||||||
| Priority: | Normal | ||||||
| Version: | 4.0-CURRENT | ||||||
| Hardware: | Any | ||||||
| OS: | Any | ||||||
| Attachments: |
|
||||||
Responsible Changed From-To: freebsd-bugs->rnordier I'll handle this. As the author of the original code, I just want to add that, while the change seems reasonable, the effect of attempting to read from a write-only stream (such as stdout) is undefined. It would be legal (from an ANSI C standpoint) to call abort() here, for instance, or to write messages to stderr, or randomly corrupt memory, etc. In other words, this is not a bug in stdio, it is a bug in the program misusing stdio. What stdio should do about such programs is the real question. (The same issue applies to writing to read-only streams.) Chris > As the author of the original code, I just want to add that, > while the change seems reasonable, the effect of attempting to > read from a write-only stream (such as stdout) is undefined. > It would be legal (from an ANSI C standpoint) to call abort() > here, for instance, or to write messages to stderr, or randomly > corrupt memory, etc. What wording in the standard are you relying on, or just omission of any explicit definition of behaviour? > In other words, this is not a bug in stdio, it is a bug in > the program misusing stdio. What stdio should do about such > programs is the real question. The concept that, where a stdio I/O function returns EOF, one of the feof and ferror will always return nonzero is well-established in the standard. Leaving aside the special case of mixed I/O operations on an update stream without fflush or a file positioning function intervening, the desired behaviour seems somewhat obvious. As an example of interpretation -- while this can naturally not be regarded as stipulative -- one of the "cursory test programs" (conform/tstdio2.c) which accompany the Plauger implementation of the Standard C library make a special case of this: pf = fopen(tname, "w"); [ ... ] assert(fgetc(pf) == EOF && feof(pf) == 0 && ferror(pf) != 0); -- Robert Nordier >What wording in the standard are you relying on, or just omission >of any explicit definition of behaviour? The latter. §4.9.3, p. 127, ll. 9--11, say that: All input takes place as if characters were read by successive calls to the |fgetc| function; all output takes place as if characters were written by successive calls to the |fputc| function. In §4.9.7.1 and §4.9.7.3, fgetc and fputc are described in terms of their effects on "input streams" and "output streams" respectively, without mentioning what happens if fgetc is applied to an output stream, or fputc to an input stream. Nothing else seems to say anything one way or another about "mismatched" operations, so it would seem to fall to §1.6. (All numbering is from the original ANSI edition.) Since my stdio takes pains (that other stdios do not) to handle mixed fgetc and fputc "correctly" even without an intervening fseek/fflush/rewind/etc. call, it does seem as though it should handle fgetc-on-output-only-stream "correctly" as well, whatever that means. :-) (Other stdios have a single "count" field, so that if you do: FILE *fp = fopen("foo", "w"); assert(fp != NULL); printf("putc returns %d\n", putc(97, fp)); printf("getc returns %d\n", getc(fp)); the first one returns 97 and the second typically returns 0. The putc sets fp->_cnt, or whatever it is named, to something like 8191, and the subsequent getc winds up doing: --fp->_cnt >= 0 ? *fp->_ptr++ : __fill(fp) which reduces to: --fp->_cnt, *fp->_ptr++ which reads from an uninitialized byte in the malloc'ed buffer associated with file "foo".) >As an example of interpretation -- while this can naturally not be >regarded as stipulative -- one of the "cursory test programs" >(conform/tstdio2.c) which accompany the Plauger implementation of >the Standard C library make a special case of this: > pf = fopen(tname, "w"); > [ ... ] > assert(fgetc(pf) == EOF && feof(pf) == 0 && ferror(pf) != 0); I believe this particular test will fail on many systems if you insert an fputc first (as described above). (It is possible that f{ge,pu}tc will check the mode, while {ge,pu}tc will not, and of course all I have ever seen is the #defines in stdio.h, not the actual code for the function variants. Might be interesting to test, though.) Chris State Changed From-To: open->closed Patch applied: src/lib/libc/stdio/refill.c rev. 1.7. |
The following little program: #include <stdio.h> #include <errno.h> int main(int argc, char **argv) { char buf; int r = fread(&buf,1,1,stdout); printf("%d ferror=%d feof=%d errno=%d\n", r, ferror(stdout), feof(stdout), errno); return 0; } produces this output: 0 ferror=0 feof=0 errno=9 I am not exactly sure that this is a bug, strictly speaking. However, there are certain indications that this behavior is non-standard. Let me explain this a bit. The issue was raised when perl5 developers have added a specific test in the most recent developer's version of perl (5.005_58, to be precise). It quickly turned out that libc on different platforms produce very different results. To my knowledge, several versions of Linux's glibc, as well as NetBSD libc and OSF1 4.0 libc do the same thing as we do. It was reported on p5p mailing list that many other systems behave differently. For example, the program above, being run on HP-UX B.10.20, produce 0 ferror=32 feof=0 errno=9 Gurusamy Sarathy communicated this with glibc developers, and they decided to change the behavior of glibc in future versions. The reason was (I quote only relevant part of the message Sarathy forwarded to p5p): --- quote start --- Message-Id: <u8r9m23tin.fsf@arthur.rhein-neckar.de> Date: 21 Jul 1999 17:35:44 +0200 From: Andreas Jaeger <aj@arthur.rhein-neckar.de> To: libc-alpha Mailinglist <libc-alpha@sourceware.cygnus.com>, gsar@activestate.com Subject: Re: [Gurusamy Sarathy <gsar@activestate.com>] ferror() after fread() on a FILE* ***opened for write [snipped by tobez] The ISO C9x draft I've got here, mentions as return value for fread: [#3] The fread function returns the number of elements successfully read, which may be less than nmemb if a read error or end-of-file is encountered. If size or nmemb is zero, fread returns zero and the contents of the array and the state of the stream remain unchanged. fread returned 0 which is less than 1 - therefore either a read error or end-of-file is encountered. But feof and ferror tell me that neither is encountered. [snipped by tobez] --- quote end --- This interpretation is also consistent with FreeBSD's man 3 fread: If an error occurs, or the end-of-file is reached, the return value is a short object count (or zero). So I think that it might be a good idea to change the behavior of our libc, too. In the Fix section I provide a patch which changes the behavior of fread() to set __SERR together with setting EBADF. How-To-Repeat: Compile and run the program from the Description section.