Jacob Hrbek
1a85688c27
This is to provide a compatibility with nix such as GNU Guix to use the project painlessly without the need of deploying a sandboxed FHS environment. Signed-off-by: Jacob Hrbek <kreyren@rixotstudio.cz>
289 lines
9.6 KiB
Bash
289 lines
9.6 KiB
Bash
#!/usr/bin/env bash
|
|
###############################################################################
|
|
# parabola-vmbootstrap -- create and start parabola virtual machines #
|
|
# #
|
|
# Copyright (C) 2017 - 2019 Andreas Grapentin #
|
|
# Copyright (C) 2019 - 2020 bill-auger #
|
|
# #
|
|
# 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 <http://www.gnu.org/licenses/>. #
|
|
###############################################################################
|
|
|
|
|
|
# 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_script() # (script_name)
|
|
{
|
|
local script_name=$1
|
|
local intree_script=$THIS_DIR/$script_name.sh
|
|
local installed_script=$script_name
|
|
|
|
[[ -f "$intree_script" ]] && echo "$intree_script" && return "$EXIT_SUCCESS"
|
|
type -p $installed_script &>/dev/null && echo "$installed_script" && return "$EXIT_SUCCESS"
|
|
error "can not find $script_name" && 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" &> /dev/null
|
|
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_script=$(pvm_get_script 'pvmboot')
|
|
local was_error=$?
|
|
|
|
[[ "$pvmboot_script" ]] || return $EXIT_FAILURE
|
|
|
|
DISPLAY='' "$pvmboot_script" "$imagefile" "${qemu_args[@]}" ; was_error=$? ;
|
|
|
|
(( ! $was_error )) && return $EXIT_SUCCESS || return $EXIT_FAILURE
|
|
}
|