#!/bin/sh
# Entrypoint of the build VM container. It packs /in into the job disk, boots
# the VM, and leaves the image at /out/image.tar.
#
# Exit codes: 0 image written, 1 build failed, 2 the VM did not run,
# 3 image over HF_VM_MAX_IMAGE_BYTES.
set -eu

cpus=${HF_VM_CPUS:-2}
mem=${HF_VM_MEMORY_MB:-2048}
disk=${HF_VM_DISK_BYTES:-21474836480}
max=${HF_VM_MAX_IMAGE_BYTES:-4294967296}

if [ ! -r /dev/kvm ] || [ ! -w /dev/kvm ]; then
	echo "hearthforge: /dev/kvm is missing or not writable in the build VM container"
	exit 2
fi

tar -c -f /work/job.tar -C /in .
truncate -s "$disk" /work/scratch.img
: > /out/image.tar

arch=$(uname -m)
case $arch in
x86_64)
	# acpi=off: QEMU puts a large initramfs over the microvm ACPI tables, and
	# the kernel hangs while it parses them.
	machine=microvm,accel=kvm,acpi=off,pit=off,pic=off,isa-serial=on,rtc=on
	console=ttyS0
	;;
aarch64)
	machine=virt,accel=kvm,gic-version=host
	console=ttyAMA0
	;;
*)
	echo "hearthforge: unsupported architecture $arch"
	exit 2
	;;
esac

# The serial numbers let the guest find its disks whatever order the
# virtio-mmio transports probe in.
qemu-system-"$arch" \
	-nodefaults -no-user-config -display none -no-reboot \
	-sandbox on,obsolete=deny,elevateprivileges=deny,spawn=deny,resourcecontrol=deny \
	-machine "$machine" -cpu host -smp "$cpus" -m "$mem" \
	-kernel /vm/vmlinuz -initrd /vm/initramfs.gz \
	-append "console=$console quiet panic=-1 reboot=t" \
	-serial stdio \
	-drive if=none,id=job,file=/work/job.tar,format=raw,readonly=on \
	-device virtio-blk-device,drive=job,serial=hfjob \
	-drive if=none,id=scratch,file=/work/scratch.img,format=raw,cache=unsafe,discard=unmap \
	-device virtio-blk-device,drive=scratch,serial=hfscratch \
	-netdev user,id=net -device virtio-net-device,netdev=net \
	-device virtio-rng-device \
	-device virtio-serial-device \
	-chardev file,id=image,path=/out/image.tar \
	-device virtserialport,chardev=image,name=hf.image &
qemu=$!

# The guest controls how much it writes. Polling lets it overshoot by about
# one second of writes, which the engine's disk absorbs.
over=0
while kill -0 "$qemu" 2>/dev/null; do
	if [ "$(wc -c < /out/image.tar)" -gt "$max" ]; then
		over=1
		# QEMU may have exited since the check.
		kill "$qemu" 2>/dev/null || true
		break
	fi
	sleep 1
done
status=0
wait "$qemu" || status=$?

size=$(wc -c < /out/image.tar)
if [ "$over" = 1 ] || [ "$size" -gt "$max" ]; then
	: > /out/image.tar
	echo "hearthforge: the image is larger than $max bytes"
	exit 3
fi
if [ "$status" != 0 ]; then
	echo "hearthforge: QEMU exited with status $status"
	exit 2
fi
[ "$size" -gt 0 ] || exit 1
