#!/usr/bin/bash
# -*- shell-script -*-
# qemu-sanity-check
# Copyright (C) 2013-2024 Red Hat Inc.
# src/qemu-sanity-check.  Generated from qemu-sanity-check.in by configure.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

prefix="/usr"
exec_prefix="/usr"
initrd="/usr/lib64/qemu-sanity-check/initrd"

arch="$(uname -m)"
canonical_arch="$(uname -m | sed 's/i[456]86/i386/')"

verbose=no
timeout=10m
accel=kvm:tcg
memory=768
console=ttyS0
machine=

# Choosing a good CPU default is a minefield.  Ideally '-cpu max' is
# supposed to choose the best CPU type, but it notably fails on RISC-V
# (not supported) and x86_64+TCG (enables LA57 which TCG does not
# emulate).  Default minimum CPU will not work on RHEL 10, since that
# requires at least x86_64-v3.
cpu=max

# Default machine and CPU type depends on arch.  You can override this
# using -m|--machine and --cpu options.
case "$canonical_arch" in
    arm*)
	console=ttyAMA0
	machine=virt ;;
    aarch*)
	console=ttyAMA0
	machine=virt ;;
    s390*)
	console=ttysclp0 ;;
esac

# Handle command line parsing.

function usage {
    echo "qemu-sanity-check [options]"
    echo "Options:"
    echo "  --help               Display this help"
    echo "  --accel=[kvm|tcg]    Force KVM or software emulation"
    echo "  --cpu=cpu            Set CPU"
    echo "  -i|--initrd=initrd   Set location of initramfs"
    echo "  -k|--kernel=vmlinuz  Set location of kernel"
    echo "  -m|--machine=machine Set machine type"
    echo "  -q|--qemu=qemu       Set location of qemu/KVM binary"
    echo "  -t|--timeout=timeout Set the timeout"
    echo "  -v|--verbose         Verbose output"
    echo "  -V|--version         Display version and exit"
    exit 0
}

TEMP=$(getopt \
    -o i:k:m:q:t:vV \
    --long help \
    --long accel: \
    --long cpu: \
    --long initrd: \
    --long kernel: \
    --long machine: \
    --long qemu: \
    --long timeout: \
    --long verbose \
    --long version \
    -n 'qemu-sanity-check' -- "$@")
if [ $? != 0 ]; then exit 2; fi
eval set -- "$TEMP"

while true; do
    case "$1" in
        --help)
            usage
            ;;
        --accel)
            accel="$2"
            shift 2
            ;;
	--cpu)
	    cpu="$2"
	    shift 2
	    ;;
        -i|--initrd)
            initrd="$2"
            shift 2
            ;;
        -k|--kernel)
            kernel="$2"
            shift 2
            ;;
        -m|--machine)
            machine="$2"
            shift 2
            ;;
        -q|--qemu)
            qemu="$2"
            shift 2
            ;;
        -t|--timeout)
            timeout="$2"
            shift 2
            ;;
        -v|--verbose)
            verbose=yes
            shift
            ;;
        -V|--version)
            echo "qemu-sanity-check 1.1.6"
            exit 0
            ;;
        --)
            shift
            break
            ;;
        *)
            echo "$0: internal error parsing options: $1"
            exit 2
            ;;
    esac
done

# Locate initrd.
if [ ! -r "$initrd" ]; then
    echo "$0: cannot find 'initrd', try using --initrd=/path/to/initrd"
    echo "If you are running qemu-sanity-check without installing, then do:"
    echo "    $0 --initrd=./initrd $@"
    echo "The default path is '/usr/lib64/initrd'."
    exit 2
fi

# Locate kernel if not specified.
if [ -z "$kernel" ]; then
    shopt -s nullglob
    if [ "$verbose" = "yes" ]; then
        echo "all kernels:"
        ls -1dvr /lib/modules/*/vmlinuz /boot/vmlinuz-*.$arch*
    fi
    kernel="$(ls -1dvr /lib/modules/*/vmlinuz /boot/vmlinuz-*.$arch* 2>/dev/null | grep -v xen | head -1)"
    if [ -z "$kernel" ]; then
        echo "$0: cannot find a Linux kernel in /boot"
        echo "Choose a kernel to test using --kernel=/path/to/vmlinuz"
        exit 2
    fi
    shopt -u nullglob
fi
if [ ! -r "$kernel" ]; then
    echo "$0: kernel $kernel is not readable"
    exit 2
fi

# Locate qemu if not specified.
if [ -z "$qemu" ]; then
    if [ "$verbose" = "yes" ]; then
        echo "all qemus:" /usr/libexec/qemu-kvm
    fi
    for q in /usr/libexec/qemu-kvm; do
        if "$q" --help >/dev/null 2>&1; then
            qemu="$q"
            break
        fi
    done
    if [ -z "$qemu" ]; then
        echo "$0: cannot find a qemu binary on the \$PATH"
        echo "Choose a qemu binary to test using --qemu=/path/to/qemu"
        exit 2
    fi
fi

# Choose a temporary file for the output.
test_output="$(mktemp --suff=.out)"

# Generate the parameters for the qemu command.
declare -a argv
i=0
argv[$((i++))]="$qemu"
argv[$((i++))]="-display"
argv[$((i++))]="none"
argv[$((i++))]="-no-user-config"
argv[$((i++))]="-nodefaults"
argv[$((i++))]="-machine"
argv[$((i++))]="$machine${machine:+,}accel=$accel"
if [ "$cpu" != "" ]; then
    argv[$((i++))]="-cpu"
    argv[$((i++))]="$cpu"
fi
argv[$((i++))]="-m"
argv[$((i++))]="$memory"
argv[$((i++))]="-no-reboot"
argv[$((i++))]="-serial"
argv[$((i++))]="file:$test_output"
argv[$((i++))]="-kernel"
argv[$((i++))]="$kernel"
argv[$((i++))]="-initrd"
argv[$((i++))]="$initrd"
argv[$((i++))]="-append"
argv[$((i++))]="console=$console oops=panic panic=-1"

if [ "$verbose" = "yes" ]; then
    echo "${argv[@]}"
fi

# Run the command.
timeout "$timeout" "${argv[@]}"
r="${PIPESTATUS[0]}"
if [ $r -eq 124 ]; then
    cat "$test_output"
    echo "$0: error: test $kernel on $qemu: timed out"
    rm "$test_output"
    exit 1
elif [ $r -ne 0 ]; then
    cat "$test_output"
    echo "$0: error: test $kernel on $qemu: failed"
    rm "$test_output"
    exit 1
fi

if [ "$verbose" = "yes" ]; then
    cat "$test_output"
fi

# Check if there was a kernel panic.  Note that oops=panic is set
# which will force a reboot for any oops condition.
if grep -sq "Kernel panic - not syncing" "$test_output"; then
    cat "$test_output"
    echo "$0: error: test $kernel on $qemu: kernel panic seen"
    rm "$test_output"
    exit 1
fi

# Verify that userspace was reached.
if ! grep -sq "initrd started up OK" "$test_output"; then
    cat "$test_output"
    echo "$0: error: test $kernel on $qemu: init process did not start up"
    rm "$test_output"
    exit 1
fi

# Successful.
rm "$test_output"
exit 0
