Skip to content

Commit 73cd9f7

Browse files
committed
zfsbootmenu: support per-kernel command-line arguments
1 parent 422010f commit 73cd9f7

6 files changed

Lines changed: 140 additions & 31 deletions

File tree

zfsbootmenu/bin/zfsbootmenu

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -258,10 +258,13 @@ while true; do
258258
tput clear
259259
tput cnorm
260260

261+
IFS=$'\t' read -r _ kernel _ <<< "$( select_kernel "${selected_be}" || echo $'-\t-\t-' )"
262+
[ "${kernel}" = "-" ] && kernel=""
263+
261264
echo ""
262-
/libexec/zfsbootmenu-preview "${selected_be}" "${BOOTFS}"
265+
/libexec/zfsbootmenu-preview -b "${BOOTFS}" -k "${kernel}" "${selected_be}"
263266

264-
BE_ARGS="$( load_be_cmdline "${selected_be}" )"
267+
BE_ARGS="$( load_be_cmdline "${selected_be}" "${kernel}" )"
265268
while IFS= read -r line; do
266269
def_args="${line}"
267270
done <<< "${BE_ARGS}"

zfsbootmenu/bin/zsnapshots

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ if [ -z "${fs}" ]; then
4747
${HAS_BORDER:+--preview-label-pos=2:bottom} \
4848
${HAS_BORDER:+--preview-label="$( colorize orange " ${preview_label} " )"} \
4949
--preview-window="up:${PREVIEW_HEIGHT}${HAS_BORDER:+,border-sharp}" \
50-
--preview="/libexec/zfsbootmenu-preview {} '${BOOTFS}'" <<< "${candidates}"
50+
--preview="/libexec/zfsbootmenu-preview -b '${BOOTFS}' {}" <<< "${candidates}"
5151
)"; then
5252
tput clear
5353
exit 0;

zfsbootmenu/lib/zfsbootmenu-core.sh

Lines changed: 69 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,7 @@ mount_zfs() {
329329
fi
330330

331331
# zfsutil is required for non-legacy mounts and omitted for legacy mounts or snapshots
332-
if [ "$(zfs get -H -o value mountpoint "${fs}")" = "legacy" ] || is_snapshot "${fs}" ; then
332+
if [ "$( zfs get -H -o value mountpoint "${fs}" )" = "legacy" ] || is_snapshot "${fs}" ; then
333333
zdebug "mounting ${fs} at ${mnt} (${rwo})"
334334
mount -o "${rwo}" -t zfs "${fs}" "${mnt}"
335335
ret=$?
@@ -388,7 +388,7 @@ kexec_kernel() {
388388
ZBM_SELECTED_MOUNTPOINT="${mnt}" \
389389
/libexec/zfsbootmenu-run-hooks "boot-sel.d"
390390

391-
cli_args="$( load_be_cmdline "${fs}" )"
391+
cli_args="$( load_be_cmdline "${fs}" "${kernel}" )"
392392
root_prefix="$( find_root_prefix "${fs}" "${mnt}" )"
393393

394394
dtb_prop="$( zfs get -H -o value org.zfsbootmenu:devicetree "${fs}" 2>/dev/null )"
@@ -866,7 +866,7 @@ find_be_initramfs() {
866866

867867
find_be_kernels() {
868868
local fs mnt
869-
local kpath ipath kernel_records
869+
local kpath ipath kernel_records kcl_path
870870

871871
fs="${1}"
872872
if [ -z "${fs}" ]; then
@@ -885,6 +885,13 @@ find_be_kernels() {
885885
kernel_records="${mnt%/*}/kernels"
886886
: > "${kernel_records}"
887887

888+
# Clear any existing KCL cache before enumerating kernels
889+
if kcl_path="$( kernel_kcl_cache "${fs}" "." )"; then
890+
kcl_path="${kcl_path%/..kcl}"
891+
mkdir -p "${kcl_path}"
892+
rm -f "${kcl_path}"/*.kcl
893+
fi
894+
888895
# Look for kernels and matching initramfs, sorted in version order
889896
while read -r kpath; do
890897
# Strip mount point from path
@@ -900,6 +907,12 @@ find_be_kernels() {
900907
else
901908
zdebug "kernel ${mnt}${kpath} has no initramfs"
902909
fi
910+
911+
if [ -r "${mnt}/${kpath}.kcl" ]; then
912+
if kcl_path="$( kernel_kcl_cache "${fs}" "${kpath}" )"; then
913+
cp "${mnt}/${kpath}.kcl" "${kcl_path}"
914+
fi
915+
fi
903916
done <<<"$(
904917
for k in "${mnt}/boot"/{{vm,}linu{x,z},kernel}{,-*}; do
905918
[ -e "${k}" ] && echo "${k}"
@@ -1060,11 +1073,42 @@ validate_cmdline_cache() {
10601073
}
10611074

10621075
# arg1: ZFS filesystem
1076+
# arg2: optional path to kernel
1077+
# prints: path to kernel-specific KCL cache
1078+
# returns: 0 on success
1079+
1080+
kernel_kcl_cache() {
1081+
local fs kernel
1082+
1083+
fs="${1}"
1084+
if [ -z "${fs}" ]; then
1085+
zerror "filesystem is undefined"
1086+
return 1
1087+
fi
1088+
1089+
kernel="${2}"
1090+
if [ -z "${kernel}" ]; then
1091+
zdebug "no kernel specified for ${fs}"
1092+
return 1
1093+
fi
1094+
1095+
# Flatten the kernel hierarchy for the kcl cache
1096+
# - Replace "/" with ":", but change literal ":" to "::" to avoid ambiguity
1097+
kernel="${kernel#/}"
1098+
kernel="${kernel//:/::}"
1099+
kernel="${kernel//\//:}"
1100+
1101+
echo "$( be_location "${fs}" )/kcl/${kernel}.kcl"
1102+
return 0
1103+
}
1104+
1105+
# arg1: ZFS filesystem
1106+
# arg2: optional kernel to select
10631107
# prints: kernel command line arguments
10641108
# returns: nothing
10651109

10661110
load_be_cmdline() {
1067-
local fs args spl_hostid kcl cache rems adds
1111+
local fs kern_kcl args spl_hostid kcl cache rems adds
10681112

10691113
fs="${1}"
10701114
if [ -z "${fs}" ]; then
@@ -1073,7 +1117,13 @@ load_be_cmdline() {
10731117
fi
10741118
zdebug "fs set to ${fs}"
10751119

1076-
cache="$( be_location "${fs}" )/cmdline"
1120+
# Ignore a pre-existing cache if a kernel-specific command-line is specified
1121+
[ -n "${2}" ] && kern_kcl="$( kernel_kcl_cache "${fs}" "${2}" )"
1122+
if [ -r "${kern_kcl}" ]; then
1123+
zdebug "found kernel KCL ${kern_kcl} on ${fs}, ignoring KCL cache"
1124+
else
1125+
cache="$( kernel_kcl_cache "${fs}" "default" )"
1126+
fi
10771127

10781128
if [ -r "${BASE}/cmdline" ]; then
10791129
# Always prefer a user-entered KCL
@@ -1093,8 +1143,9 @@ load_be_cmdline() {
10931143
# Nothing is added by default
10941144
adds=()
10951145

1096-
# In all other cases, build and attempt to cache the KCL
1097-
args="$(read_kcl_prop "${fs}" | kcl_tokenize && exit "${PIPESTATUS[0]}" )" || args=""
1146+
args="$( read_kcl_value "${fs}" "${kern_kcl}" )" || args=""
1147+
args="$( kcl_tokenize <<< "${args}" )" || args=""
1148+
10981149
# Use a very basic default KCL if none is specified
10991150
[ -n "${args}" ] || args="quiet loglevel=4"
11001151

@@ -1125,10 +1176,13 @@ load_be_cmdline() {
11251176
adds+=( "${spl_hostid}" )
11261177
fi
11271178

1128-
# Write the cached command line, if possible
1129-
zdebug "caching KCL for ${fs} at ${cache}"
11301179
args="$( kcl_suppress "${rems[@]}" <<< "${args}" | kcl_append "${adds[@]}" )"
1131-
printf "%s\n" "${args}" > "${cache}"
1180+
1181+
if [ -n "${cache}" ]; then
1182+
# Write the cached command line, if possible
1183+
zdebug "caching KCL for ${fs} at ${cache}"
1184+
printf "%s\n" "${args}" > "${cache}"
1185+
fi
11321186

11331187
kcl="$( kcl_assemble <<< "${args}" )"
11341188
zdebug "assembled commandline: '${kcl}'"
@@ -1794,7 +1848,7 @@ cache_key() {
17941848
fi
17951849

17961850
relkeyloc=""
1797-
if ksmount="$(zfs get -o value -H mountpoint "${keysrc}" 2>/dev/null )"; then
1851+
if ksmount="$( zfs get -o value -H mountpoint "${keysrc}" 2>/dev/null )"; then
17981852
case "${ksmount}" in
17991853
none|legacy)
18001854
zdebug "no discernable mountpoint for ${keysrc}, using only absolute key path"
@@ -2010,8 +2064,8 @@ emergency_shell() {
20102064
cat <<-EOF
20112065
$( colorize green "emergency shell")${1:+: $1}
20122066
2013-
type '$(colorize red "help")' for online documentation
2014-
type '$( colorize red "exit")' to return to ZFSBootMenu
2067+
type '$( colorize red "help" )' for online documentation
2068+
type '$( colorize red "exit" )' to return to ZFSBootMenu
20152069
20162070
EOF
20172071

@@ -2185,7 +2239,7 @@ is_mountpoint() {
21852239
return
21862240
fi
21872241

2188-
if ! mount_path="$(readlink -f "${1}")"; then
2242+
if ! mount_path="$( readlink -f "${1}" )"; then
21892243
zerror "parent of ${1} does not exist"
21902244
return 1
21912245
fi
@@ -2197,7 +2251,7 @@ is_mountpoint() {
21972251

21982252
# shellcheck disable=SC2034
21992253
while read -r dev path opts; do
2200-
path="$(readlink -f "${path}")" || continue
2254+
path="$( readlink -f "${path}" )" || continue
22012255
[ "${path}" = "${mount_path}" ] && return 0
22022256
done < /proc/self/mounts
22032257

zfsbootmenu/lib/zfsbootmenu-kcl.sh

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,42 @@ read_kcl_prop() {
102102
return 0
103103
}
104104

105+
# arg1: ZFS filesystem
106+
# arg2: path to command-line file (need not exist)
107+
# prints: value of command line, with %{parent} expanded from ZFS property
108+
# returns: 0 on success
109+
110+
read_kcl_value() {
111+
local zfsbe kclfile args par_args
112+
113+
zfsbe="${1}"
114+
if [ -z "${zfsbe}" ]; then
115+
zerror "zfsbe is undefined"
116+
return 1
117+
fi
118+
119+
kclfile="${2}"
120+
121+
# If there is no KCL file, just read the property
122+
if [ ! -r "${kclfile}" ]; then
123+
read_kcl_prop "${zfsbe}"
124+
return
125+
fi
126+
127+
# Otherwise, read the file...
128+
read -r args < "${kclfile}"
129+
if ! [[ "${args}" =~ "%{parent}" ]]; then
130+
zdebug "no parent reference in ${kclfile} on ${zfsbe}"
131+
echo "${args}"
132+
return 0
133+
fi
134+
135+
# ...and expand any %{parent} reference from the property
136+
par_args="$( read_kcl_prop "${zfsbe}" )" || par_args=""
137+
echo "${args//%\{parent\}/${par_args}}"
138+
return 0
139+
}
140+
105141
# arg1..argN: keys (and, optionally, associated value) to suppress from KCL
106142
# prints: tokenized KCL [read from stdin] with suppressed arguments removed
107143

zfsbootmenu/lib/zfsbootmenu-ui.sh

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ draw_be() {
169169
${HAS_BORDER:+--preview-label-pos=2:bottom} \
170170
${HAS_BORDER:+--preview-label="$( colorize orange " ${preview_label} " )"} \
171171
--header="${header}" --preview-window="up:${PREVIEW_HEIGHT}${HAS_BORDER:+,border-sharp}" \
172-
--preview="/libexec/zfsbootmenu-preview {} '${BOOTFS}'" < "${env}" )"; then
172+
--preview="/libexec/zfsbootmenu-preview -b '${BOOTFS}' {}" < "${env}" )"; then
173173
return 1
174174
fi
175175

@@ -217,7 +217,7 @@ draw_kernel() {
217217
--prompt "${benv} > " --tac --delimiter=$'\t' --with-nth=2 \
218218
--header="${header}" ${HAS_BORDER:+--border-label="$( global_header )"} \
219219
${expects} ${expects//alt-/ctrl-} ${expects//alt-/ctrl-alt-} \
220-
--preview="/libexec/zfsbootmenu-preview '${benv}' '${BOOTFS}'" \
220+
--preview="/libexec/zfsbootmenu-preview -b '${BOOTFS}' -k {2} '${benv}'" \
221221
--preview-window="up:${PREVIEW_HEIGHT}${HAS_BORDER:+,border-sharp}" < "${_kernels}" )"; then
222222
return 1
223223
fi
@@ -321,7 +321,7 @@ draw_snapshots() {
321321
--bind="ctrl-alt-d:execute[ /libexec/zfsbootmenu-diff {+} ]${HAS_REFRESH:++refresh-preview}" \
322322
${HAS_BORDER:+--preview-label-pos=${preview_offset:+${preview_offset}}bottom} \
323323
${HAS_BORDER:+--preview-label="${context}"} \
324-
--preview="/libexec/zfsbootmenu-preview '${benv}' '${BOOTFS}' ${LEGACY_CONTEXT:+\"${context}\"}" \
324+
--preview="/libexec/zfsbootmenu-preview -b '${BOOTFS}' ${LEGACY_CONTEXT:+-c \"${context}\"} '${benv}'" \
325325
--preview-window="up:$(( PREVIEW_HEIGHT + ${LEGACY_CONTEXT:-0} ))${HAS_BORDER:+,border-sharp}" <<<"${snapshots}" )"
326326
then
327327
return 1
@@ -651,7 +651,7 @@ populate_be_list() {
651651
ret=1
652652
for fs in "${candidates[@]}"; do
653653
# Remove any existing cmdline cache
654-
rm -f "$( be_location "${fs}" )/cmdline"
654+
rm -f "$( kernel_kcl_cache "${fs}" "default" )"
655655

656656
# Unlock if necessary
657657
load_key "${fs}" || continue

zfsbootmenu/libexec/zfsbootmenu-preview

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,28 @@ source /etc/zfsbootmenu.conf >/dev/null 2>&1 || exit 1
77
source /lib/kmsg-log-lib.sh >/dev/null 2>&1 || exit 1
88
source /lib/zfsbootmenu-core.sh >/dev/null 2>&1 || exit 1
99

10+
BOOTFS=""
11+
KERNEL=""
12+
CONTEXT=""
13+
14+
while getopts "b:c:k:" opt; do
15+
case "${opt}" in
16+
b) BOOTFS="${OPTARG}" ;;
17+
c) CONTEXT="${OPTARG}" ;;
18+
k) KERNEL="${OPTARG}" ;;
19+
*) ;;
20+
esac
21+
done
22+
23+
shift $((OPTIND - 1))
24+
1025
ENV="${1}"
11-
BOOTFS="${2}"
1226

13-
# shellcheck disable=SC2034
14-
IFS=$'\t' read -r _fs selected_kernel _initramfs <<<"$( select_kernel "${ENV}" || echo "- missing -" )"
15-
selected_kernel="${selected_kernel##*/}"
27+
if [ -z "${KERNEL}" ]; then
28+
# shellcheck disable=SC2034
29+
read -r _ KERNEL _ <<< "$( select_kernel "${ENV}" || echo $'-\t-\t-' )"
30+
[ "${KERNEL}" = "-" ] && KERNEL=""
31+
fi
1632

1733
if [ "${BOOTFS}" = "${ENV}" ]; then
1834
_EXTRAS+=( "default," )
@@ -31,9 +47,10 @@ else
3147
_COLOR="green"
3248
fi
3349

34-
selected_arguments="$( center_string "$( load_be_cmdline "${ENV}" )" )"
50+
selected_arguments="$( center_string "$( load_be_cmdline "${ENV}" "${KERNEL}" )" )"
3551

36-
selected_env_str="$( center_string "${ENV} (${_EXTRAS[*]}) - ${selected_kernel}" )"
52+
KERNEL="${KERNEL##*/}"
53+
selected_env_str="$( center_string "${ENV} (${_EXTRAS[*]}) - ${KERNEL:-missing}" )"
3754

3855
# colorize doesn't automatically add a newline
3956
if [ -f "${BASE}/have_errors" ]; then
@@ -47,7 +64,6 @@ fi
4764
colorize "${_COLOR}" "${selected_env_str}\n"
4865
echo "${selected_arguments}"
4966

50-
if [ -n "${3}" ]; then
51-
context="$( center_string "${3}" )"
52-
colorize "orange" "${context}"
67+
if [ -n "${CONTEXT}" ]; then
68+
colorize "orange" "$( center_string "${CONTEXT}" )"
5369
fi

0 commit comments

Comments
 (0)