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
This commit is contained in:
parent
51c5591ced
commit
95b423afe9
@ -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 \
|
||||
|
@ -116,6 +116,20 @@ Boston, MA 02111-1307, USA.
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><command>cancel</command></term>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
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 <command>upgrade</command>
|
||||
in general.
|
||||
</para>
|
||||
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><command>db</command></term>
|
||||
|
||||
|
@ -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 },
|
||||
|
121
src/app/rpmostree-builtin-cancel.c
Normal file
121
src/app/rpmostree-builtin-cancel.c
Normal file
@ -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 <string.h>
|
||||
#include <glib-unix.h>
|
||||
|
||||
#include "rpmostree-builtins.h"
|
||||
#include "rpmostree-util.h"
|
||||
#include "rpmostree-libbuiltin.h"
|
||||
#include "rpmostree-dbus-helpers.h"
|
||||
|
||||
#include <libglnx.h>
|
||||
|
||||
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;
|
||||
}
|
@ -64,6 +64,7 @@ BUILTINPROTO(upgrade);
|
||||
BUILTINPROTO(reload);
|
||||
BUILTINPROTO(deploy);
|
||||
BUILTINPROTO(rebase);
|
||||
BUILTINPROTO(cancel);
|
||||
BUILTINPROTO(cleanup);
|
||||
BUILTINPROTO(rollback);
|
||||
BUILTINPROTO(initramfs);
|
||||
|
@ -106,6 +106,16 @@
|
||||
</defaults>
|
||||
</action>
|
||||
|
||||
<action id="org.projectatomic.rpmostree1.cancel">
|
||||
<description>Cancel active transaction</description>
|
||||
<message>Authentication is required to cancel a transaction</message>
|
||||
<defaults>
|
||||
<allow_any>auth_admin</allow_any>
|
||||
<allow_inactive>auth_admin</allow_inactive>
|
||||
<allow_active>auth_admin_keep</allow_active>
|
||||
</defaults>
|
||||
</action>
|
||||
|
||||
<action id="org.projectatomic.rpmostree1.cleanup">
|
||||
<description>Clear cache</description>
|
||||
<message>Authentication is required to clear cache / pending data</message>
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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'
|
||||
|
Loading…
Reference in New Issue
Block a user