#!/bin/bash -e # # tapdisk Xen block device hotplug script # # Author George Dunlap # # Based on block-iscsi by Roger Pau Monné # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published # by the Free Software Foundation; version 2.1 only. with the special # exception on linking described in file LICENSE. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # Usage: # # Disks should be specified using the following syntax: # # For use with tapback (vbd3) (preferred): # vdev=xvda,backendtype=tap,format=vhd,target=/srv/target.vhd # # For use with blkback and the blktap2 kernel module: # script=block-tap,vdev=xvda,target=: # # format/ is either "aio" (for raw files), or "vhd" dir=$(dirname "$0") . "$dir/block-common.sh" remove_label() { echo $1 | sed "s/^\("$2"\)//" } check_tools() { if ! command -v tap-ctl > /dev/null 2>&1; then fatal "Unable to find tap-ctl tool" fi } # Sets the following global variables based on the params field passed in as # a parameter: type file parse_target() { params=($(echo "$1" | tr ":" "\n")) type=${params[0]} file=${params[1]} if [ -z "$type" ] || [ -z "$file" ]; then fatal "Cannot parse required parameters" fi } # Sets $pid and $minor to point to the device associated with the target find_device() { local info local param if [ -z "$type" ] || [ -z "$file" ]; then fatal "required parameters not set" fi info=$(tap-ctl list -t $type -f $file) for param in $(echo "$info" | tr "," "\n") do case $param in pid=*) pid=$(remove_label $param "pid=") ;; minor=*) minor=$(remove_label $param "minor=") ;; esac done if [ -z "$pid" ] || [ -z "$minor" ]; then return 1 fi return 0 } count_using() { local file="$1" local dom local dev local f local i=0 local base_path="$XENBUS_BASE_PATH/$XENBUS_TYPE" for dom in $(xenstore-list "$base_path") do for dev in $(xenstore-list "$base_path/$dom") do f=$(xenstore_read_default "$base_path/$dom/$dev/params" "") f=$(echo "$f" | cut -d ":" -f 2) if [ -n "$f" ] && [ "$file" = $f ] ; then i=$(( i + 1 )) fi done done echo "$i" } # tap_shared is used to determine if a shared tap can be closed # Since a stubdom and a guest both use the same tap, it can only # be freed when there is a single one left. tap_shared() { [ $( count_using "$file" ) -gt 1 ] } check_tap_sharing() { local file="$1" local mode="$2" local dom local dev local base_path="$XENBUS_BASE_PATH/$XENBUS_TYPE" for dom in $(xenstore-list "$base_path") ; do for dev in $(xenstore-list "$base_path/$dom") ; do local f=$(xenstore_read_default "$base_path/$dom/$dev/params" "") f=$(echo "$f" | cut -d ":" -f 2) if [ -n "$f" ] && [ "$file" = "$f" ] ; then if [ "$mode" = 'w' ] ; then if ! same_vm $dom ; then echo "guest $f" return fi else local m=$(xenstore_read_default "$base_path/$dom/$dev/mode" "") m=$(canonicalise_mode "$m") if [ "$m" = 'w' ] ; then if ! same_vm $dom ; then echo "guest $f" return fi fi fi fi done done echo 'ok' } tap_create() { if ! minor=$( tap-ctl allocate ) ; then fatal "Could not allocate minor" fi # Handle with or without kernel blktap minor=${minor#/run/blktap-control/tapdisk/tapdisk-} minor=${minor#/dev/xen/blktap-2/tapdev} # tap-ctl is spawning tapdisk which would hold the _lockfd open. # Ensure it is closed before running tap-ctl spawn, which needs to be # done in a subshell to continue holding the lock in the parent. if ! pid=$( ( eval "exec $_lockfd>&-" ; tap-ctl spawn ) ) ; then tap-ctl free -m "$minor" fatal "Could not spawn tapdisk for $minor" fi if ! tap-ctl attach -p "$pid" -m "$minor" ; then tap-ctl free -m "$minor" fatal "Could not attach $pid and $minor" fi if ! tap-ctl open -p "$pid" -m "$minor" -a "$target" ; then tap-ctl detach -p "$pid" -m "$minor" tap-ctl free -m "$minor" fatal "Could not open \"$target\"" fi } # Attaches the device and writes xenstore backend entries to connect # the device add() { local result claim_lock "block" if find_device; then result=$( check_tap_sharing "$file" "$mode" ) if [ "$result" != "ok" ] ; then do_ebusy "tap $type file $file in use " "$mode" "${result%% *}" fi else tap_create fi xenstore_write "$XENBUS_PATH/pid" "$pid" xenstore_write "$XENBUS_PATH/minor" "$minor" if [ "$XENBUS_TYPE" = "vbd3" ] ; then # Create nbd unix path. find_device/tap_create set pid & minor dev=$( printf "/run/blktap-control/nbd%ld.%d" "$pid" "$minor" ) # $dev, as a unix socket, has major:minor 0:0. If write_dev writes # physical-device, tapback would use that incorrect minor 0. So don't # write physical-device. xenstore_write "$XENBUS_PATH/physical-device-path" "$dev" success else # Construct dev path from minor dev="/dev/xen/blktap-2/tapdev$minor" [ -b "$dev" ] || fatal "blktap \"$dev\" is not a block dev" write_dev "$dev" fi release_lock "block" } # Disconnects the device remove() { local minor local pid claim_lock "block" if tap_shared ; then return fi minor=$( xenstore_read "$XENBUS_PATH/minor" ) pid=$( xenstore_read "$XENBUS_PATH/pid" ) [ -n "$minor" ] || fatal "minor missing" [ -n "$pid" ] || fatal "pid missing" do_or_die tap-ctl destroy -p "$pid" -m "$minor" > /dev/null release_lock "block" } command=$1 target=$(xenstore-read $XENBUS_PATH/params || true) if [ -z "$target" ]; then fatal "No information about the target" fi parse_target "$target" check_tools || exit 1 mode=$( xenstore_read $XENBUS_PATH/mode ) mode=$( canonicalise_mode $mode ) # needed for same_vm FRONTEND_ID=$(xenstore_read "$XENBUS_PATH/frontend-id") FRONTEND_UUID=$(xenstore_read_default \ "/local/domain/$FRONTEND_ID/vm" 'unknown') case $command in add) add ;; remove) remove ;; *) exit 1 ;; esac