The libferris virtual filesystem
November 19, 2008
This article was contributed by Ben Martin
The Unix mantra "everything is a file" gives you great flexibility over
where you store your data and how information is manipulated and
replicated. Unfortunately, many things in Unix and Linux are not files, or
ones that you might want to interact with anyway. For example, a
PostgreSQL database is ultimately stored in a collection of binary files
though you probably wouldn't want to interact with those files directly.
Instead of storing settings in a collection of tiny files, many
applications use XML to store settings in a single file but then have to
deal with parsing XML instead of just reading little files. libferris lets
you mount both PostgreSQL and XML and provides you with a useful way to
interact with the data contained in both as a virtual filesystem.
Other operating systems like Plan 9 pushed the envelope further than Unix,
making more things "just a file". Unfortunately, to use Plan 9 you had to
abandon your trusty old Unix roots and jump to an entirely new operating
system.
I started the libferris virtual filesystem project back in 2001 to push
the "everything is a file" concept further, it was all implemented on a
Linux base. Libferris is a virtual filesystem implemented as a shared
library with FUSE bindings. Because FUSE is already in the Linux kernel
you don't have to do any kernel patching to use libferris. Because
libferris is a shared library and not in the kernel, it can use other
libraries to help it mount data sources like XML, relational databases and
Emacs to name a few. And as an upshot of being out of kernel, I can work
on letting libferris mount anything I like no matter how strange it might
be without any third party approval.
There are actually two ways to use libferris -- through a native C++
interface and using the normal Unix APIs with FUSE. The FUSE interface is
very useful if you want to rsync(1) some structured information from an
XML file into a PostgreSQL database. Just mount them both with FUSE and
rsync away. Another few interesting things you can do with the FUSE
interface is expose data as a virtual office document using XSLT
stylesheets that libferris processes for you as well as geotagging with
Google Earth.
The design of libferris revolves around two primitives: exposing file
contents as C++ std::iostreams, and rich metadata support through an
interface similar to Extended Attributes (EA) attr_get(3). Since then
libferris has gained sophisticated support for indexing both the full text
contents of files as well as their metadata. Libferris is written in C++
and aims to take full advantage of the language. Interfaces are designed
to be as easy to pickup for C++ programmers as possible, for example,
displaying a directory can be done using iterators, find(), begin() to
end() etc.
Both the types of things that libferris can provide as virtual filesystems
and the metadata handling are done through a plugin interface. The
handling of metadata is done through the Extended Attributes (EA)
interface. This EA interface is also virtualized -- if you write an
attribute to file:///foo/bar and the kernel filesystem supports extended
attributes, then the value will be saved in a kernel level EA using
attr_set(3). On the other hand if file:///foo/bar happens to exist on a
network filesystem that does not support EA, then your value is saved in
RDF by libferris. In both cases the value can be read again using an
identical interface.
Looking at filesystems in an abstract way -- a hierarchy of files, file
contents, and metadata associated with files and directories as key-value
pairs -- there is somewhat of a resemblance to the data model of XML.
Although there are obvious differences: XML elements can have multiple
text nodes as contents, an XML element does not need to have specific
unique names for each child XML element and so on. In many cases it can be
advantageous to smooth over the differences and view a filesystem as XML
and vice versa. Over the years libferris has gained the ability to
interact with it's virtual filesystems as virtual Document Object Models
(DOM)s. The reverse is also true, you can take an xerces-c DOM and
interact with it as a virtual filesystem. Using virtual DOMs makes it easy
to create a view of a filesystem using a browser and XSLT. See xml.com for
information on using XQuery against a libferris virtual filesystem.
The ability to mount XML and Berkeley db4 data as filesystems has long
been a part of libferris. If you want to store a filesystem inside a
platform independent format, then using XML is great, whereas the speed of
individual file look up in a Berkeley db4 database of many many file
records can come in handy. Each format has its advantages, but they are
all just virtual filesystems as far as libferris is concerned.
When a filesystem can offer what it likes through key-value pairs (EA)
associated with files, relational databases can also be viewed as a
virtual filesystem. Databases, views, tables and result sets become
directories, tuples become files named by the value of their primary key,
and the individual values of tuples are exposed as Extended Attributes on
their tuple file. Again, PostgreSQL appears just like another virtual
filesystem. For relational data there are a few caveats, for example, to
create a new "file" in a table you must supply at least the primary key EA
as well as any EA which are explicitly marked "not null" in the database.
Libferris will automatically mount many filesystems for the user. For
example, if you try to read an XML file as though it is a directory then
libferris will implicitly mount it as one for you. This does blur the
lines between what is a directory and what is a file in the system. There
is some additional metadata that libferris makes available if you would
like to avoid the automatic mounting. For example, if you wish not to
descend into XML files then read the is-file metadata and if it is true do
not attempt to descend into the file.
One of the motivations for creating libferris as a project of its own was
to be able to expose anything that I felt could be interacted with in an
interesting manner as a filesystem as one. So libferris can mount some
things that folks might not think of as filesystems -- including Firefox,
Emacs, DBus, LDAP, Evolution, Amarok, klipper, xmms, X Window System and
gphoto2.
The metadata plugins for libferris currently support extracting
information from file formats automatically, for example, EXIF, XMP and
ID3 tags. Metadata overlays are also supported, so you can see what tags
you have associated with an image in f-spot through extended attributes in
libferris. I use the term overlays because a central repository of tag
data (in this case from f-spot) is scattered over an entire filesystem in
libferris. The lower level metadata plugins handle more standard extended
attributes usage, for example using attr_set(3) to store values or saving
them in RDF.
Many of the standard utilities have been rewritten to use the native
libferris API and take advantage of extra features it offers. Things like
ls, cp, mv, rm, cat, io-redirection, touch, head and tail all have native
libferris versions which are shipped with the main tarball. These all also
serve as code samples for how to use the libferris API. Extensions to the
normal clients include the ability to output directory listings in XML for
ferrisls, ferriscp has the ability to use memory mapped IO as well as the
more standard open(), read() and write() calls to perform the copy. Using
memory mapped IO this way also uses the madvise(2) MADV_SEQUENTIAL call to
let the kernel correctly select caching policy.
The indexing support in libferris is also handled using plugins. Two
different indexing plugin types exist; full text and metadata. There are
two types of plugin, because the strategy for how to create an index can
be quite different depending on if you are performing a search for some
words in a document text or if you wish to find files with certain
metadata values. Using inverted files can be great for resolving a ranked
full text query for "alice wonderland" but finding all files in either my
home directory or /pictures that have been modified in December 2008 can
be solved in a number of ways.
There are currently indexing plugins for CLucene, Lucene, LDAP,
Federations of other libferris indexes, ODBC, PostgreSQL, Redland (RDF),
Xapian, Beagle, Strigi and some custom designs. There are likely to be
more index plugins explicitly designed to work on NAND Flash in the
future. Those interested in indexing and libferris should see this article.
A major advantage of closely combining the index and search operations
into the virtual filesystem is that anything the virtual filesystem can
see can be indexed. When searches are performed you should also be able to
interact with any of the results as a virtual filesystem. This avoids the
issue where a discrete search library might return a URL that the client
can not do anything with.
So, what does it look like to code using libferris? Most objects in ferris
are smart pointers, many using intrusive reference counting. The type for
such objects is prefixed with "fh_" to indicate a ferris handle. The
notion of files and directories is amalgamated into a single "Context"
abstraction. To get a smart pointer to a filesystem path the Resolve()
function is used. So without further ado, to get a file and its metadata
with libferris:
fh_context c = Resolve( "~/myfile" );
{
// let the scope close it for me
fh_istream ss = c->getIOStream( ios::trunc );
ss << "Bah!" << endl;
}
// std::string getStrAttr( fh_context, eaname, default-value, ... )
string filename = getStrAttr( c, "name", "" );
string md5sum = getStrAttr( c, "md5", "" );
cout << "the filename should be myfile:" << filename << endl;
cout << "the md5 checksum is:" << md5sum << endl;
setStrAttr( c, "foo", "bar" );
fh_attribute a = c->getAttribute("foo");
fh_istream ass = a->getIStream();
cout << "Getting the metadata again:";
copy( istreambuf_iterator<char>(ass),
istreambuf_iterator<char>(),
ostreambuf_iterator<char>(cout));
cout << endl;
Libferris is steadily gaining commercial interest. Currently I provide
things like custom builds of libferris, explicit support for new test
cases in the core regression test suite that are important to clients and
of course extensions to libferris to perform a specific task that might be
desired.
There are packages available for both 32 and 64-bit Fedora 8, 9 and Ubuntu
7.10 gusty as well as 32bit packages for openSUSE 10.3. Unfortunately
there is currently a bug in building 64bit stldb4 on openSUSE. Install the
libferris-suite package to pull in all the dependencies.
Feel free to email the witme-feris mailing list or add comments to this
article suggesting any weird and wonderful (and obscure) filesystems you
have experienced in the past. Though my libferris.TODO file always grows
more than it shrinks, I'm always happy to add new and exciting suggestions
near the top of it.
Comments (6 posted)