8e15ce
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
8e15ce
From: Peter Jones <pjones@redhat.com>
8e15ce
Date: Fri, 9 Dec 2016 15:40:29 -0500
8e15ce
Subject: [PATCH] Add BLS support to grub-mkconfig
8e15ce
8e15ce
GRUB now has BootLoaderSpec support, the user can choose to use this by
8e15ce
setting GRUB_ENABLE_BLSCFG to true in /etc/default/grub. On this setup,
8e15ce
the boot menu entries are not added to the grub.cfg, instead BLS config
8e15ce
files are parsed by blscfg command and the entries created dynamically.
8e15ce
8e15ce
A 10_linux_bls grub.d snippet to generate menu entries from BLS files
8e15ce
is also added that can be used on platforms where the bootloader doesn't
8e15ce
have BLS support and only can parse a normal grub configuration file.
8e15ce
8e15ce
Portions of the 10_linux_bls were taken from the ostree-grub-generator
8e15ce
script that's included in the OSTree project.
8e15ce
8e15ce
Fixes to support multi-devices and generate a BLS section even if no
8e15ce
kernels are found in the boot directory were proposed by Yclept Nemo
8e15ce
and Tom Gundersen respectively.
8e15ce
8e15ce
Signed-off-by: Peter Jones <pjones@redhat.com>
b35c50
[javierm: remove outdated URL for BLS document]
8e15ce
Signed-off-by: Javier Martinez Canillas <javierm@redhat.com>
b35c50
[iwienand@redhat.com: skip machine ID check when updating entries]
b35c50
Signed-off-by: Ian Wienand <iwienand@redhat.com>
b35c50
[rharwood: use sort(1), commit message composits, drop man pages]
b35c50
Signed-off-by: Robbie Harwood <rharwood@redhat.com>
8e15ce
---
8e15ce
 util/grub-mkconfig.in     |   9 +-
8e15ce
 util/grub-mkconfig_lib.in |  22 ++++-
b35c50
 util/grub.d/10_linux.in   | 218 +++++++++++++++++++++++++++++++++++++++++++++-
b35c50
 3 files changed, 243 insertions(+), 6 deletions(-)
8e15ce
8e15ce
diff --git a/util/grub-mkconfig.in b/util/grub-mkconfig.in
b35c50
index 535c0f0249..f55339a3f6 100644
8e15ce
--- a/util/grub-mkconfig.in
8e15ce
+++ b/util/grub-mkconfig.in
8e15ce
@@ -50,6 +50,8 @@ grub_get_kernel_settings="${sbindir}/@grub_get_kernel_settings@"
8e15ce
 export TEXTDOMAIN=@PACKAGE@
8e15ce
 export TEXTDOMAINDIR="@localedir@"
8e15ce
 
8e15ce
+export GRUB_GRUBENV_UPDATE="yes"
8e15ce
+
8e15ce
 . "${pkgdatadir}/grub-mkconfig_lib"
8e15ce
 
8e15ce
 # Usage: usage
8e15ce
@@ -59,6 +61,7 @@ usage () {
8e15ce
     gettext "Generate a grub config file"; echo
8e15ce
     echo
8e15ce
     print_option_help "-o, --output=$(gettext FILE)" "$(gettext "output generated config to FILE [default=stdout]")"
8e15ce
+    print_option_help "--no-grubenv-update" "$(gettext "do not update variables in the grubenv file")"
8e15ce
     print_option_help "-h, --help" "$(gettext "print this message and exit")"
8e15ce
     print_option_help "-V, --version" "$(gettext "print the version information and exit")"
8e15ce
     echo
8e15ce
@@ -94,6 +97,9 @@ do
8e15ce
     --output=*)
8e15ce
 	grub_cfg=`echo "$option" | sed 's/--output=//'`
8e15ce
 	;;
8e15ce
+    --no-grubenv-update)
8e15ce
+	GRUB_GRUBENV_UPDATE="no"
8e15ce
+	;;
8e15ce
     -*)
8e15ce
 	gettext_printf "Unrecognized option \`%s'\n" "$option" 1>&2
8e15ce
 	usage
8e15ce
@@ -253,7 +259,8 @@ export GRUB_DEFAULT \
8e15ce
   GRUB_OS_PROBER_SKIP_LIST \
8e15ce
   GRUB_DISABLE_SUBMENU \
8e15ce
   GRUB_DEFAULT_DTB \
8e15ce
-  SUSE_BTRFS_SNAPSHOT_BOOTING
8e15ce
+  SUSE_BTRFS_SNAPSHOT_BOOTING \
8e15ce
+  GRUB_ENABLE_BLSCFG
8e15ce
 
8e15ce
 if test "x${grub_cfg}" != "x"; then
8e15ce
   rm -f "${grub_cfg}.new"
8e15ce
diff --git a/util/grub-mkconfig_lib.in b/util/grub-mkconfig_lib.in
b35c50
index 5e96f6cc5d..301d8a8a1e 100644
8e15ce
--- a/util/grub-mkconfig_lib.in
8e15ce
+++ b/util/grub-mkconfig_lib.in
8e15ce
@@ -30,6 +30,9 @@ fi
8e15ce
 if test "x$grub_file" = x; then
8e15ce
   grub_file="${bindir}/@grub_file@"
8e15ce
 fi
8e15ce
+if test "x$grub_editenv" = x; then
8e15ce
+  grub_editenv="${bindir}/@grub_editenv@"
8e15ce
+fi
8e15ce
 if test "x$grub_mkrelpath" = x; then
8e15ce
   grub_mkrelpath="${bindir}/@grub_mkrelpath@"
8e15ce
 fi
b35c50
@@ -122,8 +125,19 @@ EOF
8e15ce
   fi
8e15ce
 }
8e15ce
 
8e15ce
+prepare_grub_to_access_device_with_variable ()
8e15ce
+{
8e15ce
+  device_variable="$1"
8e15ce
+  shift
8e15ce
+  prepare_grub_to_access_device "$@"
8e15ce
+  unset "device_variable"
8e15ce
+}
8e15ce
+
8e15ce
 prepare_grub_to_access_device ()
8e15ce
 {
8e15ce
+  if [ -z "$device_variable" ]; then
8e15ce
+    device_variable="root"
8e15ce
+  fi
8e15ce
   old_ifs="$IFS"
8e15ce
   IFS='
8e15ce
 '
b35c50
@@ -158,18 +172,18 @@ prepare_grub_to_access_device ()
8e15ce
   # otherwise set root as per value in device.map.
8e15ce
   fs_hint="`"${grub_probe}" --device $@ --target=compatibility_hint`"
8e15ce
   if [ "x$fs_hint" != x ]; then
8e15ce
-    echo "set root='$fs_hint'"
8e15ce
+    echo "set ${device_variable}='$fs_hint'"
8e15ce
   fi
8e15ce
   if [ "x${GRUB_DISABLE_UUID}" != "xtrue" ] && fs_uuid="`"${grub_probe}" --device $@ --target=fs_uuid 2> /dev/null`" ; then
8e15ce
     hints="`"${grub_probe}" --device $@ --target=hints_string 2> /dev/null`" || hints=
8e15ce
     if [ "x$hints" != x ]; then
8e15ce
       echo "if [ x\$feature_platform_search_hint = xy ]; then"
8e15ce
-      echo "  search --no-floppy --fs-uuid --set=root ${hints} ${fs_uuid}"
8e15ce
+      echo "  search --no-floppy --fs-uuid --set=${device_variable} ${hints} ${fs_uuid}"
8e15ce
       echo "else"
8e15ce
-      echo "  search --no-floppy --fs-uuid --set=root ${fs_uuid}"
8e15ce
+      echo "  search --no-floppy --fs-uuid --set=${device_variable} ${fs_uuid}"
8e15ce
       echo "fi"
8e15ce
     else
8e15ce
-      echo "search --no-floppy --fs-uuid --set=root ${fs_uuid}"
8e15ce
+      echo "search --no-floppy --fs-uuid --set=${device_variable} ${fs_uuid}"
8e15ce
     fi
8e15ce
   fi
8e15ce
   IFS="$old_ifs"
8e15ce
diff --git a/util/grub.d/10_linux.in b/util/grub.d/10_linux.in
b35c50
index 7bb3a211a7..2851952659 100644
8e15ce
--- a/util/grub.d/10_linux.in
8e15ce
+++ b/util/grub.d/10_linux.in
b35c50
@@ -82,6 +82,218 @@ case x"$GRUB_FS" in
8e15ce
 	;;
8e15ce
 esac
8e15ce
 
8e15ce
+populate_header_warn()
8e15ce
+{
8e15ce
+if [ "x${BLS_POPULATE_MENU}" = "xtrue" ]; then
8e15ce
+  bls_parser="10_linux script"
8e15ce
+else
8e15ce
+  bls_parser="blscfg command"
8e15ce
+fi
8e15ce
+cat <
8e15ce
+
8e15ce
+# This section was generated by a script. Do not modify the generated file - all changes
8e15ce
+# will be lost the next time file is regenerated. Instead edit the BootLoaderSpec files.
8e15ce
+#
8e15ce
+# The $bls_parser parses the BootLoaderSpec files stored in /boot/loader/entries and
8e15ce
+# populates the boot menu. Please refer to the Boot Loader Specification documentation
b35c50
+# for the files format: https://systemd.io/BOOT_LOADER_SPECIFICATION/.
8e15ce
+
8e15ce
+EOF
8e15ce
+}
8e15ce
+
8e15ce
+read_config()
8e15ce
+{
8e15ce
+    config_file=${1}
8e15ce
+    title=""
8e15ce
+    initrd=""
8e15ce
+    options=""
8e15ce
+    linux=""
8e15ce
+    grub_arg=""
8e15ce
+
8e15ce
+    while read -r line
8e15ce
+    do
8e15ce
+        record=$(echo ${line} | cut -f 1 -d ' ')
8e15ce
+        value=$(echo ${line} | cut -s -f2- -d ' ')
8e15ce
+        case "${record}" in
8e15ce
+            "title")
8e15ce
+                title=${value}
8e15ce
+                ;;
8e15ce
+            "initrd")
8e15ce
+                initrd=${value}
8e15ce
+                ;;
8e15ce
+            "linux")
8e15ce
+                linux=${value}
8e15ce
+                ;;
8e15ce
+            "options")
8e15ce
+                options=${value}
8e15ce
+                ;;
8e15ce
+            "grub_arg")
8e15ce
+                grub_arg=${value}
8e15ce
+                ;;
8e15ce
+        esac
8e15ce
+    done < ${config_file}
8e15ce
+}
8e15ce
+
8e15ce
+blsdir="/boot/loader/entries"
8e15ce
+
8e15ce
+get_sorted_bls()
8e15ce
+{
b35c50
+    if ! [ -d "${blsdir}" ]; then
8e15ce
+        return
8e15ce
+    fi
8e15ce
+
8e15ce
+    local IFS=$'\n'
8e15ce
+
b35c50
+    files=($(for bls in ${blsdir}/*.conf; do
8e15ce
+        if ! [[ -e "${bls}" ]] ; then
8e15ce
+            continue
8e15ce
+        fi
8e15ce
+        bls="${bls%.conf}"
8e15ce
+        bls="${bls##*/}"
8e15ce
+        echo "${bls}"
b35c50
+    done | sort -Vr 2>/dev/null)) || :
8e15ce
+
8e15ce
+    echo "${files[@]}"
8e15ce
+}
8e15ce
+
8e15ce
+update_bls_cmdline()
8e15ce
+{
8e15ce
+    local cmdline="root=${LINUX_ROOT_DEVICE} ro ${GRUB_CMDLINE_LINUX} ${GRUB_CMDLINE_LINUX_DEFAULT}"
8e15ce
+    local -a files=($(get_sorted_bls))
8e15ce
+
8e15ce
+    for bls in "${files[@]}"; do
8e15ce
+        local options="${cmdline}"
8e15ce
+        if [ -z "${bls##*debug*}" ]; then
8e15ce
+            options="${options} ${GRUB_CMDLINE_LINUX_DEBUG}"
8e15ce
+        fi
8e15ce
+        options="$(echo "${options}" | sed -e 's/\//\\\//g')"
8e15ce
+        sed -i -e "s/^options.*/options ${options}/" "${blsdir}/${bls}.conf"
8e15ce
+    done
8e15ce
+}
8e15ce
+
8e15ce
+populate_menu()
8e15ce
+{
8e15ce
+    local -a files=($(get_sorted_bls))
8e15ce
+
8e15ce
+    gettext_printf "Generating boot entries from BLS files...\n" >&2
8e15ce
+
8e15ce
+    for bls in "${files[@]}"; do
8e15ce
+        read_config "${blsdir}/${bls}.conf"
8e15ce
+
8e15ce
+        menu="${menu}menuentry '${title}' ${grub_arg} --id=${bls} {\n"
8e15ce
+        menu="${menu}\t linux ${linux} ${options}\n"
8e15ce
+        if [ -n "${initrd}" ] ; then
8e15ce
+            menu="${menu}\t initrd ${boot_prefix}${initrd}\n"
8e15ce
+        fi
8e15ce
+        menu="${menu}}\n\n"
8e15ce
+    done
8e15ce
+    # The printf command seems to be more reliable across shells for special character (\n, \t) evaluation
8e15ce
+    printf "$menu"
8e15ce
+}
8e15ce
+
8e15ce
+# Make BLS the default if GRUB_ENABLE_BLSCFG was not set and grubby is not installed.
b35c50
+if [ -z "${GRUB_ENABLE_BLSCFG}" ] && ! command -v new-kernel-pkg >/dev/null; then
8e15ce
+	  GRUB_ENABLE_BLSCFG="true"
8e15ce
+fi
8e15ce
+
8e15ce
+if [ "x${GRUB_ENABLE_BLSCFG}" = "xtrue" ]; then
8e15ce
+  if [ x$dirname = x/ ]; then
8e15ce
+    if [ -z "${prepare_root_cache}" ]; then
8e15ce
+      prepare_grub_to_access_device ${GRUB_DEVICE}
8e15ce
+    fi
8e15ce
+  else
8e15ce
+    if [ -z "${prepare_boot_cache}" ]; then
8e15ce
+      prepare_grub_to_access_device ${GRUB_DEVICE_BOOT}
8e15ce
+    fi
8e15ce
+  fi
8e15ce
+
8e15ce
+  if [ -d /sys/firmware/efi ]; then
8e15ce
+      bootefi_device="`${grub_probe} --target=device /boot/efi/`"
8e15ce
+      prepare_grub_to_access_device_with_variable boot ${bootefi_device}
8e15ce
+  else
8e15ce
+      boot_device="`${grub_probe} --target=device /boot/`"
8e15ce
+      prepare_grub_to_access_device_with_variable boot ${boot_device}
8e15ce
+  fi
8e15ce
+
8e15ce
+  arch="$(uname -m)"
8e15ce
+  if [ "x${arch}" = "xppc64le" ] && [ -d /sys/firmware/opal ]; then
8e15ce
+
8e15ce
+      BLS_POPULATE_MENU="true"
8e15ce
+      petitboot_path="/sys/firmware/devicetree/base/ibm,firmware-versions/petitboot"
8e15ce
+
8e15ce
+      if test -e ${petitboot_path}; then
8e15ce
+          read -r -d '' petitboot_version < ${petitboot_path}
8e15ce
+          petitboot_version="$(echo ${petitboot_version//v})"
8e15ce
+
8e15ce
+	  if test -n ${petitboot_version}; then
8e15ce
+              major_version="$(echo ${petitboot_version} | cut -d . -f1)"
8e15ce
+              minor_version="$(echo ${petitboot_version} | cut -d . -f2)"
8e15ce
+
8e15ce
+              re='^[0-9]+$'
8e15ce
+              if [[ $major_version =~ $re ]] && [[ $minor_version =~ $re ]] &&
8e15ce
+                 ([[ ${major_version} -gt 1 ]] ||
8e15ce
+                  [[ ${major_version} -eq 1 &&
8e15ce
+                     ${minor_version} -ge 8  ]]); then
8e15ce
+                  BLS_POPULATE_MENU="false"
8e15ce
+              fi
8e15ce
+          fi
8e15ce
+      fi
8e15ce
+  fi
8e15ce
+
8e15ce
+  populate_header_warn
8e15ce
+
8e15ce
+  cat << EOF
8e15ce
+# The kernelopts variable should be defined in the grubenv file. But to ensure that menu
8e15ce
+# entries populated from BootLoaderSpec files that use this variable work correctly even
8e15ce
+# without a grubenv file, define a fallback kernelopts variable if this has not been set.
8e15ce
+#
8e15ce
+# The kernelopts variable in the grubenv file can be modified using the grubby tool or by
8e15ce
+# executing the grub2-mkconfig tool. For the latter, the values of the GRUB_CMDLINE_LINUX
8e15ce
+# and GRUB_CMDLINE_LINUX_DEFAULT options from /etc/default/grub file are used to set both
8e15ce
+# the kernelopts variable in the grubenv file and the fallback kernelopts variable.
8e15ce
+if [ -z "\${kernelopts}" ]; then
8e15ce
+  set kernelopts="root=${LINUX_ROOT_DEVICE} ro ${GRUB_CMDLINE_LINUX} ${GRUB_CMDLINE_LINUX_DEFAULT}"
8e15ce
+fi
8e15ce
+EOF
8e15ce
+
8e15ce
+  update_bls_cmdline
8e15ce
+
8e15ce
+  if [ "x${BLS_POPULATE_MENU}" = "xtrue" ]; then
8e15ce
+      populate_menu
8e15ce
+  else
8e15ce
+      cat << EOF
8e15ce
+
8e15ce
+insmod blscfg
8e15ce
+blscfg
8e15ce
+EOF
8e15ce
+  fi
8e15ce
+
8e15ce
+  if [ "x${GRUB_GRUBENV_UPDATE}" = "xyes" ]; then
8e15ce
+      blsdir="/boot/loader/entries"
8e15ce
+      [ -d "${blsdir}" ] && GRUB_BLS_FS="$(${grub_probe} --target=fs ${blsdir})"
8e15ce
+      if [ "x${GRUB_BLS_FS}" = "xbtrfs" ] || [ "x${GRUB_BLS_FS}" = "xzfs" ]; then
8e15ce
+          blsdir=$(make_system_path_relative_to_its_root "${blsdir}")
8e15ce
+          if [ "x${blsdir}" != "x/loader/entries" ] && [ "x${blsdir}" != "x/boot/loader/entries" ]; then
8e15ce
+              ${grub_editenv} - set blsdir="${blsdir}"
8e15ce
+          fi
8e15ce
+      fi
8e15ce
+
8e15ce
+      if [ -n "${GRUB_EARLY_INITRD_LINUX_CUSTOM}" ]; then
8e15ce
+          ${grub_editenv} - set early_initrd="${GRUB_EARLY_INITRD_LINUX_CUSTOM}"
8e15ce
+      fi
8e15ce
+
8e15ce
+      if [ -n "${GRUB_DEFAULT_DTB}" ]; then
8e15ce
+          ${grub_editenv} - set devicetree="${GRUB_DEFAULT_DTB}"
8e15ce
+      fi
8e15ce
+
8e15ce
+      if [ -n "${GRUB_SAVEDEFAULT}" ]; then
8e15ce
+           ${grub_editenv} - set save_default="${GRUB_SAVEDEFAULT}"
8e15ce
+      fi
8e15ce
+  fi
8e15ce
+
8e15ce
+  exit 0
8e15ce
+fi
8e15ce
+
8e15ce
 mktitle ()
8e15ce
 {
8e15ce
   local title_type
b35c50
@@ -121,6 +333,7 @@ linux_entry ()
8e15ce
   if [ -z "$boot_device_id" ]; then
8e15ce
       boot_device_id="$(grub_get_device_id "${GRUB_DEVICE}")"
8e15ce
   fi
8e15ce
+
8e15ce
   if [ x$type != xsimple ] ; then
8e15ce
       title=$(mktitle "$type" "$version")
8e15ce
       if [ x"$title" = x"$GRUB_ACTUAL_DEFAULT" ] || [ x"Previous Linux versions>$title" = x"$GRUB_ACTUAL_DEFAULT" ]; then
b35c50
@@ -231,6 +444,7 @@ is_top_level=true
8e15ce
 while [ "x$list" != "x" ] ; do
8e15ce
   linux=`version_find_latest $list`
8e15ce
   gettext_printf "Found linux image: %s\n" "$linux" >&2
8e15ce
+
8e15ce
   basename=`basename $linux`
8e15ce
   dirname=`dirname $linux`
8e15ce
   rel_dirname=`make_system_path_relative_to_its_root $dirname`
b35c50
@@ -269,7 +483,9 @@ while [ "x$list" != "x" ] ; do
8e15ce
     for i in ${initrd}; do
8e15ce
       initrd_display="${initrd_display} ${dirname}/${i}"
8e15ce
     done
8e15ce
-    gettext_printf "Found initrd image: %s\n" "$(echo $initrd_display)" >&2
8e15ce
+    if [ "x${GRUB_ENABLE_BLSCFG}" != "xtrue" ]; then
8e15ce
+      gettext_printf "Found initrd image: %s\n" "$(echo $initrd_display)" >&2
8e15ce
+    fi
8e15ce
   fi
8e15ce
 
8e15ce
   fdt=