From 8ea23c7f6df89bdaee08b6454631e8a6d7871903 Mon Sep 17 00:00:00 2001 From: Igor Opaniuk Date: Wed, 11 Sep 2024 18:03:10 +0200 Subject: [PATCH] sysroot: Support boot counting for boot entries 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. All "pending" entries (in the middle of boot counting) are automatically removed during creation of new boot entries for new deployments. The feature is enabled with --enable_boot_count autoconf option. 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+3.conf ostree-2+3.conf [1] https://uapi-group.org/specifications/specs/boot_loader_specification/#boot-counting Signed-off-by: Igor Opaniuk configure option --- configure.ac | 9 +++ src/libostree/ostree-deployment.c | 19 +++++ src/libostree/ostree-deployment.h | 3 + src/libostree/ostree-sysroot-deploy.c | 99 ++++++++++++++++++++++++--- 4 files changed, 121 insertions(+), 9 deletions(-) diff --git a/configure.ac b/configure.ac index 176bf65c43..21d3f632de 100644 --- a/configure.ac +++ b/configure.ac @@ -362,6 +362,14 @@ AS_IF([test "$enable_man_html" = yes && test "$enable_man" = no], [ ]) AM_CONDITIONAL(ENABLE_MAN_HTML, test "$enable_man_html" = yes) +AC_ARG_ENABLE(boot-count, + AS_HELP_STRING([--enable-boot-count], + [Enable boot counting for boot entries (default: no)]),, + enable_boot_count=no) +AS_IF([ test "$enable_boot_count" != no], [ + AC_DEFINE([ENABLE_BOOT_COUNT], 1, [Define if boot counting is enabled]) +]) + AC_ARG_WITH(libarchive, AS_HELP_STRING([--without-libarchive], [Do not use libarchive]), :, with_libarchive=maybe) @@ -705,6 +713,7 @@ echo " man pages (xsltproc): $enable_man api docs (gtk-doc): $enable_gtk_doc installed tests: $enable_installed_tests + boot counting for boot entries: $enable_boot_count gjs-based tests: $have_gjs dracut: $with_dracut mkinitcpio: $with_mkinitcpio diff --git a/src/libostree/ostree-deployment.c b/src/libostree/ostree-deployment.c index 8be2fdd507..d785b30790 100644 --- a/src/libostree/ostree-deployment.c +++ b/src/libostree/ostree-deployment.c @@ -398,6 +398,25 @@ ostree_deployment_get_origin_relpath (OstreeDeployment *self) ostree_deployment_get_deployserial (self)); } + +/** + * ostree_deployment_get_boot_tries_relpath: + * @self: A deployment + * + * Note this function only returns a *relative* path - if you want to + * access, it, you must either use fd-relative api such as openat(), + * or concatenate it with the full ostree_sysroot_get_path(). + * + * Returns: (not nullable) (transfer full): Path to deployment root directory, relative to sysroot + */ +char * +ostree_deployment_get_boot_tries_relpath (OstreeDeployment *self) +{ + return g_strdup_printf ("ostree/deploy/%s/deploy/%s.%d/etc/kernel/tries", + ostree_deployment_get_osname (self), ostree_deployment_get_csum (self), + ostree_deployment_get_deployserial (self)); +} + /** * ostree_deployment_unlocked_state_to_string: * diff --git a/src/libostree/ostree-deployment.h b/src/libostree/ostree-deployment.h index 0536d9810c..bf6b39940a 100644 --- a/src/libostree/ostree-deployment.h +++ b/src/libostree/ostree-deployment.h @@ -93,6 +93,9 @@ OstreeDeployment *ostree_deployment_clone (OstreeDeployment *self); _OSTREE_PUBLIC char *ostree_deployment_get_origin_relpath (OstreeDeployment *self); +_OSTREE_PUBLIC +char *ostree_deployment_get_boot_tries_relpath (OstreeDeployment *self); + typedef enum { OSTREE_DEPLOYMENT_UNLOCKED_NONE, diff --git a/src/libostree/ostree-sysroot-deploy.c b/src/libostree/ostree-sysroot-deploy.c index d52eecf3de..8d77284653 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,78 @@ 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); } } +#ifdef ENABLE_BOOT_COUNT + +#define BOOT_COUNT_MAX_RETRIES 3 + +static gint +bootloader_get_max_boot_tries (OstreeSysroot *self, OstreeDeployment *deployment, + GCancellable *cancellable, GError **error) +{ + g_autofree char *tries_file_path = ostree_deployment_get_boot_tries_relpath (deployment); + + glnx_autofd int fd = -1; + if (!ot_openat_ignore_enoent (self->sysroot_fd, tries_file_path, &fd, error)) + return BOOT_COUNT_MAX_RETRIES; + if (fd >= 0) + { + g_autofree char *origin_contents = glnx_fd_readall_utf8 (fd, NULL, cancellable, error); + if (!origin_contents) + return BOOT_COUNT_MAX_RETRIES; + + return atoi(origin_contents); + } + + return BOOT_COUNT_MAX_RETRIES; +} + +/* 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; +} +#endif + /* 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 +1898,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; @@ -2145,9 +2209,18 @@ install_deployment_kernel (OstreeSysroot *sysroot, int new_bootversion, glnx_autofd int bootconf_dfd = -1; if (!glnx_opendirat (sysroot->boot_fd, bootconfdir, TRUE, &bootconf_dfd, error)) return FALSE; +#ifdef ENABLE_BOOT_COUNT + gint max_tries = bootloader_get_max_boot_tries (sysroot, deployment, cancellable, error); + g_autofree char *bootconf_filename = g_strdup_printf ("%s+%d.conf", bootconf_name, max_tries); + + if (!bootloader_remove_tmp_entries(bootconf_dfd, bootconf_name, max_tries, cancellable, error)) + return FALSE; +#else + g_autofree char *bootconf_filename = g_strdup_printf ("%s.conf", bootconf_name); +#endif 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 +4249,22 @@ 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; +#ifdef ENABLE_BOOT_COUNT + gint max_tries = bootloader_get_max_boot_tries (self, deployment, cancellable, error); + g_autofree char *bootconf_filename = g_strdup_printf ("%s+%d.conf", bootconf_name, max_tries); - if (!ostree_bootconfig_parser_write_at (new_bootconfig, bootconf_dfd, bootconf_name, + if (!bootloader_remove_tmp_entries(bootconf_dfd, bootconf_name, max_tries, cancellable, error)) + return FALSE; +#else + g_autofree char *bootconf_filename = g_strdup_printf ("%s.conf", bootconf_name); +#endif + if (!ostree_bootconfig_parser_write_at (new_bootconfig, bootconf_dfd, bootconf_filename, cancellable, error)) return FALSE; }