Skip to content

Commit

Permalink
zfsbootmenu: log errors for unimportable pools
Browse files Browse the repository at this point in the history
Before landing at the emergency_shell in the main import loop, log the
output of a failed zpool import for every pool that's marked UNAVAIL .

In the emergency_shell function, write any log messages at the 'err'
level directly to the active console.

The 'print_kmsg_logs' helper has been added that accepts a
comma-separated list of log levels (as understood by util-linux dmesg).
If the feature flags supplied to dmesg fail, fall back to 'dmesg -r'
which is understood by both Busybox and util-linux dmesg. Manually
convert err,info,... log levels to a numeric code and grep for that at
the start of each line.
  • Loading branch information
zdykstra committed Sep 16, 2023
1 parent 2762b7e commit bd1319f
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 8 deletions.
9 changes: 2 additions & 7 deletions zfsbootmenu/bin/zlogtail
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

# shellcheck disable=SC1091
source /lib/zfsbootmenu-lib.sh >/dev/null 2>&1 || exit 1
source /lib/kmsg-log-lib.sh >/dev/null 2>&1 || exit 1

[ -f "${BASE}/have_errors" ] && rm "${BASE}/have_errors"
[ -f "${BASE}/have_warnings" ] && rm "${BASE}/have_warnings"
Expand Down Expand Up @@ -29,10 +30,4 @@ fi

export FZF_DEFAULT_OPTS="${fuzzy_default_options[*]}"

# Try to use feature flags found on dmesg from util-linux
if output="$( dmesg --notime -f user -l err,warn 2>/dev/null )" ; then
echo "${output}" | ${FUZZYSEL}
else
# fall back to manually parsing dmesg output; will show all ZBM generated logs up to zinfo level
dmesg | awk '/ZFSBootMenu:/{ for (i=3; i<=NF; i++){ printf("%s ", $i)}; printf "\n" }' | ${FUZZYSEL}
fi
print_kmsg_logs "err,warn" | ${FUZZYSEL}
55 changes: 55 additions & 0 deletions zfsbootmenu/lib/kmsg-log-lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,58 @@ zerror() {
: > "${BASE}/have_errors"
echo "<3>ZFSBootMenu: $1" > /dev/kmsg
}

# arg1: comma-separated log levels to print
# prints: all logs at that level
# returns: nothing

print_kmsg_logs() {
local levels levels_array grep_args

levels="${1}"
if [ -z "${levels}" ]; then
zerror "levels is undefined"
return
fi

# Try to use feature flags found in dmesg from util-linux
if output="$( dmesg --notime -f user --color=always -l "${levels}" 2>/dev/null )" ; then
echo -e "${output}"
else
# Both util-linux and Busybox dmesg support the -r flag. However, the log level that is
# reported by Busybox dmesg is larger than that reported by util-linux dmesg. Busybox dmesg
# is too bare-bones to do much of anything, so we just need to grep for both integers at
# a given log level, then refly on matching ZFSBootMenu for info and lower, and ZBM for debug.

IFS=',' read -r -a levels_array <<<"${levels}"
for level in "${levels_array[@]}"; do
case "${level}" in
err)
grep_args+=( "-e" "^<11>" "-e" "^<3>" )
;;
warn)
grep_args+=( "-e" "^<12>" "-e" "^<4>" )
;;
notice)
grep_args+=( "-e" "^<13>" "-e" "^<5>" )
;;
info)
grep_args+=( "-e" "^<14>" "-e" "^<6>" )
;;
debug)
grep_args+=( "-e" "^<15>" "-e" "^<7>" )
;;
*)
grep_args+=( "-e" "." )
;;
esac
done

dmesg -r | grep "${grep_args[@]}" \
| awk '
/ZFSBootMenu:/{ for (i=3; i<=NF; i++){ printf("%s ", $i)}; printf "\n" }
/ZBM:/{ for (i=3; i<=NF; i++){ printf("%s ", $i)}; printf "\n" }
'
fi
}

39 changes: 38 additions & 1 deletion zfsbootmenu/lib/zfsbootmenu-core.sh
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,38 @@ match_hostid() {
return 1
}

# args: no arguments
# prints: nothing
# returns: nothing

log_unimportable() {
local pool id state line error_line

while read -r line; do
case "${line}" in
pool*)
pool="${line#pool: }"
;;
id*)
id="${line#id: }"
;;
state*)
state="${line#state: }"
if [ "${state}" == "UNAVAIL" ]; then
while read -r error_line; do
zerror "${error_line}"
done <<<"$( zpool import -N "${id}" 2>&1 )"
fi
state=
pool=
id=
;;
*)
;;
esac
done <<<"$( zpool import )"
}

# args: none
# prints: nothing
# returns: 0 if at least one pool is available
Expand Down Expand Up @@ -1815,7 +1847,12 @@ emergency_shell() {
EOF

command -v efibootmgr >/dev/null 2>&1 && mount_efivarfs "rw"
if [ -f "${BASE}/have_errors" ]; then
print_kmsg_logs "err"
echo ""
fi

command -v efibootmgr >/dev/null 2>&1 && mount_efivarfs "rw"

# -i (interactive) mode will source $HOME/.bashrc
/bin/bash -i
Expand Down
2 changes: 2 additions & 0 deletions zfsbootmenu/libexec/zfsbootmenu-init
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ fi
# If a boot pool is specified, that will be tried first
try_pool="${boot_pool}"
zbm_import_attempt=0

while true; do
if [ -n "${try_pool}" ]; then
zdebug "attempting to import preferred pool ${try_pool}"
Expand Down Expand Up @@ -173,6 +174,7 @@ while true; do
continue
fi

log_unimportable
# Allow the user to attempt recovery
emergency_shell "unable to successfully import a pool"
done
Expand Down

0 comments on commit bd1319f

Please sign in to comment.