Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[merged] Introducing ostree-grub-generator #228

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 10 additions & 5 deletions Makefile-boot.am
@@ -1,4 +1,4 @@
# Makefile for dracut module
# Makefile for boot module
#
# Copyright (C) 2013 Colin Walters <walters@verbum.org>
#
Expand Down Expand Up @@ -39,19 +39,24 @@ 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 \
src/boot/dracut/ostree.conf \
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)
4 changes: 3 additions & 1 deletion Makefile-tests.am
Expand Up @@ -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 \
Expand Down Expand Up @@ -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.
Expand Down
22 changes: 16 additions & 6 deletions configure.ac
Expand Up @@ -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], [
Expand Down Expand Up @@ -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"
])
Expand Down
File renamed without changes.
101 changes: 101 additions & 0 deletions 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 ' ')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a clear enough script, but it's tempting to move the logic into C, basically what we're doing in the syslinux/u-boot ones. We don't have to do that now though, and shell script is convenient for customization.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At some point though I do want to revisit #228 (comment) - I didn't quite understand you reply there. The GRUB2 builtin BLS loader wouldn't have any on-target dependencies either, but it would need some changes to be possibly more flexible/configurable?

What I meant here is that this logic could be implemented entirely in ostree. Instead of working on making a GRUB2 builtin BLS loader more configurable, there could be a C binary (configurable) in ostree project that does what normally would be the job of "GRUB2 builtin BLS loader".

Then we could reuse the same code path what is currently used by ostree-grub-generator. Where ostree instead of executing ostree-grub-generator shell script would execute the above mentioned C binary. The benefit would be that the code is better localized. Probably there are some benefits at developing this upstream (in grub2 repo) too.

Hopefully this clarifies it better.

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} <<EOF
# This file was generated by ${script}. Do not modify the generated file - all changes will
# be lost the next time file is regenerated. For more details refer to the ${script} script.
EOF
}

populate_header()
{
cat >> ${new_grub2_cfg} <<EOF
serial --unit=0 --speed=115200 --word=8 --parity=no --stop=1
default=boot
timeout=10
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the sort of thing where we should probably have a config file at least for any general purpose use. It's basically what /etc/grub.d/00_header is right?

I guess this is where things are a bit nicer with the state we have with syslinux where the ostree-syslinux code edits just the entries in there, otherwise admins can edit it by hand. But that's only really possible because the syslinux format is easy to parse, which is definitely not true of grub2 😦

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's basically what /etc/grub.d/00_header is right

Yeah, looks like it. This definitely can be improved further by Atomic project or whoever else decides that they need it first :)

I guess this is where things are a bit nicer with the state we have with syslinux where the ostree-syslinux code edits just the entries in there, otherwise admins can edit it by hand.

I want to achieve similar funcionality in the u-boot backend by this patch:
5ee4977
Where admin can edit /uEnv.txt, deploy the edited version. Where during the deploy process OSTree prepends its env (loader/uEnv.txt) on top of the /uEnv.txt.
I am planing to revisit that change soon and add an auto tests that demonstrates the use case.

EOF
}

generate_grub2_cfg()
{
populate_warning
populate_header
populate_menu
}

generate_grub2_cfg
24 changes: 22 additions & 2 deletions src/libostree/ostree-bootloader-grub2.c
Expand Up @@ -299,8 +299,25 @@ _ostree_bootloader_grub2_write_config (OstreeBootloader *bootloader,
g_autofree char *bootversion_str = g_strdup_printf ("%u", (guint)bootversion);
g_autoptr(GFile) config_path_efi_dir = NULL;
g_autofree char *grub2_mkconfig_chroot = NULL;
gboolean use_system_grub2_mkconfig = TRUE;
const gchar *grub_exec = NULL;

#ifdef USE_BUILTIN_GRUB2_MKCONFIG
use_system_grub2_mkconfig = FALSE;
#endif
/* Autotests can set this envvar to select which code path to test, useful for OS installers as well */
grub_exec = g_getenv ("OSTREE_GRUB2_EXEC");
if (grub_exec)
{
if (g_str_has_suffix (grub_exec, GRUB2_MKCONFIG_PATH))
use_system_grub2_mkconfig = TRUE;
else
use_system_grub2_mkconfig = FALSE;
}
else
grub_exec = use_system_grub2_mkconfig ? GRUB2_MKCONFIG_PATH : LIBEXECDIR "/ostree-grub-generator";

if (ostree_sysroot_get_booted_deployment (self->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;
Expand All @@ -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);
Expand Down Expand Up @@ -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);

Expand Down
15 changes: 11 additions & 4 deletions tests/admin-test.sh
Expand Up @@ -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)
Expand Down
Expand Up @@ -25,9 +25,14 @@
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')
Expand Down
17 changes: 17 additions & 0 deletions tests/libtest.sh
Expand Up @@ -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
Expand Down Expand Up @@ -301,6 +315,9 @@ EOF
"uboot")
setup_os_boot_uboot
;;
*grub2*)
setup_os_boot_grub2 "${bootmode}"
;;
esac

cd ${test_tmpdir}
Expand Down
1 change: 1 addition & 0 deletions tests/ostree-grub-generator
6 changes: 1 addition & 5 deletions tests/test-admin-deploy-grub2.sh
Expand Up @@ -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