mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager.git
synced 2026-06-19 08:58:28 +02:00
merge: branch 'lr/nmcli-checkpoint'
https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/1207
This commit is contained in:
commit
9f9c82f39b
5 changed files with 404 additions and 92 deletions
|
|
@ -9,7 +9,7 @@
|
|||
<!--
|
||||
nmcli-examples(7) manual page
|
||||
|
||||
Copyright 2005 - 2016 Red Hat, Inc.
|
||||
Copyright 2005 - 2022 Red Hat, Inc.
|
||||
|
||||
Permission is granted to copy, distribute and/or modify this document
|
||||
under the terms of the GNU Free Documentation License, Version 1.1
|
||||
|
|
@ -640,6 +640,33 @@ Connection 'ethernet-4' (de89cdeb-a3e1-4d53-8fa0-c22546c775f4) successfully
|
|||
</para>
|
||||
</example>
|
||||
|
||||
<example><title>Device Checkpoint and Restore</title>
|
||||
|
||||
<screen><prompt>$ </prompt><userinput>nmcli dev checkpoint eth0 -- nmcli dev dis eth0</userinput>
|
||||
Device 'eth0' successfully disconnected.
|
||||
Type "Yes" to commit the changes: No
|
||||
Checkpoint was removed.</screen>
|
||||
<para>
|
||||
In this example the device eth0 was disconnected with the eth0 checkpoint
|
||||
taken. The user didn't confirm that the change is good, so the eth0 was
|
||||
brought back to the state it was when the checkpoint was taken.
|
||||
</para>
|
||||
<para>
|
||||
If the command being run unintentionaly brings down the remote connection
|
||||
(such as a
|
||||
<citerefentry><refentrytitle>ssh</refentrytitle><manvolnum>1</manvolnum></citerefentry>
|
||||
session) to the very machine it's being run on, the user wouldn't be able to
|
||||
confirm the success and the connectivity would end up being restored
|
||||
after a timeout.
|
||||
</para>
|
||||
<para>
|
||||
If, on the other hand, the command results in a success, the user could just
|
||||
confirm, causing the checkpoint to be abandoned without a rollback:
|
||||
</para>
|
||||
<screen><prompt>$ </prompt><userinput>nmcli dev checkpoint -- ip link del br0</userinput>
|
||||
Type "Yes" to commit the changes: <userinput>Yes</userinput></screen>
|
||||
</example>
|
||||
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
<!--
|
||||
nmcli(1) manual page
|
||||
|
||||
Copyright 2010 - 2018 Red Hat, Inc.
|
||||
Copyright 2010 - 2022 Red Hat, Inc.
|
||||
|
||||
Permission is granted to copy, distribute and/or modify this document
|
||||
under the terms of the GNU Free Documentation License, Version 1.1
|
||||
|
|
@ -1396,6 +1396,7 @@
|
|||
<arg choice='plain'><command>monitor</command></arg>
|
||||
<arg choice='plain'><command>wifi</command></arg>
|
||||
<arg choice='plain'><command>lldp</command></arg>
|
||||
<arg choice='plain'><command>checkpoint</command></arg>
|
||||
</group>
|
||||
<arg rep='repeat'><replaceable>ARGUMENTS</replaceable></arg>
|
||||
</cmdsynopsis>
|
||||
|
|
@ -1838,6 +1839,33 @@
|
|||
in the connection settings.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term>
|
||||
<command>checkpoint</command>
|
||||
<arg><option>--timeout</option> <replaceable>seconds</replaceable></arg>
|
||||
<arg rep='repeat'><replaceable>ifname</replaceable></arg>
|
||||
<arg choice='plain'><option>--</option></arg>
|
||||
<arg rep='repeat' choice='plain'><replaceable>COMMAND</replaceable></arg>
|
||||
</term>
|
||||
|
||||
<listitem>
|
||||
<para>Runs the command with a configuration checkpoint taken and asks for a
|
||||
confirmation when finished. When the confirmation is not given, the
|
||||
checkpoint is automatically restored after timeout.</para>
|
||||
|
||||
<para>This allows doing disruptive configuration changes over remote
|
||||
connections with an option of restoring the network configuration to a
|
||||
known good state in case of an error.</para>
|
||||
|
||||
<para>If the a list of interface names is specified, the checkpoint is
|
||||
taken, the checkpoint is takes only on the specified devices. Otherwise
|
||||
a checkpoint is taken for all devices.</para>
|
||||
|
||||
<para>Currently the timeout defaults to 15 seconds. This may change in
|
||||
a future version.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
|
|
|
|||
|
|
@ -307,6 +307,33 @@ _nm_g_ptr_array_insert(GPtrArray *array, int index_, gpointer data)
|
|||
|
||||
/*****************************************************************************/
|
||||
|
||||
#if !GLIB_CHECK_VERSION(2, 54, 0)
|
||||
static inline gboolean
|
||||
g_ptr_array_find(GPtrArray *haystack, gconstpointer needle, guint *index_)
|
||||
{
|
||||
guint i;
|
||||
g_return_val_if_fail(haystack, FALSE);
|
||||
|
||||
for (i = 0; i < haystack->len; i++) {
|
||||
if (haystack->pdata[i] == needle) {
|
||||
if (index_)
|
||||
*index_ = i;
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
#else
|
||||
#define g_ptr_array_find(haystack, needle, index_) \
|
||||
({ \
|
||||
G_GNUC_BEGIN_IGNORE_DEPRECATIONS \
|
||||
g_ptr_array_find(haystack, needle, index_); \
|
||||
G_GNUC_END_IGNORE_DEPRECATIONS \
|
||||
})
|
||||
#endif
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
#if !GLIB_CHECK_VERSION(2, 40, 0)
|
||||
static inline gboolean
|
||||
_g_key_file_save_to_file(GKeyFile *key_file, const char *filename, GError **error)
|
||||
|
|
|
|||
|
|
@ -929,7 +929,8 @@ read_again:
|
|||
}
|
||||
} else if (!rl_string) {
|
||||
/* Ctrl-D, exit */
|
||||
nmc_exit();
|
||||
if (g_main_loop_is_running(loop))
|
||||
nmc_exit();
|
||||
}
|
||||
|
||||
/* Return NULL, not empty string */
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
/*
|
||||
* Copyright (C) 2010 - 2018 Red Hat, Inc.
|
||||
* Copyright (C) 2010 - 2022 Red Hat, Inc.
|
||||
*/
|
||||
|
||||
#include "libnm-client-aux-extern/nm-default-client.h"
|
||||
|
|
@ -1042,6 +1042,18 @@ usage_device_lldp(void)
|
|||
"used to list neighbors for a particular interface.\n\n"));
|
||||
}
|
||||
|
||||
static void
|
||||
usage_device_checkpoint(void)
|
||||
{
|
||||
g_printerr(_("Usage: nmcli device checkpoint { ARGUMENTS | help }\n"
|
||||
"\n"
|
||||
"ARGUMENTS := [--timeout <seconds>] -- COMMAND...\n"
|
||||
"\n"
|
||||
"Runs the command with a configuration checkpoint taken and asks for a\n"
|
||||
"confirmation when finished. When the confirmation is not given, the\n"
|
||||
"checkpoint is automatically restored after timeout.\n\n"));
|
||||
}
|
||||
|
||||
static void
|
||||
quit(void)
|
||||
{
|
||||
|
|
@ -1109,59 +1121,72 @@ nmc_complete_device(NMClient *client, const char *prefix, gboolean wifi_only)
|
|||
complete_device(devices, prefix, wifi_only);
|
||||
}
|
||||
|
||||
static GSList *
|
||||
get_device_list(NmCli *nmc, int argc, const char *const *argv)
|
||||
static void destroy_queue_element(gpointer data);
|
||||
|
||||
static GPtrArray *
|
||||
get_device_list(NmCli *nmc, int *argc, const char *const **argv)
|
||||
{
|
||||
int arg_num = argc;
|
||||
int arg_num;
|
||||
const char *const *arg_ptr;
|
||||
|
||||
gs_strfreev char **arg_arr = NULL;
|
||||
const char *const *arg_ptr = argv;
|
||||
NMDevice **devices;
|
||||
GSList *queue = NULL;
|
||||
GPtrArray *queue = NULL;
|
||||
NMDevice *device;
|
||||
int i;
|
||||
|
||||
if (argc == 0) {
|
||||
if (nmc->ask) {
|
||||
gs_free char *line = NULL;
|
||||
if (*argc == 0 && nmc->ask) {
|
||||
gs_free char *line = NULL;
|
||||
|
||||
line = nmc_readline(&nmc->nmc_config, PROMPT_INTERFACES);
|
||||
nmc_string_to_arg_array(line, NULL, FALSE, &arg_arr, &arg_num);
|
||||
arg_ptr = (const char *const *) arg_arr;
|
||||
}
|
||||
if (arg_num == 0) {
|
||||
g_string_printf(nmc->return_text, _("Error: No interface specified."));
|
||||
nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
|
||||
goto error;
|
||||
}
|
||||
line = nmc_readline(&nmc->nmc_config, PROMPT_INTERFACES);
|
||||
nmc_string_to_arg_array(line, NULL, FALSE, &arg_arr, &arg_num);
|
||||
arg_ptr = (const char *const *) arg_arr;
|
||||
|
||||
argc = &arg_num;
|
||||
argv = &arg_ptr;
|
||||
}
|
||||
|
||||
if (*argc == 0) {
|
||||
g_string_printf(nmc->return_text, _("Error: No interface specified."));
|
||||
nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
|
||||
goto error;
|
||||
}
|
||||
|
||||
devices = nmc_get_devices_sorted(nmc->client);
|
||||
while (arg_num > 0) {
|
||||
if (arg_num == 1 && nmc->complete)
|
||||
complete_device(devices, *arg_ptr, FALSE);
|
||||
while (*argc > 0) {
|
||||
if (strcmp(**argv, "--") == 0) {
|
||||
(*argc)--;
|
||||
(*argv)++;
|
||||
break;
|
||||
}
|
||||
|
||||
if (*argc == 1 && nmc->complete)
|
||||
complete_device(devices, **argv, FALSE);
|
||||
|
||||
device = NULL;
|
||||
for (i = 0; devices[i]; i++) {
|
||||
if (!g_strcmp0(nm_device_get_iface(devices[i]), *arg_ptr)) {
|
||||
if (!g_strcmp0(nm_device_get_iface(devices[i]), **argv)) {
|
||||
device = devices[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (device) {
|
||||
if (!g_slist_find(queue, device))
|
||||
queue = g_slist_prepend(queue, device);
|
||||
if (!queue)
|
||||
queue = g_ptr_array_new_with_free_func(destroy_queue_element);
|
||||
if (!g_ptr_array_find(queue, device, NULL))
|
||||
g_ptr_array_add(queue, g_object_ref(device));
|
||||
else
|
||||
g_printerr(_("Warning: argument '%s' is duplicated.\n"), *arg_ptr);
|
||||
g_printerr(_("Warning: argument '%s' is duplicated.\n"), **argv);
|
||||
} else {
|
||||
if (!nmc->complete)
|
||||
g_printerr(_("Error: Device '%s' not found.\n"), *arg_ptr);
|
||||
g_printerr(_("Error: Device '%s' not found.\n"), **argv);
|
||||
g_string_printf(nmc->return_text, _("Error: not all devices found."));
|
||||
nmc->return_value = NMC_RESULT_ERROR_NOT_FOUND;
|
||||
}
|
||||
|
||||
/* Take next argument */
|
||||
next_arg(nmc->ask ? NULL : nmc, &arg_num, &arg_ptr, NULL);
|
||||
next_arg(nmc->ask ? NULL : nmc, argc, argv, NULL);
|
||||
}
|
||||
g_free(devices);
|
||||
|
||||
|
|
@ -2305,7 +2330,7 @@ do_device_connect(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const
|
|||
|
||||
typedef struct {
|
||||
NmCli *nmc;
|
||||
GSList *queue;
|
||||
GPtrArray *queue;
|
||||
guint timeout_id;
|
||||
gboolean cmd_disconnect;
|
||||
GCancellable *cancellable;
|
||||
|
|
@ -2329,7 +2354,7 @@ device_removed_cb(NMClient *client, NMDevice *device, DeviceCbInfo *info)
|
|||
/* Success: device has been removed.
|
||||
* It can also happen when disconnecting a software device.
|
||||
*/
|
||||
if (!g_slist_find(info->queue, device))
|
||||
if (!g_ptr_array_find(info->queue, device, NULL))
|
||||
return;
|
||||
|
||||
if (info->cmd_disconnect)
|
||||
|
|
@ -2342,7 +2367,7 @@ device_removed_cb(NMClient *client, NMDevice *device, DeviceCbInfo *info)
|
|||
static void
|
||||
disconnect_state_cb(NMDevice *device, GParamSpec *pspec, DeviceCbInfo *info)
|
||||
{
|
||||
if (!g_slist_find(info->queue, device))
|
||||
if (!g_ptr_array_find(info->queue, device, NULL))
|
||||
return;
|
||||
|
||||
if (nm_device_get_state(device) <= NM_DEVICE_STATE_DISCONNECTED) {
|
||||
|
|
@ -2368,22 +2393,16 @@ static void
|
|||
device_cb_info_finish(DeviceCbInfo *info, NMDevice *device)
|
||||
{
|
||||
if (device) {
|
||||
GSList *elem = g_slist_find(info->queue, device);
|
||||
if (!elem)
|
||||
if (!g_ptr_array_remove(info->queue, device))
|
||||
return;
|
||||
if (info->queue->len)
|
||||
return;
|
||||
info->queue = g_slist_delete_link(info->queue, elem);
|
||||
destroy_queue_element(device);
|
||||
} else {
|
||||
g_slist_free_full(info->queue, destroy_queue_element);
|
||||
info->queue = NULL;
|
||||
}
|
||||
|
||||
if (info->queue)
|
||||
return;
|
||||
|
||||
if (info->timeout_id)
|
||||
g_source_remove(info->timeout_id);
|
||||
|
||||
g_ptr_array_free(info->queue, TRUE);
|
||||
g_signal_handlers_disconnect_by_func(info->nmc->client, device_removed_cb, info);
|
||||
nm_clear_g_cancellable(&info->cancellable);
|
||||
|
||||
|
|
@ -2448,9 +2467,11 @@ do_device_reapply(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const
|
|||
nmc->nowait_flag = (nmc->timeout == 0);
|
||||
nmc->should_wait++;
|
||||
|
||||
info = g_slice_new0(DeviceCbInfo);
|
||||
info->nmc = nmc;
|
||||
info->queue = g_slist_prepend(info->queue, g_object_ref(device));
|
||||
info = g_slice_new0(DeviceCbInfo);
|
||||
info->nmc = nmc;
|
||||
|
||||
info->queue = g_ptr_array_new_with_free_func(destroy_queue_element);
|
||||
g_ptr_array_add(info->queue, g_object_ref(device));
|
||||
|
||||
/* Now reapply the connection to the device */
|
||||
nm_device_reapply_async(device, NULL, 0, 0, NULL, reapply_device_cb, info);
|
||||
|
|
@ -2615,23 +2636,29 @@ disconnect_device_cb(GObject *object, GAsyncResult *result, gpointer user_data)
|
|||
static void
|
||||
do_devices_disconnect(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const *argv)
|
||||
{
|
||||
NMDevice *device;
|
||||
DeviceCbInfo *info = NULL;
|
||||
GSList *queue, *iter;
|
||||
NMDevice *device;
|
||||
DeviceCbInfo *info = NULL;
|
||||
gs_unref_ptrarray GPtrArray *queue = NULL;
|
||||
guint i;
|
||||
|
||||
/* Set default timeout for disconnect operation. */
|
||||
if (nmc->timeout == -1)
|
||||
nmc->timeout = 10;
|
||||
|
||||
next_arg(nmc, &argc, &argv, NULL);
|
||||
queue = get_device_list(nmc, argc, argv);
|
||||
queue = get_device_list(nmc, &argc, &argv);
|
||||
if (argc) {
|
||||
g_string_printf(nmc->return_text, _("Error: invalid extra argument '%s'."), *argv);
|
||||
nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
|
||||
return;
|
||||
}
|
||||
if (!queue)
|
||||
return;
|
||||
if (nmc->complete)
|
||||
goto out;
|
||||
queue = g_slist_reverse(queue);
|
||||
return;
|
||||
|
||||
info = g_slice_new0(DeviceCbInfo);
|
||||
info->queue = g_steal_pointer(&queue);
|
||||
info->nmc = nmc;
|
||||
info->cmd_disconnect = TRUE;
|
||||
info->cancellable = g_cancellable_new();
|
||||
|
|
@ -2643,18 +2670,12 @@ do_devices_disconnect(const NMCCommand *cmd, NmCli *nmc, int argc, const char *c
|
|||
nmc->nowait_flag = (nmc->timeout == 0);
|
||||
nmc->should_wait++;
|
||||
|
||||
for (iter = queue; iter; iter = g_slist_next(iter)) {
|
||||
device = iter->data;
|
||||
for (i = 0; i < info->queue->len; i++) {
|
||||
device = info->queue->pdata[i];
|
||||
|
||||
info->queue = g_slist_prepend(info->queue, g_object_ref(device));
|
||||
g_signal_connect(device, "notify::" NM_DEVICE_STATE, G_CALLBACK(disconnect_state_cb), info);
|
||||
|
||||
/* Now disconnect the device */
|
||||
nm_device_disconnect_async(device, info->cancellable, disconnect_device_cb, info);
|
||||
}
|
||||
|
||||
out:
|
||||
g_slist_free(queue);
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
@ -2683,41 +2704,38 @@ delete_device_cb(GObject *object, GAsyncResult *result, gpointer user_data)
|
|||
static void
|
||||
do_devices_delete(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const *argv)
|
||||
{
|
||||
NMDevice *device;
|
||||
DeviceCbInfo *info = NULL;
|
||||
GSList *queue, *iter;
|
||||
DeviceCbInfo *info = NULL;
|
||||
gs_unref_ptrarray GPtrArray *queue = NULL;
|
||||
guint i;
|
||||
|
||||
/* Set default timeout for delete operation. */
|
||||
if (nmc->timeout == -1)
|
||||
nmc->timeout = 10;
|
||||
|
||||
next_arg(nmc, &argc, &argv, NULL);
|
||||
queue = get_device_list(nmc, argc, argv);
|
||||
queue = get_device_list(nmc, &argc, &argv);
|
||||
if (argc) {
|
||||
g_string_printf(nmc->return_text, _("Error: invalid extra argument '%s'."), *argv);
|
||||
nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
|
||||
return;
|
||||
}
|
||||
if (!queue)
|
||||
return;
|
||||
if (nmc->complete)
|
||||
goto out;
|
||||
queue = g_slist_reverse(queue);
|
||||
return;
|
||||
|
||||
info = g_slice_new0(DeviceCbInfo);
|
||||
info->nmc = nmc;
|
||||
info = g_slice_new0(DeviceCbInfo);
|
||||
info->queue = g_steal_pointer(&queue);
|
||||
info->nmc = nmc;
|
||||
if (nmc->timeout > 0)
|
||||
info->timeout_id = g_timeout_add_seconds(nmc->timeout, device_op_timeout_cb, info);
|
||||
|
||||
nmc->nowait_flag = (nmc->timeout == 0);
|
||||
nmc->should_wait++;
|
||||
|
||||
for (iter = queue; iter; iter = g_slist_next(iter)) {
|
||||
device = iter->data;
|
||||
|
||||
info->queue = g_slist_prepend(info->queue, g_object_ref(device));
|
||||
|
||||
/* Now delete the device */
|
||||
nm_device_delete_async(device, NULL, delete_device_cb, info);
|
||||
for (i = 0; i < queue->len; i++) {
|
||||
nm_device_delete_async(queue->pdata[i], NULL, delete_device_cb, info);
|
||||
}
|
||||
|
||||
out:
|
||||
g_slist_free(queue);
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
@ -2886,31 +2904,33 @@ device_removed(NMClient *client, NMDevice *device, NmCli *nmc)
|
|||
static void
|
||||
do_devices_monitor(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const *argv)
|
||||
{
|
||||
const GPtrArray *devices;
|
||||
gs_unref_ptrarray GPtrArray *devices_free = NULL;
|
||||
guint i;
|
||||
|
||||
if (nmc->complete)
|
||||
return;
|
||||
|
||||
next_arg(nmc, &argc, &argv, NULL);
|
||||
if (argc == 0) {
|
||||
if (argc > 0) {
|
||||
devices = devices_free = get_device_list(nmc, &argc, &argv);
|
||||
if (argc) {
|
||||
g_string_printf(nmc->return_text, _("Error: invalid extra argument '%s'."), *argv);
|
||||
nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
/* No devices specified. Monitor all. */
|
||||
const GPtrArray *devices = nm_client_get_devices(nmc->client);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < devices->len; i++)
|
||||
device_watch(nmc, g_ptr_array_index(devices, i));
|
||||
devices = nm_client_get_devices(nmc->client);
|
||||
|
||||
/* We'll watch the device additions too, never exit. */
|
||||
nmc->should_wait++;
|
||||
g_signal_connect(nmc->client, NM_CLIENT_DEVICE_ADDED, G_CALLBACK(device_added), nmc);
|
||||
} else {
|
||||
GSList *queue = get_device_list(nmc, argc, argv);
|
||||
GSList *iter;
|
||||
|
||||
/* Monitor the specified devices. */
|
||||
for (iter = queue; iter; iter = g_slist_next(iter))
|
||||
device_watch(nmc, NM_DEVICE(iter->data));
|
||||
g_slist_free(queue);
|
||||
}
|
||||
|
||||
for (i = 0; i < devices->len; i++)
|
||||
device_watch(nmc, g_ptr_array_index(devices, i));
|
||||
|
||||
g_signal_connect(nmc->client, NM_CLIENT_DEVICE_REMOVED, G_CALLBACK(device_removed), nmc);
|
||||
}
|
||||
|
||||
|
|
@ -5002,6 +5022,214 @@ do_device_lldp(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const *a
|
|||
nmc_do_cmd(nmc, device_lldp_cmds, *argv, argc, argv);
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
typedef struct {
|
||||
NmCli *nmc;
|
||||
NMCheckpoint *checkpoint;
|
||||
char **argv;
|
||||
guint removed_id;
|
||||
guint child_id;
|
||||
gboolean removed;
|
||||
} CheckpointCbInfo;
|
||||
|
||||
static void
|
||||
free_checkpoint_info(CheckpointCbInfo *info)
|
||||
{
|
||||
g_clear_object(&info->checkpoint);
|
||||
g_strfreev(info->argv);
|
||||
g_slice_free(CheckpointCbInfo, info);
|
||||
}
|
||||
|
||||
static void
|
||||
checkpoints_changed_cb(GObject *object, GParamSpec *pspec, CheckpointCbInfo *info)
|
||||
{
|
||||
const GPtrArray *checkpoints;
|
||||
guint i;
|
||||
|
||||
checkpoints = nm_client_get_checkpoints(info->nmc->client);
|
||||
for (i = 0; i < checkpoints->len; i++) {
|
||||
if (checkpoints->pdata[i] == info->checkpoint) {
|
||||
/* Our checkpoint still exists. */
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
g_string_printf(info->nmc->return_text, _("Checkpoint was removed."));
|
||||
info->nmc->return_value = NMC_RESULT_ERROR_TIMEOUT_EXPIRED;
|
||||
|
||||
info->removed = TRUE;
|
||||
|
||||
if (!info->child_id) {
|
||||
/* The command is done, we're in the confirmation prompt. */
|
||||
g_print("%s\n", _("No"));
|
||||
g_main_loop_quit(loop);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
checkpoint_destroy_cb(GObject *object, GAsyncResult *result, void *user_data)
|
||||
{
|
||||
NmCli *nmc = (NmCli *) user_data;
|
||||
gs_free_error GError *error = NULL;
|
||||
|
||||
if (!nm_client_checkpoint_destroy_finish(nmc->client, result, &error)) {
|
||||
g_string_printf(nmc->return_text,
|
||||
_("Error: Destroying a checkpoint failed: %s"),
|
||||
error->message);
|
||||
nmc->return_value = NMC_RESULT_ERROR_UNKNOWN;
|
||||
}
|
||||
|
||||
g_main_loop_quit(loop);
|
||||
}
|
||||
|
||||
static void
|
||||
child_watch_cb(GPid pid, gint wait_status, gpointer user_data)
|
||||
{
|
||||
CheckpointCbInfo *info = (CheckpointCbInfo *) user_data;
|
||||
NmCli *nmc = info->nmc;
|
||||
char *line;
|
||||
|
||||
info->child_id = 0;
|
||||
if (info->removed) {
|
||||
g_main_loop_quit(loop);
|
||||
goto out;
|
||||
}
|
||||
|
||||
while (g_main_loop_is_running(loop)) {
|
||||
line = nmc_readline(&nmc->nmc_config, "Type \"%s\" to commit the changes: ", _("Yes"));
|
||||
if (g_strcmp0(line, _("Yes")) == 0) {
|
||||
g_signal_handler_disconnect(nmc->client, info->removed_id);
|
||||
nm_client_checkpoint_destroy(nmc->client,
|
||||
nm_object_get_path(NM_OBJECT(info->checkpoint)),
|
||||
NULL,
|
||||
checkpoint_destroy_cb,
|
||||
nmc);
|
||||
break;
|
||||
}
|
||||
}
|
||||
nmc_cleanup_readline();
|
||||
out:
|
||||
free_checkpoint_info(info);
|
||||
}
|
||||
|
||||
static void
|
||||
checkpoint_create_cb(GObject *object, GAsyncResult *result, void *user_data)
|
||||
{
|
||||
NMClient *client = NM_CLIENT(object);
|
||||
CheckpointCbInfo *info = (CheckpointCbInfo *) user_data;
|
||||
gs_free_error GError *error = NULL;
|
||||
GPid pid;
|
||||
|
||||
info->checkpoint = nm_client_checkpoint_create_finish(client, result, &error);
|
||||
if (!info->checkpoint) {
|
||||
g_string_printf(info->nmc->return_text,
|
||||
_("Error: Creating a checkpoint failed: %s"),
|
||||
error->message);
|
||||
info->nmc->return_value = NMC_RESULT_ERROR_UNKNOWN;
|
||||
g_main_loop_quit(loop);
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (!g_spawn_async(NULL,
|
||||
info->argv,
|
||||
NULL,
|
||||
G_SPAWN_LEAVE_DESCRIPTORS_OPEN | G_SPAWN_SEARCH_PATH
|
||||
| G_SPAWN_CHILD_INHERITS_STDIN | G_SPAWN_DO_NOT_REAP_CHILD,
|
||||
NULL,
|
||||
info,
|
||||
&pid,
|
||||
&error)) {
|
||||
g_string_printf(info->nmc->return_text, _("Error: %s"), error->message);
|
||||
info->nmc->return_value = NMC_RESULT_ERROR_UNKNOWN;
|
||||
g_main_loop_quit(loop);
|
||||
goto err;
|
||||
}
|
||||
|
||||
info->child_id = g_child_watch_add(pid, child_watch_cb, info);
|
||||
info->removed_id = g_signal_connect(client,
|
||||
"notify::" NM_CLIENT_CHECKPOINTS,
|
||||
G_CALLBACK(checkpoints_changed_cb),
|
||||
info);
|
||||
|
||||
return;
|
||||
|
||||
err:
|
||||
free_checkpoint_info(info);
|
||||
}
|
||||
|
||||
static void
|
||||
do_device_checkpoint(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const *argv)
|
||||
{
|
||||
NMClient *client = nmc->client;
|
||||
long unsigned int timeout = 15;
|
||||
int option;
|
||||
CheckpointCbInfo *info;
|
||||
const GPtrArray *devices = NULL;
|
||||
gs_unref_ptrarray GPtrArray *devices_free = NULL;
|
||||
|
||||
while ((option = next_arg(nmc, &argc, &argv, "--timeout", NULL)) > 0) {
|
||||
switch (option) {
|
||||
case 1: /* --timeout */
|
||||
argc--;
|
||||
argv++;
|
||||
if (!argc) {
|
||||
g_string_printf(nmc->return_text, _("Error: %s argument is missing."), *(argv - 1));
|
||||
nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
|
||||
return;
|
||||
}
|
||||
if (!nmc_string_to_uint(*argv, TRUE, 0, G_MAXUINT32, &timeout)) {
|
||||
g_string_printf(nmc->return_text, _("Error: '%s' is not a valid timeout."), *argv);
|
||||
nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
nm_assert_not_reached();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (argc) {
|
||||
if (strcmp(*argv, "--") == 0) {
|
||||
devices = nm_client_get_devices(client);
|
||||
argc--;
|
||||
argv++;
|
||||
} else {
|
||||
devices = devices_free = get_device_list(nmc, &argc, &argv);
|
||||
if (!devices) {
|
||||
g_string_printf(nmc->return_text, _("Error: not all devices found."));
|
||||
nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (argc == 0) {
|
||||
g_string_printf(nmc->return_text, _("Error: Expected a command to run after '--'"));
|
||||
nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
|
||||
return;
|
||||
}
|
||||
|
||||
if (nmc->complete)
|
||||
return;
|
||||
|
||||
info = g_slice_new0(CheckpointCbInfo);
|
||||
info->nmc = nmc;
|
||||
info->argv = nm_strv_dup(argv, argc, TRUE);
|
||||
|
||||
nmc->should_wait++;
|
||||
nm_client_checkpoint_create(client,
|
||||
devices,
|
||||
(guint32) timeout,
|
||||
NM_CHECKPOINT_CREATE_FLAG_NONE,
|
||||
NULL,
|
||||
checkpoint_create_cb,
|
||||
info);
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
static gboolean
|
||||
is_single_word(const char *line)
|
||||
{
|
||||
|
|
@ -5048,6 +5276,7 @@ void
|
|||
nmc_command_func_device(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const *argv)
|
||||
{
|
||||
static const NMCCommand cmds[] = {
|
||||
{"checkpoint", do_device_checkpoint, usage_device_checkpoint, TRUE, TRUE},
|
||||
{"connect", do_device_connect, usage_device_connect, TRUE, TRUE},
|
||||
{"disconnect", do_devices_disconnect, usage_device_disconnect, TRUE, TRUE},
|
||||
{"delete", do_devices_delete, usage_device_delete, TRUE, TRUE},
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue