diff options
Diffstat (limited to 'src/nmcli/devices.c')
-rw-r--r-- | src/nmcli/devices.c | 223 |
1 files changed, 222 insertions, 1 deletions
diff --git a/src/nmcli/devices.c b/src/nmcli/devices.c index 6d032aeff7..eaa27a245e 100644 --- a/src/nmcli/devices.c +++ b/src/nmcli/devices.c @@ -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" @@ -1043,6 +1043,18 @@ usage_device_lldp(void) } 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) { if (nm_clear_g_source(&progress_id)) @@ -5009,6 +5021,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) { @@ -5055,6 +5275,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}, |