Plugging with Hotplug

As with everything in the Linux world, hotplug support is moving very quickly. Hotplug is no longer a seperate package and it's functionality has been combined with udev, the dynamic device file daemon. In fact, everything I documented below is now obsolete on my systems except for /etc/fstab! I am now running Debian Sid (a.k.a. Unstable) on all my machines with kernel 2.6.14 or later, the latest udev, dbus, and hal packages. I am also running KDE 3.5 and by right-clicking the desktop, selecting configure, then Behavior from the next screen, then the device icons tab and finally checked "Show device icons". With this configuration my camera and USB stick just "pop up" and I can mount them. I do have /mnt/sda and /mnt/sdb enabled in my /etc/fstab. This works very well.

The trend in recent years with PC hardware, especially laptops, is toward hardware that isn't permanently wired or installed into the system. Several hardware standards have addressed this issue over the years with perhaps the most popular being PCMCIA (People Can't Remember Computer Industry Acronymns) in laptops. A few years back the PC industry came up with USB (Universal Serial Bus) and Apple computer came up with a similar standard called Firewire (IEEE 1394). Work has continued at the same time on support of these systems on Linux. The PCMCIA Card Services package has traditionally handled configuring 16 bit PCMCIA cards, but does not support the later 32 bit Cardbus (PCI) cards. This brings a new package called Hotplug to the rescue.

Hotplug is now the system responsible for receiving notification from the Linux kernel when a Cardbus card or USB device is inserted or removed. It does this through a traditional dæmon that receives the event notification and then calls a shell script based on the hardware class.

This page is not exhaustive on Hotplug and probably gets some things wrong, but It Works For Me(TM), so YMMV, etc. What I will describe are the scripts I created to mount my camera and memory stick at a consistent directory in my filesystem. At some point I will grapple with completing the Hotplug support for my wireless card and document it here.

In Debian the Hotplug package loads the kernel module(s) for the device on insertion. Unfortunately, that is as far as it goes, so a little scripting is involved. Fortunately, this isn't hard. Also, since I am running a 2.4 series kernel a bit of brute force is required. A smoother method may be available through the Usb-mount package (Debian Package). I have not tried Usb-mount prefering instead to learn a bit more about Hotplug itself. Let's get started.

Editing the system files

/etc/fstab

I made the following changes to /etc/fstab:

none		/proc/bus/usb	usbfs	defaults			0	0
/dev/sda1	/mnt/sda	vfat	user,noauto,gid=100,umask=000	0	0
/dev/sdb1	/mnt/sdb	vfat	user,noauto,gid=100,umask=000	0	0

The first line mounts a place for the USB device files (not to be confused with files in /dev) and the last two are the mount points that either my camera or memory stick will be mounted. The fourth field sets the mount options. In the case of /dev/sd[a|b]1 the user option allows a user to mount the partition (not really needed, but is nice for debugging). Since my normal login account is a member of the users group, the gid=100 option makes everything on the partition a part of the users group so I can read/write files on the partition. The umask=000 sets the permission bits of the vfat (MS-DOS formatted) partition. noauto prevents mounting of the partition at system startup. If execution of programs are desired on the partition, the exec must be specified.

/etc/hotplug/usb/local.usermap

The key to Hotplug being able configure USB devices beyond loading the kernel modules is the devices being listed in one of the map files. While there are several other files where the devices are listed, I will focus on writing a custom local.usermap file which will be read by Hotplug's /etc/hotplug/usb.agent script. The format of local.usermap is explained at WLUG-Wiki - Hot Plug notes page.

Here is my /etc/hotplug/usb/local.usermap

# usb module	match_flags	idVendor	idProduct	bcdDevice_lo	bcdDevice_hi	bDeviceClass	bDeviceSubClass	bDeviceProtocol	bInterfaceClass	bInterfaceSubClass	bInterfaceProtocol	driver_info

# Lexar JumpDrive Sport
lexar		0x0003		0x05dc		0xa400		0x0		0x0		0x0		0x0		0x0		0x0		0x0			0x0			0x0

# Fuji A330 digital camera
fuji		0x0003		0x04cb		0x0148		0x0		0x0		0x0		0x0		0x0		0x0		0x0			0x0			0x0

# Lexar JumpDrive Pro
lexar		0x0003		0x05dc		0xa410		0x0		0x0		0x0		0x0		0x0		0x0		0x0			0x0			0x0

The first column is the script that will be run on a match of the idVendor and idProduct colums. The match_flags column tells which of the columns to match. The remaining columns are set to 0x0 and are ignored.

Now, leaving the idProduct field at 0x0 would likely allow a generic loading of the proper driver for similar products. I guess it boils down to how fine of control you desire.

To learn the Vendor and product IDs, the lsusb utility from the usbutils package will help you discover these values quickly. Here is the output with my memory stick inserted:

$ lsusb
Bus 001 Device 001: ID 0000:0000  
Bus 001 Device 002: ID 05dc:a400 Lexar Media, Inc. 

and with my camera inserted:

$ lsusb
Bus 001 Device 001: ID 0000:0000  
Bus 001 Device 003: ID 04cb:0148 Fuji Photo Film Co., Ltd 

As you can see, the Vendor and Product IDs are readily visible.

The scripts

To economize and to learn a bit more about shell scripting, I opted to use one script file for both of my devices with one script handling insertion and the other removal actions. I then used symbolic links with the names specified in the local.usermap pointing to the actual script. Here is the listing of my files:

$ ls -l /etc/hotplug/usb
lrwxrwxrwx  1 root root    23 2004-11-22 14:47 fuji -> /etc/hotplug/usb/insert*
lrwxrwxrwx  1 root root    23 2004-11-22 15:04 fuji.rmv -> /etc/hotplug/usb/remove*
-rwxr-xr-x  1 root root   999 2004-11-23 07:33 insert*
lrwxrwxrwx  1 root root    23 2004-11-22 14:47 lexar -> /etc/hotplug/usb/insert*
lrwxrwxrwx  1 root root    23 2004-11-22 15:05 lexar.rmv -> /etc/hotplug/usb/remove*
-rw-r--r--  1 root root   356 2004-11-23 07:34 local.usermap
-rwxr-xr-x  1 root root   402 2004-11-23 07:32 remove*

And now the insert script:

#!/bin/bash

# This script is called by ../usb.agent which sets the environment
# variables $ACTION, $DEVICE, and $REMOVER

# Since I only have two storage devices, only /dev/sda or /dev/sdb will
# be assigned.  If more devices are possible just extend the script to
# include them (sdc, sdd, etc)

# A symlink is created using the $REMOVER variable that the ../usb.agent
# script will execute on device removal.

PATH=/sbin:/bin:/usr/sbin:/usr/bin
BSNM=`basename $0`
GROUP=users

if [ "$ACTION" = "add" ] && [ -f "$DEVICE" ]
then
    # check if $GROUP really exists
    if getent group $GROUP > /dev/null; then
	chmod 664 "$DEVICE"
	chown root.$GROUP "$DEVICE"
    fi
fi

if mount /mnt/sda > /dev/null 2>&1
then
	MOUNTDIR="sda"
elif mount /mnt/sdb > /dev/null 2>&1
then
	MOUNTDIR="sdb"
else
	exit 128
fi

if [ "$BSNM" = "lexar" ]
then
	MNT="stick"
elif [ "$BSNM" = "fuji" ]
then
	MNT="cam"
else
	exit 127
fi

mount --bind /mnt/$MOUNTDIR /mnt/$MNT

ln -s /etc/hotplug/usb/$BSNM.rmv $REMOVER

exit 0

For the script to work, the mount points (directories) of /mnt/sda, /mnt/sdb, /mnt/cam, and /mnt/stick will need to be created manually. The $REMOVER variable points to a file dynamically created in /proc/bus/usb/001/ which corresponds the device number assigned upon insertion. If /etc/hotplug/usb.agent finds $REMOVER, it calls that script upon device removal.

Here is /etc/hotplug/usb/remove:

#!/bin/sh

# This file is linked to the $REMOVER variable set in ../usb.agent by
# the ./insert script.

# The mounts to be unmounted should match those in ./insert

PATH=/sbin:/bin:/usr/sbin:/usr/bin
LNK=`readlink $0`
FLNM=`basename $LNK`

if [ "$FLNM" = "lexar.rmv" ]
then
	MNT="stick"
elif [ "$FLNM" = "fuji.rmv" ]
then
	MNT="cam"
else
	exit 128
fi

umount /mnt/$MNT
umount /mnt/sda
umount /mnt/sdb

The last three lines show that I can unmount one of the named directories by either variable substitution that is deduced from the name used to call the script (the readlink command is the key) or brute force. Since I've not learned how to find out what kernel device name is assigned upon insertion, I just force a mount/umount on either sda/sdb. When I learn how to do this I will make the scripts more elegant.

Summary

These scripts reliably mount my two devices at predictable mount points. Using the --bind option to mount(8) in the insert script allows having a consistent named mount point even though the device can actually be at either sda or sdb. If I were to run the 2.6 kernel series on my laptop, a Thinkpad 390E, with the udev package, much of this kludging probably wouldn't be necessary. However, I have great hardware support from the 2.4 series and these scripts work for me. If I were to have a third USB Storage device I would probably have to add sdc to my scripts.


Take me back to the Index!

The Linux Webring:
[ Prev | Next | Random | List | Home | Stats ]

Original content Copyright © 1997-2017 Nate Bargmann NØNB n0nb@n0nb.us
any other content copyright by respective author(s).

This page last modified
January 20, 2006
Valid XHTML 1.0! Valid CSS! Built with WSMake!