Skip to content

Commit

Permalink
Add new D-Bus APIs for deployment finalization
Browse files Browse the repository at this point in the history
Teach `UpdateDeployment` to make use of libostree's staging lock and
then add a `FinalizeDeployment` API to perform the final unlock &
reboot.

I also added a hidden CLI to make testing this easier, but also because
it's likely the FCOS-agent-yet-to-be-named will just end up using the
CLI to keep it simple.

Closes: #1748

Closes: #1814
Approved by: lucab
  • Loading branch information
jlebon authored and rh-atomic-bot committed Apr 30, 2019
1 parent 9cb1f61 commit 83a2674
Show file tree
Hide file tree
Showing 15 changed files with 392 additions and 1 deletion.
1 change: 1 addition & 0 deletions Makefile-rpm-ostree.am
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ rpm_ostree_SOURCES = src/app/main.c \
src/app/rpmostree-ex-builtin-rojig2commit.c \
src/app/rpmostree-builtin-db.c \
src/app/rpmostree-builtin-start-daemon.c \
src/app/rpmostree-builtin-finalize-deployment.c \
src/app/rpmostree-db-builtin-diff.c \
src/app/rpmostree-db-builtin-list.c \
src/app/rpmostree-db-builtin-version.c \
Expand Down
2 changes: 2 additions & 0 deletions src/app/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ static RpmOstreeCommand commands[] = {
RPM_OSTREE_BUILTIN_FLAG_REQUIRES_ROOT |
RPM_OSTREE_BUILTIN_FLAG_HIDDEN,
NULL, rpmostree_builtin_start_daemon },
{ "finalize-deployment", RPM_OSTREE_BUILTIN_FLAG_HIDDEN,
NULL, rpmostree_builtin_finalize_deployment },
{ NULL }
};

Expand Down
102 changes: 102 additions & 0 deletions src/app/rpmostree-builtin-finalize-deployment.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright (C) 2019 Red Hat, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2 of the licence or (at
* your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307, USA.
*/

#include "config.h"

#include "rpmostree-builtins.h"
#include "rpmostree-libbuiltin.h"

static char *opt_osname;
static gboolean opt_allow_unlocked;
static gboolean opt_allow_missing;

static GOptionEntry option_entries[] = {
/* though there can only be one staged deployment at a time, this could still
* be useful to assert a specific osname */
{ "os", 0, 0, G_OPTION_ARG_STRING, &opt_osname, "Operate on provided OSNAME", "OSNAME" },
{ "allow-missing-checksum", 0, 0, G_OPTION_ARG_NONE, &opt_allow_missing, "Don't error out if no expected checksum is provided", NULL },
{ "allow-unlocked", 0, 0, G_OPTION_ARG_NONE, &opt_allow_unlocked, "Don't error out if staged deployment wasn't locked", NULL },
{ NULL }
};

gboolean
rpmostree_builtin_finalize_deployment (int argc,
char **argv,
RpmOstreeCommandInvocation *invocation,
GCancellable *cancellable,
GError **error)
{
g_autoptr(GOptionContext) context = g_option_context_new ("CHECKSUM");

_cleanup_peer_ GPid peer_pid = 0;
glnx_unref_object RPMOSTreeSysroot *sysroot_proxy = NULL;
if (!rpmostree_option_context_parse (context, option_entries, &argc, &argv,
invocation, cancellable, NULL, NULL,
&sysroot_proxy, &peer_pid, NULL, error))
return FALSE;

const char *checksum = NULL;
if (argc > 2)
{
rpmostree_usage_error (context, "Too many arguments passed", error);
return FALSE;
}
else if (argc < 2 && !opt_allow_missing)
{
rpmostree_usage_error (context, "Must provide expected CHECKSUM or --allow-missing-checksum", error);
return FALSE;
}
else if (argc == 2 && opt_allow_missing)
{
rpmostree_usage_error (context, "Cannot specify both CHECKSUM and --allow-missing-checksum", error);
return FALSE;
}
else if (!opt_allow_missing)
checksum = argv[1];

glnx_unref_object RPMOSTreeOS *os_proxy = NULL;
if (!rpmostree_load_os_proxy (sysroot_proxy, opt_osname,
cancellable, &os_proxy, error))
return FALSE;

GVariantDict dict;
g_variant_dict_init (&dict, NULL);
if (!opt_allow_missing)
g_variant_dict_insert (&dict, "checksum", "s", checksum);
g_variant_dict_insert (&dict, "allow-missing-checksum", "b", opt_allow_missing);
g_variant_dict_insert (&dict, "allow-unlocked", "b", opt_allow_unlocked);
g_autoptr(GVariant) options = g_variant_ref_sink (g_variant_dict_end (&dict));

g_autofree char *transaction_address = NULL;
if (!rpmostree_os_call_finalize_deployment_sync (os_proxy,
options,
&transaction_address,
cancellable,
error))
return FALSE;

if (!rpmostree_transaction_get_response_sync (sysroot_proxy,
transaction_address,
cancellable,
error))
return FALSE;

return TRUE;
}
3 changes: 3 additions & 0 deletions src/app/rpmostree-builtin-upgrade.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ static gboolean opt_upgrade_unchanged_exit_77;
static gboolean opt_cache_only;
static gboolean opt_download_only;
static char *opt_automatic;
static gboolean opt_lock_finalization;

/* "check-diff" is deprecated, replaced by "preview" */
static GOptionEntry option_entries[] = {
Expand All @@ -53,6 +54,7 @@ static GOptionEntry option_entries[] = {
{ "download-only", 0, 0, G_OPTION_ARG_NONE, &opt_download_only, "Just download latest ostree and RPM data, don't deploy", NULL },
{ "upgrade-unchanged-exit-77", 0, 0, G_OPTION_ARG_NONE, &opt_upgrade_unchanged_exit_77, "If no upgrade is available, exit 77", NULL },
{ "trigger-automatic-update-policy", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &opt_automatic, "For automated use only; triggered by automatic timer", NULL },
{ "lock-finalization", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &opt_lock_finalization, "Prevent automatic deployment finalization on shutdown", NULL },
{ NULL }
};

Expand Down Expand Up @@ -162,6 +164,7 @@ rpmostree_builtin_upgrade (int argc,
g_variant_dict_insert (&dict, "allow-downgrade", "b", opt_allow_downgrade);
g_variant_dict_insert (&dict, "cache-only", "b", opt_cache_only);
g_variant_dict_insert (&dict, "download-only", "b", opt_download_only);
g_variant_dict_insert (&dict, "lock-finalization", "b", opt_lock_finalization);
g_autoptr(GVariant) options = g_variant_ref_sink (g_variant_dict_end (&dict));

/* Use newer D-Bus API only if we have to. */
Expand Down
1 change: 1 addition & 0 deletions src/app/rpmostree-builtins.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ BUILTINPROTO(kargs);
BUILTINPROTO(reset);
BUILTINPROTO(start_daemon);
BUILTINPROTO(ex);
BUILTINPROTO(finalize_deployment);

#undef BUILTINPROTO

Expand Down
11 changes: 11 additions & 0 deletions src/daemon/org.projectatomic.rpmostree1.policy
Original file line number Diff line number Diff line change
Expand Up @@ -159,4 +159,15 @@
<allow_active>yes</allow_active>
</defaults>
</action>

<action id="org.projectatomic.rpmostree1.finalize-deployment">
<description>Finalize staged deployment</description>
<message>Authentication is required to finalize staged deployment</message>
<icon_name>package-x-generic</icon_name>
<defaults>
<allow_any>auth_admin</allow_any>
<allow_inactive>auth_admin</allow_inactive>
<allow_active>auth_admin_keep</allow_active>
</defaults>
</action>
</policyconfig>
18 changes: 18 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,20 @@
<annotation name="org.gtk.GDBus.C.UnixFD" value="true"/>
</method>

<!-- Available options:
"checksum" (type 's')
Verify that the checksum to finalize matches a specific one.
"allow-missing-checksum" (type 'b')
Allow "checksum" key to be missing. Given that this API is
meant for clients with precise state transitions, *not*
providing a checksum should be a conscious decision.
"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
66 changes: 66 additions & 0 deletions src/daemon/rpmostreed-os.c
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,10 @@ os_authorize_method (GDBusInterfaceSkeleton *interface,
no_overrides)
g_ptr_array_add (actions, "org.projectatomic.rpmostree1.override");
}
else if (g_strcmp0 (method_name, "FinalizeDeployment") == 0)
{
g_ptr_array_add (actions, "org.projectatomic.rpmostree1.finalize-deployment");
}
else
{
authorized = FALSE;
Expand Down Expand Up @@ -974,6 +978,67 @@ 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 ();
glnx_unref_object RpmostreedTransaction *transaction = NULL;
g_autoptr(OstreeSysroot) sysroot = NULL;
const char *osname;
GError *local_error = NULL;

/* try to merge with an existing transaction, otherwise start a new one */
RpmostreedSysroot *rsysroot = rpmostreed_sysroot_get ();

if (!rpmostreed_sysroot_prep_for_txn (rsysroot, invocation, &transaction, &local_error))
goto out;
if (transaction)
goto out;

if (!rpmostreed_sysroot_load_state (rsysroot, cancellable, &sysroot, NULL, &local_error))
goto out;

osname = rpmostree_os_get_name (interface);

transaction = rpmostreed_transaction_new_finalize_deployment (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 @@ -1713,6 +1778,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
Loading

0 comments on commit 83a2674

Please sign in to comment.