From e2fc81e2119ec658d76059009555cca8f6e0c231 Mon Sep 17 00:00:00 2001 From: Lubomir Rintel Date: Wed, 4 May 2022 11:07:21 +0200 Subject: [PATCH 1/7] glib-aux: add g_ptr_array_find() compat routine I want it but GLib is no good. Sad. --- src/libnm-glib-aux/nm-glib.h | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/libnm-glib-aux/nm-glib.h b/src/libnm-glib-aux/nm-glib.h index f3be3b32a4..2dc86d7b4e 100644 --- a/src/libnm-glib-aux/nm-glib.h +++ b/src/libnm-glib-aux/nm-glib.h @@ -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) From d576f4df44ce24fe6fd0eb27f501683adbdefe11 Mon Sep 17 00:00:00 2001 From: Lubomir Rintel Date: Wed, 4 May 2022 09:52:22 +0200 Subject: [PATCH 2/7] nmcli/devices: make get_device_list() shift argc/argv Prior to this patch, get_device_list() would give the caller no clue about how many options did it consume. That is okay -- it would always process all argument until the end, so the no callers would really care. In a further patch, I'd like to allow termination of the device name list (with a "--" arguments), so it will be possible to specify further arguments. Let's change the protype of this routine to use pointers to argc/argv, that it will be possible to adjust them. --- src/nmcli/devices.c | 53 ++++++++++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/src/nmcli/devices.c b/src/nmcli/devices.c index 6fce9de731..1ed253ff94 100644 --- a/src/nmcli/devices.c +++ b/src/nmcli/devices.c @@ -1110,39 +1110,42 @@ nmc_complete_device(NMClient *client, const char *prefix, gboolean wifi_only) } static GSList * -get_device_list(NmCli *nmc, int argc, const char *const *argv) +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; 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 (*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; } @@ -1152,16 +1155,16 @@ get_device_list(NmCli *nmc, int argc, const char *const *argv) if (!g_slist_find(queue, device)) queue = g_slist_prepend(queue, 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); @@ -2623,7 +2626,7 @@ do_devices_disconnect(const NMCCommand *cmd, NmCli *nmc, int argc, const char *c nmc->timeout = 10; next_arg(nmc, &argc, &argv, NULL); - queue = get_device_list(nmc, argc, argv); + queue = get_device_list(nmc, &argc, &argv); if (!queue) return; if (nmc->complete) @@ -2691,7 +2694,7 @@ do_devices_delete(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const nmc->timeout = 10; next_arg(nmc, &argc, &argv, NULL); - queue = get_device_list(nmc, argc, argv); + queue = get_device_list(nmc, &argc, &argv); if (!queue) return; if (nmc->complete) @@ -2901,7 +2904,7 @@ do_devices_monitor(const NMCCommand *cmd, NmCli *nmc, int argc, const char *cons 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 *queue = get_device_list(nmc, &argc, &argv); GSList *iter; /* Monitor the specified devices. */ From 767afeffd8bcd1b87ce1ed974b6629c7ff72a4a3 Mon Sep 17 00:00:00 2001 From: Lubomir Rintel Date: Wed, 4 May 2022 09:56:10 +0200 Subject: [PATCH 3/7] nmcli/devices: make get_device_list() terminate on "--" Don't consider "--" a device name. Instead, treat it as a signal to stop reading the device list. If a caller expects nothing beyond the device names, it now has to check. --- src/nmcli/devices.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/nmcli/devices.c b/src/nmcli/devices.c index 1ed253ff94..eb18199199 100644 --- a/src/nmcli/devices.c +++ b/src/nmcli/devices.c @@ -1140,6 +1140,12 @@ get_device_list(NmCli *nmc, int *argc, const char *const **argv) devices = nmc_get_devices_sorted(nmc->client); while (*argc > 0) { + if (strcmp(**argv, "--") == 0) { + (*argc)--; + (*argv)++; + break; + } + if (*argc == 1 && nmc->complete) complete_device(devices, **argv, FALSE); @@ -2627,6 +2633,11 @@ do_devices_disconnect(const NMCCommand *cmd, NmCli *nmc, int argc, const char *c next_arg(nmc, &argc, &argv, NULL); 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) @@ -2695,6 +2706,11 @@ do_devices_delete(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const next_arg(nmc, &argc, &argv, NULL); 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) @@ -2907,6 +2923,12 @@ do_devices_monitor(const NMCCommand *cmd, NmCli *nmc, int argc, const char *cons GSList *queue = get_device_list(nmc, &argc, &argv); GSList *iter; + if (argc) { + g_string_printf(nmc->return_text, _("Error: invalid extra argument '%s'."), *argv); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + return; + } + /* Monitor the specified devices. */ for (iter = queue; iter; iter = g_slist_next(iter)) device_watch(nmc, NM_DEVICE(iter->data)); From 2074b28976272b9452e0d48e67a17f1101707fc1 Mon Sep 17 00:00:00 2001 From: Lubomir Rintel Date: Wed, 4 May 2022 10:21:02 +0200 Subject: [PATCH 4/7] nmcli/devices: return GPtrArray instead of GSList from get_device_list() A pointer array is slightly more efficient here, since we don't really need the ability to insert elements in the middle. In fact, we'd prefer if we could just add to the end, so that we'd spare some callers from a need to do a g_slist_reverse(). Even though that alone being a good reason to use a GPtrArray instead of GSList, I'm doing this for so that I could actually use the returned value as-is in a call to nm_client_checkpoint_create() in a future patch. --- src/nmcli/devices.c | 78 ++++++++++++++++++++------------------------- 1 file changed, 35 insertions(+), 43 deletions(-) diff --git a/src/nmcli/devices.c b/src/nmcli/devices.c index eb18199199..2cce407538 100644 --- a/src/nmcli/devices.c +++ b/src/nmcli/devices.c @@ -1109,7 +1109,7 @@ nmc_complete_device(NMClient *client, const char *prefix, gboolean wifi_only) complete_device(devices, prefix, wifi_only); } -static GSList * +static GPtrArray * get_device_list(NmCli *nmc, int *argc, const char *const **argv) { int arg_num; @@ -1117,7 +1117,7 @@ get_device_list(NmCli *nmc, int *argc, const char *const **argv) gs_strfreev char **arg_arr = NULL; NMDevice **devices; - GSList *queue = NULL; + GPtrArray *queue = NULL; NMDevice *device; int i; @@ -1158,8 +1158,10 @@ get_device_list(NmCli *nmc, int *argc, const char *const **argv) } if (device) { - if (!g_slist_find(queue, device)) - queue = g_slist_prepend(queue, device); + if (!queue) + queue = g_ptr_array_new(); + if (!g_ptr_array_find(queue, device, NULL)) + g_ptr_array_add(queue, device); else g_printerr(_("Warning: argument '%s' is duplicated.\n"), **argv); } else { @@ -2623,9 +2625,10 @@ 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) @@ -2641,8 +2644,7 @@ do_devices_disconnect(const NMCCommand *cmd, NmCli *nmc, int argc, const char *c if (!queue) return; if (nmc->complete) - goto out; - queue = g_slist_reverse(queue); + return; info = g_slice_new0(DeviceCbInfo); info->nmc = nmc; @@ -2656,8 +2658,8 @@ 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 < queue->len; i++) { + device = 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); @@ -2665,9 +2667,6 @@ do_devices_disconnect(const NMCCommand *cmd, NmCli *nmc, int argc, const char *c /* Now disconnect the device */ nm_device_disconnect_async(device, info->cancellable, disconnect_device_cb, info); } - -out: - g_slist_free(queue); } static void @@ -2696,9 +2695,10 @@ 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; + NMDevice *device; + DeviceCbInfo *info = NULL; + gs_unref_ptrarray GPtrArray *queue = NULL; + guint i; /* Set default timeout for delete operation. */ if (nmc->timeout == -1) @@ -2714,8 +2714,7 @@ do_devices_delete(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const if (!queue) return; if (nmc->complete) - goto out; - queue = g_slist_reverse(queue); + return; info = g_slice_new0(DeviceCbInfo); info->nmc = nmc; @@ -2725,17 +2724,14 @@ do_devices_delete(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const 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 < queue->len; i++) { + device = queue->pdata[i]; 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); } - -out: - g_slist_free(queue); } static void @@ -2904,37 +2900,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) { - /* 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)); - - /* 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; - + 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. */ + devices = nm_client_get_devices(nmc->client); - /* Monitor the specified devices. */ - for (iter = queue; iter; iter = g_slist_next(iter)) - device_watch(nmc, NM_DEVICE(iter->data)); - g_slist_free(queue); + /* 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); } + 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); } From 5f9d2927ed0246ac699e45c887b1d53dbf8f4610 Mon Sep 17 00:00:00 2001 From: Lubomir Rintel Date: Wed, 18 May 2022 11:33:14 +0200 Subject: [PATCH 5/7] nmcli/devices: use GPtrArray from get_device_list() directly This makes get_device_list() return an array of NMDevices with a reference taken and a destroy notifier that unhooks disconnect_state_cb, so that it could replace the GSList of the same utility used by disconnect/delete commands. Suggested-by: Thomas Haller --- src/nmcli/devices.c | 55 +++++++++++++++++++-------------------------- 1 file changed, 23 insertions(+), 32 deletions(-) diff --git a/src/nmcli/devices.c b/src/nmcli/devices.c index 2cce407538..6d032aeff7 100644 --- a/src/nmcli/devices.c +++ b/src/nmcli/devices.c @@ -1109,6 +1109,8 @@ nmc_complete_device(NMClient *client, const char *prefix, gboolean wifi_only) complete_device(devices, prefix, wifi_only); } +static void destroy_queue_element(gpointer data); + static GPtrArray * get_device_list(NmCli *nmc, int *argc, const char *const **argv) { @@ -1159,9 +1161,9 @@ get_device_list(NmCli *nmc, int *argc, const char *const **argv) if (device) { if (!queue) - queue = g_ptr_array_new(); + queue = g_ptr_array_new_with_free_func(destroy_queue_element); if (!g_ptr_array_find(queue, device, NULL)) - g_ptr_array_add(queue, device); + g_ptr_array_add(queue, g_object_ref(device)); else g_printerr(_("Warning: argument '%s' is duplicated.\n"), **argv); } else { @@ -2315,7 +2317,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; @@ -2339,7 +2341,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) @@ -2352,7 +2354,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) { @@ -2378,22 +2380,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); @@ -2458,9 +2454,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); @@ -2647,6 +2645,7 @@ do_devices_disconnect(const NMCCommand *cmd, NmCli *nmc, int argc, const char *c return; info = g_slice_new0(DeviceCbInfo); + info->queue = g_steal_pointer(&queue); info->nmc = nmc; info->cmd_disconnect = TRUE; info->cancellable = g_cancellable_new(); @@ -2658,13 +2657,10 @@ do_devices_disconnect(const NMCCommand *cmd, NmCli *nmc, int argc, const char *c nmc->nowait_flag = (nmc->timeout == 0); nmc->should_wait++; - for (i = 0; i < queue->len; i++) { - device = queue->pdata[i]; + 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); } } @@ -2695,7 +2691,6 @@ 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; gs_unref_ptrarray GPtrArray *queue = NULL; guint i; @@ -2716,8 +2711,9 @@ do_devices_delete(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const if (nmc->complete) 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); @@ -2725,12 +2721,7 @@ do_devices_delete(const NMCCommand *cmd, NmCli *nmc, int argc, const char *const nmc->should_wait++; for (i = 0; i < queue->len; i++) { - device = queue->pdata[i]; - - 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); + nm_device_delete_async(queue->pdata[i], NULL, delete_device_cb, info); } } From 47eaf963e31dca8e82d6d9c9454c000528fe5fa7 Mon Sep 17 00:00:00 2001 From: Lubomir Rintel Date: Wed, 4 May 2022 09:20:01 +0200 Subject: [PATCH 6/7] nmcli: be less insistant on exiting when readline() gets no input When the input ends, we indeed eventually want to shut down. Nevertheless, it might be that we terminated the input *because* we're already shutting down and want do do our cleanup. Let's not take the shortcut to nmc_exit() in case the main loop is no longer running. This doesn't affect existing uses of nmc_readline(), but will be useful in a future patch. --- src/nmcli/common.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/nmcli/common.c b/src/nmcli/common.c index 710914e211..fa6d1caad8 100644 --- a/src/nmcli/common.c +++ b/src/nmcli/common.c @@ -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 */ From 1c17e556276ee786da374684b0752b3128e16938 Mon Sep 17 00:00:00 2001 From: Lubomir Rintel Date: Wed, 4 May 2022 09:19:01 +0200 Subject: [PATCH 7/7] nmcli/devices: add "checkpoint" command This is an interface to the Checkpoint/Restore functionality that's available for quite some time. It runs a command with a checkpoint taken and rolls back unless success is confirmed before the checkpoint times out: $ nmcli dev checkpoint eth0 -- nmcli dev dis eth0 Device 'eth0' successfully disconnected. Type "Yes" to commit the changes: No Checkpoint was removed. The details about how it's used are documented in nmcli(1) and nmcli-examples(7). --- man/nmcli-examples.xml | 29 +++++- man/nmcli.xml | 30 +++++- src/nmcli/devices.c | 223 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 279 insertions(+), 3 deletions(-) diff --git a/man/nmcli-examples.xml b/man/nmcli-examples.xml index 0afa9797a3..1fd7b76675 100644 --- a/man/nmcli-examples.xml +++ b/man/nmcli-examples.xml @@ -9,7 +9,7 @@