Skip to main content

fwrite() returning ULONG_MAX

· 3 min read

I had a strange problem recently... occasionally, one of my FastCGI programs would go into a near-infinite loop (actually, just looping approximately 18 x 1018 or 18 billion billion times!!). It was easy to trace the problem back to an fwrite() call returning ULONG_MAX (18446744073709551615 on this particular 64-bit server).

Now, the thing about that is this: fwrite() should always return an integer between 0 and the supplied nmemb parameter (always much, much less than ULONG_MAX in this case). So, why is fwrite() returning any number greater than nmemb, let alone one that is so much bigger!!?

Well, any experienced programmer would immediately recognise that ULONG_MAX is exactly the same as (usigned int)-1 (ie casting -1 to an unsigned integer). So, it would seem likely that fwrite() is returning -1 at some point... yet, the man page does not allow for fwrite() to return -1, so what's going on?

Well, it turns out that fwrite() is actually being overridden by the FCGI_fwrite() function via the FastCGI library. So, a quick dig through the FCGI_fwrite() function's source reveals three different scenarios in which fwrite() may now return -1 (cast to an unsigned integer, of course).

So, here's some pseudo-code that shows how to correctly handle this:

size_t bytes=fwrite(ptr,size,nmemb,fp);
if ((bytes==(size_t)EOF)||(bytes==(size_t)-1)) {
LogErr("fwrite returned an error.");
if ((!fp->stdio_stream)&&(!fp->fcgx_stream))
LogInfo("Invalid stream.");
else if (fp->fcgx_stream) {
if (fp->fcgx_stream->isClosed)
LogInfo("stream is closed.");
if (fp->fcgx_stream->isReader)
LogInfo("stream is read only.");
}
return false;
}

Here you can see that the return value of fwrite() is checked against both (size_t)EOF and (size_t)-1 - both of which should evaluate to ULONG_MAX. The reason I test both, even though they should should both be the one and the same number, is that the FCGI_fwrite() function returns either (size_t)EOF or (size_t)-1 depending on where the error occurred, so it is safer to evaluate both, just in case they do differ on some strange platform (perhaps EOF is not -1 on some systems?). Anyway, don't let the redundancy bother you, because when the two values are the same (always?) then any decent compiler will simply optimise out the redundant check anyway :)

As you can see, the above pseudo-code can report three different error cases (the LogInfo() calls). The first ("invalid stream") is a possible condition in which FCGI_fwrite() may return (size_t)EOF. While the other two cases ("stream is closed" and "stream is read only") may be returned by the FCGX_PutStr() function which may be called internally by the FCGI_fwrite() function.

For further reading, you can browse the definitions of the FCGI_fwrite() and FCGX_PutStr() functions at:

Well that's it, problem sorted ;)

Oh, and in case you were wondering, the reason I was getting these -1 errors in the first place, was due to a client hastily dropping the network connection before downloads completed... thus I was seeing (and not handling gracefully) the "stream is closed" scenario :)