Auto-run with udev

If you've used Xandros Desktop 3, then you know that it automatically pops up a file manager window when you insert a USB mass storage device. This is a small thing, but it's quite a nice feature, and can make for a great user experience on a properly configured system. I have wondered for a while now how Xandros does this. This article explains how I finally got my Slackware system configured to work this way.

By way of backgound, my system is set up to use automount for my USB pen drives. This essentially allows me to connect a USB drive to my system, use it, and then disconnect it when I'm done. There is no need to explicitly mount or unmount the hardware at any point, as automount handles all this transparently based on whether or not the filesystem is being accessed.

An examination of my wife's Xandros system turned up the method to run a file manager on drive insert fairly quickly. By greping for the string "XandrosFileManager" in /etc, I discovered the file /etc/dev.d/block/mountusb.dev. A quick Google search and a look at the udev man page revealed that the dev.d directory contains programs to be executed after it creates device nodes.

The /etc/dev.d directory can contain multiple subdirectories, each of which is named after a particular device or subsystem, as well as a "default" directory. When udev creates or destroys a device node, it checks /etc/dev.d for directories named for the current device and for the subsystem of which it is a part. It then executes all the files in these directories (as well as the "default" directory) that have a .dev extension.

Udev communicates information about the current device to these programs through environment variables. These are documented in the udev man page. When used in conjunction with sysfs and the udevinfo command, these variables should get you all the information you need about this device.

The upshot of this is that you can get udev to run an arbitrary program when it does anything interesting. The program can access the device information through the environment, so your possibilities here are really only limited by your programming ability and imagination. For this article, we'll just get the system to open a graphical file manager to a specific folder.

If you don't use automount, you could make things more complicated and get udev to mount the drive and open the file manager on an "add" event, and then unmount the drive on a "remove" event. You could also use this to implement an "autorun" kind of scheme, or display a Windows XP-style action chooser based on the contents of the device. However, that's all beyond the scope of this article, so if you want to do and of that, you'll have to figure it out for yourself.

Below is the script I ended up with. You can download it and adapt it to your own needs, if you like. Since this is meant for use with all USB storage devices that work with automount, it should go in the /etc/dev.d/block directory, so that it will be run whenever a new block device id detected.

#!/bin/bash # Only run through this script if we're adding a device. If we're # removing one, then exit. if [ "$ACTION" != 'add' ] ; then exit fi export PATH=/bin:/sbin:/usr/bin:/usr/sbin LocalDisplay=':0' FileManager="/opt/kde/bin/konqueror" # Get the username of whoever is at the local X11 display. # If we don't find one, then exit. X11User=`who | grep $LocalDisplay | cut -f 1 -d ' '` if [ -z "$X11User" ] ; then exit fi # Get the name of the symlink that will be created # for this device. DevicePath=/dev/`udevinfo -q symlink -p /sys$DEVPATH` # This is because a udev event gets fired for # BOTH /dev/ubc and /dev/ubc1. Since we only # want the actual USB drive partition, we check # that there's a number in the device name. if [ -z `echo $DEVNAME | grep '[0-9]'` ] ; then exit fi # Loop through the /etc/auto.* files that hold the # directories where auto-mounted filesystems will be # mounted. for autofile in /etc/auto.* do # Look for a directory entry for our device symlink. FSDir=`grep "$DevicePath" $autofile | cut -f 1 -d ' '` # If we find one... if [ ! -z $FSDir ] ; then # ...then look for the base mount point in /etc/auto.master. BaseDir=`grep "$autofile" /etc/auto.master | cut -f 1 -d ' '` # If we find the base mount point, then we can build # the full mount path and launch the file manager. if [ ! -z "$BaseDir" ] ; then MountDir=$BaseDir/$FSDir/ export DISPLAY=$LocalDisplay if [ -e /home/$X11User/.bash_profile ] ; then source /home/$X11User/.bash_profile fi # We use nice to keep the system from eating a bunch # of cycles just opening the file manager. # Note that we set $HOME so the file manager can read its # configuration file from the right place. # Thanks to Aaron Griffin for pointing this out. export HOME=/home/$X11User nice -n 5 su $X11User -c "$FileManager $MountDir &> /dev/null" & exit fi fi done

While the comments in this script should be largely self-explanatory, there are a few things worth noting. For one, you should check the $ACTION variable to make sure that the device is being added. Udev fires events for both additions and removals, and you obviously don't want to open the file manager when the device no longer exists.

Another important point when dealing with block devices is that udev creates separate device files for the raw block device and its partitions. Therefore, you must pay attention to the exact device name when writing your scripts. Popping up a file manager for the main data partition on a USB drive makes perfect sense, but doing the same thing for the raw device really doesn't.

The last thing, and the one that was not obvious to me until I saw Xandros's script, is how to run a graphical program for a regular user from a system process. To do this, we take advantage of the fact that the who command includes the terminal or X display in the output. This allows us to pick out the username that owns the local display. We can then take advantage of the fact that root can su to any user without a password to run a command. We include the nice command because the system process is running at a higher priority than regular users, which can slow things down when starting the file manager. We also source the user's .bashrc file to make Konqueror open to the specified directory rather that the start page. I'm not sure why Konqueror does this or why including .bashrc helps, but it does. Other file managers might not have that problem.

And there we have it! The work is now completely gone from using a USB thumb drive. Now all I need to do is look into autorun to see if I can get something similar to work with my optical drives.