diff --git a/README b/README
index d5b07b3..4d73677 100644
--- a/README
+++ b/README
@@ -1,11 +1,12 @@
-parabola-imagebuilder
-=====================
+parabola-vmbootstrap
+====================
-This is a collection of scripts creating parabola images for use with qemu.
+This is a collection of scripts for creating and booting parabola virtual
+machine images for use with qemu.
-image creation
---------------
+virtual machine image creation
+------------------------------
To create a new virtual machine image, run
$> sudo ./create.sh
@@ -23,12 +24,27 @@ The creation is influenced by the following environment variables:
The created images are stored in the build/ directory.
-virtual machine start
----------------------
+virtual machine boot
+--------------------
-To boot a created virtual machine, run
- $> sudo ./boot.sh [path to created image]
+To boot a created virtual machine, run:
-The start.sh script assumes that you want a throwaway session, so it will start
-the virtual machine in snapshot mode, and changes during the session will be
-discarded.
+ $> ./pvmboot.sh [path to image] [additional qemu args...]
+
+the script will attempt to determine the architecture of the provided virtual
+machine image, and set the qemu executable and sane default flags for the qemu
+invocation automatically, including kvm acceleration, if available for the
+target architecture.
+
+additionally, the script will evaluate the DISPLAY environment variable to
+determine whether a graphical desktop environment is available, and will start
+the image in serial console mode if necessary. This behavior can be forced by
+unsetting DISPLAY before executing the script:
+
+ $> DISPLAY= ./pvmboot.sh [...]
+
+The default flags can be overwritten or extended, for example to allocate more
+memory to the machine, by specifying additional qemu parameters on the command
+line following the virtual machine image name:
+
+ $> DISPLAY= ./pvmboot [path to image] -m 2G
diff --git a/boot.sh b/boot.sh
deleted file mode 100755
index de44bc5..0000000
--- a/boot.sh
+++ /dev/null
@@ -1,171 +0,0 @@
-#!/bin/bash
- ##############################################################################
- # parabola-imagebuilder #
- # #
- # Copyright (C) 2017 Andreas Grapentin #
- # #
- # 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 3 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, see . #
- ##############################################################################
- # this is a convenience script to start a parabola VM using qemu
- ##############################################################################
-
-# common directories
-startdir="$(pwd)"
-export TOPBUILDDIR="$startdir"/build
-export TOPSRCDIR="$startdir"/src
-mkdir -p "$TOPBUILDDIR"
-chown "$SUDO_USER" "$TOPBUILDDIR"
-
-# shellcheck source=src/shared/common.sh
-. "$TOPSRCDIR"/shared/common.sh
-
-# sanity checks
-if [ "$(id -u)" -ne 0 ]; then
- die -e "$ERROR_INVOCATION" "must be root"
-fi
-
-# shellcheck source=src/qemu.sh
-. "$TOPSRCDIR"/qemu.sh
-
-check_kernel_arch() {
- echo -n "checking for kernel name ..."
- local kernel
- kernel=$(find "$1" -maxdepth 1 -type f -iname '*vmlinu*' | head -n1)
- [ -n "$kernel" ] || kernel=no
- echo "$(basename "$kernel")"
-
- [ "x$kernel" != "xno" ] || return
-
- # check if the kernel has an elf header and extract arch
-
- echo -n "checking for kernel elf header ... "
- set -o pipefail
- machine=$(readelf -h "$kernel" 2>/dev/null | grep Machine | awk '{print $2}') || machine=no
- set +o pipefail
- echo "$machine"
-
- [ "x$machine" != "xno" ] && return
-
- # check if the kernel arch can be gathered from objdump
-
- echo -n "checking for kernel binary header ... "
- set -o pipefail
- machine=$(objdump -f "$kernel" 2>/dev/null | grep architecture: | awk '{print $2}' | tr -d ',') \
- || machine=no
- set +o pipefail
- echo "$machine"
-
- [ "x$machine" != "xno" ] && return
-
- # no usable binary headers? maybe arm?
-
- echo -n "checking for ARM boot executable ... "
- local is_arm=no
- file "$kernel" | grep -q 'ARM boot executable' && is_arm=yes
- echo "$is_arm"
- [ "x$is_arm" == "xyes" ] && machine=ARM
-
- [ "x$machine" != "xno" ] && return
-
- # no idea; bail.
-
- error "unable to extract kernel arch from image"
- return "$ERROR_MISSING"
-}
-
-qemu_setargs_arm() {
- qemu_args+=(
- -machine vexpress-a9
- -cpu cortex-a9
- -m 1G
- -kernel "$1"/vmlinuz-linux-libre
- -dtb "$1"/dtbs/linux-libre/vexpress-v2p-ca9.dtb
- -initrd "$1"/initramfs-linux-libre.img
- --append "console=ttyAMA0 rw root=/dev/mmcblk0p3"
- -drive if=sd,driver=raw,cache=writeback,file="$2"
- )
-}
-
-qemu_setargs_riscv64() {
- qemu_args+=(
- -machine virt
- -m 2G
- -kernel "$1"/bbl
- -append "console=ttyS0 rw root=/dev/vda"
- -drive file="${3}p3",format=raw,id=hd0
- -device virtio-blk-device,drive=hd0
- -object rng-random,filename=/dev/urandom,id=rng0
- -device virtio-rng-device,rng=rng0
- -device virtio-net-device,netdev=usernet
- -netdev user,id=usernet
- )
-}
-
-qemu_setargs_ppc64() {
- qemu_args+=(
- -machine pseries
- -m 2G
- -kernel "$1"/vmlinuz-linux-libre
- -initrd "$1"/initramfs-linux-libre.img
- -append "console=ttyS0 rw root=/dev/sda3"
- -drive file="$2"
- )
-}
-
-qemu_setargs_i386() {
- qemu_setargs_x86_64 "$@"
-}
-
-qemu_setargs_x86_64() {
- qemu_args+=(
- -m 2G
- -kernel "$1"/vmlinuz-linux-libre
- -initrd "$1"/initramfs-linux-libre.img
- -append "console=ttyS0 rw root=/dev/sda3"
- -drive file="$2"
- )
-}
-
-boot_from_image() {
- [ -f "$1" ] || die "$1: image does not exist"
-
- local loopdev
- qemu_img_losetup "$1" || return
-
- # mount the boot partition
- mkdir -p "$TOPBUILDDIR"/mnt
- mount "${loopdev}p1" "$TOPBUILDDIR"/mnt || return
- trap_add "umount -R $TOPBUILDDIR/mnt" INT TERM EXIT
-
- local machine
- check_kernel_arch "$TOPBUILDDIR"/mnt || return
-
- case "$machine" in
- RISC-V) arch=riscv64 ;;
- PowerPC64) arch=ppc64 ;;
- ARM) arch=arm ;;
- i386) arch=i386 ;;
- i386:*) arch=x86_64 ;;
- *) error "unrecognized machine '$machine'"
- return "$ERROR_UNSPECIFIED" ;;
- esac
-
- #qemu_args=(-snapshot -nographic)
- qemu_args=(-nographic)
- "qemu_setargs_$arch" "$TOPBUILDDIR"/mnt "$1" "$loopdev"
- qemu_arch_is_foreign "$arch" || qemu_args+=(-enable-kvm)
- QEMU_AUDIO_DRV=none "qemu-system-$arch" "${qemu_args[@]}"
-}
-
-boot_from_image "$1" || die "boot failed"
diff --git a/src/pvmboot.sh b/src/pvmboot.sh
new file mode 100644
index 0000000..7196e5f
--- /dev/null
+++ b/src/pvmboot.sh
@@ -0,0 +1,197 @@
+#!/bin/bash
+###############################################################################
+# parabola-vmbootstrap -- create and start parabola virtual machines #
+# #
+# Copyright (C) 2017 - 2019 Andreas Grapentin #
+# #
+# 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 3 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, see . #
+###############################################################################
+
+# shellcheck source=/usr/lib/libretools/messages.sh
+. "$(librelib messages)"
+
+usage() {
+ print "usage: %s [-h] filename [args...]" "${0##*/}"
+ echo
+ prose " this script is designed to smartly boot a parabola GNU/Linux-libre
+ virtual machine with qemu. It takes the path to a virtual machine image
+ as parameter, and determines the architecture of that image. It sets
+ default qemu parameters for the target architecture, and determines
+ whether kvm acceleration is available."
+ echo
+ prose " the script also determines whether a graphical desktop environment
+ is available by evaluating the DISPLAY environment variable, and sets
+ default options accordingly."
+ echo
+ prose " the default qemu parameters can be overwritten and extended by adding
+ custom arguments after the image file name."
+ echo
+ echo "this script is developed as part of parabola-vmbootstrap."
+}
+
+pvm_mount() {
+ if ! file "$1" | grep -q ' DOS/MBR '; then
+ error "$1: does not seem to be a raw qemu image."
+ return "$EXIT_FAILURE"
+ fi
+
+ trap 'pvm_umount' INT TERM EXIT
+
+ workdir="$(mktemp -d -t pvm-XXXXXXXXXX)" || return
+ loopdev="$(sudo losetup -fLP --show "$1")" || return
+ sudo mount "$loopdev"p1 "$workdir" || return
+}
+
+pvm_umount() {
+ trap - INT TERM EXIT
+
+ [ -n "$workdir" ] && (sudo umount "$workdir"; rmdir "$workdir")
+ unset workdir
+ [ -n "$loopdev" ] && sudo losetup -d "$loopdev"
+ unset loopdev
+}
+
+pvm_probe_arch() {
+ local kernel
+ kernel=$(find "$workdir" -maxdepth 1 -type f -iname '*vmlinu*' | head -n1)
+ if [ -z "$kernel" ]; then
+ warning "%s: unable to find kernel binary" "$1"
+ return
+ fi
+
+ # attempt to get kernel arch from elf header
+ arch="$(readelf -h "$kernel" 2>/dev/null | grep Machine | awk '{print $2}')"
+ case "$arch" in
+ PowerPC64) arch=ppc64; return;;
+ RISC-V) arch=riscv64; return;;
+ *) arch="";;
+ esac
+
+ # attempt to get kernel arch from objdump
+ arch="$(objdump -f "$kernel" 2>/dev/null | grep architecture: | awk '{print $2}' | tr -d ',')"
+ case "$arch" in
+ i386) arch=i386; return;;
+ i386:*) arch=x86_64; return;;
+ *) arch="";;
+ esac
+
+ # attempt to get kernel arch from file magic
+ arch="$(file "$kernel")"
+ case "$arch" in
+ *"ARM boot executable"*) arch=arm; return;;
+ *) arch="";;
+ esac
+
+ # no more ideas; giving up.
+}
+
+pvm_native_arch() {
+ local arch
+ case "$1" in
+ arm*) arch=armv7l;;
+ *) arch="$1";;
+ esac
+
+ setarch "$arch" /bin/true 2>/dev/null || return
+}
+
+pvm_build_qemu_args() {
+ # if we're not running on X / wayland, disable graphics
+ if [ -z "$DISPLAY" ]; then qemu_args+=(-nographic); fi
+
+ # if we're running a supported arch, enable kvm
+ if pvm_native_arch "$2"; then qemu_args+=(-enable-kvm); fi
+
+ # otherwise, decide by target arch
+ case "$2" in
+ i386|x86_64|ppc64)
+ qemu_args+=(-m 1G "$1")
+ if [ -z "$DISPLAY" ]; then qemu_args+=(-append "console=ttyS0"); fi
+ # unmount the drive early
+ pvm_umount ;;
+ arm)
+ qemu_args+=(
+ -machine vexpress-a9
+ -cpu cortex-a9
+ -m 1G
+ -kernel "$workdir"/vmlinuz-linux-libre
+ -dtb "$workdir"/dtbs/linux-libre/vexpress-v2p-ca9.dtb
+ -initrd "$workdir"/initramfs-linux-libre.img
+ -append " rw root=/dev/mmcblk0p3"
+ -drive "if=sd,driver=raw,cache=writeback,file=$1")
+ if [ -z "$DISPLAY" ]; then qemu_args+=(-append " console=ttyAMA0"); fi ;;
+ riscv64)
+ qemu_args+=(
+ -machine virt
+ -m 1G
+ -kernel "$workdir"/bbl
+ -append " rw root=/dev/vda"
+ -drive "file=${loopdev}p3,format=raw,id=hd0"
+ -device "virtio-blk-device,drive=hd0"
+ -object "rng-random,filename=/dev/urandom,id=rng0"
+ -device "virtio-rng-device,rng=rng0"
+ -device "virtio-net-device,netdev=usernet"
+ -netdev "user,id=usernet")
+ if [ -z "$DISPLAY" ]; then qemu_args+=(-append " console=ttyS0"); fi ;;
+ *)
+ error "%s: unable to determine default qemu args" "$1"
+ return "$EXIT_FAILURE" ;;
+ esac
+}
+
+main() {
+ if [ "$(id -u)" -eq 0 ]; then
+ error "This program must be run as regular user"
+ exit "$EXIT_NOPERMISSION"
+ fi
+
+ # parse options
+ while getopts 'h' arg; do
+ case "$arg" in
+ h) usage; return "$EXIT_SUCCESS";;
+ *) usage >&2; exit "$EXIT_INVALIDARGUMENT";;
+ esac
+ done
+ local shiftlen=$(( OPTIND - 1 ))
+ shift $shiftlen
+ if [ "$#" -lt 1 ]; then usage >&2; exit "$EXIT_INVALIDARGUMENT"; fi
+
+ local imagefile="$1"
+ shift
+
+ if [ ! -e "$imagefile" ]; then
+ error "%s: file not found" "$imagefile"
+ exit "$EXIT_FAILURE"
+ fi
+
+ local workdir loopdev
+ pvm_mount "$imagefile" || exit
+
+ local arch
+ pvm_probe_arch "$imagefile" || exit
+
+ if [ -z "$arch" ]; then
+ error "%s: arch is unknown" "$imagefile"
+ exit "$EXIT_FAILURE"
+ fi
+
+ local qemu_args=()
+ pvm_build_qemu_args "$imagefile" "$arch" || exit
+ qemu_args+=("$@")
+
+ (set -x; qemu-system-"$arch" "${qemu_args[@]}")
+ pvm_umount
+}
+
+main "$@"