uniform return values and better logging

This commit is contained in:
bill-auger 2019-12-22 19:50:19 -05:00
parent 50d32d2cba
commit 906535e29d
3 changed files with 139 additions and 96 deletions

@ -39,7 +39,9 @@ usage() {
}
pvm_mount() {
if ! file "$imagefile" | grep -q ' DOS/MBR '; then
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
@ -60,7 +62,9 @@ pvm_mount() {
sudo umount "$workdir"
done
if [ -z "$rootpart" ]; then
if [ -n "$rootpart" ]; then
msg "found root filesystem partition: %s" "$rootpart"
else
error "%s: unable to determine root partition." "$imagefile"
return "$EXIT_FAILURE"
fi
@ -68,7 +72,9 @@ pvm_mount() {
# find the boot partition
bootpart="$(findmnt -senF "$workdir"/etc/fstab /boot | awk '{print $2}')"
if [ -z "$bootpart" ]; then
if [ -n "$bootpart" ]; then
msg "found boot filesystem partition: %s" "$bootpart"
else
error "%s: unable to determine boot partition." "$imagefile"
return "$EXIT_FAILURE"
fi
@ -78,6 +84,8 @@ pvm_mount() {
}
pvm_umount() {
msg "un-mounting filesystems"
trap - INT TERM EXIT
[ -n "$workdir" ] && (sudo umount -R "$workdir"; rmdir "$workdir")
@ -98,7 +106,7 @@ main() {
case "$arg" in
h) usage; return "$EXIT_SUCCESS";;
o) output="$OPTARG";;
*) usage >&2; exit "$EXIT_INVALIDARGUMENT";;
*) error "invalid argument: %s\n" "$arg"; usage >&2; exit "$EXIT_INVALIDARGUMENT";;
esac
done
local shiftlen=$(( OPTIND - 1 ))
@ -140,6 +148,7 @@ main() {
# archlinuxarm and the generated parabola tarball through:
#
# `tar -tf <tarball> | sort`
msg "imploding tarball"
sudo tar -c -f "$output" -C "$workdir" -X - . << EOF
./boot/lost+found
./etc/.updated

@ -28,23 +28,23 @@ usage() {
print "USAGE: %s [-h] <img> [qemu-args ...]" "${0##*/}"
prose "Determine the architecture of <img> and boot it using qemu. <img> is assumed
to be a valid, raw-formatted parabola virtual machine image, ideally
created using pvmbootstrap. The started instance is assigned
created using pvmbootstrap. The started instances are 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
can be forced by unsetting DISPLAY manually, for example through:"
echo
echo " DISPLAY= ${0##*/} ./an.img"
echo " DISPLAY= ${0##*/} IMG ..."
echo
prose "When the architecture of <img> is compatible with the host architecture,
prose "When the architecture of IMG is compatible with the host architecture,
append -enable-kvm to the qemu arguments."
echo
prose "Further arguments provided after <img> will be passed unmodified to the
prose "Further arguments provided after IMG will be passed unmodified to the
qemu invocation. This can be used to allocate more resources to the virtual
machine, for example:"
echo
echo " ${0##*/} ./an.img -m 2G -smp 2"
echo " ${0##*/} IMG -m 2G -smp 2"
echo
echo "Supported options:"
echo " -h Display this help and exit"
@ -59,15 +59,17 @@ pvm_mount() {
return "$EXIT_FAILURE"
fi
msg "mounting filesystems"
trap 'pvm_umount' INT TERM EXIT
workdir="$(mktemp -d -t pvm-XXXXXXXXXX)" || return
loopdev="$(sudo losetup -fLP --show "$1")" || return
workdir="$(mktemp -d -t pvm-XXXXXXXXXX)" || return "$EXIT_FAILURE"
loopdev="$(sudo losetup -fLP --show "$1")" || return "$EXIT_FAILURE"
sudo mount "$loopdev"p1 "$workdir" \
|| sudo mount "$loopdev"p2 "$workdir" || return
|| sudo mount "$loopdev"p2 "$workdir" || return "$EXIT_FAILURE"
}
pvm_umount() {
msg "un-mounting filesystems"
trap - INT TERM EXIT
[ -n "$workdir" ] && (sudo umount "$workdir"; rmdir "$workdir")
@ -81,30 +83,30 @@ pvm_probe_arch() {
kernel=$(find "$workdir" -maxdepth 1 -type f -iname '*vmlinu*' | head -n1)
if [ -z "$kernel" ]; then
warning "%s: unable to find kernel binary" "$1"
return
return "$EXIT_FAILURE"
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="";;
PowerPC64) arch=ppc64; return "$EXIT_SUCCESS";;
RISC-V ) arch=riscv64; return "$EXIT_SUCCESS";;
* ) 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="";;
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;;
*) arch="";;
*"ARM boot executable"*) arch=arm; return "$EXIT_SUCCESS";;
* ) arch="";;
esac
# no more ideas; giving up.
@ -173,23 +175,24 @@ main() {
while getopts 'h' arg; do
case "$arg" in
h) usage; return "$EXIT_SUCCESS";;
*) usage >&2; exit "$EXIT_INVALIDARGUMENT";;
*) error "invalid argument: %s\n" "$arg"; usage >&2; exit "$EXIT_INVALIDARGUMENT";;
esac
done
local shiftlen=$(( OPTIND - 1 ))
shift $shiftlen
local imagefile="$1"
shift
[ ! -e "$imagefile" ] && error "%s: file not found" "$imagefile" && exit "$EXIT_FAILURE"
[ ! -n "$imagefile" ] && error "no image file specified" && exit "$EXIT_FAILURE"
[ ! -e "$imagefile" ] && error "image file not found: '%s'" "$imagefile" && exit "$EXIT_FAILURE"
msg "initializing ...."
local workdir loopdev
pvm_mount "$imagefile" || exit
local arch
pvm_probe_arch "$imagefile" || exit
if [ -z "$arch" ]; then
error "%s: arch is unknown" "$imagefile"
error "image arch is unknown: '%s'" "$arch"
exit "$EXIT_FAILURE"
fi
@ -197,9 +200,11 @@ main() {
pvm_guess_qemu_args "$imagefile" "$arch" || exit
qemu_args+=("$@")
msg "booting ...."
(set -x; qemu-system-"$arch" "${qemu_args[@]}")
# clean up the terminal, in case SeaBIOS did something weird
msg "cleaning up ...."
echo -n "[?7h"
pvm_umount
}

@ -65,25 +65,26 @@ usage() {
pvm_native_arch() {
local arch=$( [[ "$1" =~ arm.* ]] && echo 'armv7l' || echo "$1" )
setarch "$arch" /bin/true 2>/dev/null || return
setarch "$arch" /bin/true 2>/dev/null || return "$EXIT_FAILURE"
}
pvm_bootstrap() {
msg "%s: starting image creation for %s" "$file" "$arch"
msg "starting creation of %s image: %s" "$arch" "$file"
# create the raw image file
qemu-img create -f raw "$file" "${ImgSizeGb}G" || return
qemu-img create -f raw "$file" "${ImgSizeGb}G" || return "$EXIT_FAILURE"
# prepare for cleanup
trap 'pvm_cleanup' INT TERM RETURN
# mount the virtual disk
local workdir loopdev
workdir="$(mktemp -d -t pvm-rootfs-XXXXXXXXXX)" || return
loopdev="$(sudo losetup -fLP --show "$file")" || return
sudo dd if=/dev/zero of="$loopdev" bs=1M count=8 || return
workdir="$(mktemp -d -t pvm-rootfs-XXXXXXXXXX)" || return "$EXIT_FAILURE"
loopdev="$(sudo losetup -fLP --show "$file")" || return "$EXIT_FAILURE"
sudo dd if=/dev/zero of="$loopdev" bs=1M count=8 || return "$EXIT_FAILURE"
# partition
msg "partitioning blank image"
case "$arch" in
i686|x86_64)
sudo parted -s "$loopdev" \
@ -92,21 +93,21 @@ pvm_bootstrap() {
set 1 bios_grub on \
mkpart primary ext2 2MiB 514MiB \
mkpart primary linux-swap 514MiB 4610MiB \
mkpart primary ext4 4610MiB 100% || return ;;
mkpart primary ext4 4610MiB 100% || return "$EXIT_FAILURE" ;;
armv7h)
sudo parted -s "$loopdev" \
mklabel gpt \
mkpart ESP fat32 1MiB 513MiB \
set 1 boot on \
mkpart primary linux-swap 513MiB 4609MiB \
mkpart primary ext4 4609MiB 100% || return ;;
mkpart primary ext4 4609MiB 100% || return "$EXIT_FAILURE" ;;
ppc64le|riscv64)
sudo parted -s "$loopdev" \
mklabel gpt \
mkpart primary ext2 1MiB 513MiB \
set 1 boot on \
mkpart primary linux-swap 513MiB 4609MiB \
mkpart primary ext4 4609MiB 100% || return ;;
mkpart primary ext4 4609MiB 100% || return "$EXIT_FAILURE" ;;
esac
# refresh partition data
@ -114,35 +115,37 @@ pvm_bootstrap() {
# make file systems
local swapdev
msg "creating target filesystems"
case "$arch" in
i686|x86_64)
sudo mkfs.ext2 "$loopdev"p2 || return
sudo mkswap "$loopdev"p3 || return
sudo mkfs.ext4 "$loopdev"p4 || return
sudo mkfs.ext2 "$loopdev"p2 || return "$EXIT_FAILURE"
sudo mkswap "$loopdev"p3 || return "$EXIT_FAILURE"
sudo mkfs.ext4 "$loopdev"p4 || return "$EXIT_FAILURE"
swapdev="$loopdev"p3 ;;
armv7h)
sudo mkfs.vfat -F 32 "$loopdev"p1 || return
sudo mkswap "$loopdev"p2 || return
sudo mkfs.ext4 "$loopdev"p3 || return
sudo mkfs.vfat -F 32 "$loopdev"p1 || return "$EXIT_FAILURE"
sudo mkswap "$loopdev"p2 || return "$EXIT_FAILURE"
sudo mkfs.ext4 "$loopdev"p3 || return "$EXIT_FAILURE"
swapdev="$loopdev"p2 ;;
ppc64le|riscv64)
sudo mkfs.ext2 "$loopdev"p1 || return
sudo mkswap "$loopdev"p2 || return
sudo mkfs.ext4 "$loopdev"p3 || return
sudo mkfs.ext2 "$loopdev"p1 || return "$EXIT_FAILURE"
sudo mkswap "$loopdev"p2 || return "$EXIT_FAILURE"
sudo mkfs.ext4 "$loopdev"p3 || return "$EXIT_FAILURE"
swapdev="$loopdev"p2 ;;
esac
# mount partitions
msg "mounting target partitions"
case "$arch" in
i686|x86_64)
sudo mount "$loopdev"p4 "$workdir" || return
sudo mkdir -p "$workdir"/boot || return
sudo mount "$loopdev"p2 "$workdir"/boot || return
sudo mount "$loopdev"p4 "$workdir" || return "$EXIT_FAILURE"
sudo mkdir -p "$workdir"/boot || return "$EXIT_FAILURE"
sudo mount "$loopdev"p2 "$workdir"/boot || return "$EXIT_FAILURE"
;;
armv7h|ppc64le|riscv64)
sudo mount "$loopdev"p3 "$workdir" || return
sudo mkdir -p "$workdir"/boot || return
sudo mount "$loopdev"p1 "$workdir"/boot || return
sudo mount "$loopdev"p3 "$workdir" || return "$EXIT_FAILURE"
sudo mkdir -p "$workdir"/boot || return "$EXIT_FAILURE"
sudo mount "$loopdev"p1 "$workdir"/boot || return "$EXIT_FAILURE"
;;
esac
@ -158,18 +161,20 @@ pvm_bootstrap() {
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 [[ -z "$qemu_user_static" ]]; then
error "%s: missing qemu-user-static for %s" "$file" "$arch"
if [[ -n "$qemu_user_static" ]]; then
msg "found qemu-user-static for %s" "$arch"
else
error "missing qemu-user-static for %s" "$arch"
return "$EXIT_FAILURE"
fi
sudo mkdir -p "$workdir"/usr/bin
sudo cp -v "/usr/bin/qemu-$qemu_arch-"* "$workdir"/usr/bin || return
sudo cp -v "/usr/bin/qemu-$qemu_arch-"* "$workdir"/usr/bin || return "$EXIT_FAILURE"
fi
# prepare pacstrap config
local pacconf repos
pacconf="$(mktemp -t pvm-pacconf-XXXXXXXXXX)" || return
pacconf="$(mktemp -t pvm-pacconf-XXXXXXXXXX)" || return "$EXIT_FAILURE"
repos=('libre' 'core' 'extra' 'community' 'pcr')
echo -e "[options]\nArchitecture = $arch\n\n" > "$pacconf"
for repo in ${repos[@]}; do echo -e "[$repo]\nServer = $Mirror\n" >> "$pacconf"; done;
@ -203,10 +208,12 @@ pvm_bootstrap() {
local pkg_guest_cache=(ca-certificates-utils)
# pacstrap! :)
sudo pacstrap -GMc -C "$pacconf" "$workdir" "${pkgs[@]}" || return
sudo pacstrap -GM -C "$pacconf" "$workdir" "${pkg_guest_cache[@]}" || return
msg "installing packages into the work chroot"
sudo pacstrap -GMc -C "$pacconf" "$workdir" "${pkgs[@]}" || return "$EXIT_FAILURE"
sudo pacstrap -GM -C "$pacconf" "$workdir" "${pkg_guest_cache[@]}" || return "$EXIT_FAILURE"
# create an fstab
msg "generating /etc/fstab"
case "$arch" in
riscv64) ;;
*)
@ -217,14 +224,16 @@ pvm_bootstrap() {
sudo swapon --all ;;
esac
# produce a hostname
echo "parabola" | sudo tee "$workdir"/etc/hostname
# produce an /etc/locale.conf
echo "LANG=en_US.UTF-8" | sudo tee "$workdir"/etc/locale.conf
sudo sed -i 's/#en_US.UTF-8/en_US.UTF-8/' "$workdir"/etc/locale.gen
# configure the system envoronment
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 ;
sudo sed -i "s/#${lang}/${lang}/" "$workdir"/etc/locale.gen
# install a boot loader
msg "installing boot loader"
case "$arch" in
i686|x86_64)
local grub_def_file="$workdir"/etc/default/grub
@ -232,48 +241,57 @@ pvm_bootstrap() {
# enable serial console
local field=GRUB_CMDLINE_LINUX_DEFAULT
local value="console=tty0 console=ttyS0"
sudo sed -i "s/.*$field=.*/$field=\"$value\"/" "$grub_def_file" || return
sudo sed -i "s/.*$field=.*/$field=\"$value\"/" "$grub_def_file" || return "$EXIT_FAILURE"
# disable boot menu timeout
local field=GRUB_TIMEOUT
local value=0
sudo sed -i "s/.*$field=.*/$field=$value/" "$grub_def_file" || return
sudo sed -i "s/.*$field=.*/$field=$value/" "$grub_def_file" || return "$EXIT_FAILURE"
# install grub to the VM
sudo arch-chroot "$workdir" grub-install "$loopdev" || return
sudo arch-chroot "$workdir" grub-mkconfig -o $grub_cfg_file || return
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)"
;;
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
sudo wget $bbl_url -O "$workdir"/boot/bbl || return "$EXIT_FAILURE"
;;
ppc64le)
# FIXME: what about ppc64le?
echo "(ppc64le has no boot loader)"
;;
# armv7h has no boot loader.
# FIXME: what about ppc64le
esac
# regenerate the initcpio, skipping the autodetect hook
local preset_file="$workdir"/etc/mkinitcpio.d/linux-libre.preset
sudo cp "$preset_file"{,.backup} || return
echo "default_options=\"-S autodetect\"" | sudo tee -a "$preset_file" || return
sudo arch-chroot "$workdir" mkinitcpio -p linux-libre || return
sudo mv "$preset_file"{.backup,} || return
# disable audit
sudo arch-chroot "$workdir" systemctl mask systemd-journald-audit.socket
local kernel='linux-libre'
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"
sudo arch-chroot "$workdir" mkinitcpio -p ${kernel} || return "$EXIT_FAILURE"
sudo mv "$preset_file"{.backup,} || return "$EXIT_FAILURE"
# initialize the pacman keyring
msg "initializing the pacman keyring"
sudo arch-chroot "$workdir" pacman-key --init
sudo arch-chroot "$workdir" pacman-key --populate archlinux archlinux32 archlinuxarm parabola
# enable the entropy daemon, to avoid stalling https
sudo arch-chroot "$workdir" systemctl enable haveged.service
# push hooks into the image
msg "preparing hooks"
sudo mkdir -p "$workdir/root/hooks"
[ "${#Hooks[@]}" -eq 0 ] || sudo cp -v "${Hooks[@]}" "$workdir"/root/hooks/
# create a master hook script
sudo tee "$workdir"/root/hooks.sh << 'EOF'
local hooks_success_msg="[hooks.sh] pre-init hooks successful"
echo "hooks.sh:"
sudo tee "$workdir"/root/hooks.sh << EOF
#!/bin/bash
echo "[hooks.sh] boot successful - configuring ...."
systemctl disable preinit.service
# generate the locale
@ -287,8 +305,8 @@ pacman -U --noconfirm /var/cache/pacman/pkg/ca-certificates-utils-*.pkg.tar.xz
# run the hooks
for hook in /root/hooks/*; do
echo "running hook \"$hook\""
source "$hook" || return
echo "[hooks.sh] running hook: '\$(basename \$hook)'"
source "\$hook" || return
done
# clean up after yourself
@ -299,10 +317,11 @@ rm -f /var/cache/pacman/pkg/*
rm -f /root/.bash_history
# report success :)
echo "preinit hooks successful"
echo "$hooks_success_msg"
EOF
# create a preinit service to run the hooks
# create a pre-init service to run the hooks
echo "preinit.service:"
sudo tee "$workdir"/usr/lib/systemd/system/preinit.service << 'EOF'
[Unit]
Description=Oneshot VM Preinit
@ -313,19 +332,26 @@ StandardOutput=journal+console
StandardError=journal+console
ExecStart=/usr/bin/bash /root/hooks.sh
Type=oneshot
ExecStopPost=echo "powering off"
ExecStopPost=shutdown -r now
[Install]
WantedBy=multi-user.target
EOF
# enable the preinit service
sudo arch-chroot "$workdir" systemctl enable preinit.service || return
# configure services
msg "configuring services"
# disable audit
sudo arch-chroot "$workdir" systemctl mask systemd-journald-audit.socket
# enable the entropy daemon, to avoid stalling https
sudo arch-chroot "$workdir" systemctl enable haveged.service
# enable the pre-init service
sudo arch-chroot "$workdir" systemctl enable preinit.service || return "$EXIT_FAILURE"
# unmount everything
pvm_cleanup
# boot the machine to run the preinit hooks
# boot the machine to run the pre-init hooks
local pvmboot_cmd
local qemu_flags=(-no-reboot)
if [ -f "./src/pvmboot.sh" ]; then
@ -333,13 +359,13 @@ EOF
elif type -p pvmboot &>/dev/null; then
pvmboot_cmd=('pvmboot')
else
error "%s: pvmboot not available -- unable to run hooks" "$file"
error "pvmboot not available -- unable to run hooks"
return "$EXIT_FAILURE"
fi
pvmboot_cmd+=("$file" "${qemu_flags[@]}")
exec 3>&1
msg "booting the machine to run the pre-init hooks"
DISPLAY='' "${pvmboot_cmd[@]}" | tee /dev/fd/3 | grep -q "preinit hooks successful"
DISPLAY='' "${pvmboot_cmd[@]}" | tee /dev/fd/3 | grep -q -F "$hooks_success_msg"
local res=$?
exec 3>&-
! (( $res )) || error "%s: failed to complete preinit hooks" "$file"
@ -350,6 +376,7 @@ EOF
pvm_cleanup() {
trap - INT TERM RETURN
msg "cleaning up"
[ -n "$pacconf" ] && rm -f "$pacconf"
unset pacconf
if [ -n "$workdir" ]; then
@ -384,7 +411,7 @@ main() {
M) Mirror="$OPTARG";;
O) Init="-openrc";;
s) ImgSizeGb="$(sed 's|[^0-9]||g' <<<$OPTARG)";;
*) usage >&2; exit "$EXIT_INVALIDARGUMENT";;
*) error "invalid argument: %s\n" "$arg"; usage >&2; exit "$EXIT_INVALIDARGUMENT";;
esac
done
@ -392,16 +419,18 @@ main() {
shift $shiftlen
local file="$1"
local arch="$2"
[ "$#" -ne 2 ] && usage >&2 && exit "$EXIT_INVALIDARGUMENT"
[ "$ImgSizeGb" -lt $MIN_GB ] && usage >&2 && exit "$EXIT_INVALIDARGUMENT"
local has_params=$( [ "$#" -eq 2 ] && echo 1 || echo 0 )
local has_space=$( [ "$ImgSizeGb" -ge $MIN_GB ] && echo 1 || echo 0 )
(( ! $has_params )) && error "insufficient arguments" && usage >&2 && exit "$EXIT_INVALIDARGUMENT"
(( ! $has_space )) && error "image size too small" && usage >&2 && exit "$EXIT_INVALIDARGUMENT"
# determine if the target arch is supported
case "$arch" in
i686|x86_64|armv7h) ;;
ppc64le|riscv64)
warning "%s: arch %s is experimental" "$file" "$arch";;
warning "arch %s is experimental" "$arch";;
*)
error "%s: arch %s is unsupported" "$file" "$arch"
error "arch %s is unsupported" "$arch"
exit "$EXIT_INVALIDARGUMENT";;
esac
@ -418,11 +447,11 @@ main() {
# create the virtual machine
if ! pvm_bootstrap; then
error "%s: bootstrap failed" "$file"
error "bootstrap failed for image: %s" "$file"
exit "$EXIT_FAILURE"
fi
msg "%s: bootstrap complete" "$file"
msg "bootstrap complete for image: %s" "$file"
}
main "$@"