Since at least glibc-2.4, fgets and feof have not adhered to the ANSI C standard. They were fine in glibc-2.3.5 fgets should give an EOF upon reaching an end of file. feof should tell whether the file is over. The attached C code shows that this is not the case. Reproducible: Always Steps to Reproduce: Compile and run the following C fragment. /* GPL (C) magnus-swe@telia.com */ #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> /* RedHat/Fedora people have destroyed the fgets() * function in newer versions of glibc's iofgets.c code. * * Using fgets() on a file smaller then 2 bytes * and it will loop forever. * Eric.S and Jakub Jelinek said this is NOTABUG when * I submitted it to their bugzilla. * * This is very odd since everyone else ive talked to * thinks this is pure evil and non standards compliant. * feof() doesnt work in these cases either. */ int main(int argc, char *argv[]) { FILE *fp; long file_size; char *line; system("echo > /tmp/null_file"); if((fp=fopen("/tmp/null_file","r"))==NULL) { printf("Error opening file.\n"); return 0; } printf("Testing a big bug in glibc.\n"); printf("This test should not take many milliseconds.\n"); printf("Hit ctrl+c if it loops endlessly.\n\n"); printf("Starting in 3 seconds...\n"); sleep(3); fseek(fp, 0, SEEK_END); file_size = ftell(fp); rewind(fp); line = malloc(file_size+1); /* Possible workaround */ /* if( file_size > 1 ) */ while(fgets(line, file_size, fp)!=NULL) { /* Possible workaround */ /* if( line && ! *line ) break; */ printf("Line: %s\n", line); /* feof() doesnt work either */ if( feof(fp) ) break; printf("Reading file of size: [%d] Byte(s).\n", file_size); } fclose(fp); free(line); return 0; } Actual Results: Infinite loop of "Reading file of size: [1] Byte(s). Line:" Expected Results: The program should end almost instantly. Red Hat, the primary maintainer of glibc, claims that this is not a bug. man fgets disagrees. I neither found this bug nor wrote the test code, I am simply passing the information along.
Bits of `emerge --info glibc` on my system, where this code fails Portage 2.1.2.7 (default-linux/amd64/2007.0, gcc-4.1.2, glibc-2.5-r3, 2.6.20-gentoo-r8 x86_64) ================================================================= System Settings ================================================================= System uname: 2.6.20-gentoo-r8 x86_64 AMD Athlon(tm) 64 Processor 3800+ Gentoo Base System release 1.12.9 Timestamp of tree: Sat, 16 Jun 2007 23:50:01 +0000 ccache version 2.4 [enabled] dev-lang/python: 2.4.4-r4 dev-python/pycrypto: 2.0.1-r5 dev-util/ccache: 2.4-r7 sys-apps/sandbox: 1.2.17 sys-devel/autoconf: 2.13, 2.61 sys-devel/automake: 1.4_p6, 1.5, 1.6.3, 1.7.9-r1, 1.8.5-r3, 1.9.6-r2, 1.10 sys-devel/binutils: 2.16.1-r3 sys-devel/gcc-config: 1.3.16 sys-devel/libtool: 1.5.22 virtual/os-headers: 2.6.17-r2 ACCEPT_KEYWORDS="amd64" AUTOCLEAN="yes" CBUILD="x86_64-pc-linux-gnu" ... sys-libs/glibc-2.5-r3 was built with the following: CFLAGS="-O2 -fno-strict-aliasing -march=athlon64 -mtune=k8 -pipe" CXXFLAGS="-O2 -fno-strict-aliasing -march=athlon64 -mtune=k8 -pipe"
The code is flawed. The size in bytes of /tmp/null_file is 1, and this is stored in file_size. With fgets(line, file_size, fp) -- file_size is 1. fgets reads file_size-1 bytes, hence 0. Thus, end of file is never reached, since you're never reading any data!
After a conversation in #gentoo and some testing, it was observed that the program works on glibc-2.3.5 because on this library, fgets(buffer, 1, fp) returns NULL - without setting either the end-of-file or error indicators on the stream. This appears to have changed in more recent glibc, where zero bytes are quite happily "read" and a zero-length string is written to the buffer. The C standard has the following to say about fgets: char *fgets(char *restrict s, int n, FILE *restrict stream) Description The fgets function reads at most one less than the number of characters specified by n from the stream pointed to by stream into the array pointed to by s. No additional characters are read after a new-line character (which is retained) or after end-of-file. A null character is written immediately after the last character read into the array. Returns The fgets function returns s if successful. If end-of-fïle is encountered and no characters have been read into the array, the contents of the array remain unchanged and a null pointer is returned. If a read error occurs during the operation, the array contents are indeterminate and a null pointer is returned. footnote: An end-of-file and a read error can be distinguished by use of the feof and ferror functions. This makes no indication that fgets should fail under such a read. I would conclude that the bug lies in the old behaviour of glibc, rather than the new. Of course, a request to read 0 bytes makes little sense in the first place, and is unlikely to be encountered in practise.
Created attachment 122293 [details] Test case demonstrating correct behaviour. This program demonstrates correct use of fgets, works on files of length 0, 1, and >1 bytes, and with a small modification (described in comments), will demonstrate the wierd behaviour on glibc-2.3.5. Make sure to create /tmp/null_file before running.
Created attachment 122294 [details] *Correct* test case demonstrating correct behaviour. Whoops, I accidentally posted the file with the mentioned "small modification".
Jakub is correct, calling fgets() with size less than 2 is invalid, but i'm not sure that constitutes an infinite loop http://www.opengroup.org/onlinepubs/009695399/functions/fgets.html
actually, i dont see the trouble here ... the infinite loop is in the testcase, not in glibc if (n <= 0), you get NULL ... since the stream does not have an error nor is it an eof, neither gets set if (n == 1), you get a NUL string, which is correct