I have a test install of the LiveJournal code, ported to run on Apache 2/mod_perl 2 (it seemed like a good idea at the time). A recent change broke it so that all .bml files came up as 404 with a message in the log like "[Sun Aug 26 17:05:11 2007] [error] File does not exist: /home/lj/htdocs/index.bml" (but only after the server had been up for a minute or two). Since /home/lj/htdocs/index.bml did in fact exist, I decided to take a look at the code causing the error:
sub handler
{
my $r = shift;
my $file;
$Apache::BML::r = $r;
# determine what file we're supposed to work with:
if (ref $r eq "Apache::FakeRequest") {
# for testing. FakeRequest's 'notes' method is busted, always returning
# true.
$file = $r->filename;
stat($file);
} elsif ($file = $r->notes("bml_filename")) {
# when another handler needs to invoke BML directly
stat($file);
} else {
# normal case
$file = $r->filename;
$r->finfo;
}
unless (-e _) {
$r->log_error("File does not exist: $file");
return NOT_FOUND;
}
unless (-r _) {
$r->log_error("File permissions deny access: $file");
return FORBIDDEN;
}
# boring stuff...
Okay, so it's checking for the existence of something - but what? Some quick Googling turns up
this - so in the first two cases it checks that $file exists and is readable. However, in the "normal case", it's testing for the existence of whatever file stat() or a file test operator was last used on before it was called. Since the "normal case" code path is for when the code is being called by mod_perl directly as a PerlHandler, alarm bells are now ringing. Presumably, the author noticed that mod_perl was calling stat() or similar on the file before it called the handler, and decided to save a call by reusing the result.
The reason it "worked" before and suddenly broke? Changes to the config file handling meant that the code to check if the Livejournal config file has been modified (which runs as part of the request cycle) stat()ed a non-existent file. This code was somewhere else (not just a different file, but a different SVN repo), completely unrelated, and called (indirectly) from a different mod_perl handler. (Actually, if I'm reading the code in question correctly, it should always have stat()ed an existing file as the last call to stat() before it returned, but I've done some testing and it does seem to be the culprit, so...)
You know it's Enterprise Software when the vendor freebie is a red shirt.