284 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			284 lines
		
	
	
		
			7.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")
 | |
| if [ "$1" = "prepare" ] || [ "$1" = "cleanup" ]; then
 | |
|   . "$dir/xen-hotplug-common.sh"
 | |
|   command=$1
 | |
| else
 | |
|   . "$dir/block-common.sh"
 | |
| fi
 | |
| 
 | |
| shopt -s nullglob
 | |
| 
 | |
| if [ -n "$XENBUS_PATH" ]; then
 | |
|   HOTPLUG_STORE="/var/run/xen-hotplug/${XENBUS_PATH//\//-}"
 | |
| fi
 | |
| 
 | |
| 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_default "$XENBUS_PATH/params" 'MISSING')
 | |
|         if [ "$p" == "MISSING" ]; then
 | |
|           fatal "Missing device parameters ($t $XENBUS_PATH/params)"
 | |
|         fi
 | |
|         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
 | |
|         domain=$(xenstore_read_default "$XENBUS_PATH/domain" '')
 | |
|         if [ -z "$domain" ]; then
 | |
|           domid=$(xenstore_read "$XENBUS_PATH/frontend-id")
 | |
|           domain=$(xl domname $domid)
 | |
|         fi
 | |
|         echo $domain > "$HOTPLUG_STORE-domain"
 | |
| 
 | |
|         release_lock "block"
 | |
|         exit 0
 | |
|         ;;
 | |
|     esac
 | |
|     ;;
 | |
|   prepare)
 | |
|     t=$2
 | |
|     case $t in
 | |
|       snapshot|origin)
 | |
|         p=$3
 | |
|         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
 | |
|           echo "/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"
 | |
|           echo "/dev/mapper/$dm_devname"
 | |
|         fi
 | |
| 
 | |
|         release_lock "block"
 | |
|         exit 0
 | |
|         ;;
 | |
|     esac
 | |
|     ;;
 | |
|   remove|cleanup)
 | |
|     if [ "$command" = "cleanup" ]; then
 | |
|       t=$2
 | |
|     else
 | |
|       t=$(cat $HOTPLUG_STORE-type 2>/dev/null || echo 'MISSING')
 | |
|     fi
 | |
|     case "$t" in
 | |
|       snapshot|origin)
 | |
|         if [ "$command" = "cleanup" ]; then
 | |
|           node=$3
 | |
|         else
 | |
|           node=$(cat "$HOTPLUG_STORE-node" 2> /dev/null)
 | |
|         fi
 | |
| 
 | |
|         if [ -z "$node" ]; then
 | |
|           #fatal "No device node to remove"
 | |
|           #Most likely already removed
 | |
|           exit 0
 | |
|         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
 | |
|           if [ "$command" = "remove" ]; then
 | |
|             # Commit template changes
 | |
|             domain=$(cat "$HOTPLUG_STORE-domain")
 | |
|             if [ "$domain" ]; then
 | |
|               # Dont stop on errors
 | |
|               /usr/bin/qvm-template-commit "$domain" || true
 | |
|             fi
 | |
|           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 2> /dev/null || true
 | |
|           fi
 | |
|         done
 | |
| 
 | |
|         if [ -n "$HOTPLUG_STORE" ]; then
 | |
|           rm $HOTPLUG_STORE-*
 | |
|         fi
 | |
|         release_lock "block"
 | |
|         
 | |
|         exit 0
 | |
|         ;;
 | |
|     esac
 | |
|     ;;
 | |
| esac
 | |
| 
 | |
| # vim:sw=2:et:
 | 
