diff options
author | Jiří Klimeš <jklimes@redhat.com> | 2015-03-11 19:10:58 +0100 |
---|---|---|
committer | Jiří Klimeš <jklimes@redhat.com> | 2015-03-13 11:04:28 +0100 |
commit | b174f43af012d814db3beba253337e442cca873a (patch) | |
tree | 2034a9fed2015a0146638c845fbf0b0bdd641f08 | |
parent | 27bd0b73177565f44a337054dc57e82495ea6112 (diff) | |
download | NetworkManager-jk/nmcli-dev-multiple-args-bgo746097.tar.gz |
cli: allow multiple devices for 'nmcli device disconnect/delete' (bgo #746097)jk/nmcli-dev-multiple-args-bgo746097
Allow disconnecting and deleting multiple interfaces at a time. It is much
more user friendly. TAB completion is supported as well.
https://bugzilla.gnome.org/show_bug.cgi?id=746097
-rw-r--r-- | clients/cli/devices.c | 354 | ||||
-rw-r--r-- | clients/cli/nmcli-completion | 8 | ||||
-rw-r--r-- | man/nmcli.1.in | 7 |
3 files changed, 240 insertions, 129 deletions
diff --git a/clients/cli/devices.c b/clients/cli/devices.c index 89f2d42b31..b9dd0d5cb8 100644 --- a/clients/cli/devices.c +++ b/clients/cli/devices.c @@ -35,7 +35,8 @@ #include "devices.h" /* define some prompts */ -#define PROMPT_INTERFACE _("Interface: ") +#define PROMPT_INTERFACE _("Interface: ") +#define PROMPT_INTERFACES _("Interface(s): ") /* Available fields for 'device status' */ static NmcOutputField nmc_fields_dev_status[] = { @@ -265,8 +266,8 @@ usage (void) " status\n\n" " show [<ifname>]\n\n" " connect <ifname>\n\n" - " disconnect <ifname>\n\n" - " delete <ifname>\n\n" + " disconnect <ifname> ...\n\n" + " delete <ifname> ...\n\n" " wifi [list [ifname <ifname>] [bssid <BSSID>]]\n\n" " wifi connect <(B)SSID> [password <password>] [wep-key-type key|phrase] [ifname <ifname>]\n" " [bssid <BSSID>] [name <name>] [private yes|no]\n\n" @@ -320,9 +321,9 @@ usage_device_disconnect (void) { g_printerr (_("Usage: nmcli device disconnect { ARGUMENTS | help }\n" "\n" - "ARGUMENTS := <ifname>\n" + "ARGUMENTS := <ifname> ...\n" "\n" - "Disconnect the device.\n" + "Disconnect devices.\n" "The command disconnects the device and prevents it from auto-activating\n" "further connections without user/manual intervention.\n\n")); } @@ -332,10 +333,10 @@ usage_device_delete (void) { g_printerr (_("Usage: nmcli device delete { ARGUMENTS | help }\n" "\n" - "ARGUMENTS := <ifname>\n" + "ARGUMENTS := <ifname> ...\n" "\n" - "Deletes the software device.\n" - "The command removes the interface. It only works for software devices\n" + "Delete the software devices.\n" + "The command removes the interfaces. It only works for software devices\n" "(like bonds, bridges, etc.). Hardware devices cannot be deleted by the\n" "command.\n\n")); } @@ -1702,28 +1703,86 @@ error: return nmc->return_value; } +typedef struct { + NmCli *nmc; + GSList *queue; + guint timeout_id; + gboolean cmd_disconnect; +} DeviceCbInfo; + +static void device_cb_info_finish (DeviceCbInfo *info, NMDevice *device); + +static gboolean +device_op_timeout_cb (gpointer user_data) +{ + DeviceCbInfo *info = user_data; + + timeout_cb (info->nmc); + device_cb_info_finish (info, NULL); + return G_SOURCE_REMOVE; +} + static void -disconnect_state_cb (NMDevice *device, GParamSpec *pspec, gpointer user_data) +device_removed_cb (NMClient *client, NMDevice *device, DeviceCbInfo *info) { - NMDeviceState state; + /* Success: device has been removed. + * It can also happen when disconnecting a software device. + */ + if (!g_slist_find (info->queue, device)) + return; - state = nm_device_get_state (device); + if (info->cmd_disconnect) + g_print (_("Device '%s' successfully disconnected.\n"), + nm_device_get_iface (device)); + else + g_print (_("Device '%s' successfully removed.\n"), + nm_device_get_iface (device)); + device_cb_info_finish (info, device); +} - if (state == NM_DEVICE_STATE_DISCONNECTED) { - g_signal_handlers_disconnect_by_data (device, user_data); +static void +disconnect_state_cb (NMDevice *device, GParamSpec *pspec, DeviceCbInfo *info) +{ + if (!g_slist_find (info->queue, device)) + return; + + if (nm_device_get_state (device) == NM_DEVICE_STATE_DISCONNECTED) { g_print (_("Device '%s' successfully disconnected.\n"), nm_device_get_iface (device)); - quit (); + device_cb_info_finish (info, device); } } static void -device_removed_cb (NMClient *client, NMDevice *device, gpointer user_data) +destroy_queue_element (gpointer data) { - /* Success: device has been removed. It happens when disconnecting a software device. */ - g_signal_handlers_disconnect_by_data (client, user_data); - g_print (_("Device '%s' successfully disconnected.\n"), - nm_device_get_iface (device)); + g_signal_handlers_disconnect_matched (data, G_SIGNAL_MATCH_FUNC, 0, 0, 0, + disconnect_state_cb, NULL); + g_object_unref (data); +} + +static void +device_cb_info_finish (DeviceCbInfo *info, NMDevice *device) +{ + if (device) { + GSList *elem = g_slist_find (info->queue, device); + if (!elem) + return; + info->queue = g_slist_delete_link (info->queue, elem); + g_signal_handlers_disconnect_by_func (device, disconnect_state_cb, info); + g_object_unref (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_signal_handlers_disconnect_by_func (info->nmc->client, device_removed_cb, info); + g_slice_free (DeviceCbInfo, info); quit (); } @@ -1731,36 +1790,32 @@ static void disconnect_device_cb (GObject *object, GAsyncResult *result, gpointer user_data) { NMDevice *device = NM_DEVICE (object); - NmCli *nmc = (NmCli *) user_data; + DeviceCbInfo *info = (DeviceCbInfo *) user_data; + NmCli *nmc = info->nmc; NMDeviceState state; GError *error = NULL; if (!nm_device_disconnect_finish (device, result, &error)) { - g_string_printf (nmc->return_text, _("Error: Device '%s' (%s) disconnecting failed: %s"), - nm_device_get_iface (device), - nm_object_get_path (NM_OBJECT (device)), - error->message); + g_string_printf (nmc->return_text, _("Error: not all devices disconnected.")); + g_printerr (_("Error: Device '%s' (%s) disconnecting failed: %s\n"), + nm_device_get_iface (device), + nm_object_get_path (NM_OBJECT (device)), + error->message); g_error_free (error); nmc->return_value = NMC_RESULT_ERROR_DEV_DISCONNECT; - quit (); + device_cb_info_finish (info, device); } else { state = nm_device_get_state (device); - if (nmc->nowait_flag || state == NM_DEVICE_STATE_DISCONNECTED) { /* Don't want to wait or device already disconnected */ if (state == NM_DEVICE_STATE_DISCONNECTED) { if (nmc->print_output == NMC_PRINT_PRETTY) nmc_terminal_erase_line (); - g_print (_("Device '%s' has been disconnected.\n"), nm_device_get_iface (device)); + g_print (_("Device '%s' successfully disconnected.\n"), + nm_device_get_iface (device)); } - quit (); - } else { - g_signal_connect (device, "notify::state", G_CALLBACK (disconnect_state_cb), nmc); - g_signal_connect (nmc->client, NM_CLIENT_DEVICE_REMOVED, G_CALLBACK (device_removed_cb), nmc); - /* Start timer not to loop forever if "notify::state" signal is not issued */ - g_timeout_add_seconds (nmc->timeout, timeout_cb, nmc); + device_cb_info_finish (info, device); } - } } @@ -1768,9 +1823,12 @@ static NMCResultCode do_device_disconnect (NmCli *nmc, int argc, char **argv) { NMDevice **devices; - NMDevice *device = NULL; - const char *ifname = NULL; - char *ifname_ask = NULL; + NMDevice *device; + DeviceCbInfo *info = NULL; + GSList *queue = NULL, *iter; + char **arg_arr = NULL; + char **arg_ptr = argv; + int arg_num = argc; int i; /* Set default timeout for disconnect operation. */ @@ -1778,59 +1836,78 @@ do_device_disconnect (NmCli *nmc, int argc, char **argv) nmc->timeout = 10; if (argc == 0) { - if (nmc->ask) - ifname = ifname_ask = nmc_readline (PROMPT_INTERFACE); - - if (!ifname_ask) { + if (nmc->ask) { + char *line = nmc_readline (PROMPT_INTERFACES); + nmc_string_to_arg_array (line, NULL, FALSE, &arg_arr, &arg_num); + g_free (line); + arg_ptr = 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; } - } else { - ifname = *argv; - } - - if (!ifname) { - g_string_printf (nmc->return_text, _("Error: No interface specified.")); - nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; - goto error; - } - - if (next_arg (&argc, &argv) == 0) { - g_string_printf (nmc->return_text, _("Error: extra argument not allowed: '%s'."), *argv); - nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; - goto error; } devices = get_devices_sorted (nmc->client); - for (i = 0; devices[i]; i++) { - NMDevice *candidate = devices[i]; - const char *dev_iface = nm_device_get_iface (candidate); + while (arg_num > 0) { + device = NULL; + for (i = 0; devices[i]; i++) { + if (!g_strcmp0 (nm_device_get_iface (devices[i]), *arg_ptr)) { + device = devices[i]; + break; + } + } - if (!g_strcmp0 (dev_iface, ifname)) - device = candidate; + if (device) { + if (!g_slist_find (queue, device)) + queue = g_slist_prepend (queue, device); + else + g_printerr (_("Warning: argument '%s' is duplicated.\n"), *arg_ptr); + } else { + g_printerr (_("Error: Device '%s' not found.\n"), *arg_ptr); + g_string_printf (nmc->return_text, _("Error: not all devices found.")); + nmc->return_value = NMC_RESULT_ERROR_NOT_FOUND; + } + + /* Take next argument */ + next_arg (&arg_num, &arg_ptr); } g_free (devices); - if (!device) { - g_string_printf (nmc->return_text, _("Error: Device '%s' not found."), ifname); + if (!queue) { + g_string_printf (nmc->return_text, _("Error: no valid device provided.")); nmc->return_value = NMC_RESULT_ERROR_NOT_FOUND; goto error; } + queue = g_slist_reverse (queue); + + info = g_slice_new0 (DeviceCbInfo); + info->nmc = nmc; + info->cmd_disconnect = TRUE; + if (nmc->timeout > 0) + info->timeout_id = g_timeout_add_seconds (nmc->timeout, device_op_timeout_cb, info); + + g_signal_connect (nmc->client, NM_CLIENT_DEVICE_REMOVED, + G_CALLBACK (device_removed_cb), info); - /* - * Use nowait_flag instead of should_wait, because exiting has to be postponed - * till disconnect_device_cb() is called, giving NM time to check our permissions. - */ nmc->nowait_flag = (nmc->timeout == 0); nmc->should_wait = TRUE; - nm_device_disconnect_async (device, NULL, disconnect_device_cb, nmc); - /* Start progress indication */ - if (nmc->print_output == NMC_PRINT_PRETTY) - progress_id = g_timeout_add (120, progress_cb, device); + for (iter = queue; iter; iter = g_slist_next (iter)) { + device = iter->data; + + 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, NULL, disconnect_device_cb, info); + } error: + g_strfreev (arg_arr); + g_slist_free (queue); return nmc->return_value; } @@ -1838,27 +1915,35 @@ static void delete_device_cb (GObject *object, GAsyncResult *result, gpointer user_data) { NMDevice *device = NM_DEVICE (object); - NmCli *nmc = (NmCli *) user_data; + DeviceCbInfo *info = (DeviceCbInfo *) user_data; + NmCli *nmc = info->nmc; GError *error = NULL; if (!nm_device_delete_finish (device, result, &error)) { - g_string_printf (nmc->return_text, _("Error: Device '%s' (%s) deletion failed: %s"), - nm_device_get_iface (device), - nm_object_get_path (NM_OBJECT (device)), - error->message); + g_string_printf (nmc->return_text, _("Error: not all devices deleted.")); + g_printerr (_("Error: Device '%s' (%s) deletion failed: %s\n"), + nm_device_get_iface (device), + nm_object_get_path (NM_OBJECT (device)), + error->message); g_error_free (error); nmc->return_value = NMC_RESULT_ERROR_UNKNOWN; + device_cb_info_finish (info, device); + } else { + if (nmc->nowait_flag) + device_cb_info_finish (info, device); } - quit (); } static NMCResultCode do_device_delete (NmCli *nmc, int argc, char **argv) { NMDevice **devices; - NMDevice *device = NULL; - const char *ifname = NULL; - char *ifname_ask = NULL; + NMDevice *device; + DeviceCbInfo *info = NULL; + GSList *queue = NULL, *iter; + char **arg_arr = NULL; + char **arg_ptr = argv; + int arg_num = argc; int i; /* Set default timeout for delete operation. */ @@ -1866,64 +1951,82 @@ do_device_delete (NmCli *nmc, int argc, char **argv) nmc->timeout = 10; if (argc == 0) { - if (nmc->ask) - ifname = ifname_ask = nmc_readline (PROMPT_INTERFACE); - - if (!ifname_ask) { + if (nmc->ask) { + char *line = nmc_readline (PROMPT_INTERFACES); + nmc_string_to_arg_array (line, NULL, FALSE, &arg_arr, &arg_num); + g_free (line); + arg_ptr = 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; } - } else - ifname = *argv; - - if (!ifname) { - g_string_printf (nmc->return_text, _("Error: No interface specified.")); - nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; - goto error; - } - - if (next_arg (&argc, &argv) == 0) { - g_string_printf (nmc->return_text, _("Error: extra argument not allowed: '%s'."), *argv); - nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; - goto error; } devices = get_devices_sorted (nmc->client); - for (i = 0; devices[i]; i++) { - NMDevice *candidate = devices[i]; - const char *dev_iface = nm_device_get_iface (candidate); + while (arg_num > 0) { + device = NULL; + for (i = 0; devices[i]; i++) { + if (!g_strcmp0 (nm_device_get_iface (devices[i]), *arg_ptr)) { + device = devices[i]; + break; + } + } - if (!g_strcmp0 (dev_iface, ifname)) - device = candidate; + if (device) { + if (!g_slist_find (queue, device)) { + if (nm_device_is_software (device)) + queue = g_slist_prepend (queue, device); + else { + g_printerr (_("Error: Device '%s' is a hardware device. It can't be deleted.\n"), + *arg_ptr); + g_string_printf (nmc->return_text, _("Error: not all devices valid.")); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + } + } else + g_printerr (_("Warning: argument '%s' is duplicated.\n"), *arg_ptr); + } else { + g_printerr (_("Error: Device '%s' not found.\n"), *arg_ptr); + g_string_printf (nmc->return_text, _("Error: not all devices found.")); + nmc->return_value = NMC_RESULT_ERROR_NOT_FOUND; + } + + /* Take next argument */ + next_arg (&arg_num, &arg_ptr); } g_free (devices); - if (!device) { - g_string_printf (nmc->return_text, _("Error: Device '%s' not found."), ifname); + if (!queue) { + g_string_printf (nmc->return_text, _("Error: no valid device provided.")); nmc->return_value = NMC_RESULT_ERROR_NOT_FOUND; goto error; } + queue = g_slist_reverse (queue); - if (!nm_device_is_software (device)) { - g_string_printf (nmc->return_text, _("Error: Device '%s' is a hardware device. It can't be deleted."), ifname); - nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; - goto error; - } + info = g_slice_new0 (DeviceCbInfo); + info->nmc = nmc; + if (nmc->timeout > 0) + info->timeout_id = g_timeout_add_seconds (nmc->timeout, device_op_timeout_cb, info); + + g_signal_connect (nmc->client, NM_CLIENT_DEVICE_REMOVED, + G_CALLBACK (device_removed_cb), info); - /* - * Use nowait_flag instead of should_wait, because exiting has to be postponed - * till delete_device_cb() is called, giving NM time to check our permissions. - */ nmc->nowait_flag = (nmc->timeout == 0); nmc->should_wait = TRUE; - nm_device_delete_async (device, NULL, delete_device_cb, nmc); - /* Start progress indication */ - if (nmc->print_output == NMC_PRINT_PRETTY) - progress_id = g_timeout_add (120, progress_cb, device); + 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); + } error: + g_strfreev (arg_arr); + g_slist_free (queue); return nmc->return_value; } @@ -2842,7 +2945,7 @@ extern NmCli nm_cli; static char * gen_func_ifnames (const char *text, int state) { - int i, j = 0; + int i; const GPtrArray *devices; const char **ifnames; char *ret; @@ -2856,9 +2959,9 @@ gen_func_ifnames (const char *text, int state) for (i = 0; i < devices->len; i++) { NMDevice *dev = g_ptr_array_index (devices, i); const char *ifname = nm_device_get_iface (dev); - ifnames[j++] = ifname; + ifnames[i] = ifname; } - ifnames[j] = NULL; + ifnames[i] = NULL; ret = nmc_rl_gen_func_basic (text, state, ifnames); @@ -2875,14 +2978,17 @@ nmcli_device_tab_completion (const char *text, int start, int end) /* Disable readline's default filename completion */ rl_attempted_completion_over = 1; - /* Disable appending space after completion */ - rl_completion_append_character = '\0'; + if (g_strcmp0 (rl_prompt, PROMPT_INTERFACE) == 0) { + /* Disable appending space after completion */ + rl_completion_append_character = '\0'; - if (!is_single_word (rl_line_buffer)) - return NULL; + if (!is_single_word (rl_line_buffer)) + return NULL; - if (g_strcmp0 (rl_prompt, PROMPT_INTERFACE) == 0) generator_func = gen_func_ifnames; + } else if (g_strcmp0 (rl_prompt, PROMPT_INTERFACES) == 0) { + generator_func = gen_func_ifnames; + } if (generator_func) match_array = rl_completion_matches (text, generator_func); diff --git a/clients/cli/nmcli-completion b/clients/cli/nmcli-completion index 301ce9d266..51c817affe 100644 --- a/clients/cli/nmcli-completion +++ b/clients/cli/nmcli-completion @@ -1252,10 +1252,14 @@ _nmcli() fi ;; sh|sho|show| \ - c|co|con|conn|conne|connec|connect| \ + c|co|con|conn|conne|connec|connect) + if [[ ${#words[@]} -eq 3 ]]; then + _nmcli_compl_COMMAND_nl "${words[2]}" "$(_nmcli_dev_status DEVICE)" + fi + ;; d|di|dis|disc|disco|discon|disconn|disconne|disconnec|disconnect| \ de|del|dele|delet|delete) - if [[ ${#words[@]} -eq 3 ]]; then + if [[ ${#words[@]} -ge 3 ]]; then _nmcli_compl_COMMAND_nl "${words[2]}" "$(_nmcli_dev_status DEVICE)" fi ;; diff --git a/man/nmcli.1.in b/man/nmcli.1.in index 65013b8b4e..0778152fcf 100644 --- a/man/nmcli.1.in +++ b/man/nmcli.1.in @@ -758,14 +758,15 @@ will be activated. It will also consider connections that are not set to auto co .br If '--wait' option is not specified, the default timeout will be 90 seconds. .TP -.B disconnect <ifname> +.B disconnect <ifname> ... .br Disconnect a device and prevent the device from automatically activating further -connections without user/manual intervention. +connections without user/manual intervention. Note that disconnecting software +devices may mean that the devices will disappear. .br If '--wait' option is not specified, the default timeout will be 10 seconds. .TP -.B delete <ifname> +.B delete <ifname> ... .br Delete a device. The command removes the interface from the system. Note that this only works for software devices like bonds, bridges, teams, etc. |