Petr Šabata f5bf49
#!/usr/bin/env bash
Petr Šabata f5bf49
[ -z "$TESTDIR" ] && TESTDIR=$(realpath $(dirname "$0")/../)
Petr Šabata f5bf49
Petr Šabata f5bf49
SUDO="sudo"
Petr Šabata f5bf49
Petr Šabata f5bf49
declare -A MNTS=()
Petr Šabata f5bf49
declare -A DEVS=()
Petr Šabata f5bf49
Petr Šabata f5bf49
perror() {
Petr Šabata f5bf49
       echo $@>&2
Petr Šabata f5bf49
}
Petr Šabata f5bf49
Petr Šabata f5bf49
perror_exit() {
Petr Šabata f5bf49
       echo $@>&2
Petr Šabata f5bf49
       exit 1
Petr Šabata f5bf49
}
Petr Šabata f5bf49
Petr Šabata f5bf49
is_mounted()
Petr Šabata f5bf49
{
Petr Šabata f5bf49
       findmnt -k -n $1 &>/dev/null
Petr Šabata f5bf49
}
Petr Šabata f5bf49
Petr Šabata f5bf49
clean_up()
Petr Šabata f5bf49
{
Petr Šabata f5bf49
	for _mnt in ${MNTS[@]}; do
Petr Šabata f5bf49
		is_mounted $_mnt && $SUDO umount -f $_mnt
Petr Šabata f5bf49
	done
Petr Šabata f5bf49
Petr Šabata f5bf49
	for _dev in ${DEVS[@]}; do
Petr Šabata f5bf49
		[ ! -e "$_dev" ] && continue
Petr Šabata f5bf49
		[[ "$_dev" == "/dev/loop"* ]] && $SUDO losetup -d "$_dev"
Petr Šabata f5bf49
		[[ "$_dev" == "/dev/nbd"* ]] && $SUDO qemu-nbd --disconnect "$_dev"
Petr Šabata f5bf49
	done
Petr Šabata f5bf49
Petr Šabata f5bf49
	[ -d "$TMPDIR" ] && $SUDO rm --one-file-system -rf -- "$TMPDIR";
Petr Šabata f5bf49
Petr Šabata f5bf49
	sync
Petr Šabata f5bf49
}
Petr Šabata f5bf49
Petr Šabata f5bf49
trap '
Petr Šabata f5bf49
ret=$?;
Petr Šabata f5bf49
clean_up
Petr Šabata f5bf49
exit $ret;
Petr Šabata f5bf49
' EXIT
Petr Šabata f5bf49
Petr Šabata f5bf49
# clean up after ourselves no matter how we die.
Petr Šabata f5bf49
trap 'exit 1;' SIGINT
Petr Šabata f5bf49
Petr Šabata f5bf49
readonly TMPDIR="$(mktemp -d -t kexec-kdump-test.XXXXXX)"
Petr Šabata f5bf49
[ -d "$TMPDIR" ] || perror_exit "mktemp failed."
Petr Šabata f5bf49
Petr Šabata f5bf49
get_image_fmt() {
Petr Šabata f5bf49
	local image=$1 fmt
Petr Šabata f5bf49
Petr Šabata f5bf49
	[ ! -e "$image" ] && perror "image: $image doesn't exist" && return 1
Petr Šabata f5bf49
Petr Šabata f5bf49
	fmt=$(qemu-img info $image | sed -n "s/file format:\s*\(.*\)/\1/p")
Petr Šabata f5bf49
Petr Šabata f5bf49
	[ $? -eq 0 ] && echo $fmt && return 0
Petr Šabata f5bf49
Petr Šabata f5bf49
	return 1
Petr Šabata f5bf49
}
Petr Šabata f5bf49
Petr Šabata f5bf49
# If it's partitioned, return the mountable partition, else return the dev
Petr Šabata f5bf49
get_mountable_dev() {
Petr Šabata f5bf49
	local dev=$1 parts
Petr Šabata f5bf49
Petr Šabata f5bf49
	$SUDO partprobe $dev && sync
Petr Šabata f5bf49
	parts="$(ls -1 ${dev}p*)"
Petr Šabata f5bf49
	if [ -n "$parts" ]; then
Petr Šabata f5bf49
		if [ $(echo "$parts" | wc -l) -gt 1 ]; then
Petr Šabata f5bf49
			perror "It's a image with multiple partitions, using last partition as main partition"
Petr Šabata f5bf49
		fi
Petr Šabata f5bf49
		echo "$parts" | tail -1
Petr Šabata f5bf49
	else
Petr Šabata f5bf49
		echo "$dev"
Petr Šabata f5bf49
	fi
Petr Šabata f5bf49
}
Petr Šabata f5bf49
Petr Šabata f5bf49
prepare_loop() {
Petr Šabata f5bf49
	[ -n "$(lsmod | grep "^loop")" ] && return
Petr Šabata f5bf49
Petr Šabata f5bf49
	$SUDO modprobe loop
Petr Šabata f5bf49
Petr Šabata f5bf49
	[ ! -e "/dev/loop-control" ] && perror_exit "failed to load loop driver"
Petr Šabata f5bf49
}
Petr Šabata f5bf49
Petr Šabata f5bf49
prepare_nbd() {
Petr Šabata f5bf49
	[ -n "$(lsmod | grep "^nbd")" ] && return
Petr Šabata f5bf49
Petr Šabata f5bf49
	$SUDO modprobe nbd max_part=4
Petr Šabata f5bf49
Petr Šabata f5bf49
	[ ! -e "/dev/nbd0" ] && perror_exit "failed to load nbd driver"
Petr Šabata f5bf49
}
Petr Šabata f5bf49
Petr Šabata f5bf49
mount_nbd() {
Petr Šabata f5bf49
	local image=$1 size dev
Petr Šabata f5bf49
	for _dev in /sys/class/block/nbd* ; do
Petr Šabata f5bf49
		size=$(cat $_dev/size)
Petr Šabata f5bf49
		if [ "$size" -eq 0 ] ; then
Petr Šabata f5bf49
			dev=/dev/${_dev##*/}
Petr Šabata f5bf49
			$SUDO qemu-nbd --connect=$dev $image 1>&2
Petr Šabata f5bf49
			[ $? -eq 0 ] && echo $dev && break
Petr Šabata f5bf49
		fi
Petr Šabata f5bf49
	done
Petr Šabata f5bf49
Petr Šabata f5bf49
	return 1
Petr Šabata f5bf49
}
Petr Šabata f5bf49
Petr Šabata f5bf49
image_lock()
Petr Šabata f5bf49
{
Petr Šabata f5bf49
	local image=$1 timeout=5 fd
Petr Šabata f5bf49
Petr Šabata f5bf49
	eval "exec {fd}>$image.lock"
Petr Šabata f5bf49
	if [ $? -ne 0 ]; then
Petr Šabata f5bf49
		perror_exit "failed acquiring image lock"
Petr Šabata f5bf49
		exit 1
Petr Šabata f5bf49
	fi
Petr Šabata f5bf49
Petr Šabata f5bf49
	flock -n $fd
Petr Šabata f5bf49
	rc=$?
Petr Šabata f5bf49
	while [ $rc -ne 0 ]; do
Petr Šabata f5bf49
		echo "Another instance is holding the image lock ..."
Petr Šabata f5bf49
		flock -w $timeout $fd
Petr Šabata f5bf49
		rc=$?
Petr Šabata f5bf49
	done
Petr Šabata f5bf49
}
Petr Šabata f5bf49
Petr Šabata f5bf49
# Mount a device, will umount it automatially when shell exits
Petr Šabata f5bf49
mount_image() {
Petr Šabata f5bf49
	local image=$1 fmt
Petr Šabata f5bf49
	local dev mnt mnt_dev
Petr Šabata f5bf49
Petr Šabata f5bf49
	# Lock the image just in case user run this script in parrel
Petr Šabata f5bf49
	image_lock $image
Petr Šabata f5bf49
Petr Šabata f5bf49
	fmt=$(get_image_fmt $image)
Petr Šabata f5bf49
	[ $? -ne 0 ] || [ -z "$fmt" ] && perror_exit "failed to detect image format"
Petr Šabata f5bf49
Petr Šabata f5bf49
	if [ "$fmt" == "raw" ]; then
Petr Šabata f5bf49
		prepare_loop
Petr Šabata f5bf49
Petr Šabata f5bf49
		dev="$($SUDO losetup --show -f $image)"
Petr Šabata f5bf49
		[ $? -ne 0 ] || [ -z "$dev" ] && perror_exit "failed to setup loop device"
Petr Šabata f5bf49
Petr Šabata f5bf49
	elif [ "$fmt" == "qcow2" ]; then
Petr Šabata f5bf49
		prepare_nbd
Petr Šabata f5bf49
Petr Šabata f5bf49
		dev=$(mount_nbd $image)
Petr Šabata f5bf49
		[ $? -ne 0 ] || [ -z "$dev" ] perror_exit "failed to connect qemu to nbd device '$dev'"
Petr Šabata f5bf49
	else
Petr Šabata f5bf49
		perror_exit "Unrecognized image format '$fmt'"
Petr Šabata f5bf49
	fi
Petr Šabata f5bf49
	DEVS[$image]="$dev"
Petr Šabata f5bf49
Petr Šabata f5bf49
	mnt="$(mktemp -d -p $TMPDIR -t mount.XXXXXX)"
Petr Šabata f5bf49
	[ $? -ne 0 ] || [ -z "$mnt" ] && perror_exit "failed to create tmp mount dir"
Petr Šabata f5bf49
	MNTS[$image]="$mnt"
Petr Šabata f5bf49
Petr Šabata f5bf49
	mnt_dev=$(get_mountable_dev "$dev")
Petr Šabata f5bf49
	[ $? -ne 0 ] || [ -z "$mnt_dev" ] && perror_exit "failed to setup loop device"
Petr Šabata f5bf49
Petr Šabata f5bf49
	$SUDO mount $mnt_dev $mnt
Petr Šabata f5bf49
	[ $? -ne 0 ] && perror_exit "failed to mount device '$mnt_dev'"
Petr Šabata f5bf49
}
Petr Šabata f5bf49
Petr Šabata f5bf49
get_image_mount_root() {
Petr Šabata f5bf49
	local image=$1
Petr Šabata f5bf49
	local root=${MNTS[$image]}
Petr Šabata f5bf49
Petr Šabata f5bf49
	echo $root
Petr Šabata f5bf49
Petr Šabata f5bf49
	if [ -z "$root" ]; then
Petr Šabata f5bf49
		return 1
Petr Šabata f5bf49
	fi
Petr Šabata f5bf49
}
Petr Šabata f5bf49
Petr Šabata f5bf49
shell_in_image() {
Petr Šabata f5bf49
	local root=$(get_image_mount_root $1) && shift
Petr Šabata f5bf49
Petr Šabata f5bf49
	pushd $root
Petr Šabata f5bf49
Petr Šabata f5bf49
	$SHELL
Petr Šabata f5bf49
Petr Šabata f5bf49
	popd
Petr Šabata f5bf49
}
Petr Šabata f5bf49
Petr Šabata f5bf49
inst_pkg_in_image() {
Petr Šabata f5bf49
	local root=$(get_image_mount_root $1) && shift
Petr Šabata f5bf49
Petr Šabata f5bf49
	# LSB not available
Petr Šabata f5bf49
	# release_info=$($SUDO chroot $root /bin/bash -c "lsb_release -a")
Petr Šabata f5bf49
	# release=$(echo "$release_info" | sed -n "s/Release:\s*\(.*\)/\1/p")
Petr Šabata f5bf49
	# distro=$(echo "$release_info" | sed -n "s/Distributor ID:\s*\(.*\)/\1/p")
Petr Šabata f5bf49
	# if [ "$distro" != "Fedora" ]; then
Petr Šabata f5bf49
	# 	perror_exit "only Fedora image is supported"
Petr Šabata f5bf49
	# fi
Petr Šabata f5bf49
	release=$(cat $root/etc/fedora-release | sed -n "s/.*[Rr]elease\s*\([0-9]*\).*/\1/p")
Petr Šabata f5bf49
	[ $? -ne 0 ] || [ -z "$release" ] && perror_exit "only Fedora image is supported"
Petr Šabata f5bf49
Petr Šabata f5bf49
	$SUDO dnf --releasever=$release --installroot=$root install -y $@
Petr Šabata f5bf49
}
Petr Šabata f5bf49
Petr Šabata f5bf49
run_in_image() {
Petr Šabata f5bf49
	local root=$(get_image_mount_root $1) && shift
Petr Šabata f5bf49
Petr Šabata f5bf49
	$SUDO chroot $root /bin/bash -c "$@"
Petr Šabata f5bf49
}
Petr Šabata f5bf49
Petr Šabata f5bf49
inst_in_image() {
Petr Šabata f5bf49
	local image=$1 src=$2 dst=$3
Petr Šabata f5bf49
	local root=${MNTS[$image]}
Petr Šabata f5bf49
Petr Šabata f5bf49
	$SUDO cp $src $root/$dst
Petr Šabata f5bf49
}
Petr Šabata f5bf49
Petr Šabata f5bf49
# If source image is qcow2, create a snapshot
Petr Šabata f5bf49
# If source image is raw, convert to raw
Petr Šabata f5bf49
# If source image is xz, decompress then repeat the above logic
Petr Šabata f5bf49
#
Petr Šabata f5bf49
# Won't touch source image
Petr Šabata f5bf49
create_image_from_base_image() {
Petr Šabata f5bf49
	local image=$1
Petr Šabata f5bf49
	local output=$2
Petr Šabata f5bf49
	local decompressed_image
Petr Šabata f5bf49
Petr Šabata f5bf49
	local ext="${image##*.}"
Petr Šabata f5bf49
	if [[ "$ext" == 'xz' ]]; then
Petr Šabata f5bf49
		echo "Decompressing base image..."
Petr Šabata f5bf49
		xz -d -k $image
Petr Šabata f5bf49
		decompressed_image=${image%.xz}
Petr Šabata f5bf49
		image=$decompressed_image
Petr Šabata f5bf49
	fi
Petr Šabata f5bf49
Petr Šabata f5bf49
	local image_fmt=$(qemu-img info $image | sed -n "s/file format:\s*\(.*\)/\1/p")
Petr Šabata f5bf49
	if [ "$image_fmt" != "raw" ]; then
Petr Šabata f5bf49
		if [ "$image_fmt" == "qcow2" ]; then
Petr Šabata f5bf49
			echo "Source image is qcow2, using snapshot..."
Petr Šabata f5bf49
			qemu-img create -f qcow2 -b $image $output
Petr Šabata f5bf49
		else
Petr Šabata f5bf49
			perror_exit "Unrecognized base image format $image_mnt"
Petr Šabata f5bf49
		fi
Petr Šabata f5bf49
	else
Petr Šabata f5bf49
		echo "Source image is raw, converting to qcow2..."
Petr Šabata f5bf49
		qemu-img convert -f raw -O qcow2 $image $output
Petr Šabata f5bf49
	fi
Petr Šabata f5bf49
Petr Šabata f5bf49
	# Clean up decompress temp image
Petr Šabata f5bf49
	if [ -n "$decompressed_image" ]; then
Petr Šabata f5bf49
		rm $decompressed_image
Petr Šabata f5bf49
	fi
Petr Šabata f5bf49
}