Sunday, November 15, 2009

FUSE - Filesystem in Userspace part 1

Here's the first part in a series of posts on implementing file systems using FUSE.

Creating a file system can seem to be an intimidating task but with FUSE it's very easy and it gets even easier with python bindings. Any decent Linux distribution should include support for FUSE and be configured such that regular users (or a group) can mount file systems written with FUSE without having root access.

If you are running Ubuntu you need to install the python-fuse package which contains the Python bindings for FUSE.

- Enough, show me the code

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import errno
import fuse
import stat
import time

fuse.fuse_python_api = (0, 2)

class MyFS(fuse.Fuse):
    def __init__(self, *args, **kw):
        fuse.Fuse.__init__(self, *args, **kw)

    def getattr(self, path):
        st = fuse.Stat()
        st.st_mode = stat.S_IFDIR | 0755
        st.st_nlink = 2
        st.st_atime = int(time.time())
        st.st_mtime = st.st_atime
        st.st_ctime = st.st_atime

        if path == '/':
            pass
        else:
            return - errno.ENOENT
        return st

if __name__ == '__main__':
    fs = MyFS()
    fs.parse(errex=1)
    fs.main()
The example above shows how easy it is to create a mountable file system, it does nothing but is still a valid file system. The MyFS class implements a file system with no entries, it will always return ENOENT (No such file or directory) except for the root path. You should take a look at the FUSE Python Reference page which contains details about FusePython and, among other things, describes the stat object returned from the getattr method.

Paste the example to a file, MyFS.py, and make the file executable:
$ chmod 755 MyFS.py
Now create a directory which will be used as mount point and mount the file system:
$ mkdir myfs
$ ls -l
totalt 4
drwxr-xr-x 2 mario mario 4096 2009-11-15 15:59 myfs
-rwxr-xr-x 1 mario mario  648 2009-11-15 15:59 MyFS.py
$ # This will mount the file system, try ./MyFS.py --help
$ ./MyFS.py myfs
$ ls -l
totalt 0
drwxr-xr-x 2 root  root    0 2009-11-15 16:10 myfs
-rwxr-xr-x 1 mario mario 648 2009-11-15 15:59 MyFS.py
Here we can see that the owner, size and time stamp changed on myfs after we mounted the file system. You can cd into myfs, but if you try to list the directory (file system root) you'll get an error:
$ cd myfs
$ ls
ls: reading directory .: Function not implemented
You got this error because we haven't implemented the readdir method which is invoked when listing a directory. To make this work we need to add a readdir method which returns (at least) the '.' and '..' entries. Add the following snippet to the class:
    def readdir(self, path, offset):
        for e in '.', '..':
            yield fuse.Direntry(e) 
Before you continue you should unmount the file system:
$ fusermount -u myfs
This will unmount your FUSE file system as a regular user and doesn't require root access.

Retry to mount and cd into the myfs directory:
$ ./MyFS.py myfs
$ cd myfs
$ ls -a
.  ..
Alright, I think I'll stop for today. Oh, just one more thing... You won't get any print outputs nor tracebacks from your code if you don't start the file system in foreground mode. This is done by adding '-f' as argument when starting the file system.
$ ./MyFS.py -f myfs
It can be hard to tell why something doesn't work if you can't see tracebacks and print outputs.

Thursday, November 5, 2009

GMonitor

Ah, I've finally managed to make my new fresh Kubuntu 9.10 installation usable. Now it's time to do some KDE stuff and I'll show you how easy it's to create a plasmoid (applet/widget) for KDE4.

I've created a simple applet which shows the number of unread mail in a Gmail inbox. We already know how to fetch the list with unread mail, which I have explained in a previous blog post. Now I'll create a nice applet showing the count. I'm going to keep it as simple as possible, writing hard-coded values, skip error handling and so on to avoid making things more complicated than necessary.

There are several good tutorials at techbase.kde.org which explains plasma programming. They cover the basics and explains things in detail so I'll only make references to the pages instead of repeating what's already been written.

The first tutorial you should read (you don't have to read it right now, you can read it after you tried doing the applet) is Python Plasma Getting Started. The tutorial covers setting up a simple plasmoid, packaging, installing and running.

I'll name the plasmoid GMonitor because it only monitors the mailbox and shows the number of unread mail, it will not actually try to do some kind of notification (yet?!).

I've put the code on github, you can clone it with:
$ git clone git://github.com/mariob/gmonitor.git
You'll find a Makefile in the git which can be useful when doing plasmoids. The Makefile supports installing, un-installing, viewing, packaging and updating the plasmoid.

The directory tree looks like this:
gmonitor/
|-- Makefile
|-- README
|-- contents
|   `-- code
|       `-- main.py
`-- metadata.desktop
The gmonitor directory is the project home and contents/code contains the python source. The metadata.desktop file will be explained below.

To be able to install and run your applet you need to provide a metadata.desktop file to plasma. The metadata.desktop file contains important information about the applet and you can read more about the file in the Plasma Getting Started tutorial.

Feel free to change the file. The Name specifies the applet name and the Icon field gives the name to the icon to associated with this applet. These two fields are typically shown when listing applets in the 'Add Widget' dialog. There are two important fields which must be present for plasma to run your applet, the X-Plasma-API field specifies which script-engine to be used and the X-Plasma-MainScript field which script to be executed. It's also good to know that the X-KDE-PluginInfo-Name fields is used as a plasmoid identification, so it should be unique.

Some notes about the implementation. There are two classes, GMonitor and MailFrame (I know, poor name). The MaiFrame class has an icon and a label. When the mail count is set to zero (by calling setCount(cnt)), the icon is disabled, grayed and the label is set to 'No new mail'. When the mail count is set to a value greater than zero, the icon is enabled, colored and the label is set to 'Count: x', where x is the number of mail in the inbox. See the images below.



Sample of what the applet looks like

When the icon is enabled and clicked, a 'clicked()' signal is emitted. The GMonitor class connects the openBrowser() method to the 'clicked()' signal. The openBrowser() method opens a default browser in KDE to load the google mail url. The GMonitor also sets up a timer to fetch the feed in 60 second interval. The fetchFeed() method uses the KIO framework in KDE to download the content from a URL and parseFeed() parses the downloaded feed and emits a 'mailcount' signal.

The implementation depends on feedparser which can be installed on Ubuntu by running:
$ sudo aptitude install python-feedparser
You can also download and put the feedparser.py file inside the code directory.

Not that bad. It's actually possible to create a very simple 'good looking' Gmail monitor in less than 100 lines of code (excluding comments) with Python and KDE.

To view this plasmoid without installing it run (while standing in the gmonitor folder):
$ make view
This might only work on KDE4.3, not sure if the plasmoidviewer in 4.2 supports running plasmoids without first installing them. If it fails you should install the plasmoid and run plasmoidviewer yourself (or modify the makefile):
$ make install
$ plasmoidviewer pysnippet-gmonitor
If you never heard of Qt signals and slots you should read this introduction on the topic. For example, a button can 'emit' a signal when it's clicked. A slot (function/method) can be connected to a signal and each time the signal is emitted the function/method will be called.

You can find the Python KDE4.3 API docs here. Unfortunately, I don't find them as good as the Qt docs.

When running the applet, KDE will ask for the username/password when it tries to connect to Google. You can check the 'remember the password' checkbox which will make KDE cache the password as long as the desktop session is alive. If you choose not to, you'll have to enter your username/password each time the code tries to fetch the feed. A better solution is to use KWallet.

Ok, hope you enjoyed it and start doing cool plasmoids!

Monday, November 2, 2009

Kubuntu 9.10

I've installed Kubuntu 9.10 and I'm busy configuring it to a usable state. For the moment I don't have too much spare time but I'm almost done.

I hope I'll have time to do a post about creating plasmoids (KDE4 desktop widgets) in the next couple of days. I think I'll do a gmonitor applet which will show the number of unread mail in your Gmail inbox.