File: //usr/bin/prl_backup
#!/bin/bash
#
# prl_backup - utility for managing attached backups
#
# (c) 2014. Parallels IP Holdings GmbH. All rights reserved.
KPARTX=kpartx
DMSETUP=dmsetup
LSBLK=lsblk
BLOCKDEV=blockdev
UDEVADM=udevadm
GETOPT=getopt
BASENAME=basename
MOUNT=mount
UMOUNT=umount
MODEL_PREFIX="__bckp"
OFFSET=1024
SCRIPTNAME=$("$BASENAME" $0)
msg() {
echo "$@" >&2
}
die() {
code=$1
shift
msg "$@"
exit $code
}
usage() {
msg "Usage: $SCRIPTNAME list [options]"
msg " $SCRIPTNAME enable /path/to/device"
msg " $SCRIPTNAME disable /path/to/device"
msg "Options for the 'list' command"
msg " -e|--enabled - Show only enabled attached backups"
msg " -d|--disabled - Show only disabled attached backups"
msg " -r|--raw - Raw output, useful for scripting"
exit 1
}
get_model() {
echo "$($UDEVADM info --query=property -n $1 2>/dev/null | grep 'ID_MODEL=' | cut -d = -f2)"
}
match_model() {
[[ "$1" =~ ^${MODEL_PREFIX}.* ]] && return 1
return 0
}
unmangle_model() {
echo ${1/"$MODEL_PREFIX"/"backup"}
}
list() {
local opt_raw=0
local opt_enabled=0
local opt_disabled=0
local opts=$("$GETOPT" -o edr -l enabled,disabled,raw -n "$SCRIPTNAME" -- "$@")
[ $? -ne 0 ] && usage
eval set -- "$opts"
while true
do
case $1 in
-e|--enabled)
opt_enabled=1
shift
;;
-d|--disabled)
opt_disabled=1
shift
;;
-r|--raw)
opt_raw=1
shift
;;
--)
shift; break
;;
*)
usage
;;
esac
done
if [ $opt_enabled -eq 0 -a $opt_disabled -eq 0 ]; then
opt_enabled=1
opt_disabled=1
fi
local -A enabled disabled
for dev in $("$LSBLK" --nodeps --noheadings --list --output NAME 2>/dev/null); do
local model=$(get_model "$dev")
match_model "$model"
[ $? -ne 0 ] || continue
model=$(unmangle_model "$model")
local node="/dev/mapper/$model"
local device="/dev/$dev"
# check that dmsetup was properly executed
if [ ! -e "$node" ]; then
disabled[$device]=""
continue
fi
enabled[$device]="$node"
done
if [ ${#enabled[@]} -eq 0 -a ${#disabled[@]} -eq 0 ]; then
msg "Attached backups are not detected"
exit 0
fi
local lvm=0
local i text
if [ ${#enabled[@]} -ne 0 -a $opt_enabled -eq 1 ]; then
[ $opt_raw -eq 1 ] || echo -e "List of enabled attached backups:\n"
i=1
for dev in ${!enabled[@]}; do
if [ $opt_raw -eq 1 ]; then
text="$dev ${enabled[$dev]}"
[ $opt_disabled -eq 1 ] && text="enabled $text"
echo "$text"
else
echo "[$((i++))] $dev (${enabled[$dev]})"
text="$($LSBLK --output NAME,TYPE,SIZE,FSTYPE,UUID,MOUNTPOINT ${enabled[$dev]} 2>/dev/null)"
echo "$text" | grep -q LVM2_member
[ $? -eq 0 ] && lvm=1
echo -e "$text\n"
fi
done
fi
if [ ${#disabled[@]} -ne 0 -a $opt_disabled -eq 1 ]; then
[ $opt_raw -eq 1 ] || echo -e "List of disabled attached backups:\n"
i=1
for dev in ${!disabled[@]}; do
text="$dev"
if [ $opt_raw -eq 1 ]; then
[ $opt_enabled -eq 1 ] && text="disabled $text"
echo "$text"
else
echo "[$((i++))] $text"
fi
done
[ $opt_raw -eq 1 ] || echo ""
fi
if [ $opt_raw -eq 0 ]; then
[ $lvm -eq 1 ] && msg "NOTE: before activating a LVM volume from the attached backup, consider changing its VG name using vgimportclone utility"
fi
}
check_block_dev() {
[ "$1" ] || die 1 "Device is not specified"
[ -b "$1" ] || die 2 "$1 is not a block device"
}
fake_udev_rule() {
$MOUNT --bind /dev/null "$1" || return 1
$UDEVADM control --reload &>/dev/null
return 0
}
unfake_udev_rule() {
$UMOUNT "$1"
if [ $? -ne 0 ]; then
msg "Unable to $UMOUNT $1. Please try to unmount it manually"
return
fi
$UDEVADM control --reload &>/dev/null
}
enable_one() {
local dev="$1"
check_block_dev "$dev"
local model=$(get_model "$dev")
match_model "$model"
[ $? -ne 0 ] || die 3 "$dev is not an attached backup"
model=$(unmangle_model "$model")
local name="$model"
local mname="/dev/mapper/$name"
local rule="/lib/udev/rules.d/69-dm-lvm-metad.rules"
"$DMSETUP" status $name &>/dev/null
[ $? -ne 0 ] || die 4 "Attached backup $dev is already enabled"
local size=$("$BLOCKDEV" --getsz "$dev" 2>/dev/null)
[ $? -ne 0 -o -z "$size" ] && die 5 "Can not get the size of block device $dev"
[ $size -le $(($OFFSET * 2)) ] && die 6 "Wrong disk size '$size' obtained from block device $dev"
"$DMSETUP" create $name --table "0 $(($size - $OFFSET * 2)) linear $dev $OFFSET" &>/dev/null
[ $? -eq 0 ] || die 7 "dmsetup failed to create a mapping for $dev"
# Execution of /lib/udev/rules.d/69-dm-lvm-metad.rules will result in starting pvscan service
# via systemd (e.g. in Fedora 23). pvscan will scan available disks for physical volumes and
# detect duplicates since the backup LVM PV has the same UUID. After that system may start using backup
# LVM instead of the original disks. For more information see #PSBM-42980.
fake_udev_rule $rule && unfake=1
"$KPARTX" -a -p p -s "$mname" &>/dev/null
if [ $? -eq 0 ]; then
$UDEVADM settle --timeout 10 &>/dev/null
else
msg "$KPARTX failed to parse the partition table on $mname"
fi
if [ $unfake -eq 1 ]; then
unfake_udev_rule $rule
fi
}
disable_one() {
local dev="$1"
check_block_dev "$dev"
local model=$(get_model "$dev")
match_model "$model"
[ $? -ne 0 ] || die 3 "$dev is not an attached backup"
model=$(unmangle_model "$model")
local name="$model"
local mname="/dev/mapper/$name"
"$DMSETUP" status "$name" &>/dev/null
[ $? -eq 0 ] || die 4 "Attached backup $dev is not enabled"
"$KPARTX" -d -p p -s "$mname" &>/dev/null
[ $? -eq 0 ] || msg "Failed to disable partitions for attached backup $dev"
"$DMSETUP" remove "$name" &>/dev/null
[ $? -eq 0 ] || die 5 "Failed to remove mapping for attached backup $dev"
}
[ $# -ge 1 ] || usage
proceed=1
for exe in "$KPARTX" "$DMSETUP" "$LSBLK" "$BLOCKDEV" "$UDEVADM"; do
path=$(which "$exe" 2>/dev/null)
if [ $? -ne 0 -o -z "$path" ]; then
msg "$exe utility is not found"
proceed=0
fi
done
[ $proceed -ne 1 ] && die 1 "Could not proceed because of unsatisfied dependencies"
case $1 in
list)
shift
list "$@"
;;
enable)
enable_one "$2"
;;
disable)
disable_one "$2"
;;
*)
usage
;;
esac
exit 0