Skip to content

Instantly share code, notes, and snippets.

@TobleMiner
Last active March 30, 2020 15:29
Show Gist options
  • Save TobleMiner/f564ab3114ff245ee7c6a897e2fea424 to your computer and use it in GitHub Desktop.
Save TobleMiner/f564ab3114ff245ee7c6a897e2fea424 to your computer and use it in GitHub Desktop.
linux2boot, a kexec based linux boot manager. Run as login through inittab, enable respawn
console=ttyS2,1500000 root=/dev/mmcblk1p1 rw rootwait video=eDP-1:1920x1080@60 vga=current initcall_debug maxcpus=2
Change of cpu clock rate during bootup after kexec results in system lockup
Mitigated by disabling big cluster via cmdline of linux2boot kernel
Exact reasons unclear, probably crashing during change of pll parameters (might also be change of clock mux/selector/divider)
#!/bin/sh
if [ -n "$DEBUG" ]; then
set -x
fi
AUTOBOOT_WAIT=3
AUTOBOOT_TIMEOUT=60
# echo-like function for writing to stderr
# $@: Message to be written to stderr
erro() {
( >&2 echo "$@" )
}
# Find mountpoint of a block device by scanning /proc/mounts
# Exits with code 1 and returns a new mountpoint if block
# device is not mounted
# $1: The block device
get_mountpoint() {
dev_check="$1"
mountpoint=$(cat /proc/mounts | while read -r mount; do
dev="$(echo "$mount" | cut -d' ' -f1)"
if [ "$dev" = "$dev_check" ]; then
echo -e "$(echo "$mount" | cut -d' ' -f2)"
break
fi
done)
if [ -z "$mountpoint" ]; then
echo /mnt"$dev_check"
return 1
fi
echo "$mountpoint"
}
# Concatenate two path components
# $1: First component
# $2: Second component
pathcat() {
base="$1"
path="$2"
echo "${base%/}/${path#/}"
}
# Make a path absolute
# Adds an absolute prefix to non-absolute paths
# $1: The path
# $2: The absolute prefix
make_abspath() {
path="$1"
prefix="$2"
case "$path" in
/*) echo "$path";;
*) pathcat "$prefix" "$path";;
esac
}
# Make an absolute path relative
# Removes an absolute prefix from an absolute path
# $1: The path
# $2: The absolute prefix
make_relpath() {
path="$1"
prefix="$2"
num_chars="${#prefix}"
suffix_start=$((num_chars + 1))
path_no_prefix="$(echo "$path" | cut -c$suffix_start-)"
echo "${path_no_prefix#/}"
}
# Find linux2boot configs on a block device
# Returns a ':'-separated list of absolute paths to linux2boot
# configs
# Exits with code 1 if the block device can not be mounted
# $1: The block device
find_configs() {
blkdev="$1"
mountpoint="$(get_mountpoint "$blkdev")"
if [ $? -ne 0 ]; then
mkdir -p "$mountpoint"
if ! mount -o ro "$blkdev" "$mountpoint"; then
return 1
fi
fi
configdir="$mountpoint"/boot/linux2boot
if ! [ -d "$configdir" ]; then
return 1
fi
echo -n "${blkdev};"
for file in "$configdir"/*.conf; do
echo -n "${file}:"
done
}
# Iterator for boot device lists
# Iterates over lists of block devices with format
# <dev1>[;<data>][|<dev2>[;data]]...[|<devN>[;data]]
# The device and associated data are passed to the
# callback as first and second argument.
# $1: '|'-serparated list of block devices
# $2: Callback function
# $@: All remaining arguments are passed to the callback function
for_each_bootdev() {
entries="$1"
callback="$2"
shift 2
IFS='|'
for device in $entries; do
if [ -z "$device" ]; then
continue
fi
blkdev=${device%%;*}
configs="${device#*;}"
$callback "$configs" "$blkdev" "$@"
done
unset IFS
}
# Interator for boot config lists
# Interates over list of boot configurations with format
# <conf1>[:<conf2>]...[:<confN>]
# The absolute path of the config file, the mountpoint and the block device
# are passed to the callback as first, second and third argument.
# $1: ':'-separated list of configs
# $2: Block device the configs are stored on
# $3: Callback function
# $@: All remaining arguments are passed to the callback function
for_each_boot_config() {
configs="$1"
blkdev="$2"
callback="$3"
shift 3
mountpoint="$(get_mountpoint "$blkdev")"
IFS=':'
for config in $configs; do
if [ -z "$config" ]; then
continue
fi
$callback "$config" "$mountpoint" "$blkdev" "$@"
done
unset IFS
}
# for_each_boot_config iterator callback
# Returns the relative config filepath
fname_handler() {
echo "$1"
}
# Dumps a list of bootentries in human-readable
# format
# $1: List of bootentries
dump_bootentries() {
entries="$(for_each_bootdev "$1" for_each_boot_config fname_handler)"
IFS=$'\n'
i=1
for entry in $entries; do
fname="$(basename "$entry")"
echo "${i}: ${fname%.conf}"
i=$((i + 1))
done
}
# Get n-th entry from list of bootentries
# Exit code 1 if entry is not found
# $1: List of bootentries
# $2: Index of bootentry
get_bootentry() {
bootentry="$(for_each_bootdev "$1" for_each_boot_config fname_handler | cut -d$'\n' -f$2)"
if [ -z "$bootentry" ]; then
return 1
fi
echo "$bootentry"
}
# for_each_boot_config callback
# Returns the boot device name
bootdev_handler() {
echo "$3"
}
# Get block device for n-th entry from list of bootentries
# $1: List of bootentries
# $2: Index of bootentry
get_bootdevice() {
bootdev="$(for_each_bootdev "$1" for_each_boot_config bootdev_handler | cut -d$'\n' -f$2)"
if [ -z "$bootdev" ]; then
return 1
fi
echo "$bootdev"
}
# Get number of bootentries in a list of bootentries
# $1: List of bootentries
num_bootentries() {
for_each_bootdev "$1" for_each_boot_config bootdev_handler | wc -l
}
# for_each_bootdev callback
# Returns the mountpoint of the current dev if mountpoint
# it is a prefix of the path passed as $3
# $3: The path
mountpoint_handler() {
configs="$1"
blkdev="$2"
file="$3"
mountpoint="$(get_mountpoint "$blkdev")"
num_chars="${#mountpoint}"
file_prefix="$(echo "$file" | cut -c-$num_chars)"
if [ "$file_prefix" = "$mountpoint" ]; then
echo "$mountpoint"
fi
}
# Find mountpoint providing a file
# Returns an empty string of mountpoint is not found
# $1: List of bootentries
# $2: The file
find_mountpoint() {
for_each_bootdev "$1" mountpoint_handler "$2"
}
# for_each_bootdev callback
# Checks a block device for a default linux2boot config and
# returns its absolute path if found
get_default_boot_entry_device() {
blkdev="$2"
mountpoint="$(get_mountpoint "$blkdev")"
defconfig="${mountpoint}/boot/linux2boot/linux2boot.default"
if [ -f "$defconfig" ]; then
echo "$defconfig"
fi
}
# Get default boot config
# Gets the newest (mtime) default boot config
# Returns an empty string if no config was found
# $1: List of bootentries
get_default_boot_entry() {
bootentries="$1"
entries="$(for_each_bootdev "$bootentries" get_default_boot_entry_device)"
if [ -z "$entries" ]; then
return
fi
default_entry="$(ls -t $entries | cut -f1)"
if [ -n "$default_entry" ]; then
unset default
mountpoint="$(find_mountpoint "$bootentries" "$default_entry")"
source "$default_entry"
pathcat "$mountpoint" "$default"
fi
}
# Update the default boot entry on a device
# $1: Mountpoint of the device
# $2: Path to the default boot config to use
set_default_boot_entry() {
mountpoint="$1"
bootconfig="$2"
if ! mount -o remount,rw "$mountpoint"; then
erro "Failed to remount boot device rw"
return 1
fi
configdir="$(dirname "$bootconfig")"
bootconfig_rel="$(make_relpath "$bootconfig" "$mountpoint")"
defconfig="$(make_abspath "$configdir" "$mountpoint")"/linux2boot.default
if ! echo "default=\"$bootconfig_rel\"" > "$defconfig"; then
erro "Failed to save default boot config"
return 1
fi
}
# Boot from a bootconfig
# Loads and kexecs a new kernel
# $1: Absolute path to the boot config
# $2: Prefix for relative paths in boot config
boot() {
bootconfig="$1"
mountpoint="$2"
unset kernel
unset initrd
unset cmdline
unset dtb
source "$bootconfig"
if [ -z "$kernel" ]; then
erro "Invalid config, missing kernel"
return 1
fi
kernel="$(make_abspath "$kernel" "$mountpoint")"
args=''
if [ -n "$initrd" ]; then
args="$args --initrd '$(make_abspath "$initrd" "$mountpoint")'"
fi
if [ -n "dtb" ]; then
args="$args --dtb '$(make_abspath "$dtb" "$mountpoint")'"
fi
if [ -n "$cmdline" ]; then
args="$args --append '$cmdline'"
fi
echo "Loading kernel..."
if eval "kexec -l $args "$kernel""; then
echo "Saving default boot config..."
if ! set_default_boot_entry "$mountpoint" "$bootconfig"; then
erro "Failed to save default boot config"
fi
echo "Starting kernel"
kexec -e
fi
}
# Boot a bootentry from a list of bootentries
# $1: List of bootentries
# $2: Index of entry to boot
boot_bootentry() {
bootconfig="$(get_bootentry "$1" "$2")"
if [ $? -ne 0 ]; then
erro "Boot entry $2 does not exist"
return 1
fi
bootdev="$(get_bootdevice "$1" "$2")"
mountpoint="$(get_mountpoint "$bootdev")"
boot "$bootconfig" "$mountpoint"
}
# Boot a boot config
# Similar to boot; figues out the mountpoint by itself
# $1: List of bootentries
# $2: Path to boot config
boot_bootconfig() {
bootentries="$1"
bootconfig="$2"
mountpoint="$(find_mountpoint "$bootentries" "$bootconfig")"
if [ -z "$mountpoint" ]; then
erro "Failed to find mountpoint for bootconfig $bootconfig"
return 1
fi
boot "$(make_abspath "$bootconfig" "$mountpoint")" "$mountpoint"
}
# Boot the default boot config
# $1: List of bootentries
# $2: Default boot entry
boot_default() {
bootconfigs="$1"
default_boot_entry="$2"
echo "Trying to boot default config from $default_boot_entry"
if ! [ -f "$default_boot_entry" ]; then
erro "Boot config $default_boot_entry does not exist"
return 1
fi
( sleep $AUTOBOOT_WAIT && boot_bootconfig "$bootconfigs" "$default_boot_entry" ||\
erro "Autoboot failed, press [ENTER] to continue to boot menu" ) &
autoboot_pid=$!
echo "Press [ENTER] within $AUTOBOOT_WAIT seconds to stop autoboot..."
read _
kill $autoboot_pid 2> /dev/null
}
echo "Welcome to linux2boot"
unset bootconfigs
bootconfigs=$(blkid | while read -r blkinfo; do
blkdev="$(echo "$blkinfo" | cut -d':' -f1)"
erro "Scanning $blkdev for boot configurations..."
find_configs "$blkdev" && echo -n '|'
done)
echo "Found $(num_bootentries "$bootconfigs") boot entries:"
dump_bootentries "$bootconfigs"
default_boot_entry="$(get_default_boot_entry "$bootconfigs")"
if [ -n "$default_boot_entry" ]; then
boot_default "$bootconfigs" "$default_boot_entry"
fi
echo "Enter number of a boot entry to boot. Enter 'x' to execute a shell"
while read line; do
case "$line" in
x)
echo "Starting a shell, use 'exit' to get back to the boot menu"
sh
exit;;
d)
dump_bootentries "$bootconfigs";;
[0-9]*)
boot_bootentry "$bootconfigs" "$line";;
*)
echo Invalid input;;
esac
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment