The 2004 Perl Advent Calendar
[about] | [archives] | [contact] | [home]

On the 20th day of Advent my True Language brought to me..
Filesys::Virtual

Filesys::Virtual is an abstraction of a filesystem. It allows you to program your own virtual filesystem that contains whatever you want. As well as accessing the filesystem programmatically, several modules exist on CPAN that can act as servers for your virtual file system presenting it over FTP or WebDAV.

Many existing Filesys::Virtual implementations exist, from the most basic from those that simply use an existing filesystem to the more complex that can present a remote filesystem over SSH or a Apple iTunes music share as a virtual filesystem.

Let's look at the basics of using the most basic Filesys::Virual implementation, Filesys::Virtual::Plain. This simple bases itself on a real file system (in this case a directory in the file system.

  # create a new filesystem that uses the current working
  # directory as the root of the filesystem
  my $filesys = Filesys::Virtual::Plain->new({
    root_path => cwd,
  });
  # print all files on the "root" filesystem
  print join ", ", $filesys->list('/'), "\n";

So we can now access the filesystem programmatically. What we need is some way to to access this virtual filesystem from outside Perl. There's various options - we can view these filesystems as a FTP server or with a WebDAV server.

Let's write some example code. Firstly we start the script and then load all the modules:

  #!/usr/bin/perl
  # turn on the safety features
  use strict;
  use warnings;
  # load the modules
  use Filesys::Virtual::Plain;
  use Net::DAV::Server;
  use HTTP::Daemon;
  use Cwd qw(cwd);

We then create the new virtual filesystem as above:

  # create a new filesystem
  my $filesys = Filesys::Virtual::Plain->new({
    root_path => cwd,
  });

Now let's create Net::DAV::Server that understands the WebDAV protocol and can return an appropriate response for each request for the file system we just created:

  # create a new WebDAV server that deals with this filesystem
  my $webdav = Net::DAV::Server->new();
  $webdav->filesys($filesys);

We then instantiate a HTTP::Daemon webserver that listens on port 4242:

  my $server = HTTP::Daemon->new(
    LocalAddr => 'localhost',
    LocalPort => 4242,
    ReuseAddr => 1
  ) or die "Couldn't start server";
  print "Please contact me at: ", $server->url, "\n";

We finally write some code that gets each request that comes to our webserver, hands it to our instance of Net::DAV::Server and sends the response it passes us back to the client:

  # keep accepting connections and then deal with them each time
  while (my $connection = $server->accept)
  {
    # get each request for this connection
    while (my $request = $connection->get_request)
    {
      # get the webdav server to look at the
      # request for us and send the response
      my $response = $webdav->run($request);
      $connection->send_response($response);
    }
    $connection->close;
    undef($connection);
  }

This prints out the address:

  Please contact me at: http://localhost:4242/

We can access the virtual file system with a normal web browser and browse the files. If we use a fully fledged client we can read and write to the file.

Other Virtual File Systems

Often I want to access files on remote servers as if they were on my local machine. Luckily for me, Mac OS X can mount a remote FTP server as a local drive. Hitting Command-K in the finder pops open a dialog where I can enter a filename which will be mounted inside /Volumes. I can then use any of my normal editors to access the files. I've been lead to believe that a GNU/Linux system properly configured can do the same kind of thing.

The problem is that most of my servers don't have a FTP server on them. All access is via SSH. Luckily there's a Filesys::Virtual subclass Filesys::Virtual::SSH that can access files over SSH the same way that Filesys::Virtual::Plain can for local disks.

   use Filesys::Virtual::SSH;
   my $fs = Filesys::Virtual::SSH->new({
       host      => 'perladvent.org',
       cwd       => '/virtual/perladvent.org/www/html/2004/',
       root_path => '/',
       home_path => '/home',
   });
   print join ", ", $fs->list('/virtual/perladvent.org/www/html/2004/')

This prints out all the files and directories on my remote server.

  ., .., 10th, 11th, 12th, 13th, 14th, 15th, 16th, 17th,
  18th, 19th, 1st, 20th, 21st, 22nd, 23rd, 24th, 25th,
  2nd, 3rd, 4th, 5th, 6th, 7th, 8th, 9th, index.html

We can make use of this filesystem with any of the servers that can use a Filesys::Virtual file system. So we can create a local FTP server for a remote SSH accessible server:

  # load the modules
  use POE qw(Component::Server::FTP);
  use Filesys::Virtual::SSH;
  # define the server
  POE::Component::Server::FTP->spawn(
     ListenPort      => 2112,
     FilesystemClass => 'Filesys::Virtual::SSH',
     FilesystemArgs  => {
       host      => 'perladvent.org',
       cwd       => '/virtual/perladvent.org/www/html/2004/',
       root_path => '/',
       home_path => '/home',
     },
     LogLevel => 4,
  );
  # run the system
  $poe_kernel->run();

Of course the Finder's not very efficent when it comes to dealing with the SSH connection and doesn't cache very well (if at all) meaning that it makes many many calls over SSH - meaning this is very slow. But it does work.

  • Filesys::Virtual::Plain
  • Filesys::Virtual::SSH
  • Filesys::Virtual::DAAP
  • HTTP::Daemon
  • Net::DAV::Server
  • POE::Component::Server::FTP