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,