in hardware, software

How to automatically run a script after inserting a USB device on Ubuntu?

Here’s the setup: you have a computer which has a SD port (small memory cards, mostly used in cameras, phones, etc.). You want to get all of your photos from that card automatically as soon as you insert your card (may be a USB key as well), that is, run a script which will search for all of your photos, and copy them to a local directory.

I know, some programs already do this, but they require Xwindow and some ugly Gnome/KDE app. What we’re doing here is a minimal setup, using the least memory.

Normally, on a normal Unix system you would use the included device detection mechanism, which is hotplug. But on Ubuntu (at least Gutsy and Hardy), the de-facto/required way is by using udev, which is an event based system, using rules to fire new events/mount/symlink or run programs according to some patterns defined as “rules”.

So, the first thing to do is to identify your device according to USB events. udevinfo is your friend. So, after looking at the output of dmesg, you’ll see your USB device is available on some sd* device.

So run:
udevinfo -a -p /sys/block/sda
(replace sda with you device)

You will get a list of “blocks” representing each layer of drivers. For example:

root@sleek:/etc/udev/rules.d# udevinfo -a -p /sys/block/sda/

looking at device '/block/sda':
KERNEL=="sda"
SUBSYSTEM=="block"
DRIVER==""
ATTR{stat}==" 229567 [...] 456048 3697068"
ATTR{size}=="488397168"
ATTR{removable}=="0"
ATTR{range}=="16"
ATTR{dev}=="8:0"

looking at parent device '/devices/pci0000:00/00[...]:
KERNELS=="2:0:0:0"
SUBSYSTEMS=="scsi"
DRIVERS=="sd"
ATTRS{ioerr_cnt}=="0x0"
ATTRS{iodone_cnt}=="0xcc26b"
ATTRS{iorequest_cnt}=="0xcc26c"
ATTRS{iocounterbits}=="32"
ATTRS{timeout}=="30"
ATTRS{state}=="running"
ATTRS{rev}=="3.AA"
ATTRS{model}=="ST3250310AS "
ATTRS{vendor}=="ATA "

These are in fact attributes that you can use in your rule to filter the devices. So for example,
to run a shell script when a SCSI device is detected which has a size of 1GB, I’ll use the following line:

KERNEL=="sd?1", ATTRS{size}=="1999872", RUN+="/usr/local/bin/recup_usb.sh"

This line is to be put in a new file in /etc/udev/rules.d, named something like 91-backuptousbdrivetrigger.rules. It’s better to put the file at the end of the rules (the “91”), as not to disturb other module loadings.

The content of the recup_usb.sh file can be:

#!/bin/bash

if [ "${ACTION}" = "add" ]; then
rm -rf /tmp/x
mkdir /tmp/x
mount -t vfat /dev/sdc1 /tmp/x
find /tmp/x -name "*jpg" -exec /usr/bin/rsync -avz {} \
/home/foobar/Pictures/Incoming/`date +%Y-%m-%d`/ \;
umount /tmp/x
fi

(yes, I know this code is dirty, I should have used mktemp, but you get the point…)
Don’t forget to chmod +x the shell script and reload the configuration with:

udevcontrol reload_rules

For more detailed information about writing udev rules, take a look at the official documentation, or another’s detailed information about doing the same thing.