From df69bd1625e0d28aac3f910401780f6ae7d09641 Mon Sep 17 00:00:00 2001 From: Lubomir Rintel Date: Sun, 7 Jun 2015 15:26:43 +0200 Subject: [PATCH 1/4] cli: trivial: move is_property_valid() This removes one extra forward definition and saves one in future. --- clients/cli/connections.c | 49 +++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/clients/cli/connections.c b/clients/cli/connections.c index b4388647fe..cafe935f9d 100644 --- a/clients/cli/connections.c +++ b/clients/cli/connections.c @@ -4124,6 +4124,30 @@ do_questionnaire_ip (NMConnection *connection) maybe_ask_for_gateway (connection, AF_INET6); } +static NMSetting * +is_setting_valid (NMConnection *connection, const NameItem *valid_settings, char *setting) +{ + const char *setting_name; + + if (!(setting_name = check_valid_name (setting, valid_settings, NULL))) + return NULL; + return nm_connection_get_setting_by_name (connection, setting_name); +} + +static char * +is_property_valid (NMSetting *setting, const char *property, GError **error) +{ + char **valid_props = NULL; + const char *prop_name; + char *ret; + + valid_props = nmc_setting_get_valid_properties (setting); + prop_name = nmc_string_is_valid (property, (const char **) valid_props, error); + ret = g_strdup (prop_name); + g_strfreev (valid_props); + return ret; +} + static gboolean complete_connection_by_type (NMConnection *connection, const char *con_type, @@ -6379,7 +6403,6 @@ should_complete_vpn_uuids (const char *prompt, const char *line) return _get_and_check_property (prompt, line, uuid_properties, NULL, NULL); } -static char *is_property_valid (NMSetting *setting, const char *property, GError **error); static const char ** get_allowed_property_values (void) { @@ -7492,30 +7515,6 @@ split_editor_main_cmd_args (const char *str, char **setting, char **property, ch g_strfreev (args); } -static NMSetting * -is_setting_valid (NMConnection *connection, const NameItem *valid_settings, char *setting) -{ - const char *setting_name; - - if (!(setting_name = check_valid_name (setting, valid_settings, NULL))) - return NULL; - return nm_connection_get_setting_by_name (connection, setting_name); -} - -static char * -is_property_valid (NMSetting *setting, const char *property, GError **error) -{ - char **valid_props = NULL; - const char *prop_name; - char *ret; - - valid_props = nmc_setting_get_valid_properties (setting); - prop_name = nmc_string_is_valid (property, (const char **) valid_props, error); - ret = g_strdup (prop_name); - g_strfreev (valid_props); - return ret; -} - static NMSetting * create_setting_by_name (const char *name, const NameItem *valid_settings) { From 15149d915f9f0d0c7870a01b6f61723296495116 Mon Sep 17 00:00:00 2001 From: Lubomir Rintel Date: Sun, 7 Jun 2015 14:00:57 +0200 Subject: [PATCH 2/4] cli: refactor: split connection property reader from do_connection_modify() No functional change. We'll need it for specifying the properties on connection addition. --- clients/cli/connections.c | 261 +++++++++++++++++++++----------------- 1 file changed, 143 insertions(+), 118 deletions(-) diff --git a/clients/cli/connections.c b/clients/cli/connections.c index cafe935f9d..0d0e23e492 100644 --- a/clients/cli/connections.c +++ b/clients/cli/connections.c @@ -31,6 +31,7 @@ #include #include +#include "gsystem-local-alloc.h" #include "utils.h" #include "common.h" #include "settings.h" @@ -4148,6 +4149,141 @@ is_property_valid (NMSetting *setting, const char *property, GError **error) return ret; } +static gboolean +read_connection_properties (NMConnection *connection, + int argc, + char **argv, + GError **error) +{ + NMSetting *setting; + NMSettingConnection *s_con; + const char *con_type; + const char *s_dot_p; + const char *value; + char **strv = NULL; + const char *setting_name; + gboolean append = FALSE; + gboolean remove = FALSE; + gboolean success = FALSE; + GError *local = NULL; + + s_con = nm_connection_get_setting_connection (connection); + g_assert (s_con); + con_type = nm_setting_connection_get_connection_type (s_con); + + /* Go through arguments and set properties */ + while (argc) { + gs_free char *property_name = NULL; + + s_dot_p = *argv; + next_arg (&argc, &argv); + value = *argv; + next_arg (&argc, &argv); + + if (!s_dot_p) { + g_set_error_literal (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("Error: . argument is missing.")); + goto finish; + } + if (!value) { + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("Error: value for '%s' is missing."), s_dot_p); + goto finish; + } + /* Empty string will reset the value to default */ + if (value[0] == '\0') + value = NULL; + + if (s_dot_p[0] == '+') { + s_dot_p++; + append = TRUE; + } else if (s_dot_p[0] == '-') { + s_dot_p++; + remove = TRUE; + } + + strv = g_strsplit (s_dot_p, ".", 2); + if (g_strv_length (strv) != 2) { + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("Error: invalid . '%s'."), s_dot_p); + goto finish; + } + + setting_name = check_valid_name (strv[0], get_valid_settings_array (con_type), &local); + if (!setting_name) { + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("Error: invalid or not allowed setting '%s': %s."), + strv[0], local->message); + g_clear_error (&local); + goto finish; + } + setting = nm_connection_get_setting_by_name (connection, setting_name); + if (!setting) { + setting = nmc_setting_new_for_name (setting_name); + if (!setting) { + /* This should really not happen */ + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_UNKNOWN, + _("Error: don't know how to create '%s' setting."), + setting_name); + goto finish; + } + nm_connection_add_setting (connection, setting); + } + + property_name = is_property_valid (setting, strv[1], &local); + if (!property_name) { + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("Error: invalid property '%s': %s."), + strv[1], local->message); + g_clear_error (&local); + goto finish; + } + + if (!remove) { + /* Set/add value */ + if (!append) + nmc_setting_reset_property (setting, property_name, NULL); + if (!nmc_setting_set_property (setting, property_name, value, &local)) { + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("Error: failed to modify %s.%s: %s."), + strv[0], strv[1], local->message); + g_clear_error (&local); + goto finish; + } + } else { + /* Remove value + * - either empty: remove whole value + * - or specified by index <0-n>: remove item at the index + * - or option name: remove item with the option name + */ + if (value) { + unsigned long idx; + if (nmc_string_to_uint (value, TRUE, 0, G_MAXUINT32, &idx)) + nmc_setting_remove_property_option (setting, property_name, NULL, idx, &local); + else + nmc_setting_remove_property_option (setting, property_name, value, 0, &local); + if (local) { + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, + _("Error: failed to remove a value from %s.%s: %s."), + strv[0], strv[1], local->message); + g_clear_error (&local); + goto finish; + } + } else + nmc_setting_reset_property (setting, property_name, NULL); + } + + g_strfreev (strv); + strv = NULL; + } + + success = TRUE; +finish: + if (strv) + g_strfreev (strv); + return success; +} + static gboolean complete_connection_by_type (NMConnection *connection, const char *con_type, @@ -8763,20 +8899,11 @@ do_connection_modify (NmCli *nmc, { NMConnection *connection = NULL; NMRemoteConnection *rc = NULL; - NMSetting *setting; - NMSettingConnection *s_con; - const char *con_type; const char *name; const char *selector = NULL; - const char *s_dot_p; - const char *value; - char **strv = NULL; - const char *setting_name; - char *property_name = NULL; - gboolean append = FALSE; - gboolean remove = FALSE; GError *error = NULL; + nmc->return_value = NMC_RESULT_SUCCESS; nmc->should_wait = FALSE; if (argc == 0) { @@ -8816,9 +8943,6 @@ do_connection_modify (NmCli *nmc, nmc->return_value = NMC_RESULT_ERROR_NOT_FOUND; goto finish; } - s_con = nm_connection_get_setting_connection (NM_CONNECTION (rc)); - g_assert (s_con); - con_type = nm_setting_connection_get_connection_type (s_con); if (next_arg (&argc, &argv) != 0) { g_string_printf (nmc->return_text, _("Error: . argument is missing.")); @@ -8826,116 +8950,17 @@ do_connection_modify (NmCli *nmc, goto finish; } - /* Go through arguments and set properties */ - while (argc) { - s_dot_p = *argv; - next_arg (&argc, &argv); - value = *argv; - next_arg (&argc, &argv); - - if (!s_dot_p) { - g_string_printf (nmc->return_text, _("Error: . argument is missing.")); - nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; - goto finish; - } - if (!value) { - g_string_printf (nmc->return_text, _("Error: value for '%s' is missing."), s_dot_p); - nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; - goto finish; - } - /* Empty string will reset the value to default */ - if (value[0] == '\0') - value = NULL; - - if (s_dot_p[0] == '+') { - s_dot_p++; - append = TRUE; - } else if (s_dot_p[0] == '-') { - s_dot_p++; - remove = TRUE; - } - - strv = g_strsplit (s_dot_p, ".", 2); - if (g_strv_length (strv) != 2) { - g_string_printf (nmc->return_text, _("Error: invalid . '%s'."), - s_dot_p); - nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; - goto finish; - } - - setting_name = check_valid_name (strv[0], get_valid_settings_array (con_type), &error); - if (!setting_name) { - g_string_printf (nmc->return_text, _("Error: invalid or not allowed setting '%s': %s."), - strv[0], error->message); - nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; - goto finish; - } - setting = nm_connection_get_setting_by_name (NM_CONNECTION (rc), setting_name); - if (!setting) { - setting = nmc_setting_new_for_name (setting_name); - if (!setting) { - /* This should really not happen */ - g_string_printf (nmc->return_text, - "Error: don't know how to create '%s' setting.", - setting_name); - nmc->return_value = NMC_RESULT_ERROR_UNKNOWN; - goto finish; - } - nm_connection_add_setting (NM_CONNECTION (rc), setting); - } - - property_name = is_property_valid (setting, strv[1], &error); - if (!property_name) { - g_string_printf (nmc->return_text, _("Error: invalid property '%s': %s."), - strv[1], error->message); - nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; - goto finish; - } - - if (!remove) { - /* Set/add value */ - if (!append) - nmc_setting_reset_property (setting, property_name, NULL); - if (!nmc_setting_set_property (setting, property_name, value, &error)) { - g_string_printf (nmc->return_text, _("Error: failed to modify %s.%s: %s."), - strv[0], strv[1], error->message); - nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; - goto finish; - } - } else { - /* Remove value - * - either empty: remove whole value - * - or specified by index <0-n>: remove item at the index - * - or option name: remove item with the option name - */ - if (value) { - unsigned long idx; - if (nmc_string_to_uint (value, TRUE, 0, G_MAXUINT32, &idx)) - nmc_setting_remove_property_option (setting, property_name, NULL, idx, &error); - else - nmc_setting_remove_property_option (setting, property_name, value, 0, &error); - if (error) { - g_string_printf (nmc->return_text, _("Error: failed to remove a value from %s.%s: %s."), - strv[0], strv[1], error->message); - nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; - goto finish; - } - } else - nmc_setting_reset_property (setting, property_name, NULL); - } - - g_strfreev (strv); - strv = NULL; + if (!read_connection_properties (NM_CONNECTION (rc), argc, argv, &error)) { + g_string_printf (nmc->return_text, _("Error: %s."), error->message); + nmc->return_value = error->code; + g_clear_error (&error); + goto finish; } update_connection (!temporary, rc, modify_connection_cb, nmc); + nmc->should_wait = TRUE; finish: - nmc->should_wait = (nmc->return_value == NMC_RESULT_SUCCESS); - g_free (property_name); - if (strv) - g_strfreev (strv); - g_clear_error (&error); return nmc->return_value; } From b3e57cf3ca923397f269116e9cb5ec7b89b54988 Mon Sep 17 00:00:00 2001 From: Lubomir Rintel Date: Sun, 7 Jun 2015 15:35:32 +0200 Subject: [PATCH 3/4] cli: allow specifying arbitrary properties on "nmcli c add" Syntax: nmcli c add ... -- [+|-]. ... --- clients/cli/connections.c | 20 ++++++++++++++++---- man/nmcli.1.in | 12 +++++++++++- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/clients/cli/connections.c b/clients/cli/connections.c index 0d0e23e492..796a8947ca 100644 --- a/clients/cli/connections.c +++ b/clients/cli/connections.c @@ -253,7 +253,7 @@ usage (void) " show [--active] [--show-secrets] [id | uuid | path | apath] ...\n\n" " up [[id | uuid | path] ] [ifname ] [ap ] [passwd-file ]\n\n" " down [id | uuid | path | apath] ...\n\n" - " add COMMON_OPTIONS TYPE_SPECIFIC_OPTIONS IP_OPTIONS\n\n" + " add COMMON_OPTIONS TYPE_SPECIFIC_OPTIONS IP_OPTIONS [-- ([+|-]. )+]\n\n" " modify [--temporary] [id | uuid | path] ([+|-]. )+\n\n" " edit [id | uuid | path] \n" " edit [type ] [con-name ]\n\n" @@ -321,7 +321,7 @@ usage_connection_add (void) { g_printerr (_("Usage: nmcli connection add { ARGUMENTS | help }\n" "\n" - "ARGUMENTS := COMMON_OPTIONS TYPE_SPECIFIC_OPTIONS IP_OPTIONS\n\n" + "ARGUMENTS := COMMON_OPTIONS TYPE_SPECIFIC_OPTIONS IP_OPTIONS [-- ([+|-]. )+]\n\n" " COMMON_OPTIONS:\n" " type \n" " ifname | \"*\"\n" @@ -5568,8 +5568,8 @@ cleanup_olpc: ip4 = gw4 = ip6 = gw6 = NULL; - if (!nmc_parse_args (exp_args, TRUE, &argc, &argv, error)) - return FALSE; + if (!nmc_parse_args (exp_args, TRUE, &argc, &argv, NULL)) + break; /* coverity[dead_error_begin] */ if (ip4) { @@ -5643,6 +5643,18 @@ cleanup_olpc: do_questionnaire_ip (connection); } + if (argc) { + /* Set extra connection properties. */ + nmc_arg_t exp_args[] = { {"--", FALSE, NULL, TRUE}, + {NULL} }; + + if (!nmc_parse_args (exp_args, FALSE, &argc, &argv, error)) + return FALSE; + + if (!read_connection_properties (connection, argc, argv, error)) + return FALSE; + } + return TRUE; } diff --git a/man/nmcli.1.in b/man/nmcli.1.in index 87311f30d8..0c9434c232 100644 --- a/man/nmcli.1.in +++ b/man/nmcli.1.in @@ -418,7 +418,7 @@ See \fBconnection show\fP above for the description of the -specifying keywo .br If '--wait' option is not specified, the default timeout will be 10 seconds. .TP -.B add COMMON_OPTIONS TYPE_SPECIFIC_OPTIONS IP_OPTIONS +.B add COMMON_OPTIONS TYPE_SPECIFIC_OPTIONS IP_OPTIONS [-- [+|-]. ...] .br Add a connection for NetworkManager. Arguments differ according to connection types, see below. .RS @@ -644,6 +644,11 @@ to be sent back out through the slave the frame was received on (default: yes) .IP "\fI[ip6 ] [gw6 ]\fP" 42 \(en IPv6 addresses .RE +.RS +If a \fI--\fP argument is encountered, the rest of command line is interpreted +as property list in the same format as \fIconnection modify\fP command accepts. +This makes it possible to adjust the connection properties before it's added. +.RE .TP .B edit [id | uuid | path ] - edit an existing connection .RE @@ -997,6 +1002,11 @@ and disables the connection's "autoconnect" flag. non-interactively adds a VLAN connection with ID 55. The connection will use eth0 and the VLAN interface will be named Maxipes\(hyfik. +.IP "\fB\f(CWnmcli c a ifname eth0 type ethernet -- ipv4.method disabled ipv6.method link-local\fP\fP" +.IP +non-interactively adds a connection that will use eth0 Ethernet interface and only have an IPv6 link-local +address configured. + .IP "\fB\f(CWnmcli connection edit ethernet\-em1\-2\fP\fP" .IP edits existing "ethernet\(hyem1\(hy2" connection in the interactive editor. From 81f1e3da4f1398d2f0f17d064f6a66026148caca Mon Sep 17 00:00:00 2001 From: Lubomir Rintel Date: Wed, 1 Jul 2015 11:38:52 +0200 Subject: [PATCH 4/4] cli: add bash completion for 'nmcli c add -- ' Use the editor to obtain a list of possible properties for a type of connection. Let 'nmcli c modify' completion reuse it as well, to avoid code duplication. --- clients/cli/nmcli-completion | 69 ++++++++++++++++++++++-------------- 1 file changed, 43 insertions(+), 26 deletions(-) diff --git a/clients/cli/nmcli-completion b/clients/cli/nmcli-completion index 54433ef742..08c78ba062 100644 --- a/clients/cli/nmcli-completion +++ b/clients/cli/nmcli-completion @@ -688,6 +688,25 @@ _nmcli_compl_COMMAND_nl() { _nmcli_list_nl "$(printf "%s%s\n%s" "" "$V" "$a")" } +_nmcli_compl_PROPERTIES() +{ + while [[ "${#words[@]}" -gt 0 ]]; do + if [[ ${#words[@]} -le 1 ]]; then + local PREFIX="" + + if [[ "${words[0]:0:1}" == [+-] ]]; then + PREFIX="${words[0]:0:1}" + fi + _nmcli_list_nl "$(echo -e 'print\nquit\nyes' |nmcli c edit "$@" 2>/dev/null |awk -F: '/\..*:/ {print "'$PREFIX'"$1}')" + return 0 + elif [[ ${#words[@]} -le 2 ]]; then + return 0 + fi + _nmcli_array_delete_at words 0 1 + done + _nmcli_list_nl "$(echo -e 'print\nquit\nyes' |nmcli c edit "$@" 2>/dev/null |awk -F: '/\..*:/ {print $1}')" +} + _nmcli() { local cur prev words cword i @@ -712,7 +731,7 @@ _nmcli() cur='' fi - local OPTIONS_UNKNOWN_OPTION OPTIONS_TYPE OPTIONS_TYPED OPTIONS OPTIONS_MANDATORY COMMAND_ARGS_WAIT_OPTIONS OPTIONS_IP OPTIONS_MANDATORY OPTIONS_NEXT_GROUP + local OPTIONS_UNKNOWN_OPTION OPTIONS_TYPE OPTIONS_TYPED OPTIONS OPTIONS_MANDATORY COMMAND_ARGS_WAIT_OPTIONS OPTIONS_IP OPTIONS_MANDATORY OPTIONS_NEXT_GROUP OPTIONS_SEP local COMMAND_CONNECTION_TYPE COMMAND_CONNECTION_ID OPTIONS_MANDATORY_IFNAME HELP_ONLY_AS_FIRST local COMMAND_CONNECTION_ACTIVE="" @@ -956,6 +975,7 @@ _nmcli() fi OPTIONS_IP=(ip4 ip6 gw4 gw6) + OPTIONS_SEP=(--) OPTIONS_MANDATORY=() case "$OPTIONS_TYPE" in 802-3|802-3-|802-3-e|802-3-et|802-3-eth|802-3-ethe|802-3-ether|802-3-ethern|802-3-etherne|802-3-ethernet| \ @@ -1052,7 +1072,7 @@ _nmcli() if [[ "${#OPTIONS_MANDATORY[@]}" -gt 0 ]]; then _nmcli_list "$(echo "${OPTIONS[@]}") $(echo "${OPTIONS_TYPED[@]}")" else - _nmcli_list "$(echo "${OPTIONS[@]}") $(echo "${OPTIONS_TYPED[@]}") $(echo "${OPTIONS_IP[@]}")" + _nmcli_list "$(echo "${OPTIONS[@]}") $(echo "${OPTIONS_TYPED[@]}") $(echo "${OPTIONS_IP[@]}") $(echo "${OPTIONS_SEP[@]}")" fi return 0 fi @@ -1069,7 +1089,7 @@ _nmcli() if [[ "${#OPTIONS_MANDATORY[@]}" -gt 0 ]]; then _nmcli_list "$(echo "${OPTIONS[@]}")" else - _nmcli_list "$(echo "${OPTIONS[@]}") $(echo "${OPTIONS_IP[@]}")" + _nmcli_list "$(echo "${OPTIONS[@]}") $(echo "${OPTIONS_IP[@]}") $(echo "${OPTIONS_SEP[@]}")" fi return 0 fi @@ -1080,11 +1100,12 @@ _nmcli() # we have an unknown option, but still mandatory ones that must be fullfiled first. return 0 fi - if ! _nmcli_array_has_value OPTIONS_IP "${OPTIONS_UNKNOWN_OPTION:1}"; then - # the unknown option is NOT an IP option. + if ! (_nmcli_array_has_value OPTIONS_IP "${OPTIONS_UNKNOWN_OPTION:1}" || + _nmcli_array_has_value OPTIONS_SEP "${OPTIONS_UNKNOWN_OPTION:1}"); then + # the unknown option is neither an IP option nor a separator. return 0 fi - # The unknown option is an IP option, which is fine... continue... + # The unknown option is an IP option or a separator, which is fine... continue... fi fi @@ -1101,8 +1122,9 @@ _nmcli() fi if [[ "$OPTIONS_UNKNOWN_OPTION" != "" ]]; then - if ! _nmcli_array_has_value OPTIONS_IP "${OPTIONS_UNKNOWN_OPTION:1}"; then - # the unknown option is NOT an IP option. + if ! (_nmcli_array_has_value OPTIONS_IP "${OPTIONS_UNKNOWN_OPTION:1}" || + _nmcli_array_has_value OPTIONS_SEP "${OPTIONS_UNKNOWN_OPTION:1}"); then + # the unknown option is neither an IP option nor a separator. return 0 fi fi @@ -1110,8 +1132,8 @@ _nmcli() # no mandatory options... do final completion including IP options - OPTIONS=("${OPTIONS[@]}" "${OPTIONS_IP[@]}") - OPTIONS_NEXT_GROUP=("${OPTIONS_IP[@]}") + OPTIONS=("${OPTIONS[@]}" "${OPTIONS_IP[@]}" "${OPTIONS_SEP[@]}") + OPTIONS_NEXT_GROUP=("${OPTIONS_IP[@]}" "${OPTIONS_SEP[@]}") _nmcli_compl_ARGS && return 0 if [[ "$OPTIONS_UNKNOWN_OPTION" != "" ]]; then @@ -1125,11 +1147,18 @@ _nmcli() return 0 fi + # process the last group of options, as the OPTIONS_TYPED are already handled... - OPTIONS=("${OPTIONS_IP[@]}") - OPTIONS_NEXT_GROUP=() + OPTIONS=("${OPTIONS_IP[@]}" "${OPTIONS_SEP[@]}") + OPTIONS_NEXT_GROUP=("${OPTIONS_SEP[@]}") COMMAND_ARGS_WAIT_OPTIONS=0 _nmcli_compl_ARGS && return 0 + + _nmcli_array_delete_at words 0 + _nmcli_compl_PROPERTIES type "$OPTIONS_TYPE" + + return 0 + fi ;; e|ed|edi|edit) @@ -1186,21 +1215,9 @@ _nmcli() OPTIONS=(id uuid path) _nmcli_compl_ARGS_CONNECTION && return 0 - while [[ "${#words[@]}" -gt 0 ]]; do - if [[ ${#words[@]} -le 1 ]]; then - local PREFIX="" - if [[ "${words[0]:0:1}" == [+-] ]]; then - PREFIX="${words[0]:0:1}" - fi - _nmcli_list_nl "$(nmcli --fields profile connection show ${COMMAND_CONNECTION_TYPE} "$COMMAND_CONNECTION_ID" 2>/dev/null | sed -n 's/^\([^:]\+\):.*/'$PREFIX'\1/p')" - return 0 - elif [[ ${#words[@]} -le 2 ]]; then - return 0 - fi - _nmcli_array_delete_at words 0 1 - done - _nmcli_list_nl "$(nmcli --fields profile connection show ${COMMAND_CONNECTION_TYPE} "$COMMAND_CONNECTION_ID" 2>/dev/null | sed -n 's/^\([^:]\+\):.*/\1/p')" + _nmcli_compl_PROPERTIES ${COMMAND_CONNECTION_TYPE} "$COMMAND_CONNECTION_ID" + return 0 fi ;;