diff --git a/man/ostree.repo-config.xml b/man/ostree.repo-config.xml
index 181d7ba9e2..99735f2379 100644
--- a/man/ostree.repo-config.xml
+++ b/man/ostree.repo-config.xml
@@ -416,6 +416,14 @@ License along with this library. If not, see .
+
+ boot_counting
+ 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.
+
+
+
bls-append-except-default
A semicolon separated string list of key-value pairs. For example:
diff --git a/src/libostree/ostree-repo-private.h b/src/libostree/ostree-repo-private.h
index 21b0fc14e9..c6be7681cb 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..a72aac4428 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", "0",
+ &boot_counting_str, NULL);
+
+ if (boot_counting_str)
+ /* Ensure boot count value is in [0,5] */
+ self->boot_counting
+ = MAX (0, MIN (5, 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 d52eecf3de..160ca6eaa0 100644
--- a/src/libostree/ostree-sysroot-deploy.c
+++ b/src/libostree/ostree-sysroot-deploy.c
@@ -1776,13 +1776,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.
@@ -1792,14 +1792,72 @@ 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);
+}
+
+/* Drop all temporary entries in /boot/loader/entries for this deployment,
+ * which were created during automatic boot assesment
+ * https://uapi-group.org/specifications/specs/boot_loader_specification/#boot-counting
+ */
+static gboolean
+bootloader_remove_tmp_entries (int dfd, const char *entry_name, gint max_tries, GCancellable *cancellable,
+ GError **error)
+{
+ g_auto(GLnxDirFdIterator) dfd_iter = { 0, };
+ g_autofree char *entry_name_init = g_strdup_printf ("%s+%d", entry_name, max_tries);
+
+ if (!glnx_dirfd_iterator_init_at (dfd, ".", FALSE, &dfd_iter, error))
+ return FALSE;
+
+ while (TRUE)
+ {
+ struct dirent *dent = NULL;
+
+ if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&dfd_iter, &dent, cancellable, error))
+ return FALSE;
+ if (dent == NULL)
+ break;
+
+ /* Don't remove default boot entry (with +3 suffix) */
+ if (g_str_has_prefix (dent->d_name, entry_name_init))
+ continue;
+
+ if (g_str_has_prefix (dent->d_name, entry_name))
+ {
+ if (!glnx_shutil_rm_rf_at (dfd_iter.fd, dent->d_name, cancellable, error))
+ return FALSE;
+ }
+
+ }
+
+ return TRUE;
+}
+
/* 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.
@@ -1834,7 +1892,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;
@@ -2146,8 +2204,22 @@ 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);
+ bootconf_filename = g_strdup_printf ("%s+%u.conf", bootconf_name, max_tries);
+
+ if (!bootloader_remove_tmp_entries(bootconf_dfd, bootconf_name, max_tries, cancellable, error))
+ return FALSE;
+ }
+ 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;
@@ -4176,14 +4248,28 @@ 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);
+ bootconf_filename = g_strdup_printf ("%s+%u.conf", bootconf_name, max_tries);
+
+ if (!bootloader_remove_tmp_entries(bootconf_dfd, bootconf_name, max_tries, cancellable, error))
+ return FALSE;
+ }
+ 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;
}