Skip to content

Commit

Permalink
sysroot: Support boot counting for boot entries
Browse files Browse the repository at this point in the history
Add support for boot counting for bootloader entries [1].
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.

The feature is enabled via sysroot configuration:
[sysroot]
boot-counting-tries=3

Testing:
$ ostree admin deploy 91fc19319be9e79d07159303dff125f40f10e5c25614630dcbed23d95e36f907
Copying /etc changes: 2 modified, 3 removed, 4 added
bootfs is sufficient for calculated new size: 0 bytes
Transaction complete; bootconfig swap: yes; bootversion: boot.0.1, deployment count change: 1

$ ls /boot/loader/entries
ostree-1.conf  ostree-2+3.conf

[1] https://uapi-group.org/specifications/specs/boot_loader_specification/#boot-counting
Signed-off-by: Igor Opaniuk <[email protected]>
  • Loading branch information
igoropaniuk committed Dec 5, 2024
1 parent 74efebd commit a0f03d2
Show file tree
Hide file tree
Showing 6 changed files with 203 additions and 9 deletions.
12 changes: 12 additions & 0 deletions man/ostree.repo-config.xml
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,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:
Expand Down
93 changes: 93 additions & 0 deletions src/libostree/ostree-bootconfig-parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ struct _OstreeBootconfigParser
gboolean parsed;
const char *separators;

guint64 tries_left;
guint64 tries_done;

GHashTable *options;

/* Additional initrds; the primary initrd is in options. */
Expand Down Expand Up @@ -56,6 +59,94 @@ ostree_bootconfig_parser_clone (OstreeBootconfigParser *self)
return parser;
}

/**
* ostree_bootconfig_parser_parse_tries:
* @self: Parser
* @path: File path
*
* Parses a suffix of two counters in the form "+LEFT-DONE" from the end of the
* filename (excluding file extension).
*/
static void
ostree_bootconfig_parser_parse_tries (OstreeBootconfigParser *self, const char *filename)
{
gchar *counter = NULL;
gchar *old_counter = NULL;
self->tries_left = 0;
self->tries_done = 0;

for (;;)
{
char *plus = strchr (counter ?: filename, '+');
if (plus)
{
/* Look for the last "+" symbol in the filename */
counter = plus + 1;
continue;
}
if (counter)
break;

/* No boot counter found */
return;
}

guint64 tries_left, tries_done = 0;

old_counter = counter;
tries_left = g_ascii_strtoull (old_counter, &counter, 10);
if ((old_counter == counter) || (tries_left > INT_MAX))
return;

/* Parse done counter only if present */
if (*counter == '-')
{
old_counter = counter;
tries_done = g_ascii_strtoull (counter + 1, &counter, 10);
if ((old_counter == counter) || (tries_left > INT_MAX))
return;
}

self->tries_left = tries_left;
self->tries_done = tries_done;
}

/**
* ostree_bootconfig_parser_get_tries_left:
* @self: Parser
*
* Returns: Amount of boot tries left
*/
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;
}

/**
* ostree_bootconfig_parser_get_tries_done:
* @self: Parser
*
* Returns: TRUE if the boot config was parsed from existing boot config file
*/
gboolean
ostree_bootconfig_parser_is_parsed (OstreeBootconfigParser *self)
{
return self->parsed;
}

/**
* ostree_bootconfig_parser_parse_at:
* @self: Parser
Expand Down Expand Up @@ -116,6 +207,8 @@ ostree_bootconfig_parser_parse_at (OstreeBootconfigParser *self, int dfd, const
self->overlay_initrds = (char **)g_ptr_array_free (g_steal_pointer (&overlay_initrds), FALSE);
}

ostree_bootconfig_parser_parse_tries(self, glnx_basename(path));

self->parsed = TRUE;

return TRUE;
Expand Down
9 changes: 9 additions & 0 deletions src/libostree/ostree-bootconfig-parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,13 @@ 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);

_OSTREE_PUBLIC
gboolean ostree_bootconfig_parser_is_parsed (OstreeBootconfigParser *self);

G_END_DECLS
1 change: 1 addition & 0 deletions src/libostree/ostree-repo-private.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
Expand Down
14 changes: 14 additions & 0 deletions src/libostree/ostree-repo.c
Original file line number Diff line number Diff line change
Expand Up @@ -3297,6 +3297,20 @@ 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;

(void)ot_keyfile_get_value_with_default_group_optional (self->config, "sysroot", "boot-counting-tries", "0",
&boot_counting_str, NULL);

if (boot_counting_str)
/* Ensure boot count value is in [0,5] */
self->boot_counting
= MAX (0, MIN (INT_MAX, g_ascii_strtoull (boot_counting_str, NULL, 10)));
else
self->boot_counting = 0;
}

g_autofree char *bootloader = NULL;

if (!ot_keyfile_get_value_with_default_group_optional (self->config, "sysroot", "bootloader",
Expand Down
83 changes: 74 additions & 9 deletions src/libostree/ostree-sysroot-deploy.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include <gio/gunixinputstream.h>
#include <gio/gunixoutputstream.h>
#include <glib-unix.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stdint.h>
#include <sys/ioctl.h>
Expand Down Expand Up @@ -1801,14 +1802,60 @@ parse_os_release (const char *contents, const char *split)
return ret;
}

/* Generate the filename we will use in /boot/loader/entries for this deployment.
/* Generate the entry name we will use in /boot/loader/entries for this deployment.
* The provided n_deployments should be the total number of target deployments (which
* might be different from the cached value in the sysroot).
*/
static char *
bootloader_entry_name (OstreeSysroot *sysroot, guint n_deployments,
OstreeDeployment *deployment)
{
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
// the version field. xref https://github.com/ostreedev/ostree/issues/2961
bool use_old_naming = (sysroot->opt_flags & OSTREE_SYSROOT_GLOBAL_OPT_BOOTLOADER_NAMING_1) > 0;
if (use_old_naming)
{
const char *stateroot = ostree_deployment_get_osname (deployment);
return g_strdup_printf ("ostree-%d-%s", index, stateroot);
}
else
{
return g_strdup_printf ("ostree-%d", index);
}
}

static guint
bootloader_get_max_boot_tries (OstreeSysroot *self, GCancellable *cancellable, GError **error)
{
g_autoptr (OstreeRepo) repo = NULL;
if (!ostree_sysroot_get_repo (self, &repo, cancellable, error))
return 0;

return repo->boot_counting;
}

static gboolean
bootloader_is_boot_count_enabled (OstreeSysroot *self, GCancellable *cancellable, GError **error)
{
g_autoptr (OstreeRepo) repo = NULL;
if (!ostree_sysroot_get_repo (self, &repo, cancellable, error))
return FALSE;

return (repo->boot_counting != 0 ? TRUE : FALSE);
}

/* Generate the entry filename we will use in /boot/loader/entries for this deployment.
* The provided n_deployments should be the total number of target deployments (which
* might be different from the cached value in the sysroot).
*/
static char *
bootloader_entry_filename (OstreeSysroot *sysroot, guint n_deployments,
OstreeDeployment *deployment)
OstreeDeployment *deployment, GCancellable *cancellable,
GError **error)
{
g_autofree char *bootconf_name;
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
Expand All @@ -1817,12 +1864,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 (!bootloader_is_boot_count_enabled(sysroot, cancellable, error))
return g_strdup_printf ("%s.conf", bootconf_name);

guint max_tries = bootloader_get_max_boot_tries (sysroot, cancellable, error);
OstreeBootconfigParser *bootconfig = ostree_deployment_get_bootconfig (deployment);

if (!ostree_bootconfig_parser_is_parsed (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
Expand Down Expand Up @@ -1859,7 +1922,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;
Expand Down Expand Up @@ -2173,8 +2235,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,
cancellable, error);

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;
Expand Down Expand Up @@ -4212,15 +4277,15 @@ 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
= bootloader_entry_filename (self, self->deployments->len, deployment);
g_autofree char *bootconf_filename
= bootloader_entry_filename (self, self->deployments->len, deployment, cancellable, error);

g_autofree char *bootconfdir = g_strdup_printf ("loader.%d/entries", self->bootversion);
glnx_autofd int bootconf_dfd = -1;
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;
}
Expand Down

0 comments on commit a0f03d2

Please sign in to comment.