vds89

20_linux_zfs

Jan 2nd, 2023 (edited)
83
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 43.13 KB | None | 0 0
  1. #! /bin/sh
  2. set -e
  3.  
  4. # grub-mkconfig helper script.
  5. # Copyright (C) 2019 Canonical Ltd.
  6. #
  7. # GRUB is free software: you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation, either version 3 of the License, or
  10. # (at your option) any later version.
  11. #
  12. # GRUB is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU General Public License
  18. # along with GRUB. If not, see <http://www.gnu.org/licenses/>.
  19.  
  20. prefix="/usr"
  21. datarootdir="/usr/share"
  22. ubuntu_recovery="1"
  23. quiet_boot="1"
  24. quick_boot="1"
  25. gfxpayload_dynamic="1"
  26. vt_handoff="1"
  27.  
  28. . "${pkgdatadir}/grub-mkconfig_lib"
  29.  
  30. export TEXTDOMAIN=grub
  31. export TEXTDOMAINDIR="${datarootdir}/locale"
  32.  
  33. set -u
  34.  
  35. ## Skip early if zfs utils isn't installed (instead of failing on first zpool list)
  36. if ! `which zfs >/dev/null 2>&1`; then
  37. exit 0
  38. fi
  39.  
  40. imported_pools=""
  41. MNTDIR="$(mktemp -d ${TMPDIR:-/tmp}/zfsmnt.XXXXXX)"
  42. ZFSTMP="$(mktemp -d ${TMPDIR:-/tmp}/zfstmp.XXXXXX)"
  43.  
  44.  
  45. machine="$(uname -m)"
  46. case "${machine}" in
  47. i?86) GENKERNEL_ARCH="x86" ;;
  48. mips|mips64) GENKERNEL_ARCH="mips" ;;
  49. mipsel|mips64el) GENKERNEL_ARCH="mipsel" ;;
  50. arm*) GENKERNEL_ARCH="arm" ;;
  51. *) GENKERNEL_ARCH="${machine}" ;;
  52. esac
  53.  
  54. RC=0
  55. on_exit() {
  56. # Restore initial zpool import state
  57. for pool in ${imported_pools}; do
  58. zpool export "${pool}"
  59. done
  60.  
  61. mountpoint -q "${MNTDIR}" && umount "${MNTDIR}" || true
  62. rmdir "${MNTDIR}"
  63. rm -rf "${ZFSTMP}"
  64. exit "${RC}"
  65. }
  66. trap on_exit EXIT INT QUIT ABRT PIPE TERM
  67.  
  68. # List ONLINE and DEGRADED pools
  69. import_pools() {
  70. # We have to ignore zpool import output, as potentially multiple / will be available,
  71. # and we need to autodetect all zpools this way with their real mountpoints.
  72. local initial_pools="$(zpool list | awk '{if (NR>1) print $1}')"
  73. local all_pools=""
  74. local imported_pools=""
  75. local err=""
  76.  
  77. set +e
  78. err="$(zpool import -f -a -o cachefile=none -o readonly=on -N 2>&1)"
  79. # Only print stderr if the command returned an error
  80. # (it can echo "No zpool to import" with success, which we don't want)
  81. if [ $? -ne 0 ]; then
  82. echo "Some pools couldn't be imported and will be ignored:\n${err}" >&2
  83. fi
  84. set -e
  85.  
  86. all_pools="$(zpool list | awk '{if (NR>1) print $1}')"
  87. for pool in ${all_pools}; do
  88. if echo "${initial_pools}" | grep -wq "${pool}"; then
  89. continue
  90. fi
  91. imported_pools="${imported_pools} ${pool}"
  92. done
  93.  
  94. echo "${imported_pools}"
  95. }
  96.  
  97. # List all the dataset with a root mountpoint
  98. get_root_datasets() {
  99. local pools="$(zpool list | awk '{if (NR>1) print $1}')"
  100.  
  101. for p in ${pools}; do
  102. local rel_pool_root=$(zpool get -H altroot ${p} | awk '{print $3}')
  103. if [ "${rel_pool_root}" = "-" ]; then
  104. rel_pool_root="/"
  105. fi
  106.  
  107. zfs list -H -o name,canmount,mountpoint -t filesystem | grep -E '^'"${p}"'(\s|/[[:print:]]*\s)(on|noauto)\s'"${rel_pool_root}"'$' | awk '{print $1}'
  108. done
  109. }
  110.  
  111. # find if given datasets can be mounted for directory and return its path (snapshot or real path)
  112. # $1 is our current dataset name
  113. # $2 directory path we look for (cannot contains /)
  114. # $3 is the temporary mount directory to use
  115. # $4 is the optional snapshot name
  116. # return path for directory (which can be a mountpoint)
  117. validate_system_dataset() {
  118. local dataset="$1"
  119. local directory="$2"
  120. local mntdir="$3"
  121. local snapshot_name="$4"
  122.  
  123. local mount_path="${mntdir}/${directory}"
  124.  
  125. if ! zfs list "${dataset}" >/dev/null 2>&1; then
  126. return
  127. fi
  128.  
  129. if ! mount -o noatime,zfsutil -t zfs "${dataset}" "${mount_path}"; then
  130. grub_warn "Failed to find a valid directory '${directory}' for dataset '${dataset}@${snapshot_name}'. Ignoring"
  131. return
  132. fi
  133.  
  134. local candidate_path="${mount_path}"
  135. if [ -n "${snapshot_name}" ]; then
  136. # WORKAROUND a bug https://github.com/zfsonlinux/zfs/issues/9958
  137. # Reading the content of a snapshot fails if it is not the first mount
  138. # for a given dataset
  139. first_mntdir=$(awk '{if ($1 == "'${dataset}'") {print $2; exit;}}' /proc/mounts)
  140. if [ "${first_mntdir}" = "/" ]; then
  141. # prevents // on candidate_path
  142. first_mntdir=""
  143. fi
  144. candidate_path="${first_mntdir}/.zfs/snapshot/${snapshot_name}"
  145. fi
  146.  
  147. if [ -n "$(ls "${candidate_path}" 2>/dev/null)" ]; then
  148. echo "${candidate_path}"
  149. return
  150. else
  151. mountpoint -q "${mount_path}" && umount "${mount_path}" || true
  152. fi
  153. }
  154.  
  155. # Detect system directory relevant to the other, trying to find the ones associated on the current dataset or snapshot/
  156. # System directory should be at most a direct child dataset of main datasets (no recursivity)
  157. # We can fallback trying other zfs pools if no match has been found.
  158. # $1 is our current dataset name (which can have @snapshot name)
  159. # $2 directory path we look for (cannot contains /)
  160. # $3 restrict_to_same_pool (true|false) force looking for dataset with the same basename in the current dataset pool only
  161. # $4 is the temporary mount directory to use
  162. # $5 is the optional etc directory (if not $2 is not etc itself)
  163. # return path for directory (which can be a mountpoint)
  164. get_system_directory() {
  165. local dataset_path="$1"
  166. local directory="$2"
  167. local restrict_to_same_pool="$3"
  168. local mntdir="$4"
  169. local etc_dir="$5"
  170.  
  171. if [ -z "${etc_dir}" ]; then
  172. etc_dir="${mntdir}/etc"
  173. fi
  174.  
  175. local candidate_path="${mntdir}/${directory}"
  176.  
  177. # 1. Look for /etc/fstab first (which will mount even on top of non empty $directory)
  178. local mounted_fstab_entry="false"
  179. if [ -f "${etc_dir}/fstab" ]; then
  180. mount_args=$(awk '/^[^#].*[ \t]\/'"${directory}"'[ \t]/ {print "-t", $3, $1}' "${etc_dir}/fstab")
  181. if [ -n "${mount_args}" ]; then
  182. mounted_fstab_entry="true"
  183. mount -o noatime ${mount_args} "${candidate_path}" || mounted_fstab_entry="false"
  184. fi
  185. fi
  186.  
  187. # If directory isn't empty. Only count if coming from /etc/fstab. Will be
  188. # handled below otherwise as we are interested in potential snapshots.
  189. if [ "${mounted_fstab_entry}" = "true" -a -n "$(ls ${candidate_path} 2>/dev/null)" ]; then
  190. echo "${candidate_path}"
  191. return
  192. fi
  193.  
  194. # 2. Handle zfs case, which can be a snapshots.
  195.  
  196. local base_dataset_path="${dataset_path}"
  197. local snapshot_name=""
  198. # For snapshots we extract the parent dataset
  199. if echo "${dataset_path}" | grep -q '@'; then
  200. base_dataset_path=$(echo "${dataset_path}" | cut -d '@' -f1)
  201. snapshot_name=$(echo "${dataset_path}" | cut -d '@' -f2)
  202. fi
  203. base_dataset_name="${base_dataset_path##*/}"
  204. base_pool="$(echo "${base_dataset_path}" | cut -d'/' -f1)"
  205.  
  206. # 2.a) Look for child dataset included in base dataset, which needs to hold same snapshot if any
  207. candidate_path=$(validate_system_dataset "${base_dataset_path}/${directory}" "${directory}" "${mntdir}" "${snapshot_name}")
  208. if [ -n "${candidate_path}" ]; then
  209. echo "${candidate_path}"
  210. return
  211. fi
  212.  
  213. # 2.b) Look for current dataset (which is already mounted as /)
  214. candidate_path="${mntdir}/${directory}"
  215. if [ -n "${snapshot_name}" ]; then
  216. # WORKAROUND a bug https://github.com/zfsonlinux/zfs/issues/9958
  217. # Reading the content of a snapshot fails if it is not the first mount
  218. # for a given dataset
  219. first_mntdir=$(awk '{if ($1 == "'${base_dataset_path}'") {print $2; exit;}}' /proc/mounts)
  220. if [ "${first_mntdir}" = "/" ]; then
  221. # prevents // on candidate_path
  222. first_mntdir=""
  223. fi
  224. candidate_path="${first_mntdir}/.zfs/snapshot/${snapshot_name}/${directory}"
  225. fi
  226. if [ -n "$(ls "${candidate_path}" 2>/dev/null)" ]; then
  227. echo "${candidate_path}"
  228. return
  229. fi
  230.  
  231. # 2.c) Look for every datasets in every pool which isn't the current dataset which holds:
  232. # - the same dataset name (last section) than our base_dataset_name
  233. # - mountpoint=directory
  234. # - canmount!=off
  235. all_same_base_dataset_name="$(zfs list -H -t filesystem -o name,canmount | awk '/^[^ ]+\/'"${base_dataset_name}"'[ \t](on|noauto)/ {print $1}') "
  236.  
  237. # order by local pool datasets first
  238. current_pool_same_base_datasets=""
  239. other_pools_same_base_datasets=""
  240. root_pool=$(echo "${dataset_path%%/*}")
  241. for d in ${all_same_base_dataset_name}; do
  242. cur_dataset_pool=$(echo "${d%%/*}")
  243. if echo "${cur_dataset_pool}" | grep -wq "${root_pool}" 2>/dev/null ; then
  244. current_pool_same_base_datasets="${current_pool_same_base_datasets} ${d}"
  245. else
  246. other_pools_same_base_datasets="${other_pools_same_base_datasets} ${d}"
  247. fi
  248. done
  249. ordered_same_base_datasets="${current_pool_same_base_datasets} ${other_pools_same_base_datasets}"
  250. if [ "${restrict_to_same_pool}" = "true" ]; then
  251. ordered_same_base_datasets="${current_pool_same_base_datasets}"
  252. fi
  253.  
  254. # now, loop over them
  255. for d in ${ordered_same_base_datasets}; do
  256. cur_dataset_pool=$(echo "${d%%/*}")
  257.  
  258. rel_pool_root=$(zpool get -H altroot ${cur_dataset_pool} | awk '{print $3}')
  259. if [ "${rel_pool_root}" = "-" ]; then
  260. rel_pool_root=""
  261. fi
  262.  
  263. # check mountpoint match
  264. candidate_dataset=$(zfs get -H mountpoint ${d} | grep -E "mountpoint\s${rel_pool_root}/${directory}\s" | awk '{print $1}')
  265. if [ -z "${candidate_dataset}" ]; then
  266. continue
  267. fi
  268.  
  269. candidate_path=$(validate_system_dataset "${candidate_dataset}" "${directory}" "${mntdir}" "${snapshot_name}")
  270. if [ -n "${candidate_path}" ]; then
  271. echo "${candidate_path}"
  272. return
  273. fi
  274. done
  275.  
  276. # 2.d) If we didn't find anything yet: check for persistent datasets corresponding to our mountpoint, with canmount=on without any snapshot associated:
  277. # Note: we go over previous datasets as well, but this is ok, as we didn't include them before.
  278. all_mountable_datasets="$(zfs list -t filesystem -o name,canmount | awk '/^[^ ]+[ \t]+on/ {print $1}')"
  279.  
  280. # order by local pool datasets first
  281. current_pool_datasets=""
  282. other_pools_datasets=""
  283. root_pool=$(echo "${dataset_path%%/*}")
  284. for d in ${all_mountable_datasets}; do
  285. cur_dataset_pool=$(echo "${d%%/*}")
  286. if echo "${cur_dataset_pool}" | grep -wq "${root_pool}" 2>/dev/null ; then
  287. current_pool_datasets="${current_pool_datasets} ${d}"
  288. else
  289. other_pools_datasets="${other_pools_datasets} ${d}"
  290. fi
  291. done
  292. ordered_datasets="${current_pool_datasets} ${other_pools_datasets}"
  293. if [ "${restrict_to_same_pool}" = "true" ]; then
  294. ordered_datasets="${current_pool_datasets}"
  295. fi
  296.  
  297. for d in ${ordered_datasets}; do
  298. cur_dataset_pool=$(echo "${d%%/*}")
  299.  
  300. rel_pool_root=$(zpool get -H altroot ${cur_dataset_pool} | awk '{print $3}')
  301. if [ "${rel_pool_root}" = "-" ]; then
  302. rel_pool_root=""
  303. fi
  304.  
  305. # check mountpoint match
  306. candidate_dataset=$(zfs get -H mountpoint ${d} | grep -E "mountpoint\s${rel_pool_root}/${directory}\s" | awk '{print $1}')
  307. if [ -z "${candidate_dataset}" ]; then
  308. continue
  309. fi
  310.  
  311. candidate_path=$(validate_system_dataset "${d}" "${directory}" "${mntdir}" "")
  312. if [ -n "${candidate_path}" ]; then
  313. echo "${candidate_path}"
  314. return
  315. fi
  316. done
  317.  
  318. grub_warn "Failed to find a valid directory '${directory}' for dataset '${dataset_path}'. Ignoring"
  319. return
  320. }
  321.  
  322. # Try our default layout bpool as a prefered layout (fast path)
  323. # This is get_system_directory for boot optimized for our default installation layout
  324. # $1 is our current dataset name (which can have @snapshot name)
  325. # $2 is the temporary mount directory to use
  326. # return path for directory (which can be a mountpoint) if found
  327. try_default_layout_bpool() {
  328. local root_dataset_path="$1"
  329. local mntdir="$2"
  330.  
  331. dataset_basename="${root_dataset_path##*/}"
  332. candidate_dataset="bpool/BOOT/${dataset_basename}"
  333. dataset_properties="$(zfs get -H mountpoint,canmount "${candidate_dataset}" 2>/dev/null | cut -f3 | paste -sd ' ')"
  334. if [ -z "${dataset_properties}" ]; then
  335. return
  336. fi
  337.  
  338. rel_pool_root=$(zpool get -H altroot bpool | awk '{print $3}')
  339. if [ "${rel_pool_root}" = "-" ]; then
  340. rel_pool_root=""
  341. fi
  342.  
  343. snapshot_name="${dataset_basename##*@}"
  344. [ "${snapshot_name}" = "${dataset_basename}" ] && snapshot_name=""
  345. if [ -z "${snapshot_name}" ]; then
  346. if ! echo "${dataset_properties}" | grep -Eq "${rel_pool_root}/boot (on|noauto)"; then
  347. return
  348. fi
  349. else
  350. candidate_dataset=$(echo "${candidate_dataset}" | cut -d '@' -f1)
  351. fi
  352.  
  353. validate_system_dataset "${candidate_dataset}" "boot" "${mntdir}" "${snapshot_name}"
  354. }
  355.  
  356. # Return if secure boot is enabled on that system
  357. is_secure_boot_enabled() {
  358. if LANG=C mokutil --sb-state 2>/dev/null | grep -qi enabled; then
  359. echo "true"
  360. return
  361. fi
  362. echo "false"
  363. return
  364. }
  365.  
  366. # Given a filesystem or snapshot dataset, returns dataset|machine id|pretty name|last used
  367. # $1 is dataset we want information from
  368. # $2 is the temporary mount directory to use
  369. get_dataset_info() {
  370. local dataset="$1"
  371. local mntdir="$2"
  372.  
  373. local base_dataset="${dataset}"
  374. local etc_dir="${mntdir}/etc"
  375. local is_snapshot="false"
  376. # For snapshot we extract the parent dataset
  377. if echo "${dataset}" | grep -q '@'; then
  378. base_dataset=$(echo "${dataset}" | cut -d '@' -f1)
  379. is_snapshot="true"
  380. fi
  381.  
  382. mount -o noatime,zfsutil -t zfs "${base_dataset}" "${mntdir}"
  383.  
  384. # read machine-id/os-release from /etc
  385. etc_dir=$(get_system_directory "${dataset}" "etc" "true" "${mntdir}" "")
  386. if [ -z "${etc_dir}" ]; then
  387. grub_warn "Ignoring ${dataset}"
  388. mountpoint -q "${mntdir}/etc" && umount "${mntdir}/etc" || true
  389. umount "${mntdir}"
  390. return
  391. fi
  392.  
  393. machine_id=""
  394. if [ -f "${etc_dir}/machine-id" ]; then
  395. machine_id=$(cat "${etc_dir}/machine-id")
  396. fi
  397. # We have to use a random temporary id if we don't have any machine-id file or if this one is empty
  398. # (mostly the case of new installations before first boot).
  399. # Let's use the dataset name directly for this.
  400. # Consequence is that all datasets are then separated.
  401. if [ -z "${machine_id}" ]; then
  402. machine_id="${dataset}"
  403. fi
  404. pretty_name=$(. "${etc_dir}/os-release" && echo "${PRETTY_NAME}")
  405. mountpoint -q "${mntdir}/etc" && umount "${mntdir}/etc" || true
  406.  
  407. # read available kernels from /boot
  408. boot_dir="$(try_default_layout_bpool "${dataset}" "${mntdir}")"
  409. if [ -z "${boot_dir}" ]; then
  410. boot_dir=$(get_system_directory "${dataset}" "boot" "false" "${mntdir}" "${etc_dir}")
  411. fi
  412.  
  413. if [ -z "${boot_dir}" ]; then
  414. grub_warn "Ignoring ${dataset}"
  415. mountpoint -q "${mntdir}/boot" && umount "${mntdir}/boot" || true
  416. umount "${mntdir}"
  417. return
  418. fi
  419.  
  420. initrd_list=""
  421. kernel_list=""
  422. candidate_kernel_list="$(find "${boot_dir}" -maxdepth 1 -type f -regex '.*/\(vmlinuz\|vmlinux\|kernel\)-.*')"
  423. while [ -n "${candidate_kernel_list}" ] ; do
  424. list_basename="$(echo "${candidate_kernel_list}" | sed -e 's#.*/##')"
  425. linux=$(version_find_latest ${list_basename})
  426. linux=$(echo "${candidate_kernel_list}" | while read k; do
  427. if [ "$(basename "${k}")" = "${linux}" ]; then
  428. echo -n "${k}"
  429. break
  430. fi
  431. done)
  432. # || true to not abort even if candidate_kernel_list is empty on last entry
  433. candidate_kernel_list="$(echo "${candidate_kernel_list}" | fgrep -vx "${linux}"||true)"
  434.  
  435. if ! grub_file_is_not_garbage "${linux}" ; then
  436. continue
  437. fi
  438.  
  439. # Filters entry if efi/non efi.
  440. # Note that for now we allow kernel without .efi.signed as those are signed kernel
  441. # on ubuntu, loaded by the shim.
  442. case "${linux}" in
  443. *.efi.signed)
  444. if [ "$(is_secure_boot_enabled)" = "false" ]; then
  445. continue
  446. fi
  447. ;;
  448. esac
  449.  
  450. linux_basename=$(basename "${linux}")
  451. linux_dirname=$(dirname "${linux}")
  452. version=$(echo "${linux_basename}" | sed -e "s,^[^0-9]*-,,g")
  453. alt_version=$(echo "${version}" | sed -e "s,\.old$,,g")
  454.  
  455. gettext_printf "Found linux image: %s in %s\n" "${linux_basename}" "${dataset}" >&2
  456.  
  457. initrd=""
  458. for i in "initrd.img-${version}" "initrd-${version}.img" "initrd-${version}.gz" \
  459. "initrd-${version}" "initramfs-${version}.img" \
  460. "initrd.img-${alt_version}" "initrd-${alt_version}.img" \
  461. "initrd-${alt_version}" "initramfs-${alt_version}.img" \
  462. "initramfs-genkernel-${version}" \
  463. "initramfs-genkernel-${alt_version}" \
  464. "initramfs-genkernel-${GENKERNEL_ARCH}-${version}" \
  465. "initramfs-genkernel-${GENKERNEL_ARCH}-${alt_version}"; do
  466. if test -e "${linux_dirname}/${i}" ; then
  467. initrd="$i"
  468. break
  469. fi
  470. done
  471.  
  472. if test -z "${initrd}" ; then
  473. grub_warn "Couldn't find any valid initrd for dataset ${dataset}."
  474. continue
  475. fi
  476.  
  477. gettext_printf "Found initrd image: %s in %s\n" "${initrd}" "${dataset}" >&2
  478.  
  479. rel_linux_dirname=$(make_system_path_relative_to_its_root "${linux_dirname}")
  480.  
  481. initrd_list="${initrd_list}|${rel_linux_dirname}/${initrd}"
  482. kernel_list="${kernel_list}|${rel_linux_dirname}/${linux_basename}"
  483. done
  484.  
  485. initrd_list="${initrd_list#|}"
  486. kernel_list="${kernel_list#|}"
  487.  
  488. initrd_device=$(${grub_probe} --target=device "${boot_dir}" | head -1)
  489.  
  490. mountpoint -q "${mntdir}/boot" && umount "${mntdir}/boot" || true
  491. # We needed to look in / for snapshots on root dataset, umount there before zfs lazily unmount it
  492. case "${boot_dir}" in /boot/.zfs/snapshot/*)
  493. umount "${boot_dir}" || true
  494. ;;
  495. esac
  496.  
  497. # for zsys snapshots: we want to know which kernel we successful last booted with
  498. last_booted_kernel=$(zfs get -H com.ubuntu.zsys:last-booted-kernel "${dataset}" | awk -v FS='\t' '{print $3}')
  499.  
  500. # snapshot: last_used is dataset creation time
  501. if [ "${is_snapshot}" = "true" ]; then
  502. last_used="$(zfs get -pH creation "${dataset}" | awk -F '\t' '{print $3}')"
  503. # otherwise, last_used is manually marked at boot/shutdown on a root dataset for zsys
  504. else
  505. # if current system, take current time
  506. if zfs mount | awk '/[ \t]+\/$/ {print $1}' | grep -q "${dataset}"; then
  507. last_used=$(date +%s)
  508. else
  509. last_used=$(zfs get -H com.ubuntu.zsys:last-used "${dataset}" | awk '{print $3}')
  510. # case of non zsys, or zsys without annotation, take /etc/machine-id stat (as we mounted with noatime).
  511. # However, as systems can be relatime, if system is current mounted one, set current time (case of clone + reboot
  512. # within the same d).
  513. if [ "${last_used}" = "-" ]; then
  514. last_used=$(stat --printf="%X" "${mntdir}/etc/os-release")
  515. if [ -f "${mntdir}/etc/machine-id" ]; then
  516. last_used=$(stat --printf="%X" "${mntdir}/etc/machine-id")
  517. fi
  518. fi
  519. fi
  520. fi
  521.  
  522. is_zsys=$(zfs get -H com.ubuntu.zsys:bootfs "${base_dataset}" | awk '{print $3}')
  523.  
  524. if [ -n "${initrd_list}" -a -n "${kernel_list}" ]; then
  525. 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}"
  526. else
  527. grub_warn "didn't find any valid initrd or kernel."
  528. fi
  529.  
  530. umount "${mntdir}" || true
  531. # We needed to look in / for snapshots on root dataset, umount the snapshot for etc before zfs lazily unmount it
  532. case "${etc_dir}" in /.zfs/snapshot/*/etc)
  533. snapshot_path="$(findmnt -n -o TARGET -T "${etc_dir}")"
  534. umount "${snapshot_path}" || true
  535. ;;
  536. esac
  537. }
  538.  
  539. # Scan available boot options and returns in a formatted list
  540. # $1 is the temporary mount directory to use
  541. bootlist() {
  542. local mntdir="$1"
  543. local boot_list=""
  544.  
  545. for dataset in $(get_root_datasets); do
  546. # get information from current root dataset
  547. boot_list="${boot_list}$(get_dataset_info "${dataset}" ${mntdir})\n"
  548.  
  549. # get information from snapshots of this root dataset
  550. snapshots="$(zfs list -H -o name -t snapshot "${dataset}"|while read snapshot_dataset; do
  551. get_dataset_info "${snapshot_dataset}" ${mntdir}
  552. done)"
  553. [ -n "${snapshots}" ] && boot_list="${boot_list}${snapshots}\n"
  554. done
  555. echo "${boot_list}"
  556. }
  557.  
  558.  
  559. # Order machine ids by last_used from their main entry
  560. get_machines_sorted() {
  561. local bootlist="$1"
  562.  
  563. local machineids="$(echo "${bootlist}" | awk '{print $3}' | sort -u)"
  564. for machineid in ${machineids}; do
  565. echo "${bootlist}" | awk 'BEGIN{FS="\t"} $1 !~ /.*@.*/ {print $5, $3}' | sort -nr | grep -E "[^^]\b${machineid}\b" | head -1
  566. done | sort -nr | awk '{print $2}'
  567. }
  568.  
  569. # Sort entries by last_used for a given machineid
  570. sort_entries_for_machineid() {
  571. local bootlist="$1"
  572. local machineid="$2"
  573.  
  574. tab="$(printf '\t')"
  575. echo "${bootlist}" | grep -E "[^^]\b${machineid}\b" | sort -k5,5r -k1,1 -t "${tab}"
  576. }
  577.  
  578. # Return main entry index
  579. get_main_entry() {
  580. local entries="$1"
  581.  
  582. echo "${entries}" | awk 'BEGIN{FS="\t"} $1 !~ /.*@.*/ {print}' | head -1
  583. }
  584.  
  585. # Return specific field at index from entry
  586. get_field_from_entry() {
  587. local entry="$1"
  588. local index="$2"
  589.  
  590. echo "${entry}" | awk "BEGIN{FS=\"\t\"} {print \$$index}"
  591. }
  592.  
  593. # Get the main entry metadata
  594. main_entry_meta() {
  595. local main_entry="$1"
  596.  
  597. initrd=$(get_field_from_entry "${main_entry}" 7 | cut -d'|' -f1)
  598. kernel=$(get_field_from_entry "${main_entry}" 8 | cut -d'|' -f1)
  599.  
  600. # Take first element (most recent entry) which is not a snapshot
  601. echo "${main_entry}" | awk "BEGIN{ FS=\"\t\"; OFS=\"\t\"} {print \$3, \$2, \"main\", \$4, \$1, \$6, \"$initrd\", \"$kernel\"}"
  602. }
  603.  
  604. # Get advanced entries metadata
  605. advanced_entries_meta() {
  606. local main_entry="$1"
  607.  
  608. last_used_kernel="$(get_field_from_entry "${main_entry}" 9 )"
  609.  
  610. # We must align initrds with kernels.
  611. # Adds initrds to the stack then pop them 1 by 1 as we process the kernels
  612. oldIFS="$IFS"
  613. export IFS='|'
  614. set -- $(get_field_from_entry "${main_entry}" 7)
  615. for kernel in $(get_field_from_entry "${main_entry}" 8); do
  616. # get initrd and pop to the next one
  617. initrd="$1"; shift
  618.  
  619. was_last_used_kernel="false"
  620. kernel_basename=$(basename "${kernel}")
  621. if [ "${kernel_basename}" = "${last_used_kernel}" ]; then
  622. was_last_used_kernel="true"
  623. fi
  624.  
  625. echo "${main_entry}" | awk "BEGIN{ FS=\"\t\"; OFS=\"\t\"} {print \$3, \$2, \"advanced\", \$4, \$1, \$6, \"$initrd\", \"$kernel\", \"$was_last_used_kernel\"}"
  626. done
  627. IFS="$oldIFS"
  628. }
  629.  
  630. # Get history metadata
  631. history_entries_meta() {
  632. local entries="$1"
  633. local main_dataset_name="$2"
  634. local main_dataset_releasename="$3"
  635.  
  636. if [ -z "${entries}" ]; then
  637. return
  638. fi
  639.  
  640. # Traverse snapshots and clones
  641. echo "${entries}" | while read entry; do
  642. name=""
  643. # Compute snapshot/filesystem dataset name
  644. snap_dataset_name="$(get_field_from_entry "${entry}" 1)"
  645.  
  646. snapname="${snap_dataset_name##*@}"
  647. # If, this is a clone, take what is after main_dataset_name
  648. if [ "${snapname}" = "${snap_dataset_name}" ]; then
  649. snapname="${snap_dataset_name##${main_dataset_name}_}"
  650.  
  651. # Handle manual user clone (not prefixed by "main_dataset_name")
  652. snapname="${snapname##*/}"
  653. fi
  654.  
  655. # We keep the snapname only if it is not only a zsys auto snapshot
  656. if echo "${snapname}" | grep -q "^autozsys_"; then
  657. snapname=""
  658. fi
  659.  
  660. # We store the release only if it different from main dataset release (snapshot before a release upgrade)
  661. releasename=$(get_field_from_entry "${entry}" 4)
  662. if [ "${releasename}" = "${main_dataset_releasename}" ]; then
  663. releasename=""
  664. fi
  665.  
  666. # Snapshot date
  667. foo="$(get_field_from_entry "${entry}" 5)"
  668. snapdate="$(date -d @$(get_field_from_entry "${entry}" 5) "+%x @ %H:%M")"
  669.  
  670. # For snapshots/clones the name can have the following formats:
  671. # <DATE>: autozsys, same release
  672. # <OLD_RELEASE> on <DATE>: autozsys, different release
  673. # <SNAPNAME> on <DATE>: Manual snapshot, same release
  674. # <SNAPNAME>, <OLD_RELEASE> on <DATE>: Manual snapshot, different release
  675. if [ "${snapname}" = "" -a "${releasename}" = "" ]; then
  676. name="${snapdate}"
  677. elif [ "${snapname}" = "" -a "${releasename}" != "" ]; then
  678. name=$(gettext_printf "%s on %s" "${releasename}" "${snapdate}")
  679. elif [ "${snapname}" != "" -a "${releasename}" = "" ]; then
  680. name=$(gettext_printf "%s on %s" "${snapname}" "${snapdate}")
  681. else # snapname != "" && releasename != ""
  682. name=$(gettext_printf "%s, %s on %s" "${snapname}" "${releasename}" "${snapdate}")
  683. fi
  684.  
  685. # Choose kernel and initrd if the snapshot was booted successfully on a specific kernel before
  686. # Take latest by default if no match
  687. initrd=$(get_field_from_entry "${entry}" 7 | cut -d'|' -f1)
  688. kernel=$(get_field_from_entry "${entry}" 8 | cut -d'|' -f1)
  689. last_used_kernel="$(get_field_from_entry "${entry}" 9)"
  690.  
  691. # We must align initrds with kernels.
  692. # Adds initrds to the stack then pop them 1 by 1 as we process the kernels
  693. oldIFS="$IFS"
  694. export IFS='|'
  695. set -- $(get_field_from_entry "${entry}" 7)
  696. for k in $(get_field_from_entry "${entry}" 8); do
  697. # get initrd and pop to the next one
  698. candidate_initrd="$1"; shift
  699.  
  700. kernel_basename=$(basename -- "${k}")
  701. if [ "${kernel_basename}" = "${last_used_kernel}" ]; then
  702. kernel="${k}"
  703. initrd="${candidate_initrd}"
  704. break
  705. fi
  706. done
  707. IFS="$oldIFS"
  708.  
  709. echo "${entry}" | awk "BEGIN{ FS=\"\t\"; OFS=\"\t\"} {print \$3, \$2, \"history\", \"$name\", \$1, \$6, \"$initrd\", \"$kernel\"}"
  710. done
  711. }
  712.  
  713. # Generate metadata from a BOOTLIST that will subsequently used to generate
  714. # the final grub menu entries
  715. generate_grub_menu_metadata() {
  716. local bootlist="$1"
  717.  
  718. # Sort machineids by last_used from their main entry
  719. for machineid in $(get_machines_sorted "${bootlist}"); do
  720. entries="$(sort_entries_for_machineid "${bootlist}" ${machineid})"
  721. main_entry="$(get_main_entry "${entries}")"
  722.  
  723. if [ -z "$main_entry" ]; then
  724. continue
  725. fi
  726.  
  727. main_entry_meta "${main_entry}"
  728. advanced_entries_meta "${main_entry}"
  729.  
  730. main_dataset_name="$(get_field_from_entry "${main_entry}" 1)"
  731. main_dataset_releasename="$(get_field_from_entry "${main_entry}" 4)"
  732. # grep -v errcode != 0 if there is no match. || true to not fail with -e
  733. other_entries="$(echo "${entries}" | grep -v "${main_entry}" || true)"
  734. history_entries_meta "${other_entries}" "${main_dataset_name}" "${main_dataset_releasename}"
  735. done
  736. }
  737.  
  738. # Print the configuration part common to all sections
  739. # Note:
  740. # If 10_linux runs these part will be defined twice in grub configuration
  741. print_menu_prologue() {
  742. cat << 'EOF'
  743. function gfxmode {
  744. set gfxpayload="${1}"
  745. EOF
  746. if [ "${vt_handoff}" = 1 ]; then
  747. cat << 'EOF'
  748. if [ "${1}" = "keep" ]; then
  749. set vt_handoff=vt.handoff=1
  750. else
  751. set vt_handoff=
  752. fi
  753. EOF
  754. fi
  755. cat << EOF
  756. }
  757. EOF
  758.  
  759. # Use ELILO's generic "efifb" when it's known to be available.
  760. # FIXME: We need an interface to select vesafb in case efifb can't be used.
  761. GRUB_GFXPAYLOAD_LINUX="${GRUB_GFXPAYLOAD_LINUX:-}"
  762. if [ "${GRUB_GFXPAYLOAD_LINUX}" != "" ] || [ "${gfxpayload_dynamic}" = 0 ]; then
  763. echo "set linux_gfx_mode=${GRUB_GFXPAYLOAD_LINUX}"
  764. else
  765. cat << EOF
  766. if [ "\${recordfail}" != 1 ]; then
  767. if [ -e \${prefix}/gfxblacklist.txt ]; then
  768. if hwmatch \${prefix}/gfxblacklist.txt 3; then
  769. if [ \${match} = 0 ]; then
  770. set linux_gfx_mode=keep
  771. else
  772. set linux_gfx_mode=text
  773. fi
  774. else
  775. set linux_gfx_mode=text
  776. fi
  777. else
  778. set linux_gfx_mode=keep
  779. fi
  780. else
  781. set linux_gfx_mode=text
  782. fi
  783. EOF
  784. fi
  785. cat << EOF
  786. export linux_gfx_mode
  787. EOF
  788. }
  789.  
  790. # Cache for prepare_grub_to_access_device call
  791. # $1: boot_device
  792. # $2: submenu_level
  793. prepare_grub_to_access_device_cached() {
  794. local boot_device="$1"
  795. local submenu_level="$2"
  796.  
  797. local boot_device_idx="$(echo ${boot_device} | tr '/' '_')"
  798.  
  799. cache_file="${ZFSTMP}/$(echo boot_device${boot_device_idx})"
  800. if [ ! -f "${cache_file}" ]; then
  801. set +u
  802. echo "$(prepare_grub_to_access_device "${boot_device}")" > "${cache_file}"
  803. set -u
  804. for i in 0 1 2; do
  805. submenu_indentation="$(printf %${i}s | tr " " "${grub_tab}")"
  806. sed "s/^/${submenu_indentation} /" "${cache_file}" > "${cache_file}--${i}"
  807. done
  808. fi
  809.  
  810. cat "${cache_file}--${submenu_level}"
  811. }
  812.  
  813.  
  814. # Print a grub menu entry
  815. zfs_linux_entry () {
  816. submenu_level="$1"
  817. title="$2"
  818. type="$3"
  819. dataset="$4"
  820. boot_device="$5"
  821. initrd="$6"
  822. kernel="$7"
  823. kernel_version="$8"
  824. kernel_additional_args="${9:-}"
  825. boot_devices="${10:-}"
  826.  
  827. submenu_indentation="$(printf %${submenu_level}s | tr " " "${grub_tab}")"
  828.  
  829. echo "${submenu_indentation}menuentry '$(echo "${title}" | grub_quote)' ${CLASS} \${menuentry_id_option} 'gnulinux-${dataset}-${kernel_version}' {"
  830.  
  831. if [ "${quick_boot}" = 1 ]; then
  832. echo "${submenu_indentation} recordfail"
  833. fi
  834.  
  835. if [ "${type}" != "recovery" ] ; then
  836. GRUB_SAVEDEFAULT=${GRUB_SAVEDEFAULT:-}
  837. default_entry="$(save_default_entry)"
  838. if [ -n "${default_entry}" ]; then
  839. echo "${submenu_indentation} ${default_entry}"
  840. fi
  841. fi
  842.  
  843. # Use ELILO's generic "efifb" when it's known to be available.
  844. # FIXME: We need an interface to select vesafb in case efifb can't be used.
  845. if [ "${GRUB_GFXPAYLOAD_LINUX}" = "" ]; then
  846. echo "${submenu_indentation} load_video"
  847. else
  848. if [ "${GRUB_GFXPAYLOAD_LINUX}" != "text" ]; then
  849. echo "${submenu_indentation} load_video"
  850. fi
  851. fi
  852.  
  853. if ([ "${ubuntu_recovery}" = 0 ] || [ "${type}" != "recovery" ]) && \
  854. ([ "${GRUB_GFXPAYLOAD_LINUX}" != "" ] || [ "${gfxpayload_dynamic}" = 1 ]); then
  855. echo "${submenu_indentation} gfxmode \${linux_gfx_mode}"
  856. fi
  857.  
  858. echo "${submenu_indentation} insmod gzio"
  859. echo "${submenu_indentation} if [ \"\${grub_platform}\" = xen ]; then insmod xzio; insmod lzopio; fi"
  860.  
  861. if [ -n "$boot_devices" ]; then
  862. for device in ${boot_devices}; do
  863. echo "${submenu_indentation} if [ "${boot_device}" = "${device}" ]; then"
  864. echo "$(prepare_grub_to_access_device_cached "${device}" $(( submenu_level +1 )) )"
  865. echo "${submenu_indentation} fi"
  866. done
  867. else
  868. echo "$(prepare_grub_to_access_device_cached "${boot_device}" "${submenu_level}")"
  869. fi
  870.  
  871. if [ "${quiet_boot}" = 0 ] || [ "${type}" != simple ]; then
  872. echo "${submenu_indentation} echo $(gettext_printf "Loading Linux %s ..." ${kernel_version} | grub_quote)"
  873. fi
  874.  
  875. linux_default_args="${GRUB_CMDLINE_LINUX} ${GRUB_CMDLINE_LINUX_DEFAULT}"
  876. if [ ${type} = "recovery" ]; then
  877. linux_default_args="${GRUB_CMDLINE_LINUX_RECOVERY} ${GRUB_CMDLINE_LINUX}"
  878. fi
  879.  
  880. # echo in echo trims end of line spaces
  881. echo "${submenu_indentation} linux \"${kernel}\" root=ZFS=\"${dataset}\" ro $(echo ${linux_default_args} ${kernel_additional_args})"
  882.  
  883. if [ "${quiet_boot}" = 0 ] || [ "${type}" != simple ]; then
  884. echo "${submenu_indentation} echo '$(gettext_printf "Loading initial ramdisk ..." | grub_quote)'"
  885. fi
  886. echo "${submenu_indentation} initrd \"${initrd}\""
  887. echo "${submenu_indentation}}"
  888. }
  889.  
  890. # Generate a GRUB Menu from menu meta data
  891. # $1 menu metadata
  892. generate_grub_menu() {
  893. local menu_metadata="$1"
  894. local last_section=""
  895. local main_dataset_name=""
  896. local main_dataset=""
  897. local have_zsys=""
  898.  
  899. if [ -z "${menu_metadata}" ]; then
  900. return
  901. fi
  902.  
  903. CLASS="--class gnu-linux --class gnu --class os"
  904.  
  905. if [ "${GRUB_DISTRIBUTOR}" = "" ] ; then
  906. OS=GNU/Linux
  907. else
  908. case ${GRUB_DISTRIBUTOR} in
  909. Ubuntu|Kubuntu)
  910. OS="${GRUB_DISTRIBUTOR}"
  911. ;;
  912. *)
  913. OS="${GRUB_DISTRIBUTOR} GNU/Linux"
  914. ;;
  915. esac
  916. CLASS="--class $(echo ${GRUB_DISTRIBUTOR} | tr 'A-Z' 'a-z' | cut -d' ' -f1 | LC_ALL=C sed 's,[^[:alnum:]_],_,g') ${CLASS}"
  917. fi
  918.  
  919. if [ -x /lib/recovery-mode/recovery-menu ]; then
  920. GRUB_CMDLINE_LINUX_RECOVERY=recovery
  921. else
  922. GRUB_CMDLINE_LINUX_RECOVERY=single
  923. fi
  924. if [ "${ubuntu_recovery}" = 1 ]; then
  925. GRUB_CMDLINE_LINUX_RECOVERY="${GRUB_CMDLINE_LINUX_RECOVERY} nomodeset"
  926. fi
  927.  
  928. case "$GENKERNEL_ARCH" in
  929. x86*) GRUB_CMDLINE_LINUX_RECOVERY="$GRUB_CMDLINE_LINUX_RECOVERY dis_ucode_ldr";;
  930. esac
  931.  
  932.  
  933. if [ "${vt_handoff}" = 1 ]; then
  934. for word in ${GRUB_CMDLINE_LINUX_DEFAULT}; do
  935. if [ "${word}" = splash ]; then
  936. GRUB_CMDLINE_LINUX_DEFAULT="${GRUB_CMDLINE_LINUX_DEFAULT} \${vt_handoff}"
  937. fi
  938. done
  939. fi
  940.  
  941. print_menu_prologue
  942.  
  943. cat<<'EOF'
  944. function zsyshistorymenu {
  945. # $1: root dataset (eg rpool/ROOT/ubuntu_2zhm07@autozsys_k56fr6)
  946. # $2: boot device id (eg 411f29ce1557bfed)
  947. # $3: initrd (eg /BOOT/ubuntu_2zhm07@autozsys_k56fr6/initrd.img-5.4.0-21-generic)
  948. # $4: kernel (eg /BOOT/ubuntu_2zhm07@autozsys_k56fr6/vmlinuz-5.4.0-21-generic)
  949. # $5: kernel_version (eg 5.4.0-21-generic)
  950.  
  951. set root_dataset="${1}"
  952. set boot_device="${2}"
  953. set initrd="${3}"
  954. set kernel="${4}"
  955. set kversion="${5}"
  956.  
  957. EOF
  958. boot_devices=$(echo "${menu_metadata}" | cut -d"$(printf '\t')" -f6 | sort -u)
  959.  
  960. title=$(gettext_printf "Revert system only")
  961. zfs_linux_entry 1 "${title}" "simple" '${root_dataset}' '${boot_device}' '${initrd}' '${kernel}' '${kversion}' '' "${boot_devices}"
  962.  
  963. title="$(gettext_printf "Revert system and user data")"
  964. zfs_linux_entry 1 "${title}" "simple" '${root_dataset}' '${boot_device}' '${initrd}' '${kernel}' '${kversion}' 'zsys-revert=userdata' "${boot_devices}"
  965.  
  966. GRUB_DISABLE_RECOVERY="${GRUB_DISABLE_RECOVERY:-}"
  967. if [ "${GRUB_DISABLE_RECOVERY}" != "true" ]; then
  968. title="$(gettext_printf "Revert system only (%s)" "$(gettext "${GRUB_RECOVERY_TITLE}")")"
  969. zfs_linux_entry 1 "${title}" "recovery" '${root_dataset}' '${boot_device}' '${initrd}' '${kernel}' '${kversion}' '' "${boot_devices}"
  970.  
  971. title="$(gettext_printf "Revert system and user data (%s)" "$(gettext "${GRUB_RECOVERY_TITLE}")")"
  972. zfs_linux_entry 1 "${title}" "recovery" '${root_dataset}' '${boot_device}' '${initrd}' '${kernel}' '${kversion}' 'zsys-revert=userdata' "${boot_devices}"
  973. fi
  974. echo "}"
  975. echo
  976.  
  977. # IFS is set to TAB (ASCII 0x09)
  978. echo "${menu_metadata}" |
  979. {
  980. at_least_one_entry=0
  981. have_zsys="$(which zsysd || true)"
  982. while IFS="$(printf '\t')" read -r machineid iszsys section name dataset device initrd kernel opt; do
  983.  
  984. # Disable history for non zsys system or if systems is a zsys one and zsys isn't installed.
  985. # In pure zfs systems, we identified multiple issues due to the mount generator
  986. # in upstream zfs which makes it incompatible. Don't show history for now.
  987. if [ "${section}" = "history" ]; then
  988. if [ "${iszsys}" != "yes" ] || [ "${iszsys}" = "yes" -a -z "${have_zsys}" ]; then
  989. continue
  990. fi
  991. fi
  992.  
  993. if [ "${last_section}" != "${section}" -a -n "${last_section}" ]; then
  994. # Close previous section wrapper
  995. if [ "${last_section}" != "main" ]; then
  996. echo "}" # Add grub_tabs
  997. at_least_one_entry=0
  998. fi
  999. fi
  1000.  
  1001. case "${section}" in
  1002. main)
  1003. title="${name}"
  1004. main_dataset_name="${name}"
  1005. main_dataset="${dataset}"
  1006.  
  1007. kernel_version=$(basename "${kernel}" | sed -e "s,^[^0-9]*-,,g")
  1008. zfs_linux_entry 0 "${title}" "simple" "${dataset}" "${device}" "${initrd}" "${kernel}" "${kernel_version}"
  1009. at_least_one_entry=1
  1010. ;;
  1011. advanced)
  1012. # normal and recovery entries for a given kernel
  1013. if [ "${last_section}" != "${section}" ]; then
  1014. echo "submenu '$(gettext_printf "Advanced options for %s" "${main_dataset_name}" | grub_quote)' \${menuentry_id_option} 'gnulinux-advanced-${main_dataset}' {"
  1015. fi
  1016.  
  1017. last_booted_kernel_marker=""
  1018. if [ "${opt}" = "true" ]; then
  1019. last_booted_kernel_marker="* "
  1020. fi
  1021.  
  1022. kernel_version=$(basename "${kernel}" | sed -e "s,^[^0-9]*-,,g")
  1023. title="$(gettext_printf "%s%s, with Linux %s" "${last_booted_kernel_marker}" "${name}" "${kernel_version}")"
  1024. zfs_linux_entry 1 "${title}" "advanced" "${dataset}" "${device}" "${initrd}" "${kernel}" "${kernel_version}"
  1025.  
  1026. GRUB_DISABLE_RECOVERY=${GRUB_DISABLE_RECOVERY:-}
  1027. if [ "${GRUB_DISABLE_RECOVERY}" != "true" ]; then
  1028. title="$(gettext_printf "%s%s, with Linux %s (%s)" "${last_booted_kernel_marker}" "${name}" "${kernel_version}" "$(gettext "${GRUB_RECOVERY_TITLE}")")"
  1029. zfs_linux_entry 1 "${title}" "recovery" "${dataset}" "${device}" "${initrd}" "${kernel}" "${kernel_version}"
  1030. fi
  1031. at_least_one_entry=1
  1032. ;;
  1033. history)
  1034. # Revert to a snapshot
  1035. # revert system, revert system and user data and associated recovery entries
  1036. if [ "${last_section}" != "${section}" ]; then
  1037. echo "submenu '$(gettext_printf "History for %s" "${main_dataset_name}" | grub_quote)' \${menuentry_id_option} 'gnulinux-history-${main_dataset}' {"
  1038. fi
  1039.  
  1040. if [ "${iszsys}" = "yes" ]; then
  1041. title="$(gettext_printf "Revert to %s" "${name}" | grub_quote)"
  1042. else
  1043. title="$(gettext_printf "Boot on %s" "${name}" | grub_quote)"
  1044. fi
  1045. echo " submenu '${title}' \${menuentry_id_option} 'gnulinux-history-${dataset}' {"
  1046.  
  1047. kernel_version=$(basename "${kernel}" | sed -e "s,^[^0-9]*-,,g")
  1048.  
  1049. # Zsys only: let revert system without destroying snapshots
  1050. if [ "${iszsys}" = "yes" ]; then
  1051. echo "${grub_tab}${grub_tab}zsyshistorymenu" \"${dataset}\" \"${device}\" \"${initrd}\" \"${kernel}\" \"${kernel_version}\"
  1052. # Non-zsys: boot temporarly on snapshots or rollback (destroying intermediate snapshots)
  1053. else
  1054. title="$(gettext_printf "One time boot")"
  1055. zfs_linux_entry 2 "${title}" "simple" "${dataset}" "${device}" "${initrd}" "${kernel}" "${kernel_version}"
  1056.  
  1057. GRUB_DISABLE_RECOVERY="${GRUB_DISABLE_RECOVERY:-}"
  1058. if [ "${GRUB_DISABLE_RECOVERY}" != "true" ]; then
  1059. title="$(gettext_printf "One time boot (%s)" "$(gettext "${GRUB_RECOVERY_TITLE}")")"
  1060. zfs_linux_entry 2 "${title}" "recovery" "${dataset}" "${device}" "${initrd}" "${kernel}" "${kernel_version}"
  1061. fi
  1062.  
  1063. title="$(gettext_printf "Revert system (all intermediate snapshots will be destroyed)")"
  1064. zfs_linux_entry 2 "${title}" "simple" "${dataset}" "${device}" "${initrd}" "${kernel}" "${kernel_version}" "rollback=yes"
  1065. fi
  1066.  
  1067. echo " }"
  1068. at_least_one_entry=1
  1069. ;;
  1070. *)
  1071. grub_warn "unknown section: ${section}. Ignoring entry ${name} for ${dataset}"
  1072. ;;
  1073. esac
  1074. last_section="${section}"
  1075. done
  1076.  
  1077. if [ "${at_least_one_entry}" -eq 1 ]; then
  1078. echo "}"
  1079. fi
  1080. }
  1081. }
  1082.  
  1083. # don't add trailing newline of variable is empty
  1084. # $1: content to write
  1085. # $2: destination file
  1086. trailing_newline_if_not_empty() {
  1087. content="$1"
  1088. dest="$2"
  1089.  
  1090. if [ -z "${content}" ]; then
  1091. rm -f "${dest}"
  1092. touch "${dest}"
  1093. return
  1094. fi
  1095. echo "${content}" > "${dest}"
  1096. }
  1097.  
  1098.  
  1099. GRUB_LINUX_ZFS_TEST="${GRUB_LINUX_ZFS_TEST:-}"
  1100. case "${GRUB_LINUX_ZFS_TEST}" in
  1101. bootlist)
  1102. # Import all available pools on the system and return imported list
  1103. imported_pools=$(import_pools)
  1104. boot_list="$(bootlist ${MNTDIR})"
  1105. trailing_newline_if_not_empty "${boot_list}" "${GRUB_LINUX_ZFS_TEST_OUTPUT}"
  1106. break
  1107. ;;
  1108. metamenu)
  1109. boot_list="$(cat ${GRUB_LINUX_ZFS_TEST_INPUT})"
  1110. menu_metadata="$(generate_grub_menu_metadata "${boot_list}")"
  1111. trailing_newline_if_not_empty "${menu_metadata}" "${GRUB_LINUX_ZFS_TEST_OUTPUT}"
  1112. break
  1113. ;;
  1114. grubmenu)
  1115. menu_metadata="$(cat ${GRUB_LINUX_ZFS_TEST_INPUT})"
  1116. grub_menu=$(generate_grub_menu "${menu_metadata}")
  1117. trailing_newline_if_not_empty "${grub_menu}" "${GRUB_LINUX_ZFS_TEST_OUTPUT}"
  1118. break
  1119. ;;
  1120. *)
  1121. # Import all available pools on the system and return imported list
  1122. imported_pools=$(import_pools)
  1123. # Generate the complete list of boot entries
  1124. boot_list="$(bootlist ${MNTDIR})"
  1125. # Create boot menu meta data from the list of boot entries
  1126. menu_metadata="$(generate_grub_menu_metadata "${boot_list}")"
  1127. # Create boot menu meta data from the list of boot entries
  1128. grub_menu="$(generate_grub_menu "${menu_metadata}")"
  1129. if [ -n "${grub_menu}" ]; then
  1130. # We want the trailing newline as a marker will be added
  1131. echo "${grub_menu}"
  1132. fi
  1133. ;;
  1134. esac
Advertisement
Add Comment
Please, Sign In to add comment