The FUSE
project allows you to create filesystems in userspace – which means you
can create a filesystem without having to get your hands dirty and
modify your kernel source. This is insanely cool, and can be used for
many purposes. Here we’re going to look at using the Ruby bindings to
create a simple filesystem.

The FUSE project allows you to create a filesystem entirely in userspace merely by writing a couple of primitives – such as:

  • Get contents of directory.
  • Get file permissions.
  • Write to file.

With enough of the basic primitives implemented you’ll end up with a
working filesystem. Unfortunately unless you’re a fan of working in C
this process is a little more complex than it should be. To ease this
there are bindings for Perl, Ruby, and other languages which makes
getting started very simple.

My recent FUSE experiments have been Ruby-based, using the libfusefs-ruby1.8 packages.

Assuming you’re working with a recent kernel you should be able to
get started writing a filesystem very quickly. Here’s what it took for
me:

<span class="prompt">trashvm:~#</span> <span class="input">apt-get install libfusefs-ruby1.8 fuse-utils ruby1.8</span><br />Reading package lists... Done<br />Building dependency tree... Done<br />The following extra packages will be installed:<br />  libfuse2 libruby1.8<br />The following NEW packages will be installed<br />  fuse-utils libfuse2 libfusefs-ruby1.8 libruby1.8<br />0 upgraded, 4 newly installed, 0 to remove and 0 not upgraded.<br />Need to get 1716kB of archives.<br />After unpacking 6476kB of additional disk space will be used.<br />..<br /></pre>
<p>Once I installed the packages I inserted the <tt>fuse</tt> kernel module:</p>
<pre class="terminal"><span class="prompt">trashvm:~#</span> <span class="input">modprobe fuse</span><br /></pre>
<p>Assuming this completes without errors you're ready to begin! Lets
create a simple filesystem that just proves we can. Create the file <tt>hello.rb</tt>:</p>
<pre>require 'fusefs'<br /><br />class HelloDir<br />  def contents(path)<br />    ['hello.txt']<br />  end<br />  def file?(path)<br />    path == '/hello.txt'<br />  end<br />  def read_file(path)<br />    "Hello, World!\n"<br />  end<br />end<br /><br />hellodir = HelloDir.new<br />FuseFS.set_root( hellodir )<br /><br /># Mount under a directory given on the command line.<br />FuseFS.mount_under ARGV.shift<br />FuseFS.run<br /></pre>
<p>Once you've done that you can mount your filesystem by running:</p>
<pre class="terminal"><span class="prompt">trashvm:~#</span> <span class="input">mkdir hello/</span><br /><span class="prompt">trashvm:~#</span> <span class="input">ruby hello.rb hello/ &</span><br /></pre>
<p>Once you've done this you should find you've got a directory called
"hello/" which has our filesystem mounted in it. This filesystem just
creates a file called "hello.txt" - and when you read it you'll get the
expected contents:</p>
<pre class="terminal"><span class="prompt">trashvm:~/hello#</span> <span class="input">ls</span><br />hello.txt<br /><span class="prompt">trashvm:~/hello#</span> <span class="input">cat hello</span><br />Hello, World!<br /></pre>
<p>You can now kill the userspace program and all will be gone:</p>
<pre class="terminal"><span class="prompt">trashvm:~#</span> <span class="input">kill -9 %1</span><br />[1]+  Killed                  ruby1.8 hello.rb hello<br /><span class="prompt">trashvm:~#</span> <span class="input">umount  hello</span><br /><span class="prompt">trashvm:~#</span> <span class="input">ls hello</span><br /><span class="prompt">trashvm:~#</span><br /></pre>
<p>All gone.  Now is time to confess that I didn't create that <tt>hello.rb</tt> file - it is one of the examples included with the <a href="http://packages.debian.org/libfusefs-ruby1.8" rel="nofollow">libfusefs-ruby1.8 package</a>.  If you take a look beneath <tt>/usr/share/doc/libfusefs-ruby1.8</tt> directory you'll find several examples which each work in a similar way to the one we've demonstrated.</p>
<p>So, what could you do with a FUSE filesystem? Well if you're
creative there are almost no limits. As a more useful example I've
placed a simple filesystem I created online at the foot of this
article. This allows you to mount MySQL as a filesystem:</p>
<pre class="terminal"><span class="prompt">skx@gold:/mysql/yawnstrashvm:~# apt-get install libfusefs-ruby1.8 fuse-utils ruby1.8
Reading package lists... Done
Building dependency tree... Done
The following extra packages will be installed:
libfuse2 libruby1.8
The following NEW packages will be installed
fuse-utils libfuse2 libfusefs-ruby1.8 libruby1.8
0 upgraded, 4 newly installed, 0 to remove and 0 not upgraded.
Need to get 1716kB of archives.
After unpacking 6476kB of additional disk space will be used.
..

Once I installed the packages I inserted the fuse kernel module:

trashvm:~# modprobe fuse

Assuming this completes without errors you’re ready to begin! Lets
create a simple filesystem that just proves we can. Create the file hello.rb:

require 'fusefs'

class HelloDir
def contents(path)
['hello.txt']
end
def file?(path)
path == '/hello.txt'
end
def read_file(path)
"Hello, World!\n"
end
end

hellodir = HelloDir.new
FuseFS.set_root( hellodir )

# Mount under a directory given on the command line.
FuseFS.mount_under ARGV.shift
FuseFS.run

Once you’ve done that you can mount your filesystem by running:

trashvm:~# mkdir hello/
trashvm:~# ruby hello.rb hello/ &

Once you’ve done this you should find you’ve got a directory called
“hello/” which has our filesystem mounted in it. This filesystem just
creates a file called “hello.txt” – and when you read it you’ll get the
expected contents:

trashvm:~/hello# ls
hello.txt
trashvm:~/hello# cat hello
Hello, World!

You can now kill the userspace program and all will be gone:

trashvm:~# kill -9 %1
[1]+ Killed ruby1.8 hello.rb hello
trashvm:~# umount hello
trashvm:~# ls hello
trashvm:~#

All gone. Now is time to confess that I didn’t create that hello.rb file – it is one of the examples included with the libfusefs-ruby1.8 package. If you take a look beneath /usr/share/doc/libfusefs-ruby1.8 directory you’ll find several examples which each work in a similar way to the one we’ve demonstrated.

So, what could you do with a FUSE filesystem? Well if you’re
creative there are almost no limits. As a more useful example I’ve
placed a simple filesystem I created online at the foot of this
article. This allows you to mount MySQL as a filesystem:

skx@gold:/mysql/yawns$ ls
about_pages.data notifications.data related.data
about_pages.sql notifications.sql related.sql
adverts.data permissions.data scratchpads.data
adverts.sql permissions.sql scratchpads.sql
articles.data poll_anon_voters.data sessions.data
articles.sql poll_anon_voters.sql sessions.sql
bookmarks.data poll_answers.data submission_notes.data
bookmarks.sql poll_answers.sql submission_notes.sql
comments.data poll_questions.data submissions.data
comments.sql poll_questions.sql submissions.sql
db_version.data poll_submissions_answers.data tags.data
db_version.sql poll_submissions_answers.sql tags.sql
ip_blacklist.data poll_submissions.data tips.data
ip_blacklist.sql poll_submissions.sql tips.sql
messages.data poll_voters.data users.data
messages.sql poll_voters.sql users.sql
news.data preferences.data weblogs.data
news.sql preferences.sql weblogs.sql
lt;/span><span class="input"> ls</span><br />about_pages.data notifications.data related.data<br />about_pages.sql notifications.sql related.sql<br />adverts.data permissions.data scratchpads.data<br />adverts.sql permissions.sql scratchpads.sql<br />articles.data poll_anon_voters.data sessions.data<br />articles.sql poll_anon_voters.sql sessions.sql<br />bookmarks.data poll_answers.data submission_notes.data<br />bookmarks.sql poll_answers.sql submission_notes.sql<br />comments.data poll_questions.data submissions.data<br />comments.sql poll_questions.sql submissions.sql<br />db_version.data poll_submissions_answers.data tags.data<br />db_version.sql poll_submissions_answers.sql tags.sql<br />ip_blacklist.data poll_submissions.data tips.data<br />ip_blacklist.sql poll_submissions.sql tips.sql<br />messages.data poll_voters.data users.data<br />messages.sql poll_voters.sql users.sql<br />news.data preferences.data weblogs.data<br />news.sql preferences.sql weblogs.sql<br />

Each directory beneath /mysql is a database, and each table of that database is presented as two files:

  • foo.sql – Just the table structure.
  • foo.data – The contents of the table.

The code is very simple and mostly revolves around invoking mysqldump correctly, but despite that it is surprisingly useful.

I’d love to hear suggestions on interesting uses for FUSE, and what you’re doing with it!

This is the code which implements the MySQL filesystem. It is very short as it only involves implementing a couple of methods:

  • contents – Return the filenames in a given directory.
  • directory? – Is the given name a directory?
  • file? – Is the given name a file?
  • read_file – Return the contents of a file.

from http://www.debian-administration.org/articles/619

Advertisements