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