zfsbootmenu: refactor installation, support system and user runtime hooks

- All core components, docs and runtime hooks are handled by a common
  install_helper that is aware of both dracut and mkinitcpio methods

- Installation now adds "system" runtime hooks in addition to those
  configured by the user; user hooks with names that conflict with
  system hooks will "mask" the system version

- The hooks `10-console-init.sh` and `xhci-teardown.sh` have been
  elevated to "system" status and are always installed in ZBM images

- The `zfsbootmenu/hook` path has been renamed to `zfsbootmenu/pre-init`
  to avoid confusion with the new `zfsbootmenu/hooks` directory of
  system runtime hooks, and the dracut-only `hook` scripts have been
  moved to the dracut module

- A new `zbm.skip_hooks` command-line argument allows a comma-separated
  list of hooks to skip (matched on basename only) to provide an easy
  mechanism for skipping default hooks without `zbm.hookroot`

- Create the `zfsbootmenu-run-hooks` libexec helper, allowing easy
  addition of future hook points.

Co-authored-by: Zach Dykstra <dykstra.zachary@gmail.org>
Co-authored-by: Andrew J. Hesford <ajh@sideband.org>
This commit is contained in:
Zach Dykstra 2023-06-19 11:27:58 -05:00 committed by Andrew J. Hesford
parent e5cfb463b0
commit ff5956298a
15 changed files with 230 additions and 140 deletions

View File

@ -133,6 +133,12 @@ These options are set on the kernel command line when booting the initramfs or U
zbm.kcl_override="some alternate set='of arguments'"
**zbm.skip_hooks=<hooklist>**
Skip execution of any early-setup, setup or teardown hooks with file names matching any entry in the comma-separated list *hooklist*. Only base names of hooks (*i.e.*, with any other path component removed) are matched against the *hooklist*.
**NOTE**: The *hooklist* argument **MUST NOT** contain spaces and **MUST NOT** be enclosed in quotes.
Deprecated Command-Line Parameters
==================================

View File

@ -40,8 +40,9 @@ install() {
# BUILDROOT is an initcpio-ism
# shellcheck disable=SC2154,2034
BUILDROOT="${initdir}"
# shellcheck disable=SC2034
BUILDSTYLE="dracut"
ZBM_BUILDSTYLE="dracut"
local _rule _exec _ret
@ -84,90 +85,43 @@ install() {
fi
done <<< "${_libgcc_s}"
# shellcheck disable=SC2154
while read -r doc ; do
relative="${doc//${zfsbootmenu_module_root}\//}"
inst_simple "${doc}" "/usr/share/docs/${relative}"
done <<<"$( find "${zfsbootmenu_module_root}/help-files" -type f )"
compat_dirs=( "/etc/zfs/compatibility.d" "/usr/share/zfs/compatibility.d/" )
for compat_dir in "${compat_dirs[@]}"; do
# shellcheck disable=2164
[ -d "${compat_dir}" ] && tar -cf - "${compat_dir}" | ( cd "${initdir}" ; tar xfp - )
done
_ret=0
# Core ZFSBootMenu functionality
# shellcheck disable=SC2154
for _lib in "${zfsbootmenu_module_root}"/lib/*; do
inst_simple "${_lib}" "/lib/$( basename "${_lib}" )" || _ret=$?
done
# Install core ZFSBootMenu functionality
install_zbm_core || _ret=$?
# Helper tools not intended for direct human consumption
for _libexec in "${zfsbootmenu_module_root}"/libexec/*; do
inst_simple "${_libexec}" "/libexec/$( basename "${_libexec}" )" || _ret=$?
done
# User-facing utilities, useful for running in a recovery shell
for _bin in "${zfsbootmenu_module_root}"/bin/*; do
inst_simple "${_bin}" "/bin/$( basename "${_bin}" )" || _ret=$?
done
# Install runtime hooks
install_zbm_hooks || _ret=$?
# Hooks necessary to initialize ZBM
inst_hook cmdline 95 "${zfsbootmenu_module_root}/hook/zfsbootmenu-parse-commandline.sh" || _ret=$?
inst_hook pre-mount 90 "${zfsbootmenu_module_root}/hook/zfsbootmenu-preinit.sh" || _ret=$?
inst_hook cmdline 95 "${zfsbootmenu_module_root}/pre-init/zfsbootmenu-parse-commandline.sh" || _ret=$?
inst_hook pre-mount 90 "${zfsbootmenu_module_root}/pre-init/zfsbootmenu-preinit.sh" || _ret=$?
# Hooks to force the dracut event loop to fire at least once
# Things like console configuration are done in optional event-loop hooks
inst_hook initqueue/settled 99 "${zfsbootmenu_module_root}/hook/zfsbootmenu-ready-set.sh" || _ret=$?
inst_hook initqueue/finished 99 "${zfsbootmenu_module_root}/hook/zfsbootmenu-ready-chk.sh" || _ret=$?
# optionally enable early Dracut profiling
if [ -n "${dracut_trace_enable}" ]; then
inst_hook cmdline 00 "${zfsbootmenu_module_root}/profiling/profiling-lib.sh"
fi
# Install "early setup" hooks
# shellcheck disable=SC2154
if [ -n "${zfsbootmenu_early_setup}" ]; then
for _exec in ${zfsbootmenu_early_setup}; do
if [ -x "${_exec}" ]; then
inst_simple "${_exec}" "/libexec/early-setup.d/$(basename "${_exec}")" || _ret=$?
else
dwarning "setup script (${_exec}) missing or not executable; cannot install"
fi
done
fi
# Install "setup" hooks
# shellcheck disable=SC2154
if [ -n "${zfsbootmenu_setup}" ]; then
for _exec in ${zfsbootmenu_setup}; do
if [ -x "${_exec}" ]; then
inst_simple "${_exec}" "/libexec/setup.d/$(basename "${_exec}")" || _ret=$?
else
dwarning "setup script (${_exec}) missing or not executable; cannot install"
fi
done
fi
# Install "teardown" hooks
# shellcheck disable=SC2154
if [ -n "${zfsbootmenu_teardown}" ]; then
for _exec in ${zfsbootmenu_teardown}; do
if [ -x "${_exec}" ]; then
inst_simple "${_exec}" "/libexec/teardown.d/$(basename "${_exec}")" || _ret=$?
else
dwarning "teardown script (${_exec}) missing or not executable; cannot install"
fi
done
fi
inst_hook initqueue/settled 99 "${moddir}/zfsbootmenu-ready-set.sh" || _ret=$?
inst_hook initqueue/finished 99 "${moddir}/zfsbootmenu-ready-chk.sh" || _ret=$?
if [ ${_ret} -ne 0 ]; then
dfatal "Unable to install core ZFSBootMenu functions"
exit 1
fi
# Install online documentation if possible
install_zbm_docs
# optionally enable early Dracut profiling
if [ -n "${dracut_trace_enable}" ]; then
inst_hook cmdline 00 "${zfsbootmenu_module_root}/profiling/profiling-lib.sh"
fi
# vdev_id.conf and hostid files are host-specific
# and do not belong in public release images
if [ -z "${release_build}" ]; then

View File

@ -1,5 +1,4 @@
zfsbootmenu_teardown+=" /zbm/contrib/xhci-teardown.sh "
zfsbootmenu_early_setup+=" /zbm/contrib/10-console-init.sh /zbm/contrib/20-console-autosize.sh "
zfsbootmenu_early_setup+=" /zbm/contrib/20-console-autosize.sh "
# zbm-kcl
install_optional_items+=" /zbm/bin/zbm-kcl "

View File

@ -122,7 +122,7 @@ build() {
source "${zfsbootmenu_module_root}/install-helpers.sh" || exit 1
# shellcheck disable=SC2034
BUILDSTYLE="initcpio"
ZBM_BUILDSTYLE="mkinitcpio"
# Modules (required and optional) used by ZBM
# shellcheck disable=SC2154
@ -154,52 +154,32 @@ build() {
fi
done <<< "${_libgcc_s}"
# On-line documentation
while read -r doc; do
relative="${doc#"${zfsbootmenu_module_root}/"}"
[ "${relative}" = "${doc}" ] && continue
add_file "${doc}" "/usr/share/docs/${relative}"
done <<< "$( find "${zfsbootmenu_module_root}/help-files" -type f )"
# Install core ZBM functionality
for _file in "${zfsbootmenu_module_root}"/lib/*; do
add_file "${_file}" "/lib/${_file##*/}"
done
if ! install_zbm_core; then
error "Failed to install ZFSBootMenu core"
exit 1
fi
for _file in "${zfsbootmenu_module_root}"/libexec/*; do
add_file "${_file}" "/libexec/${_file##*/}"
done
# Install runtime hooks
if ! install_zbm_hooks; then
error "Failed to install runtime hooks"
exit 1
fi
for _file in "${zfsbootmenu_module_root}"/bin/*; do
add_file "${_file}" "/bin/${_file##*/}"
done
# Install online documentation if possible
install_zbm_docs
hooks=( zfsbootmenu-{parse-commandline,preinit}.sh )
for _file in "${hooks[@]}"; do
add_file "${zfsbootmenu_module_root}/hook/${_file}" "/lib/${_file}"
# Install pre-init scripts
for _file in "${zfsbootmenu_module_root}"/pre-init/*; do
add_file "${_file}" "/lib/${_file##*/}" && continue;
error "Failed to install ZFSBootMenu component ${_file}"
exit 1
done
# allow mount(8) to "autodetect" ZFS
echo 'zfs' >>"${BUILDROOT}/etc/filesystems"
# shellcheck disable=SC2154
for _file in "${zfsbootmenu_early_setup[@]}"; do
[ -x "${_file}" ] || continue
add_file "${_file}" "/libexec/early-setup.d/${_file##*/}"
done
# shellcheck disable=SC2154
for _file in "${zfsbootmenu_setup[@]}"; do
[ -x "${_file}" ] || continue
add_file "${_file}" "/libexec/setup.d/${_file##*/}"
done
# shellcheck disable=SC2154
for _file in "${zfsbootmenu_teardown[@]}"; do
[ -x "${_file}" ] || continue
add_file "${_file}" "/libexec/teardown.d/${_file##*/}"
done
compat_dirs=( "/etc/zfs/compatibility.d" "/usr/share/zfs/compatibility.d/" )
for compat_dir in "${compat_dirs[@]}"; do
[ -d "${compat_dir}" ] && add_full_dir "${compat_dir}"

View File

@ -68,14 +68,8 @@ if [ -r "${BASE}/bootfs" ]; then
fi
# Run setup hooks, if they exist
if [ -d /libexec/setup.d ]; then
tput clear
for _hook in /libexec/setup.d/*; do
zinfo "Processing hook: ${_hook}"
[ -x "${_hook}" ] && "${_hook}"
done
unset _hook
fi
tput clear
/libexec/zfsbootmenu-run-hooks "setup.d"
# Clear screen before a possible password prompt
tput clear

View File

@ -6,6 +6,10 @@
## which causes the normal dracut initqueue to be thrown out; the ZFSBootMenu
## initqueue hooks that force the dracut event loop to run at least once are
## purged, so dracut terminates the event loop before console initialization.
##
# There is nothing to do if we're not in dracut
[ "${ZBM_BUILDSTYLE,,}" = "dracut" ] || exit 0
# There is nothing to do if the console initializer is not executable
[ -x /lib/udev/console_init ] || exit 0

View File

@ -100,6 +100,13 @@ create_zbm_conf() {
has_column=1
fi
# Normalize ZBM_BUILDSTYLE, if set
case "${ZBM_BUILDSTYLE,,}" in
mkinitcpio) ZBM_BUILDSTYLE="mkinitcpio" ;;
dracut) ZBM_BUILDSTYLE="dracut" ;;
*) ZBM_BUILDSTYLE="" ;;
esac
cat > "${BUILDROOT}/etc/zfsbootmenu.conf" <<-'EOF'
# Include guard
[ -n "${_ETC_ZFSBOOTMENU_CONF}" ] && return
@ -111,6 +118,7 @@ create_zbm_conf() {
export HAS_DISABLED="${has_disabled}"
export HAS_BORDER="${has_border}"
export HAS_COLUMN="${has_column}"
export ZBM_BUILDSTYLE="${ZBM_BUILDSTYLE}"
EOF
}
@ -149,6 +157,27 @@ create_zbm_profiles() {
ln -s "/root/.bashrc" "${BUILDROOT}/root/.profile"
}
zbm_install_file() {
case "${ZBM_BUILDSTYLE,,}" in
mkinitcpio)
if ! add_file "${1}" "${2}"; then
error "failed to install file '${1}'"
return 1
fi
;;
dracut)
if ! inst_simple "${1}" "${2}"; then
dfatal "failed to install file '${1}'"
return 1
fi
;;
*)
echo "ERROR: unrecognized build style; unable to install files" >&2
return 1
;;
esac
}
create_zbm_traceconf() {
local zbm_prof_lib
@ -167,16 +196,7 @@ create_zbm_traceconf() {
return
fi
case "${BUILDSTYLE}" in
initcpio)
add_file "${zbm_prof_lib}" "/lib/profiling-lib.sh"
;;
dracut)
inst_simple "${zfsbootmenu_module_root}/profiling/profiling-lib.sh" "/lib/profiling-lib.sh"
;;
*)
;;
esac
zbm_install_file "${zbm_prof_lib}" "/lib/profiling-lib.sh"
# shellcheck disable=SC2154
cat > "${BUILDROOT}/etc/profiling.conf" <<-EOF
@ -185,6 +205,101 @@ create_zbm_traceconf() {
EOF
}
install_zbm_core() {
local cdir cfile ret
ret=0
for cdir in lib libexec bin; do
for cfile in "${zfsbootmenu_module_root}/${cdir}"/*; do
zbm_install_file "${cfile}" "/${cdir}/${cfile##*/}" || ret=$?
done
done
return $ret
}
install_zbm_docs() {
local doc relative ret
ret=0
while read -r doc; do
relative="${doc#"${zfsbootmenu_module_root}"}"
[ "${relative}" = "${doc}" ] && continue
relative="${relative#/}"
zbm_install_file "${doc}" "/usr/share/docs/${relative}" || ret=$?
done <<< "$( find "${zfsbootmenu_module_root}/help-files" -type f )"
return $ret
}
populate_hook_dir() {
local hfile ret hlev
hlev="${1}"
if [ -z "${hlev}" ]; then
echo "ERROR: a hook level is required" >&2
return 1
fi
shift
[ "$#" -gt 0 ] || return 0
mkdir -p "${BUILDROOT}/libexec/${hlev}" || return 1
ret=0
for hfile in "$@"; do
[ -x "${hfile}" ] || continue
zbm_install_file "${hfile}" "/libexec/${hlev}/${hfile##*/}" || ret=$?
done
return $ret
}
install_zbm_hooks() {
local hdir hsrc hfile ret
ret=0
# Install system hooks first
for hdir in early-setup.d setup.d teardown.d; do
hsrc="${zfsbootmenu_module_root}/hooks/${hdir}"
[ -d "${hsrc}" ] || continue
populate_hook_dir "${hdir}" "${hsrc}"/* || ret=$?
done
# Next, install user hooks to allow them to override system versions
# shellcheck disable=SC2154
if [[ "${zfsbootmenu_early_setup@a}" != *a* ]]; then
# shellcheck disable=SC2086
populate_hook_dir "early-setup.d" ${zfsbootmenu_early_setup} || ret=$?
else
populate_hook_dir "early-setup.d" "${zfsbootmenu_early_setup[@]}" || ret=$?
fi
# shellcheck disable=SC2154
if [[ "${zfsbootmenu_setup@a}" != *a* ]]; then
# shellcheck disable=SC2086
populate_hook_dir "setup.d" ${zfsbootmenu_setup} || ret=$?
else
populate_hook_dir "setup.d" "${zfsbootmenu_setup[@]}" || ret=$?
fi
# shellcheck disable=SC2154
if [[ "${zfsbootmenu_teardown@a}" != *a* ]]; then
# shellcheck disable=SC2086
populate_hook_dir "teardown.d" ${zfsbootmenu_teardown} || ret=$?
else
populate_hook_dir "teardown.d" "${zfsbootmenu_teardown[@]}" || ret=$?
fi
return $ret
}
find_libgcc_s() {
local f libdirs libbase ldir zlibs matched

View File

@ -273,7 +273,7 @@ mount_zfs() {
# returns: 1 on error, otherwise does not return
kexec_kernel() {
local selected fs kernel initramfs tdhook output
local selected fs kernel initramfs output
selected="${1}"
if [ -z "${selected}" ]; then
@ -332,15 +332,10 @@ kexec_kernel() {
done <<<"$( zpool list -H -o name )"
# Run teardown hooks, if they exist
if [ -d /libexec/teardown.d ]; then
for tdhook in /libexec/teardown.d/*; do
[ -x "${tdhook}" ] || continue
zinfo "Processing hook: ${tdhook}"
env "ZBM_SELECTED_INITRAMFS=${initramfs}" \
"ZBM_SELECTED_KERNEL=${kernel}" "ZBM_SELECTED_BE=${fs}" "${tdhook}"
done
unset tdhook
fi
env "ZBM_SELECTED_INITRAMFS=${initramfs}" \
"ZBM_SELECTED_KERNEL=${kernel}" \
"ZBM_SELECTED_BE=${fs}" \
/libexec/zfsbootmenu-run-hooks "teardown.d"
if ! output="$( kexec -e -i 2>&1 )"; then
zerror "kexec -e -i failed!"

View File

@ -83,16 +83,28 @@ if [ -n "${zbm_hook_root}" ]; then
import_zbm_hooks "${zbm_hook_root}"
fi
# Run early setup hooks, if they exist
if [ -d /libexec/early-setup.d ]; then
tput clear
for _hook in /libexec/early-setup.d/*; do
zinfo "Processing hook: ${_hook}"
[ -x "${_hook}" ] && "${_hook}"
# Remove the executable bit from any hooks in the skip list
if zbm_skip_hooks="$( get_zbm_arg zbm.skip_hooks )" && [ -n "${zbm_skip_hooks}" ]; then
zdebug "processing hook skip directives: ${zbm_skip_hooks}"
IFS=',' read -r -a zbm_skip_hooks <<<"${zbm_skip_hooks}"
for _skip in "${zbm_skip_hooks[@]}"; do
[ -n "${_skip}" ] || continue
for _hook in /libexec/{early-setup,setup,teardown}.d/*; do
[ -e "${_hook}" ] || continue
if [ "${_skip}" = "${_hook##*/}" ]; then
zinfo "Disabling hook: ${_hook}"
chmod 000 "${_hook}"
fi
done
done
unset _hook
unset _hook _skip
fi
# Run early setup hooks, if they exist
tput clear
/libexec/zfsbootmenu-run-hooks "early-setup.d"
# Prefer a specific pool when checking for a bootfs value
# shellcheck disable=SC2154
if [ "${root}" = "zfsbootmenu" ]; then

View File

@ -0,0 +1,31 @@
#!/bin/bash
# vim: softtabstop=2 shiftwidth=2 expandtab
# shellcheck disable=SC1091
sources=(
/lib/profiling-lib.sh
/etc/zfsbootmenu.conf
/lib/kmsg-log-lib.sh
)
for src in "${sources[@]}"; do
# shellcheck disable=SC1090
source "${src}" >/dev/null 2>&1 || exit 1
done
hook_stage="${1}"
if [ -z "${hook_stage}" ]; then
zerror "required hook stage undefined"
exit 1
fi
if [ -d "/libexec/${hook_stage}" ]; then
for _hook in /libexec/"${hook_stage}"/*; do
if [ -x "${_hook}" ]; then
zinfo "processing ${_hook}"
"${_hook}"
fi
done
fi