1
0
mirror of https://github.com/ostreedev/ostree.git synced 2026-02-05 09:44:55 +01:00

Merge pull request #3310 from igoropaniuk/boot_count

sysroot: Support boot counting for boot entries
This commit is contained in:
Colin Walters
2025-07-10 07:24:06 -04:00
committed by GitHub
13 changed files with 252 additions and 11 deletions

View File

@@ -110,6 +110,7 @@ libostree_1_la_SOURCES = \
src/libostree/ostree-soft-reboot.c \
src/libostree/ostree-impl-system-generator.c \
src/libostree/ostree-bootconfig-parser.c \
src/libostree/ostree-bootconfig-parser-private.h \
src/libostree/ostree-deployment.c \
src/libostree/ostree-bootloader.h \
src/libostree/ostree-bootloader.c \

View File

@@ -122,6 +122,7 @@ _installed_or_uninstalled_test_scripts = \
tests/test-osupdate-dtb.sh \
tests/test-admin-instutil-set-kargs.sh \
tests/test-admin-upgrade-not-backwards.sh \
tests/test-admin-boot-counting-tries.sh \
tests/test-admin-pull-deploy-commit.sh \
tests/test-admin-pull-deploy-split.sh \
tests/test-admin-locking.sh \
@@ -280,7 +281,7 @@ endif
_installed_or_uninstalled_test_programs = tests/test-varint tests/test-ot-unix-utils tests/test-bsdiff tests/test-otcore tests/test-mutable-tree \
tests/test-keyfile-utils tests/test-ot-opt-utils tests/test-ot-tool-util \
tests/test-checksum tests/test-lzma tests/test-rollsum \
tests/test-checksum tests/test-lzma tests/test-rollsum tests/test-bootconfig-parser-internals \
tests/test-basic-c tests/test-sysroot-c tests/test-pull-c tests/test-repo tests/test-include-ostree-h tests/test-kargs \
tests/test-rfc2616-dates tests/test-pem
@@ -348,6 +349,10 @@ tests_test_kargs_SOURCES = src/libostree/ostree-kernel-args.c tests/test-kargs.c
tests_test_kargs_CFLAGS = $(TESTS_CFLAGS)
tests_test_kargs_LDADD = $(TESTS_LDADD)
tests_test_bootconfig_parser_internals_SOURCES = tests/test-bootconfig-parser-internals.c
tests_test_bootconfig_parser_internals_CFLAGS = $(TESTS_CFLAGS)
tests_test_bootconfig_parser_internals_LDADD = $(TESTS_LDADD)
tests_test_repo_finder_config_SOURCES = tests/test-repo-finder-config.c
tests_test_repo_finder_config_CFLAGS = $(TESTS_CFLAGS)
tests_test_repo_finder_config_LDADD = $(TESTS_LDADD)

View File

@@ -39,6 +39,8 @@ ostree_bootconfig_parser_set
ostree_bootconfig_parser_get
ostree_bootconfig_parser_set_overlay_initrds
ostree_bootconfig_parser_get_overlay_initrds
ostree_bootconfig_parser_get_tries_left
ostree_bootconfig_parser_get_tries_done
<SUBSECTION Standard>
OSTREE_BOOTCONFIG_PARSER
OSTREE_IS_BOOTCONFIG_PARSER

View File

@@ -408,6 +408,18 @@ License along with this library. If not, see <https://www.gnu.org/licenses/>.
</listitem>
</varlistentry>
<varlistentry>
<term><varname>boot-counting-tries</varname></term>
<listitem><para>Integer value controlling the number of maximum boot attempts. The boot
counting data is stored in the name of the boot loader entry. A boot loader entry file name
may contain a plus (+) followed by a number. This may optionally be followed by
a minus (-) followed by a second number. The dot (.) and file name suffix (conf or efi) must
immediately follow. More details in the
<ulink url="https://uapi-group.org/specifications/specs/boot_loader_specification/#boot-counting">
The Boot Loader Specification</ulink>
</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>bls-append-except-default</varname></term>
<listitem><para>A semicolon separated string list of key-value pairs. For example:

View File

@@ -37,4 +37,6 @@ global:
ostree_sysroot_deployment_can_soft_reboot;
ostree_sysroot_deployment_set_soft_reboot;
ostree_sysroot_clear_soft_reboot;
ostree_bootconfig_parser_get_tries_left;
ostree_bootconfig_parser_get_tries_done;
} LIBOSTREE_2025.2;

View File

@@ -0,0 +1,11 @@
/* SPDX-License-Identifier: LGPL-2.0+ */
#pragma once
#include "ostree-bootconfig-parser.h"
G_BEGIN_DECLS
const char *_ostree_bootconfig_parser_filename (OstreeBootconfigParser *self);
G_END_DECLS

View File

@@ -17,16 +17,19 @@
#include "config.h"
#include "ostree-bootconfig-parser.h"
#include "ostree-bootconfig-parser-private.h"
#include "otutil.h"
struct _OstreeBootconfigParser
{
GObject parent_instance;
gboolean parsed;
char *filename;
const char *separators;
guint64 tries_left;
guint64 tries_done;
GHashTable *options;
/* Additional initrds; the primary initrd is in options. */
@@ -51,11 +54,88 @@ ostree_bootconfig_parser_clone (OstreeBootconfigParser *self)
GLNX_HASH_TABLE_FOREACH_KV (self->options, const char *, k, const char *, v)
g_hash_table_replace (parser->options, g_strdup (k), g_strdup (v));
parser->filename = g_strdup (self->filename);
parser->overlay_initrds = g_strdupv (self->overlay_initrds);
return parser;
}
/*
* Parses a suffix of two counters in the form "+LEFT-DONE" from the end of the
* filename (excluding file extension).
*/
static void
parse_bootloader_tries (const char *filename, guint64 *out_left, guint64 *out_done)
{
*out_left = 0;
*out_done = 0;
const char *counter = strrchr (filename, '+');
if (!counter)
return;
counter += 1;
guint64 tries_left = 0;
guint64 tries_done = 0;
// Negative numbers are invalid
if (*counter == '-')
return;
{
char *endp = NULL;
tries_left = g_ascii_strtoull (counter, &endp, 10);
if (endp == counter || (tries_left == G_MAXUINT64 && errno == ERANGE))
return;
counter = endp;
}
/* Parse done counter only if present */
if (*counter == '-')
{
counter += 1;
char *endp = NULL;
tries_done = g_ascii_strtoull (counter, &endp, 10);
if (endp == counter || (tries_done == G_MAXUINT64 && errno == ERANGE))
return;
}
*out_left = tries_left;
*out_done = tries_done;
}
/**
* ostree_bootconfig_parser_get_tries_left:
* @self: Parser
*
* Returns: Amount of boot tries left
*
* Since: 2025.2
*/
guint64
ostree_bootconfig_parser_get_tries_left (OstreeBootconfigParser *self)
{
return self->tries_left;
}
/**
* ostree_bootconfig_parser_get_tries_done:
* @self: Parser
*
* Returns: Amount of boot tries
*/
guint64
ostree_bootconfig_parser_get_tries_done (OstreeBootconfigParser *self)
{
return self->tries_done;
}
const char *
_ostree_bootconfig_parser_filename (OstreeBootconfigParser *self)
{
return self->filename;
}
/**
* ostree_bootconfig_parser_parse_at:
* @self: Parser
@@ -70,7 +150,7 @@ gboolean
ostree_bootconfig_parser_parse_at (OstreeBootconfigParser *self, int dfd, const char *path,
GCancellable *cancellable, GError **error)
{
g_assert (!self->parsed);
g_assert (!self->filename);
g_autofree char *contents = glnx_file_get_contents_utf8_at (dfd, path, NULL, cancellable, error);
if (!contents)
@@ -116,8 +196,10 @@ ostree_bootconfig_parser_parse_at (OstreeBootconfigParser *self, int dfd, const
self->overlay_initrds = (char **)g_ptr_array_free (g_steal_pointer (&overlay_initrds), FALSE);
}
self->parsed = TRUE;
const char *basename = glnx_basename (path);
parse_bootloader_tries (basename, &self->tries_left, &self->tries_done);
self->filename = g_strdup (basename);
return TRUE;
}
@@ -262,6 +344,7 @@ ostree_bootconfig_parser_finalize (GObject *object)
{
OstreeBootconfigParser *self = OSTREE_BOOTCONFIG_PARSER (object);
g_free (self->filename);
g_strfreev (self->overlay_initrds);
g_hash_table_unref (self->options);

View File

@@ -67,4 +67,10 @@ void ostree_bootconfig_parser_set_overlay_initrds (OstreeBootconfigParser *self,
_OSTREE_PUBLIC
char **ostree_bootconfig_parser_get_overlay_initrds (OstreeBootconfigParser *self);
_OSTREE_PUBLIC
guint64 ostree_bootconfig_parser_get_tries_left (OstreeBootconfigParser *self);
_OSTREE_PUBLIC
guint64 ostree_bootconfig_parser_get_tries_done (OstreeBootconfigParser *self);
G_END_DECLS

View File

@@ -247,6 +247,7 @@ struct OstreeRepo
GHashTable
*bls_append_values; /* Parsed key-values from bls-append-except-default key in config. */
gboolean enable_bootprefix; /* If true, prepend bootloader entries with /boot */
guint boot_counting;
OstreeRepo *parent_repo;
};

View File

@@ -3318,6 +3318,16 @@ reload_remote_config (OstreeRepo *self, GCancellable *cancellable, GError **erro
static gboolean
reload_sysroot_config (OstreeRepo *self, GCancellable *cancellable, GError **error)
{
g_autofree char *boot_counting_str = NULL;
if (!ot_keyfile_get_value_with_default_group_optional (
self->config, "sysroot", "boot-counting-tries", "0", &boot_counting_str, error))
return FALSE;
guint64 v;
if (!g_ascii_string_to_unsigned (boot_counting_str, 10, 0, 5, &v, error))
return glnx_prefix_error (error, "Parsing sysroot.boot-counting-tries");
self->boot_counting = (guint)v;
g_autofree char *bootloader = NULL;
if (!ot_keyfile_get_value_with_default_group_optional (self->config, "sysroot", "bootloader",

View File

@@ -23,6 +23,7 @@
#include <gio/gunixinputstream.h>
#include <gio/gunixoutputstream.h>
#include <glib-unix.h>
#include <inttypes.h>
#include <linux/kexec.h>
#include <stdbool.h>
#include <stdint.h>
@@ -37,6 +38,7 @@
#endif
#include "libglnx.h"
#include "ostree-bootconfig-parser-private.h"
#include "ostree-core-private.h"
#include "ostree-deployment-private.h"
#include "ostree-kernel-args-private.h"
@@ -1806,6 +1808,7 @@ static char *
bootloader_entry_filename (OstreeSysroot *sysroot, guint n_deployments,
OstreeDeployment *deployment)
{
g_autofree char *bootconf_name = NULL;
guint index = n_deployments - ostree_deployment_get_index (deployment);
// Allow opt-out to dropping the stateroot in case of compatibility issues.
// As of 2024.5, we have a new naming scheme because grub2 parses the *filename* and ignores
@@ -1814,12 +1817,28 @@ bootloader_entry_filename (OstreeSysroot *sysroot, guint n_deployments,
if (use_old_naming)
{
const char *stateroot = ostree_deployment_get_osname (deployment);
return g_strdup_printf ("ostree-%d-%s.conf", index, stateroot);
bootconf_name = g_strdup_printf ("ostree-%d-%s", index, stateroot);
}
else
{
return g_strdup_printf ("ostree-%d.conf", index);
bootconf_name = g_strdup_printf ("ostree-%d", index);
}
if (!sysroot->repo->boot_counting)
return g_strdup_printf ("%s.conf", bootconf_name);
guint max_tries = sysroot->repo->boot_counting;
OstreeBootconfigParser *bootconfig = ostree_deployment_get_bootconfig (deployment);
if (!_ostree_bootconfig_parser_filename (bootconfig))
return g_strdup_printf ("%s+%u.conf", bootconf_name, max_tries);
else if (!ostree_bootconfig_parser_get_tries_left (bootconfig)
&& !ostree_bootconfig_parser_get_tries_done (bootconfig))
return g_strdup_printf ("%s.conf", bootconf_name);
else
return g_strdup_printf ("%s+%" PRIu64 "-%" PRIu64 ".conf", bootconf_name,
ostree_bootconfig_parser_get_tries_left (bootconfig),
ostree_bootconfig_parser_get_tries_done (bootconfig));
}
/* Given @deployment, prepare it to be booted; basically copying its
@@ -1856,7 +1875,6 @@ install_deployment_kernel (OstreeSysroot *sysroot, int new_bootversion,
const char *bootcsum = ostree_deployment_get_bootcsum (deployment);
g_autofree char *bootcsumdir = g_strdup_printf ("ostree/%s-%s", osname, bootcsum);
g_autofree char *bootconfdir = g_strdup_printf ("loader.%d/entries", new_bootversion);
g_autofree char *bootconf_name = bootloader_entry_filename (sysroot, n_deployments, deployment);
if (!glnx_shutil_mkdir_p_at (sysroot->boot_fd, bootcsumdir, 0775, cancellable, error))
return FALSE;
@@ -2163,8 +2181,11 @@ install_deployment_kernel (OstreeSysroot *sysroot, int new_bootversion,
if (!glnx_opendirat (sysroot->boot_fd, bootconfdir, TRUE, &bootconf_dfd, error))
return FALSE;
g_autofree char *bootconf_filename
= bootloader_entry_filename (sysroot, n_deployments, deployment);
if (!ostree_bootconfig_parser_write_at (ostree_deployment_get_bootconfig (deployment),
bootconf_dfd, bootconf_name, cancellable, error))
bootconf_dfd, bootconf_filename, cancellable, error))
return FALSE;
return TRUE;
@@ -4303,7 +4324,7 @@ ostree_sysroot_deployment_set_kargs_in_place (OstreeSysroot *self, OstreeDeploym
OstreeBootconfigParser *new_bootconfig = ostree_deployment_get_bootconfig (deployment);
ostree_bootconfig_parser_set (new_bootconfig, "options", kargs_str);
g_autofree char *bootconf_name
g_autofree char *bootconf_filename
= bootloader_entry_filename (self, self->deployments->len, deployment);
g_autofree char *bootconfdir = g_strdup_printf ("loader.%d/entries", self->bootversion);
@@ -4311,7 +4332,7 @@ ostree_sysroot_deployment_set_kargs_in_place (OstreeSysroot *self, OstreeDeploym
if (!glnx_opendirat (self->boot_fd, bootconfdir, TRUE, &bootconf_dfd, error))
return FALSE;
if (!ostree_bootconfig_parser_write_at (new_bootconfig, bootconf_dfd, bootconf_name,
if (!ostree_bootconfig_parser_write_at (new_bootconfig, bootconf_dfd, bootconf_filename,
cancellable, error))
return FALSE;
}

View File

@@ -0,0 +1,27 @@
#!/bin/bash
#
# SPDX-License-Identifier: LGPL-2.0+
set -euo pipefail
. $(dirname $0)/libtest.sh
setup_os_repository "archive" "syslinux"
${CMD_PREFIX} ostree --repo=sysroot/ostree/repo config set sysroot.boot-counting-tries 3
v=$(${CMD_PREFIX} ostree --repo=sysroot/ostree/repo config get sysroot.boot-counting-tries)
assert_streq "$v" 3
tap_ok "init boot counting tries"
${CMD_PREFIX} ostree --repo=sysroot/ostree/repo pull-local --remote=testos testos-repo testos/buildmain/x86_64-runtime
rev=$(${CMD_PREFIX} ostree --repo=sysroot/ostree/repo rev-parse testos/buildmain/x86_64-runtime)
export rev
${CMD_PREFIX} ostree admin deploy --karg=quiet --stateroot=testos testos:testos/buildmain/x86_64-runtime
entry=$(ls sysroot/boot/loader/entries/)
assert_streq "${entry}" ostree-1+3.conf
tap_ok "deploy with boot counting"
tap_end

View File

@@ -0,0 +1,60 @@
/*
* SPDX-License-Identifier: LGPL-2.0+
*/
#include "config.h"
#define _OSTREE_PUBLIC
#include "../src/libostree/ostree-bootconfig-parser.c"
static void
test_parse_tries_valid (void)
{
guint64 left, done;
parse_bootloader_tries ("foo", &left, &done);
g_assert_cmpuint (left, ==, 0);
g_assert_cmpuint (done, ==, 0);
parse_bootloader_tries ("foo+1", &left, &done);
g_assert_cmpuint (left, ==, 1);
g_assert_cmpuint (done, ==, 0);
parse_bootloader_tries ("foo+1-2", &left, &done);
g_assert_cmpuint (left, ==, 1);
g_assert_cmpuint (done, ==, 2);
parse_bootloader_tries ("foo+1-2.conf", &left, &done);
g_assert_cmpuint (left, ==, 1);
g_assert_cmpuint (done, ==, 2);
}
static void
test_parse_tries_invalid (void)
{
guint64 left, done;
parse_bootloader_tries ("foo+1-", &left, &done);
g_assert_cmpuint (left, ==, 0);
g_assert_cmpuint (done, ==, 0);
parse_bootloader_tries ("foo+-1", &left, &done);
g_assert_cmpuint (left, ==, 0);
g_assert_cmpuint (done, ==, 0);
parse_bootloader_tries ("foo+1-a", &left, &done);
g_assert_cmpuint (left, ==, 0);
g_assert_cmpuint (done, ==, 0);
parse_bootloader_tries ("foo+a-1", &left, &done);
g_assert_cmpuint (left, ==, 0);
g_assert_cmpuint (done, ==, 0);
}
int
main (int argc, char *argv[])
{
g_test_init (&argc, &argv, NULL);
g_test_add_func ("/bootconfig-parser/tries/valid", test_parse_tries_valid);
g_test_add_func ("/bootconfig-parser/tries/invalid", test_parse_tries_invalid);
return g_test_run ();
}