#!/bin/bash # libvirt-usb-hotplug.bash # This script can be used to hotplug USB devices to libvirt virtual # machines from udev rules with systemd services. # References: # - https://github.com/olavmrk/usb-libvirt-hotplug # - http://kicherer.org/joomla/index.php/en/blog/48-automatic-hotplugging-of-usb-devices-for-libvirt-managed-vms # - https://blog.tjll.net/systemd-for-device-activation-and-media-archiving/ # Abort script execution on errors set -e readvars() { PROG="$(basename "$0")" ACTION=$1 IFS='_' read DOMAIN ID_VENDOR_ID ID_MODEL_ID <<< "$2" if [ -z "${DOMAIN}" ]; then echo "Missing libvirt domain parameter for ${PROG}." >&2 exit 1 fi } findmatch() { # Iterate through devlist to find match for i in "${devlist[@]}"; do # If we find a match, return successfully [ "${ID_VENDOR_ID}:${ID_MODEL_ID}" = "$i" ] && return 0 # If a match can't be found, keep looping continue done # If no match was found, return unsuccessfully return 1 } case ${ACTION} in add) readvars COMMAND='attach-device' ;; remove) readvars COMMAND='detach-device' ;; match) # This section is responsible for determining if the corresponding udev rule # matches against the device in question. Udev watches exit code to determine # if a match was made. # # Ref: udev(7) manpage # Read the config file source /etc/libvirt-usb-hotplug.conf # If we should match against any usb device, just exit successfully [ "$matchall" = "true" ] && exit 0 # If the devlist array is empty, no matches can be made [ ${#devlist[@]} = 0 ] && exit 1 # If targetdomain is unset or empty, exit unsuccessfully [ ! -v targetdomain ] && exit 1 [ "$targetdomain" = "" ] && exit 1 # Print the targetdomain so it can be picked back up by the udev rule printf "%s" "$targetdomain" case $listmode in whitelist) # findmatch() returns 0 if match found, and 1 if match not found findmatch exit $? ;; blacklist) # Reverse the exit status of findmatch() for blacklist mode ! findmatch exit $? ;; *) # If listmode is unset, or set improperly, exit unsuccessfully exit 1 ;; esac ;; *) echo "Invalid script ACTION: ${ACTION}" >&2 exit 1 ;; esac # Run the appropriate virsh-command, and ask it to read the update XML from stdin. echo "Running virsh ${COMMAND} ${DOMAIN} --persistent for USB vendor=0x${ID_VENDOR_ID} product=0x${ID_MODEL_ID}:" >&2 (virsh "${COMMAND}" "${DOMAIN}" /dev/stdin --persistent || true) < END