merge,cli: branch 'lr/cli-add-properties'

Make it possible to specify properties upon connection addition.

Addresses all these:

https://bugzilla.gnome.org/show_bug.cgi?id=750590
https://bugzilla.gnome.org/show_bug.cgi?id=698076
https://bugzilla.gnome.org/show_bug.cgi?id=740749
This commit is contained in:
Lubomir Rintel 2015-07-02 09:34:09 +02:00
commit 39ed60dc60
3 changed files with 237 additions and 174 deletions

View file

@ -31,6 +31,7 @@
#include <readline/readline.h>
#include <readline/history.h>
#include "gsystem-local-alloc.h"
#include "utils.h"
#include "common.h"
#include "settings.h"
@ -252,7 +253,7 @@ usage (void)
" show [--active] [--show-secrets] [id | uuid | path | apath] <ID> ...\n\n"
" up [[id | uuid | path] <ID>] [ifname <ifname>] [ap <BSSID>] [passwd-file <file with passwords>]\n\n"
" down [id | uuid | path | apath] <ID> ...\n\n"
" add COMMON_OPTIONS TYPE_SPECIFIC_OPTIONS IP_OPTIONS\n\n"
" add COMMON_OPTIONS TYPE_SPECIFIC_OPTIONS IP_OPTIONS [-- ([+|-]<setting>.<property> <value>)+]\n\n"
" modify [--temporary] [id | uuid | path] <ID> ([+|-]<setting>.<property> <value>)+\n\n"
" edit [id | uuid | path] <ID>\n"
" edit [type <new_con_type>] [con-name <new_con_name>]\n\n"
@ -320,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 [-- ([+|-]<setting>.<property> <value>)+]\n\n"
" COMMON_OPTIONS:\n"
" type <type>\n"
" ifname <interface name> | \"*\"\n"
@ -4124,6 +4125,165 @@ 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
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: <setting>.<property> 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 <setting>.<property> '%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,
@ -5408,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) {
@ -5483,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;
}
@ -6379,7 +6551,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 +7663,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)
{
@ -8764,20 +8911,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) {
@ -8817,9 +8955,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: <setting>.<property> argument is missing."));
@ -8827,116 +8962,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: <setting>.<property> 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 <setting>.<property> '%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;
}

View file

@ -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
;;

View file

@ -418,7 +418,7 @@ See \fBconnection show\fP above for the description of the <ID>-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 [-- [+|-]<setting>.<property> <value> ...]
.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 <IPv6 address>] [gw6 <IPv6 gateway>]\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 ] <ID> - 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.