diff --git a/apidoc/ostree-sections.txt b/apidoc/ostree-sections.txt index 66b4223301..c1d6b35ef9 100644 --- a/apidoc/ostree-sections.txt +++ b/apidoc/ostree-sections.txt @@ -37,6 +37,8 @@ ostree_bootconfig_parser_write ostree_bootconfig_parser_write_at ostree_bootconfig_parser_set ostree_bootconfig_parser_get +ostree_bootconfig_parser_set_overlay_initrds +ostree_bootconfig_parser_get_overlay_initrds OSTREE_BOOTCONFIG_PARSER OSTREE_IS_BOOTCONFIG_PARSER @@ -545,7 +547,10 @@ ostree_sysroot_write_deployments ostree_sysroot_write_deployments_with_options ostree_sysroot_write_origin_file ostree_sysroot_stage_tree +ostree_sysroot_stage_tree_with_options +ostree_sysroot_stage_overlay_initrd ostree_sysroot_deploy_tree +ostree_sysroot_deploy_tree_with_options ostree_sysroot_get_merge_deployment ostree_sysroot_query_deployments_for ostree_sysroot_origin_new_from_refspec diff --git a/src/libostree/libostree-devel.sym b/src/libostree/libostree-devel.sym index f74f0e006f..341a22a8fe 100644 --- a/src/libostree/libostree-devel.sym +++ b/src/libostree/libostree-devel.sym @@ -24,6 +24,11 @@ global: */ ostree_repo_static_delta_execute_offline_with_signature; ostree_repo_static_delta_verify_signature; + ostree_bootconfig_parser_get_overlay_initrds; + ostree_bootconfig_parser_set_overlay_initrds; + ostree_sysroot_deploy_tree_with_options; + ostree_sysroot_stage_tree_with_options; + ostree_sysroot_stage_overlay_initrd; } LIBOSTREE_2020.4; /* Stub section for the stable release *after* this development one; don't diff --git a/src/libostree/ostree-bootconfig-parser.c b/src/libostree/ostree-bootconfig-parser.c index 67f9fb5896..a36a4118af 100644 --- a/src/libostree/ostree-bootconfig-parser.c +++ b/src/libostree/ostree-bootconfig-parser.c @@ -30,6 +30,9 @@ struct _OstreeBootconfigParser const char *separators; GHashTable *options; + + /* Additional initrds; the primary initrd is in options. */ + char **overlay_initrds; }; typedef GObjectClass OstreeBootconfigParserClass; @@ -50,6 +53,8 @@ ostree_bootconfig_parser_clone (OstreeBootconfigParser *self) GLNX_HASH_TABLE_FOREACH_KV (self->options, const char*, k, const char*, v) g_hash_table_replace (parser->options, g_strdup (k), g_strdup (v)); + parser->overlay_initrds = g_strdupv (self->overlay_initrds); + return parser; } @@ -76,6 +81,8 @@ ostree_bootconfig_parser_parse_at (OstreeBootconfigParser *self, if (!contents) return FALSE; + g_autoptr(GPtrArray) overlay_initrds = NULL; + g_auto(GStrv) lines = g_strsplit (contents, "\n", -1); for (char **iter = lines; *iter; iter++) { @@ -87,8 +94,19 @@ ostree_bootconfig_parser_parse_at (OstreeBootconfigParser *self, items = g_strsplit_set (line, self->separators, 2); if (g_strv_length (items) == 2 && items[0][0] != '\0') { - g_hash_table_insert (self->options, items[0], items[1]); - g_free (items); /* Transfer ownership */ + if (g_str_equal (items[0], "initrd") && + g_hash_table_contains (self->options, "initrd")) + { + if (!overlay_initrds) + overlay_initrds = g_ptr_array_new_with_free_func (g_free); + g_ptr_array_add (overlay_initrds, items[1]); + g_free (items[0]); + } + else + { + g_hash_table_insert (self->options, items[0], items[1]); + } + g_free (items); /* Free container; we stole the elements */ } else { @@ -97,6 +115,12 @@ ostree_bootconfig_parser_parse_at (OstreeBootconfigParser *self, } } + if (overlay_initrds) + { + g_ptr_array_add (overlay_initrds, NULL); + self->overlay_initrds = (char**)g_ptr_array_free (g_steal_pointer (&overlay_initrds), FALSE); + } + self->parsed = TRUE; return TRUE; @@ -127,6 +151,41 @@ ostree_bootconfig_parser_get (OstreeBootconfigParser *self, return g_hash_table_lookup (self->options, key); } +/** + * ostree_bootconfig_parser_set_overlay_initrds: + * @self: Parser + * @initrds: (array zero-terminated=1) (transfer none) (allow-none): Array of overlay + * initrds or %NULL to unset. + * + * These are rendered as additional `initrd` keys in the final bootloader configs. The + * base initrd is part of the primary keys. + * + * Since: 2020.7 + */ +void +ostree_bootconfig_parser_set_overlay_initrds (OstreeBootconfigParser *self, + char **initrds) +{ + g_assert (g_hash_table_contains (self->options, "initrd")); + g_strfreev (self->overlay_initrds); + self->overlay_initrds = g_strdupv (initrds); +} + +/** + * ostree_bootconfig_parser_get_overlay_initrds: + * @self: Parser + * + * Returns: (array zero-terminated=1) (transfer none) (nullable): Array of initrds or %NULL + * if none are set. + * + * Since: 2020.7 + */ +char** +ostree_bootconfig_parser_get_overlay_initrds (OstreeBootconfigParser *self) +{ + return self->overlay_initrds; +} + static void write_key (OstreeBootconfigParser *self, GString *buf, @@ -165,6 +224,15 @@ ostree_bootconfig_parser_write_at (OstreeBootconfigParser *self, } } + /* Write overlay initrds */ + if (self->overlay_initrds && (g_strv_length (self->overlay_initrds) > 0)) + { + /* we should've written the primary initrd already */ + g_assert (g_hash_table_contains (keys_written, "initrd")); + for (char **it = self->overlay_initrds; it && *it; it++) + write_key (self, buf, "initrd", *it); + } + /* Write unknown fields */ GLNX_HASH_TABLE_FOREACH_KV (self->options, const char*, k, const char*, v) { @@ -197,6 +265,7 @@ ostree_bootconfig_parser_finalize (GObject *object) { OstreeBootconfigParser *self = OSTREE_BOOTCONFIG_PARSER (object); + g_strfreev (self->overlay_initrds); g_hash_table_unref (self->options); G_OBJECT_CLASS (ostree_bootconfig_parser_parent_class)->finalize (object); diff --git a/src/libostree/ostree-bootconfig-parser.h b/src/libostree/ostree-bootconfig-parser.h index aec0753581..d03c931c55 100644 --- a/src/libostree/ostree-bootconfig-parser.h +++ b/src/libostree/ostree-bootconfig-parser.h @@ -73,5 +73,11 @@ _OSTREE_PUBLIC const char *ostree_bootconfig_parser_get (OstreeBootconfigParser *self, const char *key); +_OSTREE_PUBLIC +void ostree_bootconfig_parser_set_overlay_initrds (OstreeBootconfigParser *self, + char **initrds); + +_OSTREE_PUBLIC +char** ostree_bootconfig_parser_get_overlay_initrds (OstreeBootconfigParser *self); G_END_DECLS diff --git a/src/libostree/ostree-deployment-private.h b/src/libostree/ostree-deployment-private.h index ad77317d79..b339ae2618 100644 --- a/src/libostree/ostree-deployment-private.h +++ b/src/libostree/ostree-deployment-private.h @@ -37,6 +37,8 @@ G_BEGIN_DECLS * @origin: How to construct an upgraded version of this tree * @unlocked: The unlocked state * @staged: TRUE iff this deployment is staged + * @overlay_initrds: Checksums of staged additional initrds for this deployment + * @overlay_initrds_id: Unique ID generated from initrd checksums; used to compare deployments */ struct _OstreeDeployment { @@ -52,8 +54,15 @@ struct _OstreeDeployment GKeyFile *origin; OstreeDeploymentUnlockedState unlocked; gboolean staged; + char **overlay_initrds; + char *overlay_initrds_id; }; void _ostree_deployment_set_bootcsum (OstreeDeployment *self, const char *bootcsum); +void _ostree_deployment_set_overlay_initrds (OstreeDeployment *self, + char **overlay_initrds); + +char** _ostree_deployment_get_overlay_initrds (OstreeDeployment *self); + G_END_DECLS diff --git a/src/libostree/ostree-deployment.c b/src/libostree/ostree-deployment.c index 70e1bc4928..182bceea9f 100644 --- a/src/libostree/ostree-deployment.c +++ b/src/libostree/ostree-deployment.c @@ -158,6 +158,34 @@ _ostree_deployment_set_bootcsum (OstreeDeployment *self, self->bootcsum = g_strdup (bootcsum); } +void +_ostree_deployment_set_overlay_initrds (OstreeDeployment *self, + char **overlay_initrds) +{ + g_clear_pointer (&self->overlay_initrds, g_strfreev); + g_clear_pointer (&self->overlay_initrds_id, g_free); + + if (!overlay_initrds || g_strv_length (overlay_initrds) == 0) + return; + + /* Generate a unique ID representing this combination of overlay initrds. This is so that + * ostree_sysroot_write_deployments_with_options() can easily compare initrds when + * comparing deployments for whether a bootswap is necessary. We could be fancier here but + * meh... this works. */ + g_autoptr(GString) id = g_string_new (NULL); + for (char **it = overlay_initrds; it && *it; it++) + g_string_append (id, *it); + + self->overlay_initrds = g_strdupv (overlay_initrds); + self->overlay_initrds_id = g_string_free (g_steal_pointer (&id), FALSE); +} + +char** +_ostree_deployment_get_overlay_initrds (OstreeDeployment *self) +{ + return self->overlay_initrds; +} + /** * ostree_deployment_clone: * @self: Deployment @@ -175,6 +203,8 @@ ostree_deployment_clone (OstreeDeployment *self) new_bootconfig = ostree_bootconfig_parser_clone (self->bootconfig); ostree_deployment_set_bootconfig (ret, new_bootconfig); + _ostree_deployment_set_overlay_initrds (ret, self->overlay_initrds); + if (self->origin) { g_autoptr(GKeyFile) new_origin = NULL; @@ -238,6 +268,8 @@ 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->overlay_initrds); + g_free (self->overlay_initrds_id); G_OBJECT_CLASS (ostree_deployment_parent_class)->finalize (object); } diff --git a/src/libostree/ostree-sysroot-cleanup.c b/src/libostree/ostree-sysroot-cleanup.c index ffad413080..2712283405 100644 --- a/src/libostree/ostree-sysroot-cleanup.c +++ b/src/libostree/ostree-sysroot-cleanup.c @@ -298,6 +298,8 @@ cleanup_old_deployments (OstreeSysroot *self, g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); g_autoptr(GHashTable) active_boot_checksums = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + g_autoptr(GHashTable) active_overlay_initrds = + g_hash_table_new (g_str_hash, g_str_equal); /* borrows from deployment's bootconfig */ for (guint i = 0; i < self->deployments->len; i++) { OstreeDeployment *deployment = self->deployments->pdata[i]; @@ -306,6 +308,11 @@ cleanup_old_deployments (OstreeSysroot *self, /* Transfer ownership */ g_hash_table_replace (active_deployment_dirs, deployment_path, deployment_path); g_hash_table_replace (active_boot_checksums, bootcsum, bootcsum); + + OstreeBootconfigParser *bootconfig = ostree_deployment_get_bootconfig (deployment); + char **initrds = ostree_bootconfig_parser_get_overlay_initrds (bootconfig); + for (char **it = initrds; it && *it; it++) + g_hash_table_add (active_overlay_initrds, (char*)glnx_basename (*it)); } /* Find all deployment directories, both active and inactive */ @@ -349,6 +356,42 @@ cleanup_old_deployments (OstreeSysroot *self, return FALSE; } + /* Clean up overlay initrds */ + glnx_autofd int overlays_dfd = + glnx_opendirat_with_errno (self->sysroot_fd, _OSTREE_SYSROOT_INITRAMFS_OVERLAYS, FALSE); + if (overlays_dfd < 0) + { + if (errno != ENOENT) + return glnx_throw_errno_prefix (error, "open(initrd_overlays)"); + } + else + { + g_autoptr(GPtrArray) initrds_to_delete = g_ptr_array_new_with_free_func (g_free); + g_auto(GLnxDirFdIterator) dfd_iter = { 0, }; + if (!glnx_dirfd_iterator_init_at (overlays_dfd, ".", TRUE, &dfd_iter, error)) + return FALSE; + while (TRUE) + { + struct dirent *dent; + if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&dfd_iter, &dent, cancellable, error)) + return FALSE; + if (dent == NULL) + break; + + /* there shouldn't be other file types there, but let's be conservative */ + if (dent->d_type != DT_REG) + continue; + + if (!g_hash_table_lookup (active_overlay_initrds, dent->d_name)) + g_ptr_array_add (initrds_to_delete, g_strdup (dent->d_name)); + } + for (guint i = 0; i < initrds_to_delete->len; i++) + { + if (!ot_ensure_unlinked_at (overlays_dfd, initrds_to_delete->pdata[i], error)) + return FALSE; + } + } + return TRUE; } diff --git a/src/libostree/ostree-sysroot-deploy.c b/src/libostree/ostree-sysroot-deploy.c index d97d96a83d..1c4fb5dc8b 100644 --- a/src/libostree/ostree-sysroot-deploy.c +++ b/src/libostree/ostree-sysroot-deploy.c @@ -1859,6 +1859,47 @@ install_deployment_kernel (OstreeSysroot *sysroot, } } + g_autoptr(GPtrArray) overlay_initrds = NULL; + for (char **it = _ostree_deployment_get_overlay_initrds (deployment); it && *it; it++) + { + char *checksum = *it; + + /* Overlay initrds are not part of the bootcsum dir; they're not part of the tree + * proper. Instead they're in /boot/ostree/initramfs-overlays/ named by their csum. + * Doing it this way allows sharing the same bootcsum dir for multiple deployments + * with the only change being in overlay initrds (or conversely, the same overlay + * across different boocsums). Eventually, it'd be nice to have an OSTree repo in + * /boot itself and drop the boocsum dir concept entirely. */ + + g_autofree char *destpath = + g_strdup_printf ("/" _OSTREE_SYSROOT_BOOT_INITRAMFS_OVERLAYS "/%s.img", checksum); + const char *rel_destpath = destpath + 1; + + /* lazily allocate array and create dir so we don't pollute /boot if not needed */ + if (overlay_initrds == NULL) + { + overlay_initrds = g_ptr_array_new_with_free_func (g_free); + + if (!glnx_shutil_mkdir_p_at (boot_dfd, _OSTREE_SYSROOT_BOOT_INITRAMFS_OVERLAYS, + 0755, cancellable, error)) + return FALSE; + } + + if (!glnx_fstatat_allow_noent (boot_dfd, rel_destpath, NULL, 0, error)) + return FALSE; + if (errno == ENOENT) + { + g_autofree char *srcpath = + g_strdup_printf (_OSTREE_SYSROOT_RUNSTATE_STAGED_INITRDS_DIR "/%s", checksum); + if (!install_into_boot (repo, sepolicy, AT_FDCWD, srcpath, boot_dfd, rel_destpath, + cancellable, error)) + return FALSE; + } + + /* these are used lower down to populate the bootconfig */ + g_ptr_array_add (overlay_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 +1979,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 (overlay_initrds) + { + g_ptr_array_add (overlay_initrds, NULL); + ostree_bootconfig_parser_set_overlay_initrds (bootconfig, (char**)overlay_initrds->pdata); + } } else { @@ -2135,6 +2182,10 @@ deployment_bootconfigs_equal (OstreeRepo *repo, if (strcmp (a_bootcsum, b_bootcsum) != 0) return FALSE; + /* same initrd overlays? */ + if (g_strcmp0 (a->overlay_initrds_id, b->overlay_initrds_id) != 0) + return FALSE; + /* same kargs? */ g_autofree char *a_boot_options_without_ostree = get_deployment_nonostree_kargs (a); g_autofree char *b_boot_options_without_ostree = get_deployment_nonostree_kargs (b); @@ -2688,7 +2739,7 @@ sysroot_initialize_deployment (OstreeSysroot *self, const char *osname, const char *revision, GKeyFile *origin, - char **override_kernel_argv, + OstreeSysrootDeployTreeOpts *opts, OstreeDeployment **out_new_deployment, GCancellable *cancellable, GError **error) @@ -2721,7 +2772,8 @@ sysroot_initialize_deployment (OstreeSysroot *self, return FALSE; _ostree_deployment_set_bootcsum (new_deployment, kernel_layout->bootcsum); - _ostree_deployment_set_bootconfig_from_kargs (new_deployment, override_kernel_argv); + _ostree_deployment_set_bootconfig_from_kargs (new_deployment, opts ? opts->override_kernel_argv : NULL); + _ostree_deployment_set_overlay_initrds (new_deployment, opts ? opts->overlay_initrds : NULL); if (!prepare_deployment_etc (self, repo, new_deployment, deployment_dfd, cancellable, error)) @@ -2853,13 +2905,13 @@ sysroot_finalize_deployment (OstreeSysroot *self, } /** - * ostree_sysroot_deploy_tree: + * ostree_sysroot_deploy_tree_with_options: * @self: Sysroot * @osname: (allow-none): osname to use for merge deployment * @revision: Checksum to add * @origin: (allow-none): Origin to use for upgrades * @provided_merge_deployment: (allow-none): Use this deployment for merge path - * @override_kernel_argv: (allow-none) (array zero-terminated=1) (element-type utf8): Use these as kernel arguments; if %NULL, inherit options from provided_merge_deployment + * @opts: (allow-none): Options * @out_new_deployment: (out): The new deployment path * @cancellable: Cancellable * @error: Error @@ -2869,23 +2921,25 @@ sysroot_finalize_deployment (OstreeSysroot *self, * * When booted into the sysroot, you should use the * ostree_sysroot_stage_tree() API instead. + * + * Since: 2020.7 */ gboolean -ostree_sysroot_deploy_tree (OstreeSysroot *self, - const char *osname, - const char *revision, - GKeyFile *origin, - OstreeDeployment *provided_merge_deployment, - char **override_kernel_argv, - OstreeDeployment **out_new_deployment, - GCancellable *cancellable, - GError **error) +ostree_sysroot_deploy_tree_with_options (OstreeSysroot *self, + const char *osname, + const char *revision, + GKeyFile *origin, + OstreeDeployment *provided_merge_deployment, + OstreeSysrootDeployTreeOpts *opts, + OstreeDeployment **out_new_deployment, + GCancellable *cancellable, + GError **error) { if (!_ostree_sysroot_ensure_writable (self, error)) return FALSE; g_autoptr(OstreeDeployment) deployment = NULL; - if (!sysroot_initialize_deployment (self, osname, revision, origin, override_kernel_argv, + if (!sysroot_initialize_deployment (self, osname, revision, origin, opts, &deployment, cancellable, error)) return FALSE; @@ -2897,6 +2951,39 @@ ostree_sysroot_deploy_tree (OstreeSysroot *self, return TRUE; } +/** + * ostree_sysroot_deploy_tree: + * @self: Sysroot + * @osname: (allow-none): osname to use for merge deployment + * @revision: Checksum to add + * @origin: (allow-none): Origin to use for upgrades + * @provided_merge_deployment: (allow-none): Use this deployment for merge path + * @override_kernel_argv: (allow-none) (array zero-terminated=1) (element-type utf8): Use these as kernel arguments; if %NULL, inherit options from provided_merge_deployment + * @out_new_deployment: (out): The new deployment path + * @cancellable: Cancellable + * @error: Error + * + * Older version of ostree_sysroot_stage_tree_with_options(). + * + * Since: 2018.5 + */ +gboolean +ostree_sysroot_deploy_tree (OstreeSysroot *self, + const char *osname, + const char *revision, + GKeyFile *origin, + OstreeDeployment *provided_merge_deployment, + char **override_kernel_argv, + OstreeDeployment **out_new_deployment, + GCancellable *cancellable, + GError **error) +{ + OstreeSysrootDeployTreeOpts opts = { .override_kernel_argv = override_kernel_argv }; + return ostree_sysroot_deploy_tree_with_options (self, osname, revision, origin, + provided_merge_deployment, &opts, + out_new_deployment, cancellable, error); +} + /* Serialize information about a deployment to a variant, used by the staging * code. */ @@ -2956,6 +3043,63 @@ _ostree_sysroot_deserialize_deployment_from_variant (GVariant *v, } +/** + * ostree_sysroot_stage_overlay_initrd: + * @self: Sysroot + * @fd: (transfer none): File descriptor to overlay initrd + * @out_checksum: (out) (transfer full): Overlay initrd checksum + * @cancellable: Cancellable + * @error: Error + * + * Stage an overlay initrd to be used in an upcoming deployment. Returns a checksum which + * can be passed to ostree_sysroot_deploy_tree_with_options() or + * ostree_sysroot_stage_tree_with_options() via the `overlay_initrds` array option. + * + * Since: 2020.7 + */ +gboolean +ostree_sysroot_stage_overlay_initrd (OstreeSysroot *self, + int fd, + char **out_checksum, + GCancellable *cancellable, + GError **error) +{ + g_return_val_if_fail (fd != -1, FALSE); + g_return_val_if_fail (out_checksum != NULL, FALSE); + + if (!glnx_shutil_mkdir_p_at (AT_FDCWD, _OSTREE_SYSROOT_RUNSTATE_STAGED_INITRDS_DIR, + 0755, cancellable, error)) + return FALSE; + + glnx_autofd int staged_initrds_dfd = -1; + if (!glnx_opendirat (AT_FDCWD, _OSTREE_SYSROOT_RUNSTATE_STAGED_INITRDS_DIR, FALSE, + &staged_initrds_dfd, error)) + return FALSE; + + g_auto(GLnxTmpfile) overlay_initrd = { 0, }; + if (!glnx_open_tmpfile_linkable_at (staged_initrds_dfd, ".", O_WRONLY | O_CLOEXEC, + &overlay_initrd, error)) + return FALSE; + + char checksum[_OSTREE_SHA256_STRING_LEN+1]; + { + g_autoptr(GOutputStream) output = g_unix_output_stream_new (overlay_initrd.fd, FALSE); + g_autoptr(GInputStream) input = g_unix_input_stream_new (fd, FALSE); + g_autofree guchar *digest = NULL; + if (!ot_gio_splice_get_checksum (output, input, &digest, cancellable, error)) + return FALSE; + ot_bin2hex (checksum, (guint8*)digest, _OSTREE_SHA256_DIGEST_LEN); + } + + if (!glnx_link_tmpfile_at (&overlay_initrd, GLNX_LINK_TMPFILE_REPLACE, + staged_initrds_dfd, checksum, error)) + return FALSE; + + *out_checksum = g_strdup (checksum); + return TRUE; +} + + /** * ostree_sysroot_stage_tree: * @self: Sysroot @@ -2968,8 +3112,7 @@ _ostree_sysroot_deserialize_deployment_from_variant (GVariant *v, * @cancellable: Cancellable * @error: Error * - * Like ostree_sysroot_deploy_tree(), but "finalization" only occurs at OS - * shutdown time. + * Older version of ostree_sysroot_stage_tree_with_options(). * * Since: 2018.5 */ @@ -2983,6 +3126,41 @@ ostree_sysroot_stage_tree (OstreeSysroot *self, OstreeDeployment **out_new_deployment, GCancellable *cancellable, GError **error) +{ + OstreeSysrootDeployTreeOpts opts = { .override_kernel_argv = override_kernel_argv }; + return ostree_sysroot_stage_tree_with_options (self, osname, revision, origin, + merge_deployment, &opts, + out_new_deployment, cancellable, error); +} + + +/** + * ostree_sysroot_stage_tree_with_options: + * @self: Sysroot + * @osname: (allow-none): osname to use for merge deployment + * @revision: Checksum to add + * @origin: (allow-none): Origin to use for upgrades + * @merge_deployment: (allow-none): Use this deployment for merge path + * @opts: Options + * @out_new_deployment: (out): The new deployment path + * @cancellable: Cancellable + * @error: Error + * + * Like ostree_sysroot_deploy_tree(), but "finalization" only occurs at OS + * shutdown time. + * + * Since: 2020.7 + */ +gboolean +ostree_sysroot_stage_tree_with_options (OstreeSysroot *self, + const char *osname, + const char *revision, + GKeyFile *origin, + OstreeDeployment *merge_deployment, + OstreeSysrootDeployTreeOpts *opts, + OstreeDeployment **out_new_deployment, + GCancellable *cancellable, + GError **error) { if (!_ostree_sysroot_ensure_writable (self, error)) return FALSE; @@ -3014,8 +3192,8 @@ ostree_sysroot_stage_tree (OstreeSysroot *self, } /* OSTREE_SYSROOT_DEBUG_TEST_STAGED_PATH */ g_autoptr(OstreeDeployment) deployment = NULL; - if (!sysroot_initialize_deployment (self, osname, revision, origin, override_kernel_argv, - &deployment, cancellable, error)) + if (!sysroot_initialize_deployment (self, osname, revision, origin, opts, &deployment, + cancellable, error)) return FALSE; /* Write out the origin file using the sepolicy from the non-merged root for @@ -3050,9 +3228,12 @@ ostree_sysroot_stage_tree (OstreeSysroot *self, g_variant_builder_add (builder, "{sv}", "merge-deployment", serialize_deployment_to_variant (merge_deployment)); - if (override_kernel_argv) + if (opts && opts->override_kernel_argv) g_variant_builder_add (builder, "{sv}", "kargs", - g_variant_new_strv ((const char *const*)override_kernel_argv, -1)); + g_variant_new_strv ((const char *const*)opts->override_kernel_argv, -1)); + if (opts && opts->overlay_initrds) + g_variant_builder_add (builder, "{sv}", "overlay-initrds", + g_variant_new_strv ((const char *const*)opts->overlay_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-private.h b/src/libostree/ostree-sysroot-private.h index 1af2fd27ca..318b0b199e 100644 --- a/src/libostree/ostree-sysroot-private.h +++ b/src/libostree/ostree-sysroot-private.h @@ -84,10 +84,14 @@ struct OstreeSysroot { /* We keep some transient state in /run */ #define _OSTREE_SYSROOT_RUNSTATE_STAGED "/run/ostree/staged-deployment" #define _OSTREE_SYSROOT_RUNSTATE_STAGED_LOCKED "/run/ostree/staged-deployment-locked" +#define _OSTREE_SYSROOT_RUNSTATE_STAGED_INITRDS_DIR "/run/ostree/staged-initrds/" #define _OSTREE_SYSROOT_DEPLOYMENT_RUNSTATE_DIR "/run/ostree/deployment-state/" #define _OSTREE_SYSROOT_DEPLOYMENT_RUNSTATE_FLAG_DEVELOPMENT "unlocked-development" #define _OSTREE_SYSROOT_DEPLOYMENT_RUNSTATE_FLAG_TRANSIENT "unlocked-transient" +#define _OSTREE_SYSROOT_BOOT_INITRAMFS_OVERLAYS "ostree/initramfs-overlays" +#define _OSTREE_SYSROOT_INITRAMFS_OVERLAYS "boot/" _OSTREE_SYSROOT_BOOT_INITRAMFS_OVERLAYS + gboolean _ostree_sysroot_ensure_writable (OstreeSysroot *self, GError **error); diff --git a/src/libostree/ostree-sysroot.c b/src/libostree/ostree-sysroot.c index e412ea4da7..e0813b5544 100644 --- a/src/libostree/ostree-sysroot.c +++ b/src/libostree/ostree-sysroot.c @@ -815,6 +815,24 @@ list_deployments_process_one_boot_entry (OstreeSysroot *self, return FALSE; ostree_deployment_set_bootconfig (deployment, config); + char **overlay_initrds = ostree_bootconfig_parser_get_overlay_initrds (config); + g_autoptr(GPtrArray) initrds_chksums = NULL; + for (char **it = overlay_initrds; it && *it; it++) + { + const char *basename = glnx_basename (*it); + if (strlen (basename) != (_OSTREE_SHA256_STRING_LEN + strlen (".img"))) + return glnx_throw (error, "Malformed overlay initrd filename: %s", basename); + + if (!initrds_chksums) /* lazy init */ + initrds_chksums = g_ptr_array_new_full (g_strv_length (overlay_initrds), g_free); + g_ptr_array_add (initrds_chksums, g_strndup (basename, _OSTREE_SHA256_STRING_LEN)); + } + + if (initrds_chksums) + { + g_ptr_array_add (initrds_chksums, NULL); + _ostree_deployment_set_overlay_initrds (deployment, (char**)initrds_chksums->pdata); + } g_ptr_array_add (inout_deployments, g_object_ref (deployment)); return TRUE; @@ -967,8 +985,10 @@ _ostree_sysroot_reload_staged (OstreeSysroot *self, /* Parse it */ g_autoptr(GVariant) target = NULL; g_autofree char **kargs = NULL; + g_autofree char **overlay_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, "overlay-initrds", "^a&s", &overlay_initrds); if (target) { g_autoptr(OstreeDeployment) staged = @@ -980,6 +1000,8 @@ _ostree_sysroot_reload_staged (OstreeSysroot *self, if (!load_origin (self, staged, NULL, error)) return FALSE; + _ostree_deployment_set_overlay_initrds (staged, overlay_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 d9f5a54685..3a3b6a774a 100644 --- a/src/libostree/ostree-sysroot.h +++ b/src/libostree/ostree-sysroot.h @@ -186,6 +186,21 @@ gboolean ostree_sysroot_write_deployments_with_options (OstreeSysroot *self, GCancellable *cancellable, GError **error); +_OSTREE_PUBLIC +gboolean ostree_sysroot_stage_overlay_initrd (OstreeSysroot *self, + int fd, + char **out_checksum, + GCancellable *cancellable, + GError **error); + +typedef struct { + gboolean unused_bools[8]; + int unused_ints[8]; + char **override_kernel_argv; + char **overlay_initrds; + gpointer unused_ptrs[6]; +} OstreeSysrootDeployTreeOpts; + _OSTREE_PUBLIC gboolean ostree_sysroot_deploy_tree (OstreeSysroot *self, const char *osname, @@ -197,6 +212,17 @@ gboolean ostree_sysroot_deploy_tree (OstreeSysroot *self, GCancellable *cancellable, GError **error); +_OSTREE_PUBLIC +gboolean ostree_sysroot_deploy_tree_with_options (OstreeSysroot *self, + const char *osname, + const char *revision, + GKeyFile *origin, + OstreeDeployment *provided_merge_deployment, + OstreeSysrootDeployTreeOpts *opts, + OstreeDeployment **out_new_deployment, + GCancellable *cancellable, + GError **error); + _OSTREE_PUBLIC gboolean ostree_sysroot_stage_tree (OstreeSysroot *self, const char *osname, @@ -208,6 +234,18 @@ gboolean ostree_sysroot_stage_tree (OstreeSysroot *self, GCancellable *cancellable, GError **error); +_OSTREE_PUBLIC +gboolean ostree_sysroot_stage_tree_with_options (OstreeSysroot *self, + const char *osname, + const char *revision, + GKeyFile *origin, + OstreeDeployment *merge_deployment, + OstreeSysrootDeployTreeOpts *opts, + OstreeDeployment **out_new_deployment, + GCancellable *cancellable, + GError **error); + + _OSTREE_PUBLIC gboolean ostree_sysroot_deployment_set_mutable (OstreeSysroot *self, OstreeDeployment *deployment, diff --git a/src/ostree/ot-admin-builtin-deploy.c b/src/ostree/ot-admin-builtin-deploy.c index bcece3f655..8156cc153c 100644 --- a/src/ostree/ot-admin-builtin-deploy.c +++ b/src/ostree/ot-admin-builtin-deploy.c @@ -44,6 +44,7 @@ static gboolean opt_kernel_proc_cmdline; static char *opt_osname; static char *opt_origin_path; static gboolean opt_kernel_arg_none; +static char **opt_overlay_initrds; static GOptionEntry options[] = { { "os", 0, 0, G_OPTION_ARG_STRING, &opt_osname, "Use a different operating system root than the current one", "OSNAME" }, @@ -59,6 +60,7 @@ static GOptionEntry options[] = { { "karg", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_kernel_argv, "Set kernel argument, like root=/dev/sda1; this overrides any earlier argument with the same name", "NAME=VALUE" }, { "karg-append", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_kernel_argv_append, "Append kernel argument; useful with e.g. console= that can be used multiple times", "NAME=VALUE" }, { "karg-none", 0, 0, G_OPTION_ARG_NONE, &opt_kernel_arg_none, "Do not import kernel arguments", NULL }, + { "overlay-initrd", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_overlay_initrds, "Overlay iniramfs file", "FILE" }, { NULL } }; @@ -167,24 +169,76 @@ ot_admin_builtin_deploy (int argc, char **argv, OstreeCommandInvocation *invocat ostree_kernel_args_append_argv (kargs, opt_kernel_argv_append); } - g_autoptr(OstreeDeployment) new_deployment = NULL; + g_autoptr(GPtrArray) overlay_initrd_chksums = NULL; + for (char **it = opt_overlay_initrds; it && *it; it++) + { + const char *path = *it; + + glnx_autofd int fd = -1; + if (!glnx_openat_rdonly (AT_FDCWD, path, TRUE, &fd, error)) + return FALSE; + + g_autofree char *chksum = NULL; + if (!ostree_sysroot_stage_overlay_initrd (sysroot, fd, &chksum, cancellable, error)) + return FALSE; + + if (!overlay_initrd_chksums) + overlay_initrd_chksums = g_ptr_array_new_full (g_strv_length (opt_overlay_initrds), g_free); + g_ptr_array_add (overlay_initrd_chksums, g_steal_pointer (&chksum)); + } + + if (overlay_initrd_chksums) + g_ptr_array_add (overlay_initrd_chksums, NULL); + g_auto(GStrv) kargs_strv = kargs ? ostree_kernel_args_to_strv (kargs) : NULL; + + OstreeSysrootDeployTreeOpts opts = { + .override_kernel_argv = kargs_strv, + .overlay_initrds = overlay_initrd_chksums ? (char**)overlay_initrd_chksums->pdata : NULL, + }; + + g_autoptr(OstreeDeployment) new_deployment = NULL; if (opt_stage) { if (opt_retain_pending || opt_retain_rollback) return glnx_throw (error, "--stage cannot currently be combined with --retain arguments"); if (opt_not_as_default) return glnx_throw (error, "--stage cannot currently be combined with --not-as-default"); - if (!ostree_sysroot_stage_tree (sysroot, opt_osname, revision, origin, merge_deployment, - kargs_strv, &new_deployment, cancellable, error)) - return FALSE; + /* use old API if we can to exercise it in CI */ + if (!overlay_initrd_chksums) + { + if (!ostree_sysroot_stage_tree (sysroot, opt_osname, revision, origin, + merge_deployment, kargs_strv, &new_deployment, + cancellable, error)) + return FALSE; + } + else + { + if (!ostree_sysroot_stage_tree_with_options (sysroot, opt_osname, revision, + origin, merge_deployment, &opts, + &new_deployment, cancellable, error)) + return FALSE; + } g_assert (new_deployment); } else { - if (!ostree_sysroot_deploy_tree (sysroot, opt_osname, revision, origin, merge_deployment, - kargs_strv, &new_deployment, cancellable, error)) - return FALSE; + /* use old API if we can to exercise it in CI */ + if (!overlay_initrd_chksums) + { + if (!ostree_sysroot_deploy_tree (sysroot, opt_osname, revision, origin, + merge_deployment, kargs_strv, &new_deployment, + cancellable, error)) + return FALSE; + } + else + { + if (!ostree_sysroot_deploy_tree_with_options (sysroot, opt_osname, revision, + origin, merge_deployment, &opts, + &new_deployment, cancellable, + error)) + return FALSE; + } g_assert (new_deployment); OstreeSysrootSimpleWriteDeploymentFlags flags = OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_NO_CLEAN; diff --git a/tests/kolainst/destructive/overlay-initrds.sh b/tests/kolainst/destructive/overlay-initrds.sh new file mode 100755 index 0000000000..b24d2d08f6 --- /dev/null +++ b/tests/kolainst/destructive/overlay-initrds.sh @@ -0,0 +1,96 @@ +#!/bin/bash +# https://github.com/ostreedev/ostree/issues/1667 +set -xeuo pipefail + +. ${KOLA_EXT_DATA}/libinsttest.sh + +# we don't just use `rpm-ostree initramfs-etc` here because we want to be able +# to test more things + +# dracut prints all the cmdline args, including those from /etc/cmdline.d, so +# the way we test that an initrd was included is to just add kargs there and +# grep for it +create_initrd_with_dracut_karg() { + local karg=$1; shift + local d + d=$(mktemp -dp /var/tmp) + mkdir -p "${d}/etc/cmdline.d" + echo "${karg}" > "${d}/etc/cmdline.d/${karg}.conf" + echo "etc/cmdline.d/${karg}.conf" | \ + cpio -D "${d}" -o -H newc --reproducible > "/var/tmp/${karg}.img" +} + +check_for_dracut_karg() { + local karg=$1; shift + # https://github.com/dracutdevs/dracut/blob/38ea7e821b/modules.d/98dracut-systemd/dracut-cmdline.sh#L17 + journalctl -b 0 -t dracut-cmdline \ + --grep "Using kernel command line parameters:.* ${karg} " +} + +case "${AUTOPKGTEST_REBOOT_MARK:-}" in + "") + create_initrd_with_dracut_karg ostree.test1 + # let's use the deploy API first + ostree admin deploy "${host_refspec}" \ + --overlay-initrd /var/tmp/ostree.test1.img + /tmp/autopkgtest-reboot "2" + ;; + 2) + # verify that ostree.test1 is here + check_for_dracut_karg ostree.test1 + img_sha=$(sha256sum < /var/tmp/ostree.test1.img | cut -f 1 -d ' ') + test -f "/boot/ostree/initramfs-overlays/${img_sha}.img" + + # now let's change to ostree.test2 + create_initrd_with_dracut_karg ostree.test2 + + # let's use the staging API this time + ostree admin deploy "${host_refspec}" --stage \ + --overlay-initrd /var/tmp/ostree.test2.img + /tmp/autopkgtest-reboot "3" + ;; + 3) + # verify that ostree.test1 is gone, but ostree.test2 is here + if check_for_dracut_karg ostree.test1; then + assert_not_reached "Unexpected ostree.test1 karg found" + fi + check_for_dracut_karg ostree.test2 + + # both the new and old initrds should still be there since they're + # referenced in the BLS + test1_sha=$(sha256sum < /var/tmp/ostree.test1.img | cut -f 1 -d ' ') + test2_sha=$(sha256sum < /var/tmp/ostree.test2.img | cut -f 1 -d ' ') + test -f "/boot/ostree/initramfs-overlays/${test1_sha}.img" + test -f "/boot/ostree/initramfs-overlays/${test2_sha}.img" + + # OK, now let's deploy an identical copy of this test + ostree admin deploy "${host_refspec}" \ + --overlay-initrd /var/tmp/ostree.test2.img + + # Now the deployment with ostree.test1 should've been GC'ed; check that its + # initrd was cleaned up + test ! -f "/boot/ostree/initramfs-overlays/${test1_sha}.img" + test -f "/boot/ostree/initramfs-overlays/${test2_sha}.img" + + # deploy again to check that no bootconfig swap was needed; this verifies + # that deployment overlay initrds can be successfully compared + ostree admin deploy "${host_refspec}" \ + --overlay-initrd /var/tmp/ostree.test2.img |& tee /tmp/out.txt + assert_file_has_content /tmp/out.txt 'bootconfig swap: no' + + # finally, let's check that we can overlay multiple initrds + ostree admin deploy "${host_refspec}" --stage \ + --overlay-initrd /var/tmp/ostree.test1.img \ + --overlay-initrd /var/tmp/ostree.test2.img + /tmp/autopkgtest-reboot "4" + ;; + 4) + check_for_dracut_karg ostree.test1 + check_for_dracut_karg ostree.test2 + test1_sha=$(sha256sum < /var/tmp/ostree.test1.img | cut -f 1 -d ' ') + test2_sha=$(sha256sum < /var/tmp/ostree.test2.img | cut -f 1 -d ' ') + test -f "/boot/ostree/initramfs-overlays/${test1_sha}.img" + test -f "/boot/ostree/initramfs-overlays/${test2_sha}.img" + ;; + *) fatal "Unexpected AUTOPKGTEST_REBOOT_MARK=${AUTOPKGTEST_REBOOT_MARK}" ;; +esac