�PNG  IHDR22?�� pHYs  �� OiCCPPhotoshop ICC profilexڝSgTS�=���BK���KoR RB���&*! J�!��Q�EEȠ�����Q, � ��!���������{�kּ������>���� �H3Q5� �B�������.@� $p�d!s�#�~<<+"��x� �M��0���B�\���t�8K�@z�B�@F���&S�`�cb�P-`'������{[�!�� e�Dh;��V�EX0fK�9�-0IWfH���� � 0Q��){`�##x��F�W<�+��*x��<�$9E�[-qWW.(�I+6aa�@.�y�2�4���������x����6��_-��"bb���ϫp@�t~��,/��;�m��%�h^ �u��f�@����W�p�~<�5�j>{�-�]c�K'Xt����o��(�h���w��?�G�%�fI�q^D$.Tʳ?�D��*�A��,���� �`6�B$��BB d�r`)��B(�Ͱ*`/�@4�Qh��p.�U�=p�a��(�� A�a!ڈb�X#����!�H�$ ɈQ"K�5H1R�T UH�=r9�\F��;�2����G1���Q=� �C��7�F� �dt1�����r�=�6��Ыhڏ>C�0��3�l0.��B�8, �c˱"� ���V����cϱw�E� 6wB aAHXLXN�H� $4� 7 �Q�'"��K�&���b21�XH,#��/{�C�7$�C2'��I��T��F�nR#�,��4H#���dk�9�, +ȅ����3��!�[ �b@q��S�(R�jJ��4�e�2AU��Rݨ�T5�ZB���R�Q��4u�9̓IK�����hh�i��t�ݕN��W���G���w ��Ljg(�gw��L�Ӌ�T071���oUX*�*|�� �J�&�*/T����ު U�U�T��^S}�FU3S� Ԗ�U��P�SSg�;���g�oT?�~Y��Y�L�OC�Q��_�� c�x,!k ��u�5�&���|v*�����=���9C3J3W�R�f?�q��tN �(���~���)�)�4L�1e\k����X�H�Q�G�6������E�Y��A�J'\'Gg����S�Sݧ �M=:��.�k���Dw�n��^��Lo��y���}/�T�m���G X� $� �<�5qo</���QC]�@C�a�a�ᄑ��<��F�F�i�\�$�m�mƣ&&!&KM�M�RM��)�;L;L���͢�֙5�=1�2��כ߷`ZxZ,����eI��Z�Yn�Z9Y�XUZ]�F���%ֻ�����N�N���gð�ɶ�����ۮ�m�}agbg�Ů��}�}��= ���Z~s�r:V:ޚΜ�?}����/gX���3��)�i�S��Ggg�s�󈋉K��.�>.���Ƚ�Jt�q]�z���������ۯ�6�i�ܟ�4�)�Y3s���C�Q��? ��0k߬~OCO�g��#/c/�W�װ��w��a�>�>r��>�<7�2�Y_�7��ȷ�O�o�_��C#�d�z����%g��A�[��z|!��?:�e����A���AA�������!h�쐭!��Α�i�P~���a�a��~ '���W�?�p�X�1�5w��Cs�D�D�Dޛg1O9�-J5*>�.j<�7�4�?�.fY��X�XIlK9.*�6nl������� �{�/�]py�����.,:�@L�N8��A*��%�w%� y��g"/�6ш�C\*N�H*Mz�쑼5y$�3�,幄'���L Lݛ:��v m2=:�1����qB�!M��g�g�fvˬe����n��/��k���Y- �B��TZ(�*�geWf�͉�9���+��̳�ې7�����ᒶ��KW-X潬j9�������(�x��oʿ�ܔ���Ĺd�f�f���-�[����n �ڴ �V����E�/��(ۻ��C���<��e����;?T�T�T�T6��ݵa��n��{��4���[���>ɾ�UUM�f�e�I���?�������m]�Nmq����#�׹���=TR��+�G�����w- 6 U����#pDy��� �� :�v�{���vg/jB��F�S��[b[�O�>����z�G��499�?r����C�d�&����ˮ/~�����јѡ�򗓿m|������������x31^�V���w�w��O�| (�h���SЧ��������c3-� cHRMz%������u0�`:�o�_�F5IDATx��ytUս�?�;���fN$$��@B�!�k��� -*�gE���j�O�kQ�ʠ`U�B_ �AÐ0%�Ȕ�@r3ޛ;߳��$�@��v����^�s����������$��ߢ !�W��5�̱'-٘������4�>�Rƫ$�G��0U�H}�_ _� #��k�MJ/=�9Du *ڲ�k�PQ;0��j|�*�$�^컂脛y���>�z×� B��� ��(�:$�ʔ_�6��C!B����MVx��a��ζyY����j��ƋY2�9�F�{r��r�딟F��Q��hY6��� x��[l��RC��������nt@��;P��3��� F�w��7��:�P���ɋ'���mj���Bƪ�$�Q�L��y��߷g��\���z����v5�p�v$�v櫻�`6~K �Y�X����,�:��"�o�e0II"(��,������l_�C���/4�+f�E�!q��:����u����c��樼.UE�@� �]K�W���$-�h]�JG���Dz)�`��.�A/ ���mᏣc�3jK� �!�>���$�Q�!�_�? \�C2(E �M$Z I{�U���s�ǣ���=��@Բe ޻��W�����@ �w����}�9v,)��.}*|"�s��di���,���G�]W�AC�������|<����[��ߕ������K�*�G nߴ!��‹��J�!��6(L�a)��Am��������;adf��:�xm��nh�;֦I��(���o�C~ډa?�=��s����h��E������xp����)���_���t��W�����´;8���`dz=(��@@�+,�;�`�fhl��XȌ�3,uI�~z(J����� �ZX�A����H'�À�㜦�82|��a�n�z2��I���� ���EY�h�ε<�0�q�{��C��v�˅��?"@��χ�x�WS+��N�~�KQ�J�8ل�� _.W��� !TU{�����,��"�� �;�������HI S� ��))�o�1�H�D� ���� (� ;����"Ҕ��,���� �F=���O��&�Ph>�`���y��߲ &S��P�<��'��1s�1cĂ�N����2)����@�u��tv��<�+�-&�����h��[!FSG���' � z z0��<����8d&�����E0�,��<��"!P�b4��� �{S�����X����.�囤���T��hEF��X�q0"-<����h���]�Py*����F⩍��@v�˂�Y08�� .q��a��s����(!<̫1�3�L�H��7sYW�`��qtu�\7��?�@�Y�� %Ka�x�GZ�*W�P��y�����N0r9��Z�c�%���q,��k�����!!,��>���IB��� �x1�Xu�Y���v-���ֺ��N������JP��a�2`�e5��w��u;�6�i���̃��(K���ťa��(l51��JL��O'%���]���ٖ뿉!��{*�p+f ���i8��D�ȑ���ܩ�M��V��ӟz��x�7Z�1i}�^͜���6e����x��)u�d�ˏ�V�eG�\�X1�����=n�U'�~Y����L�\,�(d!x ,�:�TW8� M`r���@��r+v�Q0��>^;˴�VbF��('����� �*O��i��j�Ӆ����^�S3&� ���{������.ی�� N;��NU%d0�Ce\r"�C�b��&t7N�7��� �ȪD�[%l�۪�$��'m?�A9tI�9>��NVL ��� 6v~M�����ъ�B���0����9$&=ώm��W�0���L���&��E���f�f��'+�9��hv��7,���M��6�D�&�^� �����8 D֮�����o��k^z��O��hP�8q�5d*��YVq3�{��������#LX4�?��jP=3�~�؅c�ݰ�̱�ұ��8̲��2�G�����j��έ�9��w7���U�X�w�85C��?!I&���o&����L���I#�^�eV�,�^t5�k� �xs˛\��U�8��."���{���ǵ�l4��ݼ�;��r"���G�/~���gJV�N糦��ZZ^k+NN�D��I$�=OE�z����|*�*{��%k���G��>�`9��b;�s���8,�qs��c��.b��q�%�>r��n�R�Ƨ�%�,�A��le#�8���}; 21�0�b�&�QX�f���q�00����$>�h�N�'�a3��A���/�r ��L5�8�GԲ'�����w;��c#�q1���h$*��V^�k��QĽ�b:i������� ����H F#�_���,��]g����Ը����`RUU���� ��"�qD���>-��ϤI�~��3�̴�����NZ[[555�eeek7�m����YF#[��Đ��R��i��L�H�r ��@��J-�Pŝ��_|�-��6y��ks�V�U~��6���w.����(��0A� 4��f��� /���C�xŊ�ٳ����z=6�����rg���mKo��o�/����a�g���?'9%9��P��斓��~���L{�� �H��{AW��B��$����v�1b�&Mb�ر��ر#�0`�`xMBp! )Z�Nmo�mZ�g�}�eذaWTVV ���'99��C5��&�&+5�bT>|8��PUUETT����&���t��~TE%��'������b�tEGG;RSS�S�N�v��ѯ��t����u1@�ޚ"f��͈���r'L�Ph��m��ƌ�74o��`�x�q�P���NRSS�X,�����l&&&��MFFA!-��`��ҷ�n��Yuu�����x<I�LB%����KԒ50�!�3�bz�����ʸKL���;---�l6[��`��������Ȏ;�����egg4��!D�T��jWujyZ��������K����%����p�^���X,6��sV=�ܖ����6�3�X ���4ҿ��A2�&����_k�;d�� �VfOIEND�B`�#! /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 . 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} | 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="" list=$(find "${boot_dir}" -maxdepth 1 -type f -regex '.*/\(vmlinuz\|vmlinux\|kernel\)-.*') while [ "x$list" != "x" ] ; do linux=`version_find_latest $list` list=`echo $list | tr ' ' '\n' | fgrep -vx "$linux" | tr '\n' ' '` 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 '{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 for snapshot_dataset in $(zfs list -H -o name -t snapshot "${dataset}"); do boot_list="${boot_list}$(get_dataset_info ${snapshot_dataset} ${mntdir})\n" done 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 set -- $(get_field_from_entry "${main_entry}" 7 | tr "|" " ") for kernel in $(get_field_from_entry "${main_entry}" 8 | tr "|" " "); 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 } # 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: # : autozsys, same release # on : autozsys, different release # on : Manual snapshot, same release # , on : 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 set -- $(get_field_from_entry "${entry}" 7 | tr "|" " ") for k in $(get_field_from_entry "${entry}" 8|tr "|" " "); 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 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 "${submenu_indentation} linux ${kernel} root=ZFS=${dataset} ro ${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