#!/bin/sh dir=$(dirname "$0") . "$dir/block-common.sh" expand_dev() { local dev case $1 in /*) dev=$1 ;; *) dev=/dev/$1 ;; esac echo -n $dev } ## # canonicalise_mode mode # # Takes the given mode, which may be r, w, ro, rw, w!, or rw!, or variations # thereof, and canonicalises them to one of # # 'r': perform checks for a new read-only mount; # 'w': perform checks for a read-write mount; or # '!': perform no checks at all. # canonicalise_mode() { local mode="$1" if ! expr index "$mode" 'w' >/dev/null then echo 'r' elif ! expr index "$mode" '!' >/dev/null then echo 'w' else echo '!' 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; # '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 echo 'ok' } same_vm() { local otherdom="$1" # Note that othervm can be MISSING here, because Xend will be racing with # the hotplug scripts -- the entries in /local/domain can be removed by # Xend before the hotplug scripts have removed the entry in # /local/domain/0/backend/. In this case, we want to pretend that the # VM is the same as FRONTEND_UUID, because that way the 'sharing' will be # allowed. local othervm=$(xenstore_read_default "/local/domain/$otherdom/vm" \ "$FRONTEND_UUID") [ "$FRONTEND_UUID" == "$othervm" ] } ## # 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 [ "$mode" == '!' ] 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") fi case $t in phy) dev=$(expand_dev $p) FRONTEND_ID=$(xenstore_read "$XENBUS_PATH/frontend-id") FRONTEND_UUID=$(xenstore_read_default \ "/local/domain/$FRONTEND_ID/vm" 'unknown') 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 -a | grep ' \[0*'${dev}'\]:'${inode} | cut -d : -f 1) for dev in "$shared_list" do if [ -n "$dev" ] then check_file_sharing "$file" "$dev" "$mode" fi done fi loopdev=$(losetup -f) if [ "$loopdev" == '' ] then fatal 'Failed to find an unused loop device' fi cmode=`canonicalise_mode $mode` if [ "$cmode" == 'r' ] then do_or_die losetup -r "$loopdev" "$file" else do_or_die losetup "$loopdev" "$file" fi xenstore_write "$XENBUS_PATH/node" "$loopdev" write_dev "$loopdev" release_lock "block" exit 0 ;; "") claim_lock "block" success release_lock "block" ;; esac ;; remove) case $t in phy) exit 0 ;; file) claim_lock "block" node=$(xenstore_read "$XENBUS_PATH/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 /etc/xen/scripts/block-"$t" ] && \ /etc/xen/scripts/block-"$t" "$command" $node