From 4872be99ac4b06ece60a63b87ac0a200e2b9d014 Mon Sep 17 00:00:00 2001 From: bill-auger Date: Sat, 8 Feb 2020 18:25:10 -0500 Subject: [PATCH] improve fs detection - factor out common functions --- README | 53 ++++++--- src/pvm-common.sh.inc | 264 ++++++++++++++++++++++++++++++++++++++++++ src/pvm2tarball.sh | 115 ++++-------------- src/pvmboot.sh | 226 ++++++++++++++---------------------- src/pvmbootstrap.sh | 246 +++++++++++++++++++-------------------- 5 files changed, 522 insertions(+), 382 deletions(-) create mode 100644 src/pvm-common.sh.inc diff --git a/README b/README index 0b2c077..7e209bb 100644 --- a/README +++ b/README @@ -26,6 +26,8 @@ the script will emit a warning and ask for confirmation to proceed. The creation can be influenced by providing one or more of the following options to pvmbootstrap: + -b -- Select one of the pre-defined package-sets described below + (default: 'standard') -H -- Enable a hook to customize the created image. This can be the path to a custom script, or one of the predefined hooks described below. The VM will boot the newly created image, @@ -39,16 +41,21 @@ options to pvmbootstrap: -O -- Bootstrap an openrc system instead of a systemd one -p -- Specify additional packages to be installed in the VM image. This option can be specified multiple times. - -s -- Set the size (in GB) of the VM image (minimum: 1, default: 64) + -s -- Set the size (in MB) of the root partition (default: 32000). + If this is 0 (or less than the requires), + the VM image will be the smallest size possible, + fit to the ; and any -p will be ignored. -S -- Set the size (in MB) of the swap partition (default: 0) -The creation hooks currently supported are: +Pre-defined package-sets: + minimal: base + standard: base parabola-base + devel: base parabola-base base-devel -'ethernet-dhcp': - - This hook will setup ethernet in the VM by enabling systemd-resolved and - openresolv properly, as well as creating and enabling a systemd-networkd - configuration. +Pre-defined hooks: + ethernet-dhcp: This hook will setup ethernet in the VM by enabling + systemd-resolved and openresolv properly, as well as creating + and enabling a systemd-networkd configuration. (systemd only) -------------------- @@ -59,10 +66,23 @@ To boot a created virtual machine, run: $> pvmboot [options] [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. +The script will attempt to determine the architecture and partition layout +of the provided virtual machine image, and set the qemu executable and sane +default flags for the qemu invocation automatically; and will enable KVM +acceleration, if available for the target architecture. + +The pvmbootstrap script creates a boot partition formatted with either the +vfat or ext2 filesystems, and a root partition formatted with the ext4 +filesystems. If the specified image was not created using pvmbootstrap, the +partition detection will fail unless the image coforms to this expected schema. +The first vfat or ext2 filesystem detected, will be considered as the boot +partition; and the first ext4 filesystem detected, will be considered as the +root partition. + +The default kernel installed by pvmbootstrap is 'linux-libre'. +The -k option ca be used to specify an alternate kernel to boot. + + $> pvmboot -k linux-libre-lts ./an.img Additionally, the script will evaluate the DISPLAY environment variable to determine whether a graphical desktop environment is available, and will start @@ -71,12 +91,7 @@ unsetting DISPLAY before executing the script: $> DISPLAY= pvmboot ./an.img -The default kernel installed by pvmbootstrap is 'linux-libre'. -The -k option ca be used to specify an alternate kernel to boot. - - $> pvmboot -k linux-libre-lts ./an.img - -When the VM will boot into graphical mode, the serial console can be redirected +If qemu boots into graphical mode, the serial console can be redirected to the host console by passing the -r option. $> pvmboot -r ./an.img @@ -104,5 +119,5 @@ pvmbootstrap always creates a /boot partition. for example, to generate the parabola armv7h release tarball: $> img_filename=parabola-systemd-cli-armv7h-tarball-$(date +%Y.%m).img - $> pvmbootstrap -s 1 -H ethernet-dhcp $img_filename armv7h - $> pvm2tarball $img_filename + $> pvmbootstrap -b minimal -H ethernet-dhcp -s 0 -S0 $img_file armv7h + $> pvm2tarball $img_file diff --git a/src/pvm-common.sh.inc b/src/pvm-common.sh.inc new file mode 100644 index 0000000..53c46b0 --- /dev/null +++ b/src/pvm-common.sh.inc @@ -0,0 +1,264 @@ +# readonly DATA_IMG=./pvmdata.img # optional large qemu disk +readonly THIS_DIR="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" +readonly UNPRIVILEGED_ERR_MSG="This program must be run as a regular user" +readonly MOUNTS_ERR_MSG="some PVM mountpoints are mounted - possibly orphans from a previous run - unmount them first" +readonly MOUNTVARS_ERR_MSG="FIXME: pvm_setup_loopdev() was called in an improper state - one of [ \$bootdir , \$workdir , \$loopdev ] is already set" + + +# shellcheck source=/usr/lib/libretools/messages.sh +source "$(librelib messages)" + + +pvm_get_pvmboot_cmd() +{ + local intree_cmd=$THIS_DIR/pvmboot.sh + local installed_cmd='pvmboot' + + [[ -f "$intree_cmd" ]] && echo "$intree_cmd" && return "$EXIT_SUCCESS" + type -p $installed_cmd &>/dev/null && echo "$installed_cmd" && return "$EXIT_SUCCESS" + error "can not find pvmboot" && return "$EXIT_FAILURE" +} + +pvm_get_hook() # ( hook_name ) +{ + local hook_name=$1 + local intree_dir=$THIS_DIR/hooks + local installed_dir=/usr/lib/parabola-vmbootstrap + local hook_filename=hook-$hook_name.sh + local locations=( "$intree_dir/$hook_filename" "$installed_dir/$hook_filename" "$hook_name" '' ) + local location + + for location in "${locations[@]}" ; do [[ -f "$location" ]] && break ; done ; + [[ "$location" ]] && echo "$location" || warning "no such hook: '%s'" "$hook_name" + [[ "$location" ]] && return "$EXIT_SUCCESS" || return "$EXIT_FAILURE" +} + +pvm_find_part_n() # ( imagefile fs_types ) , sets: $part_n +{ + local imagefile="$1" ; shift ; + local fs_types="$@" + + # try locating the partition by filesystem type + for fs_type in $fs_types + do local part_data=$(parted "$imagefile" print 2> /dev/null | grep $fs_type | head -n 1) + part_n=$( echo $part_data | cut -d ' ' -f 1 ) + + ! [[ "$part_n" =~ ^[0-9]+$ ]] && part_n='' || break + done + + [[ "$part_n" =~ ^[0-9]+$ ]] && return "$EXIT_SUCCESS" || return "$EXIT_FAILURE" +} + +pvm_find_boot_part_n() # ( imagefile ) , sets: boot_part_n +{ + local imagefile="$1" + local part_n + + pvm_find_part_n "$imagefile" fat32 ext2 || return "$EXIT_FAILURE" + + boot_part_n=$part_n + + return "$EXIT_SUCCESS" +} + +pvm_find_root_part_n() # ( imagefile ) , sets: root_part_n +{ + local imagefile="$1" + local part_n + + pvm_find_part_n "$imagefile" ext4 || return "$EXIT_FAILURE" + + root_part_n=$part_n + + return "$EXIT_SUCCESS" +} + +pvm_check_unprivileged() # exits on failure +{ + [[ "$(id -u)" -eq 0 ]] && error "$UNPRIVILEGED_ERR_MSG" && exit "$EXIT_NOPERMISSION" +} + +pvm_native_arch() # ( arch ) +{ + local arch=$1 + local native_arch=$( [[ "$arch" =~ arm.* ]] && echo 'armv7l' || echo "$arch" ) + + setarch "$native_arch" /bin/true 2>/dev/null && return "$EXIT_SUCCESS" || \ + return "$EXIT_FAILURE" +} + +pvm_check_file_exists_writable() # (file_path [ is_error_if_not_exists ]) +{ + local file_path="$1" + local is_error_if_not_exists=$( [[ "$2" == 'true' ]] && echo 1 || echo 0 ) + + if [[ -e "$file_path" ]] + then if [[ -w "$file_path" ]] + then return "$EXIT_SUCCESS" + else error "file exists but is not writable: '%s'" "$file_path" + return "$EXIT_FAILURE" + fi + elif (( ! $is_error_if_not_exists )) + then return "$EXIT_SUCCESS" + else error "no such file: %s" "$file_path" + return "$EXIT_FAILURE" + fi +} + +pvm_check_file_exists_and_writable() # (file_path) +{ + local file_path="$1" + + pvm_check_file_exists_writable $file_path true && return "$EXIT_SUCCESS" || \ + return "$EXIT_FAILURE" +} + +pvm_check_file_not_exists_or_writable() # (file_path) +{ + local file_path="$1" + + pvm_check_file_exists_writable $file_path && return "$EXIT_SUCCESS" || \ + return "$EXIT_FAILURE" +} + +pvm_prompt_clobber_file() # (file_path) +{ + local file_path="$1" + + if pvm_check_file_not_exists_or_writable "$file_path" + then if [[ -e "$file_path" ]] + then warning "file exists: '%s'\nContinue? [y/N]" "$file_path" + read -p " " -n 1 -r ; echo ; + + [[ $REPLY =~ ^[Yy]$ ]] || return "$EXIT_FAILURE" + rm -f "$file_path" || return "$EXIT_FAILURE" + fi + return "$EXIT_SUCCESS" + else return "$EXIT_FAILURE" + fi +} + +pvm_setup_loopdev() # assumes: $imagefile , sets: $bootdir $workdir $loopdev , traps: INT TERM EXIT +{ + if file "$imagefile" | grep -Eq ': (data|DOS/MBR )'; then + if [[ -z "${bootdir}${workdir}${loopdev}" ]]; then + pvm_check_no_mounts && msg "creating loopback devices" || return "$EXIT_FAILURE" + else + error "$MOUNTVARS_ERR_MSG" + return "$EXIT_FAILURE" + fi + else + error "not a raw qemu image: '%s'" "$imagefile" + return "$EXIT_FAILURE" + fi + + trap 'pvm_cleanup' INT TERM EXIT + + # setup the loopback device + bootdir="$(mktemp -d -t pvm-bootfs-XXXXXXXXXX)" || return "$EXIT_FAILURE" + workdir="$(mktemp -d -t pvm-rootfs-XXXXXXXXXX)" || return "$EXIT_FAILURE" + loopdev="$(sudo losetup -fLP --show "$imagefile")" || return "$EXIT_FAILURE" + + return "$EXIT_SUCCESS" +} + +pvm_mount() # assumes: $imagefile $loopdev $bootdir $workdir +{ + pvm_setup_loopdev || return "$EXIT_FAILURE" # sets: $bootdir $workdir $loopdev + + # find boot and root filesystem partitions + local boot_part_n root_part_n + pvm_find_boot_part_n "$imagefile" || return "$EXIT_FAILURE" # sets: $boot_part_n + pvm_find_root_part_n "$imagefile" || return "$EXIT_FAILURE" # sets: $root_part_n + + # mount boot and root filesystems + msg "mounting image filesystems" + sudo mount "$loopdev"p$boot_part_n "$bootdir" || return "$EXIT_FAILURE" + sudo mount "$loopdev"p$root_part_n "$workdir" || return "$EXIT_FAILURE" + + return "$EXIT_SUCCESS" +} + +pvm_umount() # unsets: $bootdir $workdir +{ + [[ "${bootdir}${workdir}${loopdev}" ]] && msg "un-mounting image filesystems" + + (sudo umount "$workdir"/boot && rmdir "$workdir") 2> /dev/null + (sudo umount "$bootdir" && rmdir "$bootdir") 2> /dev/null + (sudo umount "$workdir" && rmdir "$workdir") 2> /dev/null + + unset bootdir + unset workdir +} + +pvm_cleanup() # unsets: $loopdev , untraps: INT TERM EXIT +{ + trap - INT TERM EXIT + + pvm_umount + sudo losetup -d "$loopdev" + pvm_check_no_mounts || return "$EXIT_FAILURE" + + unset loopdev + + return "$EXIT_SUCCESS" +} + +pvm_check_no_mounts() # assumes: $imagefile +{ + local n_pvm_mounts=$( mount | grep /tmp/pvm | wc --lines ) + local n_loop_devs=$( sudo losetup --associated $imagefile | wc --lines ) + local are_any_mounts=$( (( $n_pvm_mounts + $n_loop_devs )) && echo 1 || echo 0 ) + + (( $are_any_mounts )) && error "$MOUNTS_ERR_MSG" && return "$EXIT_FAILURE" || \ + return "$EXIT_SUCCESS" +} + +pvm_probe_arch() # assumes: $bootdir $workdir $imagefile , sets: $arch +{ + msg "detecting CPU architecture for image" + + local kernel=$(find "$bootdir" -maxdepth 1 -type f -iname '*vmlinu*' | head -n1) + local guest_arch + + if [ -n "$kernel" ]; then + msg2 "found kernel binary: %s" "$kernel" + else + warning "%s: unable to find kernel binary" "$imagefile" + return "$EXIT_FAILURE" + fi + + guest_arch="$(readelf -h "$workdir"/bin/true 2>/dev/null | \ + grep Machine | sed 's|[^:]*:\s*\([^:,]*\).*|\1|')" + + case "$guest_arch" in + ARM ) arch=armv7h ;; + i386|i386:*|*\ 80386) arch=i686 ;; + PowerPC64 ) arch=ppc64le ;; + RISC-V ) arch=riscv64 ;; + x86_64|*\ X86-64 ) arch=x86_64 ;; + * ) arch='' ;; + esac + + if [[ "$arch" ]]; then + msg2 "detected guest \`/bin/true\` arch: '%s'=>'%s'" "$guest_arch" "$arch" + return "$EXIT_SUCCESS" + else + error "image arch is unknown: '%s'" "$guest_arch" + return "$EXIT_FAILURE" + fi +} + +pvm_boot() # ( imagefile qemu_args ) +{ + local imagefile="$1" ; shift ; + local qemu_args=(-no-reboot $@) + local pvmboot_cmd=$(pvm_get_pvmboot_cmd) + local was_error=$? + + [[ "$pvmboot_cmd" ]] || return $EXIT_FAILURE + + DISPLAY='' "$pvmboot_cmd" "$imagefile" "${qemu_args[@]}" ; was_error=$? ; + + (( ! $was_error )) && return $EXIT_SUCCESS || return $EXIT_FAILURE +} diff --git a/src/pvm2tarball.sh b/src/pvm2tarball.sh index 3d2796a..dfb3113 100755 --- a/src/pvm2tarball.sh +++ b/src/pvm2tarball.sh @@ -19,10 +19,9 @@ # along with this program. If not, see . # ############################################################################### -# shellcheck source=/usr/lib/libretools/messages.sh -source "$(librelib messages)" -usage() { +usage() +{ print "USAGE:" print " pvm2tarball [-h] [-o ] " echo @@ -41,78 +40,16 @@ usage() { echo " " } -pvm_mount() { - if file "$imagefile" | grep -q ' DOS/MBR '; then - msg "mounting filesystems" - else - error "%s: does not seem to be a raw qemu image." "$imagefile" - return "$EXIT_FAILURE" - fi - - trap 'pvm_umount' INT TERM EXIT - - workdir="$(mktemp -d -t pvm-XXXXXXXXXX)" || return - loopdev="$(sudo losetup -fLP --show "$imagefile")" || return - - # find the root partition - local part rootpart bootpart - for part in "$loopdev"p*; do - sudo mount "$part" "$workdir" || continue - if [ -f "$workdir"/etc/fstab ]; then - rootpart="$part" - break - fi - sudo umount "$workdir" - done - - if [ -n "$rootpart" ]; then - msg "found root filesystem partition: %s" "$rootpart" - else - error "%s: unable to determine root partition." "$imagefile" - return "$EXIT_FAILURE" - fi - - # find the boot partition - if (( $(find /boot/ -name initramfs-* | wc -l) > 0 )) && \ - (( $(find /boot/ -name vmlinuz-* | wc -l) > 0 )); then - msg "found /boot on root filesystem partition" - else - bootpart="$(findmnt -senF "$workdir"/etc/fstab /boot | awk '{print $2}')" - - if [ -n "$bootpart" ]; then - # mount and be happy - msg "found boot filesystem partition: %s" "$bootpart" - sudo mount "$bootpart" "$workdir"/boot || return "$EXIT_FAILURE" - else - error "%s: unable to determine boot filesystem partition." "$imagefile" - return "$EXIT_FAILURE" - fi - fi -} - -pvm_umount() { - msg "un-mounting filesystems" - - trap - INT TERM EXIT - - [ -n "$workdir" ] && (sudo umount -R "$workdir"; rmdir "$workdir") - unset workdir - [ -n "$loopdev" ] && sudo losetup -d "$loopdev" - unset loopdev -} - -main() { - if [ "$(id -u)" -eq 0 ]; then - error "This program must be run as a regular user" - exit "$EXIT_NOPERMISSION" - fi +main() +{ + pvm_check_unprivileged # exits on failure # parse options - local output + local outfile while getopts 'ho:' arg; do case "$arg" in h) usage; return "$EXIT_SUCCESS";; - o) output="$OPTARG";; + o) outfile="$OPTARG";; *) error "invalid argument: %s\n" "$arg"; usage >&2; exit "$EXIT_INVALIDARGUMENT";; esac done @@ -124,29 +61,16 @@ main() { image_filename="$(basename "$imagefile")" shift - # check for input file presence - if [ ! -e "$imagefile" ]; then - error "%s: file not found" "$imagefile" - exit "$EXIT_FAILURE" - fi - # determine output file - [ -n "$output" ] || output="${image_filename%.img*}.tar.gz" + [ -n "$outfile" ] || outfile="$(dirname $imagefile)/${image_filename%.img*}.tar.gz" - # check for output file presence - if [ -e "$output" ]; then - warning "%s: file exists. Continue? [y/N]" "$output" - read -p " " -n 1 -r - echo - if [[ ! $REPLY =~ ^[Yy]$ ]]; then - exit "$EXIT_FAILURE" - fi - rm -f "$output" || exit - fi + # ensure that the image file exists, prompt to clobber existing output file + pvm_check_file_exists_and_writable "$imagefile" || exit "$EXIT_FAILURE" + pvm_prompt_clobber_file "$outfile" || exit "$EXIT_FAILURE" # mount the root filesystem - local workdir loopdev - pvm_mount || exit + local bootdir workdir loopdev + pvm_mount || exit "$EXIT_FAILURE" # assumes: $imagefile , sets: $loopdev $bootdir $workdir # tar the root filesystem, excluding unneeded things # HACKING: @@ -156,7 +80,7 @@ main() { # # `tar -tf | sort` msg "imploding tarball" - sudo tar -c -f "$output" -C "$workdir" -X - . << EOF + sudo tar -c -f "$outfile" -C "$workdir" -X - . << EOF ./boot/lost+found ./etc/.updated ./etc/pacman.d/gnupg @@ -169,10 +93,15 @@ main() { EOF # give the archive back to the user - sudo chown "$(id -u)":"$(id -g)" "$output" + sudo chown "$(id -u)":"$(id -g)" "$outfile" # cleanup - pvm_umount + pvm_cleanup } -main "$@" + +if source /usr/lib/parabola-vmbootstrap/pvm-common.sh.inc 2> /dev/null || \ + source "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")"/pvm-common.sh.inc 2> /dev/null +then main "$@" +else echo "can not find pvm-common.sh.inc" && exit 1 +fi diff --git a/src/pvmboot.sh b/src/pvmboot.sh index 422b8d8..a403910 100755 --- a/src/pvmboot.sh +++ b/src/pvmboot.sh @@ -19,8 +19,6 @@ # along with this program. If not, see . # ############################################################################### -# shellcheck source=/usr/lib/libretools/messages.sh -source "$(librelib messages)" readonly DEF_KERNEL='linux-libre' # ASSERT: must be 'linux-libre', per 'parabola-base' readonly DEF_RAM_MB=1000 @@ -29,14 +27,16 @@ Kernel=$DEF_KERNEL RedirectSerial=0 -usage() { +usage() +{ print "USAGE:" print " pvmboot [-h] [-k ] [-r] [qemu-args ...]" echo prose "Determine the architecture of and boot it using qemu. is assumed to be a valid, raw-formatted parabola virtual machine image, ideally - created using pvmbootstrap. The started instances are assigned - ${DEF_RAM_MB}MB of RAM and one SMP core." + created using pvmbootstrap. If the image was not created using pvmbootstrap, + the boot partition must be vfat or ext2, and the root partition must be ext4 + The machine instance is assigned ${DEF_RAM_MB}MB of RAM and one SMP core." echo prose "When a graphical desktop environment is available, start the machine normally, otherwise append -nographic to the qemu options. This behavior @@ -63,176 +63,120 @@ usage() { echo " " } -pvm_mount() { - if ! file "$imagefile" | grep -q ' DOS/MBR '; then - error "%s: does not seem to be a raw qemu image." "$imagefile" - return "$EXIT_FAILURE" - fi - msg "mounting filesystems" - trap 'pvm_umount' INT TERM EXIT - - workdir="$(mktemp -d -t pvm-XXXXXXXXXX)" || return "$EXIT_FAILURE" - loopdev="$(sudo losetup -fLP --show "$imagefile")" || return "$EXIT_FAILURE" - sudo mount "$loopdev"p1 "$workdir" || \ - sudo mount "$loopdev"p2 "$workdir" || return "$EXIT_FAILURE" -} - -pvm_umount() { - trap - INT TERM EXIT - - [ -n "$workdir" ] && (sudo umount "$workdir"; rmdir "$workdir") - [ -n "$loopdev" ] && sudo losetup -d "$loopdev" - unset workdir - 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" "$imagefile" - return "$EXIT_FAILURE" - fi - - # attempt to get kernel arch from elf header - arch="$(readelf -h "$kernel" 2>/dev/null | grep Machine | awk '{print $2}')" +pvm_guess_qemu_cmd() # assumes: $arch , sets: $qemu_cmd +{ case "$arch" in - PowerPC64) arch=ppc64 ; return "$EXIT_SUCCESS" ;; - RISC-V ) arch=riscv64 ; return "$EXIT_SUCCESS" ;; - * ) arch="" ;; + armv7h ) qemu_cmd="qemu-system-arm" ;; + i686 ) qemu_cmd="qemu-system-i386" ;; + ppc64le) qemu_cmd="qemu-system-ppc64" ;; + riscv64) qemu_cmd="qemu-system-riscv64" ;; + x86_64 ) qemu_cmd="qemu-system-x86_64" ;; + * ) error "unknown image arch: '%s'" "$arch" ; return "$EXIT_FAILURE" ;; 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 "$EXIT_SUCCESS" ;; - i386:*) arch=x86_64 ; return "$EXIT_SUCCESS" ;; - * ) arch="" ;; - esac - - # attempt to get kernel arch from file magic - arch="$(file "$kernel")" - case "$arch" in - *"ARM boot executable"*) arch=arm ; return "$EXIT_SUCCESS" ;; - * ) arch="" ;; - esac - - # no more ideas; giving up. } -pvm_native_arch() { - local arch +pvm_guess_qemu_args() # assumes: $qemu_args $imagefile $arch $bootdir , appends: $qemu_args +{ + msg "configuring the virtual machine ($arch)" - case "$1" in - arm*) arch=armv7l ;; - * ) arch="$1" ;; - esac + qemu_args+=(-m $DEF_RAM_MB ) - setarch "$arch" /bin/true 2>/dev/null || return -} + # optional large qemu disk + qemu_args+=( $( [[ -w $DATA_IMG ]] && echo "-hdb $DATA_IMG" ) ) -pvm_guess_qemu_args() { # if we're not running on X / wayland, disable graphics if [ -z "$DISPLAY" ]; then qemu_args+=(-nographic); elif (( ${RedirectSerial} )); then qemu_args+=(-serial "mon:stdio"); fi + # find root filesystem partition + local root_part_n + pvm_find_root_part_n "$imagefile" || return "$EXIT_FAILURE" # sets: $root_part_n + # if we're running a supported arch, enable kvm if pvm_native_arch "$arch"; then qemu_args+=(-enable-kvm); fi - # find root filesystem partition (necessary for arches without bootloader) - local root_loopdev_n=$(echo $(parted "$imagefile" print 2> /dev/null | grep ext4) | cut -d ' ' -f 1) - local root_loopdev="$loopdev"p$root_loopdev_n - local root_vdev=/dev/vda$root_loopdev_n - if [[ -b "$root_loopdev" ]] - then - msg "found root filesystem loop device: %s" "$root_loopdev" - else - error "%s: unable to determine root filesystem loop device" "$imagefile" - return "$EXIT_FAILURE" - fi - # set arch-specific args - local kernel_console + local kernel_tty case "$arch" in - i386|x86_64|ppc64) - qemu_args+=(-m $DEF_RAM_MB -hda "$imagefile") - # unmount the unneeded virtual drive early - pvm_umount ;; - arm) - kernel_console="console=tty0 console=ttyAMA0 " - qemu_args+=(-machine virt - -m $DEF_RAM_MB - -kernel "$workdir"/vmlinuz-${Kernel} - -initrd "$workdir"/initramfs-${Kernel}.img - -append "${kernel_console}rw root=${root_vdev}" - -drive "if=none,file=${imagefile},format=raw,id=hd" - -device "virtio-blk-device,drive=hd" - -netdev "user,id=mynet" - -device "virtio-net-device,netdev=mynet") ;; - riscv64) - kernel_console=$( [ -z "$DISPLAY" ] && echo "console=ttyS0 " ) - qemu_args+=(-machine virt - -m $DEF_RAM_MB - -kernel "$workdir"/bbl - -append "${kernel_console}rw root=/dev/vda" - -drive "file=${root_vdev},format=raw,id=hd0" - -device "virtio-blk-device,drive=hd0" - -object "rng-random,filename=/dev/urandom,id=rng0" - -device "virtio-rng-device,rng=rng0" - -netdev "user,id=usernet" - -device "virtio-net-device,netdev=usernet") ;; - *) - error "%s: unable to determine default qemu args" "$imagefile" - return "$EXIT_FAILURE" ;; + armv7h ) kernel_tty="console=tty0 console=ttyAMA0 " ;; + i686 ) kernel_tty=$( [[ -z "$DISPLAY" ]] && echo "console=ttyS0 " ) ;; + ppc64le) ;; # TODO: + riscv64) ;; # TODO: + x86_64 ) kernel_tty=$( [[ -z "$DISPLAY" ]] && echo "console=ttyS0 " ) ;; + esac + case "$arch" in + armv7h ) qemu_args+=(-machine virt + -kernel "$bootdir"/vmlinuz-${Kernel} + -initrd "$bootdir"/initramfs-${Kernel}.img + -append "${kernel_tty}rw root=/dev/vda$root_part_n" + -drive "if=none,file=${imagefile},format=raw,id=hd" + -device "virtio-blk-device,drive=hd" + -netdev "user,id=mynet" + -device "virtio-net-device,netdev=mynet") ;; + i686 ) qemu_args+=(-hda "$imagefile") ;; + ppc64le) qemu_args+=(-hda " $imagefile") ;; + riscv64) qemu_args+=(-machine virt + -kernel "$bootdir"/bbl + -append "${kernel_tty}rw root=/dev/vda" + -drive "file=/dev/vda$root_part_n,format=raw,id=hd0" + -device "virtio-blk-device,drive=hd0" + -object "rng-random,filename=/dev/urandom,id=rng0" + -device "virtio-rng-device,rng=rng0" + -netdev "user,id=usernet" + -device "virtio-net-device,netdev=usernet") ;; + x86_64 ) qemu_args+=(-hda "$imagefile") ;; esac } -main() { - if [ "$(id -u)" -eq 0 ]; then - error "This program must be run as a regular user" - exit "$EXIT_NOPERMISSION" - fi +main() # ( [cli_options] imagefile qemu_args ) +{ + pvm_check_unprivileged # exits on failure # parse options while getopts 'hk:r' arg; do case "$arg" in - h) usage; return "$EXIT_SUCCESS";; - k) Kernel="$OPTARG";; - r) RedirectSerial=1;; - *) error "invalid argument: %s\n" "$arg"; usage >&2; exit "$EXIT_INVALIDARGUMENT";; + h) usage; return "$EXIT_SUCCESS" ;; + k) Kernel="$OPTARG" ;; + r) RedirectSerial=1 ;; + *) error "invalid argument: %s\n" "$arg"; usage >&2; exit "$EXIT_INVALIDARGUMENT" ;; esac done - local shiftlen=$(( OPTIND - 1 )) - shift $shiftlen - local imagefile="$1" - shift - [ ! -n "$imagefile" ] && error "no image file specified" && exit "$EXIT_FAILURE" - [ ! -e "$imagefile" ] && error "image file not found: '%s'" "$imagefile" && exit "$EXIT_FAILURE" + local shiftlen=$(( OPTIND - 1 )) ; shift $shiftlen ; + local imagefile="$1" ; shift ; + local cli_args=$@ + [ ! -n "$imagefile" ] && error "no image file specified" && exit "$EXIT_FAILURE" + [ ! -e "$imagefile" ] && error "image file not found: '%s'" "$imagefile" && exit "$EXIT_FAILURE" + [ ! -w "$imagefile" ] && error "image file not writable: %s" "$imagefile" && exit "$EXIT_FAILURE" msg "initializing ...." - local workdir loopdev - pvm_mount || exit - + local bootdir workdir loopdev local arch - pvm_probe_arch || exit - if [ -z "$arch" ]; then - error "image arch is unknown: '%s'" "$arch" - exit "$EXIT_FAILURE" - fi - + local qemu_cmd local qemu_args=() - pvm_guess_qemu_args || exit - qemu_args+=("$@") + local was_error + pvm_mount || exit "$EXIT_FAILURE" # assumes: $imagefile , sets: $loopdev $bootdir $workdir + pvm_probe_arch || exit "$EXIT_FAILURE" # assumes: $bootdir $workdir $imagefile , sets: $arch + pvm_guess_qemu_cmd || exit "$EXIT_FAILURE" # assumes: $arch , sets: $qemu_cmd + pvm_guess_qemu_args || exit "$EXIT_FAILURE" # assumes: $qemu_args $imagefile $arch $bootdir , appends: $qemu_args - msg "booting VM ...." - (set -x; qemu-system-"$arch" "${qemu_args[@]}") + # unmount the virtual disks early, for images with a bootloader + [[ "$arch" =~ ^i686$|^x86_64$|^ppc64le$ ]] && pvm_cleanup + + msg "booting the virtual machine ...." + (set -x; $qemu_cmd "${qemu_args[@]}" $cli_args) ; was_error=$? ; # clean up the terminal, in case SeaBIOS did something weird echo -n "[?7h" - pvm_umount + pvm_cleanup + + (( ! $was_error )) && exit "$EXIT_SUCCESS" || exit "$EXIT_FAILURE" } -main "$@" + +if source /usr/lib/parabola-vmbootstrap/pvm-common.sh.inc 2> /dev/null || \ + source "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")"/pvm-common.sh.inc 2> /dev/null +then main "$@" +else echo "can not find pvm-common.sh.inc" && exit 1 +fi diff --git a/src/pvmbootstrap.sh b/src/pvmbootstrap.sh index 43b532a..c7358f3 100755 --- a/src/pvmbootstrap.sh +++ b/src/pvmbootstrap.sh @@ -20,10 +20,6 @@ ############################################################################### -# shellcheck source=/usr/lib/libretools/messages.sh -source "$(librelib messages)" - - # defaults readonly PKG_SET_MIN='minimal' readonly PKG_SET_STD='standard' @@ -34,16 +30,22 @@ readonly DEV_PKGS=('base' 'parabola-base' 'base-devel') ; readonly ROOT_MB_DEV=1 readonly DEF_PKGS=(${STD_PKGS[@]} ) ; readonly DEF_MIN_MB=$ROOT_MB_STD ; readonly DEF_KERNEL='linux-libre' # ASSERT: must be 'linux-libre', per 'parabola-base' readonly DEF_MIRROR=https://repo.parabola.nu -readonly DEF_ROOT_MB=64000 +readonly DEF_ROOT_MB=32000 readonly DEF_BOOT_MB=100 readonly DEF_SWAP_MB=0 +readonly MANDATORY_PKGS_ALL=( ) +readonly MANDATORY_PKGS_armv7h=( haveged net-tools ) +readonly MANDATORY_PKGS_i686=( haveged net-tools grub ) +readonly MANDATORY_PKGS_ppc64le=( haveged net-tools ) +readonly MANDATORY_PKGS_riscv64=( ) +readonly MANDATORY_PKGS_x86_64=( haveged net-tools grub ) # misc -readonly THIS_DIR="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" readonly GUEST_CACHED_PKGS=('ca-certificates-utils') +readonly PVM_HOOKS_SUCCESS_MSG="[hooks.sh] pre-init hooks successful" # options -BasePkgSet=$PKG_SET_STD +BasePkgSet=$DEF_PKG_SET MinRootMb=$DEF_MIN_MB Hooks=() Kernels=() @@ -57,7 +59,8 @@ SwapSizeMb=$DEF_SWAP_MB HasSwap=0 -usage() { +usage() +{ print "USAGE:" print " pvmbootstrap [-b ] [-h] [-H ] [-k ] [-M ]" print " [-O] [-p ] [-s ] [-S ]" @@ -110,27 +113,26 @@ usage() { echo " " } -pvm_native_arch() { - local arch=$( [[ "$1" =~ arm.* ]] && echo 'armv7l' || echo "$1" ) +pvm_bootstrap() # assumes: $arch $imagefile $loopdev $workdir , traps: INT TERM RETURN +{ + # prompt to clobber if the target output file already exists + pvm_check_no_mounts || return "$EXIT_FAILURE" + mkdir -p "$(dirname "$imagefile")" || return "$EXIT_FAILURE" + pvm_prompt_clobber_file "$imagefile" || return "$EXIT_FAILURE" - setarch "$arch" /bin/true 2>/dev/null || return "$EXIT_FAILURE" -} - -pvm_bootstrap() { - msg "starting creation of %s image: %s" "$arch" "$imagefile" + msg "starting build for %s image: %s" "$arch" "$imagefile" # create the raw image file local img_mb=$(( $BootSizeMb + $SwapSizeMb + $RootSizeMb )) qemu-img create -f raw "$imagefile" "${img_mb}M" || return "$EXIT_FAILURE" # prepare for cleanup - trap 'pvm_cleanup' INT TERM RETURN + trap 'pvm_bootstrap_cleanup' INT TERM RETURN # mount the virtual disk - local workdir loopdev - workdir="$(mktemp -d -t pvm-rootfs-XXXXXXXXXX)" || return "$EXIT_FAILURE" - loopdev="$(sudo losetup -fLP --show "$imagefile")" || return "$EXIT_FAILURE" - sudo dd if=/dev/zero of="$loopdev" bs=1M count=8 || return "$EXIT_FAILURE" + local bootdir workdir loopdev + pvm_setup_loopdev || return "$EXIT_FAILURE" # sets: $bootdir $workdir $loopdev + sudo dd if=/dev/zero of="$loopdev" bs=1M count=8 || return "$EXIT_FAILURE" # partition local boot_begin="$( [[ "$arch" =~ i686|x86_64 ]] && echo 2 || echo 1 )MiB" @@ -202,20 +204,19 @@ pvm_bootstrap() { # setup qemu-user-static, if necessary if ! pvm_native_arch "$arch"; then - # target arch can't execute natively, pacstrap is going to need help by qemu local qemu_arch case "$arch" in armv7h) qemu_arch=arm ;; * ) qemu_arch="$arch" ;; esac - local qemu_user_static=$(sudo grep -l -F -e "interpreter /usr/bin/qemu-$qemu_arch-" \ - -r -- /proc/sys/fs/binfmt_misc 2>/dev/null | \ - xargs -r sudo grep -xF 'enabled' ) - if [[ -n "$qemu_user_static" ]]; then - msg "found qemu-user-static for %s" "$arch" + local qemu_static=$(sudo grep -l -F -e "interpreter /usr/bin/qemu-$qemu_arch-" \ + -r -- /proc/sys/fs/binfmt_misc 2>/dev/null | \ + xargs -r sudo grep -xF 'enabled' ) + if [[ -n "$qemu_static" ]]; then + msg "found qemu-user-static for arch: '%s'" "$qemu_arch" else - error "missing qemu-user-static for %s" "$arch" + error "missing qemu-user-static for arch: '%s'" "$qemu_arch" return "$EXIT_FAILURE" fi @@ -224,9 +225,8 @@ pvm_bootstrap() { fi # prepare pacstrap config - local pacconf repos - pacconf="$(mktemp -t pvm-pacconf-XXXXXXXXXX)" || return "$EXIT_FAILURE" - repos=(libre core extra community pcr) + local pacconf="$(mktemp -t pvm-pacconf-XXXXXXXXXX)" || return "$EXIT_FAILURE" + local repos=(libre core extra community pcr) (( $IsNonsystemd )) && repos=('nonsystemd' ${repos[@]}) echo -e "[options]\nArchitecture = $arch" > "$pacconf" for repo in ${repos[@]}; do echo "[$repo]" >> "$pacconf"; @@ -234,15 +234,17 @@ pvm_bootstrap() { done # prepare package lists - local kernels=( ${Kernels[@]} ) - local pkgs=( ${Pkgs[@]} ${Kernels[@]} ${OptPkgs[@]} ) - local pkgs_cached=( ${GUEST_CACHED_PKGS[@]} ) + local kernels=( ${Kernels[@]} ) + local pkgs=( ${Pkgs[@]} ${Kernels[@]} ${OptPkgs[@]} ${MANDATORY_PKGS_ALL[@]} ) + local pkgs_cached=( ${GUEST_CACHED_PKGS[@]} ) case "$arch" in - i686|x86_64) pkgs+=(grub) ;; - riscv64 ) ;; - * ) pkgs+=(haveged net-tools) ;; + armv7h ) pkgs+=( ${MANDATORY_PKGS_armv7h[@]} ) ;; + i686 ) pkgs+=( ${MANDATORY_PKGS_i686[@]} ) ;; + ppc64le) pkgs+=( ${MANDATORY_PKGS_ppc64le[@]} ) ;; + riscv64) pkgs+=( ${MANDATORY_PKGS_riscv64[@]} ) ;; + x86_64 ) pkgs+=( ${MANDATORY_PKGS_x86_64[@]} ) ;; esac - (( $IsNonsystemd )) && && pkgs+=(libelogind) + (( $IsNonsystemd )) && [[ "$BasePkgSet" == "$PKG_SET_MIN" ]] && pkgs+=(libelogind) (( ! $IsNonsystemd )) && [[ "${Hooks[@]}" =~ hook-ethernet-dhcp.sh ]] && pkgs+=(dhcpcd) # minimize package lists @@ -254,6 +256,9 @@ pvm_bootstrap() { msg "installing packages into the work chroot" sudo pacstrap -GMc -C "$pacconf" "$workdir" "${pkgs[@]}" || return "$EXIT_FAILURE" sudo pacstrap -GM -C "$pacconf" "$workdir" "${pkgs_cached[@]}" || return "$EXIT_FAILURE" + msg2 "creating a list of installed packages" + pacman -Sl -r "$workdir/" --config "$pacconf" | \ + awk '/\[installed\]$/ {print $1 "/" $2 "-" $3}' > $(dirname $imagefile)/pkglist.txt # create an fstab msg "generating /etc/fstab" @@ -270,13 +275,16 @@ pvm_bootstrap() { local hostname='parabola' local lang='en_US.UTF-8' msg "configuring system envoronment" - echo "/etc/hostname: " ; echo $hostname | sudo tee "$workdir"/etc/hostname ; - echo "/etc/locale.conf: " ; echo "LANG=$lang" | sudo tee "$workdir"/etc/locale.conf ; + echo -n "/etc/hostname: " ; echo $hostname | sudo tee "$workdir"/etc/hostname ; + echo -n "/etc/locale.conf: " ; echo "LANG=$lang" | sudo tee "$workdir"/etc/locale.conf ; sudo sed -i "s/#${lang}/${lang}/" "$workdir"/etc/locale.gen # install a boot loader msg "installing boot loader" case "$arch" in + armv7h) + msg2 "(armv7h has no boot loader)" + ;; i686|x86_64) local grub_def_file="$workdir"/etc/default/grub local grub_cfg_file=/boot/grub/grub.cfg @@ -292,26 +300,26 @@ pvm_bootstrap() { sudo arch-chroot "$workdir" grub-install "$loopdev" || return "$EXIT_FAILURE" sudo arch-chroot "$workdir" grub-mkconfig -o $grub_cfg_file || return "$EXIT_FAILURE" ;; - armv7h) - echo "(armv7h has no boot loader)" + ppc64le) + msg2 "(ppc64le has no boot loader)" ;; riscv64) - # FIXME: for the time being, use fedora bbl to boot - warning "(riscv64 requires a blob - downloading it now)" - local bbl_url=https://fedorapeople.org/groups/risc-v/disk-images/bbl - sudo wget $bbl_url -O "$workdir"/boot/bbl || return "$EXIT_FAILURE" - ;; - ppc64le) - # FIXME: what about ppc64le? - echo "(ppc64le has no boot loader)" + # FIXME: for the time being, use berkeley bootloader to boot + if [[ -f /usr/lib/parabola-vmbootstrap/bbl ]]; then + cp /usr/lib/parabola-vmbootstrap/bbl "$workdir"/boot/ + else + error "riscv64 requires the berkeley bootloader from the 'parabola-vmbootstrap' package" + return "$EXIT_FAILURE" + fi ;; esac - # regenerate the initcpio(s), skipping the autodetect hook + # regenerate the initcpio(s), to skip the 'autodetect' hook for kernel in ${Kernels[@]} do local preset_file="$workdir"/etc/mkinitcpio.d/${kernel}.preset local default_options="default_options=\"-S autodetect\"" + msg "regenerating initcpio for kernel: '${kernel}'" sudo cp "$preset_file"{,.backup} || return "$EXIT_FAILURE" echo "$default_options" | sudo tee -a "$preset_file" > /dev/null || return "$EXIT_FAILURE" @@ -326,19 +334,16 @@ pvm_bootstrap() { # push hooks into the image msg "preparing hooks" - sudo mkdir -p "$workdir/root/hooks" + sudo mkdir -p "$workdir"/root/hooks [ "${#Hooks[@]}" -eq 0 ] || sudo cp -v "${Hooks[@]}" "$workdir"/root/hooks/ (( $IsNonsystemd )) && sudo rm "$workdir"/root/hooks/hook-ethernet-dhcp.sh # systemd-only hook # create a master hook script - local hooks_success_msg="[hooks.sh] pre-init hooks successful" - echo "hooks.sh:" + msg2 "hooks.sh:" sudo tee "$workdir"/root/hooks.sh << EOF #!/bin/bash echo "[hooks.sh] boot successful - configuring ...." -systemctl disable preinit.service - # generate the locale locale-gen @@ -355,19 +360,20 @@ for hook in /root/hooks/*; do done # clean up after yourself +systemctl disable preinit.service +rm -f /root/.bash_history rm -rf /root/hooks -rm -f /root/hooks.sh -rm -f /usr/lib/systemd/system/preinit.service -rm -f /var/cache/pacman/pkg/* -rm -f /root/.bash_history +rm -f /root/hooks.sh +rm -f /usr/lib/systemd/system/preinit.service +rm -f /var/cache/pacman/pkg/* # report success :) -echo "$hooks_success_msg - powering off" +echo "$PVM_HOOKS_SUCCESS_MSG - powering off" [[ -e "/usr/lib/libretools/common.sh" ]] && rm -f /usr/lib/libretools/common.sh EOF # create a pre-init service to run the hooks - echo "preinit.service:" + msg2 "preinit.service:" sudo tee "$workdir"/usr/lib/systemd/system/preinit.service << 'EOF' [Unit] Description=Oneshot VM Preinit @@ -394,23 +400,18 @@ EOF sudo arch-chroot "$workdir" systemctl enable preinit.service || return "$EXIT_FAILURE" # unmount everything - pvm_cleanup + pvm_bootstrap_cleanup +} + +pvm_bootstrap_preinit() # assumes: $imagefile +{ + pvm_check_no_mounts || return "$EXIT_FAILURE" # boot the machine to run the pre-init hooks - local pvmboot_cmd - local qemu_flags=(-no-reboot) - if [ -f "$THIS_DIR/pvmboot.sh" ]; then # in-tree - pvmboot_cmd=("$THIS_DIR/pvmboot.sh") - elif type -p pvmboot &>/dev/null; then # installed - pvmboot_cmd=('pvmboot') - else - error "pvmboot not available -- unable to run hooks" - return "$EXIT_FAILURE" - fi - pvmboot_cmd+=("$imagefile" "${qemu_flags[@]}") + [[ "$(pvm_get_pvmboot_cmd)" ]] && msg "booting the VM to run the pre-init hooks" || \ + warning "unable to run pre-init hooks" exec 3>&1 - msg "booting the machine to run the pre-init hooks" - DISPLAY='' "${pvmboot_cmd[@]}" | tee /dev/fd/3 | grep -q -F "$hooks_success_msg" + pvm_boot "$imagefile" | tee /dev/fd/3 | grep -q -F "$PVM_HOOKS_SUCCESS_MSG" local res=$? exec 3>&- ! (( $res )) || error "%s: failed to complete preinit hooks" "$imagefile" @@ -418,28 +419,22 @@ EOF return $res } -pvm_cleanup() { +pvm_bootstrap_cleanup() # sets: $pacconf , untraps: INT TERM RETURN +{ trap - INT TERM RETURN - [ -n "${workdir}${loopdev}${pacconf}" ] && msg "cleaning up" + [[ "${workdir}${pacconf}" ]] && msg "cleaning up" + + [[ -n "$workdir" ]] && sudo rm -f "$workdir"/usr/bin/qemu-*C + [[ -n "$pacconf" ]] && rm -f "$pacconf" + pvm_cleanup || return "$EXIT_FAILURE" - if [ -n "$workdir" ]; then - sudo rm -f "$workdir"/usr/bin/qemu-*C - sudo umount -R "$workdir" 2> /dev/null - rmdir "$workdir" - fi - if [ -n "$loopdev" ]; then sudo losetup -d "$loopdev"; fi; - if [ -n "$pacconf" ]; then rm -f "$pacconf"; fi; - unset workdir - unset loopdev unset pacconf } -main() { - if [ "$(id -u)" -eq 0 ]; then - error "This program must be run as a regular user" - exit "$EXIT_NOPERMISSION" - fi +main() # ( [cli_options] imagefile arch ) +{ + pvm_check_unprivileged # exits on failure # parse options while getopts 'b:hH:k:M:Op:s:S:' arg; do @@ -450,28 +445,19 @@ main() { Pkgs=(${STD_PKGS[@]}) ; MinRootMb=$ROOT_MB_STD ;; $PKG_SET_DEV) BasePkgSet=$OPTARG ; Kernels+=($DEF_KERNEL) ; Pkgs=(${DEV_PKGS[@]}) ; MinRootMb=$ROOT_MB_DEV ;; - * ) warning "%s: invalid base set" "$OPTARG" ;; - esac ;; - h) usage; return "$EXIT_SUCCESS";; - H) if [ -e "$THIS_DIR/hooks/hook-$OPTARG.sh" ]; then # in-tree - Hooks+=("$THIS_DIR/hooks/hook-$OPTARG.sh") - elif [ -e "/usr/lib/libretools/pvmbootstrap/hook-$OPTARG.sh" ]; then # installed - Hooks+=("/usr/lib/libretools/pvmbootstrap/hook-$OPTARG.sh") - elif [ -e "$OPTARG" ]; then - Hooks+=("$OPTARG") - else - warning "%s: hook does not exist" "$OPTARG" - fi ;; - k) Kernels+=($OPTARG);; - M) Mirror="$OPTARG";; - O) IsNonsystemd=0;; # TODO: - p) OptPkgs+=($OPTARG);; - s) RootSizeMb="$(sed 's|[^0-9]||g' <<<$OPTARG)";; - S) SwapSizeMb="$(sed 's|[^0-9]||g' <<<$OPTARG)";; - *) error "invalid argument: %s\n" "$arg"; usage >&2; exit "$EXIT_INVALIDARGUMENT";; + * ) warning "invalid base set: %s" "$OPTARG" ;; + esac ;; + h) usage; return "$EXIT_SUCCESS" ;; + H) Hooks+=( "$(pvm_get_hook $OPTARG)" ) ;; + k) Kernels+=($OPTARG) ;; + M) Mirror="$OPTARG" ;; + O) IsNonsystemd=0 ;; # TODO: + p) OptPkgs+=($OPTARG) ;; + s) RootSizeMb="$(sed 's|[^0-9]||g' <<<$OPTARG)" ;; + S) SwapSizeMb="$(sed 's|[^0-9]||g' <<<$OPTARG)" ;; + *) error "invalid option: '%s'" "$arg" ; usage >&2 ; exit "$EXIT_INVALIDARGUMENT" ;; esac done - local shiftlen=$(( OPTIND - 1 )) shift $shiftlen local imagefile="$1" @@ -484,32 +470,34 @@ main() { RootSizeMb=$(( $RootSizeMb + (${#Kernels[@]} * 75) )) HasSwap=$( (( $SwapSizeMb > 0 )) && echo 1 || echo 0 ) + msg "making $arch image: $imagefile" + # determine if the target arch is supported case "$arch" in - i686|x86_64|armv7h) ;; - ppc64le|riscv64 ) warning "arch %s is experimental" "$arch" ;; - * ) error "arch %s is unsupported" "$arch" - exit "$EXIT_INVALIDARGUMENT" ;; + i686|x86_64|armv7h) ;; + ppc64le|riscv64 ) warning "arch is experimental: %s" "$arch" ;; + * ) error "arch is unsupported: %s" "$arch" + exit "$EXIT_INVALIDARGUMENT" ;; esac - # determine whether the target output file already exists - if [ -e "$imagefile" ]; then - warning "%s: file exists. Continue? [y/N]" "$imagefile" - read -p " " -n 1 -r - echo - if [[ ! $REPLY =~ ^[Yy]$ ]]; then + # create the virtual machine + if pvm_bootstrap; then + if pvm_bootstrap_preinit; then + msg "bootstrap complete for image: %s" "$imagefile" + exit "$EXIT_SUCCESS" + else + error "bootstrap complete, but preinit failed for image: %s" "$imagefile" exit "$EXIT_FAILURE" fi - rm -f "$imagefile" || exit - fi - - # create the virtual machine - if ! pvm_bootstrap; then + else error "bootstrap failed for image: %s" "$imagefile" exit "$EXIT_FAILURE" fi - - msg "bootstrap complete for image: %s" "$imagefile" } -main "$@" + +if source /usr/lib/parabola-vmbootstrap/pvm-common.sh.inc 2> /dev/null || \ + source "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")"/pvm-common.sh.inc 2> /dev/null +then main "$@" +else echo "can not find pvm-common.sh.inc" && exit 1 +fi