diff --git a/man/ostree.repo-config.xml b/man/ostree.repo-config.xml
index 181d7ba9e2..70fef35aee 100644
--- a/man/ostree.repo-config.xml
+++ b/man/ostree.repo-config.xml
@@ -416,6 +416,18 @@ License along with this library. If not, see .
+
+ boot-counting-tries
+ 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
+
+ The Boot Loader Specification
+
+
+
bls-append-except-default
A semicolon separated string list of key-value pairs. For example:
diff --git a/src/libostree/ostree-bootconfig-parser.c b/src/libostree/ostree-bootconfig-parser.c
index 4c3e80d083..518b0793f4 100644
--- a/src/libostree/ostree-bootconfig-parser.c
+++ b/src/libostree/ostree-bootconfig-parser.c
@@ -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. */
@@ -56,6 +59,95 @@ 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;
+ size_t prefix_len = counter - filename;
+
+ 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
@@ -116,6 +208,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;
diff --git a/src/libostree/ostree-bootconfig-parser.h b/src/libostree/ostree-bootconfig-parser.h
index 5fdad72eed..c2628a90ab 100644
--- a/src/libostree/ostree-bootconfig-parser.h
+++ b/src/libostree/ostree-bootconfig-parser.h
@@ -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
diff --git a/src/libostree/ostree-repo-private.h b/src/libostree/ostree-repo-private.h
index 1e27fc772d..3ea1ff65a6 100644
--- a/src/libostree/ostree-repo-private.h
+++ b/src/libostree/ostree-repo-private.h
@@ -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;
};
diff --git a/src/libostree/ostree-repo.c b/src/libostree/ostree-repo.c
index 2f4e836c65..e3e179a174 100644
--- a/src/libostree/ostree-repo.c
+++ b/src/libostree/ostree-repo.c
@@ -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",
diff --git a/src/libostree/ostree-sysroot-deploy.c b/src/libostree/ostree-sysroot-deploy.c
index 43f380f68c..5a894059a2 100644
--- a/src/libostree/ostree-sysroot-deploy.c
+++ b/src/libostree/ostree-sysroot-deploy.c
@@ -1801,13 +1801,13 @@ 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_filename (OstreeSysroot *sysroot, guint n_deployments,
- OstreeDeployment *deployment)
+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.
@@ -1817,14 +1817,34 @@ 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);
+ return g_strdup_printf ("ostree-%d-%s", index, stateroot);
}
else
{
- return g_strdup_printf ("ostree-%d.conf", index);
+ 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);
+}
+
/* Given @deployment, prepare it to be booted; basically copying its
* kernel/initramfs into /boot/ostree (if needed) and writing out an entry in
* /boot/loader/entries.
@@ -1859,7 +1879,7 @@ 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);
+ g_autofree char *bootconf_name = bootloader_entry_name (sysroot, n_deployments, deployment);
if (!glnx_shutil_mkdir_p_at (sysroot->boot_fd, bootcsumdir, 0775, cancellable, error))
return FALSE;
@@ -2173,8 +2193,28 @@ 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;
+ if (bootloader_is_boot_count_enabled(sysroot, cancellable, error))
+ {
+ guint max_tries = bootloader_get_max_boot_tries (sysroot, cancellable, error);
+
+ if (!ostree_bootconfig_parser_is_parsed(bootconfig))
+ bootconf_filename = 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) )
+ bootconf_filename = g_strdup_printf ("%s.conf", bootconf_name);
+ else
+ bootconf_filename = g_strdup_printf ("%s+%lu-%lu.conf", bootconf_name,
+ ostree_bootconfig_parser_get_tries_left(bootconfig),
+ ostree_bootconfig_parser_get_tries_done(bootconfig));
+ }
+ else
+ {
+ bootconf_filename = g_strdup_printf ("%s.conf", bootconf_name);
+ }
+
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;
@@ -4213,14 +4253,35 @@ ostree_sysroot_deployment_set_kargs_in_place (OstreeSysroot *self, OstreeDeploym
ostree_bootconfig_parser_set (new_bootconfig, "options", kargs_str);
g_autofree char *bootconf_name
- = bootloader_entry_filename (self, self->deployments->len, deployment);
+ = bootloader_entry_name (self, self->deployments->len, deployment);
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,
+ g_autofree char *bootconf_filename;
+ if (bootloader_is_boot_count_enabled(self, cancellable, error))
+ {
+ guint max_tries = bootloader_get_max_boot_tries (self, cancellable, error);
+
+ if (!ostree_bootconfig_parser_is_parsed (new_bootconfig))
+ bootconf_filename = g_strdup_printf ("%s+%u.conf", bootconf_name, max_tries);
+ else if (!ostree_bootconfig_parser_get_tries_left (new_bootconfig)
+ && !ostree_bootconfig_parser_get_tries_done (new_bootconfig))
+ bootconf_filename = g_strdup_printf ("%s.conf", bootconf_name);
+ else
+ bootconf_filename
+ = g_strdup_printf ("%s+%lu-%lu.conf", bootconf_name,
+ ostree_bootconfig_parser_get_tries_left (new_bootconfig),
+ ostree_bootconfig_parser_get_tries_done (new_bootconfig));
+ }
+ else
+ {
+ bootconf_filename = g_strdup_printf ("%s.conf", bootconf_name);
+ }
+
+ if (!ostree_bootconfig_parser_write_at (new_bootconfig, bootconf_dfd, bootconf_filename,
cancellable, error))
return FALSE;
}