From: Gatis Paeglis Date: Fri, 1 Apr 2016 11:51:18 +0000 (+0200) Subject: Introducing ostree-grub-generator X-Git-Tag: archive/raspbian/2022.1-3+rpi1~1^2~456^2^2~34 X-Git-Url: https://dgit.raspbian.org/?a=commitdiff_plain;h=4e815484470fb22a1b0dc52af0193d5ce1a3caf7;p=ostree.git Introducing ostree-grub-generator ostree-grub-generator can be used to customize the generated grub.cfg file. Compile time decision ostree-grub-generator vs grub2-mkconfig can be overwritten with the OSTREE_GRUB2_EXEC envvar - useful for auto tests and OS installers. Why this alternative approach: 1) The current approach is less flexible than using a custom 'ostree-grub-generator' script. Each system can adjust this script for its needs, instead of using the hardcoded values from ostree-bootloader-grub2.c. 2) Too much overhead on embedded to generate grub.cfg via /etc/grub.d/ configuration files. It is still possible to do so, even with this patch applied. No need to install grub2 package on a target device. 3) The grub2-mkconfig code path has other issues: https://bugzilla.gnome.org/show_bug.cgi?id=761180 Task: https://bugzilla.gnome.org/show_bug.cgi?id=762220 Closes: #228 Approved by: cgwalters --- diff --git a/Makefile-boot.am b/Makefile-boot.am index 01ed7470..a2cdc28f 100644 --- a/Makefile-boot.am +++ b/Makefile-boot.am @@ -1,4 +1,4 @@ -# Makefile for dracut module +# Makefile for boot module # # Copyright (C) 2013 Colin Walters # @@ -39,14 +39,17 @@ systemdsystemunit_DATA = src/boot/ostree-prepare-root.service \ src/boot/ostree-remount.service endif -dist_pkglibexec_SCRIPTS = src/boot/grub2-15_ostree - -if BUILDOPT_GRUB2 +if !BUILDOPT_BUILTIN_GRUB2_MKCONFIG +# We're using the system grub2-mkconfig generator +libexec_SCRIPTS = src/boot/grub2/grub2-15_ostree install-grub2-config-hook: mkdir -p $(DESTDIR)$(grub2configdir) - ln -sf $(pkglibexecdir)/grub2-15_ostree $(DESTDIR)$(grub2configdir)/15_ostree + ln -sf $(libexecdir)/grub2-15_ostree $(DESTDIR)$(grub2configdir)/15_ostree grub2configdir = $(sysconfdir)/grub.d INSTALL_DATA_HOOKS += install-grub2-config-hook +else +# We're using our internal generator +libexec_SCRIPTS = src/boot/grub2/ostree-grub-generator endif EXTRA_DIST += src/boot/dracut/module-setup.sh \ @@ -54,4 +57,6 @@ EXTRA_DIST += src/boot/dracut/module-setup.sh \ src/boot/mkinitcpio/ostree \ src/boot/ostree-prepare-root.service \ src/boot/ostree-remount.service \ + src/boot/grub2/grub2-15_ostree \ + src/boot/grub2/ostree-grub-generator \ $(NULL) diff --git a/Makefile-tests.am b/Makefile-tests.am index 1bc1bc90..b7ff268d 100644 --- a/Makefile-tests.am +++ b/Makefile-tests.am @@ -59,6 +59,7 @@ test_scripts = \ tests/test-admin-deploy-switch.sh \ tests/test-admin-deploy-etcmerge-cornercases.sh \ tests/test-admin-deploy-uboot.sh \ + tests/test-admin-deploy-grub2.sh \ tests/test-admin-instutil-set-kargs.sh \ tests/test-admin-upgrade-not-backwards.sh \ tests/test-admin-pull-deploy-commit.sh \ @@ -97,7 +98,8 @@ installed_test_data = tests/archive-test.sh \ tests/pre-endian-deltas-repo-little.tar.xz \ $(NULL) -test_extra_scripts = tests/syslinux-entries-crosscheck.py +test_extra_scripts = tests/bootloader-entries-crosscheck.py \ + tests/ostree-grub-generator # We can't use nobase_ as we need to strip off the tests/, can't # use plain installed_ as we do need the gpghome/ prefix. diff --git a/configure.ac b/configure.ac index 86df2a41..7dfcd500 100644 --- a/configure.ac +++ b/configure.ac @@ -251,11 +251,20 @@ AS_IF([test "x$with_dracut" = "xyes" || test "x$with_mkinitcpio" = "xyes"], [ ]) AM_CONDITIONAL(BUILDOPT_SYSTEMD, test x$with_systemd = xyes) -AC_ARG_WITH(grub2, - AS_HELP_STRING([--with-grub2], - [Install grub2 hook (default: yes)]),, - [with_grub2=yes]) -AM_CONDITIONAL(BUILDOPT_GRUB2, test x$with_grub2 = xyes) +AC_ARG_WITH(builtin-grub2-mkconfig, + AS_HELP_STRING([--with-builtin-grub-mkconfig], + [Use a builtin minimal grub2-mkconfig to generate a GRUB2 configuration file (default: no)]),, + [with_builtin_grub2_mkconfig=no]) +AM_CONDITIONAL(BUILDOPT_BUILTIN_GRUB2_MKCONFIG, test x$with_builtin_grub2_mkconfig = xyes) +AM_COND_IF(BUILDOPT_BUILTIN_GRUB2_MKCONFIG, + AC_DEFINE([USE_BUILTIN_GRUB2_MKCONFIG], 1, [Define if using internal ostree-grub-generator]), +[ + # Otherwise, look for the path to the system generator. On some + # distributions GRUB2 *-mkconfig executable has 'grub2' prefix and + # on some 'grub'. + AC_CHECK_PROG(GRUB2_MKCONFIG, grub2-mkconfig, grub2-mkconfig, grub-mkconfig) + AC_DEFINE_UNQUOTED([GRUB2_MKCONFIG_PATH], ["$GRUB2_MKCONFIG"], [The system grub2-mkconfig executible name]) +]) dnl for tests AS_IF([test "x$found_introspection" = xyes], [ @@ -292,7 +301,8 @@ echo " api docs (gtk-doc): $enable_gtk_doc gjs-based tests: $have_gjs dracut: $with_dracut - mkinitcpio: $with_mkinitcpio" + mkinitcpio: $with_mkinitcpio + builtin grub2-mkconfig (instead of system): $with_builtin_grub2_mkconfig" AS_IF([test "x$with_systemd" = "xyes"], [ echo " systemd unit dir: $with_systemdsystemunitdir" ]) diff --git a/src/boot/grub2-15_ostree b/src/boot/grub2-15_ostree deleted file mode 100644 index dfff6e89..00000000 --- a/src/boot/grub2-15_ostree +++ /dev/null @@ -1,47 +0,0 @@ -#!/bin/sh -# -# Copyright (C) 2014 Colin Walters -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation; either version 2 of the licence or (at -# your option) any later version. -# -# This library 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 -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General -# Public License along with this library; if not, write to the -# Free Software Foundation, Inc., 59 Temple Place, Suite 330, -# Boston, MA 02111-1307, USA. - -# Gracefully exit if ostree is not installed -if ! which ostree >/dev/null 2>/dev/null; then - exit 0 -fi - -# Make sure we're in the right environment -if ! test -n "${GRUB_DEVICE}"; then - echo "This script must be run as a child of grub2-mkconfig" 1>&2 - exit 1 -fi - -set -e - -# Pick up stuff from grub's helper that we want to inject into our -# generated bootloader configuration. Yes, this is pretty awful, but -# it's a lot better than reimplementing the config-generating bits of -# OSTree in shell script. - -. /usr/share/grub/grub-mkconfig_lib - -DEVICE=${GRUB_DEVICE_BOOT:-${GRUB_DEVICE}} - -GRUB2_BOOT_DEVICE_ID="$(grub_get_device_id ${DEVICE})" -export GRUB2_BOOT_DEVICE_ID -GRUB2_PREPARE_ROOT_CACHE="$(prepare_grub_to_access_device ${DEVICE})" -export GRUB2_PREPARE_ROOT_CACHE - -exec ostree admin instutil grub2-generate diff --git a/src/boot/grub2/grub2-15_ostree b/src/boot/grub2/grub2-15_ostree new file mode 100644 index 00000000..dfff6e89 --- /dev/null +++ b/src/boot/grub2/grub2-15_ostree @@ -0,0 +1,47 @@ +#!/bin/sh +# +# Copyright (C) 2014 Colin Walters +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation; either version 2 of the licence or (at +# your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General +# Public License along with this library; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place, Suite 330, +# Boston, MA 02111-1307, USA. + +# Gracefully exit if ostree is not installed +if ! which ostree >/dev/null 2>/dev/null; then + exit 0 +fi + +# Make sure we're in the right environment +if ! test -n "${GRUB_DEVICE}"; then + echo "This script must be run as a child of grub2-mkconfig" 1>&2 + exit 1 +fi + +set -e + +# Pick up stuff from grub's helper that we want to inject into our +# generated bootloader configuration. Yes, this is pretty awful, but +# it's a lot better than reimplementing the config-generating bits of +# OSTree in shell script. + +. /usr/share/grub/grub-mkconfig_lib + +DEVICE=${GRUB_DEVICE_BOOT:-${GRUB_DEVICE}} + +GRUB2_BOOT_DEVICE_ID="$(grub_get_device_id ${DEVICE})" +export GRUB2_BOOT_DEVICE_ID +GRUB2_PREPARE_ROOT_CACHE="$(prepare_grub_to_access_device ${DEVICE})" +export GRUB2_PREPARE_ROOT_CACHE + +exec ostree admin instutil grub2-generate diff --git a/src/boot/grub2/ostree-grub-generator b/src/boot/grub2/ostree-grub-generator new file mode 100644 index 00000000..5673b264 --- /dev/null +++ b/src/boot/grub2/ostree-grub-generator @@ -0,0 +1,101 @@ +#!/bin/sh + +# To use a custrom script for generating grub.cfg, set the --with-grub2-mkconfig=no +# configure switch when configuring and building OSTree. +# +# This script is called by ostree/src/libostree/ostree-bootloader-grub2.c whenever +# boot loader configuration file needs to be updated. It can be used as a template +# for a custom grub.cfg generator. What to consider when writing a custom grub.cfg +# generator: +# +# - The populate_menu() function converts boot loader entries as defined by +# https://www.freedesktop.org/wiki/Specifications/BootLoaderSpec/ into GRUB2 +# menuentry sections. This is the core logic that is required by OSTree +# based system. +# +# - Embedded systems: Be aware that this script is executed not only on a host machine by OS +# installer, but also on a target device, thus think about shell portability. A target device +# for example might be using busybox with a limited shell. +# +# Feel free to edit this script to fit your requirements. + +set -e + +script=$(basename ${0}) +# Atomically safe location where to generete grub.cfg when executing system upgrade. +new_grub2_cfg=${2} +entries_path=$(dirname $new_grub2_cfg)/entries + +read_config() +{ + config_file=${entries_path}/${1} + title="" + initrd="" + options="" + linux="" + + while read -r line + do + record=$(echo ${line} | cut -f 1 -d ' ') + value=$(echo ${line} | cut -s -f2- -d ' ') + case "${record}" in + "title") + title=${value} + ;; + "initrd") + initrd=${value} + ;; + "linux") + linux=${value} + ;; + "options") + options=${value} + ;; + esac + done < ${config_file} + + if [ -z "${title}" ]; then + title="(Untitled)" + fi +} + +populate_menu() +{ + boot_prefix="${OSTREE_BOOT_PARTITION}" + for config in $(ls ${entries_path}); do + read_config ${config} + menu="${menu}menuentry '${title}' {\n" + menu="${menu}\t linux ${boot_prefix}${linux} ${options}\n" + menu="${menu}\t initrd ${boot_prefix}${initrd}\n" + menu="${menu}}\n\n" + done + # The printf command seems to be more reliable across shells for special character (\n, \t) evaluation + printf "$menu" >> ${new_grub2_cfg} +} + +populate_warning() +{ +cat >> ${new_grub2_cfg} <> ${new_grub2_cfg} <sysroot) == NULL + if (use_system_grub2_mkconfig && ostree_sysroot_get_booted_deployment (self->sysroot) == NULL && g_file_has_parent (self->sysroot->path, NULL)) { g_autoptr(GPtrArray) deployments = NULL; @@ -318,6 +335,9 @@ _ostree_bootloader_grub2_write_config (OstreeBootloader *bootloader, * * In the case of an installer, use the first deployment root (which * will most likely be the only one. + * + * This all only applies if we're not using the builtin + * generator, which handles being run outside of the root. */ tool_deployment_root = ostree_sysroot_get_deployment_directory (self->sysroot, tool_deployment); grub2_mkconfig_chroot = g_file_get_path (tool_deployment_root); @@ -361,7 +381,7 @@ _ostree_bootloader_grub2_write_config (OstreeBootloader *bootloader, Upstream is fixed though. */ proc = g_subprocess_launcher_spawn (launcher, error, - "grub2-mkconfig", "-o", + grub_exec, "-o", gs_file_get_path_cached (new_config_path), NULL); diff --git a/tests/admin-test.sh b/tests/admin-test.sh index 43dcbb55..dcc34068 100755 --- a/tests/admin-test.sh +++ b/tests/admin-test.sh @@ -21,10 +21,17 @@ set -euo pipefail echo "1..16" function validate_bootloader() { - (cd ${test_tmpdir}; - if test -f sysroot/boot/syslinux/syslinux.cfg; then - $(dirname $0)/syslinux-entries-crosscheck.py sysroot - fi) + cd ${test_tmpdir}; + bootloader="" + if test -f sysroot/boot/syslinux/syslinux.cfg; then + bootloader="syslinux" + elif test -f sysroot/boot/grub2/grub.cfg; then + bootloader="grub2" + fi + if test -n "${bootloader}"; then + $(dirname $0)/bootloader-entries-crosscheck.py sysroot ${bootloader} + fi + cd - } orig_mtime=$(stat -c '%.Y' sysroot/ostree/deploy) diff --git a/tests/bootloader-entries-crosscheck.py b/tests/bootloader-entries-crosscheck.py new file mode 100755 index 00000000..38e8e451 --- /dev/null +++ b/tests/bootloader-entries-crosscheck.py @@ -0,0 +1,112 @@ +#!/usr/bin/python +# +# Copyright (C) 2015 Red Hat +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. + +import os +import sys + +if len(sys.argv) == 1: + sysroot = '' +else: + sysroot = sys.argv[1] + +bootloader = sys.argv[2] +loaderpath = sysroot + '/boot/loader/entries' +syslinuxpath = sysroot + '/boot/syslinux/syslinux.cfg' + +if bootloader == "grub2": + sys.stdout.write('GRUB2 configuration validation not implemented.\n') + sys.exit(0) + +def fatal(msg): + sys.stderr.write(msg) + sys.stderr.write('\n') + sys.exit(1) + +def compare_entries_descending(a, b): + return int(b['version']) - int(a['version']) + +def get_ostree_option(optionstring): + for o in optionstring.split(): + if o.startswith('ostree='): + return o[8:] + raise ValueError('ostree= not found') + +entries = [] +syslinux_entries = [] + +# Parse loader configs +for fname in os.listdir(loaderpath): + path = os.path.join(loaderpath, fname) + with open(path) as f: + entry = {} + for line in f: + line = line.strip() + if (line == '' or line.startswith('#')): + continue + s = line.find(' ') + assert s > 0 + k = line[0:s] + v = line[s+1:] + entry[k] = v + entries.append(entry) + entries.sort(compare_entries_descending) + +# Parse SYSLINUX config +with open(syslinuxpath) as f: + in_ostree_config = False + syslinux_entry = None + syslinux_default = None + for line in f: + line = line.strip() + if line.startswith('DEFAULT '): + if syslinux_entry is not None: + syslinux_default = line.split(' ', 1)[1] + elif line.startswith('LABEL '): + if syslinux_entry is not None: + syslinux_entries.append(syslinux_entry) + syslinux_entry = {} + syslinux_entry['title'] = line.split(' ', 1)[1] + elif line.startswith('KERNEL '): + syslinux_entry['linux'] = line.split(' ', 1)[1] + elif line.startswith('INITRD '): + syslinux_entry['initrd'] = line.split(' ', 1)[1] + elif line.startswith('APPEND '): + syslinux_entry['options'] = line.split(' ', 1)[1] + if syslinux_entry is not None: + syslinux_entries.append(syslinux_entry) + +if len(entries) != len(syslinux_entries): + fatal("Found {0} loader entries, but {1} SYSLINUX entries\n".format(len(entries), len(syslinux_entries))) + +def assert_matches_key(a, b, key): + aval = a[key] + bval = b[key] + if aval != bval: + fatal("Mismatch on {0}: {1} != {2}".format(key, aval, bval)) + +for i,(entry,syslinuxentry) in enumerate(zip(entries, syslinux_entries)): + assert_matches_key(entry, syslinuxentry, 'linux') + assert_matches_key(entry, syslinuxentry, 'initrd') + entry_ostree = get_ostree_option(entry['options']) + syslinux_ostree = get_ostree_option(syslinuxentry['options']) + if entry_ostree != syslinux_ostree: + fatal("Mismatch on ostree option: {0} != {1}".format(entry_ostree, syslinux_ostree)) + +sys.stdout.write('SYSLINUX configuration validated\n') +sys.exit(0) diff --git a/tests/libtest.sh b/tests/libtest.sh index 765e987d..572f023c 100755 --- a/tests/libtest.sh +++ b/tests/libtest.sh @@ -229,6 +229,20 @@ setup_os_boot_uboot() { ln -s loader/uEnv.txt sysroot/boot/uEnv.txt } +setup_os_boot_grub2() { + grub2_options=$1 + mkdir -p sysroot/boot/grub2/ + ln -s ../loader/grub.cfg sysroot/boot/grub2/grub.cfg + export OSTREE_BOOT_PARTITION="/boot" + case "$grub2_options" in + *ostree-grub-generator*) + cp ${test_srcdir}/ostree-grub-generator ${test_tmpdir} + chmod +x ${test_tmpdir}/ostree-grub-generator + export OSTREE_GRUB2_EXEC=${test_tmpdir}/ostree-grub-generator + ;; + esac +} + setup_os_repository () { mode=$1 bootmode=$2 @@ -301,6 +315,9 @@ EOF "uboot") setup_os_boot_uboot ;; + *grub2*) + setup_os_boot_grub2 "${bootmode}" + ;; esac cd ${test_tmpdir} diff --git a/tests/ostree-grub-generator b/tests/ostree-grub-generator new file mode 120000 index 00000000..587281cd --- /dev/null +++ b/tests/ostree-grub-generator @@ -0,0 +1 @@ +../src/boot/grub2/ostree-grub-generator \ No newline at end of file diff --git a/tests/syslinux-entries-crosscheck.py b/tests/syslinux-entries-crosscheck.py deleted file mode 100755 index 8b58ed43..00000000 --- a/tests/syslinux-entries-crosscheck.py +++ /dev/null @@ -1,107 +0,0 @@ -#!/usr/bin/python -# -# Copyright (C) 2015 Red Hat -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2 of the License, or (at your option) any later version. -# -# This library 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 -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the -# Free Software Foundation, Inc., 59 Temple Place - Suite 330, -# Boston, MA 02111-1307, USA. - -import os -import sys - -if len(sys.argv) == 1: - sysroot = '' -else: - sysroot = sys.argv[1] - -loaderpath = sysroot + '/boot/loader/entries' -syslinuxpath = sysroot + '/boot/syslinux/syslinux.cfg' - -def fatal(msg): - sys.stderr.write(msg) - sys.stderr.write('\n') - sys.exit(1) - -def compare_entries_descending(a, b): - return int(b['version']) - int(a['version']) - -def get_ostree_option(optionstring): - for o in optionstring.split(): - if o.startswith('ostree='): - return o[8:] - raise ValueError('ostree= not found') - -entries = [] -syslinux_entries = [] - -# Parse loader configs -for fname in os.listdir(loaderpath): - path = os.path.join(loaderpath, fname) - with open(path) as f: - entry = {} - for line in f: - line = line.strip() - if (line == '' or line.startswith('#')): - continue - s = line.find(' ') - assert s > 0 - k = line[0:s] - v = line[s+1:] - entry[k] = v - entries.append(entry) - entries.sort(compare_entries_descending) - -# Parse SYSLINUX config -with open(syslinuxpath) as f: - in_ostree_config = False - syslinux_entry = None - syslinux_default = None - for line in f: - line = line.strip() - if line.startswith('DEFAULT '): - if syslinux_entry is not None: - syslinux_default = line.split(' ', 1)[1] - elif line.startswith('LABEL '): - if syslinux_entry is not None: - syslinux_entries.append(syslinux_entry) - syslinux_entry = {} - syslinux_entry['title'] = line.split(' ', 1)[1] - elif line.startswith('KERNEL '): - syslinux_entry['linux'] = line.split(' ', 1)[1] - elif line.startswith('INITRD '): - syslinux_entry['initrd'] = line.split(' ', 1)[1] - elif line.startswith('APPEND '): - syslinux_entry['options'] = line.split(' ', 1)[1] - if syslinux_entry is not None: - syslinux_entries.append(syslinux_entry) - -if len(entries) != len(syslinux_entries): - fatal("Found {0} loader entries, but {1} SYSLINUX entries\n".format(len(entries), len(syslinux_entries))) - -def assert_matches_key(a, b, key): - aval = a[key] - bval = b[key] - if aval != bval: - fatal("Mismatch on {0}: {1} != {2}".format(key, aval, bval)) - -for i,(entry,syslinuxentry) in enumerate(zip(entries, syslinux_entries)): - assert_matches_key(entry, syslinuxentry, 'linux') - assert_matches_key(entry, syslinuxentry, 'initrd') - entry_ostree = get_ostree_option(entry['options']) - syslinux_ostree = get_ostree_option(syslinuxentry['options']) - if entry_ostree != syslinux_ostree: - fatal("Mismatch on ostree option: {0} != {1}".format(entry_ostree, syslinux_ostree)) - -sys.stdout.write('SYSLINUX configuration validated\n') -sys.exit(0) diff --git a/tests/test-admin-deploy-grub2.sh b/tests/test-admin-deploy-grub2.sh index 8da294d7..6109a950 100755 --- a/tests/test-admin-deploy-grub2.sh +++ b/tests/test-admin-deploy-grub2.sh @@ -21,11 +21,7 @@ set -euo pipefail . $(dirname $0)/libtest.sh -echo "1..1" - # Exports OSTREE_SYSROOT so --sysroot not needed. -setup_os_repository "archive-z2" "grub2" - -echo "ok setup" +setup_os_repository "archive-z2" "grub2 ostree-grub-generator" . $(dirname $0)/admin-test.sh