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