Skip to content

Commit

Permalink
Allow zfsbootmenu to takeover a running instance
Browse files Browse the repository at this point in the history
Instead of requiring a user to rm /zfsbootmenu/active, running 'zfsbootmenu' or 'zbm' from the recovery shell will automatically stop an already-running copy of /bin/zfsbootmenu. It will also block the auto-boot countdown sequence that might be executing on the main tty (tty0, ttyS0, etc). 

If the running instance can't be killed for some reason (/zfsbootmenu/active still exists), zfsbootmenu will spin with the option to exit to a recovery shell until that file is removed.
  • Loading branch information
zdykstra committed Jul 17, 2021
1 parent c351624 commit 687295a
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 16 deletions.
19 changes: 12 additions & 7 deletions 90zfsbootmenu/zfsbootmenu-init.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,6 @@ if ! is_lib_sourced > /dev/null 2>&1 ; then
exec /bin/bash
fi

if [ -z "${BASE}" ]; then
export BASE="/zfsbootmenu"
fi

mkdir -p "${BASE}"

# Attempt to load spl normally
Expand Down Expand Up @@ -189,11 +185,14 @@ fi
: > "${BASE}/initialized"

# If BOOTFS is not empty display the fast boot menu
if [ -n "${BOOTFS}" ]; then
# shellcheck disable=SC2154
if [ "${menu_timeout}" -ge 0 ] && [ -n "${BOOTFS}" ]; then
# Draw a countdown menu
# shellcheck disable=SC2154
if [ "${menu_timeout}" -ge 0 ]; then
if delay="${menu_timeout}" prompt="Booting ${BOOTFS} in %0.${#menu_timeout}d seconds" timed_prompt "[ENTER] to boot" "[ESC] boot menu" ; then
if delay="${menu_timeout}" prompt="Booting ${BOOTFS} in %0.${#menu_timeout}d seconds" timed_prompt "[ENTER] to boot" "[ESC] boot menu" ; then
# This lock file is present if someone has SSH'd to take control
# Do not attempt to automatically boot if present
if [ ! -e "${BASE}/active" ] ; then
# Clear screen before a possible password prompt
tput clear
if ! NO_CACHE=1 load_key "${BOOTFS}"; then
Expand All @@ -206,6 +205,12 @@ if [ -n "${BOOTFS}" ]; then
fi
fi

# If the lock file is present, drop to a recovery shell to avoid
# stealing control back from an SSH session
if [ -e "${BASE}/active" ] ; then
emergency_shell "type 'exit' to return to ZFSBootMenu"
fi

while true; do
if [ -x /bin/zfsbootmenu ]; then
/bin/zfsbootmenu
Expand Down
59 changes: 58 additions & 1 deletion 90zfsbootmenu/zfsbootmenu-lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ zlog() {
[ "${1}" -le "${loglevel:=4}" ] || return

# shellcheck disable=SC2086
_script="$( basename $0 )"
_script="$( basename -- $0 )"
_func="${FUNCNAME[2]}"

# Only add script/function tracing to debug messages
Expand Down Expand Up @@ -2095,6 +2095,7 @@ emergency_shell() {

tput clear
tput cnorm
stty echo

echo -n "Launching emergency shell: "
echo -e "${message}\n"
Expand All @@ -2120,6 +2121,62 @@ zbmcmdline() {
[ -f "${BASE}/zbm.cmdline" ] && echo | cat "${BASE}/zbm.cmdline" -
}

# arg1: pid
# prints: child pid
# returns: 0 if a child pid was found, 1 if there are no children

find_child_pid() {
local pid child

pid="${1}"
if [ -z "${pid}" ]; then
zdebug "empty pid"
return 1
fi

if [ -e "/proc/${pid}/task/${pid}/children" ] ; then
read -r child < "/proc/${pid}/task/${pid}/children"

if [ -n "${child}" ] ; then
echo "${child}"
return 0
fi
fi

return 1
}

# prints: nothing
# returns: nothing

takeover() {
local pid child

if [ -e "${BASE}/active" ] ; then
read -r pid < "${BASE}/active"
parent=${pid}

zinfo "Stopping active zfsbootmenu with a PID of ${pid}"

# Trip the USR1 handler in /bin/zfsbootmenu - 'exit 0'
zdebug "sending USR1 to ${parent}"
kill -USR1 "${parent}"

# find the last child process of the active /bin/zfsbootmenu
while child="$( find_child_pid "${parent}" )" ; do
if [ -n "${child}" ] ; then
parent=${child}
continue
fi
break
done

# Kill the blocking child so that the USR1 handler actually executes
zdebug "killing child process ${parent}"
[ -n "${parent}" ] && kill "${parent}"
fi
}

# prints: nothing
# returns: 0

Expand Down
1 change: 1 addition & 0 deletions 90zfsbootmenu/zfsbootmenu-preinit.sh
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ mkdir -p "${BASE}"
# shellcheck disable=SC2154
cat >> "/etc/profile" <<EOF
# Added by zfsbootmenu-preinit.sh
export BASE="/zfsbootmenu"
export endian="${endian}"
export spl_hostid="${spl_hostid}"
export import_policy="${import_policy}"
Expand Down
15 changes: 7 additions & 8 deletions 90zfsbootmenu/zfsbootmenu.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,6 @@ fi
# Make sure /dev/zfs exists, otherwise drop to a recovery shell
[ -e /dev/zfs ] || emergency_shell "/dev/zfs missing, check that kernel modules are loaded"

if [ -z "${BASE}" ]; then
export BASE="/zfsbootmenu"
fi

mkdir -p "${BASE}"

while [ ! -e "${BASE}/initialized" ]; do
Expand All @@ -34,8 +30,11 @@ while [ ! -e "${BASE}/initialized" ]; do
fi
done

[ -e "${BASE}/active" ] && takeover

# If the takeover fails for some reason, spin until it ends
while [ -e "${BASE}/active" ]; do
if ! delay=5 prompt="Press [ESC] to cancel" timed_prompt "Waiting for other ZFSBootMenu instace to terminate"; then
if ! delay=1 prompt="Press [ESC] to cancel" timed_prompt "Waiting for other ZFSBootMenu instance to terminate"; then
zdebug "exited while waiting to own ${BASE}/active"
tput cnorm
tput clear
Expand All @@ -44,11 +43,13 @@ while [ -e "${BASE}/active" ]; do
done

# Prevent conflicting use of the boot menu
: > "${BASE}/active"
echo "$$" > "${BASE}/active"
zdebug "creating ${BASE}/active"

# shellcheck disable=SC2064
trap "rm -f '${BASE}/active'" EXIT
trap "zdebug 'exiting via USR1 signal' ; tput clear ; exit 0" SIGUSR1
trap '' SIGINT

if [ -r "${BASE}/bootfs" ]; then
read -r BOOTFS < "${BASE}/bootfs"
Expand All @@ -66,8 +67,6 @@ if [ -d /libexec/setup.d ]; then
unset _hook
fi

trap '' SIGINT

# shellcheck disable=SC2016
fuzzy_default_options=( "--ansi" "--no-clear"
"--layout=reverse-list" "--inline-info" "--tac" "--color=16"
Expand Down

0 comments on commit 687295a

Please sign in to comment.