#!/bin/bash dir=$(dirname "$0") . "$dir/block-common.sh" HOTPLUG_STORE="/var/run/xen-hotplug/${XENBUS_PATH//\//-}" expand_dev() { local dev case $1 in /*) dev=$1 ;; *) dev=/dev/$1 ;; esac echo -n $dev } find_free_loopback_helper() { local next_devnum=0 local busy_devnum while read busy_devnum; do if [ "$next_devnum" != "$busy_devnum" ]; then break fi let next_devnum=$next_devnum+1 done echo "/dev/loop${next_devnum}" } # Not all distros have "losetup -f" find_free_loopback_dev() { local loopdev loopdev=$(losetup -a | sed -e 's+^/dev/loop++' -e 's/:.*//' | find_free_loopback_helper) if [ -n "$loopdev" ] && [ -b "$loopdev" ]; then echo "$loopdev" fi } ## # check_sharing device mode # # Check whether the device requested is already in use. To use the device in # read-only mode, it may be in use in read-only mode, but may not be in use in # read-write anywhere at all. To use the device in read-write mode, it must # not be in use anywhere at all. # # Prints one of # # 'local': the device may not be used because it is mounted in the current # (i.e. the privileged domain) in a way incompatible with the # requested mode; # 'guest': the device may not be used because it already mounted by a guest # in a way incompatible with the requested mode; or # 'ok': the device may be used. # check_sharing() { local dev="$1" local mode="$2" local devmm=$(device_major_minor "$dev") local file if [ "$mode" = 'w' ] then toskip="^$" else toskip="^[^ ]* [^ ]* [^ ]* ro[, ]" fi for file in $(cat /proc/mounts | grep -v "$toskip" | cut -f 1 -d ' ') do if [ -e "$file" ] then local d=$(device_major_minor "$file") if [ "$d" = "$devmm" ] then echo 'local' return fi fi done 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 d=$(xenstore_read_default "$base_path/$dom/$dev/physical-device" "") if [ "$d" = "$devmm" ] then if [ "$mode" = 'w' ] then if ! same_vm $dom then echo 'guest' 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' return fi fi fi fi done done echo 'ok' } ## # check_device_sharing dev mode # # Perform the sharing check for the given physical device and mode. # check_device_sharing() { local dev="$1" local mode=$(canonicalise_mode "$2") local result if [ "x$mode" = 'x!' ] then return 0 fi result=$(check_sharing "$dev" "$mode") if [ "$result" != 'ok' ] then do_ebusy "Device $dev is mounted " "$mode" "$result" fi } ## # check_device_sharing file dev mode # # Perform the sharing check for the given file mounted through the given # loopback interface, in the given mode. # check_file_sharing() { local file="$1" local dev="$2" local mode="$3" result=$(check_sharing "$dev" "$mode") if [ "$result" != 'ok' ] then do_ebusy "File $file is loopback-mounted through $dev, which is mounted " "$mode" "$result" fi } ## # do_ebusy prefix mode result # # Helper function for check_device_sharing check_file_sharing, calling ebusy # with an error message constructed from the given prefix, mode, and result # from a call to check_sharing. # do_ebusy() { local prefix="$1" local mode="$2" local result="$3" if [ "$result" = 'guest' ] then dom='a guest ' when='now' else dom='the privileged ' when='by a guest' fi if [ "$mode" = 'w' ] then m1='' m2='' else m1='read-write ' m2='read-only ' fi release_lock "block" ebusy \ "${prefix}${m1}in ${dom}domain, and so cannot be mounted ${m2}${when}." } t=$(xenstore_read_default "$XENBUS_PATH/type" 'MISSING') case "$command" in add) phys=$(xenstore_read_default "$XENBUS_PATH/physical-device" 'MISSING') if [ "$phys" != 'MISSING' ] then # Depending upon the hotplug configuration, it is possible for this # script to be called twice, so just bail. exit 0 fi if [ -n "$t" ] then p=$(xenstore_read "$XENBUS_PATH/params") mode=$(xenstore_read "$XENBUS_PATH/mode") echo $p > "$HOTPLUG_STORE-params" echo $mode > "$HOTPLUG_STORE-mode" echo $t > "$HOTPLUG_STORE-type" fi FRONTEND_ID=$(xenstore_read "$XENBUS_PATH/frontend-id") FRONTEND_UUID=$(xenstore_read_default \ "/local/domain/$FRONTEND_ID/vm" 'unknown') case $t in phy) dev=$(expand_dev $p) if [ -L "$dev" ] then dev=$(readlink -f "$dev") || fatal "$dev link does not exist." fi test -e "$dev" || fatal "$dev does not exist." test -b "$dev" || fatal "$dev is not a block device." claim_lock "block" check_device_sharing "$dev" "$mode" write_dev "$dev" release_lock "block" exit 0 ;; file) # Canonicalise the file, for sharing check comparison, and the mode # for ease of use here. file=$(readlink -f "$p") || fatal "$p does not exist." test -f "$file" || fatal "$file does not exist." mode=$(canonicalise_mode "$mode") claim_lock "block" # Avoid a race with the remove if the path has been deleted, or # otherwise changed from "InitWait" state e.g. due to a timeout xenbus_state=$(xenstore_read_default "$XENBUS_PATH/state" 'unknown') if [ "$xenbus_state" != '2' ] then release_lock "block" fatal "Path closed or removed during hotplug add: $XENBUS_PATH state: $xenbus_state" fi if [ "$mode" = 'w' ] && ! stat "$file" -c %A | grep -q w then release_lock "block" ebusy \ "File $file is read-only, and so I will not mount it read-write in a guest domain." fi if [ "x$mode" != 'x!' ] then inode=$(stat -c '%i' "$file") dev=$(stat -c '%D' "$file") if [ -z "$inode" ] || [ -z "$dev" ] then fatal "Unable to lookup $file: dev: $dev inode: $inode" fi shared_list=$(losetup -j "$file" | head -n 1 | cut -d : -f 1) for dev in "$shared_list" do if [ -n "$dev" ] then check_file_sharing "$file" "$dev" "$mode" loopdev="$dev" fi done fi if [ -z "$loopdev" ]; then loopdev=$(losetup -f 2>/dev/null || find_free_loopback_dev) if [ "$loopdev" = '' ] then release_lock "block" fatal 'Failed to find an unused loop device' fi if LANG=C losetup -h 2>&1 | grep read-only >/dev/null then roflag="-$mode"; roflag="${roflag#-w}"; roflag="${roflag#-!}" else roflag='' fi do_or_die losetup $roflag "$loopdev" "$file" fi xenstore_write "$XENBUS_PATH/node" "$loopdev" echo $loopdev > "$HOTPLUG_STORE-node" write_dev "$loopdev" release_lock "block" exit 0 ;; "") claim_lock "block" success release_lock "block" ;; esac ;; remove) t=$(cat $HOTPLUG_STORE-type) case $t in phy) exit 0 ;; file) claim_lock "block" node=$(cat "$HOTPLUG_STORE-node") losetup -d "$node" release_lock "block" exit 0 ;; "") exit 0 ;; esac ;; esac # If we've reached here, $t is neither phy nor file, so fire a helper script. [ -x ${XEN_SCRIPT_DIR}/block-"$t" ] && \ ${XEN_SCRIPT_DIR}/block-"$t" "$command" $node