##############################################################################
# run_qemu translates the ambiguous exit status in Table1 to that in Table2.
# Table3 simply documents the complete status table.
#
# Table1: Before fixup
# --------------------
# 0      - Unexpected exit from QEMU (possible signal), or the unittest did
#          not use debug-exit
# 1      - most likely unittest succeeded, or QEMU failed
#
# Table2: After fixup
# -------------------
# 0      - Everything succeeded
# 1      - most likely QEMU failed
#
# Table3: Complete table
# ----------------------
# 0      - SUCCESS
# 1      - most likely QEMU failed
# 2      - most likely a run script failed
# 3      - most likely the unittest failed
# 124    - most likely the unittest timed out
# 127    - most likely the unittest called abort()
# 1..127 - FAILURE (could be QEMU, a run script, or the unittest)
# >= 128 - Signal (signum = status - 128)
##############################################################################
run_qemu ()
{
	local stdout errors ret sig

	echo -n "$@"
	initrd_create &&
		echo -n " #"
	echo " $INITRD"

	# stdout to {stdout}, stderr to $errors and stderr
	exec {stdout}>&1
	errors=$("${@}" $INITRD </dev/null 2> >(tee /dev/stderr) > /dev/fd/$stdout)
	ret=$?
	exec {stdout}>&-

	[ $ret -eq 134 ] && echo "QEMU Aborted" >&2

	if [ "$errors" ]; then
		sig=$(grep 'terminating on signal' <<<"$errors")
		if [ "$sig" ]; then
			sig=$(sed 's/.*terminating on signal \([0-9][0-9]*\).*/\1/' <<<"$sig")
		fi
	fi

	if [ $ret -eq 0 ]; then
		# Some signals result in a zero return status, but the
		# error log tells the truth.
		if [ "$sig" ]; then
			((ret=sig+128))
		else
			# Exiting with zero (non-debugexit) is an error
			ret=1
		fi
	elif [ $ret -eq 1 ]; then
		# Even when ret==1 (unittest success) if we also got stderr
		# logs, then we assume a QEMU failure. Otherwise we translate
		# status of 1 to 0 (SUCCESS)
		if [ -z "$(echo "$errors" | grep -vi warning)" ]; then
			ret=0
		fi
	fi

	return $ret
}

run_qemu_status ()
{
	local stdout ret

	exec {stdout}>&1
	lines=$(run_qemu "$@" > >(tee /dev/fd/$stdout))
	ret=$?
	exec {stdout}>&-

	if [ $ret -eq 1 ]; then
		testret=$(grep '^EXIT: ' <<<"$lines" | sed 's/.*STATUS=\([0-9][0-9]*\).*/\1/')
		if [ "$testret" ]; then
			if [ $testret -eq 1 ]; then
				ret=0
			else
				ret=$testret
			fi
		fi
	fi

	return $ret
}

timeout_cmd ()
{
	if [ "$TIMEOUT" ] && [ "$TIMEOUT" != "0" ]; then
		echo "timeout -k 1s --foreground $TIMEOUT"
	fi
}

qmp ()
{
	echo '{ "execute": "qmp_capabilities" }{ "execute":' "$2" '}' | nc -U $1
}

run_migration ()
{
	if ! command -v nc >/dev/null 2>&1; then
		echo "${FUNCNAME[0]} needs nc (netcat)" >&2
		return 2
	fi

	migsock=`mktemp -u -t mig-helper-socket.XXXXXXXXXX`
	migout1=`mktemp -t mig-helper-stdout1.XXXXXXXXXX`
	qmp1=`mktemp -u -t mig-helper-qmp1.XXXXXXXXXX`
	qmp2=`mktemp -u -t mig-helper-qmp2.XXXXXXXXXX`
	fifo=`mktemp -u -t mig-helper-fifo.XXXXXXXXXX`
	qmpout1=/dev/null
	qmpout2=/dev/null

	trap 'kill 0; exit 2' INT TERM
	trap 'rm -f ${migout1} ${migsock} ${qmp1} ${qmp2} ${fifo}' RETURN EXIT

	eval "$@" -chardev socket,id=mon1,path=${qmp1},server,nowait \
		-mon chardev=mon1,mode=control | tee ${migout1} &

	# We have to use cat to open the named FIFO, because named FIFO's, unlike
	# pipes, will block on open() until the other end is also opened, and that
	# totally breaks QEMU...
	mkfifo ${fifo}
	eval "$@" -chardev socket,id=mon2,path=${qmp2},server,nowait \
		-mon chardev=mon2,mode=control -incoming unix:${migsock} < <(cat ${fifo}) &
	incoming_pid=`jobs -l %+ | awk '{print$2}'`

	# The test must prompt the user to migrate, so wait for the "migrate" keyword
	while ! grep -q -i "migrate" < ${migout1} ; do
		sleep 1
	done

	qmp ${qmp1} '"migrate", "arguments": { "uri": "unix:'${migsock}'" }' > ${qmpout1}

	# Wait for the migration to complete
	migstatus=`qmp ${qmp1} '"query-migrate"' | grep return`
	while ! grep -q '"completed"' <<<"$migstatus" ; do
		sleep 1
		migstatus=`qmp ${qmp1} '"query-migrate"' | grep return`
		if grep -q '"failed"' <<<"$migstatus" ; then
			echo "ERROR: Migration failed." >&2
			qmp ${qmp1} '"quit"'> ${qmpout1} 2>/dev/null
			qmp ${qmp2} '"quit"'> ${qmpout2} 2>/dev/null
			return 2
		fi
	done
	qmp ${qmp1} '"quit"'> ${qmpout1} 2>/dev/null
	echo > ${fifo}
	wait $incoming_pid
	ret=$?
	wait
	return $ret
}

migration_cmd ()
{
	if [ "$MIGRATION" = "yes" ]; then
		echo "run_migration"
	fi
}

search_qemu_binary ()
{
	local save_path=$PATH
	local qemucmd qemu

	export PATH=$PATH:/usr/libexec
	for qemucmd in ${QEMU:-qemu-system-$ARCH_NAME qemu-kvm}; do
		if $qemucmd --help 2>/dev/null | grep -q 'QEMU'; then
			qemu="$qemucmd"
			break
		fi
	done

	if [ -z "$qemu" ]; then
		echo "A QEMU binary was not found." >&2
		echo "You can set a custom location by using the QEMU=<path> environment variable." >&2
		return 2
	fi
	command -v $qemu
	export PATH=$save_path
}

initrd_create ()
{
	local ret

	env_add_errata
	ret=$?

	unset INITRD
	[ -f "$ENV" ] && INITRD="-initrd $ENV"

	return $ret
}

env_add_errata ()
{
	local line errata ret=1

	if [ -f "$ENV" ] && grep -q '^ERRATA_' <(env); then
		for line in $(grep '^ERRATA_' "$ENV"); do
			errata=${line%%=*}
			test -v $errata && continue
			eval export "$line"
		done
	elif [ ! -f "$ENV" ]; then
		env_generate_errata
	fi

	if grep -q '^ERRATA_' <(env); then
		export ENV_OLD="$ENV"
		export ENV=$(mktemp)
		trap_exit_push 'rm -f $ENV; [ "$ENV_OLD" ] && export ENV="$ENV_OLD" || unset ENV; unset ENV_OLD'
		[ -f "$ENV_OLD" ] && grep -v '^ERRATA_' "$ENV_OLD" > $ENV
		grep '^ERRATA_' <(env) >> $ENV
		ret=0
	fi

	return $ret
}

env_generate_errata ()
{
	local kernel_version_string=$(uname -r)
	local kernel_version kernel_patchlevel kernel_sublevel kernel_extraversion
	local line commit minver errata rest v p s x have

	IFS=. read -r kernel_version kernel_patchlevel rest <<<$kernel_version_string
	IFS=- read -r kernel_sublevel kernel_extraversion <<<$rest
	kernel_sublevel=${kernel_sublevel%%[!0-9]*}
	kernel_extraversion=${kernel_extraversion%%[!0-9]*}

	! [[ $kernel_sublevel =~ ^[0-9]+$ ]] && unset $kernel_sublevel
	! [[ $kernel_extraversion =~ ^[0-9]+$ ]] && unset $kernel_extraversion

	[ "$ENVIRON_DEFAULT" != "yes" ] && return
	[ ! -f "$ERRATATXT" ] && return

	for line in $(grep -v '^#' "$ERRATATXT" | tr -d '[:blank:]' | cut -d: -f1,2); do
		commit=${line%:*}
		minver=${line#*:}

		errata="ERRATA_$commit"
		test -v $errata && continue

		IFS=. read -r v p rest <<<"$minver"
		IFS=- read -r s x <<<"$rest"
		s=${s%%[!0-9]*}
		x=${x%%[!0-9]*}

		if ! [[ $v =~ ^[0-9]+$ ]] || ! [[ $p =~ ^[0-9]+$ ]]; then
			echo "Bad minimum kernel version in $ERRATATXT, $minver"
			return 2
		fi
		! [[ $s =~ ^[0-9]+$ ]] && unset $s
		! [[ $x =~ ^[0-9]+$ ]] && unset $x

		if (( $kernel_version > $v ||
		      ($kernel_version == $v && $kernel_patchlevel > $p) )); then
			have=y
		elif (( $kernel_version == $v && $kernel_patchlevel == $p )); then
			if [ "$kernel_sublevel" ] && [ "$s" ]; then
				if (( $kernel_sublevel > $s )); then
					have=y
				elif (( $kernel_sublevel == $s )); then
					if [ "$kernel_extraversion" ] && [ "$x" ]; then
						if (( $kernel_extraversion >= $x )); then
							have=y
						else
							have=n
						fi
					elif [ "$x" ] && (( $x != 0 )); then
						have=n
					else
						have=y
					fi
				else
					have=n
				fi
			elif [ "$s" ] && (( $s != 0 )); then
				have=n
			else
				have=y
			fi
		else
			have=n
		fi
		eval export "$errata=$have"
	done
}

trap_exit_push ()
{
	local old_exit=$(trap -p EXIT | sed "s/^[^']*'//;s/'[^']*$//")
	trap -- "$1; $old_exit" EXIT
}

kvm_available ()
{
	[ -c /dev/kvm ] ||
		return 1

	[ "$HOST" = "$ARCH_NAME" ] ||
		( [ "$HOST" = aarch64 ] && [ "$ARCH" = arm ] ) ||
		( [ "$HOST" = x86_64 ] && [ "$ARCH" = i386 ] )
}

get_qemu_accelerator ()
{
	if [ "$ACCEL" = "kvm" ] && ! kvm_available; then
		echo "KVM is needed, but not available on this host" >&2
		return 2
	fi

	if [ "$ACCEL" ]; then
		echo $ACCEL
	elif kvm_available; then
		echo kvm
	else
		echo tcg
	fi
}
