Skip to content

Commit

Permalink
WIP: Add D-Bus API for finalization
Browse files Browse the repository at this point in the history
Still need to test this properly, but essentially, we teach
`UpdateDeployment` to make use of libostree's staging lock and then add
a `FinalizeDeployment` API to perform the final unlock & reboot.

Closes: #1748
  • Loading branch information
jlebon committed Apr 18, 2019
1 parent 6b2ac58 commit 126bf71
Show file tree
Hide file tree
Showing 8 changed files with 233 additions and 1 deletion.
14 changes: 14 additions & 0 deletions src/daemon/org.projectatomic.rpmostree1.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
-->
<method name="UpdateDeployment">
<arg type="a{sv}" name="modifiers" direction="in"/>
Expand All @@ -347,6 +351,16 @@
<annotation name="org.gtk.GDBus.C.UnixFD" value="true"/>
</method>

<!-- Available options:
"expect-checksum" (type 's')
Verify that the checksum to finalize matches a specific one.
"allow-unlocked" (type 'b')
Don't error out if the staged deployment wasn't locked.
-->
<method name="FinalizeDeployment">
<arg type="a{sv}" name="options" direction="in"/>
<arg type="s" name="transaction_address" direction="out"/>
</method>
</interface>

<interface name="org.projectatomic.rpmostree1.OSExperimental">
Expand Down
4 changes: 4 additions & 0 deletions src/daemon/rpmostree-sysroot-core.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
18 changes: 18 additions & 0 deletions src/daemon/rpmostree-sysroot-upgrader.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions src/daemon/rpmostree-sysroot-upgrader.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand All @@ -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.
Expand Down
8 changes: 7 additions & 1 deletion src/daemon/rpmostreed-deployment-utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
60 changes: 60 additions & 0 deletions src/daemon/rpmostreed-os.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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() */
Expand Down
120 changes: 120 additions & 0 deletions src/daemon/rpmostreed-transaction-types.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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 {
Expand Down
8 changes: 8 additions & 0 deletions src/daemon/rpmostreed-transaction-types.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit 126bf71

Please sign in to comment.