1#!/bin/bash -e 2# 3# tapdisk Xen block device hotplug script 4# 5# Author George Dunlap <george.dunlap@eu.citrix.com> 6# 7# Based on block-iscsi by Roger Pau Monné <roger.pau@citrix.com> 8# 9# This program is free software; you can redistribute it and/or modify 10# it under the terms of the GNU Lesser General Public License as published 11# by the Free Software Foundation; version 2.1 only. with the special 12# exception on linking described in file LICENSE. 13# 14# This program is distributed in the hope that it will be useful, 15# but WITHOUT ANY WARRANTY; without even the implied warranty of 16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17# GNU Lesser General Public License for more details. 18# 19# Usage: 20# 21# Disks should be specified using the following syntax: 22# 23# For use with tapback (vbd3) (preferred): 24# vdev=xvda,backendtype=tap,format=vhd,target=/srv/target.vhd 25# 26# For use with blkback and the blktap2 kernel module: 27# script=block-tap,vdev=xvda,target=<type>:<file> 28# 29# format/<type> is either "aio" (for raw files), or "vhd" 30 31dir=$(dirname "$0") 32. "$dir/block-common.sh" 33 34remove_label() 35{ 36 echo $1 | sed "s/^\("$2"\)//" 37} 38 39check_tools() 40{ 41 if ! command -v tap-ctl > /dev/null 2>&1; then 42 fatal "Unable to find tap-ctl tool" 43 fi 44} 45 46# Sets the following global variables based on the params field passed in as 47# a parameter: type file 48parse_target() 49{ 50 params=($(echo "$1" | tr ":" "\n")) 51 52 type=${params[0]} 53 file=${params[1]} 54 if [ -z "$type" ] || [ -z "$file" ]; then 55 fatal "Cannot parse required parameters" 56 fi 57} 58 59# Sets $pid and $minor to point to the device associated with the target 60find_device() 61{ 62 local info 63 local param 64 65 if [ -z "$type" ] || [ -z "$file" ]; then 66 fatal "required parameters not set" 67 fi 68 69 info=$(tap-ctl list -t $type -f $file) 70 71 for param in $(echo "$info" | tr "," "\n") 72 do 73 case $param in 74 pid=*) 75 pid=$(remove_label $param "pid=") 76 ;; 77 minor=*) 78 minor=$(remove_label $param "minor=") 79 ;; 80 esac 81 done 82 83 if [ -z "$pid" ] || [ -z "$minor" ]; then 84 return 1 85 fi 86 87 return 0 88} 89 90count_using() 91{ 92 local file="$1" 93 local dom 94 local dev 95 local f 96 97 local i=0 98 local base_path="$XENBUS_BASE_PATH/$XENBUS_TYPE" 99 for dom in $(xenstore-list "$base_path") 100 do 101 for dev in $(xenstore-list "$base_path/$dom") 102 do 103 f=$(xenstore_read_default "$base_path/$dom/$dev/params" "") 104 f=$(echo "$f" | cut -d ":" -f 2) 105 106 if [ -n "$f" ] && [ "$file" = $f ] ; then 107 i=$(( i + 1 )) 108 fi 109 done 110 done 111 112 echo "$i" 113} 114 115# tap_shared is used to determine if a shared tap can be closed 116# Since a stubdom and a guest both use the same tap, it can only 117# be freed when there is a single one left. 118tap_shared() { 119 [ $( count_using "$file" ) -gt 1 ] 120} 121 122check_tap_sharing() 123{ 124 local file="$1" 125 local mode="$2" 126 local dom 127 local dev 128 129 local base_path="$XENBUS_BASE_PATH/$XENBUS_TYPE" 130 for dom in $(xenstore-list "$base_path") ; do 131 for dev in $(xenstore-list "$base_path/$dom") ; do 132 local f=$(xenstore_read_default "$base_path/$dom/$dev/params" "") 133 f=$(echo "$f" | cut -d ":" -f 2) 134 135 if [ -n "$f" ] && [ "$file" = "$f" ] ; then 136 if [ "$mode" = 'w' ] ; then 137 if ! same_vm $dom ; then 138 echo "guest $f" 139 return 140 fi 141 else 142 local m=$(xenstore_read_default "$base_path/$dom/$dev/mode" 143 "") 144 m=$(canonicalise_mode "$m") 145 146 if [ "$m" = 'w' ] ; then 147 if ! same_vm $dom ; then 148 echo "guest $f" 149 return 150 fi 151 fi 152 fi 153 fi 154 done 155 done 156 157 echo 'ok' 158} 159 160tap_create() 161{ 162 if ! minor=$( tap-ctl allocate ) ; then 163 fatal "Could not allocate minor" 164 fi 165 166 # Handle with or without kernel blktap 167 minor=${minor#/run/blktap-control/tapdisk/tapdisk-} 168 minor=${minor#/dev/xen/blktap-2/tapdev} 169 170 # tap-ctl is spawning tapdisk which would hold the _lockfd open. 171 # Ensure it is closed before running tap-ctl spawn, which needs to be 172 # done in a subshell to continue holding the lock in the parent. 173 if ! pid=$( ( eval "exec $_lockfd>&-" ; tap-ctl spawn ) ) ; then 174 tap-ctl free -m "$minor" 175 fatal "Could not spawn tapdisk for $minor" 176 fi 177 178 if ! tap-ctl attach -p "$pid" -m "$minor" ; then 179 tap-ctl free -m "$minor" 180 fatal "Could not attach $pid and $minor" 181 fi 182 183 if ! tap-ctl open -p "$pid" -m "$minor" -a "$target" ; then 184 tap-ctl detach -p "$pid" -m "$minor" 185 tap-ctl free -m "$minor" 186 fatal "Could not open \"$target\"" 187 fi 188} 189 190# Attaches the device and writes xenstore backend entries to connect 191# the device 192add() 193{ 194 local result 195 196 claim_lock "block" 197 198 if find_device; then 199 result=$( check_tap_sharing "$file" "$mode" ) 200 if [ "$result" != "ok" ] ; then 201 do_ebusy "tap $type file $file in use " "$mode" "${result%% *}" 202 fi 203 else 204 tap_create 205 fi 206 207 xenstore_write "$XENBUS_PATH/pid" "$pid" 208 xenstore_write "$XENBUS_PATH/minor" "$minor" 209 210 if [ "$XENBUS_TYPE" = "vbd3" ] ; then 211 # Create nbd unix path. find_device/tap_create set pid & minor 212 dev=$( printf "/run/blktap-control/nbd%ld.%d" "$pid" "$minor" ) 213 214 # $dev, as a unix socket, has major:minor 0:0. If write_dev writes 215 # physical-device, tapback would use that incorrect minor 0. So don't 216 # write physical-device. 217 xenstore_write "$XENBUS_PATH/physical-device-path" "$dev" 218 219 success 220 else 221 # Construct dev path from minor 222 dev="/dev/xen/blktap-2/tapdev$minor" 223 [ -b "$dev" ] || fatal "blktap \"$dev\" is not a block dev" 224 write_dev "$dev" 225 fi 226 227 release_lock "block" 228} 229 230# Disconnects the device 231remove() 232{ 233 local minor 234 local pid 235 236 claim_lock "block" 237 238 if tap_shared ; then 239 return 240 fi 241 242 minor=$( xenstore_read "$XENBUS_PATH/minor" ) 243 pid=$( xenstore_read "$XENBUS_PATH/pid" ) 244 245 [ -n "$minor" ] || fatal "minor missing" 246 [ -n "$pid" ] || fatal "pid missing" 247 do_or_die tap-ctl destroy -p "$pid" -m "$minor" > /dev/null 248 249 release_lock "block" 250} 251 252command=$1 253target=$(xenstore-read $XENBUS_PATH/params || true) 254if [ -z "$target" ]; then 255 fatal "No information about the target" 256fi 257 258parse_target "$target" 259 260check_tools || exit 1 261 262mode=$( xenstore_read $XENBUS_PATH/mode ) 263mode=$( canonicalise_mode $mode ) 264 265# needed for same_vm 266FRONTEND_ID=$(xenstore_read "$XENBUS_PATH/frontend-id") 267FRONTEND_UUID=$(xenstore_read_default \ 268 "/local/domain/$FRONTEND_ID/vm" 'unknown') 269 270case $command in 271add) 272 add 273 ;; 274remove) 275 remove 276 ;; 277*) 278 exit 1 279 ;; 280esac 281