ae52e1829a
Normally should not happen because all domains needs at least one snapshot device, but in some rare situation can be helpful to cleanup stale devices.
216 lines
5.7 KiB
Bash
Executable File
216 lines
5.7 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# Usage: block-snapshot add|remove img-file cow-file
|
|
#
|
|
# This creates dm-snapshot device on given arguments
|
|
|
|
dir=$(dirname "$0")
|
|
. "$dir/block-common.sh"
|
|
|
|
shopt -s nullglob
|
|
|
|
HOTPLUG_STORE="/var/run/xen-hotplug/${XENBUS_PATH//\//-}"
|
|
|
|
get_dev() {
|
|
dev=$1
|
|
|
|
if [ -L "$dev" ]; then
|
|
dev=$(readlink -f "$dev") || fatal "$dev link does not exist."
|
|
fi
|
|
|
|
if [ -f "$dev" ]; then
|
|
file=$dev
|
|
|
|
inode=$(stat -c '%i' "$file")
|
|
devnum=$(stat -c '%D' "$file")
|
|
if [ -z "$inode" ] || [ -z "$devnum" ]
|
|
then
|
|
release_lock "block"
|
|
fatal "Unable to lookup $file: dev: $devnum inode: $inode"
|
|
fi
|
|
|
|
dev_list=$(losetup -a | grep ' \[0*'${devnum}'\]:'${inode} | cut -d : -f 1)
|
|
for loopdev in $dev_list; do
|
|
# found existing loop to this file
|
|
echo $loopdev
|
|
return
|
|
done
|
|
|
|
|
|
# assign new loop device
|
|
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
|
|
|
|
do_or_die losetup "$loopdev" "$file"
|
|
echo $loopdev
|
|
else
|
|
test -e "$dev" || fatal "$dev does not exist."
|
|
test -b "$dev" || fatal "$dev is not a block device nor file."
|
|
fi
|
|
}
|
|
|
|
get_dm_snapshot_name() {
|
|
base=$1
|
|
cow=$2
|
|
|
|
echo snapshot-$(stat -c '%D:%i' "$base")-$(stat -c '%D:%i' "$cow")
|
|
}
|
|
|
|
create_dm_snapshot() {
|
|
local base_dev cow_dev base_sz
|
|
|
|
dm_devname=$1
|
|
base=$2
|
|
cow=$3
|
|
|
|
if [ ! -e /dev/mapper/$dm_devname ]; then
|
|
# prepare new snapshot device
|
|
base_dev=$(get_dev $base)
|
|
cow_dev=$(get_dev $cow)
|
|
base_sz=$(blockdev --getsz $base_dev)
|
|
do_or_die dmsetup create $dm_devname --table "0 $base_sz snapshot $base_dev $cow_dev P 256"
|
|
fi
|
|
|
|
}
|
|
|
|
create_dm_snapshot_origin() {
|
|
local base_dev base_sz
|
|
|
|
dm_devname=$1
|
|
base=$2
|
|
|
|
if [ ! -e /dev/mapper/$dm_devname ]; then
|
|
# prepare new snapshot-origin device
|
|
base_dev=$(get_dev $base)
|
|
base_sz=$(blockdev --getsz $base_dev)
|
|
do_or_die dmsetup create $dm_devname --table "0 $base_sz snapshot-origin $base_dev"
|
|
fi
|
|
}
|
|
|
|
t=$(xenstore_read_default "$XENBUS_PATH/type" 'MISSING')
|
|
|
|
case "$command" in
|
|
add)
|
|
case $t in
|
|
snapshot|origin)
|
|
p=$(xenstore_read "$XENBUS_PATH/params")
|
|
base=${p/:*/}
|
|
cow=${p/*:/}
|
|
|
|
if [ -L "$base" ]; then
|
|
base=$(readlink -f "$base") || fatal "$base link does not exist."
|
|
fi
|
|
|
|
if [ -L "$cow" ]; then
|
|
cow=$(readlink -f "$cow") || fatal "$cow link does not exist."
|
|
fi
|
|
|
|
# first ensure that snapshot device exists (to write somewhere changes from snapshot-origin)
|
|
dm_devname=$(get_dm_snapshot_name "$base" "$cow")
|
|
|
|
claim_lock "block"
|
|
|
|
# prepare snapshot device
|
|
create_dm_snapshot $dm_devname "$base" "$cow"
|
|
|
|
if [ "$t" == "snapshot" ]; then
|
|
#that's all for snapshot, store name of prepared device
|
|
xenstore_write "$XENBUS_PATH/node" "/dev/mapper/$dm_devname"
|
|
echo "/dev/mapper/$dm_devname" > "$HOTPLUG_STORE-node"
|
|
write_dev /dev/mapper/$dm_devname
|
|
elif [ "$t" == "origin" ]; then
|
|
# for origin - prepare snapshot-origin device and store its name
|
|
dm_devname=origin-$(stat -c '%D:%i' "$base")
|
|
create_dm_snapshot_origin $dm_devname "$base"
|
|
xenstore_write "$XENBUS_PATH/node" "/dev/mapper/$dm_devname"
|
|
echo "/dev/mapper/$dm_devname" > "$HOTPLUG_STORE-node"
|
|
write_dev /dev/mapper/$dm_devname
|
|
fi
|
|
# Save domain name for template commit on device remove
|
|
domid=$(xenstore_read "$XENBUS_PATH/frontend-id")
|
|
domain=$(xl domname $domid)
|
|
echo $domain > "$HOTPLUG_STORE-domain"
|
|
|
|
release_lock "block"
|
|
exit 0
|
|
;;
|
|
esac
|
|
;;
|
|
remove)
|
|
case $t in
|
|
snapshot|origin)
|
|
node=$(cat "$HOTPLUG_STORE-node")
|
|
|
|
if [ -z "$node" ]; then
|
|
fatal "No device node to remove"
|
|
fi
|
|
|
|
if [ ! -e "$node" ]; then
|
|
fatal "Device $node does not exists"
|
|
fi
|
|
|
|
claim_lock "block"
|
|
|
|
use_count=$(dmsetup info $node|grep Open|awk '{print $3}')
|
|
|
|
# do not remove snapshot if snapshot origin is still present
|
|
if [ "${node/snapshot/}" != "$node" -a -e "/dev/mapper/origin-$(echo $node|cut -d- -f2)" ]; then
|
|
use_count=1
|
|
fi
|
|
|
|
if [ "$use_count" -gt 0 ]; then
|
|
log info "Device $node still in use - not removing"
|
|
release_lock "block"
|
|
exit 0
|
|
fi
|
|
|
|
# get list of used (loop) devices
|
|
deps="$(dmsetup deps $node | cut -d: -f2 | sed -e 's#(7, \([0-9]\+\))#/dev/loop\1#g')"
|
|
|
|
# if this is origin
|
|
if [ "${node/origin/}" != "$node" ]; then
|
|
# remove unused snapshots
|
|
for snap in /dev/mapper/snapshot-$(echo $node|cut -d- -f2)-*; do
|
|
use_count=$(dmsetup info $snap|grep Open|awk '{print $3}')
|
|
if [ "$use_count" -eq 0 ]; then
|
|
# unused snapshot - remove it
|
|
deps="$deps $(dmsetup deps $snap | cut -d: -f2 | sed -e 's#(7, \([0-9]\+\))#/dev/loop\1#g')"
|
|
log debug "Removing $snap"
|
|
dmsetup remove $snap
|
|
fi
|
|
done
|
|
# Commit template changes
|
|
domain=$(cat "$HOTPLUG_STORE-domain")
|
|
if [ "$domain" ]; then
|
|
# Dont stop on errors
|
|
/usr/bin/qvm-template-commit "$domain" || true
|
|
fi
|
|
fi
|
|
|
|
if [ -e $node ]; then
|
|
log debug "Removing $node"
|
|
dmsetup remove $node
|
|
fi
|
|
|
|
# try to free loop devices
|
|
for dev in $deps; do
|
|
if [ -b "$dev" ]; then
|
|
log debug "Removing $dev"
|
|
losetup -d $dev || true 2> /dev/null
|
|
fi
|
|
done
|
|
|
|
release_lock "block"
|
|
|
|
exit 0
|
|
;;
|
|
esac
|
|
;;
|
|
esac
|
|
|
|
# vim:sw=2:et:
|