201 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			201 lines
		
	
	
		
			5.1 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"
 | |
| 
 | |
| 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"
 | |
|           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"
 | |
|           write_dev /dev/mapper/$dm_devname
 | |
|         fi
 | |
| 
 | |
|         release_lock "block"
 | |
|         exit 0
 | |
|         ;;
 | |
|     esac
 | |
|     ;;
 | |
|   remove)
 | |
|     case $t in
 | |
|       snapshot|origin)
 | |
|         node=$(xenstore_read "$XENBUS_PATH/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
 | |
|         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:
 | 
