From 04de1dd66e09a939175b9564933322b4fe799b5a Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Wed, 8 Nov 2023 17:19:19 -0500 Subject: [PATCH] sysroot: Stabilize deployment finalization, add API It's about time we do this; deployment finalization locking is a useful feature. An absolutely key thing here is that we've slowly been moving towards the deployments as the primary "source of truth". Specifically in bootc for example, we will GC container images not referenced by a deployment. This is then neecessary to support a "pull but don't apply automatically" model. Closes: https://github.com/ostreedev/ostree/issues/3025 --- apidoc/ostree-sections.txt | 1 + src/libostree/libostree-devel.sym | 1 + src/libostree/ostree-deployment-private.h | 1 + src/libostree/ostree-deployment.c | 13 ++++++++++++ src/libostree/ostree-deployment.h | 2 ++ src/libostree/ostree-sysroot-deploy.c | 23 +++++++++++++++++---- src/libostree/ostree-sysroot-private.h | 3 +++ src/libostree/ostree-sysroot.c | 2 ++ src/libostree/ostree-sysroot.h | 5 ++++- src/ostree/ot-admin-builtin-deploy.c | 13 +++++++++--- src/ostree/ot-admin-builtin-status.c | 4 +++- tests/kolainst/destructive/staged-deploy.sh | 6 +++++- 12 files changed, 64 insertions(+), 10 deletions(-) diff --git a/apidoc/ostree-sections.txt b/apidoc/ostree-sections.txt index 7a8966421f..4deba32229 100644 --- a/apidoc/ostree-sections.txt +++ b/apidoc/ostree-sections.txt @@ -191,6 +191,7 @@ ostree_deployment_get_origin_relpath ostree_deployment_get_unlocked ostree_deployment_is_pinned ostree_deployment_is_staged +ostree_deployment_is_finalization_locked ostree_deployment_set_index ostree_deployment_set_bootserial ostree_deployment_set_bootconfig diff --git a/src/libostree/libostree-devel.sym b/src/libostree/libostree-devel.sym index 02d2ac2950..1d5d50337a 100644 --- a/src/libostree/libostree-devel.sym +++ b/src/libostree/libostree-devel.sym @@ -23,6 +23,7 @@ LIBOSTREE_2023.8 { global: ostree_sysroot_update_post_copy; + ostree_deployment_is_finalization_locked; } LIBOSTREE_2023.4; /* Stub section for the stable release *after* this development one; don't diff --git a/src/libostree/ostree-deployment-private.h b/src/libostree/ostree-deployment-private.h index 2a28bffd92..f6766c39bd 100644 --- a/src/libostree/ostree-deployment-private.h +++ b/src/libostree/ostree-deployment-private.h @@ -51,6 +51,7 @@ struct _OstreeDeployment GKeyFile *origin; OstreeDeploymentUnlockedState unlocked; gboolean staged; + gboolean finalization_locked; char **overlay_initrds; char *overlay_initrds_id; }; diff --git a/src/libostree/ostree-deployment.c b/src/libostree/ostree-deployment.c index 1480d74656..9b7cdddd59 100644 --- a/src/libostree/ostree-deployment.c +++ b/src/libostree/ostree-deployment.c @@ -461,3 +461,16 @@ ostree_deployment_is_staged (OstreeDeployment *self) { return self->staged; } + +/** + * ostree_deployment_is_finalization_locked: + * @self: Deployment + * + * Returns: `TRUE` if deployment is queued to be "finalized" at shutdown time, but requires + * additional action. Since: 2023.7 + */ +gboolean +ostree_deployment_is_finalization_locked (OstreeDeployment *self) +{ + return self->finalization_locked; +} diff --git a/src/libostree/ostree-deployment.h b/src/libostree/ostree-deployment.h index 0d4a5d7b02..0536d9810c 100644 --- a/src/libostree/ostree-deployment.h +++ b/src/libostree/ostree-deployment.h @@ -71,6 +71,8 @@ GKeyFile *ostree_deployment_get_origin (OstreeDeployment *self); _OSTREE_PUBLIC gboolean ostree_deployment_is_staged (OstreeDeployment *self); _OSTREE_PUBLIC +gboolean ostree_deployment_is_finalization_locked (OstreeDeployment *self); +_OSTREE_PUBLIC gboolean ostree_deployment_is_pinned (OstreeDeployment *self); _OSTREE_PUBLIC diff --git a/src/libostree/ostree-sysroot-deploy.c b/src/libostree/ostree-sysroot-deploy.c index 116143c160..5cd6a9f66a 100644 --- a/src/libostree/ostree-sysroot-deploy.c +++ b/src/libostree/ostree-sysroot-deploy.c @@ -3657,6 +3657,10 @@ ostree_sysroot_stage_tree_with_options (OstreeSysroot *self, const char *osname, g_autoptr (GVariantBuilder) builder = g_variant_builder_new ((GVariantType *)"a{sv}"); g_variant_builder_add (builder, "{sv}", "target", serialize_deployment_to_variant (deployment)); + if (opts->locked) + g_variant_builder_add (builder, "{sv}", _OSTREE_SYSROOT_STAGED_KEY_LOCKED, + g_variant_new_boolean (TRUE)); + if (merge_deployment) g_variant_builder_add (builder, "{sv}", "merge-deployment", serialize_deployment_to_variant (merge_deployment)); @@ -3722,11 +3726,22 @@ _ostree_sysroot_finalize_staged_inner (OstreeSysroot *self, GCancellable *cancel } /* Check if finalization is locked. */ - if (!glnx_fstatat_allow_noent (AT_FDCWD, _OSTREE_SYSROOT_RUNSTATE_STAGED_LOCKED, NULL, 0, error)) - return FALSE; - if (errno == 0) + gboolean locked = false; + (void)g_variant_lookup (self->staged_deployment_data, _OSTREE_SYSROOT_STAGED_KEY_LOCKED, "b", + &locked); + if (locked) + g_debug ("staged is locked via metadata"); + else + { + if (!glnx_fstatat_allow_noent (AT_FDCWD, _OSTREE_SYSROOT_RUNSTATE_STAGED_LOCKED, NULL, 0, + error)) + return FALSE; + if (errno == 0) + locked = TRUE; + } + if (locked) { - ot_journal_print (LOG_INFO, "Not finalizing; found " _OSTREE_SYSROOT_RUNSTATE_STAGED_LOCKED); + ot_journal_print (LOG_INFO, "Not finalizing; deployment is locked for finalization"); return TRUE; } diff --git a/src/libostree/ostree-sysroot-private.h b/src/libostree/ostree-sysroot-private.h index 8e6945b293..5be07c24ba 100644 --- a/src/libostree/ostree-sysroot-private.h +++ b/src/libostree/ostree-sysroot-private.h @@ -93,6 +93,9 @@ struct OstreeSysroot OstreeSysrootDebugFlags debug_flags; }; +/* Key in staged deployment variant for finalization locking */ +#define _OSTREE_SYSROOT_STAGED_KEY_LOCKED "locked" + #define OSTREE_SYSROOT_LOCKFILE "ostree/lock" /* We keep some transient state in /run */ #define _OSTREE_SYSROOT_RUNSTATE_STAGED "/run/ostree/staged-deployment" diff --git a/src/libostree/ostree-sysroot.c b/src/libostree/ostree-sysroot.c index 91b63f945a..62adc6221c 100644 --- a/src/libostree/ostree-sysroot.c +++ b/src/libostree/ostree-sysroot.c @@ -1106,6 +1106,8 @@ _ostree_sysroot_reload_staged (OstreeSysroot *self, GError **error) * canonical "staged_deployment" reference. */ self->staged_deployment->staged = TRUE; + (void)g_variant_dict_lookup (staged_deployment_dict, _OSTREE_SYSROOT_STAGED_KEY_LOCKED, + "b", &self->staged_deployment->finalization_locked); } } diff --git a/src/libostree/ostree-sysroot.h b/src/libostree/ostree-sysroot.h index b7ba6ac97b..7a49ddb0e6 100644 --- a/src/libostree/ostree-sysroot.h +++ b/src/libostree/ostree-sysroot.h @@ -178,7 +178,10 @@ gboolean ostree_sysroot_stage_overlay_initrd (OstreeSysroot *self, int fd, char typedef struct { - gboolean unused_bools[8]; + /* If set to true, then this deployment will be staged but "locked" and not automatically applied + * on reboot. */ + gboolean locked; + gboolean unused_bools[7]; int unused_ints[8]; char **override_kernel_argv; char **overlay_initrds; diff --git a/src/ostree/ot-admin-builtin-deploy.c b/src/ostree/ot-admin-builtin-deploy.c index c0faaab908..69a543362f 100644 --- a/src/ostree/ot-admin-builtin-deploy.c +++ b/src/ostree/ot-admin-builtin-deploy.c @@ -60,7 +60,7 @@ static GOptionEntry options[] = { "Do not apply configuration (/etc and kernel arguments) from booted deployment", NULL }, { "retain", 0, 0, G_OPTION_ARG_NONE, &opt_retain, "Do not delete previous deployments", NULL }, { "stage", 0, 0, G_OPTION_ARG_NONE, &opt_stage, "Complete deployment at OS shutdown", NULL }, - { "lock-finalization", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &opt_lock_finalization, + { "lock-finalization", 0, 0, G_OPTION_ARG_NONE, &opt_lock_finalization, "Prevent automatic deployment finalization on shutdown", NULL }, { "retain-pending", 0, 0, G_OPTION_ARG_NONE, &opt_retain_pending, "Do not delete pending deployments", NULL }, @@ -123,6 +123,10 @@ ot_admin_builtin_deploy (int argc, char **argv, OstreeCommandInvocation *invocat return FALSE; } + // Locking implies staging + if (opt_lock_finalization) + opt_stage = TRUE; + const char *refspec = argv[1]; OstreeRepo *repo = ostree_sysroot_repo (sysroot); @@ -236,6 +240,7 @@ ot_admin_builtin_deploy (int argc, char **argv, OstreeCommandInvocation *invocat g_auto (GStrv) kargs_strv = kargs ? ostree_kernel_args_to_strv (kargs) : NULL; OstreeSysrootDeployTreeOpts opts = { + .locked = opt_lock_finalization, .override_kernel_argv = kargs_strv, .overlay_initrds = overlay_initrd_chksums ? (char **)overlay_initrd_chksums->pdata : NULL, }; @@ -247,9 +252,11 @@ ot_admin_builtin_deploy (int argc, char **argv, OstreeCommandInvocation *invocat 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"); - /* touch file *before* we stage to avoid races */ + /* For compatibility with older versions of ostree, also write this legacy file. + * This can likely be safely deleted in the middle of 2024 say. */ if (opt_lock_finalization) { + g_debug ("Writing legacy finalization lockfile"); if (!glnx_shutil_mkdir_p_at (AT_FDCWD, dirname (strdupa (_OSTREE_SYSROOT_RUNSTATE_STAGED_LOCKED)), 0755, cancellable, error)) @@ -262,7 +269,7 @@ ot_admin_builtin_deploy (int argc, char **argv, OstreeCommandInvocation *invocat _OSTREE_SYSROOT_RUNSTATE_STAGED_LOCKED); } /* use old API if we can to exercise it in CI */ - if (!overlay_initrd_chksums) + if (!(overlay_initrd_chksums || opt_lock_finalization)) { if (!ostree_sysroot_stage_tree (sysroot, opt_osname, revision, origin, merge_deployment, kargs_strv, &new_deployment, cancellable, error)) diff --git a/src/ostree/ot-admin-builtin-status.c b/src/ostree/ot-admin-builtin-status.c index 3addfd1615..f9fa19b85c 100644 --- a/src/ostree/ot-admin-builtin-status.c +++ b/src/ostree/ot-admin-builtin-status.c @@ -98,7 +98,9 @@ deployment_print_status (OstreeSysroot *sysroot, OstreeRepo *repo, OstreeDeploym GKeyFile *origin = ostree_deployment_get_origin (deployment); const char *deployment_status = ""; - if (ostree_deployment_is_staged (deployment)) + if (ostree_deployment_is_finalization_locked (deployment)) + deployment_status = " (finalization locked)"; + else if (ostree_deployment_is_staged (deployment)) deployment_status = " (staged)"; else if (is_pending) deployment_status = " (pending)"; diff --git a/tests/kolainst/destructive/staged-deploy.sh b/tests/kolainst/destructive/staged-deploy.sh index ff6f8d7a8d..cc5bf9a4ac 100755 --- a/tests/kolainst/destructive/staged-deploy.sh +++ b/tests/kolainst/destructive/staged-deploy.sh @@ -83,7 +83,11 @@ EOF test '!' -f /run/ostree/staged-deployment test '!' -f /run/ostree/staged-deployment - ostree admin deploy --stage staged-deploy --lock-finalization + ostree admin status > status.txt + assert_not_file_has_content status.txt 'finalization locked' + ostree admin deploy staged-deploy --lock-finalization + ostree admin status > status.txt + assert_file_has_content status.txt 'finalization locked' test -f /run/ostree/staged-deployment test -f /run/ostree/staged-deployment-locked # check that we can cleanup the staged deployment