#!/bin/bash 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; # '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 "$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' } 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 [ "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") 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." mode=$(canonicalise_mode "$mode") claim_lock "block" 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 loopdev='' for dev in /dev/loop* do if [ ! -b "$dev" ] then continue fi f=$(losetup "$dev" 2>/dev/null) || f='' if [ "$f" ] then # $dev is in use. Check sharing. if [ "x$mode" = 'x!' ] then continue fi f=$(echo "$f" | sed -e 's/.*(\(.*\)).*/\1/g') # $f is the filename, as read from losetup, but the loopback # driver truncates filenames at 64 characters, so we need to go # trawling through the store if it's longer than that. Truncation # is indicated by an asterisk at the end of the filename. if expr index "$f" '*' >/dev/null then found="" for dom in $(xenstore-list "$XENBUS_BASE_PATH") do for domdev in $(xenstore-list "$XENBUS_BASE_PATH/$dom") do d=$(xenstore_read_default \ "$XENBUS_BASE_PATH/$dom/$domdev/node" "") if [ "$d" = "$dev" ] then f=$(xenstore_read "$XENBUS_BASE_PATH/$dom/$domdev/params") found=1 break 2 fi done done if [ ! "$found" ] then # This loopback device is in use by someone else, so skip it. log debug "Loopback sharing check skips device $dev." continue fi fi # Canonicalise the filename for the comparison. # I have seen this readlink fails because the filename given by # losetup is only the basename. This cannot happen when the loop # device is set up through this script, because file is # canonicalised above, but it may happen when loop devices are set # up some other way. This readlink may also conceivably fail if # the file backing this loop device has been removed. # For maximum safety, in the case that $f does not resolve, we # assume that $file and $f are in the same directory. # If you create a loopback filesystem, remove it and continue to # run on it, and then create another file with the same name, then # this check will block that -- don't do that. # If you create loop devices through some other mechanism, use # relative filenames, and then use the same filename through this # script, then this check will block that -- don't do that either. f=$(readlink -f "$f" || echo $(dirname "$file")/$(basename "$f")) if [ "$f" = "$file" ] then check_file_sharing "$file" "$dev" "$mode" fi else # $dev is not in use, so we'll remember it for use later; we want # to finish the sharing check first. if [ "$loopdev" = '' ] then loopdev="$dev" fi fi done if [ "$loopdev" = '' ] then fatal 'Failed to find an unused loop device' fi do_or_die losetup "$loopdev" "$file" 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) node=$(xenstore_read "$XENBUS_PATH/node") losetup -d "$node" 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