Using Udev to mount a backup disk

I use the wonderful Bacula to backup the systems that I run. This saves the backup volumes onto a couple of NAS devices.

The volume of data this produces does not really allow off-site backups to be performed over an ADSL line. Naturally this makes me a little nervous so I implement offsite backups by syncing the backup volumes onto USB hard-drives on a weekly basis and taking these to a remote storage location.

The USB drives contain an encrypted filesystem and prior to now I typically just mount the filesystem manually and use an rsync script to perform the copying.

This does become a bit of a pain because the servers are hidden away and I have to trot between server and desktop to mount/unmount the volumes.

I’m all for a bit of automation and wondered how difficult it would be to hook into udev to get it to automatically mount on hotplug and launch the copying.

Matching the device with udev rules

The first step is to write a rule that matches an individual drive hotplug properly.

Each drive has a serial number so it is a good idea to match on the make/serial number. The udev toolset provides the udevadm tool to help identify suitable attributes to match on.

richm@bishop:~$ udevadm info --name=/dev/sdd --attribute-walk

Udevadm info starts with the device specified by the devpath and then
walks up the chain of parent devices. It prints for every device
found, all possible attributes in the udev rules key format.
A rule to match, can be composed by the attributes of the device
and the attributes from one single parent device.

  looking at device '/devices/pci0000:00/0000:00:12.2/usb1/1-3/1-3:1.0/host14/target14:0:0/14:0:0:0/block/sdd':
    KERNEL=="sdd"
    SUBSYSTEM=="block"
    DRIVER==""
    ATTR{range}=="16"
    ATTR{ext_range}=="256"
    ATTR{removable}=="0"
    ATTR{ro}=="0"
    ATTR{size}=="3907028984"
    ATTR{alignment_offset}=="0"
    ATTR{discard_alignment}=="0"
    ATTR{capability}=="50"
    ATTR{stat}=="      15        0      120      100        0        0        0        0        0      100      100"
    ATTR{inflight}=="       0        0"
    ATTR{events}==""
    ATTR{events_async}==""
    ATTR{events_poll_msecs}=="-1"

... snip ...

  looking at parent device '/devices/pci0000:00/0000:00:12.2/usb1/1-3':
    KERNELS=="1-3"
    SUBSYSTEMS=="usb"
    DRIVERS=="usb"
    ATTRS{configuration}=="USB Mass Storage"
    ATTRS{bNumInterfaces}==" 1"
    ATTRS{bConfigurationValue}=="1"
    ATTRS{bmAttributes}=="c0"
    ATTRS{bMaxPower}=="  8mA"
    ATTRS{urbnum}=="141"
    ATTRS{idVendor}=="4971"
    ATTRS{idProduct}=="1015"
    ATTRS{bcdDevice}=="0000"
    ATTRS{bDeviceClass}=="00"
    ATTRS{bDeviceSubClass}=="00"
    ATTRS{bDeviceProtocol}=="00"
    ATTRS{bNumConfigurations}=="1"
    ATTRS{bMaxPacketSize0}=="64"
    ATTRS{speed}=="480"
    ATTRS{busnum}=="1"
    ATTRS{devnum}=="10"
    ATTRS{devpath}=="3"
    ATTRS{version}==" 2.10"
    ATTRS{maxchild}=="0"
    ATTRS{quirks}=="0x0"
    ATTRS{avoid_reset_quirk}=="0"
    ATTRS{authorized}=="1"
    ATTRS{manufacturer}=="HitachiGST"
    ATTRS{product}=="Touro Desk 3.0"
    ATTRS{serial}=="31001206110000003819"

... snip ...
  looking at parent device '/devices/pci0000:00':
    KERNELS=="pci0000:00"
    SUBSYSTEMS==""
    DRIVERS==""

This produces a large volume of output and gives a large amount to choose from.

In our case we want to match on the addition of the block device associated with the specific drive and then run a script to perform the actions that we desire.

richm@bishop:~$ cat /etc/udev/rules.d/local-disk.rules
ACTION=="add", SUBSYSTEM=="block", ATTRS{manufacturer}=="HitachiGST", ATTRS{serial}=="31001206110000003819", RUN+="/usr/local/bin/disk123-inserted"

Gotcha – do not block udev

The rules are executed sequentially by the udev subsystem so you have to be careful to ensure that nothing takes a long time and indeed that none of the scripts perform anything that would cause further udev rules to be executed.

In particular part of the crypto setup and subsequent mounting of the partition requires causes the system to wait for udev rules to be executed. This causes udev to deadlock and after a minute udev will forcibly kill the script without full completion.

Thankfully a simple way around this is to launch the commands in the background so that udev just carries on an performs the rest of its duties whilst the mount continues.

Further Reading

The Debian udev wiki page gives a good overview of how udev works and explains how to interpret the output of udevadm.

A more comprehensive set of information can be found in Daniel Drake’s Writing udev rules document.