Gentoo Websites Logo
Go to: Gentoo Home Documentation Forums Lists Bugs Planet Store Wiki Get Gentoo!
Bug 182288 - fgets goes into infinite loop when called with size set to less than 2 bytes
Summary: fgets goes into infinite loop when called with size set to less than 2 bytes
Status: RESOLVED INVALID
Alias: None
Product: Gentoo Linux
Classification: Unclassified
Component: [OLD] Core system (show other bugs)
Hardware: All Linux
: High minor (vote)
Assignee: Gentoo Toolchain Maintainers
URL: http://bugzilla.redhat.com/bugzilla/s...
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2007-06-17 03:13 UTC by Ryan H.
Modified: 2007-06-17 08:54 UTC (History)
2 users (show)

See Also:
Package list:
Runtime testing required: ---


Attachments
Test case demonstrating correct behaviour. (fgets.c,655 bytes, text/plain)
2007-06-17 06:04 UTC, Nick Bowler
Details
*Correct* test case demonstrating correct behaviour. (fgets.c,657 bytes, text/plain)
2007-06-17 06:08 UTC, Nick Bowler
Details

Note You need to log in before you can comment on or make changes to this bug.
Description Ryan H. 2007-06-17 03:13:36 UTC
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.
Comment 1 Ryan H. 2007-06-17 04:16:04 UTC
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"
Comment 2 Nick Bowler 2007-06-17 04:52:54 UTC
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!
Comment 3 Nick Bowler 2007-06-17 05:57:19 UTC
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.
Comment 4 Nick Bowler 2007-06-17 06:04:10 UTC
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.
Comment 5 Nick Bowler 2007-06-17 06:08:21 UTC
Created attachment 122294 [details]
*Correct* test case demonstrating correct behaviour.

Whoops, I accidentally posted the file with the mentioned "small modification".
Comment 6 SpanKY gentoo-dev 2007-06-17 08:47:05 UTC
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
Comment 7 SpanKY gentoo-dev 2007-06-17 08:54:43 UTC
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