Merging Filesystems Virtually Under Apache

Synopsis

This is rather old code, but saved my bacon more than once. Runs under Apache with Mod_Perl, and “merges” any number of filesystems. You’ll see the @docroots array that takes a list of “document roots”. These all need correct permissions in Apache, or else the end request will fail. This module doesn’t trump Apache’s security model. In short, it tries the request against each of the entries in @docroots successively, setting the URI to the first found file, or declining altogether if none is found.

Strategy

Most people who understand their web systems, know where their requests go. For performance, you really want to make sure the file system most likely to serve the request is listed first, the next most likely second, et cetera.

The exception to this is if you’re using this as a migration strategy. I have, twice, listed a mostly empty file system first, making the second file system “read-only”. The webby people then had a way to migrate their content to the new file system systematically and deliberately, with immediate results. This module is very efficient, so failed lookups are invisible to the user experience.

Code

=head1 NAME

M::Apache::fsmerge - Want to merge multiple filesystems into one Apache filesystem?

=head1 SYNOPSIS

PerlModule M::Apache::fsmerge

<VirtualHost>

PerlTransHandler M::Apache::fsmerge

</VirtualHost>

=cut

package M::Apache::fsmerge;

use Apache2::Const qw(DECLINED);
use Apache2::RequestUtil ();

=head1 VARIABLES

=over 4

=item @docroots

An array of filesystem paths to merge. They are processed in array order, and the first match wins.

=cut

my @docroots = (
                        "/var/www/html/web/managed/groups",
                        "/var/www/html/webusers",
                        );
sub handler {
        my $r = shift;
        my $sofar=0;
        foreach my $dr (@docroots) {
                my $file=$dr . $r->uri();
                $sofar=0;
                my $newuri=$dr; # $newuri is the uri we're building
                unless(-e $file) {
                        # File doesn't exist, let's try to find it!
                        my @uribits=split(/\//,$r->uri());
                        shift @uribits; # shift off the beginning ''
                        foreach my $bit (@uribits) {
                                $bit =~ s/\(|\)|\`//g; # stupid url tricks
                                $sofar=0; # reset sofar, so we know if we're still on track
                                opendir(FD,$newuri) or last;
                                foreach my $dthing (readdir(FD)) {
                                        if($dthing =~ /^\./) { next; } # safety first;
                                        if($dthing =~ /^$bit$/i) { # case-insenstive pattern match
                                                # We have a match!
                                                $sofar=1;
                                                $newuri .= "/" . $dthing;
                                                last;
                                        }
                                }
                                closedir(FD);
                                unless($sofar) { last; } # we missed this bit, don't bother recursing further
                        }
                } else {
                        # Woo hoo!
                        $sofar=1;
                        $newuri=$file;
                }

                if($sofar) {
                        # We made it!
                        $newuri =~ s/^$dr//; # strip off the document_root from the new uri
                        $r->document_root($dr); # Set the new docroot
                        $r->uri($newuri); # Set the uri to the new uri
                        last;
                } # else we can't do anything...
        }

        return DECLINED; # Pass it on, regardless of the outcome

}

1;
This entry was posted in Architecture, Coding, Linuxy and tagged , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published.