#!/bin/bash

PROG="${0##*/}"
TEST=
message()
{
	printf >&2 '%s%s: %s\n' "$PROG" "${TEST:+ ($TESTNAME)}" "$*"
}

fatal()
{
	message "$*"
	exit 1
}

gh_group_start()
{
	echo "::group::$*"
}

gh_group_end()
{
	echo "::endgroup::"
}

run()
{
	message "run: $*"
	"$@"
}

cat_exec()
{
	cat > "$1"
	chmod +x "$1"
}

valid_log()
{
	if grep -qse '^IT WORKS!' "$2"; then
		message "$1 DONE"
		return 0
	else
		message "$1 FAIL"
		return 1
	fi
}


qemu_args()
{
	printf -- 'qemu_args+=("%s")\n' "$@"
}


pack_sysimage()
{
	cat_exec "$top_workdir/run.sh" <<-EOF
	#!/bin/bash -efux

	bytes=\$(du -sb /image |cut -f1)
	bytes=\$(( \$bytes + ( ( \$bytes * 30 ) / 100 ) ))

	dd count=1 bs=\$bytes if=/dev/zero of=/host/$workdir/sysimage.img
	/sbin/mke2fs -t ext3 -L SYSIMAGE -d /image /host/$workdir/sysimage.img
	EOF

	run podman run --rm -ti \
		--mount="type=image,src=localhost/mi-$VENDOR:sysimage,dst=/image" \
		--mount="type=tmpfs,destination=/image$builddir" \
		--mount="type=bind,src=$topdir/.build/dest,dst=/image$builddir/.build/dest" \
		--mount="type=bind,src=$topdir/data,dst=/image$builddir/data" \
		--mount="type=bind,src=$topdir/features,dst=/image$builddir/features" \
		--mount="type=bind,src=$topdir/guess,dst=/image$builddir/guess" \
		--mount="type=bind,src=$topdir/tools,dst=/image$builddir/tools" \
		--volume="$topdir:/host" \
		"localhost/mi-$VENDOR:sys" "/host/$workdir/run.sh"
}

prepare_testsuite()
{
	mkdir -p -- \
		"$workdir" \
		"$logdir" \
		"$statusdir"
	echo FAILED \
		> "$statusdir/$VENDOR-$TESTNAME"
	if [ -e "$topdir/testing/$TESTNAME.cfg" ]; then
		sed \
			-e "s#@CMDLINE@#$BOOT_CMDLINE#g" \
			\
			"$topdir/testing/testing-$VENDOR${VARIANT:+-$VARIANT}-ks-sysimage.cfg" \
			"$topdir/testing/$TESTNAME.cfg" \
			"$topdir/testing/testing-$VENDOR${VARIANT:+-$VARIANT}-ks-initrd.cfg" \
			"$topdir/testing/testing-$VENDOR${VARIANT:+-$VARIANT}-ks-done.cfg" \
			\
			> "$workdir/ks.cfg"
	fi

	case "${SYSIMAGE_SIZE:-full}" in
		mini) SYSIMAGE_PACKAGES="$SYSIMAGE_BASE_PACKAGES" ;;
		full) SYSIMAGE_PACKAGES="$SYSIMAGE_BASE_PACKAGES $SYSIMAGE_EXTRA_PACKAGES" ;;
	esac

	if [ "${BOOTLOADER_TYPE-}" = "none" ]; then
		BOOTLOADER_EFI_PACKAGES=""
		BOOTLOADER_PC_PACKAGES=""
	fi

	case "${BOOT_BIOS:-pc}" in
		efi) SYSIMAGE_PACKAGES+=" $BOOTLOADER_EFI_PACKAGES" ;;
		pc)  SYSIMAGE_PACKAGES+=" $BOOTLOADER_PC_PACKAGES" ;;
	esac
}

qemu_setup_cpu()
{
	:> "$workdir/qemu-cpu"

	if [ ! -c /dev/kvm ]; then
		local ncpu

		ncpu=$(grep -c ^processor /proc/cpuinfo) ||
				ncpu=1

		qemu_args -smp $(( $ncpu * 2 )) -accel tcg,thread=multi,tb-size=1024 \
		 >> "$workdir/qemu-cpu"
	else
		qemu_args -enable-kvm -cpu host \
		 >> "$workdir/qemu-cpu"
	fi

	case "${BOOT_BIOS:-pc}" in
		efi)
			[ -f "$top_workdir"/efivars.bin ] ||
				cp -vTLf /usr/share/OVMF/OVMF_VARS.fd "$top_workdir"/efivars.bin

			qemu_args \
			 -machine q35 \
			 -drive if=pflash,format=raw,readonly=on,file=/usr/share/OVMF/OVMF_CODE.fd \
			 -drive if=pflash,format=raw,file="$top_workdir/efivars.bin" \
			 >> "$workdir/qemu-cpu"
			;;
		pc)
			qemu_args \
			 -machine pc \
			 >> "$workdir/qemu-cpu"
			;;
	esac
}

create_qemu_wrapper()
{
	local fn

	fn="$1"

	cat_exec "$fn-wrapper" <<-EOF
	#!/bin/bash
	qemu_args=()
	qemu_args+=( -m ${QEMU_MEMORY:-1G} -no-reboot )
	qemu_args+=( ${BOOT_KERNEL:+-kernel \"$BOOT_KERNEL\"} )
	qemu_args+=( ${BOOT_INITRD:+-initrd \"$BOOT_INITRD\"} )
	qemu_args+=( ${BOOT_APPEND:+-append \"$BOOT_APPEND\"} )
	qemu_args+=( -rtc base=localtime,clock=vm )
	qemu_args+=( -nic user,model=e1000 )
	qemu_args+=( -nographic -serial chardev:stdio -chardev stdio,mux=on,id=stdio,signal=off )
	qemu_args+=( -mon chardev=stdio,mode=readline )
	qemu_args+=( -pidfile "$top_workdir/qemu.pid" )
	. "$top_workdir/qemu-cpu"
	. "$top_workdir/qemu-disks"
	set -x
	exec qemu-system-$ARCH "\${qemu_args[@]}"
	EOF

	cat_exec "$fn-runner" <<-EOF
	#!/bin/bash -x
	exec timeout --foreground --signal=TERM --kill-after=5s ${QEMU_TIMEOUT:-5m} \
	 ${BOOT_PROG:+"$topdir/testing/$BOOT_PROG"} "$fn-wrapper"
	EOF
}

qemu_create_disk()
{
	local i fn fmt size

	i="$1"; shift
	fn="$1"; shift
	fmt="${QEMU_DISK_FORMAT:-qcow2}"
	size="3G"

	[ ! -f "$fn" ] ||
		return 0

	case "$fmt" in
		qcow2|raw)
			qemu-img create -q -f "$fmt" "$fn" "$size"
			;;
	esac
}

qemu_activate_disks()
{
	local i fn fmt

	while read -r i fmt fn; do
		[ ! -f "$top_workdir/nbd-$i.pid" ] ||
			continue
		message "Start NBD Server for \`$fn' ..."
		qemu-nbd --fork --persistent \
			--pid-file="$top_workdir/nbd-$i.pid" \
			--socket="$top_workdir/nbd-$i.sock" \
			--discard=unmap \
			--detect-zeroes=unmap \
			--cache=unsafe \
			--format="$fmt" "$fn"
	done < "$workdir/disks.map"
}

qemu_init_blockdev()
{
	:> "$top_workdir/disks.map"
	:> "$top_workdir/qemu-disks"

	qemu_args \
	 -device "virtio-scsi-pci,id=scsi" \
	 >"$workdir/qemu-disks"
}

qemu_add_disk()
{
	local fn fmt i

	i="$1"; shift
	fn="$1"; shift

	[ "$#" -eq 0 ] &&
		fmt="${QEMU_DISK_FORMAT:-qcow2}" ||
		fmt="$1"

	qemu_args \
	 -object "iothread,id=iothread$i" \
	 -blockdev "node-name=file$i,driver=file,filename=$fn,aio=native,auto-read-only=on,discard=unmap,cache.direct=on,cache.no-flush=off" \
	 -blockdev "node-name=drive$i,driver=$fmt,file=file$i" \
	 -device "virtio-blk-pci,drive=drive$i,iothread=iothread$i,num-queues=1" \
	 >>"$workdir/qemu-disks"
}

qemu_setup_boot_disks()
{
	qemu_init_blockdev

	for (( i=1; i <= BOOT_DISKS; i++ )); do
		qemu_add_disk "$i" "$top_workdir/disk-$i.img"
	done

	qemu_activate_disks
}

qemu_setup_kickstart_disks()
{
	local i fn

	qemu_init_blockdev

	qemu_add_disk "0" "$top_workdir/sysimage.img" "raw"

	for (( i=1; i <= KICKSTART_DISKS; i++ )); do
		message "creating QEMU disk $i ..."

		fn="$top_workdir/disk-$i.img"

		qemu_create_disk "$i" "$fn"
		qemu_add_disk "$i" "$fn"
	done

	qemu_activate_disks
}

qemu_calc_boot_duration()
{
	local start=''
	local stop=''

	sed -r -n -e '/^Booting from (Hard Disk|ROM)\.\.\./,${ s/^\[([0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2})\] .*/\1/p }' |
		sort -u |
		sed -r -n -e '1p' -e '$p' |
	while read -r s; do
		t="$(date --date="$s +0000" '+%s')"

		if [ -z "$start" ]; then
			start="$t"
			continue
		elif [ -z "$stop" ]; then
			stop="$t"
		fi

		echo "$(( $stop - $start ))"
		break
	done
}

qemu_exec()
{
	create_qemu_wrapper "$top_workdir/qemu-bin"

	qemu_watchdog "$top_logdir/qemu.log" &

	local rc=0
	script -f "$top_logdir/qemu.log" -c "$top_workdir/qemu-bin-runner" ||
		rc=$?

	valid_log 'check' "$top_logdir/qemu.log" ||
		rc=$?

	local duration=0

	[ "$rc" -ne 0 ] ||
		duration="$(qemu_calc_boot_duration < "$top_logdir/qemu.log" ||:)"
	{
		printf 'vendor="%q"\n' "$VENDOR"
		printf 'testname="%q"\n' "$TESTNAME"
		printf 'duration=%d\n' "${duration:-0}"
		printf 'status=%d\n' "$rc"
	} > "$statusdir/artifact-$VENDOR-$TESTNAME.txt"

	set -- ${BOOT_LOGS-}

	while [ "$#" -gt 0 ]; do
		[ -f "$1" ] || continue
		echo "<<<<<< $1 <<<<<<"
		cat "$1"
		echo ">>>>>>"
		shift
	done

	[ $rc -eq 0 ]
}

qemu_watchdog()
{
	local i pid logfile failure

	logfile="$1"; shift
	touch "$logfile"

	i=0
	while [ "$i" -lt 20 ] && [ ! -e "$top_workdir/qemu.pid" ]; do
		sleep 1
		i=$(( $i + 1 ))
	done

	[ "$i" -lt 20 ] ||
		return 0

	failure=''
	while [ -e "$top_workdir/qemu.pid" ]; do
		for err in \
			"(or press Control-D to continue):" \
			"/.initrd/uevent/queues/udev.startup: No such file or directory" \
			"rdshell: The waiting time expired!" \
			"rdshell: The init program \`/sbin/init' not found in the root directory." \
		;
		do
			if grep -qsF -e "$err" "$logfile"; then
				failure=1
				break
			fi
		done

		if [ -n "$failure" ]; then
			pid="$(cat "$top_workdir/qemu.pid")"
			[ -z "$pid" ] ||
				kill -9 "$pid" ||:
			{
				printf '\n\n'
				printf '%s\n' \
					"ERROR!!!" \
					"The sign of a fatal error was found in the log." \
					"There is no reason to wait for a timeout."
				printf '\n\n'
			} >>"$logfile"
			break
		fi
	done
}

