From 95b423afe9e963d6271bcc14a03a90086880cb38 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Tue, 5 Dec 2017 17:14:18 -0500 Subject: [PATCH] Add `cancel` verb and DBus API Right now the fact that one can only cancel via `Ctrl-C` of an existing client process is rather frustrating if for example one's ssh connection to a machine drops. Now, upon reconnecting, one can easily `rpm-ostree cancel` a hung update or whatever rather than doing the more forcible `systemctl stop rpm-ostreed` (which is safe of course, unless livefs is involved). Closes: #1019 Approved by: jlebon --- Makefile-rpm-ostree.am | 1 + man/rpm-ostree.xml | 14 ++ src/app/main.c | 3 + src/app/rpmostree-builtin-cancel.c | 121 ++++++++++++++++++ src/app/rpmostree-builtins.h | 1 + .../org.projectatomic.rpmostree1.policy | 10 ++ src/daemon/rpmostreed-sysroot.c | 4 + tests/vmcheck/test-layering-scripts.sh | 35 +++-- 8 files changed, 180 insertions(+), 9 deletions(-) create mode 100644 src/app/rpmostree-builtin-cancel.c diff --git a/Makefile-rpm-ostree.am b/Makefile-rpm-ostree.am index 1e511eb9..b2fb067e 100644 --- a/Makefile-rpm-ostree.am +++ b/Makefile-rpm-ostree.am @@ -28,6 +28,7 @@ rpm_ostree_SOURCES = src/app/main.c \ src/app/rpmostree-builtin-deploy.c \ src/app/rpmostree-builtin-reload.c \ src/app/rpmostree-builtin-rebase.c \ + src/app/rpmostree-builtin-cancel.c \ src/app/rpmostree-builtin-cleanup.c \ src/app/rpmostree-builtin-initramfs.c \ src/app/rpmostree-builtin-livefs.c \ diff --git a/man/rpm-ostree.xml b/man/rpm-ostree.xml index 6ff05ae6..140e1561 100644 --- a/man/rpm-ostree.xml +++ b/man/rpm-ostree.xml @@ -116,6 +116,20 @@ Boston, MA 02111-1307, USA. + + cancel + + + + Cancel a pending transaction. Exits successfully and does + nothing if no transaction is running. Note that it is fully + safe to cancel transactions such as upgrade + in general. + + + + + db diff --git a/src/app/main.c b/src/app/main.c index 874df88f..2a40abaa 100644 --- a/src/app/main.c +++ b/src/app/main.c @@ -65,6 +65,9 @@ static RpmOstreeCommand commands[] = { { "reload", 0, "Reload configuration", rpmostree_builtin_reload }, + { "cancel", 0, + "Cancel an active transaction", + rpmostree_builtin_cancel }, { "initramfs", 0, "Enable or disable local initramfs regeneration", rpmostree_builtin_initramfs }, diff --git a/src/app/rpmostree-builtin-cancel.c b/src/app/rpmostree-builtin-cancel.c new file mode 100644 index 00000000..cfcbfdc9 --- /dev/null +++ b/src/app/rpmostree-builtin-cancel.c @@ -0,0 +1,121 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2017 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 +#include + +#include "rpmostree-builtins.h" +#include "rpmostree-util.h" +#include "rpmostree-libbuiltin.h" +#include "rpmostree-dbus-helpers.h" + +#include + +static GOptionEntry option_entries[] = { + { NULL } +}; + +static void +on_cancel_method_completed (GObject *src, + GAsyncResult *res, + gpointer user_data) +{ + /* Nothing right now - we ideally should check for an error, but the + * transaction could have gone away. A better fix here would be to keep + * transactions around for some period of time. + */ + (void) rpmostree_transaction_call_cancel_finish ((RPMOSTreeTransaction*)src, res, NULL); +} + +static void +on_active_txn_path_changed (GObject *object, + GParamSpec *pspec, + gpointer user_data) +{ + gboolean *donep = user_data; + *donep = TRUE; + g_main_context_wakeup (NULL); +} + +int +rpmostree_builtin_cancel (int argc, + char **argv, + RpmOstreeCommandInvocation *invocation, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GOptionContext) context = g_option_context_new (""); + glnx_unref_object RPMOSTreeSysroot *sysroot_proxy = NULL; + _cleanup_peer_ GPid peer_pid = 0; + + if (!rpmostree_option_context_parse (context, + option_entries, + &argc, &argv, + invocation, + cancellable, + NULL, NULL, + &sysroot_proxy, + &peer_pid, + error)) + return EXIT_FAILURE; + + + /* Keep track of the txn path we saw first, as well as checking if it changed + * over time. + */ + g_autofree char *txn_path = NULL; + glnx_unref_object RPMOSTreeTransaction *txn_proxy = NULL; + if (!rpmostree_transaction_connect_active (sysroot_proxy, &txn_path, &txn_proxy, + cancellable, error)) + return EXIT_FAILURE; + if (!txn_proxy) + { + /* Let's not make this an error; cancellation may race with completion. + * Perhaps in the future we could try to see whether a txn exited + * "recently" but eh. + */ + g_print ("No active transaction.\n"); + return EXIT_SUCCESS; + } + + const char *title = rpmostree_transaction_get_title (txn_proxy); + g_print ("Cancelling transaction: %s\n", title); + + /* Asynchronously cancel, waiting for the sysroot property to change */ + rpmostree_transaction_call_cancel (txn_proxy, cancellable, + on_cancel_method_completed, NULL); + + gboolean done = FALSE; + g_signal_connect (txn_proxy, "notify::active-transaction-path", + G_CALLBACK (on_active_txn_path_changed), &done); + /* Wait for the transaction to go away */ + while (!done) + { + const char *current_txn_path = rpmostree_sysroot_get_active_transaction_path (sysroot_proxy); + if (g_strcmp0 (txn_path, current_txn_path) != 0) + break; + g_main_context_iteration (NULL, TRUE); + } + g_print ("Cancelled.\n"); + + return EXIT_SUCCESS; +} diff --git a/src/app/rpmostree-builtins.h b/src/app/rpmostree-builtins.h index 17c930f9..a607f761 100644 --- a/src/app/rpmostree-builtins.h +++ b/src/app/rpmostree-builtins.h @@ -64,6 +64,7 @@ BUILTINPROTO(upgrade); BUILTINPROTO(reload); BUILTINPROTO(deploy); BUILTINPROTO(rebase); +BUILTINPROTO(cancel); BUILTINPROTO(cleanup); BUILTINPROTO(rollback); BUILTINPROTO(initramfs); diff --git a/src/daemon/org.projectatomic.rpmostree1.policy b/src/daemon/org.projectatomic.rpmostree1.policy index 304e89db..8f180d3e 100644 --- a/src/daemon/org.projectatomic.rpmostree1.policy +++ b/src/daemon/org.projectatomic.rpmostree1.policy @@ -106,6 +106,16 @@ + + Cancel active transaction + Authentication is required to cancel a transaction + + auth_admin + auth_admin + auth_admin_keep + + + Clear cache Authentication is required to clear cache / pending data diff --git a/src/daemon/rpmostreed-sysroot.c b/src/daemon/rpmostreed-sysroot.c index d490f979..1a8f5390 100644 --- a/src/daemon/rpmostreed-sysroot.c +++ b/src/daemon/rpmostreed-sysroot.c @@ -569,6 +569,10 @@ sysroot_authorize_method (GDBusInterfaceSkeleton *interface, { action = "org.projectatomic.rpmostree1.reload-daemon"; } + else if (g_strcmp0 (method_name, "Cancel") == 0) + { + action = "org.projectatomic.rpmostree1.cancel"; + } else if (g_strcmp0 (method_name, "RegisterClient") == 0 || g_strcmp0 (method_name, "UnregisterClient") == 0) { diff --git a/tests/vmcheck/test-layering-scripts.sh b/tests/vmcheck/test-layering-scripts.sh index 225a2cf4..7f97f1b2 100755 --- a/tests/vmcheck/test-layering-scripts.sh +++ b/tests/vmcheck/test-layering-scripts.sh @@ -159,22 +159,39 @@ if vm_rpmostree install rofiles-violation; then assert_not_reached "installed test-post-rofiles-violation!" fi -# Test cancellation via having a script hang -cursor=$(vm_get_journal_cursor) +# Test cancellation via having a script hang; we interrupt directly by sending +# SIGINT to the client binary. vm_build_rpm post-that-hangs \ post "echo entering post-that-hangs-infloop 1>&2; while true; do sleep 1h; done" -# use a systemd transient service as an easy way to run in the background -vm_cmd systemd-run --unit vmcheck-install-hang rpm-ostree install post-that-hangs -if ! vm_wait_content_after_cursor "${cursor}" "entering post-that-hangs-infloop"; then - vm_cmd systemctl stop vmcheck-install-hang - assert_not_reached "failed to wait for post-that-hangs" -fi +background_install_post_that_hangs() { + local cursor="$1" + # use a systemd transient service as an easy way to run in the background; be + # sure any previous failed instances are cleaned up + vm_cmd systemctl stop vmcheck-install-hang || true + vm_cmd systemctl reset-failed vmcheck-install-hang || true + vm_cmd systemd-run --unit vmcheck-install-hang rpm-ostree install post-that-hangs + if ! vm_wait_content_after_cursor "${cursor}" "entering post-that-hangs-infloop"; then + vm_cmd systemctl stop vmcheck-install-hang || true + assert_not_reached "failed to wait for post-that-hangs" + fi +} +cursor=$(vm_get_journal_cursor) +background_install_post_that_hangs "${cursor}" vm_cmd pkill --signal INT -f "'rpm-ostree install post-that-hangs'" # Wait for our expected result vm_wait_content_after_cursor "${cursor}" "Txn.*failed.*Running %post for post-that-hangs" # Forcibly restart now to avoid any races with the txn finally exiting vm_cmd systemctl restart rpm-ostreed -echo "ok cancel infinite post" +echo "ok cancel infinite post via SIGINT" + +# Test `rpm-ostree cancel` (which is the same as doing a Ctrl-C on the client) +cursor=$(vm_get_journal_cursor) +background_install_post_that_hangs "${cursor}" +vm_rpmostree cancel +vm_wait_content_after_cursor "${cursor}" "Txn.*failed.*Running %post for post-that-hangs" +# Forcibly restart now to avoid any races with the txn finally exiting +vm_cmd systemctl restart rpm-ostreed +echo "ok cancel infinite post via `rpm-ostree cancel`" # Test rm -rf /! vm_cmd 'useradd testuser || true'