2010-03-28

Samsung Story Station Plus external e-SATA hard drive speed test on Linux

This blog post shows the results of the speed test I've done with my new Samsung Story Station Plus external e-SATA hard drive on a Linux nettop. Executive summary: the drive is large, fast and quiet; e-SATA with hot-plug works fine and full speed on Ubuntu Hardy and Ubuntu Karmic out-of-the box.

Speed results:

  • sustained local hard drive sequential read: 53 MB/s; dd if=/dev/sda of=/dev/null bs=1M
  • sustained local hard drive sequential write: (not tried to avoid possible damage to the existing system)
  • sustained local hard drive random seek + read 4 KB: 50 seeks/s, 206 kB/s
  • sustained external e-SATA hard drive sequential read: 112 MB/s: dd if=/dev/sdb of=/dev/null bs=1M
  • sustained external e-SATA hard drive sequential write: 108 MB/s: dd if=/dev/zero of=/dev/sdb bs=1M seek=32000
  • sustained external e-SATA hard drive random seek + read 4 KB: 90 seeks/s, 370 kB/s

System information:

  • Linux Ubuntu Hardy 64-bit desktop
  • dual Intel Atom 330 @ 1.60GHz
  • Nvidia ION
  • 3.3GB RAM
  • internal hard drive: 2.5"; 250 GB; Hitachi HTS54322 FBEO
  • external e-SATA hard drive: Samsung Story Station Plus; 3.5"; 2TB; SAMSUNG HD203WI 1AN1
  • random seek benchmark implemented in Python, using 0.3% CPU

Other parameters of the external drive:

  • very quiet, I was not able to tell when it's reading during the sequential read or write benchmarks
  • very quiet, I had to lean with my ear 5 cm from the drive to hear it working during the random seek test in a work environment. However, at home, when there is no other noise, I can hear the seeks if I concentrate.
  • low-power (0.1 Watt in standby, goes to standby quickly).

I've repeated the speed test with a Fujitsu Siemens Amilo 3560 notebook running Ubuntu Karmic 32-bit desktop, and the speeds are about the same as with the nettop.

How to cope with NIS (YP) server outages on Linux

This blog post explains how to cope with NIS (YP) server outages on Linux (tested on Debian Lenny), i.e. how to give applications instant and valid response even when the NIS server is unreachable. When parts of the user (passwd) and group database is not stored locally, but on a NIS (YP) server, then each user and group lookup request (such as getpwnam(3) and id USERNAME) is sent to the NIS server. Should the NIS server be down or unreachable, each such request is blocked until a very long timeout (sometimes more than a minute). Also sometimes the client (ypbind) doesn't detect properly that the NIS server is back up again, so user lookups tend to fail or time out even when the NIS server has recovered. Another problem with communicating with the NIS server is that it's hard to write a strict firewall rule, because the UDP port number of the NIS service (ypserv) is not fixed, but dynamically assigned and managed by the RPC portmap service.

The solution presented here involves caching all NIS data locally, updating the cache from a cron job, and directing applications to always read the cache. This design gives instant response to applications, no matter whether the NIS server is reachable, and since cache updates are performed by a dedicated user, more precise firewall packet filters can be written.

The instructions below have been verified on Debian Lenny, but they should work similarly on other Linux systems. It is assumed that the NIS domain to connect to is called mydomain (the real domain name usually available in /etc/domainname), and the IP address of the NIS server is 1.2.3.4.

  1. Make sure root can download the passwd database from NIS. Try this command: # ypcat -d mydomain -h 1.2.3.4 passwd This should print the the NIS user passwd database, including user names and (encrypted) passwords. If the 2nd field is x for all users than the passwords may be in the shadow file on the NIS server. Try this: # ypcat -d mydomain -h 1.2.3.4 shadow If this doesn't print any encrypted passwords, and you need password-based login on your machine for NIS users, then it wouldn't work for you (you may try complaining to the admin of the NIS server).
  2. Install software (as root) to the client Linux system: # apt-get install sudo nis python2.4 libnss-extrausers
  3. Create a user named nis-update: $ adduser --system --home=/tmp/blah --group nis-update (This disables login by default, and sets the user's login shell to /bin/false.)
  4. Make sure that the line in /etc/shadow starting with nis-update: starts with nis-update:*: instead of nis-update-!:. This disables login for that user.
  5. If you have a firewall which restricts outgoing packets on the local machine, you may have to add these rules to allow the nis-update user can download the NIS data:
    # Allow portmap.
    iptables -A OUTPUT -m owner --uid-owner nis-update \
        -p tcp --dport 111 -d 1.2.3.4 -j ACCEPT
    # Allow all Sun RPC, including NIS (YP).
    iptables -A OUTPUT -m owner --uid-owner nis-update \
        -p udp --dport 511:2999  -d 1.2.3.4 -j ACCEPT
  6. Make sure that the nis-update user can download the passwd database from NIS. Try that this command prints all user records: # sudo -c nis-update ypcat -d mydomain -h 1.2.3.4 passwd
  7. Add the following script as an executable /usr/local/sbin/nis-update.py:
    #! /usr/bin/python2.4
    # by pts@fazekas.hu at Sun Mar 28 18:21:20 CEST 2010
    
    """Script to download NIS (YP) users and groups for libnss_extrausers.so* .
    
    This script should be run from a cron job.
    
    Please also check /etc/nsswitch.conf for libnss_extrausers.so* . It reads
    from /var/lib/extrausers/passwd etc.
    """
    
    __author__ = 'pts@fazekas.hu (Peter Szabo)'
    
    import pwd
    import signal
    import sys
    import os
    
    NIS_UPDATE_DOWNLOAD_DIR = '/var/cache/nis-update'
    NIS_UPDATE_COPY_TARGET_DIR = '/var/lib/extrausers'
    NIS_SERVER='1.2.3.4'   #### fix at install time
    NIS_DOMAIN='mydomain'  #### fix at install time
    
    def NisUpdate(uid, gid, nis_filename):
      assert nis_filename in ('passwd', 'group', 'shadow')
      download_filename = os.path.join(NIS_UPDATE_DOWNLOAD_DIR, nis_filename)
      target_filename = os.path.join(NIS_UPDATE_COPY_TARGET_DIR, nis_filename)
      fd = os.open(download_filename, os.O_WRONLY|os.O_TRUNC|os.O_CREAT, 0644)
      pid = os.fork()
      euid = os.geteuid()
      assert euid == uid or euid == 0, (euid, uid)
      if not pid:
        try:
          fdnull = os.open('/dev/null', os.O_RDONLY)
          if fdnull != 0:
            os.dup2(fdnull, 0)
            os.close(fdnull)
          if fd != 1:
            os.dup2(fd, 1)
            os.close(fd)
          if euid != uid:  # It's root
            os.setgroups([])
            os.setregid(gid, gid)
            os.setreuid(uid, uid)
          signal.alarm(5)
          # This doesn't need a running ypbind.
          os.execl('/usr/bin/ypcat', 'ypcat', '-d', NIS_DOMAIN, '-h', NIS_SERVER,
                   nis_filename)
        except:
          exc_info = sys.exc_info()
          print >>sys.stderr, 'error in child: %s: %s' % (
             exc_info[1].__class__, exc_info[1])
          os._exit(1)
      os.close(fd)
      got_pid, status = os.waitpid(pid, 0)
      assert got_pid == pid
      st = os.stat(download_filename)
      if not status:
        status = 0
      if status or not st.st_size:
        print >>sys.stderr, 'warning: child %s failed with status 0x%x' % (
            nis_filename, status)
        return False
      os.rename(download_filename, target_filename)
      return True
    
    if __name__ == '__main__':
      # Don't print anything, we're optimized for running as a cron job. 
      p = pwd.getpwnam('nis-update')
      NisUpdate(p.pw_uid, p.pw_gid, 'passwd')
      NisUpdate(p.pw_uid, p.pw_gid, 'group')
      # TODO: Download and os.chmod the shadow file if necessary.
  8. In the script above, search for ####, and customize those settings.
  9. As root, create the script output directories: # mkdir -p /var/cache/nis-update /var/lib/extrausers; chown root. /var/cache/nis-update /var/lib/extrausers; chmod 700 /var/cache/nis-update; chmod 755 /var/lib/extrausers
  10. Run the script as root: # /usr/local/sbin/nis-update.py This should not print any error messages, and it should create the files /var/lib/extrausers/{passwd,group}.
  11. If you also need the shadow file (because it contains passwords), then modify the script so it creates that file as well.
  12. Add the script to your crontab so it runs every 5 minutes: # echo '0-55/5 * * * * root /usr/local/sbin/nis-update.py' | sudo tee /etc/cron.d/nis-update
  13. Make sure that the nss-extrausers pacakge is installed. (There was an instruction above.) # apt-get install nss-extrausers
  14. Edit /etc/nsswitch.conf, and set the following values:
    passwd: files extrausers
    group: files extrausers
    shadow: files extrausers
    (The previous values were probably compat or files nis.)
  15. At this point, applications would use the cached files /var/lib/extrausers/{passwd,group}. Test this by checking that $ perl -e 'while(@L=getpwent){print join("+",@L),"\n"}' prints all local users followed by all NIS users.
  16. Turn off the NIS client: # /etc/init.d/nis stop
  17. Run the check above again and make sure that all NIS users are present: $ perl -e 'while(@L=getpwent){print join("+",@L),"\n"}'
  18. Disable NIS client startup at boot time: # mv /etc/domainname{,.not}
  19. Occasionally check root's e-mail for error messages from the nis-update.py cron job.

2010-03-27

Python web framework of the day

Since there are quite a lot of web application frameworks for Python, I think one more wouldn't hurt.

I was impressed by the compactness of Ruby's Sinatra:

require 'rubygems'
require 'sinatra'
get '/' do
  'Hello world!'
end

My goal was to create something similar for Python, as a proof-of-concept. Here is the equivalent Hello, world web application with my Python syntax:

from wfotd import *
@GET
def _():
  return 'Hello world!'

The Ruby solution looks much more clear and beautiful. Nevertheless, it was a nice Python coding experiment. I named it PYthon Web Framework Of The Day. The source is available at http://code.google.com/p/pts-mini-gpl/source/browse/#svn/trunk/pywfotd.

Here is a larger Python example, demonstrating the scoping and argument passing features of pywfotd:

from wfotd import *
@GET
def _(name=None):
  name = name or 'World'
  return 'Hello, %s!' % name

@GET
def foo():
  return 'This is /foo'

class bar:
  class baz:
    @GET
    def _():
      return 'This is /bar/baz'
    @GET
    def quux():
      return 'This is /bar/baz/quux'

2010-02-20

How to try the GDM login screen in many resolutions

This blog post explains how to try the GDM login screen and make screen shots in any, user-specified screen resolution. This can be useful when designing GDM themes. The instructions were tried on Ubuntu Hardy (8.04), but they should work on Ubuntu Intrepid (8.10) and Ubuntu Jaunty (9.04). They won't work on Ubuntu Karmic or Ubuntu Lucid, because they contain GDM 2, which doesn't have the --xnest feature implemented (** (gdmflexiserver:18758): WARNING **: Not yet implemented). Installation instructions:

  1. (When asked to run a command, run it in a terminal window.)
  2. Make sure GDM is your display manager. Since GDM is the default, chances are that it is. To make sure, run sudo dpkg-reconfigure gdm, and select GDM if it asks you.
  3. Log in using GDM if you haven't already done so. To do that, run sudo /etc/init.d/gdm restart . (You will lose your X11 session doing so.)
  4. Install Xephyr, a modern nested (embedded) X server, similar to Xnest: sudo apt-get install xserver-xephyr
  5. Install ImageMagick for making screen shots in various image file formats: sudo apt-get install imagemagick
  6. Try Xephyr: Xephyr :9 -screen 800x600 -extension RANDR . A new window should appear with the usual a black-and-white diagonal background and a diagonal cross-shaped cursor. Abort Xephyr in the terminal window. Try with different -screen sizes.
  7. Find your GDM Xnest command line by running grep ^Xnest= /etc/gdm/gdm.conf . You should see something like Xnest=/usr/share/gdm/gdmXnestWrapper -br -audit 0.
  8. Create a new xnest wrapper script:
    echo '#!/bin/sh
    GEOMETRY="`cat /tmp/new-xnest-geometry`"
    GEOMETRY="${GEOMETRY:-800x600}"
    exec Xephyr "$@" -screen "$GEOMETRY" -extension RANDR' |
    sudo tee  /usr/local/sbin/gdm-xnest &&
    chmod 755 /usr/local/sbin/gdm-xnest
  9. Create the screenshot helper script:
    echo '#! /bin/bash --
    # by pts@fazekas.hu at Sat Feb 20 11:09:20 CET 2010
    GOT=($(ps x | perl -ne '\''if (/[ ]Xephyr :/) {
      $c++;
      print"$1 $2\n" if (/[ ]Xephyr :(\d+)(?= ).* -auth (\S+)/)}
      END{die"Xephyr not found\n"if!$c;die"multiple Xephyrs found\n"if$c>1}'\''))
    test "$?" = 0 || exit "$?"
    if test ${#GOT} != 2; then
      echo "Xephyr detection failed" >&2
      exit 90
    fi
    export DISPLAY=":${GOT[0]}" XAUTHORITY="${GOT[1]}"
    exec import -window root "$@"' |
    sudo tee -a    /usr/local/bin/xephyr-import &&
    sudo chmod 755 /usr/local/bin/xephyr-import 
  10. Change the Xrandr= setting in /etc/gdm/gdm.conf to use the new wrapper script: sudo perl -pi -0777 -e 's@^Xnest=.*@Xnest=/usr/local/sbin/gdm-xnest -br -audit 0@gm' /etc/gdm/gdm.conf
  11. Make sure the Xrandr= setting is changed: check that running grep ^Xnest= /etc/gdm/gdm.conf displays Xnest=/usr/local/sbin/gdm-xnest -br -audit 0
  12. Save your work in your X11 session, and restart GDM: sudo /etc/init.d/gdm restart . (You will lose your X11 session doing so.)
Usage instructions (how to show the GDM login dialog inside a window, and how to make a screen shot):
  1. If not already logged in, login in at the GDM login screen.
  2. Run gdmflexiserver --xnest to get the GDM login dialog inside a window of your regular session. You can specify the window size using e.g. echo 1024x768 >/tmp/new-xnest-geometry && gdmflexiserver --xnest
  3. To make a JPEG screen shot, run xephyr-import -quality 75 screenshot.jpg while the Xephyr window with the GDM login screen is still open.
  4. To make a PNG screen shot, run xephyr-import screenshot.png .

2010-02-19

How to create a Debian/Ubuntu package (.deb) manually

This blog post gives some hints how to create a .deb package file (to be used with Debian, Ubuntu and similar Linux distributions). The intended audience is programmers and system administrators, both with a strong Debian or Ubuntu Linux background and strong scripting skills. It is assumed that you know almost everything about your Linux system, you have installed .deb packages by hand (with dpkg. and apt-get), you understand the dependencies etc., and the only information you need how to create a .deb package from your software.

This blog post describes a low-level, hacky and quick approach with little automatic validation and instrumentation. This approach is useful for packages which don't require compilation or complicated runtime configuration. To start learning the more consistent, heavy-weight, validated approaches, fetch some source packages with apt-get source, and have a look at their debian subdirectories. The merit of our approach is simplicity (you have to understand only a few basic concepts to create your first package) and speed. Disadvantages are that it lets you create suboptimal, nonworking or nonstandard packages since there is only very little automatic validation, so you may sometimes recognize problems with your .deb packages too late, when it's too expensive to fix them; also this approach doesn't force you learn and follow the conventions and best practices of your Linux distribution.

First, have a look at the contents of .deb packages similar to your software. To get the .deb file, use apt-get install --reinstall ${PACKAGENAME}, abort the process once all files are downloaded, and fetch the file you are interested in from /var/cache/apt/sources. Once you have the .deb file, extract it with dpkg-deb:

$ dpkg-deb -x .../${MYFILE}.deb /tmp/${MYFILE}
$ dpkg-deb -e .../${MYFILE}.deb /tmp/${MYFILE}/DEBIAN

Important text files are:

  • /tmp/${MYFILE}/DEBIAN/control: all meta-information: package name, size, version number, dependencies, short description, long description
  • /tmp/${MYFILE}/DEBIAN/md5sums: md5sum values for all regular files in the package
  • /tmp/${MYFILE}/DEBIAN/postinst: executable script which will be run by dpkg -i after dependencies are installed, and files of this package are extracted
  • /tmp/${MYFILE}/DEBIAN/preinst: a similar executable script
  • /tmp/${MYFILE}/DEBIAN/prerm: another similar executable script
  • /tmp/${MYFILE}/DEBIAN/postrm: another similar executable script

Use Google to find out more about these (and possibly other) special files. To get examples, look at the special files of the packages installed to your system; those special files are in /var/lib/dpkg/info.

Google is your friend. Search for how to build a debian package. The first two results I've found seem to be useful, I strongly recommend you read them: http://tldp.org/HOWTO/html_single/Debian-Binary-Package-Building-HOWTO/; http://www.linuxfordevices.com/c/a/Linux-For-Devices-Articles/How-to-make-deb-packages/. However, be prepared that some of the information you find is outdated or obsoleted. Always compare the instructions found on the web to what the packages of your distribution contain. Also be prepared that you don't find any definitive, comprehensive, up-to-date or useful documentation on a particular subtopic: learn from examples then.

Follow the these steps to create your .deb package:

  1. Learn everything you possible need to know, as indicated above. Learn primarily from examples (by examining some relevant packages on your Linux distribution).
  2. Create a directory named ${PACKAGENAME}_${VERSION}_${ARCHITECTURE}. We'll call this the package directory.
  3. Create subdirectories and files in the package directory. For example, if you want to have your package contain /usr/bin/footool, then create usr, create usr/bin and copy your prebuilt footool binary to usr/bin.
  4. Make sure the owner, group and permission bits are right (as intended on the target system) for all files and directories.
  5. Create the DEBIAN subdirectory in the package directory.
  6. Create your DEBIAN/control file, possibly copying a similar one, and modifying some fields.
  7. If you need to run some code during installation after the package files are extracted to /, then create DEBIAN/postinst as a script (usually starting with #! /bin/sh), and make it executable. To prevent future troubles, make this script robust and idempotent, i.e. it should exit successfully even after a previous half-finished package installation. However, if there is something wrong in the system, the script should exit with a non-zero exit code. set -e can help here: it makes the shell running the script exit at the first nonsuccessful command (outside a subshell).
  8. Compute md5sums by running md5sum `find . -type f | grep -v '^[.]/DEBIAN/'` >DEBIAN/md5sums in the package directory.
  9. Set the Installed-Size: field of the DEBIAN/control file to last number in the total output of du run in the package directory.
  10. Make sure that the package name, version number and architecture in the package directory name are the same is what's indicated in DEBIAN/control.
  11. Make sure there are no backup files (*~, especially DEBIAN/*~) or temporary files lying around inside the package directory.
  12. Go one level above the package directory, and run dpkg-deb -b ${PACKAGENAME}_${VERSION}_${ARCHITECTURE} . This creates ${PACKAGENAME}_${VERSION}_${ARCHITECTURE}.deb.
  13. Your .deb package is ready. Try installing it to your system (sudo dpkg -i ${PACKAGENAME}_${VERSION}_${ARCHITECTURE}.deb . Play with it. Fix problems and rebuild it as many times as necessary. Increase the version number.

2010-02-18

How to create a screen shot of the GDM login screen on Ubuntu Hardy

This blog pts explains how to create a screen shot of the currently running X11 GDM login screen (the graphics login screen at system startup) on Ubuntu Hardy.

  1. Install ImageMagick for the image file format conversion below:
    $ sudo apt-get install imagemagick
  2. Create a helper script:
    $ echo 'DISPLAY=:0 XAUTHORITY=/var/lib/gdm/:0.Xauth xwd -root' >/tmp/shot.sh
  3. Make sure your login screen is active (log out or reboot the machine, and wait until you see the login screen).
  4. Log in in text mode (by pressing Ctrl-Alt-F1), or using SSH.
  5. Create the screen shot by running
    $ sudo bash /tmp/shot.sh >/tmp/shot.xwd
  6. You can log in now (by pressing Ctrl-AltF7 first to get back to the GDM login screen).
  7. Convert the screen shot to JPEG and/or PNG:
    $ convert -quality 50 /tmp/shot.xwd /tmp/shot.jpg
    $ convert /tmp/shot.xwd /tmp/shot.png
  8. View the screen shot in your favourite image viewer.

2010-02-11

How to run Firefox 3.6 on Debian Etch

This blog post explains how to run Firefox 3.6 32-bit on an old Linux system, such as Debian Etch.

First, here is some technical background. The most important problem with the stock Firefox 3.6 for Linux (especially with Flash Player 10) is that it needs newer versions of system libraries than what is available on some old Linux systems, such as Debian Etch. Copying those libraries from a newer system (such as Ubuntu Hardy) does the trick, except that some other data files (such as Pango's libpango1.0-0.modules file) also have to be copied, and Linux has to be persuaded that it should read libraries and data files from non-standard locations. For the latter, setting LD_LIBRARY_PATH and specifying the new lib/ld-linux.so.2 explicitly almost solves the problem, but it won't force the new directory for the config and data files. For that, we have to LD_PRELOAD our library overriding open(), fopen() etc.

Here is how to download and prepare it:

# Exit from the firefox.
# Make sure gcc, tar, bzip2, wget and an X11 system with the GTK libraries
# (no matter how old) are installed.
$ killall firefox-bin
$ killall -9 firefox-bin
$ cd /tmp
$ wget -O pts-etch-firefox3.6.tbz2 \
  http://pts-mini-gpl.googlecode.com/files/pts-etch-firefox3.6.tbz2
$ tar xjf pts-etch-firefox3.6.tbz2
$ (cd firefox3.6; ./fakeopen.c)

Here is how to use it:

$ firefox3.6/start.sh
... (prints a lot, but eventually opens a Firefox window)

Later the directory can be moved to /usr/local, properly chmodded, and a symlink can be created, like this:

sudo ln -sf /usr/local/firefox3.6/start.sh /usr/local/bin/firefox

2010-01-31

How to swap two nodes in a doubly linked list

This blog post gives example C code how to swap two elements (nodes, items) of a doubly linked list. The code works for both circular and non-circular lists, even if the two arguments are the same, or if they are adjacent in the list. (It is surprisingly complicated to give a correct and elegant solution.)

#include <stdlib.h>  /* NULL */

typedef int node_data_t;  /* can be any other type as well */

struct node {
  struct node* prev;
  struct node* next;
  node_data_t data;
};

/** If node1 and node2 are non-NULL members of a doubly-linked list
 * (circular or not), swap their data fields.
 */
void swap_data(struct node* node1, struct node* node2) {
  node_data_t temp_data = node1->data;
  node1->data = node2->data;
  node2->data = temp_data;
}

/** If node1 and node2 are non-NULL members of a doubly-linked list
 * (circular or not), swap their data fields. If node1 or node2 is the head
 * of the non-circular list, return the new head.
 */
struct node* swap(struct node* node1, struct node* node2) {
  struct node* temp;
  temp = node1->next;
  node1->next = node2->next;
  node2->next = temp;
  if (node1->next != NULL)
    node1->next->prev = node1;
  if (node2->next != NULL)
    node2->next->prev = node2;
  temp = node1->prev;
  node1->prev = node2->prev;
  node2->prev = temp;
  if (node1->prev != NULL)
    node1->prev->next = node1;
  if (node2->prev == NULL)
    return node2;
  node2->prev->next = node2;
  return node1;
}

The code above is based on the discussion at http://bytes.com/topic/c/answers/219236-double-linked-list-elements-swap

swap_data is simpler, and it's also faster if the data is small (i.e. at most 2 pointers). However, swap_data cannot be used when there are external pointers inside the data.

2010-01-30

How to make Midnight Commander exit to its current directory

This blog post gives instructions how to make the current directory of the shell calling Midnight Commander be mc's current directory upon exiting from mc.

The quick answer is to add alias mc=". /usr/share/mc/bin/mc-wrapper.sh" (or something similar, with a different path) to your shell startup script. If you don't know how to do that, read on.

On Ubuntu (Hardy, Jaunty, Karmic etc.) or Debian (Lenny etc.), run this as root (copy-paste as a whole):

grep -l bash\.bashrc /etc/profile || (echo
    echo 'test "$PS1" && test "$BASH" && . /etc/bash.bashrc') |
    tee -a /etc/profile
echo 'type -p -a mc >/dev/null &&
    alias mc=". /usr/share/mc/bin/mc-wrapper.sh"' | tee -a /etc/bash.bashrc

On the Mac OS/X with mc installed from MacPorts, open a Terminal window, type sudo bash, press Enter, type your password, press Enter, and then run the following (copy-paste as a whole):

grep -l bashrc /etc/profile || (echo
    echo 'test "$PS1" && test "$BASH" && . /etc/bashrc') |
    tee -a /etc/profile
echo 'type -p -a mc >/dev/null &&
    alias mc=". /opt/local/share/mc/bin/mc-wrapper.sh"' | tee -a /etc/bashrc

After doing so, open a new terminal window (or SSH connection), and it should work as expected (try it by running mc /tmp, and exiting by pressing Esc, 0, Enter).

For your information, the underlying solution (in mc-wrapper.sh) makes Midnight Commander print its current directory when it exits, and then the caller shell code does a cd command to that directory.

2010-01-16

How to regenerate the Apache SSL key and certificate on Debian Lenny

This blog post explains how to regerated the Apache 2 SSL server key and certification on Debian Lenny.

The default, self-signed certificate used by Apache 2 is called snakeoil, it's generated based on the hostname (as reported by hostname -f) to files /etc/ssl/certs/ssl-cert-snakeoil.pem and /etc/ssl/private/ssl-cert-snakeoil.key when the ssl-cert package is installed. Here is how to regenerate the key and the certificate in case the hostname is changed:

# hostname -f
# make-ssl-cert generate-default-snakeoil --force-overwrite
# ls -l /etc/ssl/certs/ssl-cert-snakeoil.pem /etc/ssl/private/ssl-cert-snakeoil.key
... (check the last-modified-time)
# /etc/init.d/apache2 restart

2010-01-08

Emulating Stackless Python using greenlet

This blog post documents why and how to emulate Stackless Python using greenlet.

Stackless Python is an extended version of the Python language (and its CPython reference implementation). New features include lightweight coroutines (called tasklets), communication primitives using message passing (called channels), manual and/or automatic coroutine scheduling, not using the C stack Python function calls, and serialization of coroutines (for reloading in another process). Stackless Python could not be implemented as a Python extension module – the core of the CPython compiler and interpreter had to be patched.

greenlet is an extension module to CPython providing coroutines and low-level (explicit) scheduling. The most important advantage of greenlet over Stackless Python is that greenlet could be implemented as a Python extension module, so the whole Python interpreter doesn't have to be recompiled in order to use greenlet. Disadvantages of greenlet include speed (Stackless Python can be 10%, 35% or 900% faster, depending on the workflow); possible memory leaks if coroutines have references to each other; and that the provided functionality is low-level (i.e. only manual coroutine scheduling, no message passing provided).

greenstackless, the Python module I've recently developed, provides most of the (high-level) Stackless Python API using greenlet, so it eliminates the disadvantage of greenlet that it is low-level. See the source code and some tests (the latter with tricky corner cases). Please note that although greenstackless is optimized a bit, it can be much slower than Stackless Python, and it also doesn't fix the memory leaks. Using greenstackless is thus not recommended in production environments; but it can be used as a temporary, drop-in replacement for Stackless Python if replacing the Python interpreter is not feasible.

Some other software that emulates Stackless using greenlet:

  • part of Concurrence: doesn't support stackless.main, tasklet.next, tasklet.prev, tasklet.insert, tasklet.remove, stackless.schedule_remove, doesn't send exceptions properly. (Because of these features missing, it doesn't pass the unit test above.)
  • part of pypy doesn't support stackless.main, tasklet.next, tasklet.prev, doesn't pass the unit test above.

For the other way round (emulating greenlet using Stackless), see greenlet_fix (source code). Although Stackless is a bit faster than greenlet, the emulation in greenlet_tix makes it about about 20% slower than native greenlet.

My original purpose for this emulation was to use gevent with Stackless and see if it becomes faster (than the default greenlet). It turned out to become slower. Then I benchmarked Concurrence (with libevent and Stackless by default), pyevent with Stackless, pyevent with greenlet, gevent (with libevent and greenlet by default), Syncless (with epoll and Stackless by default), eventlet and Tornado (using callbacks), and found out the following:

  • Syncless was the fastest in my nonscientific benchmark, which was suprising since it had a clumsy event loop implementation in pure Python.
  • Wrapping libevent's struct evbuffer in Pyrex for line buffering is slower than manual buffering.
  • Using input buffering (for readline()) is much slower than manual buffering (reading until '\n\r\n' and splitting the input by \n).
  • Providing a proper WSGI environment is much slower than ad-hoc parsing of the first line of the HTTP request.
  • Wrapping libevent's struct evbuffer and the coroutine switching in Pyrex made it about 5% faster than manual buffering.
  • Syncless does too much unnecessary communication (using Stackless channels) between a worker and a main loop. This can be simplified and made faster using stackless.schedule_remove(). So the current Syncless implementation is a dead end, it should be rewritten from scratch.

My conclusion was that in order to get the fastest coroutine-based, non-blocking, line-buffering-capable I/O library for Python, I should wrap libevent (including event registration and I/O buffering) and Stackless and the WSGI server in hand-optimized Pyrex, manually checking the .c file Pyrex generates for inefficiencies. I'll be doing so soon.

2010-01-03

Does string append run in linear time in Python?

In Python 2.x, the string append operation (string1 += string2) runs in average linear time (in the length of string2) iff there is only one reference to string1.

Example (leave exactly one of the lines in the loop body uncommented):

s = 'v' 
h = {'k': 'v'} 
for i in xrange(int(__import__('sys').argv))): 
  s += 'v'  # linear (one reference to s) 
  t = s; s += 'v'  # quadratic (two references to s) 
  h['k'] += 'v'  # quadratic (two intermediate references to h['k']) 
  s = h['k']; h['k'] = ''; s += 'W'; h['k'] = s  # linear 
  s = h.pop('k'); s += 'W'; h['k'] = s  # linear

2009-12-31

How to fix the Ctrl-Y Ctrl-Z inconsistency in GNOME Terminal with international keyboard layouts

This blog post describes and fixes an inconsistency between some keys with or without Ctrl in GNOME Terminal with international keyboard layouts.

The quick fix:

$ wget -O- -q http://pts-mini-gpl.googlecode.com/svn/trunk/pts-vtefix/vtefix.sh |
sudo bash

In GNOME Terminal, with the Hungarian and German keyboard layouts the keys Y and Z are switched with respect to the US English layout. So when the national layout is active, one of the keys yields the string "z", but when the same key is pressed with Ctrl, it yields "^Y", instead of the expected "^Z". All other applications (e.g. GIMP, Firefox, xterm) work as expected, except for those using VTE (e.g. GNOME Terminal).

Here is a script which fixes this on Ubuntu Hardy, Ubuntu Karmic and Ubuntu Lucid:

perl -we '
  use integer;
  use strict;
  my $fn = $ARGV[0];
  print STDERR "info: vtefix library filename: $fn\n";
  die "error: file not found: $fn\n" if !open F, "<", $fn;
  $_ = join "", ;
  my @L;
  while (/\xf6[\x80-\x8f].[\x09-\x0a]\0\0[\x00\x04]
         |\x80[\x78-\x7f].\0\x74.\xf6\x85.\xff\xff\xff[\x00\x04]
         |\x80\x7d\x32\x00\x74\x11\x41\xf6\xc5[\x00\x04]
          (?=(?:\x0f\x1f\x80\0\0\0\0)?\x0f\x85)
         /sgx) {
    push @L, pos($_) - 1;
  }
  # We patch only the first occurrence, the 2nd one is different.
  pop @L if @L == 2 and vec($_, $L[0] - 6, 8) == 0xf6 and
                        vec($_, $L[1] - 6, 8) == 0xf6;
  if (@L == 1) {
    if (vec($_, $L[0], 8) == 4) {  # need to patch
      print "info: patching at offset $L[0]\n";
      die "error: cannot open file for writing: $fn\n" if !open F, "+<", $fn;
      die if !sysseek F, $L[0], 0;
      die if !syswrite F, "\0";
      print "info: patching OK, no need to restart gnome-terminal\n";
    } else {
      print "info: already patched at offset $L[0]\n";
      exit 2;
    }
  } else {
    die "error: instruction to patch not found (@L)\n";
  }
' -- "${1:-/usr/lib/libvte.so.9}"

Download the script from: http://pts-mini-gpl.googlecode.com/svn/trunk/pts-vtefix/vtefix.sh

The script has to be run each time after the libvte9 package is upgraded.

The script modifies the i386 and amd64 compiled libvte binary:

# amd64 Ubuntu Hardy:
# $ objdump -d /usr/lib/libvte.so.9.2.17 | grep testb | grep '[$]0x4,'
# 2a786:       f6 80 88 0a 00 00 04    testb  $0x4,0xa88(%rax)
# 2a7ba:       f6 87 88 0a 00 00 04    testb  $0x4,0xa88(%rdi)
# 
# i386 Ubuntu Hardy:
# $ objdump -d /usr/lib/libvte.so.9.2.17 | grep testb | grep '[$]0x4,'
# 25cd8:       f6 80 4c 09 00 00 04    testb  $0x0,0x94c(%eax)
# 25d0d:       f6 81 4c 09 00 00 04    testb  $0x4,0x94c(%ecx)

This corresponds to the following snipped in vte-0.16.3/src/vte.c:

if (handled == FALSE && normal == NULL && special == NULL) {
  if (event->group &&
      (terminal->pvt->modifiers & GDK_CONTROL_MASK))
    keyval = vte_translate_national_ctrlkeys(event);
  keychar = gdk_keyval_to_unicode(keyval);

The script replaces GDK_CONTROL_MASK (== 4) by 0, so the condition is always false, and vte_translate_national_ctrlkeys(...) is never called, which fixes the problem. Due to memory mapping, it is not necessary to restart GNOME Terminal instances already running.

To find the spot to patch in the binary, grep for gdk_keyval_to_unicode in the output of objdump -d /usr/lib/libvte.so.9.

More information about the bug (problem):

2009-12-19

Experimental HTTP server using Stackless Python

This blog post documents my experiment to write a non-blocking HTTP server based on coroutines (tasklets) of Stackless Python. My goal was to write a minimalistic web server server which can handle cuncurrent requests by using non-blocking system calls, multiplexing with select(2) or epoll(2), returning a simple Hello, World page for each request, using the coroutines of Stackless Python. I've done this, and measured its speed using ApacheBench, and compared it to the Hello, World server of Node.js.

The code is here: http://code.google.com/p/pts-mini-gpl/source/browse/#svn/trunk/pts-stackless-httpd http://syncless.googlecode.com/svn/trunk/benchmark.old/

Relevant ApacheBench spee results (for ab -n 100000 -c 50 http://127.0.0.1:.../):

Notes about the speed measurements:
  • I was using a recently compiled Stackless Python 2.6 and a recently compiled psyco for JITting.
  • I was surpriesed that my experimental code using select(2) and Stackless Python is faster than Node.js (by a factor of 1.925 on average, and the worst-case times are faster as well).
  • The speed comparison is not fair since Node.js has a real HTTP server protocol implementation, with its overhead, and my code just skips the HTTP header without parsing it.
  • Setting the TCP socket listen queue size to 100 (using listen(2)) made a huge difference on the worst case connection time. Compared to the setting of 5, it reduced the worst-case connection time from 9200 ms to 23 ms (!) in the measurement.
  • The source code of both servers can be found in the repository above.
  • My conclusion about the speed measurements is that a HTTP server based on Stackless Python and epoll(2) can be a viable alternative of Node.js. It would be worthwhile implementing one, and then doing proper benchmarks.

The advantage of using Stackless Python over callback-based solutions (such as Node.js in JavaScript, Twisted and Tornado) is that one can implement a non-blocking TCP server without being forced to use callbacks.

The unique advantage of Node.js over other solutions is that in Node.js not only socket communication is non-blocking, but DNS lookups, local filesystem access and other system calls as well – Node.js is non-blocking by design, but with other frameworks the programmer has to be careful not to accidentally call a blocking function. Avoiding a blocking function is especially cumbersome if a library used only provides a blocking interface.

Update: I've created a HTTP server capable of running WSGI applications. I've also integrated dnspython as an asynchronous DNS resolver. See it as project Syncless.

Update: Added (web.py) and CherryPy support.

Update: I've realized that the functionality of Syncless has already been implemented many times in Python. Examples: Concurrence, eventlet, gevent. See the comparison.

Minimalistic client for Amazon S3 (AWS) in Python

I've written py-mini-s3, a minimalistic client for Amazon S3 (AWS) in pure Python. It supports the GET and PUT operations. See its source code.

The implementation is based on the documentation http://docs.amazonwebservices.com/AmazonS3/latest/index.html?RESTAuthentication.html

2009-12-15

Sex, Unix style

My instruction sequence compilation of the traditional Unix sex theme:

date; touch; kiss; grep; strip; unzip; finger; mount; fsck; more; yes; uptime; gasp; umount; sleep

2009-12-06

Polynomials in Python with operator overloading

This blog post is a fun example for implementing univariate polynomials with operator overloading in Python. It is almost a domain-specific language (DSL). Here is the code:

#! /usr/bin/python2.4

class Polynomial(object):

  __attrs__ = ['co']

  def __init__(self, co=None):
    if co:
      co = list(co)
      while len(co) > 1 and not co[-1]:
        co.pop()
      self.co = co
    else:
      self.co = [0]

  def __repr__(self):
    items = []
    for i in xrange(len(self.co)):
      if self.co[i]:
        if i == 0:
          items.append('%s' % self.co[i])
        else:
          if self.co[i] == 1:
            pre = ''
          else:
            pre = '%s * ' % self.co[i]
          if i == 1:
            post = ''
          else:
            post = ' ** %s' % i
          items.append('%sx%s' % (pre, post))
    return ' + '.join(items) or '0'

  __str__ = __repr__

  def __call__(self, x):
    i = len(self.co)
    v = 0
    while i > 0:
      i -= 1
      v = self.co[i] + x * v
    return v

  def __add__(self, p):
    if isinstance(p, Polynomial):
      q = self
      if len(p.co) > len(q.co):
        p, q = q, p
      co = list(q.co)
      for i in xrange(len(p.co)):
        co[i] += p.co[i]
    else:
      co = list(self.co)
      co[0] += p
    return type(self)(co)

  __radd__ = __add__

  def __mul__(self, p):
    if isinstance(p, Polynomial):
      co = []
      for i in xrange(len(self.co) + len(p.co) - 1):
        s = 0
        for j in xrange(max(0, i - len(p.co) + 1), min(i + 1, len(self.co))):
          s += self.co[j] * p.co[i - j]
        co.append(s)
      return type(self)(co)
    else:
      return type(self)([x * p for x in self.co])

  __rmul__ = __mul__

  def __pow__(self, n):
    assert isinstance(n, int)
    assert n >= 0
    if n == 0:
      return type(self)([1])
    co = type(self)(self.co)
    while n > 1:
      co = co * self
      n -= 1
    return co

  #__rpow__ = __pow__

  def __sub__(self, p):
    return self + (-1) * p

  def __rsub__(self, p):
    return (-1) * self + p

x = Polynomial([0, 1])

# --- Test

for p in (x * (x + 1) * (2 * x + 1),
          (x + 1) * (1 + x),
          (x + 2) * (2 + x),
          (x + 3) ** 5,
          0 * x,
          x ** 2 - 2 * x + 1,
          (x + 3) + (3 * x ** 2 + 6) + x ** 4):
  print 'p = ', p
  print 'p(0) = ', p(0)
  print 'p(7) = ', p(7)
  print

Similar code for symbolic calculation with polynomials in Perl.

2009-11-17

How fast does 8g in Google Go compile?

To get an idea how fast Google Go code can be compiled, I've compiled a big chunk of the Google Go library code with the default 8g compiler. Here are the results:
  • 90 8g compiler invocations
  • no linking
  • 21MB .8 object data generated
  • 89540 lines of .go source from goroot/src/pkg
  • 345305 words of .go source
  • 2.22MB of .go source
  • 17908 lines per second compilation speed
  • 3676757 .8 bytes per second compilation speed
  • 389473 source bytes per second compilation speed
  • average 5.7s real time
  • average 4.8s user time
  • average 0.9s system time
  • 3G RAM and Intel Core2 Duo CPU T5250 @ 1.50GHz

As a quick comparison, I've compiled Ruby 1.8.6 with gcc:

  • 38 invocations of gcc -s -O2 (GCC 4.2.4)
  • 38 .c files of Ruby 1.8.6 source code (without extensions)
  • 15 .h files
  • 1044075 bytes generated in .o files
  • 92543 source lines
  • 283554 source words
  • 2.15MB of source code
  • 62190 source bytes per second compilation speed
  • 27916 .o bytes per second compilation speed
  • 37.4s real time
  • 35.9s user time
  • 1.0s system time
  • 0.4626 .o bytes generated per source byte

What I see here is that 8g is 131 times faster than GCC if we count object bytes per second, and it is 6.26 times faster if we count source bytes per second. But we cannot learn much from these ratios, because not the same code was compiled. What is obvious is that Go object files are much larger than C object files (relative to their source files) in this experiment.

How to write a program with multiple packages in Google Go

This blog post explains how to write a Google Go program which contains multiple packages. Let's suppose your software contains multiple packages: a and b, a consisting of two source files: a1.go and a2.go, and b contains only 1 source file importing package a. Here is an example:

$ cat a1.go
package a
type Rectangle struct {
  width int;
  Height int;
}

$ cat a2.go
package a
func (r *Rectangle) GetArea() int { return r.width * r.Height }
func (r *Rectangle) Populate() {
  r.width = 2;
  r.Height = 3;
}

$ cat b.go
package main
import (
  "a";
)
func main() {
  r := new(a.Rectangle);
  r.Populate();
  // Fields (etc.) starting with a small letter are undefined.
  // (private): b.go:9: r.width undefined (type a.Rectangle has no field width)
  // print(r.width, "\n");
  print(r.GetArea(), "\n");
  print(r.Height, "\n");
  print(r, "\n");
  print("done\n")
}

$ cat abc.sh
#! /bin/bash --
set -ex
rm -f *.6 6.out
6g -o a.6 a1.go a2.go
6g -I. b.go
6l b.6  # links a.6 as well

$ ./abc.sh
...
$ ./6.out
6
3
0x7f18309691a8
done

Notes:

  • Symbols and structure fields starting with lower case are not visible in other packages.
  • You have to specify -I. for the compiler so it will find a.6 in the current directory.
  • The linker figures out that it has to load prerequisite a.6 when linking b.6.

2009-11-15

FUSE protocol tutorial for Linux 2.6

Introduction and copyright

This is a tutorial on writing FUSE (Filesystem in UserSpacE for Linux and other systems) servers speaking the FUSE protocol and the wire format, as used on the /dev/fuse device for communication between the Linux 2.6 kernel (FUSE client) and the userspace filesystem implementation (FUSE server). This tutorial is useful for writing a FUSE server witout using libfuse. This tutorial is not a reference: it doesn't contain everything, but you should be able to figure out the rest yourself.

This document has been written by Péter Szabó on 2009-11-15. It can be used and redistributed under a Creative Commons Attribution-Share Alike 2.5 Switzerland License.

This tutorial was based on versions 7.5 and 7.8 of the FUSE protocol (FUSE_KERNEL_VERSION . FUSE_KERNEL_MINOR_VERSION), but it should work with newer versions in the 7 major series.

This tutorial doesn't give all the details. For that see the sample Python source code for a FUSE server.

Further reading

There is no complete and up-to-date reference manual for the FUSE protocol. The best documents and sources are:

Requirements

  • Linux 2.6 (even though FUSE has been ported to many operating systems, this tutorial focuses on the default Linux implementatation);
  • the fuse kernel module 7.5 or later compiled and loaded;
  • you being familiar with writing a FUSE server (with libfuse or one of the Perl, Python, Ruby or other scripting language bindings);
  • support for running external programs (like with system(3)) in the programming language;
  • support for creating socketpairs (socketpair(2)) in the programming language;
  • support for receiving filehandles (with recvmsg(2)) in the programming language (this is tricky, see below).

Overview of the operation of a FUSE server

This overview assumes that the FUSE server is single-threaded.
  1. Fetch the mount point and the mount options from the command-line.
  2. Optionally, create the directory $MOUNT_POINT (libfuse doesn't do this).
  3. Optionally, do a fusermount -u $MOUNT_POINT to clean up an existing or stale FUSE filesystem in the mount point directory. (libfuse doesn't do this).
  4. Run fusermount(1) to mount a filesystem.
  5. Receive the /dev/fuse filehandle from fusermount(1).
  6. Receive, process and rely o the FUSE_INIT message.
  7. In an infinite loop:
    1. Receive a message (with a buffer or ≤ 8192 bytes, recommended 65536 + 100 bytes) from the FUSE client on the file descriptor.
    2. If ENODEV is the result, or FUSE_DESTROY is received, break from the loop.
    3. Process the message.
    4. Send the reply to on the file descriptor, except for FUSE_FORGET.
  8. Clean up so your backend filesystem remains in consistent state.
  9. Exit from the FUSE server process.

fusermount(1), when called above does the following:

  1. Uses its setuid bit to run as root.
  2. Opens the character device /dev/fuse.
  3. Mounts the filesystem with the mount(2) system call, passing the file descripto of /dev/fuse to it.

Steps of running fusermount(1) and obtaining the /dev/fuse file descriptor:

  1. Create a socketpair (AF_UNIX, SOCK_STREAM) with fd0 and fd1 as file descriptors.
  2. Run (with system(3)): export _FUSE_COMMFD=$FD0; fusermount -o $OPTS $MOUNT_POINT . Example: export _FUSE_COMMFD=3; fusermount -o ro /tmp/foo .
  3. Receive the /dev/fuse file descriptor from fd1. This is tricky. See receive_fd function in the sample receive_fd.c for this. The sample fuse0.py contains a Python implementation (using the ctypes or the dl module to call C code).
  4. Close fd0 and fd1.

Wire format and communication

Once you have received the /dev/fuse file descriptor, do a read(dev_fuse_fd, bug, 8192) on it to read the FUSE_INIT message, and you have to send your reply. After that, you should be reading more messages, and reply to all of them in sequence (except for FUSE_DESTROY and FUSE_FORGET messages, which don't require a reply). All input (FUSE client → server) message types share the same, fixed-length header format, but the message may contain optional, possible variable-length parts as well, depending the message type (opcode). Nevertheless, the whole message must be read in a single read(3), so you have to preallocate a buffer for that (at least 8192 bytes, may be larger based on FUSE_INIT negotiation, preallocate 65536 + 100 bytes to be safe). All integers in messages are unsigned (except for the negative of errno).

The input message header is:

  • uint32 size; size of the message in bytes, including the header;
  • uint32 opcode; one of the FUSE_* constants describing the message type and the interpretation of the rest of the header;
  • uint64 unique; unique identifier of the message, must be repeated in the reply;
  • uint64 nodeid; nodeid (describing a file or directory) this message applies to (can be FUSE_ROOT_ID == 1, or a larger number, what you have returned in a previous FUSE_LOOKUP repy);
  • uint32 uid; the fsuid (user ID) of the process initiating the operation (use this for access control checks if needed);
  • uint32 gid; the fsgid (group ID) of the process initiating the operation (use this for access control checks if needed);
  • uint32 gid; the PID (process ID) of the process initiating the operation;
  • uint32 padding; zeroes to pad up to 64-bits.
The interpretation of the rest of the input message depends on the opcode. The most common input message types are:
  • FUSE_LOOKUP = 1: input is a '\0'-terminated filename without slashes (relative to nodeid), output is struct fuse_entry_out;
  • FUSE_FORGET = 2: input is a struct fuse_forget_in, there is no output message;
  • FUSE_GETATTR = 3: input is empty, output is struct fuse_attr_out;
  • FUSE_OPEN = 14: input is struct fuse_open_in, output is struct fuse_open_out;
  • FUSE_READ = 15: input is struct fuse_read_in, output is the byte sequence read;
  • FUSE_RELEASE = 18: input is struct fuse_release_in, output is empty;
  • FUSE_INIT = 26: input is struct fuse_init_in, output is struct fuse_init_out;
  • FUSE_OPENDIR = 27: input is struct fuse_open_in, output is struct fuse_open_out;
  • FUSE_READDIR = 28: input is struct fuse_read_in, output is the byte sequence read (serialized as FUSE-specific dirents);
  • FUSE_RELEASEDIR = 29: input is struct fuse_release_in, output is empty;
  • FUSE_DESTROY = 38: input is empty; there is no output message.
For a read-only filesystem with some files and directories, it is enough to implement only the opcodes above. See more opcodes and their coressponding C structs in the table. The linked document contains more details about some of the message fields. The complete up-to-date opcodes and message structs can be found in fuse_kernel.h. Each reply output message (FUSE server → client) starts with this header:
  • uint32 size; size of the message in bytes, including the header;
  • int32 error; zero for successful completion, a negative errno value (such as -EIO or -ENOENT) on failure; upon failure, only the reply header is sent;
  • uint64 unique; unique identifier copied from the input message;

Please note that you have to write the whole reply at once (one write(2) call). Using any kind of buffered IO (such as stdio.h or C++ streams) can lead to problems, so don't do that.

Feel free to experiment: whatever junk you write as a reply, it won't make the kernel crash, but you'll get an EINVAL errno for the write(2) call.

Your FUSE server doesn't have to implement all possible operations (opcodes). By default, you can just return ENOSYS as errno for any operation (except for FUSE_INIT, FUSE_DESTROY and FUSE_FORGET) you don't want to implement.

Common errno values the FUSE server can return:

  • ENOSYS: The operation (opcode) is not implemented.
  • EIO: Generic I/O error, if other errno values are not appropriate.
  • EACCES: Permission denied.
  • EPERM: Operation not permitted. Most of the time you need EACCES instead.
  • ENOENT: No such file or directory.
  • ENOTDIR: Not a directoy. Return it if a directory operation was attempted on a nodeid which is not a directory.

The format of struct fuse_init_in used in FUSE_INIT:

  • uint32 init_major; the FUSE_KERNEL_VERSION in the kernel; must be exactly the same your code supports;
  • uint32 init_minor; the FUSE_KERNEL_MINOR_VERSION in the kernel; must be at least what your code supports;
  • uint32 init_readahead; ??;
  • uint32 init_flags; ??;

The format of struct fuse_init_out reply used in FUSE_INIT:

  • uint32 major; the same as FUSE_KERNEL_VERSION in the input;
  • uint32 minor; at most FUSE_KERNEL_MINOR_VERSION (init_minor) in the input, feel free to set it to less if you don't support the newest version;
  • uint32 max_readahead; ?? set it to 65536;
  • uint32 flags; ?? set it to 0;
  • uint32 unused; set it to 0;
  • uint32 max_write; ?? set it to 65536;
You have to implement FUSE_GETATTR to make the user able to do an ls -l (or stat(2)) on the mount point. It will be caled with nodeid FUSE_ROOT_ID (== 1) for the mount point.

The format of struct fuse_attr_out reply used in FUSE_GETATTR:

  • uint64 attr_value; number of seconds the kernel is allowed to cache the attributes returned, without issuing a FUSE_GETATTR call again; a zero value is OK; for non-networking filesystems you can set a very high value, since nobody else would change the attributes anyway;
  • uint32 attr_value_ns; number of nanoseconds to add to attr_value;
  • uint32 padding; to 64 bits;
  • struct fuse_attr attr; node attributes (permissions, owners etc.).

The format of struct fuse_attr reply used in FUSE_GETATTR and FUSE_LOOKUP:

  • uint64 ino; inode number copied to st_ino; can be any positive integer, the kernel doesn't depend on its uniqueness; it has no releation to nodeids used in FUSE (except for the name);
  • uint64 size; file size in bytes (or 0 for devices); make sure you set it correctly, because the kernel would truncate rads at this size even if your FUSE_READ returns more; be aware of the size being cached (using attr_value);
  • uint64 blocks; number of 512-byte blocks occupied on disk; you can safely set it to zero or any arbitrary value;
  • uint64 atime; the last access (read) time, in seconds since the Unix epoch;
  • uint64 mtime; the last content modification (write) time, in seconds since the Unix epoch;
  • uint64 ctime; the last attribute (inode) change time, in seconds since the Unix epoch;
  • uint32 atime_ns; nanoseconds part of atime;
  • uint32 mtime_ns; nanoseconds part of mtime;
  • uint32 ctime_ns; nanoseconds part of ctime;
  • uint32 more; file type and permissions; example file: S_IFREG | 0644; example directory: S_IFDIR | 0755;
  • uint32 nlink; total number of hard links; set it to 1 for both files and directories by default; for directories, you can speed up some listing operations (such as find(1)) by setting it to 2 + the number of subdirectories;
  • uint32 uid; user ID of the owner
  • uint32 gid; group ID of the owner
  • uint32 rdev; device major and minor number for device for character devices (mode & S_IFCHR) and block devices (mode & S_IFBLK).

Nodeid and generation number rules

In FUSE_LOOKUP you should return entry_nodeid and generation numbers. If I undestand correctly, the following rules hold:
  1. When a (nodeid, name) pair selected which you have never returned before, you can return any entry nodeid and generation number (except for those which are in use, see below). These two numbers uniquely identify the node for the kernel.
  2. When called again for the same (nodeid, name) pair, you must return the same entry_nodeid and generation numbers. (So you must remember what numbers you have returned previously).
  3. You should count the number of FUSE_LOOKUP requests on the same (nodeid, name). When you receive a FUSE_FORGET request for the specified entry nodeid, you must decrement the counter by the nlookups field of the FUSE_FORGET request. Once the counter is 0, you may safely forget about the entry nodeid (so it no longer considered to be in use), and next time you may return the same or a different nodeid at your choice for the same (nodeid, name) -- but with an increased generation number.
  4. You must never return the same nodeid with the same generation number again for a different inode, even after FUSE_FORGET dropped the reference counter to 0. That is: nodeids that have been released by the kernel may be recycled with a different generation number (but not with the same one!).

How to list the entries in a directory

TODO

How to read the contents of a file

TODO

Other TODO

This tutorial is work in progress. In the meantime, please see the sample Python source code for a FUSE server.

Open questions

  • How does nodeid and generation allocation and deallocation work?
  • How to run a multithreaded FUSE server?

2009-11-12

Buffered IO speed test of Google Go

This blog post presents the buffered IO speed test I've done with the standard implementation of the programming language Google Go, as compared to C on Linux amd64. The program I've run just copies everything from standard input to standard output, character by character using the default buffered IO. The conclusion: C (with the stdio in glibc) is about 2.68 times faster than Google Go (with its bufio).

$ cat cat.c
/* by pts@fazekas.hu at Thu Nov 12 12:04:49 CET 2009 */
#include 

int main(int argc, char **argv) {
  int c;
  while ((c = getchar()) >= 0 && putchar(c) >= 0) {}
  fflush(stdout);
  if (ferror(stdin)) {
    perror("error reading stdin");
    return 1;
  }
  if (ferror(stdout)) {
    perror("error reading stdin");
    return 1;
  }
  return 0;
}
$ cat cat.go
// by pts@fazekas.hu at Thu Nov 12 11:56:06 CET 2009
package main

import (
  "bufio";
  "os";
  "syscall";
  "unsafe";
)

func Isatty(f *os.File) bool {
  const TCGETS = 0x5401;  // Linux-specific
  var b [256]byte;
  _, _, e1 := syscall.Syscall(
      syscall.SYS_IOCTL, uintptr(f.Fd()), uintptr(TCGETS),
      uintptr(unsafe.Pointer(&b)));
  return e1 == 0;
}

func main() {
  r := bufio.NewReader(os.Stdin);
  w := bufio.NewWriter(os.Stdout);
  defer w.Flush();
  wisatty := Isatty(os.Stdout);
  for {
    c, err := r.ReadByte();
    if err == os.EOF { break }
    if err != nil { panic("error reading stdin: " + err.String()) }
    err = w.WriteByte(c);
    if err != nil { panic("error writing stdout: " + err.String()) }
    if (wisatty && c == '\n') {
      err = w.Flush();
      if err != nil { panic("error writing stdout: " + err.String()) }
    }
  }
}

Compilation and preparation:

$ gcc -s -O2 cat.c  # create a.out
$ ls -l a.out
-rwxr-xr-x 1 pts pts 4968 Nov 12 11:59 a.out
$ 6g cat.go && 6l cat.6  # create 6.out
$ ls -l 6.out
-rwxr-xr-x 1 pts pts 325257 Nov 12 11:55 6.out
$ dd if=/dev/urandom of=/tmp/data bs=1M count=256
$ time ./6.out /dev/null  # median of multiple runs
./6.out < /tmp/data > /dev/null  15.31s user 0.16s system 99% cpu 15.497 total
$ time ./6.out /dev/null  # median of multiple runs
./a.out < /tmp/data > /dev/null  5.92s user 0.20s system 99% cpu 6.153 total

Update: My naive implementation of zcat (gzip decompressor) in Google Go is only about 2.42 times slower than the C implementation (gcc -O3), and the C implementation is about 5.2 times slower than zcat(1).

2009-11-10

How to read a whole file to String in Java

Reading a whole file to a String in Java is tricky, one has to pay attention to many aspects:

  • Read with the proper character set (encoding).
  • Don't ignore the newline at the end of the file.
  • Don't waste CPU and memory by adding String objects in a loop (use a StringBuffer or an ArrayList<String> instead).
  • Don't waste memory (by line-buffering or double-buffering).

See my solution at http://stackoverflow.com/questions/1656797/how-to-read-a-file-into-string-in-java/1708115#1708115

For your convenience, here it is my code:

// charsetName can be null to use the default charset.    
public static String readFileAsString(String fileName, String charsetName)    
    throws java.io.IOException {    
  java.io.InputStream is = new java.io.FileInputStream(fileName);    
  try {    
    final int bufsize = 4096;    
    int available = is.available();    
    byte data[] = new byte[available < bufsize ? bufsize : available];    
    int used = 0;    
    while (true) {    
      if (data.length - used < bufsize) {    
        byte newData[] = new byte[data.length << 1];    
        System.arraycopy(data, 0, newData, 0, used);    
        data = newData;    
      }    
      int got = is.read(data, used, data.length - used);    
      if (got <= 0) break;    
      used += got;    
    }    
    return charsetName != null ? new String(data, 0, used, charsetName)    
                               : new String(data, 0, used);    
  } finally {
    is.close();  
  }
}