diff --git a/src/daemon/org.projectatomic.rpmostree1.xml b/src/daemon/org.projectatomic.rpmostree1.xml index 6ae04a4590..2c6f551791 100644 --- a/src/daemon/org.projectatomic.rpmostree1.xml +++ b/src/daemon/org.projectatomic.rpmostree1.xml @@ -339,6 +339,10 @@ "idempotent-layering" (type 'b') Don't error out on requests in install-* or uninstall-* modifiers that are already satisfied. + "lock-finalization" (type 'b') + Prevent automatic deployment finalization on shutdown. + Clients must manually call FinalizeDeployment() when ready + to apply the update and reboot. --> @@ -347,6 +351,16 @@ + + + + + diff --git a/src/daemon/rpmostree-sysroot-core.h b/src/daemon/rpmostree-sysroot-core.h index c136175f8e..585a1b1934 100644 --- a/src/daemon/rpmostree-sysroot-core.h +++ b/src/daemon/rpmostree-sysroot-core.h @@ -31,6 +31,10 @@ /* The legacy dir, which we will just delete if we find it */ #define RPMOSTREE_OLD_TMP_ROOTFS_DIR "extensions/rpmostree/commit" +/* Really, this is an OSTree API, but let's consider it hidden for now like the + * /run/ostree/staged-deployment path and company. */ +#define _OSTREE_SYSROOT_RUNSTATE_STAGED_LOCKED "/run/ostree/staged-deployment-locked" + gboolean rpmostree_syscore_cleanup (OstreeSysroot *sysroot, OstreeRepo *repo, diff --git a/src/daemon/rpmostree-sysroot-upgrader.c b/src/daemon/rpmostree-sysroot-upgrader.c index 27551a10ef..26b9516f03 100644 --- a/src/daemon/rpmostree-sysroot-upgrader.c +++ b/src/daemon/rpmostree-sysroot-upgrader.c @@ -1299,6 +1299,21 @@ rpmostree_sysroot_upgrader_deploy (RpmOstreeSysrootUpgrader *self, if (use_staging) { + /* touch file *before* we stage to avoid races */ + if (self->flags & RPMOSTREE_SYSROOT_UPGRADER_FLAGS_LOCK_FINALIZATION) + { + if (!glnx_shutil_mkdir_p_at (AT_FDCWD, + dirname (strdupa (_OSTREE_SYSROOT_RUNSTATE_STAGED_LOCKED)), + 0755, cancellable, error)) + return FALSE; + + glnx_autofd int fd = open (_OSTREE_SYSROOT_RUNSTATE_STAGED_LOCKED, + O_CREAT | O_WRONLY | O_NOCTTY | O_CLOEXEC, 0640); + if (fd == -1) + return glnx_throw_errno_prefix (error, "touch(%s)", + _OSTREE_SYSROOT_RUNSTATE_STAGED_LOCKED); + } + g_auto(RpmOstreeProgress) task = { 0, }; rpmostree_output_task_begin (&task, "Staging deployment"); if (!ostree_sysroot_stage_tree (self->sysroot, self->osname, @@ -1395,6 +1410,9 @@ rpmostree_sysroot_upgrader_flags_get_type (void) { RPMOSTREE_SYSROOT_UPGRADER_FLAGS_SYNTHETIC_PULL, "RPMOSTREE_SYSROOT_UPGRADER_FLAGS_SYNTHETIC_PULL", "synthetic-pull" }, + { RPMOSTREE_SYSROOT_UPGRADER_FLAGS_LOCK_FINALIZATION, + "RPMOSTREE_SYSROOT_UPGRADER_FLAGS_LOCK_FINALIZATION", + "lock-finalization" }, }; GType g_define_type_id = g_flags_register_static (g_intern_static_string ("RpmOstreeSysrootUpgraderFlags"), values); diff --git a/src/daemon/rpmostree-sysroot-upgrader.h b/src/daemon/rpmostree-sysroot-upgrader.h index 8cb9f83df5..c7ccca841e 100644 --- a/src/daemon/rpmostree-sysroot-upgrader.h +++ b/src/daemon/rpmostree-sysroot-upgrader.h @@ -43,6 +43,7 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC (RpmOstreeSysrootUpgrader, g_object_unref) * @RPMOSTREE_SYSROOT_UPGRADER_FLAGS_DRY_RUN: Don't deploy new base. If layering packages, only print the transaction * @RPMOSTREE_SYSROOT_UPGRADER_FLAGS_PKGCACHE_ONLY: Don't try to update cached packages. * @RPMOSTREE_SYSROOT_UPGRADER_FLAGS_SYNTHETIC_PULL: Don't actually pull, just resolve ref and timestamp check + * @RPMOSTREE_SYSROOT_UPGRADER_FLAGS_LOCK_FINALIZATION: Prevent deployment finalization on shutdown * * Flags controlling operation of an #RpmOstreeSysrootUpgrader. */ @@ -54,6 +55,7 @@ typedef enum { RPMOSTREE_SYSROOT_UPGRADER_FLAGS_DRY_RUN = (1 << 3), RPMOSTREE_SYSROOT_UPGRADER_FLAGS_PKGCACHE_ONLY = (1 << 4), RPMOSTREE_SYSROOT_UPGRADER_FLAGS_SYNTHETIC_PULL = (1 << 5), + RPMOSTREE_SYSROOT_UPGRADER_FLAGS_LOCK_FINALIZATION = (1 << 6), } RpmOstreeSysrootUpgraderFlags; /* _NONE means we're doing pure ostree, no client-side computation. diff --git a/src/daemon/rpmostreed-deployment-utils.c b/src/daemon/rpmostreed-deployment-utils.c index 3e86cbafef..f7563dcb2f 100644 --- a/src/daemon/rpmostreed-deployment-utils.c +++ b/src/daemon/rpmostreed-deployment-utils.c @@ -373,7 +373,13 @@ rpmostreed_deployment_generate_variant (OstreeSysroot *sysroot, g_variant_dict_insert (&dict, "live-replaced", "s", live_replaced); if (ostree_deployment_is_staged (deployment)) - g_variant_dict_insert (&dict, "staged", "b", TRUE); + { + g_variant_dict_insert (&dict, "staged", "b", TRUE); + if (!glnx_fstatat_allow_noent (AT_FDCWD, _OSTREE_SYSROOT_RUNSTATE_STAGED_LOCKED, + NULL, 0, error)) + return FALSE; + g_variant_dict_insert (&dict, "finalization-locked", "b", errno == 0); + } if (refspec) g_variant_dict_insert (&dict, "origin", "s", refspec); diff --git a/src/daemon/rpmostreed-os.c b/src/daemon/rpmostreed-os.c index 915947b3ec..1260935405 100644 --- a/src/daemon/rpmostreed-os.c +++ b/src/daemon/rpmostreed-os.c @@ -974,6 +974,65 @@ os_handle_modify_yum_repo (RPMOSTreeOS *interface, return TRUE; } +static void +on_finalize_done (RpmostreedTransaction *transaction, RpmostreedOS *self) +{ + g_autoptr(GError) local_error = NULL; + if (!rpmostreed_os_load_internals (self, &local_error)) + { + sd_journal_print (LOG_WARNING, "Failed to reload internals: %s", + local_error->message); + } +} + +static gboolean +os_handle_finalize_deployment (RPMOSTreeOS *interface, + GDBusMethodInvocation *invocation, + GVariant *arg_options) +{ + RpmostreedOS *self = RPMOSTREED_OS (interface); + g_autoptr(GCancellable) cancellable = g_cancellable_new (); + GError *local_error = NULL; + + /* try to merge with an existing transaction, otherwise start a new one */ + RpmostreedSysroot *rsysroot = rpmostreed_sysroot_get (); + + glnx_unref_object RpmostreedTransaction *transaction = NULL; + if (!rpmostreed_sysroot_prep_for_txn (rsysroot, invocation, &transaction, &local_error)) + goto out; + if (transaction) + goto out; + + g_autoptr(OstreeSysroot) sysroot = NULL; + if (!rpmostreed_sysroot_load_state (rsysroot, cancellable, &sysroot, NULL, &local_error)) + goto out; + + const char *osname = rpmostree_os_get_name (interface); + + transaction = rpmostreed_transaction_new_finalize (invocation, sysroot, osname, + arg_options, cancellable, &local_error); + if (transaction == NULL) + goto out; + + rpmostreed_sysroot_set_txn (rsysroot, transaction); + + /* Really, we just want to refresh `DefaultDeployment`, but meh... */ + g_signal_connect (transaction, "closed", G_CALLBACK (on_finalize_done), self); + +out: + if (local_error != NULL) + { + g_dbus_method_invocation_take_error (invocation, local_error); + } + else + { + const char *client_address = rpmostreed_transaction_get_client_address (transaction); + rpmostree_os_complete_finalize_deployment (interface, invocation, client_address); + } + + return TRUE; +} + /* This is an older variant of Cleanup, kept for backcompat */ static gboolean os_handle_clear_rollback_target (RPMOSTreeOS *interface, @@ -1730,6 +1789,7 @@ rpmostreed_os_iface_init (RPMOSTreeOSIface *iface) iface->handle_rollback = os_handle_rollback; iface->handle_set_initramfs_state = os_handle_set_initramfs_state; iface->handle_update_deployment = os_handle_update_deployment; + iface->handle_finalize_deployment = os_handle_finalize_deployment; /* legacy cleanup API; superseded by Cleanup() */ iface->handle_clear_rollback_target = os_handle_clear_rollback_target; /* legacy deployment change API; superseded by UpdateDeployment() */ diff --git a/src/daemon/rpmostreed-transaction-types.c b/src/daemon/rpmostreed-transaction-types.c index 86112d1b8b..29d009e2cd 100644 --- a/src/daemon/rpmostreed-transaction-types.c +++ b/src/daemon/rpmostreed-transaction-types.c @@ -812,6 +812,8 @@ deploy_transaction_execute (RpmostreedTransaction *transaction, upgrader_flags |= RPMOSTREE_SYSROOT_UPGRADER_FLAGS_ALLOW_OLDER; if (dry_run) upgrader_flags |= RPMOSTREE_SYSROOT_UPGRADER_FLAGS_DRY_RUN; + if (deploy_has_bool_option (self, "lock-finalization")) + upgrader_flags |= RPMOSTREE_SYSROOT_UPGRADER_FLAGS_LOCK_FINALIZATION; /* DOWNLOAD_METADATA_ONLY isn't directly exposed at the D-Bus API level, so we shouldn't * ever run into these conflicting options */ @@ -2204,6 +2206,124 @@ rpmostreed_transaction_new_modify_yum_repo (GDBusMethodInvocation *invocation, return (RpmostreedTransaction *) self; } +/* ================================ FinalizeDeployment ================================ */ + +typedef struct { + RpmostreedTransaction parent; + char *osname; + GVariantDict *options; +} FinalizeTransaction; + +typedef RpmostreedTransactionClass FinalizeTransactionClass; + +GType finalize_transaction_get_type (void); + +G_DEFINE_TYPE (FinalizeTransaction, + finalize_transaction, + RPMOSTREED_TYPE_TRANSACTION) + +static void +finalize_transaction_finalize (GObject *object) +{ + FinalizeTransaction *self; + + self = (FinalizeTransaction *) object; + g_free (self->osname); + g_clear_pointer (&self->options, g_variant_dict_unref); + + G_OBJECT_CLASS (finalize_transaction_parent_class)->finalize (object); +} + +static gboolean +finalize_transaction_execute (RpmostreedTransaction *transaction, + GCancellable *cancellable, + GError **error) +{ + FinalizeTransaction *self = (FinalizeTransaction *) transaction; + OstreeSysroot *sysroot = rpmostreed_transaction_get_sysroot (transaction); + OstreeRepo *repo = ostree_sysroot_repo (sysroot); + + g_autoptr(GPtrArray) deployments = ostree_sysroot_get_deployments (sysroot); + if (deployments->len == 0) + return glnx_throw (error, "No deployments found"); + + OstreeDeployment *default_deployment = deployments->pdata[0]; + if (!ostree_deployment_is_staged (default_deployment)) + return glnx_throw (error, "No pending staged deployment found"); + if (!g_str_equal (ostree_deployment_get_osname (default_deployment), self->osname)) + return glnx_throw (error, "Staged deployment is not for osname '%s'", self->osname); + + gboolean is_layered = FALSE; + g_autofree char *base_checksum = NULL; + if (!rpmostree_deployment_get_layered_info (repo, default_deployment, &is_layered, NULL, + &base_checksum, NULL, NULL, NULL, error)) + return FALSE; + const char *checksum = base_checksum ?: ostree_deployment_get_csum (default_deployment); + + const char *expected_checksum = + vardict_lookup_ptr (self->options, "expect-checksum", "&s"); + if (expected_checksum && !g_str_equal (checksum, expected_checksum)) + return glnx_throw (error, "Expected staged base checksum %s, but found %s", + expected_checksum, checksum); + + if (unlink (_OSTREE_SYSROOT_RUNSTATE_STAGED_LOCKED) < 0) + { + if (errno != ENOENT) + return glnx_throw_errno_prefix (error, "unlink(%s)", + _OSTREE_SYSROOT_RUNSTATE_STAGED_LOCKED); + if (!vardict_lookup_bool (self->options, "allow-unlocked", FALSE)) + return glnx_throw (error, "Staged deployment was already unlocked"); + } + + rpmostreed_reboot (cancellable, error); + return TRUE; +} + +static void +finalize_transaction_class_init (FinalizeTransactionClass *class) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (class); + object_class->finalize = finalize_transaction_finalize; + + class->execute = finalize_transaction_execute; +} + +static void +finalize_transaction_init (FinalizeTransaction *self) +{ +} + +RpmostreedTransaction * +rpmostreed_transaction_new_finalize (GDBusMethodInvocation *invocation, + OstreeSysroot *sysroot, + const char *osname, + GVariant *options, + GCancellable *cancellable, + GError **error) +{ + g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), NULL); + g_return_val_if_fail (OSTREE_IS_SYSROOT (sysroot), NULL); + + g_autoptr(GVariantDict) options_dict = g_variant_dict_new (options); + + FinalizeTransaction *self = + g_initable_new (finalize_transaction_get_type (), + cancellable, error, + "invocation", invocation, + "sysroot-path", gs_file_get_path_cached (ostree_sysroot_get_path (sysroot)), + NULL); + + if (self != NULL) + { + self->osname = g_strdup (osname); + self->options = g_variant_dict_ref (options_dict); + } + + return (RpmostreedTransaction *) self; +} + /* ================================KernelArg================================ */ typedef struct { diff --git a/src/daemon/rpmostreed-transaction-types.h b/src/daemon/rpmostreed-transaction-types.h index ca40c11ca5..b1cafee35d 100644 --- a/src/daemon/rpmostreed-transaction-types.h +++ b/src/daemon/rpmostreed-transaction-types.h @@ -69,6 +69,14 @@ rpmostreed_transaction_new_deploy (GDBusMethodInvocation *invocation, GCancellable *cancellable, GError **error); +RpmostreedTransaction * +rpmostreed_transaction_new_finalize (GDBusMethodInvocation *invocation, + OstreeSysroot *sysroot, + const char *osname, + GVariant *options, + GCancellable *cancellable, + GError **error); + RpmostreedTransaction * rpmostreed_transaction_new_initramfs_state (GDBusMethodInvocation *invocation, OstreeSysroot *sysroot,