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 "$@"