about summary refs log tree commit diff
path: root/src/nmcli/devices.c
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2022-08-16 18:24:19 +0200
committerMichael Biebl <biebl@debian.org>2022-08-16 18:24:19 +0200
commit0018d1f3cf71d680d7b6bceda55a5717244d8b26 (patch)
treea058f1d106d172d3354179437ef034c9355cdf9c /src/nmcli/devices.c
parent6accbd3ec0e42d8633bbde4d47ed7bfe854e7e0b (diff)
New upstream version 1.39.90 upstream/1.39.90
Diffstat (limited to 'src/nmcli/devices.c')
-rw-r--r--src/nmcli/devices.c407
1 files changed, 318 insertions, 89 deletions
diff --git a/src/nmcli/devices.c b/src/nmcli/devices.c
index be51731f..43bd3724 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))
@@ -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 < info->queue->len; i++) {
+        nm_device_delete_async(info->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},