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:
@@ -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 \
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
|
||||
11
src/libostree/ostree-bootconfig-parser-private.h
Normal file
11
src/libostree/ostree-bootconfig-parser-private.h
Normal 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
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
27
tests/test-admin-boot-counting-tries.sh
Executable file
27
tests/test-admin-boot-counting-tries.sh
Executable 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
|
||||
60
tests/test-bootconfig-parser-internals.c
Normal file
60
tests/test-bootconfig-parser-internals.c
Normal 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 ();
|
||||
}
|
||||
Reference in New Issue
Block a user