#!/bin/sh
# ============================================================
# qemu_launch.sh -- Build QEMU arguments and launch guest VM
#
# Reads from environment (exported by init):
#   ISO_PATH          -- absolute path to the ISO on mounted USB
#   LOG_DIR           -- absolute path to the log directory
#   OVMF_FULL_PATH    -- absolute path to OVMF firmware on ESP
#   EFI_MOUNT         -- ESP mount point
#   HIPERISO_TRACE_LEVEL -- standard|detailed|full|none
#   HIPERISO_GUEST_RAM   -- guest RAM in MB
#   HIPERISO_GUEST_CPUS  -- guest vCPU count
#   HIPERISO_DISPLAY     -- none|gtk|vnc
#   HIPERISO_VGA         -- none|std|virtio
#   KVM_AVAILABLE        -- 1 if KVM detected, 0 otherwise
#
# Exit codes:
#   0     QEMU ran and exited cleanly
#   2     ISO file not found
#   3     OVMF firmware not found
#   4     Insufficient RAM
#   127   QEMU binary missing
#   other QEMU's own exit code
# ============================================================

. /hiperiso-lib.sh

QEMU_BIN="/usr/bin/qemu-system-x86_64"

ISO_PATH="${ISO_PATH:?ISO_PATH not set}"
LOG_DIR="${LOG_DIR:?LOG_DIR not set}"

trap 'rm -f /tmp/hw_mon.in /tmp/hw_mon.out 2>/dev/null; for _pf in /tmp/hw_mon_reader.pid /tmp/hw_mon_writer.pid; do [ -f "$_pf" ] && kill "$(cat "$_pf" 2>/dev/null)" 2>/dev/null && rm -f "$_pf"; done' INT TERM

# ── Validate QEMU binary ─────────────────────────────────────
if [ ! -x "$QEMU_BIN" ]; then
    hiperiso_log "FATAL: QEMU binary not found at $QEMU_BIN"
    exit 127
fi

# ── Validate ISO exists ──────────────────────────────────────
if [ ! -f "$ISO_PATH" ]; then
    hiperiso_log "FATAL: ISO file not found: $ISO_PATH"
    exit 2
fi

# ── Resolve OVMF firmware ────────────────────────────────────
OVMF_FILE=""
for _candidate in \
    "${OVMF_FULL_PATH:-}" \
    "${EFI_MOUNT:-/mnt/efi}${OVMF_PATH}" \
    "/EFI/hiperiso/OVMF.fd" \
    "/usr/share/hiperiso/OVMF.fd" \
    "/usr/share/OVMF/OVMF_CODE.fd"; do
    if [ -n "$_candidate" ] && [ -f "$_candidate" ]; then
        OVMF_FILE="$_candidate"
        break
    fi
done

if [ -z "$OVMF_FILE" ]; then
    hiperiso_log "FATAL: OVMF firmware not found"
    hiperiso_log "  Searched ESP, initramfs, /usr/share/hiperiso/, /usr/share/OVMF/"
    exit 3
fi
hiperiso_log "Using OVMF firmware: $OVMF_FILE"

# ── Plugin: Secure Boot OVMF ─────────────────────────────────
if [ "${HIPERISO_SECURE_BOOT:-0}" = "1" ]; then
    _ovmf_secure="${EFI_MOUNT:-/mnt/efi}/EFI/hiperiso/OVMF_SECURE.fd"
    if [ -f "$_ovmf_secure" ]; then
        hiperiso_log "Plugin: Secure Boot requested, switching firmware to $_ovmf_secure"
        OVMF_FILE="$_ovmf_secure"
    else
        hiperiso_log "WARNING: Secure Boot requested but firmware not found: $_ovmf_secure"
    fi
fi

# ── Resolve trace events file ────────────────────────────────
_trace_level="${HIPERISO_TRACE_LEVEL:-standard}"
TRACE_EVENTS_FILE=""

if [ "$_trace_level" != "none" ]; then
    for _trace_file in \
        "${EFI_MOUNT:-/mnt/efi}${TRACE_EVENTS_DIR}/trace-${_trace_level}.events" \
        "/EFI/hiperiso/trace/trace-${_trace_level}.events"; do
        if [ -f "$_trace_file" ]; then
            TRACE_EVENTS_FILE="$_trace_file"
            hiperiso_log "Trace: level=${_trace_level} events=${TRACE_EVENTS_FILE}"
            break
        fi
    done
    if [ -z "$TRACE_EVENTS_FILE" ]; then
        hiperiso_log "WARNING: trace events file not found for level=${_trace_level}"
        hiperiso_log "  Tracing disabled for this session"
    fi
fi

# ── Verify sufficient RAM ────────────────────────────────────
_guest_ram="${HIPERISO_GUEST_RAM:-2048}"

if ! hiperiso_check_ram "$_guest_ram"; then
    hiperiso_log "FATAL: Insufficient RAM for guest"
    hiperiso_log "  Requested ${_guest_ram}MB guest + 512MB host overhead"
    hiperiso_log "  Available: $(awk '/^MemTotal:/ {printf "%d", $2/1024}' /proc/meminfo 2>/dev/null)MB"
    exit 4
fi

# ── Plugin: memdisk boot mode ────────────────────────────────
if [ "${HIPERISO_BOOT_MODE:-normal}" = "memdisk" ]; then
    hiperiso_log "Plugin: memdisk boot mode -- loading ISO into RAM"
    cat "$ISO_PATH" > /tmp/iso_memdisk
    ISO_PATH="/tmp/iso_memdisk"
fi

# ── Collect host hardware inventory ──────────────────────────
hiperiso_timing_mark "hw_collect_pre"
/hw_collect.sh pre

# ── Build QEMU argument list ─────────────────────────────────
hiperiso_timing_mark "qemu_args_built"
_guest_cpus="${HIPERISO_GUEST_CPUS:-2}"
_display="${HIPERISO_DISPLAY:-none}"
_vga="${HIPERISO_VGA:-std}"

if [ "${KVM_AVAILABLE:-1}" = "1" ]; then
    _machine="q35,accel=kvm"
    _cpu_model="host"
else
    _machine="q35"
    _cpu_model="qemu64"
    hiperiso_log "WARNING: KVM unavailable -- using TCG software emulation (very slow)"
fi

_cpu_arg="$_cpu_model"
if [ -n "${HIPERISO_CPU_FEATURES:-}" ]; then
    hiperiso_log "Plugin: CPU features ${HIPERISO_CPU_FEATURES}"
    _oldifs="$IFS"
    IFS=','
    for _feat in $HIPERISO_CPU_FEATURES; do
        _cpu_arg="${_cpu_arg},+${_feat}"
    done
    IFS="$_oldifs"
fi

set -- -machine "$_machine" -cpu "$_cpu_arg"

_OVMF_TMP="/tmp/OVMF.fd"
if ! cp "$OVMF_FILE" "$_OVMF_TMP" 2>/dev/null; then
    hiperiso_log "FATAL: Failed to copy OVMF firmware ($OVMF_FILE) to $_OVMF_TMP"
    exit 3
fi

set -- "$@" \
    -m "$_guest_ram" \
    -smp "$_guest_cpus" \
    -drive "file=${ISO_PATH},if=none,id=cd0,format=raw,media=cdrom,readonly=on" \
    -device ahci,id=ahci0 \
    -device ide-cd,drive=cd0,bus=ahci0.0,bootindex=1 \
    -drive "if=pflash,format=raw,readonly=on,file=${OVMF_FILE}" \
    -drive "if=pflash,format=raw,file=${_OVMF_TMP}" \
    -serial "file:${LOG_DIR}/serial.log" \
    -display "$_display" \
    -vga "$_vga" \
    -monitor "unix:${LOG_DIR}/monitor.sock,server,nowait" \
    -nodefaults \
    -no-reboot

if [ -n "$TRACE_EVENTS_FILE" ]; then
    set -- "$@" \
        -trace "events=${TRACE_EVENTS_FILE},file=${LOG_DIR}/trace.bin"
fi

# ── Plugin: persistence .dat ─────────────────────────────────
if [ -n "${HIPERISO_PERSISTENCE:-}" ]; then
    hiperiso_log "Plugin: persistence file ${HIPERISO_PERSISTENCE}"
    set -- "$@" \
        -drive "file=${DATA_MOUNT:-}${HIPERISO_PERSISTENCE},if=none,id=persist0,format=raw" \
        -device "virtio-blk-pci,drive=persist0"
fi

# ── Plugin: auto-install script ──────────────────────────────
_floppy_drives=""
_floppy_devices=""
if [ -n "${HIPERISO_AUTO_INSTALL:-}" ]; then
    hiperiso_log "Plugin: auto-install script ${HIPERISO_AUTO_INSTALL}"
    /make_floppy.sh /tmp/auto_install.img "${DATA_MOUNT:-}${HIPERISO_AUTO_INSTALL}"
    _floppy_drives="$_floppy_drives -drive file=/tmp/auto_install.img,if=none,id=floppy0,format=raw"
    _floppy_devices="driveA=floppy0"
fi

# ── Plugin: Driver Update Disk ───────────────────────────────
if [ -n "${HIPERISO_DUD:-}" ]; then
    hiperiso_log "Plugin: Driver Update Disk ${HIPERISO_DUD}"
    set -- "$@" \
        -drive "file=${DATA_MOUNT:-}${HIPERISO_DUD},if=none,id=dud0,format=raw,media=cdrom,readonly=on" \
        -device "ide-cd,drive=dud0,bus=ahci0.1"
fi

# ── Plugin: injection archive ────────────────────────────────
if [ -n "${HIPERISO_INJECTION:-}" ]; then
    hiperiso_log "Plugin: injection archive ${HIPERISO_INJECTION}"
    mkdir -p /tmp/injection
    tar xzf "${DATA_MOUNT:-}${HIPERISO_INJECTION}" -C /tmp/injection
    /make_floppy.sh /tmp/injection.img /tmp/injection/*
    if [ -n "$_floppy_devices" ]; then
        _floppy_drives="$_floppy_drives -drive file=/tmp/injection.img,if=none,id=floppy1,format=raw"
        _floppy_devices="$_floppy_devices,driveB=floppy1"
    else
        _floppy_drives="$_floppy_drives -drive file=/tmp/injection.img,if=none,id=floppy1,format=raw"
        _floppy_devices="driveA=floppy1"
    fi
fi

# Single isa-fdc controller for all floppies (QEMU allows only one)
if [ -n "$_floppy_devices" ]; then
    set -- "$@" $_floppy_drives -device "isa-fdc,$_floppy_devices"
fi

# ── Plugin: virtual TPM ──────────────────────────────────────
if [ "${HIPERISO_TPM:-0}" = "1" ]; then
    if command -v swtpm >/dev/null 2>&1; then
        hiperiso_log "Plugin: virtual TPM (swtpm) starting"
        mkdir -p /tmp/tpmstate
        swtpm socket --tpmstate dir=/tmp/tpmstate \
            --ctrl type=unixio,path=/tmp/swtpm.sock \
            --tpm2 --daemon
        set -- "$@" \
            -chardev "socket,id=tpm0,path=/tmp/swtpm.sock" \
            -tpmdev "emulator,id=tpm0,chardev=tpm0" \
            -device "tpm-crb,tpmdev=tpm0"
    else
        hiperiso_log "WARNING: TPM requested but swtpm binary not found"
    fi
fi

# ── Plugin: network packet capture ───────────────────────────
if [ "${HIPERISO_NET_DUMP:-0}" = "1" ]; then
    hiperiso_log "Plugin: network pcap capture enabled"
    set -- "$@" \
        -netdev "user,id=net0" \
        -device "virtio-net-pci,netdev=net0" \
        -object "filter-dump,id=netdump,netdev=net0,file=${LOG_DIR}/network.pcap"
fi

# ── Hardware inventory: monitor FIFO + pipe chardev ──────────
# Sets up /tmp/hw_mon.{in,out} FIFOs and launches background
# reader/writer processes.  The pipe chardev lets us send HMP
# commands (info qtree, info pci, etc.) to QEMU while it runs.
hiperiso_timing_mark "hw_monitor_setup"
/hw_collect.sh monitor
set -- "$@" \
    -chardev "pipe,id=hwmon,path=/tmp/hw_mon" \
    -mon "chardev=hwmon,mode=readline"

# ── Save the exact command line ──────────────────────────────
{
    printf '%s' "$QEMU_BIN"
    for _arg in "$@"; do
        printf ' %s' "$_arg"
    done
    printf '\n'
} > "${LOG_DIR}/qemu.cmdline"

hiperiso_log "QEMU command line saved to ${LOG_DIR}/qemu.cmdline"

# ── Launch QEMU ──────────────────────────────────────────────
hiperiso_timing_mark "qemu_started"
hiperiso_log "Starting QEMU..."
hiperiso_log "  Guest: ${_guest_ram}MB RAM, ${_guest_cpus} vCPU(s)"
hiperiso_log "  ISO:   $ISO_PATH"
hiperiso_log "  Video: display=${_display} vga=${_vga}"

"$QEMU_BIN" "$@"
_qemu_exit=$?

hiperiso_timing_mark "qemu_exited"

# ── Parse QEMU hardware inventory ────────────────────────────
/hw_collect.sh post

hiperiso_log "QEMU exited with code: $_qemu_exit"

case "$_qemu_exit" in
    0)
        hiperiso_log "Guest VM shut down cleanly"
        ;;
    1)
        hiperiso_log "QEMU reported an error (see serial.log for details)"
        ;;
    *)
        hiperiso_log "QEMU exited with non-zero code: $_qemu_exit"
        ;;
esac

exit "$_qemu_exit"
