No one wants to do backups, but everyone wants restore once something went wrong. The solution we look for is fully automated backups that trigger once you plug-in your backupdrive (or it appears $somehow).

So our objective is to detect the plugging-in of a USB-stick or harddisk and trigger a backupscript.

udev

The solution is: udev. udev is the solution in most Linux-Distributions responsible for handling hotplug-events.

Those events can be monitored via udevadm monitor. When then plugging in a device, we see events like

KERNEL[1108.237335] add      /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2 (usb)
KERNEL [1108.778873] add    /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/host6 (scsi)

for different subsystems (USB-subsystem and SCSI-subsystem in this case) which we can hook into and trigger arbitrary commands.

writing udev-rules

To identify a device, we need to take a look at its properties. For a harddisk this can be done by

udevadm info -a -p $(udevadm info -q path -n $devicename)

where $devicename can be sdb or /dev/sdb, for example. udevadm info -q path -n $devicename gives us the indentifier in the /sys-subsystem we already have seen in the output of udevadm monitor, udevadm info -a -p $devicepath uses this identifier to walk through the /sys-subsystem and gives us all the properties that are associated with the device or its parent nodes, for example my thumbdrive’s occurrence in the scsi-subsystem:

KERNELS=="6:0:0:0"
SUBSYSTEMS=="scsi"
DRIVERS=="sd"
ATTRS{device_blocked}=="0"
ATTRS{device_busy}=="0"
ATTRS{dh_state}=="detached"
ATTRS{eh_timeout}=="10"
ATTRS{evt_capacity_change_reported}=="0"
ATTRS{evt_inquiry_change_reported}=="0"
ATTRS{evt_lun_change_reported}=="0"
ATTRS{evt_media_change}=="0"
ATTRS{evt_mode_parameter_change_reported}=="0"
ATTRS{evt_soft_threshold_reached}=="0"
ATTRS{inquiry}==""
ATTRS{iocounterbits}=="32"
ATTRS{iodone_cnt}=="0x117"
ATTRS{ioerr_cnt}=="0x2"
ATTRS{iorequest_cnt}=="0x117"
ATTRS{max_sectors}=="240"
ATTRS{model}=="DataTraveler 2.0"
ATTRS{queue_depth}=="1"
ATTRS{queue_type}=="none"
ATTRS{rev}=="PMAP"
ATTRS{scsi_level}=="7"
ATTRS{state}=="running"
ATTRS{timeout}=="30"
ATTRS{type}=="0"
ATTRS{vendor}=="Kingston"

We can use those attributes to identify our device amongst all the devices that we have.

A udev-rule now contains two major component types:

  • matching statements that identify the event and device we want to match
  • action statements that take action on the matched device

which is of course a simplification, but it will suffice for the purpose of this blogpost and most standard applications.

To do so we first create a file /etc/udev/rules.d/myfirstudevrule.rules. The filename doesn’t matter as long as it ends in .rules as only those files will be read by udev.

In this udev-rule, we first need to match our device. To do so, I will pick three of the properties above that sound like they are sufficient to uniquely match my thumbdrive.

SUBSYSTEMS=="scsi", ACTION=="add" ATTRS{model}=="DataTraveler 2.0", ATTRS{vendor}=="Kingston"

I have added a statement matching the ACTION, as we of course only want to trigger a backup when the device appears. You can also match entire device classes by choosing the matcher-properties accordingly.

To trigger a command, we can add it to the list of commands RUN that shall run when the device is inserted, for example creating the file /tmp/itsalive:

RUN+="/usr/bin/touch /tmp/itsalive"

So our entire (rather lengthy) udev-rule in /etc/udev/rules.d/myfirstudevrule.rules reads

SUBSYSTEMS=="scsi", ACTION=="add" ATTRS{model}=="DataTraveler 2.0", ATTRS{vendor}=="Kingston", RUN+="/usr/bin/touch /tmp/itsalive"

and we can trigger arbitrary commands with it.

trigger long runnig jobs

However, commands in the RUN-list have a time constraint of 180 seconds to run. For a backup, this is likely to be be insufficient. So we need a way to create long-running jobs from udev.

The solution for this is to outsource the command into a systemd-unit. Besides being able to run for longer than 180s, that way we also get proper logging of our backup-command in the journal, which is always good to have.

So we create a file /etc/systemd/system/mybackup.service containing

[Unit]
Description=backup system

[Service]
User=backupuser  # optional, might be yourself, root, …
Type=simple
ExecStart=/path/to/backupscript.sh

We then need to modify the action part of our rule from appending to RUN to read

TAG+="systemd", ENV{SYSTEMD_WANTS}="mybackup.service"

Our entire udev-rule then reads

SUBSYSTEMS=="scsi", ACTION=="add" ATTRS{model}=="DataTraveler 2.0", ATTRS{vendor}=="Kingston", TAG+="systemd", ENV{SYSTEMD_WANTS}="mybackup.service"

improve usability

To further improve usability, we can additionally append.

SYMLINK+="backupdisk"

This way, an additional symlink /dev/backupdisk will be created upon the device appearing that can be used for scripting.

using a disk in a dockingstation

At home I have a dockingstation for my laptop and the disk I use for local backups is built into the bay of the dockingstation. From my computer’s point of view, this disk is connected over an internal port. When docking onto the station it is not recognized, as internal ports are not monitored for hotplugging. Upon docking, it is therefore necessary to rescan the internal scsi-bus the disk is connected to. This can be done issuing an entire rescan of the scsi-host host1 by

echo '- - -' > /sys/class/scsi_host/host1/scan

In my case the disk is connected to the port at scsi-host 1. You can find out the scsi-host of your disk by running

ls -l /sys/block/sdb

where sda is the device mapper of the block device whose scsi-host you are interested in. This returns a string like

../devices/pci0000:00/0000:00:1f.2/ata1/host1/target0:0:0/0:0:0:0/block/sda

where you can see that for this disk, the corresponding scsi-host is host1. This can then be used to issue the rescan of the correct scsi-bus. The according udev-rule in my case reads

SUBSYSTEM=="platform", ACTION=="change", ATTR{type}=="ata_bay", RUN+="/usr/bin/echo '- - -' > /sys/class/scsi_host/host1/scan"

Afterwards, the disk should show up and can be matched like any other disk as described above.