mirror of
https://github.com/xen-project/xen.git
synced 2026-10-28 10:11:27 +01:00
During the attachment of a loopback mounted image file, the mode of all curent instances of this device already attached to other domains must be checked. This requires finding all loopback devices pointing to the inode of the shared image file, and then comparing the major and minor number of these devices to the major and minor number of every vbd device found in the xenstore database. Prior to this patch, the entire xenstore database is walked for every instance of every loopback device pointing to the same shared image file. This process causes the block attachment process to becomes exponentially slower with every additional attachment of a shared image. Rather than scanning all of xenstore for every instance of a shared loopback device, this patch creates a list of the major and minor numbers from all matching loopback devices. After generating this list, Xenstore is walked once, and major and minor numbers from every vbd are checked against the list. If a match is found, the mode of that vbd is checked for compatibility with the mode of the device being attached. Signed-off-by: Mike Latimer <mlatimer@suse.com> Acked-by: Ian Jackson <ian.jackson@eu.citrix.com>
373 lines
8.3 KiB
Bash
373 lines
8.3 KiB
Bash
#!/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
|
|
}
|
|
|
|
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 devtype device mode [inode]
|
|
#
|
|
# 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 $d': the device ($d) 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 $d': the device may not be used because it is already mounted
|
|
# through device $d by a guest in a way incompatible with the
|
|
# requested mode; or
|
|
# 'ok': the device may be used.
|
|
#
|
|
check_sharing()
|
|
{
|
|
local devtype=$1
|
|
local dev="$2"
|
|
local mode="$3"
|
|
local devmm=","
|
|
|
|
if [ "$devtype" = "file" ];
|
|
then
|
|
local inode="$4"
|
|
|
|
shared_list=$(losetup -a |
|
|
sed -n -e "s@^\([^:]\+\)\(:[[:blank:]]\[0*${dev}\]:${inode}[[:blank:]](.*)\)@\1@p" )
|
|
for dev in $shared_list
|
|
do
|
|
if [ -n "$dev" ]
|
|
then
|
|
devmm="${devmm}$(device_major_minor $dev),"
|
|
fi
|
|
done
|
|
# if $devmm is unchanged, file being checked is not a shared loopback device
|
|
if [ "$devmm" = "," ];
|
|
then
|
|
echo 'ok'
|
|
return
|
|
fi
|
|
else
|
|
devmm=${devmm}$(device_major_minor "$dev")","
|
|
fi
|
|
|
|
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")
|
|
|
|
# checking for $d in $devmm is best through the [[...]] bashism
|
|
if [[ "$devmm" == *",$d,"* ]]
|
|
then
|
|
echo "local $d"
|
|
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" "")
|
|
|
|
# checking for $d in $devmm is best through the [[...]] bashism
|
|
if [ -n "$d" ] && [[ "$devmm" == *",$d,"* ]]
|
|
then
|
|
if [ "$mode" = 'w' ]
|
|
then
|
|
if ! same_vm $dom
|
|
then
|
|
echo "guest $d"
|
|
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 $d"
|
|
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 type="device"
|
|
local result
|
|
|
|
if [ "x$mode" = 'x!' ]
|
|
then
|
|
return 0
|
|
fi
|
|
|
|
result=$(check_sharing "$type" "$dev" "$mode")
|
|
|
|
if [ "$result" != 'ok' ]
|
|
then
|
|
do_ebusy "Device $dev is mounted " "$mode" "${result%% *}"
|
|
fi
|
|
}
|
|
|
|
|
|
##
|
|
# check_device_sharing file dev mode inode
|
|
#
|
|
# Perform the sharing check for the given file, with its corresponding
|
|
# device, inode and mode. As the file can be mounted multiple times,
|
|
# the inode is passed through to check_sharing for all instances to be
|
|
# checked.
|
|
#
|
|
check_file_sharing()
|
|
{
|
|
local file="$1"
|
|
local dev="$2"
|
|
local mode="$3"
|
|
local inode="$4"
|
|
local type="file"
|
|
local result
|
|
|
|
result=$(check_sharing "$type" "$dev" "$mode" "$inode")
|
|
|
|
if [ "$result" != 'ok' ]
|
|
then
|
|
do_ebusy "File $file is loopback-mounted through ${result#* },
|
|
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')
|
|
p=$(xenstore_read "$XENBUS_PATH/params")
|
|
mode=$(xenstore_read "$XENBUS_PATH/mode")
|
|
if [ -b "$p" ]; then
|
|
truetype="phy"
|
|
elif [ -f "$p" ]; then
|
|
truetype="file"
|
|
fi
|
|
|
|
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
|
|
|
|
FRONTEND_ID=$(xenstore_read "$XENBUS_PATH/frontend-id")
|
|
FRONTEND_UUID=$(xenstore_read_default \
|
|
"/local/domain/$FRONTEND_ID/vm" 'unknown')
|
|
|
|
case $truetype 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
|
|
|
|
check_file_sharing "$file" "$dev" "$mode" "$inode"
|
|
fi
|
|
|
|
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"
|
|
xenstore_write "$XENBUS_PATH/node" "$loopdev"
|
|
write_dev "$loopdev"
|
|
release_lock "block"
|
|
exit 0
|
|
;;
|
|
|
|
"")
|
|
claim_lock "block"
|
|
success
|
|
release_lock "block"
|
|
;;
|
|
esac
|
|
;;
|
|
|
|
remove)
|
|
case $truetype 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 ${XEN_SCRIPT_DIR}/block-"$t" ] && \
|
|
${XEN_SCRIPT_DIR}/block-"$t" "$command"
|