summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJiří Klimeš <jklimes@redhat.com>2015-03-11 19:10:58 +0100
committerJiří Klimeš <jklimes@redhat.com>2015-03-13 11:04:28 +0100
commitb174f43af012d814db3beba253337e442cca873a (patch)
tree2034a9fed2015a0146638c845fbf0b0bdd641f08
parent27bd0b73177565f44a337054dc57e82495ea6112 (diff)
downloadNetworkManager-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.c354
-rw-r--r--clients/cli/nmcli-completion8
-rw-r--r--man/nmcli.1.in7
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.