1135 lines
42 KiB
Bash
Executable File
1135 lines
42 KiB
Bash
Executable File
#! /bin/sh
|
|
set -e
|
|
|
|
# grub-mkconfig helper script.
|
|
# Copyright (C) 2019 Canonical Ltd.
|
|
#
|
|
# GRUB is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# GRUB is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with GRUB. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
prefix="/usr"
|
|
datarootdir="/usr/share"
|
|
ubuntu_recovery="1"
|
|
quiet_boot="1"
|
|
quick_boot="1"
|
|
gfxpayload_dynamic="1"
|
|
vt_handoff="1"
|
|
|
|
. "${pkgdatadir}/grub-mkconfig_lib"
|
|
|
|
export TEXTDOMAIN=grub
|
|
export TEXTDOMAINDIR="${datarootdir}/locale"
|
|
|
|
set -u
|
|
|
|
## Skip early if zfs utils isn't installed (instead of failing on first zpool list)
|
|
if ! `which zfs >/dev/null 2>&1`; then
|
|
exit 0
|
|
fi
|
|
|
|
imported_pools=""
|
|
MNTDIR="$(mktemp -d ${TMPDIR:-/tmp}/zfsmnt.XXXXXX)"
|
|
ZFSTMP="$(mktemp -d ${TMPDIR:-/tmp}/zfstmp.XXXXXX)"
|
|
|
|
|
|
machine="$(uname -m)"
|
|
case "${machine}" in
|
|
i?86) GENKERNEL_ARCH="x86" ;;
|
|
mips|mips64) GENKERNEL_ARCH="mips" ;;
|
|
mipsel|mips64el) GENKERNEL_ARCH="mipsel" ;;
|
|
arm*) GENKERNEL_ARCH="arm" ;;
|
|
*) GENKERNEL_ARCH="${machine}" ;;
|
|
esac
|
|
|
|
RC=0
|
|
on_exit() {
|
|
# Restore initial zpool import state
|
|
for pool in ${imported_pools}; do
|
|
zpool export "${pool}"
|
|
done
|
|
|
|
mountpoint -q "${MNTDIR}" && umount "${MNTDIR}" || true
|
|
rmdir "${MNTDIR}"
|
|
rm -rf "${ZFSTMP}"
|
|
exit "${RC}"
|
|
}
|
|
trap on_exit EXIT INT QUIT ABRT PIPE TERM
|
|
|
|
# List ONLINE and DEGRADED pools
|
|
import_pools() {
|
|
# We have to ignore zpool import output, as potentially multiple / will be available,
|
|
# and we need to autodetect all zpools this way with their real mountpoints.
|
|
local initial_pools="$(zpool list | awk '{if (NR>1) print $1}')"
|
|
local all_pools=""
|
|
local imported_pools=""
|
|
local err=""
|
|
|
|
set +e
|
|
err="$(zpool import -f -a -o cachefile=none -o readonly=on -N 2>&1)"
|
|
# Only print stderr if the command returned an error
|
|
# (it can echo "No zpool to import" with success, which we don't want)
|
|
if [ $? -ne 0 ]; then
|
|
echo "Some pools couldn't be imported and will be ignored:\n${err}" >&2
|
|
fi
|
|
set -e
|
|
|
|
all_pools="$(zpool list | awk '{if (NR>1) print $1}')"
|
|
for pool in ${all_pools}; do
|
|
if echo "${initial_pools}" | grep -wq "${pool}"; then
|
|
continue
|
|
fi
|
|
imported_pools="${imported_pools} ${pool}"
|
|
done
|
|
|
|
echo "${imported_pools}"
|
|
}
|
|
|
|
# List all the dataset with a root mountpoint
|
|
get_root_datasets() {
|
|
local pools="$(zpool list | awk '{if (NR>1) print $1}')"
|
|
|
|
for p in ${pools}; do
|
|
local rel_pool_root=$(zpool get -H altroot ${p} | awk '{print $3}')
|
|
if [ "${rel_pool_root}" = "-" ]; then
|
|
rel_pool_root="/"
|
|
fi
|
|
|
|
zfs list -H -o name,canmount,mountpoint -t filesystem | grep -E '^'"${p}"'(\s|/[[:print:]]*\s)(on|noauto)\s'"${rel_pool_root}"'$' | awk '{print $1}'
|
|
done
|
|
}
|
|
|
|
# find if given datasets can be mounted for directory and return its path (snapshot or real path)
|
|
# $1 is our current dataset name
|
|
# $2 directory path we look for (cannot contains /)
|
|
# $3 is the temporary mount directory to use
|
|
# $4 is the optional snapshot name
|
|
# return path for directory (which can be a mountpoint)
|
|
validate_system_dataset() {
|
|
local dataset="$1"
|
|
local directory="$2"
|
|
local mntdir="$3"
|
|
local snapshot_name="$4"
|
|
|
|
local mount_path="${mntdir}/${directory}"
|
|
|
|
if ! zfs list "${dataset}" >/dev/null 2>&1; then
|
|
return
|
|
fi
|
|
|
|
if ! mount -o noatime,zfsutil -t zfs "${dataset}" "${mount_path}"; then
|
|
grub_warn "Failed to find a valid directory '${directory}' for dataset '${dataset}@${snapshot_name}'. Ignoring"
|
|
return
|
|
fi
|
|
|
|
local candidate_path="${mount_path}"
|
|
if [ -n "${snapshot_name}" ]; then
|
|
# WORKAROUND a bug https://github.com/zfsonlinux/zfs/issues/9958
|
|
# Reading the content of a snapshot fails if it is not the first mount
|
|
# for a given dataset
|
|
first_mntdir=$(awk '{if ($1 == "'${dataset}'") {print $2; exit;}}' /proc/mounts)
|
|
if [ "${first_mntdir}" = "/" ]; then
|
|
# prevents // on candidate_path
|
|
first_mntdir=""
|
|
fi
|
|
candidate_path="${first_mntdir}/.zfs/snapshot/${snapshot_name}"
|
|
fi
|
|
|
|
if [ -n "$(ls "${candidate_path}" 2>/dev/null)" ]; then
|
|
echo "${candidate_path}"
|
|
return
|
|
else
|
|
mountpoint -q "${mount_path}" && umount "${mount_path}" || true
|
|
fi
|
|
}
|
|
|
|
# Detect system directory relevant to the other, trying to find the ones associated on the current dataset or snapshot/
|
|
# System directory should be at most a direct child dataset of main datasets (no recursivity)
|
|
# We can fallback trying other zfs pools if no match has been found.
|
|
# $1 is our current dataset name (which can have @snapshot name)
|
|
# $2 directory path we look for (cannot contains /)
|
|
# $3 restrict_to_same_pool (true|false) force looking for dataset with the same basename in the current dataset pool only
|
|
# $4 is the temporary mount directory to use
|
|
# $5 is the optional etc directory (if not $2 is not etc itself)
|
|
# return path for directory (which can be a mountpoint)
|
|
get_system_directory() {
|
|
local dataset_path="$1"
|
|
local directory="$2"
|
|
local restrict_to_same_pool="$3"
|
|
local mntdir="$4"
|
|
local etc_dir="$5"
|
|
|
|
if [ -z "${etc_dir}" ]; then
|
|
etc_dir="${mntdir}/etc"
|
|
fi
|
|
|
|
local candidate_path="${mntdir}/${directory}"
|
|
|
|
# 1. Look for /etc/fstab first (which will mount even on top of non empty $directory)
|
|
local mounted_fstab_entry="false"
|
|
if [ -f "${etc_dir}/fstab" ]; then
|
|
mount_args=$(awk '/^[^#].*[ \t]\/'"${directory}"'[ \t]/ {print "-t", $3, $1}' "${etc_dir}/fstab")
|
|
if [ -n "${mount_args}" ]; then
|
|
mounted_fstab_entry="true"
|
|
mount -o noatime ${mount_args} "${candidate_path}" || mounted_fstab_entry="false"
|
|
fi
|
|
fi
|
|
|
|
# If directory isn't empty. Only count if coming from /etc/fstab. Will be
|
|
# handled below otherwise as we are interested in potential snapshots.
|
|
if [ "${mounted_fstab_entry}" = "true" -a -n "$(ls ${candidate_path} 2>/dev/null)" ]; then
|
|
echo "${candidate_path}"
|
|
return
|
|
fi
|
|
|
|
# 2. Handle zfs case, which can be a snapshots.
|
|
|
|
local base_dataset_path="${dataset_path}"
|
|
local snapshot_name=""
|
|
# For snapshots we extract the parent dataset
|
|
if echo "${dataset_path}" | grep -q '@'; then
|
|
base_dataset_path=$(echo "${dataset_path}" | cut -d '@' -f1)
|
|
snapshot_name=$(echo "${dataset_path}" | cut -d '@' -f2)
|
|
fi
|
|
base_dataset_name="${base_dataset_path##*/}"
|
|
base_pool="$(echo "${base_dataset_path}" | cut -d'/' -f1)"
|
|
|
|
# 2.a) Look for child dataset included in base dataset, which needs to hold same snapshot if any
|
|
candidate_path=$(validate_system_dataset "${base_dataset_path}/${directory}" "${directory}" "${mntdir}" "${snapshot_name}")
|
|
if [ -n "${candidate_path}" ]; then
|
|
echo "${candidate_path}"
|
|
return
|
|
fi
|
|
|
|
# 2.b) Look for current dataset (which is already mounted as /)
|
|
candidate_path="${mntdir}/${directory}"
|
|
if [ -n "${snapshot_name}" ]; then
|
|
# WORKAROUND a bug https://github.com/zfsonlinux/zfs/issues/9958
|
|
# Reading the content of a snapshot fails if it is not the first mount
|
|
# for a given dataset
|
|
first_mntdir=$(awk '{if ($1 == "'${base_dataset_path}'") {print $2; exit;}}' /proc/mounts)
|
|
if [ "${first_mntdir}" = "/" ]; then
|
|
# prevents // on candidate_path
|
|
first_mntdir=""
|
|
fi
|
|
candidate_path="${first_mntdir}/.zfs/snapshot/${snapshot_name}/${directory}"
|
|
fi
|
|
if [ -n "$(ls "${candidate_path}" 2>/dev/null)" ]; then
|
|
echo "${candidate_path}"
|
|
return
|
|
fi
|
|
|
|
# 2.c) Look for every datasets in every pool which isn't the current dataset which holds:
|
|
# - the same dataset name (last section) than our base_dataset_name
|
|
# - mountpoint=directory
|
|
# - canmount!=off
|
|
all_same_base_dataset_name="$(zfs list -H -t filesystem -o name,canmount | awk '/^[^ ]+\/'"${base_dataset_name}"'[ \t](on|noauto)/ {print $1}') "
|
|
|
|
# order by local pool datasets first
|
|
current_pool_same_base_datasets=""
|
|
other_pools_same_base_datasets=""
|
|
root_pool=$(echo "${dataset_path%%/*}")
|
|
for d in ${all_same_base_dataset_name}; do
|
|
cur_dataset_pool=$(echo "${d%%/*}")
|
|
if echo "${cur_dataset_pool}" | grep -wq "${root_pool}" 2>/dev/null ; then
|
|
current_pool_same_base_datasets="${current_pool_same_base_datasets} ${d}"
|
|
else
|
|
other_pools_same_base_datasets="${other_pools_same_base_datasets} ${d}"
|
|
fi
|
|
done
|
|
ordered_same_base_datasets="${current_pool_same_base_datasets} ${other_pools_same_base_datasets}"
|
|
if [ "${restrict_to_same_pool}" = "true" ]; then
|
|
ordered_same_base_datasets="${current_pool_same_base_datasets}"
|
|
fi
|
|
|
|
# now, loop over them
|
|
for d in ${ordered_same_base_datasets}; do
|
|
cur_dataset_pool=$(echo "${d%%/*}")
|
|
|
|
rel_pool_root=$(zpool get -H altroot ${cur_dataset_pool} | awk '{print $3}')
|
|
if [ "${rel_pool_root}" = "-" ]; then
|
|
rel_pool_root=""
|
|
fi
|
|
|
|
# check mountpoint match
|
|
candidate_dataset=$(zfs get -H mountpoint ${d} | grep -E "mountpoint\s${rel_pool_root}/${directory}\s" | awk '{print $1}')
|
|
if [ -z "${candidate_dataset}" ]; then
|
|
continue
|
|
fi
|
|
|
|
candidate_path=$(validate_system_dataset "${candidate_dataset}" "${directory}" "${mntdir}" "${snapshot_name}")
|
|
if [ -n "${candidate_path}" ]; then
|
|
echo "${candidate_path}"
|
|
return
|
|
fi
|
|
done
|
|
|
|
# 2.d) If we didn't find anything yet: check for persistent datasets corresponding to our mountpoint, with canmount=on without any snapshot associated:
|
|
# Note: we go over previous datasets as well, but this is ok, as we didn't include them before.
|
|
all_mountable_datasets="$(zfs list -t filesystem -o name,canmount | awk '/^[^ ]+[ \t]+on/ {print $1}')"
|
|
|
|
# order by local pool datasets first
|
|
current_pool_datasets=""
|
|
other_pools_datasets=""
|
|
root_pool=$(echo "${dataset_path%%/*}")
|
|
for d in ${all_mountable_datasets}; do
|
|
cur_dataset_pool=$(echo "${d%%/*}")
|
|
if echo "${cur_dataset_pool}" | grep -wq "${root_pool}" 2>/dev/null ; then
|
|
current_pool_datasets="${current_pool_datasets} ${d}"
|
|
else
|
|
other_pools_datasets="${other_pools_datasets} ${d}"
|
|
fi
|
|
done
|
|
ordered_datasets="${current_pool_datasets} ${other_pools_datasets}"
|
|
if [ "${restrict_to_same_pool}" = "true" ]; then
|
|
ordered_datasets="${current_pool_datasets}"
|
|
fi
|
|
|
|
for d in ${ordered_datasets}; do
|
|
cur_dataset_pool=$(echo "${d%%/*}")
|
|
|
|
rel_pool_root=$(zpool get -H altroot ${cur_dataset_pool} | awk '{print $3}')
|
|
if [ "${rel_pool_root}" = "-" ]; then
|
|
rel_pool_root=""
|
|
fi
|
|
|
|
# check mountpoint match
|
|
candidate_dataset=$(zfs get -H mountpoint ${d} | grep -E "mountpoint\s${rel_pool_root}/${directory}\s" | awk '{print $1}')
|
|
if [ -z "${candidate_dataset}" ]; then
|
|
continue
|
|
fi
|
|
|
|
candidate_path=$(validate_system_dataset "${d}" "${directory}" "${mntdir}" "")
|
|
if [ -n "${candidate_path}" ]; then
|
|
echo "${candidate_path}"
|
|
return
|
|
fi
|
|
done
|
|
|
|
grub_warn "Failed to find a valid directory '${directory}' for dataset '${dataset_path}'. Ignoring"
|
|
return
|
|
}
|
|
|
|
# Try our default layout bpool as a prefered layout (fast path)
|
|
# This is get_system_directory for boot optimized for our default installation layout
|
|
# $1 is our current dataset name (which can have @snapshot name)
|
|
# $2 is the temporary mount directory to use
|
|
# return path for directory (which can be a mountpoint) if found
|
|
try_default_layout_bpool() {
|
|
local root_dataset_path="$1"
|
|
local mntdir="$2"
|
|
|
|
dataset_basename="${root_dataset_path##*/}"
|
|
candidate_dataset="bpool/BOOT/${dataset_basename}"
|
|
dataset_properties="$(zfs get -H mountpoint,canmount "${candidate_dataset}" 2>/dev/null | cut -f3 | paste -sd ' ')"
|
|
if [ -z "${dataset_properties}" ]; then
|
|
return
|
|
fi
|
|
|
|
rel_pool_root=$(zpool get -H altroot bpool | awk '{print $3}')
|
|
if [ "${rel_pool_root}" = "-" ]; then
|
|
rel_pool_root=""
|
|
fi
|
|
|
|
snapshot_name="${dataset_basename##*@}"
|
|
[ "${snapshot_name}" = "${dataset_basename}" ] && snapshot_name=""
|
|
if [ -z "${snapshot_name}" ]; then
|
|
if ! echo "${dataset_properties}" | grep -Eq "${rel_pool_root}/boot (on|noauto)"; then
|
|
return
|
|
fi
|
|
else
|
|
candidate_dataset=$(echo "${candidate_dataset}" | cut -d '@' -f1)
|
|
fi
|
|
|
|
validate_system_dataset "${candidate_dataset}" "boot" "${mntdir}" "${snapshot_name}"
|
|
}
|
|
|
|
# Return if secure boot is enabled on that system
|
|
is_secure_boot_enabled() {
|
|
if LANG=C mokutil --sb-state 2>/dev/null | grep -qi enabled; then
|
|
echo "true"
|
|
return
|
|
fi
|
|
echo "false"
|
|
return
|
|
}
|
|
|
|
# Given a filesystem or snapshot dataset, returns dataset|machine id|pretty name|last used
|
|
# $1 is dataset we want information from
|
|
# $2 is the temporary mount directory to use
|
|
get_dataset_info() {
|
|
local dataset="$1"
|
|
local mntdir="$2"
|
|
|
|
local base_dataset="${dataset}"
|
|
local etc_dir="${mntdir}/etc"
|
|
local is_snapshot="false"
|
|
# For snapshot we extract the parent dataset
|
|
if echo "${dataset}" | grep -q '@'; then
|
|
base_dataset=$(echo "${dataset}" | cut -d '@' -f1)
|
|
is_snapshot="true"
|
|
fi
|
|
|
|
mount -o noatime,zfsutil -t zfs "${base_dataset}" "${mntdir}"
|
|
|
|
# read machine-id/os-release from /etc
|
|
etc_dir=$(get_system_directory "${dataset}" "etc" "true" "${mntdir}" "")
|
|
if [ -z "${etc_dir}" ]; then
|
|
grub_warn "Ignoring ${dataset}"
|
|
mountpoint -q "${mntdir}/etc" && umount "${mntdir}/etc" || true
|
|
umount "${mntdir}"
|
|
return
|
|
fi
|
|
|
|
machine_id=""
|
|
if [ -f "${etc_dir}/machine-id" ]; then
|
|
machine_id=$(cat "${etc_dir}/machine-id")
|
|
fi
|
|
# We have to use a random temporary id if we don't have any machine-id file or if this one is empty
|
|
# (mostly the case of new installations before first boot).
|
|
# Let's use the dataset name directly for this.
|
|
# Consequence is that all datasets are then separated.
|
|
if [ -z "${machine_id}" ]; then
|
|
machine_id="${dataset}"
|
|
fi
|
|
pretty_name=$(. "${etc_dir}/os-release" && echo "${PRETTY_NAME}")
|
|
mountpoint -q "${mntdir}/etc" && umount "${mntdir}/etc" || true
|
|
|
|
# read available kernels from /boot
|
|
boot_dir="$(try_default_layout_bpool "${dataset}" "${mntdir}")"
|
|
if [ -z "${boot_dir}" ]; then
|
|
boot_dir=$(get_system_directory "${dataset}" "boot" "false" "${mntdir}" "${etc_dir}")
|
|
fi
|
|
|
|
if [ -z "${boot_dir}" ]; then
|
|
grub_warn "Ignoring ${dataset}"
|
|
mountpoint -q "${mntdir}/boot" && umount "${mntdir}/boot" || true
|
|
umount "${mntdir}"
|
|
return
|
|
fi
|
|
|
|
initrd_list=""
|
|
kernel_list=""
|
|
candidate_kernel_list="$(find "${boot_dir}" -maxdepth 1 -type f -regex '.*/\(vmlinuz\|vmlinux\|kernel\)-.*')"
|
|
while [ -n "${candidate_kernel_list}" ] ; do
|
|
list_basename="$(echo "${candidate_kernel_list}" | sed -e 's#.*/##')"
|
|
linux=$(version_find_latest ${list_basename})
|
|
linux=$(echo "${candidate_kernel_list}" | while read k; do
|
|
if [ "$(basename "${k}")" = "${linux}" ]; then
|
|
echo -n "${k}"
|
|
break
|
|
fi
|
|
done)
|
|
# || true to not abort even if candidate_kernel_list is empty on last entry
|
|
candidate_kernel_list="$(echo "${candidate_kernel_list}" | fgrep -vx "${linux}"||true)"
|
|
|
|
if ! grub_file_is_not_garbage "${linux}" ; then
|
|
continue
|
|
fi
|
|
|
|
# Filters entry if efi/non efi.
|
|
# Note that for now we allow kernel without .efi.signed as those are signed kernel
|
|
# on ubuntu, loaded by the shim.
|
|
case "${linux}" in
|
|
*.efi.signed)
|
|
if [ "$(is_secure_boot_enabled)" = "false" ]; then
|
|
continue
|
|
fi
|
|
;;
|
|
esac
|
|
|
|
linux_basename=$(basename "${linux}")
|
|
linux_dirname=$(dirname "${linux}")
|
|
version=$(echo "${linux_basename}" | sed -e "s,^[^0-9]*-,,g")
|
|
alt_version=$(echo "${version}" | sed -e "s,\.old$,,g")
|
|
|
|
gettext_printf "Found linux image: %s in %s\n" "${linux_basename}" "${dataset}" >&2
|
|
|
|
initrd=""
|
|
for i in "initrd.img-${version}" "initrd-${version}.img" "initrd-${version}.gz" \
|
|
"initrd-${version}" "initramfs-${version}.img" \
|
|
"initrd.img-${alt_version}" "initrd-${alt_version}.img" \
|
|
"initrd-${alt_version}" "initramfs-${alt_version}.img" \
|
|
"initramfs-genkernel-${version}" \
|
|
"initramfs-genkernel-${alt_version}" \
|
|
"initramfs-genkernel-${GENKERNEL_ARCH}-${version}" \
|
|
"initramfs-genkernel-${GENKERNEL_ARCH}-${alt_version}"; do
|
|
if test -e "${linux_dirname}/${i}" ; then
|
|
initrd="$i"
|
|
break
|
|
fi
|
|
done
|
|
|
|
if test -z "${initrd}" ; then
|
|
grub_warn "Couldn't find any valid initrd for dataset ${dataset}."
|
|
continue
|
|
fi
|
|
|
|
gettext_printf "Found initrd image: %s in %s\n" "${initrd}" "${dataset}" >&2
|
|
|
|
rel_linux_dirname=$(make_system_path_relative_to_its_root "${linux_dirname}")
|
|
|
|
initrd_list="${initrd_list}|${rel_linux_dirname}/${initrd}"
|
|
kernel_list="${kernel_list}|${rel_linux_dirname}/${linux_basename}"
|
|
done
|
|
|
|
initrd_list="${initrd_list#|}"
|
|
kernel_list="${kernel_list#|}"
|
|
|
|
initrd_device=$(${grub_probe} --target=device "${boot_dir}" | head -1)
|
|
|
|
mountpoint -q "${mntdir}/boot" && umount "${mntdir}/boot" || true
|
|
# We needed to look in / for snapshots on root dataset, umount there before zfs lazily unmount it
|
|
case "${boot_dir}" in /boot/.zfs/snapshot/*)
|
|
umount "${boot_dir}" || true
|
|
;;
|
|
esac
|
|
|
|
# for zsys snapshots: we want to know which kernel we successful last booted with
|
|
last_booted_kernel=$(zfs get -H com.ubuntu.zsys:last-booted-kernel "${dataset}" | awk -v FS='\t' '{print $3}')
|
|
|
|
# snapshot: last_used is dataset creation time
|
|
if [ "${is_snapshot}" = "true" ]; then
|
|
last_used="$(zfs get -pH creation "${dataset}" | awk -F '\t' '{print $3}')"
|
|
# otherwise, last_used is manually marked at boot/shutdown on a root dataset for zsys
|
|
else
|
|
# if current system, take current time
|
|
if zfs mount | awk '/[ \t]+\/$/ {print $1}' | grep -q "${dataset}"; then
|
|
last_used=$(date +%s)
|
|
else
|
|
last_used=$(zfs get -H com.ubuntu.zsys:last-used "${dataset}" | awk '{print $3}')
|
|
# case of non zsys, or zsys without annotation, take /etc/machine-id stat (as we mounted with noatime).
|
|
# However, as systems can be relatime, if system is current mounted one, set current time (case of clone + reboot
|
|
# within the same d).
|
|
if [ "${last_used}" = "-" ]; then
|
|
last_used=$(stat --printf="%X" "${mntdir}/etc/os-release")
|
|
if [ -f "${mntdir}/etc/machine-id" ]; then
|
|
last_used=$(stat --printf="%X" "${mntdir}/etc/machine-id")
|
|
fi
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
is_zsys=$(zfs get -H com.ubuntu.zsys:bootfs "${base_dataset}" | awk '{print $3}')
|
|
|
|
if [ -n "${initrd_list}" -a -n "${kernel_list}" ]; then
|
|
echo "${dataset}\t${is_zsys}\t${machine_id}\t${pretty_name}\t${last_used}\t${initrd_device}\t${initrd_list}\t${kernel_list}\t${last_booted_kernel}"
|
|
else
|
|
grub_warn "didn't find any valid initrd or kernel."
|
|
fi
|
|
|
|
umount "${mntdir}" || true
|
|
# We needed to look in / for snapshots on root dataset, umount the snapshot for etc before zfs lazily unmount it
|
|
case "${etc_dir}" in /.zfs/snapshot/*/etc)
|
|
snapshot_path="$(findmnt -n -o TARGET -T "${etc_dir}")"
|
|
umount "${snapshot_path}" || true
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Scan available boot options and returns in a formatted list
|
|
# $1 is the temporary mount directory to use
|
|
bootlist() {
|
|
local mntdir="$1"
|
|
local boot_list=""
|
|
|
|
for dataset in $(get_root_datasets); do
|
|
# get information from current root dataset
|
|
boot_list="${boot_list}$(get_dataset_info "${dataset}" ${mntdir})\n"
|
|
|
|
# get information from snapshots of this root dataset
|
|
snapshots="$(zfs list -H -o name -t snapshot "${dataset}"|while read snapshot_dataset; do
|
|
get_dataset_info "${snapshot_dataset}" ${mntdir}
|
|
done)"
|
|
[ -n "${snapshots}" ] && boot_list="${boot_list}${snapshots}\n"
|
|
done
|
|
echo "${boot_list}"
|
|
}
|
|
|
|
|
|
# Order machine ids by last_used from their main entry
|
|
get_machines_sorted() {
|
|
local bootlist="$1"
|
|
|
|
local machineids="$(echo "${bootlist}" | awk '{print $3}' | sort -u)"
|
|
for machineid in ${machineids}; do
|
|
echo "${bootlist}" | awk 'BEGIN{FS="\t"} $1 !~ /.*@.*/ {print $5, $3}' | sort -nr | grep -E "[^^]\b${machineid}\b" | head -1
|
|
done | sort -nr | awk '{print $2}'
|
|
}
|
|
|
|
# Sort entries by last_used for a given machineid
|
|
sort_entries_for_machineid() {
|
|
local bootlist="$1"
|
|
local machineid="$2"
|
|
|
|
tab="$(printf '\t')"
|
|
echo "${bootlist}" | grep -E "[^^]\b${machineid}\b" | sort -k5,5r -k1,1 -t "${tab}"
|
|
}
|
|
|
|
# Return main entry index
|
|
get_main_entry() {
|
|
local entries="$1"
|
|
|
|
echo "${entries}" | awk 'BEGIN{FS="\t"} $1 !~ /.*@.*/ {print}' | head -1
|
|
}
|
|
|
|
# Return specific field at index from entry
|
|
get_field_from_entry() {
|
|
local entry="$1"
|
|
local index="$2"
|
|
|
|
echo "${entry}" | awk "BEGIN{FS=\"\t\"} {print \$$index}"
|
|
}
|
|
|
|
# Get the main entry metadata
|
|
main_entry_meta() {
|
|
local main_entry="$1"
|
|
|
|
initrd=$(get_field_from_entry "${main_entry}" 7 | cut -d'|' -f1)
|
|
kernel=$(get_field_from_entry "${main_entry}" 8 | cut -d'|' -f1)
|
|
|
|
# Take first element (most recent entry) which is not a snapshot
|
|
echo "${main_entry}" | awk "BEGIN{ FS=\"\t\"; OFS=\"\t\"} {print \$3, \$2, \"main\", \$4, \$1, \$6, \"$initrd\", \"$kernel\"}"
|
|
}
|
|
|
|
# Get advanced entries metadata
|
|
advanced_entries_meta() {
|
|
local main_entry="$1"
|
|
|
|
last_used_kernel="$(get_field_from_entry "${main_entry}" 9 )"
|
|
|
|
# We must align initrds with kernels.
|
|
# Adds initrds to the stack then pop them 1 by 1 as we process the kernels
|
|
oldIFS="$IFS"
|
|
export IFS='|'
|
|
set -- $(get_field_from_entry "${main_entry}" 7)
|
|
for kernel in $(get_field_from_entry "${main_entry}" 8); do
|
|
# get initrd and pop to the next one
|
|
initrd="$1"; shift
|
|
|
|
was_last_used_kernel="false"
|
|
kernel_basename=$(basename "${kernel}")
|
|
if [ "${kernel_basename}" = "${last_used_kernel}" ]; then
|
|
was_last_used_kernel="true"
|
|
fi
|
|
|
|
echo "${main_entry}" | awk "BEGIN{ FS=\"\t\"; OFS=\"\t\"} {print \$3, \$2, \"advanced\", \$4, \$1, \$6, \"$initrd\", \"$kernel\", \"$was_last_used_kernel\"}"
|
|
done
|
|
IFS="$oldIFS"
|
|
}
|
|
|
|
# Get history metadata
|
|
history_entries_meta() {
|
|
local entries="$1"
|
|
local main_dataset_name="$2"
|
|
local main_dataset_releasename="$3"
|
|
|
|
if [ -z "${entries}" ]; then
|
|
return
|
|
fi
|
|
|
|
# Traverse snapshots and clones
|
|
echo "${entries}" | while read entry; do
|
|
name=""
|
|
# Compute snapshot/filesystem dataset name
|
|
snap_dataset_name="$(get_field_from_entry "${entry}" 1)"
|
|
|
|
snapname="${snap_dataset_name##*@}"
|
|
# If, this is a clone, take what is after main_dataset_name
|
|
if [ "${snapname}" = "${snap_dataset_name}" ]; then
|
|
snapname="${snap_dataset_name##${main_dataset_name}_}"
|
|
|
|
# Handle manual user clone (not prefixed by "main_dataset_name")
|
|
snapname="${snapname##*/}"
|
|
fi
|
|
|
|
# We keep the snapname only if it is not only a zsys auto snapshot
|
|
if echo "${snapname}" | grep -q "^autozsys_"; then
|
|
snapname=""
|
|
fi
|
|
|
|
# We store the release only if it different from main dataset release (snapshot before a release upgrade)
|
|
releasename=$(get_field_from_entry "${entry}" 4)
|
|
if [ "${releasename}" = "${main_dataset_releasename}" ]; then
|
|
releasename=""
|
|
fi
|
|
|
|
# Snapshot date
|
|
foo="$(get_field_from_entry "${entry}" 5)"
|
|
snapdate="$(date -d @$(get_field_from_entry "${entry}" 5) "+%x @ %H:%M")"
|
|
|
|
# For snapshots/clones the name can have the following formats:
|
|
# <DATE>: autozsys, same release
|
|
# <OLD_RELEASE> on <DATE>: autozsys, different release
|
|
# <SNAPNAME> on <DATE>: Manual snapshot, same release
|
|
# <SNAPNAME>, <OLD_RELEASE> on <DATE>: Manual snapshot, different release
|
|
if [ "${snapname}" = "" -a "${releasename}" = "" ]; then
|
|
name="${snapdate}"
|
|
elif [ "${snapname}" = "" -a "${releasename}" != "" ]; then
|
|
name=$(gettext_printf "%s on %s" "${releasename}" "${snapdate}")
|
|
elif [ "${snapname}" != "" -a "${releasename}" = "" ]; then
|
|
name=$(gettext_printf "%s on %s" "${snapname}" "${snapdate}")
|
|
else # snapname != "" && releasename != ""
|
|
name=$(gettext_printf "%s, %s on %s" "${snapname}" "${releasename}" "${snapdate}")
|
|
fi
|
|
|
|
# Choose kernel and initrd if the snapshot was booted successfully on a specific kernel before
|
|
# Take latest by default if no match
|
|
initrd=$(get_field_from_entry "${entry}" 7 | cut -d'|' -f1)
|
|
kernel=$(get_field_from_entry "${entry}" 8 | cut -d'|' -f1)
|
|
last_used_kernel="$(get_field_from_entry "${entry}" 9)"
|
|
|
|
# We must align initrds with kernels.
|
|
# Adds initrds to the stack then pop them 1 by 1 as we process the kernels
|
|
oldIFS="$IFS"
|
|
export IFS='|'
|
|
set -- $(get_field_from_entry "${entry}" 7)
|
|
for k in $(get_field_from_entry "${entry}" 8); do
|
|
# get initrd and pop to the next one
|
|
candidate_initrd="$1"; shift
|
|
|
|
kernel_basename=$(basename -- "${k}")
|
|
if [ "${kernel_basename}" = "${last_used_kernel}" ]; then
|
|
kernel="${k}"
|
|
initrd="${candidate_initrd}"
|
|
break
|
|
fi
|
|
done
|
|
IFS="$oldIFS"
|
|
|
|
echo "${entry}" | awk "BEGIN{ FS=\"\t\"; OFS=\"\t\"} {print \$3, \$2, \"history\", \"$name\", \$1, \$6, \"$initrd\", \"$kernel\"}"
|
|
done
|
|
}
|
|
|
|
# Generate metadata from a BOOTLIST that will subsequently used to generate
|
|
# the final grub menu entries
|
|
generate_grub_menu_metadata() {
|
|
local bootlist="$1"
|
|
|
|
# Sort machineids by last_used from their main entry
|
|
for machineid in $(get_machines_sorted "${bootlist}"); do
|
|
entries="$(sort_entries_for_machineid "${bootlist}" ${machineid})"
|
|
main_entry="$(get_main_entry "${entries}")"
|
|
|
|
if [ -z "$main_entry" ]; then
|
|
continue
|
|
fi
|
|
|
|
main_entry_meta "${main_entry}"
|
|
advanced_entries_meta "${main_entry}"
|
|
|
|
main_dataset_name="$(get_field_from_entry "${main_entry}" 1)"
|
|
main_dataset_releasename="$(get_field_from_entry "${main_entry}" 4)"
|
|
# grep -v errcode != 0 if there is no match. || true to not fail with -e
|
|
other_entries="$(echo "${entries}" | grep -v "${main_entry}" || true)"
|
|
history_entries_meta "${other_entries}" "${main_dataset_name}" "${main_dataset_releasename}"
|
|
done
|
|
}
|
|
|
|
# Print the configuration part common to all sections
|
|
# Note:
|
|
# If 10_linux runs these part will be defined twice in grub configuration
|
|
print_menu_prologue() {
|
|
cat << 'EOF'
|
|
function gfxmode {
|
|
set gfxpayload="${1}"
|
|
EOF
|
|
if [ "${vt_handoff}" = 1 ]; then
|
|
cat << 'EOF'
|
|
if [ "${1}" = "keep" ]; then
|
|
set vt_handoff=vt.handoff=1
|
|
else
|
|
set vt_handoff=
|
|
fi
|
|
EOF
|
|
fi
|
|
cat << EOF
|
|
}
|
|
EOF
|
|
|
|
# Use ELILO's generic "efifb" when it's known to be available.
|
|
# FIXME: We need an interface to select vesafb in case efifb can't be used.
|
|
GRUB_GFXPAYLOAD_LINUX="${GRUB_GFXPAYLOAD_LINUX:-}"
|
|
if [ "${GRUB_GFXPAYLOAD_LINUX}" != "" ] || [ "${gfxpayload_dynamic}" = 0 ]; then
|
|
echo "set linux_gfx_mode=${GRUB_GFXPAYLOAD_LINUX}"
|
|
else
|
|
cat << EOF
|
|
if [ "\${recordfail}" != 1 ]; then
|
|
if [ -e \${prefix}/gfxblacklist.txt ]; then
|
|
if hwmatch \${prefix}/gfxblacklist.txt 3; then
|
|
if [ \${match} = 0 ]; then
|
|
set linux_gfx_mode=keep
|
|
else
|
|
set linux_gfx_mode=text
|
|
fi
|
|
else
|
|
set linux_gfx_mode=text
|
|
fi
|
|
else
|
|
set linux_gfx_mode=keep
|
|
fi
|
|
else
|
|
set linux_gfx_mode=text
|
|
fi
|
|
EOF
|
|
fi
|
|
cat << EOF
|
|
export linux_gfx_mode
|
|
EOF
|
|
}
|
|
|
|
# Cache for prepare_grub_to_access_device call
|
|
# $1: boot_device
|
|
# $2: submenu_level
|
|
prepare_grub_to_access_device_cached() {
|
|
local boot_device="$1"
|
|
local submenu_level="$2"
|
|
|
|
local boot_device_idx="$(echo ${boot_device} | tr '/' '_')"
|
|
|
|
cache_file="${ZFSTMP}/$(echo boot_device${boot_device_idx})"
|
|
if [ ! -f "${cache_file}" ]; then
|
|
set +u
|
|
echo "$(prepare_grub_to_access_device "${boot_device}")" > "${cache_file}"
|
|
set -u
|
|
for i in 0 1 2; do
|
|
submenu_indentation="$(printf %${i}s | tr " " "${grub_tab}")"
|
|
sed "s/^/${submenu_indentation} /" "${cache_file}" > "${cache_file}--${i}"
|
|
done
|
|
fi
|
|
|
|
cat "${cache_file}--${submenu_level}"
|
|
}
|
|
|
|
|
|
# Print a grub menu entry
|
|
zfs_linux_entry () {
|
|
submenu_level="$1"
|
|
title="$2"
|
|
type="$3"
|
|
dataset="$4"
|
|
boot_device="$5"
|
|
initrd="$6"
|
|
kernel="$7"
|
|
kernel_version="$8"
|
|
kernel_additional_args="${9:-}"
|
|
boot_devices="${10:-}"
|
|
|
|
submenu_indentation="$(printf %${submenu_level}s | tr " " "${grub_tab}")"
|
|
|
|
echo "${submenu_indentation}menuentry '$(echo "${title}" | grub_quote)' ${CLASS} \${menuentry_id_option} 'gnulinux-${dataset}-${kernel_version}' {"
|
|
|
|
if [ "${quick_boot}" = 1 ]; then
|
|
echo "${submenu_indentation} recordfail"
|
|
fi
|
|
|
|
if [ "${type}" != "recovery" ] ; then
|
|
GRUB_SAVEDEFAULT=${GRUB_SAVEDEFAULT:-}
|
|
default_entry="$(save_default_entry)"
|
|
if [ -n "${default_entry}" ]; then
|
|
echo "${submenu_indentation} ${default_entry}"
|
|
fi
|
|
fi
|
|
|
|
# Use ELILO's generic "efifb" when it's known to be available.
|
|
# FIXME: We need an interface to select vesafb in case efifb can't be used.
|
|
if [ "${GRUB_GFXPAYLOAD_LINUX}" = "" ]; then
|
|
echo "${submenu_indentation} load_video"
|
|
else
|
|
if [ "${GRUB_GFXPAYLOAD_LINUX}" != "text" ]; then
|
|
echo "${submenu_indentation} load_video"
|
|
fi
|
|
fi
|
|
|
|
if ([ "${ubuntu_recovery}" = 0 ] || [ "${type}" != "recovery" ]) && \
|
|
([ "${GRUB_GFXPAYLOAD_LINUX}" != "" ] || [ "${gfxpayload_dynamic}" = 1 ]); then
|
|
echo "${submenu_indentation} gfxmode \${linux_gfx_mode}"
|
|
fi
|
|
|
|
echo "${submenu_indentation} insmod gzio"
|
|
echo "${submenu_indentation} if [ \"\${grub_platform}\" = xen ]; then insmod xzio; insmod lzopio; fi"
|
|
|
|
if [ -n "$boot_devices" ]; then
|
|
for device in ${boot_devices}; do
|
|
echo "${submenu_indentation} if [ "${boot_device}" = "${device}" ]; then"
|
|
echo "$(prepare_grub_to_access_device_cached "${device}" $(( submenu_level +1 )) )"
|
|
echo "${submenu_indentation} fi"
|
|
done
|
|
else
|
|
echo "$(prepare_grub_to_access_device_cached "${boot_device}" "${submenu_level}")"
|
|
fi
|
|
|
|
if [ "${quiet_boot}" = 0 ] || [ "${type}" != simple ]; then
|
|
echo "${submenu_indentation} echo $(gettext_printf "Loading Linux %s ..." ${kernel_version} | grub_quote)"
|
|
fi
|
|
|
|
linux_default_args="${GRUB_CMDLINE_LINUX} ${GRUB_CMDLINE_LINUX_DEFAULT}"
|
|
if [ ${type} = "recovery" ]; then
|
|
linux_default_args="${GRUB_CMDLINE_LINUX_RECOVERY} ${GRUB_CMDLINE_LINUX}"
|
|
fi
|
|
|
|
# echo in echo trims end of line spaces
|
|
echo "${submenu_indentation} linux \"${kernel}\" root=ZFS=\"${dataset}\" ro $(echo ${linux_default_args} ${kernel_additional_args})"
|
|
|
|
if [ "${quiet_boot}" = 0 ] || [ "${type}" != simple ]; then
|
|
echo "${submenu_indentation} echo '$(gettext_printf "Loading initial ramdisk ..." | grub_quote)'"
|
|
fi
|
|
echo "${submenu_indentation} initrd \"${initrd}\""
|
|
echo "${submenu_indentation}}"
|
|
}
|
|
|
|
# Generate a GRUB Menu from menu meta data
|
|
# $1 menu metadata
|
|
generate_grub_menu() {
|
|
local menu_metadata="$1"
|
|
local last_section=""
|
|
local main_dataset_name=""
|
|
local main_dataset=""
|
|
local have_zsys=""
|
|
|
|
if [ -z "${menu_metadata}" ]; then
|
|
return
|
|
fi
|
|
|
|
CLASS="--class gnu-linux --class gnu --class os"
|
|
|
|
if [ "${GRUB_DISTRIBUTOR}" = "" ] ; then
|
|
OS=GNU/Linux
|
|
else
|
|
case ${GRUB_DISTRIBUTOR} in
|
|
Ubuntu|Kubuntu)
|
|
OS="${GRUB_DISTRIBUTOR}"
|
|
;;
|
|
*)
|
|
OS="${GRUB_DISTRIBUTOR} GNU/Linux"
|
|
;;
|
|
esac
|
|
CLASS="--class $(echo ${GRUB_DISTRIBUTOR} | tr 'A-Z' 'a-z' | cut -d' ' -f1 | LC_ALL=C sed 's,[^[:alnum:]_],_,g') ${CLASS}"
|
|
fi
|
|
|
|
if [ -x /lib/recovery-mode/recovery-menu ]; then
|
|
GRUB_CMDLINE_LINUX_RECOVERY=recovery
|
|
else
|
|
GRUB_CMDLINE_LINUX_RECOVERY=single
|
|
fi
|
|
if [ "${ubuntu_recovery}" = 1 ]; then
|
|
GRUB_CMDLINE_LINUX_RECOVERY="${GRUB_CMDLINE_LINUX_RECOVERY} nomodeset"
|
|
fi
|
|
|
|
case "$GENKERNEL_ARCH" in
|
|
x86*) GRUB_CMDLINE_LINUX_RECOVERY="$GRUB_CMDLINE_LINUX_RECOVERY dis_ucode_ldr";;
|
|
esac
|
|
|
|
|
|
if [ "${vt_handoff}" = 1 ]; then
|
|
for word in ${GRUB_CMDLINE_LINUX_DEFAULT}; do
|
|
if [ "${word}" = splash ]; then
|
|
GRUB_CMDLINE_LINUX_DEFAULT="${GRUB_CMDLINE_LINUX_DEFAULT} \${vt_handoff}"
|
|
fi
|
|
done
|
|
fi
|
|
|
|
print_menu_prologue
|
|
|
|
cat<<'EOF'
|
|
function zsyshistorymenu {
|
|
# $1: root dataset (eg rpool/ROOT/ubuntu_2zhm07@autozsys_k56fr6)
|
|
# $2: boot device id (eg 411f29ce1557bfed)
|
|
# $3: initrd (eg /BOOT/ubuntu_2zhm07@autozsys_k56fr6/initrd.img-5.4.0-21-generic)
|
|
# $4: kernel (eg /BOOT/ubuntu_2zhm07@autozsys_k56fr6/vmlinuz-5.4.0-21-generic)
|
|
# $5: kernel_version (eg 5.4.0-21-generic)
|
|
|
|
set root_dataset="${1}"
|
|
set boot_device="${2}"
|
|
set initrd="${3}"
|
|
set kernel="${4}"
|
|
set kversion="${5}"
|
|
|
|
EOF
|
|
boot_devices=$(echo "${menu_metadata}" | cut -d"$(printf '\t')" -f6 | sort -u)
|
|
|
|
title=$(gettext_printf "Revert system only")
|
|
zfs_linux_entry 1 "${title}" "simple" '${root_dataset}' '${boot_device}' '${initrd}' '${kernel}' '${kversion}' '' "${boot_devices}"
|
|
|
|
title="$(gettext_printf "Revert system and user data")"
|
|
zfs_linux_entry 1 "${title}" "simple" '${root_dataset}' '${boot_device}' '${initrd}' '${kernel}' '${kversion}' 'zsys-revert=userdata' "${boot_devices}"
|
|
|
|
GRUB_DISABLE_RECOVERY="${GRUB_DISABLE_RECOVERY:-}"
|
|
if [ "${GRUB_DISABLE_RECOVERY}" != "true" ]; then
|
|
title="$(gettext_printf "Revert system only (%s)" "$(gettext "${GRUB_RECOVERY_TITLE}")")"
|
|
zfs_linux_entry 1 "${title}" "recovery" '${root_dataset}' '${boot_device}' '${initrd}' '${kernel}' '${kversion}' '' "${boot_devices}"
|
|
|
|
title="$(gettext_printf "Revert system and user data (%s)" "$(gettext "${GRUB_RECOVERY_TITLE}")")"
|
|
zfs_linux_entry 1 "${title}" "recovery" '${root_dataset}' '${boot_device}' '${initrd}' '${kernel}' '${kversion}' 'zsys-revert=userdata' "${boot_devices}"
|
|
fi
|
|
echo "}"
|
|
echo
|
|
|
|
# IFS is set to TAB (ASCII 0x09)
|
|
echo "${menu_metadata}" |
|
|
{
|
|
at_least_one_entry=0
|
|
have_zsys="$(which zsysd || true)"
|
|
while IFS="$(printf '\t')" read -r machineid iszsys section name dataset device initrd kernel opt; do
|
|
|
|
# Disable history for non zsys system or if systems is a zsys one and zsys isn't installed.
|
|
# In pure zfs systems, we identified multiple issues due to the mount generator
|
|
# in upstream zfs which makes it incompatible. Don't show history for now.
|
|
if [ "${section}" = "history" ]; then
|
|
if [ "${iszsys}" != "yes" ] || [ "${iszsys}" = "yes" -a -z "${have_zsys}" ]; then
|
|
continue
|
|
fi
|
|
fi
|
|
|
|
if [ "${last_section}" != "${section}" -a -n "${last_section}" ]; then
|
|
# Close previous section wrapper
|
|
if [ "${last_section}" != "main" ]; then
|
|
echo "}" # Add grub_tabs
|
|
at_least_one_entry=0
|
|
fi
|
|
fi
|
|
|
|
case "${section}" in
|
|
main)
|
|
title="${name}"
|
|
main_dataset_name="${name}"
|
|
main_dataset="${dataset}"
|
|
|
|
kernel_version=$(basename "${kernel}" | sed -e "s,^[^0-9]*-,,g")
|
|
zfs_linux_entry 0 "${title}" "simple" "${dataset}" "${device}" "${initrd}" "${kernel}" "${kernel_version}"
|
|
at_least_one_entry=1
|
|
;;
|
|
advanced)
|
|
# normal and recovery entries for a given kernel
|
|
if [ "${last_section}" != "${section}" ]; then
|
|
echo "submenu '$(gettext_printf "Advanced options for %s" "${main_dataset_name}" | grub_quote)' \${menuentry_id_option} 'gnulinux-advanced-${main_dataset}' {"
|
|
fi
|
|
|
|
last_booted_kernel_marker=""
|
|
if [ "${opt}" = "true" ]; then
|
|
last_booted_kernel_marker="* "
|
|
fi
|
|
|
|
kernel_version=$(basename "${kernel}" | sed -e "s,^[^0-9]*-,,g")
|
|
title="$(gettext_printf "%s%s, with Linux %s" "${last_booted_kernel_marker}" "${name}" "${kernel_version}")"
|
|
zfs_linux_entry 1 "${title}" "advanced" "${dataset}" "${device}" "${initrd}" "${kernel}" "${kernel_version}"
|
|
|
|
GRUB_DISABLE_RECOVERY=${GRUB_DISABLE_RECOVERY:-}
|
|
if [ "${GRUB_DISABLE_RECOVERY}" != "true" ]; then
|
|
title="$(gettext_printf "%s%s, with Linux %s (%s)" "${last_booted_kernel_marker}" "${name}" "${kernel_version}" "$(gettext "${GRUB_RECOVERY_TITLE}")")"
|
|
zfs_linux_entry 1 "${title}" "recovery" "${dataset}" "${device}" "${initrd}" "${kernel}" "${kernel_version}"
|
|
fi
|
|
at_least_one_entry=1
|
|
;;
|
|
history)
|
|
# Revert to a snapshot
|
|
# revert system, revert system and user data and associated recovery entries
|
|
if [ "${last_section}" != "${section}" ]; then
|
|
echo "submenu '$(gettext_printf "History for %s" "${main_dataset_name}" | grub_quote)' \${menuentry_id_option} 'gnulinux-history-${main_dataset}' {"
|
|
fi
|
|
|
|
if [ "${iszsys}" = "yes" ]; then
|
|
title="$(gettext_printf "Revert to %s" "${name}" | grub_quote)"
|
|
else
|
|
title="$(gettext_printf "Boot on %s" "${name}" | grub_quote)"
|
|
fi
|
|
echo " submenu '${title}' \${menuentry_id_option} 'gnulinux-history-${dataset}' {"
|
|
|
|
kernel_version=$(basename "${kernel}" | sed -e "s,^[^0-9]*-,,g")
|
|
|
|
# Zsys only: let revert system without destroying snapshots
|
|
if [ "${iszsys}" = "yes" ]; then
|
|
echo "${grub_tab}${grub_tab}zsyshistorymenu" \"${dataset}\" \"${device}\" \"${initrd}\" \"${kernel}\" \"${kernel_version}\"
|
|
# Non-zsys: boot temporarly on snapshots or rollback (destroying intermediate snapshots)
|
|
else
|
|
title="$(gettext_printf "One time boot")"
|
|
zfs_linux_entry 2 "${title}" "simple" "${dataset}" "${device}" "${initrd}" "${kernel}" "${kernel_version}"
|
|
|
|
GRUB_DISABLE_RECOVERY="${GRUB_DISABLE_RECOVERY:-}"
|
|
if [ "${GRUB_DISABLE_RECOVERY}" != "true" ]; then
|
|
title="$(gettext_printf "One time boot (%s)" "$(gettext "${GRUB_RECOVERY_TITLE}")")"
|
|
zfs_linux_entry 2 "${title}" "recovery" "${dataset}" "${device}" "${initrd}" "${kernel}" "${kernel_version}"
|
|
fi
|
|
|
|
title="$(gettext_printf "Revert system (all intermediate snapshots will be destroyed)")"
|
|
zfs_linux_entry 2 "${title}" "simple" "${dataset}" "${device}" "${initrd}" "${kernel}" "${kernel_version}" "rollback=yes"
|
|
fi
|
|
|
|
echo " }"
|
|
at_least_one_entry=1
|
|
;;
|
|
*)
|
|
grub_warn "unknown section: ${section}. Ignoring entry ${name} for ${dataset}"
|
|
;;
|
|
esac
|
|
last_section="${section}"
|
|
done
|
|
|
|
if [ "${at_least_one_entry}" -eq 1 ]; then
|
|
echo "}"
|
|
fi
|
|
}
|
|
}
|
|
|
|
# don't add trailing newline of variable is empty
|
|
# $1: content to write
|
|
# $2: destination file
|
|
trailing_newline_if_not_empty() {
|
|
content="$1"
|
|
dest="$2"
|
|
|
|
if [ -z "${content}" ]; then
|
|
rm -f "${dest}"
|
|
touch "${dest}"
|
|
return
|
|
fi
|
|
echo "${content}" > "${dest}"
|
|
}
|
|
|
|
|
|
GRUB_LINUX_ZFS_TEST="${GRUB_LINUX_ZFS_TEST:-}"
|
|
case "${GRUB_LINUX_ZFS_TEST}" in
|
|
bootlist)
|
|
# Import all available pools on the system and return imported list
|
|
imported_pools=$(import_pools)
|
|
boot_list="$(bootlist ${MNTDIR})"
|
|
trailing_newline_if_not_empty "${boot_list}" "${GRUB_LINUX_ZFS_TEST_OUTPUT}"
|
|
break
|
|
;;
|
|
metamenu)
|
|
boot_list="$(cat ${GRUB_LINUX_ZFS_TEST_INPUT})"
|
|
menu_metadata="$(generate_grub_menu_metadata "${boot_list}")"
|
|
trailing_newline_if_not_empty "${menu_metadata}" "${GRUB_LINUX_ZFS_TEST_OUTPUT}"
|
|
break
|
|
;;
|
|
grubmenu)
|
|
menu_metadata="$(cat ${GRUB_LINUX_ZFS_TEST_INPUT})"
|
|
grub_menu=$(generate_grub_menu "${menu_metadata}")
|
|
trailing_newline_if_not_empty "${grub_menu}" "${GRUB_LINUX_ZFS_TEST_OUTPUT}"
|
|
break
|
|
;;
|
|
*)
|
|
# Import all available pools on the system and return imported list
|
|
imported_pools=$(import_pools)
|
|
# Generate the complete list of boot entries
|
|
boot_list="$(bootlist ${MNTDIR})"
|
|
# Create boot menu meta data from the list of boot entries
|
|
menu_metadata="$(generate_grub_menu_metadata "${boot_list}")"
|
|
# Create boot menu meta data from the list of boot entries
|
|
grub_menu="$(generate_grub_menu "${menu_metadata}")"
|
|
if [ -n "${grub_menu}" ]; then
|
|
# We want the trailing newline as a marker will be added
|
|
echo "${grub_menu}"
|
|
fi
|
|
;;
|
|
esac
|