In the past, I used a mechanism based on scripts called by udev rules [1,2] to automatically add USB drives to VMs. On a new machine this fails now with the following message:

libvirtd[832]: internal error: unable to execute QEMU command 'device_add': failed to find host usb device 3:3

It looks like the device is not ready yet when the script is called. Even adding a small delay using sleep does not help. Probably, the device does not settle until all udev rules have finished.

In order to disconnect the attach script from the udev rules, I found a way to use systemd services to automatically attach and detach the USB drive. For some devices, systemd automatically creates a .device device unit configuration. This service starts and stops automatically when a device appears and vanishes.  Hence, we can bind another service to this service that will execute our script. Unfortunately, for my USB drive, it creates a service file with the following name sys-devices-pci0000:00-0000:00:10.0-usb3-3\x2d1.device. This name changes every time we plug in the device. To make the naming reliable, I created a udev rule with a SYMLINK setting similar to this

SUBSYSTEM=="usb", \
    ENV{ID_VENDOR_ID}=="<usb vendor id>", \
    ENV{ID_MODEL_ID}=="<usb model id>", \
    SYMLINK+="<my USB drive name>", \
    TAG+="systemd", \
    ENV{SYSTEMD_WANTS}="my-usb-hotplug.service"

Afterwards, udev creates a device /dev/myChosenName and the last two lines cause systemd to create a dev-myChosenName.device unit and to start the my-usb-hotplug.service unit when the drive is plugged in. The my-usb-hotplug.service unit looks like this:

[Unit]
Description=my usb hotplug service
BindsTo=dev-myChosenName.device
After=dev-myChosenName.device

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/local/sbin/my-usb-hotplug.sh add <my vm name> <usb vendor id> <usb model id>
ExecStop=/usr/local/sbin/my-usb-hotplug.sh remove <my vm name> <usb vendor id> <usb model id>

[Install]
WantedBy=multi-user.target

With the BindsTo= and After= settings, we make sure that this service is also stopped when the device service is stopped. The my-usb-hotplug.sh file looks like this:

#! /bin/bash

ACTION=$1
VM_NAME=$2
ID_VENDOR_ID=$3
ID_MODEL_ID=$4

if [ "${ACTION}" == "add" ]; then
        CMD="attach-device"
else
        CMD="detach-device"
fi

virsh ${CMD} ${VM_NAME} /dev/stdin <<EOF
<hostdev mode='subsystem' type='usb'>
  <source>
   <vendor id="0x${ID_VENDOR_ID}" />
   <product id="0x${ID_MODEL_ID}" />
  </source>
</hostdev>
EOF

After all files are in place:

  • make the script executable: chmod +x /usr/local/sbin/my-usb-hotplug.sh
  • reload the udev rules with sudo udevadm control -R
  • reload systemd service units with sudo systemctl daemon-reload
  • enable the script service unit: sudo systemctl enable my-usb-hotplug.service

[1] https://github.com/olavmrk/usb-libvirt-hotplug

[2] http://rolandtapken.de/blog/2011-04/how-auto-hotplug-usb-devices-libvirt-vms-update-1