From 58b1f0306565e01fd066744460b7bf4938c3883e Mon Sep 17 00:00:00 2001 From: Jonathan Lebon Date: Thu, 13 Aug 2020 15:50:42 -0400 Subject: [PATCH] lib/deploy: Add support for additional initrds In FCOS and RHCOS, the need to configure software in the initramfs has come up multiple times. Sometimes, using kernel arguments suffices. Other times, it really must be a configuration file. Rebuilding the initramfs on the client-side however is a costly operation. Not only does it add complexity to the update workflow, it also erodes a lot of the value obtained from using the baked "blessed" initramfs from the tree itself. One elegant way to address this is to allow specifying multiple initramfses. This is supported by most bootloaders (notably GRUB) and results in each initrd being checked out on top of each other. This patch allows libostree clients to leverage this so that they can avoid regenerating the initramfs entirely. libostree itself is agnostic as to what kind and how much data overlay initrds contain. It's up to the clients to enforce such boundaries. To implement this, we add a new option to the deploy APIs called `additional_initrds`: this is an array of filepaths to CPIO archives. We copy these files into the bootcsum on `/boot` and add them to the BLS as another `initrd` entry. --- src/libostree/ostree-deployment-private.h | 6 +++ src/libostree/ostree-deployment.c | 15 ++++++++ src/libostree/ostree-sysroot-deploy.c | 46 +++++++++++++++++++++++ src/libostree/ostree-sysroot.c | 4 ++ src/libostree/ostree-sysroot.h | 3 +- 5 files changed, 73 insertions(+), 1 deletion(-) diff --git a/src/libostree/ostree-deployment-private.h b/src/libostree/ostree-deployment-private.h index ad77317d79..000c0bbc8d 100644 --- a/src/libostree/ostree-deployment-private.h +++ b/src/libostree/ostree-deployment-private.h @@ -47,6 +47,7 @@ struct _OstreeDeployment char *csum; int deployserial; char *bootcsum; + char **additional_initrds; int bootserial; OstreeBootconfigParser *bootconfig; GKeyFile *origin; @@ -56,4 +57,9 @@ struct _OstreeDeployment void _ostree_deployment_set_bootcsum (OstreeDeployment *self, const char *bootcsum); +void _ostree_deployment_set_additional_initrds (OstreeDeployment *self, + char **additional_initrds); +char** +_ostree_deployment_get_additional_initrds (OstreeDeployment *self); + G_END_DECLS diff --git a/src/libostree/ostree-deployment.c b/src/libostree/ostree-deployment.c index 70e1bc4928..6de1f0f43b 100644 --- a/src/libostree/ostree-deployment.c +++ b/src/libostree/ostree-deployment.c @@ -158,6 +158,20 @@ _ostree_deployment_set_bootcsum (OstreeDeployment *self, self->bootcsum = g_strdup (bootcsum); } +void +_ostree_deployment_set_additional_initrds (OstreeDeployment *self, + char **additional_initrds) +{ + g_strfreev (self->additional_initrds); + self->additional_initrds = g_strdupv (additional_initrds); +} + +char** +_ostree_deployment_get_additional_initrds (OstreeDeployment *self) +{ + return self->additional_initrds; +} + /** * ostree_deployment_clone: * @self: Deployment @@ -238,6 +252,7 @@ ostree_deployment_finalize (GObject *object) g_free (self->bootcsum); g_clear_object (&self->bootconfig); g_clear_pointer (&self->origin, g_key_file_unref); + g_strfreev (self->additional_initrds); G_OBJECT_CLASS (ostree_deployment_parent_class)->finalize (object); } diff --git a/src/libostree/ostree-sysroot-deploy.c b/src/libostree/ostree-sysroot-deploy.c index 4f09d30192..ab35303a85 100644 --- a/src/libostree/ostree-sysroot-deploy.c +++ b/src/libostree/ostree-sysroot-deploy.c @@ -1859,6 +1859,42 @@ install_deployment_kernel (OstreeSysroot *sysroot, } } + g_autoptr(GPtrArray) additional_initrds = NULL; + for (char **it = _ostree_deployment_get_additional_initrds (deployment); it && *it; it++) + { + char *initrd = *it; + + /* Additional initrds are not part of the bootcsum; they're not part of the tree + * proper. But they're named by their csum. Doing it this way allows sharing the same + * bootcsum dir for multiple deployments with the only change being in additional + * initrds. The downside is that we would need to implement our own GC based on + * referenced overlays from all the bootconfigs; that said, they're tiny overlays and + * will get blown away on each kernel/initrd update. */ + + g_autofree char *checksum = + ot_checksum_file_at (AT_FDCWD, initrd, G_CHECKSUM_SHA256, cancellable, error); + if (checksum == NULL) + return glnx_prefix_error (error, "Checksumming %s", initrd); + + g_autofree char *destpath = + g_strdup_printf ("/%s/initramfs-overlay-%s.img", bootcsumdir, checksum); + + const char *base_destpath = glnx_basename (destpath); + if (!glnx_fstatat_allow_noent (bootcsum_dfd, base_destpath, &stbuf, 0, error)) + return FALSE; + + if (errno == ENOENT) + { + if (!install_into_boot (repo, sepolicy, AT_FDCWD, initrd, bootcsum_dfd, + base_destpath, sysroot->debug_flags, cancellable, error)) + return FALSE; + } + + if (additional_initrds == NULL) + additional_initrds = g_ptr_array_new_with_free_func (g_free); + g_ptr_array_add (additional_initrds, g_steal_pointer (&destpath)); + } + g_autofree char *contents = NULL; if (!glnx_fstatat_allow_noent (deployment_dfd, "usr/lib/os-release", &stbuf, 0, error)) return FALSE; @@ -1938,6 +1974,12 @@ install_deployment_kernel (OstreeSysroot *sysroot, g_autofree char * initrd_boot_relpath = g_strconcat ("/", bootcsumdir, "/", kernel_layout->initramfs_namever, NULL); ostree_bootconfig_parser_set (bootconfig, "initrd", initrd_boot_relpath); + + if (additional_initrds) + { + for (guint i = 0; i < additional_initrds->len; i++) + ostree_bootconfig_parser_add_initrd (bootconfig, additional_initrds->pdata[i]); + } } else { @@ -2716,6 +2758,7 @@ sysroot_initialize_deployment (OstreeSysroot *self, _ostree_deployment_set_bootcsum (new_deployment, kernel_layout->bootcsum); _ostree_deployment_set_bootconfig_from_kargs (new_deployment, opts->override_kernel_argv); + _ostree_deployment_set_additional_initrds (new_deployment, opts->additional_initrds); if (!prepare_deployment_etc (self, repo, new_deployment, deployment_dfd, cancellable, error)) @@ -3116,6 +3159,9 @@ ostree_sysroot_stage_tree_with_options (OstreeSysroot *self, if (opts->override_kernel_argv) g_variant_builder_add (builder, "{sv}", "kargs", g_variant_new_strv ((const char *const*)opts->override_kernel_argv, -1)); + if (opts->additional_initrds) + g_variant_builder_add (builder, "{sv}", "additional-initrds", + g_variant_new_strv ((const char *const*)opts->additional_initrds, -1)); const char *parent = dirname (strdupa (_OSTREE_SYSROOT_RUNSTATE_STAGED)); if (!glnx_shutil_mkdir_p_at (AT_FDCWD, parent, 0755, cancellable, error)) diff --git a/src/libostree/ostree-sysroot.c b/src/libostree/ostree-sysroot.c index b211fea723..9c7b41522a 100644 --- a/src/libostree/ostree-sysroot.c +++ b/src/libostree/ostree-sysroot.c @@ -966,8 +966,10 @@ _ostree_sysroot_reload_staged (OstreeSysroot *self, /* Parse it */ g_autoptr(GVariant) target = NULL; g_autofree char **kargs = NULL; + g_autofree char **additional_initrds = NULL; g_variant_dict_lookup (staged_deployment_dict, "target", "@a{sv}", &target); g_variant_dict_lookup (staged_deployment_dict, "kargs", "^a&s", &kargs); + g_variant_dict_lookup (staged_deployment_dict, "additional-initrds", "^a&s", &additional_initrds); if (target) { g_autoptr(OstreeDeployment) staged = @@ -979,6 +981,8 @@ _ostree_sysroot_reload_staged (OstreeSysroot *self, if (!load_origin (self, staged, NULL, error)) return FALSE; + _ostree_deployment_set_additional_initrds (staged, additional_initrds); + self->staged_deployment = g_steal_pointer (&staged); self->staged_deployment_data = g_steal_pointer (&staged_deployment_data); /* We set this flag for ostree_deployment_is_staged() because that API diff --git a/src/libostree/ostree-sysroot.h b/src/libostree/ostree-sysroot.h index 45d6d63cc4..3092b60bd0 100644 --- a/src/libostree/ostree-sysroot.h +++ b/src/libostree/ostree-sysroot.h @@ -190,7 +190,8 @@ typedef struct { gboolean unused_bools[8]; int unused_ints[8]; char **override_kernel_argv; - gpointer unused_ptrs[7]; + char **additional_initrds; + gpointer unused_ptrs[6]; } OstreeSysrootDeployTreeOpts; _OSTREE_PUBLIC