File vm-snapshot-disk of Package virt-utils
#!/bin/bash
#============================================================================
# vm-snapshot-disk
#
# Version = 0.6.0
# Date = 2011-07-01
#
# Copyright - Ron Terry
# License - GPL
#
# Maintainer(s) = Ron Terry - roncterry (at) gmail (dot) com
#
# The latest version can be found at:
#
# http://pronetworkconsulting.com/linux/scripts/virt-tools.html
#
# Description:
# This script creates and manages snapshots of qcow2 and vhd virtual disks
# and virtual disks on file systems that support snapshots (i.e. OCFS2)
#
# Individual snapshots and snapshot trees are supported. The names
# of the snapshots in the snapshot tree are stored in the actual
# filenames of the snapshots themselves. Descriptions of each
# snapshot can be provided and will be stored in a file named
# .$DISK.snap_descriptions
#
# The following operations can be performed:
#
# create -creates a new snapshot from the current position in the
# snapshot tree
# branch -creates a new branch of the snapshot tree from the
# specified snapshot
# revert -reverts to the specified snapshot and deletes all linked
# snapshots from the snapshot tree
# remove -removes all snapshosts and snapshot descriptions from
# the disk image
# list -list all snapshots and their descriptions
# help -displays the description and usage
#
#============================================================================
###################################################################################
# Read config files and set variables
###################################################################################
if which qemu-img > /dev/null 2>&1
then
QEMU_IMG_CMD=qemu-img
HAVE_QEMU_IMG=true
elif which qemu-img-xen > /dev/null 2>&1
then
QEMU_IMG_CMD=qemu-img-xen
HAVE_QEMU_IMG=true
else
HAVE_QEMU_IMG=false
fi
if which reflink > /dev/null 2>&1
then
REFLINK_CMD=`which reflink`
HAVE_REFLINK=true
else
HAVE_REFLINK=false
fi
if which vhd-util > /dev/null 2>&1
then
VHD_UTIL_CMD=`which vhd-util`
HAVE_VHD_UTIL=true
else
HAVE_VHD_UTIL=false
fi
OCFS2_VOLS=`cat /proc/mounts | grep ocfs2 | cut -d \ -f 2`
if `uname -r | grep -i xen`
then
HYPERVISOR=xen
else
HYPERVISOR=qemu
fi
###################################################################################
# Script Functions
###################################################################################
########## Function: description ###############################
description() {
echo "
================================================================================
Description:
This script creates and manages snapshots of qcow2 and vhd virtual disks
and virtual disks on file systems that support snapshots (i.e. OCFS2)
Individual snapshots and snapshot trees are supported. The names
of the snapshots in the snapshot tree are stored in the actual
filenames of the snapshots themselves. Descriptions of each
snapshot can be provided and will be stored in a file named
.\$DISK.snap_descriptions
The following operations can be performed:
create -creates a new snapshot from the current position in the
snapshot tree
branch -creates a new branch of the snapshot tree from the
specified snapshot
revert -reverts to the specified snapshot and deletes all linked
snapshots from the snapshot tree
remove -removes all snapshosts and snapshot descriptions from
the disk image
list -list all snapshots and their descriptions
help -displays the description and usage
================================================================================
"
# delete -deletes the specified snapshot and all linked snapshots
# from the snapshot tree
}
########## Function: usage ###########################################
usage() {
echo
echo "Usage: vm-snapshot-disk [create|branch|revert|remove|list|help] options"
echo
echo "Examples:"
echo " vm-snapshot-disk create disk=<DISK_NAME> [snapname=<SNAPSHOT_NAME> description=\"<SNAPSHOT_DESCRIPTION>\"]"
echo " vm-snapshot-disk branch disk=<DISK_NAME> snapname=<SNAPSHOT_NAME> [description=\"<SNAPSHOT_DESCRIPTION>\"]"
echo " vm-snapshot-disk revert disk=<DISK_NAME> snapname=<SNAPSHOT_NAME>"
echo " vm-snapshot-disk remove disk=<DISK_NAME>"
echo " vm-snapshot-disk list disk=<DISK_NAME>"
echo " vm-snapshot-disk help"
echo
}
########## Function: get_options #####################################
get_options() {
case "$1" in
#create|branch|revert|delete|help)
create|branch|revert|remove|list)
MODE="$1"
;;
help|-h|--help)
MODE=help
;;
*)
echo
echo "ERROR: You must provide a valid mode!"
usage
exit 1
;;
esac
if echo "$*" | grep -q "disk="
then
local DISK_IMAGE_FULL=`echo "$*" | grep -o "disk=.*" | cut -d '=' -f 2 | cut -d ' ' -f 1`
fi
if echo $* | grep -q "snapname="
then
SNAPSHOT_NAME=`echo $* | grep -o "snapname=.*" | cut -d '=' -f 2 | cut -d ' ' -f 1`
fi
if echo $* | grep -q "description="
then
#SNAPSHOT_DESCR=`echo $* | grep -o "description=.*" | cut -d '=' -f 2 | cut -d ' ' -f 1`
SNAPSHOT_DESCR=`echo $* | grep -o "description=.*" | cut -d '=' -f 2`
fi
if [ -z "$DISK_IMAGE_FULL" ]
then
case "$MODE" in
help|-h)
MODE=help
description
usage
exit 99
;;
*)
echo
echo "ERROR: You must supply a disk image!"
usage
exit 1
esac
else
DISK_IMAGE=`get_file_name $DISK_IMAGE_FULL`
DISK_PATH=`get_file_path $DISK_IMAGE_FULL`
fi
}
########## Function: get_file_path ######################################
get_file_path() {
if [ -z $1 ]
then
echo "Usage: get_file_path <absolute|relative_path_to_file>"
return 1
else
local FILE="$1"
fi
local DIR_COUNT=`echo $FILE|grep -o "/"|wc -l`
((DIR_COUNT++))
local FILE_NAME=`echo $FILE|cut -d "/" -f $DIR_COUNT`
local FILE_DIR=`echo $FILE|sed "s/$FILE_NAME$//g"`
echo $FILE_DIR
}
########## Function: get_file_name ######################################
get_file_name() {
if [ -z $1 ]
then
echo "Usage: get_file_name <absolute|relative_path_to_file>"
return 1
else
local FILE="$1"
fi
local DIR_COUNT=`echo $FILE|grep -o "/"|wc -l`
((DIR_COUNT++))
local FILE_NAME=`echo $FILE|cut -d "/" -f $DIR_COUNT`
local FILE_DIR=`echo $FILE|sed "s/$FILE_NAME$//g"`
echo $FILE_NAME
}
########## Function: test_disk ###########################################
test_disk() {
local DISK="$1"
if ! [ -e "$DISK" ]
then
echo
echo "ERROR: The specified disk \"$DISK\" does not exist!"
echo
exit 2
fi
ON_OCFS2=false
for VOL in ${OCFS2_VOLS}
do
while [ "${ON_OCFS2}" == false ]
do
if echo "${DISK}" | grep -q "${VOL}"
then
ON_OCFS2=true
fi
done
done
if ($QEMU_IMG_CMD info $DISK | grep "file format" | cut -d ' ' -f 3 | grep -q qcow2) 2> /dev/null
then
IMG_TYPE=qcow2
elif ($QEMU_IMG_CMD info $DISK | grep "file format" | cut -d ' ' -f 3 | grep -q raw) 2> /dev/null
then
IMG_TYPE=raw
elif ($QEMU_IMG_CMD info $DISK | grep "file format" | cut -d ' ' -f 3 | grep -q vmdk) 2> /dev/null
then
IMG_TYPE=vmdk
elif ($QEMU_IMG_CMD info $DISK | grep "file format" | cut -d ' ' -f 3 | grep -q vpc) 2> /dev/null
then
IMG_TYPE=vhd
else
echo
echo "ERROR: The specified disk \"$DISK\" does not appear to be of a supported type"
echo " You cannot snpashot this disk!"
exit 3
fi
}
########## Function: get_backing_disk #####################################
get_backing_disk() {
case ${IMG_TYPE} in
qcow2)
"$QEMU_IMG_CMD" info "$1" | grep "backing file" | cut -d : -f 3 | sed 's/^ *//g' | sed 's/)//g'
;;
vhd)
"$VHD_UTIL_CMD" scan "$1" | cut -d \ -f 5 | cut -d \= -f 2
;;
*)
echo "none"
;;
esac
}
########## Function: find_unused_snap_name ################################
find_unused_snap_name() {
local DISK="$1"
local SNAPNAME="$2"
local SNAP_DESCR="$3"
if [ -z "$SNAPNAME" ]
then
SNAMECOUNT=1
SNAPNAME=snap"$SNAMECOUNT"
if ls "$DISK".* > /dev/null 2>&1
then
#echo "Finding and unused snap name"
until ! ls "$DISK".* | grep -q snap"$SNAMECOUNT"
do
((SNAMECOUNT++))
SNAPNAME=snap"$SNAMECOUNT"
done
fi
fi
echo "$SNAPNAME"
}
########## Function: create_first_snapshot ################################
create_first_snapshot() {
local DISK="$1"
local SNAPNAME="$2"
local SNAP_DESCR="$3"
local BDISK="$DISK".base
start_first_snap_process() {
SNAPNAME=`find_unused_snap_name "$DISK" "$SNAPNAME" "$SNAP_DESCR"`
echo " Creating new snapshot: $BDISK.$SNAPNAME"
echo "*****************************************************************"
}
rename_orig_disk() {
mv "$DISK" "$BDISK"
}
link_orig_disk() {
ln "$BDISK" "$BDISK"."$SNAPNAME"
chmod a-w "$BDISK"
chmod a-w "$BDISK"."$SNAPNAME"
}
do_qemu_image_first_snap() {
"$QEMU_IMG_CMD" create -f qcow2 -b "$BDISK" "$BDISK"."$SNAPNAME"._working
}
do_reflink_first_snap() {
"${REFLINK_CMD}" "${BDISK}" "${BDISK}"."${SNAPNAME}"._working
}
do_vhd_util_first_snap() {
"$VHD_UTIL_CMD" snapshot -p "$BDISK" -n "$BDISK"."$SNAPNAME"._working
}
symlink_new_disk() {
ln -s "$BDISK"."$SNAPNAME"._working "$DISK"
}
add_snapshot_description() {
echo `echo "$BDISK.$SNAPNAME" | sed 's/\./_/g' | sed 's/__working//g'`"='$SNAP_DESCR'" >> ."$DISK".snap_descriptions
}
# case ${HYPERVISOR} in
# qemu)
case ${IMG_TYPE} in
qcow2)
start_first_snap_process
rename_orig_disk
link_orig_disk
do_qemu_image_first_snap
symlink_new_disk
add_snapshot_description
;;
vhd)
start_first_snap_process
rename_orig_disk
link_orig_disk
do_vhd_util_first_snap
symlink_new_disk
add_snapshot_description
;;
*)
case ${ON_OCFS2} in
false)
echo "ERROR: Disk is not of type qcow2 or vhd and not on a file system the supports snapshots!"
echo
echo " Will not create a snapshot"
exit 6
;;
true)
start_first_snap_process
rename_orig_disk
link_orig_disk
do_reflink_first_snap
symlink_new_disk
add_snapshot_description
;;
esac
;;
esac
# ;;
# xen)
# start_first_snap_process
# rename_orig_disk
# link_orig_disk
# do_reflink_first_snap
# symlink_new_disk
# add_snapshot_description
# ;;
# esac
}
########## Function: create_additional_snapshot ###########################
create_additional_snapshot() {
local DISK="$1"
local SNAPNAME="$2"
local SNAP_DESCR="$3"
local BDISK=`ls -l "$DISK" |cut -d ">" -f 2 | sed 's/^ *//g'`
start_additional_snap_process() {
SNAPNAME=`find_unused_snap_name "$DISK" "$SNAPNAME" "$SNAP_DESCR"`
BASE_BDISK="`echo "$BDISK" | sed 's/\._working//g'`"
echo " Creating new snapshot: $BASE_BDISK.$SNAPNAME"
echo "*****************************************************************"
}
do_qemu_image_additional_snap() {
"$QEMU_IMG_CMD" create -f qcow2 -b "$BASE_BDISK"."$SNAPNAME" "$BASE_BDISK"."$SNAPNAME"._working
}
do_vhd_util_additional_snap() {
"$VHD_UTIL_CMD" snapshot -p "$BASE_BDISK"."$SNAPNAME" -n "$BASE_BDISK"."$SNAPNAME"._working
}
do_reflink_additional_snap() {
"${REFLINK_CMD}" "${BASE_DISK}"."${SNAPNAME}" "${BASE_DISK}"."${SNAPNAME}"._working
}
move_working_to_snap() {
rm -f "$DISK"
mv "$BDISK" "$BASE_BDISK"."$SNAPNAME"
chmod a-w "$BASE_BDISK"."$SNAPNAME"
}
symlink_new_working_snap() {
ln -s "$BASE_BDISK"."$SNAPNAME"._working "$DISK"
}
add_additional_snapshot_description() {
echo `echo "$BASE_DISK.$SNAPNAME" | sed 's/\./_/g' | sed 's/__working//g'`"='$SNAP_DESCR'" >> ."$DISK".snap_descriptions
}
# case ${HYPERVISOR} in
# qemu)
case ${IMG_TYPE} in
qcow2)
start_additional_snap_process
move_working_to_snap
do_qemu_image_additional_snap
symlink_new_working_snap
add_additional_snapshot_description
;;
vhd)
start_additional_snap_process
move_working_to_snap
do_vhd_util_additional_snap
symlink_new_working_snap
add_additional_snapshot_description
;;
*)
case ${ON_OCFS2} in
false)
echo "ERROR: Disk is not of type qcow2 and not on a file system the supports snapshots!"
echo
echo " Will not create a snapshot"
exit 6
;;
true)
start_additional_snap_process
move_working_to_snap
do_reflink_additional_snap
symlink_new_working_snap
add_additional_snapshot_description
;;
esac
;;
esac
# ;;
# xen)
# start_additional_snap_process
# move_working_to_snap
# do_reflink_additional_snap
# symlink_new_working_snap
# add_additional_snapshot_description
# ;;
# esac
}
########## Function: create_snapshot ######################################
create_snapshot() {
local DISK="$1"
local SNAPNAME="$2"
local SNAP_DESCR="$3"
#echo "Testing for base disk image file"
if ! [ -e "$DISK".base ]
then
create_first_snapshot "$DISK" "$SNAPNAME" "$SNAP_DESCR"
#return
elif [ -L "$DISK" ]
then
create_additional_snapshot "$DISK" "$SNAPNAME" "$SNAP_DESCR"
#return
fi
}
########## Function: branch_at_snapshot #####################################
branch_at_snapshot() {
local DISK="$1"
local SNAPNAME="$2"
local SNAP_DESCR="$3"
local SNAP_POINT=`ls "$DISK".* | grep "$SNAPNAME$"`
start_branch_process() {
#echo "Finding and unused snap name"
local SNAMECOUNT=1
until ! ls "$DISK".* | grep -q "$SNAPNAME"-"$SNAMECOUNT"$
do
((SNAMECOUNT++))
done
SUB_SNAPNAME="$SNAPNAME"-"$SNAMECOUNT"
echo "*****************************************************************"
echo " Branching from snapshot: $SNAP_POINT"
}
remove_current_working() {
rm -f "$DISK"
rm -f "$DISK".*._working
}
do_qemu_img_branch_snap() {
"$QEMU_IMG_CMD" create -f qcow2 -b "$SNAP_POINT" "$SNAP_POINT"."$SUB_SNAPNAME"._working
}
do_vhd_util_branch_snap() {
"$VHD_UITL_CMD" snapshot -p "$SNAP_POINT" -n "$SNAP_POINT"."$SUB_SNAPNAME"._working
}
do_reflink_branch_snap() {
"${REFLINK_CMD}" "${SNAP_POINT}" "${SNAP_POINT}"."${SUB_SNAPNAME}"._working
}
symlink_new_branch_snap() {
ln -s "$SNAP_POINT"."$SUB_SNAPNAME"._working "$DISK"
chmod a-w "$SNAP_POINT"
}
add_branch_snapshot_description() {
echo `echo "${SNAP_POINT}.${SUB_SNAPNAME}" | sed 's/\./_/g' | sed 's/__working//g'`"='$SNAP_DESCR'" >> ."$DISK".snap_descriptions
}
if [ -z "$SNAPNAME" ]
then
echo
echo "ERROR: You must supply a snapshot name to branch from!"
echo
exit 4
fi
if ! ls "$DISK".* | grep -q "$SNAPNAME$"
then
echo
echo "ERROR: The specified snapshot \"$SNAPNAME\" does not appear to exist!"
echo
exit 5
fi
# case ${HYPERVISOR} in
# qemu)
case ${IMG_TYPE} in
qcow2)
start_branch_process
remove_current_working
do_qemu_img_branch_snap
symlink_new_branch_snap
add_branch_snapshot_description
;;
*)
case ${ON_OCFS2} in
false)
echo "ERROR: Disk is not of type qcow2 and not on a file system the supports snapshots!"
echo
echo " Will not create a snapshot"
exit 6
;;
true)
start_branch_process
remove_current_working
do_reflink_branch_snap
symlink_new_branch_snap
add_branch_snapshot_description
;;
esac
;;
esac
# ;;
# xen)
# start_branch_process
# remove_current_working
# do_reflink_branch_snap
# symlink_new_branch_snap
# add_branch_snapshot_description
# ;;
# esac
}
########## Function: revert_to_snapshot #####################################
revert_to_snapshot() {
local DISK="$1"
local SNAPNAME="$2"
local SNAP_DESCR="$3"
local SNAP_POINT=`ls "$DISK".* | grep "$SNAPNAME$"`
start_revert_process() {
echo "*****************************************************************"
echo " Reverting to snapshot: $SNAP_POINT"
echo "*****************************************************************"
}
remove_current_working_and_snap_point() {
rm -f "$DISK"
rm -f "$DISK".*._working
rm -f "$SNAP_POINT".*
}
do_new_qemu_img_working_snap() {
"$QEMU_IMG_CMD" create -f qcow2 -b "$SNAP_POINT" "$SNAP_POINT"._working
}
do_new_vhd_util_working_snap() {
"$VHD_UTIL_CMD" snapshot -p "$SNAP_POINT" -n "$SNAP_POINT"._working
}
do_new_reflink_working_snap() {
"${REFLINK_CMD}" "${SNAP_POINT}" "${SNAP_POINT}"._working
}
symlink_to_reverted_snap() {
ln -s "$SNAP_POINT"._working "$DISK"
chmod a-w "$SNAP_POINT"
}
remove_reverted_snapshot_description() {
sed -i "/$SNAP_POINT\..*/d" ."$DISK".snap_descriptions
}
if [ -z "$SNAPNAME" ]
then
echo
echo "ERROR: You must supply a snapshot name to revert to!"
echo
exit 4
fi
if ! ls "$DISK".* | grep -q "$SNAPNAME$"
then
echo
echo "ERROR: The specified snapshot \"$SNAPNAME\" does not appear to exist!"
echo
exit 5
fi
# case ${HYPERVISOR} in
# qemu)
case ${IMG_TYPE} in
qcow2)
start_revert_process
remove_current_working_and_snap_point
do_new_qemu_img_working_snap
symlink_to_reverted_snap
remove_reverted_snapshot_description
;;
*)
case ${ON_OCFS2} in
false)
echo "ERROR: Disk is not of type qcow2 and not on a file system the supports snapshots!"
echo
echo " Will not create a snapshot"
exit 6
;;
true)
start_revert_process
remove_current_working_and_snap_point
do_new_reflink_working_snap
symlink_to_reverted_snap
remove_reverted_snapshot_description
;;
esac
;;
esac
# ;;
# xen)
# start_revert_process
# remove_current_working_and_snap_point
# do_new_reflink_working_snap
# symlink_to_reverted_snap
# remove_reverted_snapshot_description
# ;;
# esac
}
########## Function: remove_snapshot #####################################
remove_snapshots() {
local DISK="$1"
local SNAPNAME="$2"
local SNAP_DESCR="$3"
#echo "Testing for base disk image file"
if ! [ -e "$DISK".base ]
then
echo "$DISK doesn't appear to have any snapshots"
else
echo "*****************************************************************"
echo " Removing all snapshots from: $DISK"
echo "*****************************************************************"
rm -f "$DISK"
chmod u+w "$DISK".base.*
rm -r "$DISK".base.*
# remove snapshot description
rm -f ."$DISK".snap_descriptions
mv "$DISK".base "$DISK"
fi
}
########## Function: list_snapshots ######################################
list_snapshots() {
local DISK="$1"
local SNAPNAME="$2"
local SNAP_DESCR="$3"
#echo "Testing for base disk image file"
if ! [ -e "$DISK".base ]
then
echo "$DISK doesn't appear to have any snapshots"
else
for SNAP in `ls "$DISK".base.*`
do
if ! echo "$SNAP" | grep -q "_working"
then
local DOT_COUNT=`echo $SNAP | grep -o \\\. | wc -l`
((DOT_COUNT++))
local SNAP_NAME=`echo "$SNAP" | cut -d \. -f "$DOT_COUNT"`
local SNAP_ENTRY=`echo $SNAP | sed 's/\./_/g'`
echo -n "$SNAP_NAME = ";grep "$SNAP_ENTRY=" ."$DISK".snap_descriptions | cut -d '=' -f 2
fi
done
fi
}
########## Function: delete_snapshot #####################################
# Disabled until I can figure out how to 'merge' cow snapshots
delete_snapshot() {
local DISK="$1"
local SNAPNAME="$2"
local SNAP_DESC="$3"
if [ -z "$SNAPNAME" ]
then
echo
echo "ERROR: You must supply a snapshot name to delete!"
echo
exit 4
fi
if ! ls "$DISK".* | grep -q "$SNAPNAME$"
then
echo
echo "ERROR: The specified snapshot \"$SNAPNAME\" does not appear to exist!"
echo
exit 5
fi
if [ "$DISK.$SNAPNAME" = "$DISK.base" ]
then
echo
echo "ERROR: You cannot delete the base image!"
echo
exit 6
fi
local SNAP_DEL=`ls "$DISK".* | grep "$SNAPNAME$"`
local SNAP_POINT=`echo $SNAP_DEL | sed "s/\.$SNAPNAME$//g"`
if [ "$SNAP_POINT" = "$DISK.base" ]
then
echo > /dev/null
#echo "Guess its the base"
#continue
elif ls "$SNAP_POINT".* > /dev/null 2>&1
then
#echo "SNAP_POINT=$SNAP_POINT, looking for end of snap train"
local SNAP_POINT_OPTIONS=`ls "$SNAP_POINT".*`
#echo "My choices are: $SNAP_POINT_OPTIONS"
if ! [ "$SNAP_POINT_OPTIONS" = "$SNAP_DEL" ]
then
for SP in $SNAP_POINT_OPTIONS
do
if ! ls "$SP".* > /dev/null 2>&1
then
if ! [ "$SP" = "$SNAP_DEL" ]
then
SNAP_POINT="$SP"
fi
fi
done
#else
# echo "Oops. guess I don't need to look after all"
fi
elif [ "$SNAP_POINT" = "$SNAP_DEL" ]
then
#echo "Opps. Del point = Snap point. trying to fix..."
local FCOUNT=`echo $SNAP_POINT | grep -o "\." | wc -l`
((FCOUNT++))
LAST_FLD=`echo $SNAP_POINT | cut -d '.' -f $FCOUNT`
SNAP_POINT=`echo $SNAP_POINT | sed "s/\.$LAST_FLD//g"`
fi
echo "*****************************************************************"
echo " Deleting snapshot: $SNAP_DEL"
echo " Reverting to snapshot: $SNAP_POINT"
echo "*****************************************************************"
if [ -L "$DISK" ]
then
rm -f "$DISK"
fi
rm -f "$DISK".*._working
rm -f "$SNAP_DEL"*
"$QEMU_IMG_CMD" create -f qcow2 -b "$SNAP_POINT" "$SNAP_POINT"._working
ln -s "$SNAP_POINT"._working "$DISK"
# remove snapshot description
sed -i "/$SNAP_DEL.*/d" ."$DISK".snap_descriptions
}
###################################################################################
# Main Code Body
###################################################################################
get_options $*
cd "$DISK_PATH"
test_disk "$DISK_IMAGE"
case "$MODE" in
create)
echo "*****************************************************************"
create_snapshot "$DISK_IMAGE" "$SNAPSHOT_NAME" "$SNAPSHOT_DESCR"
;;
branch)
branch_at_snapshot "$DISK_IMAGE" "$SNAPSHOT_NAME" "$SNAPSHOT_DESCR"
;;
revert)
revert_to_snapshot "$DISK_IMAGE" "$SNAPSHOT_NAME" "$SNAPSHOT_DESCR"
;;
remove)
remove_snapshots "$DISK_IMAGE" "$SNAPSHOT_NAME" "$SNAPSHOT_DESCR"
;;
list)
list_snapshots "$DISK_IMAGE" "$SNAPSHOT_NAME" "$SNAPSHOT_DESCR"
;;
# Disabled until I can figure out how to merge cow snapshots
# delete)
# delete_snapshot "$DISK_IMAGE" "$SNAPSHOT_NAME" "$SNAPSHOT_DESCR"
# ;;
help)
description
usage
;;
esac
exit 0