From 90e8691111889a7b5f3c812f5a41f15a8a058913 Mon Sep 17 00:00:00 2001 From: Michael Biebl Date: Tue, 7 Nov 2017 00:14:39 +0100 Subject: New upstream version 1.9.90 --- clients/common/nm-client-utils.c | 517 +++ clients/common/nm-client-utils.h | 59 + clients/common/nm-meta-setting-access.c | 638 +++ clients/common/nm-meta-setting-access.h | 101 + clients/common/nm-meta-setting-desc.c | 7284 +++++++++++++++++++++++++++++++ clients/common/nm-meta-setting-desc.h | 444 ++ clients/common/nm-secret-agent-simple.c | 73 +- clients/common/nm-vpn-helpers.c | 41 +- clients/common/settings-docs.c | 363 ++ clients/common/settings-docs.c.in | 363 ++ clients/common/settings-docs.xsl | 49 + clients/common/tests/test-general.c | 162 + 12 files changed, 10054 insertions(+), 40 deletions(-) create mode 100644 clients/common/nm-client-utils.c create mode 100644 clients/common/nm-client-utils.h create mode 100644 clients/common/nm-meta-setting-access.c create mode 100644 clients/common/nm-meta-setting-access.h create mode 100644 clients/common/nm-meta-setting-desc.c create mode 100644 clients/common/nm-meta-setting-desc.h create mode 100644 clients/common/settings-docs.c create mode 100644 clients/common/settings-docs.c.in create mode 100644 clients/common/settings-docs.xsl create mode 100644 clients/common/tests/test-general.c (limited to 'clients/common') diff --git a/clients/common/nm-client-utils.c b/clients/common/nm-client-utils.c new file mode 100644 index 00000000..bf4dcc77 --- /dev/null +++ b/clients/common/nm-client-utils.c @@ -0,0 +1,517 @@ +/* nmcli - command-line tool to control NetworkManager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright 2010 - 2017 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-client-utils.h" + +#include "nm-device-bond.h" +#include "nm-device-bridge.h" +#include "nm-device-team.h" + +/* + * Convert string to unsigned integer. + * If required, the resulting number is checked to be in the range. + */ +static gboolean +nmc_string_to_uint_base (const char *str, + int base, + gboolean range_check, + unsigned long int min, + unsigned long int max, + unsigned long int *value) +{ + char *end; + unsigned long int tmp; + + errno = 0; + tmp = strtoul (str, &end, base); + if (errno || *end != '\0' || (range_check && (tmp < min || tmp > max))) { + return FALSE; + } + *value = tmp; + return TRUE; +} + +gboolean +nmc_string_to_uint (const char *str, + gboolean range_check, + unsigned long int min, + unsigned long int max, + unsigned long int *value) +{ + return nmc_string_to_uint_base (str, 10, range_check, min, max, value); +} + +gboolean +nmc_string_to_bool (const char *str, gboolean *val_bool, GError **error) +{ + const char *s_true[] = { "true", "yes", "on", "1", NULL }; + const char *s_false[] = { "false", "no", "off", "0", NULL }; + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + if (g_strcmp0 (str, "o") == 0) { + g_set_error (error, 1, 0, + /* Translators: the first %s is the partial value entered by + * the user, the second %s a list of compatible values. + */ + _("'%s' is ambiguous (%s)"), str, "on x off"); + return FALSE; + } + + if (nmc_string_is_valid (str, s_true, NULL)) + *val_bool = TRUE; + else if (nmc_string_is_valid (str, s_false, NULL)) + *val_bool = FALSE; + else { + g_set_error (error, 1, 0, + _("'%s' is not valid; use [%s] or [%s]"), + str, "true, yes, on", "false, no, off"); + return FALSE; + } + return TRUE; +} + +gboolean +nmc_string_to_tristate (const char *str, NMCTriStateValue *val, GError **error) +{ + const char *s_true[] = { "true", "yes", "on", NULL }; + const char *s_false[] = { "false", "no", "off", NULL }; + const char *s_unknown[] = { "unknown", NULL }; + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + if (g_strcmp0 (str, "o") == 0) { + g_set_error (error, 1, 0, + /* Translators: the first %s is the partial value entered by + * the user, the second %s a list of compatible values. + */ + _("'%s' is ambiguous (%s)"), str, "on x off"); + return FALSE; + } + + if (nmc_string_is_valid (str, s_true, NULL)) + *val = NMC_TRI_STATE_YES; + else if (nmc_string_is_valid (str, s_false, NULL)) + *val = NMC_TRI_STATE_NO; + else if (nmc_string_is_valid (str, s_unknown, NULL)) + *val = NMC_TRI_STATE_UNKNOWN; + else { + g_set_error (error, 1, 0, + _("'%s' is not valid; use [%s], [%s] or [%s]"), + str, "true, yes, on", "false, no, off", "unknown"); + return FALSE; + } + return TRUE; +} + +/* + * Check whether 'input' is contained in 'allowed' array. It performs case + * insensitive comparison and supports shortcut strings if they are unique. + * Returns: a pointer to found string in allowed array on success or NULL. + * On failure: error->code : 0 - string not found; 1 - string is ambiguous + */ +const char * +nmc_string_is_valid (const char *input, const char **allowed, GError **error) +{ + const char **p; + size_t input_ln, p_len; + gboolean prev_match = FALSE; + const char *ret = NULL; + + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + if (!input || !*input) + goto finish; + + input_ln = strlen (input); + for (p = allowed; p && *p; p++) { + p_len = strlen (*p); + if (g_ascii_strncasecmp (input, *p, input_ln) == 0) { + if (input_ln == p_len) { + ret = *p; + break; + } + if (!prev_match) + ret = *p; + else { + g_set_error (error, 1, 1, _("'%s' is ambiguous (%s x %s)"), + input, ret, *p); + return NULL; + } + prev_match = TRUE; + } + } + +finish: + if (ret == NULL) { + char *valid_vals = g_strjoinv (", ", (char **) allowed); + if (!input || !*input) + g_set_error (error, 1, 0, _("missing name, try one of [%s]"), valid_vals); + else + g_set_error (error, 1, 0, _("'%s' not among [%s]"), input, valid_vals); + + g_free (valid_vals); + } + return ret; +} + +/* + * Wrapper function for g_strsplit_set() that removes empty strings + * from the vector as they are not useful in most cases. + */ +char ** +nmc_strsplit_set (const char *str, const char *delimiter, int max_tokens) +{ + /* remove empty strings */ + return _nm_utils_strv_cleanup (g_strsplit_set (str, delimiter, max_tokens), + FALSE, TRUE, FALSE); +} + +gboolean +matches (const char *cmd, const char *pattern) +{ + size_t len = strlen (cmd); + if (!len || len > strlen (pattern)) + return FALSE; + return memcmp (pattern, cmd, len) == 0; +} + +const char * +nmc_bond_validate_mode (const char *mode, GError **error) +{ + unsigned long mode_int; + static const char *valid_modes[] = { "balance-rr", + "active-backup", + "balance-xor", + "broadcast", + "802.3ad", + "balance-tlb", + "balance-alb", + NULL }; + if (nmc_string_to_uint (mode, TRUE, 0, 6, &mode_int)) { + /* Translate bonding mode numbers to mode names: + * https://www.kernel.org/doc/Documentation/networking/bonding.txt + */ + return valid_modes[mode_int]; + } else + return nmc_string_is_valid (mode, valid_modes, error); +} + +const char * +nmc_device_state_to_string (NMDeviceState state) +{ + switch (state) { + case NM_DEVICE_STATE_UNMANAGED: + return _("unmanaged"); + case NM_DEVICE_STATE_UNAVAILABLE: + return _("unavailable"); + case NM_DEVICE_STATE_DISCONNECTED: + return _("disconnected"); + case NM_DEVICE_STATE_PREPARE: + return _("connecting (prepare)"); + case NM_DEVICE_STATE_CONFIG: + return _("connecting (configuring)"); + case NM_DEVICE_STATE_NEED_AUTH: + return _("connecting (need authentication)"); + case NM_DEVICE_STATE_IP_CONFIG: + return _("connecting (getting IP configuration)"); + case NM_DEVICE_STATE_IP_CHECK: + return _("connecting (checking IP connectivity)"); + case NM_DEVICE_STATE_SECONDARIES: + return _("connecting (starting secondary connections)"); + case NM_DEVICE_STATE_ACTIVATED: + return _("connected"); + case NM_DEVICE_STATE_DEACTIVATING: + return _("deactivating"); + case NM_DEVICE_STATE_FAILED: + return _("connection failed"); + case NM_DEVICE_STATE_UNKNOWN: + return _("unknown"); + } + + return _("unknown"); +} + +const char * +nmc_device_metered_to_string (NMMetered value) +{ + switch (value) { + case NM_METERED_YES: + return _("yes"); + case NM_METERED_NO: + return _("no"); + case NM_METERED_GUESS_YES: + return _("yes (guessed)"); + case NM_METERED_GUESS_NO: + return _("no (guessed)"); + case NM_METERED_UNKNOWN: + return _("unknown"); + } + + return _("unknown"); +} + +const char * +nmc_device_reason_to_string (NMDeviceStateReason reason) +{ + switch (reason) { + case NM_DEVICE_STATE_REASON_NONE: + return _("No reason given"); + case NM_DEVICE_STATE_REASON_UNKNOWN: + return _("Unknown error"); + case NM_DEVICE_STATE_REASON_NOW_MANAGED: + return _("Device is now managed"); + case NM_DEVICE_STATE_REASON_NOW_UNMANAGED: + return _("Device is now unmanaged"); + case NM_DEVICE_STATE_REASON_CONFIG_FAILED: + return _("The device could not be readied for configuration"); + case NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE: + return _("IP configuration could not be reserved (no available address, timeout, etc.)"); + case NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED: + return _("The IP configuration is no longer valid"); + case NM_DEVICE_STATE_REASON_NO_SECRETS: + return _("Secrets were required, but not provided"); + case NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT: + return _("802.1X supplicant disconnected"); + case NM_DEVICE_STATE_REASON_SUPPLICANT_CONFIG_FAILED: + return _("802.1X supplicant configuration failed"); + case NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED: + return _("802.1X supplicant failed"); + case NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT: + return _("802.1X supplicant took too long to authenticate"); + case NM_DEVICE_STATE_REASON_PPP_START_FAILED: + return _("PPP service failed to start"); + case NM_DEVICE_STATE_REASON_PPP_DISCONNECT: + return _("PPP service disconnected"); + case NM_DEVICE_STATE_REASON_PPP_FAILED: + return _("PPP failed"); + case NM_DEVICE_STATE_REASON_DHCP_START_FAILED: + return _("DHCP client failed to start"); + case NM_DEVICE_STATE_REASON_DHCP_ERROR: + return _("DHCP client error"); + case NM_DEVICE_STATE_REASON_DHCP_FAILED: + return _("DHCP client failed"); + case NM_DEVICE_STATE_REASON_SHARED_START_FAILED: + return _("Shared connection service failed to start"); + case NM_DEVICE_STATE_REASON_SHARED_FAILED: + return _("Shared connection service failed"); + case NM_DEVICE_STATE_REASON_AUTOIP_START_FAILED: + return _("AutoIP service failed to start"); + case NM_DEVICE_STATE_REASON_AUTOIP_ERROR: + return _("AutoIP service error"); + case NM_DEVICE_STATE_REASON_AUTOIP_FAILED: + return _("AutoIP service failed"); + case NM_DEVICE_STATE_REASON_MODEM_BUSY: + return _("The line is busy"); + case NM_DEVICE_STATE_REASON_MODEM_NO_DIAL_TONE: + return _("No dial tone"); + case NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER: + return _("No carrier could be established"); + case NM_DEVICE_STATE_REASON_MODEM_DIAL_TIMEOUT: + return _("The dialing request timed out"); + case NM_DEVICE_STATE_REASON_MODEM_DIAL_FAILED: + return _("The dialing attempt failed"); + case NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED: + return _("Modem initialization failed"); + case NM_DEVICE_STATE_REASON_GSM_APN_FAILED: + return _("Failed to select the specified APN"); + case NM_DEVICE_STATE_REASON_GSM_REGISTRATION_NOT_SEARCHING: + return _("Not searching for networks"); + case NM_DEVICE_STATE_REASON_GSM_REGISTRATION_DENIED: + return _("Network registration denied"); + case NM_DEVICE_STATE_REASON_GSM_REGISTRATION_TIMEOUT: + return _("Network registration timed out"); + case NM_DEVICE_STATE_REASON_GSM_REGISTRATION_FAILED: + return _("Failed to register with the requested network"); + case NM_DEVICE_STATE_REASON_GSM_PIN_CHECK_FAILED: + return _("PIN check failed"); + case NM_DEVICE_STATE_REASON_FIRMWARE_MISSING: + return _("Necessary firmware for the device may be missing"); + case NM_DEVICE_STATE_REASON_REMOVED: + return _("The device was removed"); + case NM_DEVICE_STATE_REASON_SLEEPING: + return _("NetworkManager went to sleep"); + case NM_DEVICE_STATE_REASON_CONNECTION_REMOVED: + return _("The device's active connection disappeared"); + case NM_DEVICE_STATE_REASON_USER_REQUESTED: + return _("Device disconnected by user or client"); + case NM_DEVICE_STATE_REASON_CARRIER: + return _("Carrier/link changed"); + case NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED: + return _("The device's existing connection was assumed"); + case NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE: + return _("The supplicant is now available"); + case NM_DEVICE_STATE_REASON_MODEM_NOT_FOUND: + return _("The modem could not be found"); + case NM_DEVICE_STATE_REASON_BT_FAILED: + return _("The Bluetooth connection failed or timed out"); + case NM_DEVICE_STATE_REASON_GSM_SIM_NOT_INSERTED: + return _("GSM Modem's SIM card not inserted"); + case NM_DEVICE_STATE_REASON_GSM_SIM_PIN_REQUIRED: + return _("GSM Modem's SIM PIN required"); + case NM_DEVICE_STATE_REASON_GSM_SIM_PUK_REQUIRED: + return _("GSM Modem's SIM PUK required"); + case NM_DEVICE_STATE_REASON_GSM_SIM_WRONG: + return _("GSM Modem's SIM wrong"); + case NM_DEVICE_STATE_REASON_INFINIBAND_MODE: + return _("InfiniBand device does not support connected mode"); + case NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED: + return _("A dependency of the connection failed"); + case NM_DEVICE_STATE_REASON_BR2684_FAILED: + return _("A problem with the RFC 2684 Ethernet over ADSL bridge"); + case NM_DEVICE_STATE_REASON_MODEM_MANAGER_UNAVAILABLE: + return _("ModemManager is unavailable"); + case NM_DEVICE_STATE_REASON_SSID_NOT_FOUND: + return _("The Wi-Fi network could not be found"); + case NM_DEVICE_STATE_REASON_SECONDARY_CONNECTION_FAILED: + return _("A secondary connection of the base connection failed"); + case NM_DEVICE_STATE_REASON_DCB_FCOE_FAILED: + return _("DCB or FCoE setup failed"); + case NM_DEVICE_STATE_REASON_TEAMD_CONTROL_FAILED: + return _("teamd control failed"); + case NM_DEVICE_STATE_REASON_MODEM_FAILED: + return _("Modem failed or no longer available"); + case NM_DEVICE_STATE_REASON_MODEM_AVAILABLE: + return _("Modem now ready and available"); + case NM_DEVICE_STATE_REASON_SIM_PIN_INCORRECT: + return _("SIM PIN was incorrect"); + case NM_DEVICE_STATE_REASON_NEW_ACTIVATION: + return _("New connection activation was enqueued"); + case NM_DEVICE_STATE_REASON_PARENT_CHANGED: + return _("The device's parent changed"); + case NM_DEVICE_STATE_REASON_PARENT_MANAGED_CHANGED: + return _("The device parent's management changed"); + + case NM_DEVICE_STATE_REASON_OVSDB_FAILED: + return _("OpenVSwitch database connection failed"); + } + + /* TRANSLATORS: Unknown reason for a device state change (NMDeviceStateReason) */ + return _("Unknown"); +} + +const char * +nm_active_connection_state_reason_to_string (NMActiveConnectionStateReason reason) +{ + switch (reason) { + case NM_ACTIVE_CONNECTION_STATE_REASON_UNKNOWN: + return _("Unknown reason"); + case NM_ACTIVE_CONNECTION_STATE_REASON_NONE: + return _("The connection was disconnected"); + case NM_ACTIVE_CONNECTION_STATE_REASON_USER_DISCONNECTED: + return _("Disconnected by user"); + case NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_DISCONNECTED: + return _("The base network connection was interrupted"); + case NM_ACTIVE_CONNECTION_STATE_REASON_SERVICE_STOPPED: + return _("The VPN service stopped unexpectedly"); + case NM_ACTIVE_CONNECTION_STATE_REASON_IP_CONFIG_INVALID: + return _("The VPN service returned invalid configuration"); + case NM_ACTIVE_CONNECTION_STATE_REASON_CONNECT_TIMEOUT: + return _("The connection attempt timed out"); + case NM_ACTIVE_CONNECTION_STATE_REASON_SERVICE_START_TIMEOUT: + return _("The VPN service did not start in time"); + case NM_ACTIVE_CONNECTION_STATE_REASON_SERVICE_START_FAILED: + return _("The VPN service failed to start"); + case NM_ACTIVE_CONNECTION_STATE_REASON_NO_SECRETS: + return _("No valid secrets"); + case NM_ACTIVE_CONNECTION_STATE_REASON_LOGIN_FAILED: + return _("Invalid secrets"); + case NM_ACTIVE_CONNECTION_STATE_REASON_CONNECTION_REMOVED: + return _("The connection was removed"); + case NM_ACTIVE_CONNECTION_STATE_REASON_DEPENDENCY_FAILED: + return _("Master connection failed"); + case NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_REALIZE_FAILED: + return _("Could not create a software link"); + case NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_REMOVED: + return _("The device disappeared"); + default: + /* TRANSLATORS: Unknown reason for a connection state change (NMActiveConnectionStateReason) */ + return _("Unknown"); + } +} + +NMActiveConnectionState +nmc_activation_get_effective_state (NMActiveConnection *active, + NMDevice *device, + const char **reason) +{ + NMActiveConnectionState ac_state; + NMActiveConnectionStateReason ac_reason; + NMDeviceState dev_state = NM_DEVICE_STATE_UNKNOWN; + NMDeviceStateReason dev_reason = NM_DEVICE_STATE_REASON_UNKNOWN; + + g_return_val_if_fail (active, NM_ACTIVE_CONNECTION_STATE_UNKNOWN); + g_return_val_if_fail (reason, NM_ACTIVE_CONNECTION_STATE_UNKNOWN); + + *reason = NULL; + ac_reason = nm_active_connection_get_state_reason (active); + + if (device) { + dev_state = nm_device_get_state (device); + dev_reason = nm_device_get_state_reason (device); + } + + ac_state = nm_active_connection_get_state (active); + switch (ac_state) { + case NM_ACTIVE_CONNECTION_STATE_DEACTIVATED: + if ( !device + || ac_reason != NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_DISCONNECTED + || nm_device_get_active_connection (device) != active) { + /* (1) + * - we have no device, + * - or, @ac_reason is specific + * - or, @device no longer references the current @active + * >> we complete with @ac_reason. */ + *reason = nm_active_connection_state_reason_to_string (ac_reason); + } else if ( dev_state <= NM_DEVICE_STATE_DISCONNECTED + || dev_state >= NM_DEVICE_STATE_FAILED) { + /* (2) + * - not (1) + * - and, the device is no longer in an activated state, + * >> we complete with @dev_reason. */ + *reason = nmc_device_reason_to_string (dev_reason); + } else { + /* (3) + * we wait for the device go disconnect. We will get a better + * failure reason from the device (2). */ + return NM_ACTIVE_CONNECTION_STATE_UNKNOWN; + } + break; + case NM_ACTIVE_CONNECTION_STATE_ACTIVATING: + /* activating master connection does not automatically activate any slaves, so their + * active connection state will not progress beyond ACTIVATING state. + * Monitor the device instead. */ + if ( device + && ( NM_IS_DEVICE_BOND (device) + || NM_IS_DEVICE_TEAM (device) + || NM_IS_DEVICE_BRIDGE (device)) + && dev_state >= NM_DEVICE_STATE_IP_CONFIG + && dev_state <= NM_DEVICE_STATE_ACTIVATED) { + *reason = "master waiting for slaves"; + return NM_ACTIVE_CONNECTION_STATE_ACTIVATED; + } + break; + default: + break; + } + + return ac_state; +} diff --git a/clients/common/nm-client-utils.h b/clients/common/nm-client-utils.h new file mode 100644 index 00000000..ac18fe94 --- /dev/null +++ b/clients/common/nm-client-utils.h @@ -0,0 +1,59 @@ +/* nmcli - command-line tool to control NetworkManager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright 2010 - 2017 Red Hat, Inc. + */ + +#ifndef __NM_CLIENT_UTILS_H__ +#define __NM_CLIENT_UTILS_H__ + +#include "nm-meta-setting.h" +#include "nm-active-connection.h" +#include "nm-device.h" + +typedef enum { + NMC_TRI_STATE_NO, + NMC_TRI_STATE_YES, + NMC_TRI_STATE_UNKNOWN, +} NMCTriStateValue; + +const char *nmc_string_is_valid (const char *input, const char **allowed, GError **error); + +char **nmc_strsplit_set (const char *str, const char *delimiter, int max_tokens); + +gboolean nmc_string_to_uint (const char *str, + gboolean range_check, + unsigned long int min, + unsigned long int max, + unsigned long int *value); +gboolean nmc_string_to_bool (const char *str, gboolean *val_bool, GError **error); +gboolean nmc_string_to_tristate (const char *str, NMCTriStateValue *val, GError **error); + +gboolean matches (const char *cmd, const char *pattern); + +/* FIXME: don't expose this function on it's own, at least not from this file. */ +const char *nmc_bond_validate_mode (const char *mode, GError **error); + +const char *nm_active_connection_state_reason_to_string (NMActiveConnectionStateReason reason); +const char *nmc_device_state_to_string (NMDeviceState state); +const char *nmc_device_reason_to_string (NMDeviceStateReason reason); +const char *nmc_device_metered_to_string (NMMetered value); + +NMActiveConnectionState nmc_activation_get_effective_state (NMActiveConnection *active, + NMDevice *device, + const char **reason); + +#endif /* __NM_CLIENT_UTILS_H__ */ diff --git a/clients/common/nm-meta-setting-access.c b/clients/common/nm-meta-setting-access.c new file mode 100644 index 00000000..cd7ef783 --- /dev/null +++ b/clients/common/nm-meta-setting-access.c @@ -0,0 +1,638 @@ +/* NetworkManager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright 2010 - 2017 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-meta-setting-access.h" + +/*****************************************************************************/ + +const NMMetaSettingInfoEditor * +nm_meta_setting_info_editor_find_by_name (const char *setting_name, gboolean use_alias) +{ + const NMMetaSettingInfo *meta_setting_info; + const NMMetaSettingInfoEditor *setting_info; + guint i; + + g_return_val_if_fail (setting_name, NULL); + + meta_setting_info = nm_meta_setting_infos_by_name (setting_name); + setting_info = NULL; + if (meta_setting_info) { + nm_assert (nm_streq0 (meta_setting_info->setting_name, setting_name)); + if (meta_setting_info->meta_type < G_N_ELEMENTS (nm_meta_setting_infos_editor)) { + setting_info = &nm_meta_setting_infos_editor[meta_setting_info->meta_type]; + nm_assert (setting_info->general == meta_setting_info); + } + } + if (!setting_info && use_alias) { + for (i = 0; i < _NM_META_SETTING_TYPE_NUM; i++) { + if (nm_streq0 (nm_meta_setting_infos_editor[i].alias, setting_name)) { + setting_info = &nm_meta_setting_infos_editor[i]; + break; + } + } + } + + return setting_info; +} + +const NMMetaSettingInfoEditor * +nm_meta_setting_info_editor_find_by_gtype (GType gtype) +{ + const NMMetaSettingInfo *meta_setting_info; + const NMMetaSettingInfoEditor *setting_info; + + meta_setting_info = nm_meta_setting_infos_by_gtype (gtype); + + if (!meta_setting_info) + return NULL; + + g_return_val_if_fail (meta_setting_info->get_setting_gtype, NULL); + g_return_val_if_fail (meta_setting_info->get_setting_gtype () == gtype, NULL); + + if (meta_setting_info->meta_type >= G_N_ELEMENTS (nm_meta_setting_infos_editor)) + return NULL; + + setting_info = &nm_meta_setting_infos_editor[meta_setting_info->meta_type]; + + g_return_val_if_fail (setting_info->general == meta_setting_info, NULL); + + return setting_info; +} + +const NMMetaSettingInfoEditor * +nm_meta_setting_info_editor_find_by_setting (NMSetting *setting) +{ + const NMMetaSettingInfoEditor *setting_info; + + g_return_val_if_fail (NM_IS_SETTING (setting), NULL); + + setting_info = nm_meta_setting_info_editor_find_by_gtype (G_OBJECT_TYPE (setting)); + + nm_assert (setting_info == nm_meta_setting_info_editor_find_by_name (nm_setting_get_name (setting), FALSE)); + nm_assert (!setting_info || G_TYPE_CHECK_INSTANCE_TYPE (setting, setting_info->general->get_setting_gtype ())); + + return setting_info; +} + +const NMMetaPropertyInfo * +nm_meta_setting_info_editor_get_property_info (const NMMetaSettingInfoEditor *setting_info, const char *property_name) +{ + guint i; + + g_return_val_if_fail (setting_info, NULL); + g_return_val_if_fail (property_name, NULL); + + for (i = 0; i < setting_info->properties_num; i++) { + nm_assert (setting_info->properties[i]->property_name); + nm_assert (setting_info->properties[i]->setting_info == setting_info); + if (nm_streq (setting_info->properties[i]->property_name, property_name)) + return setting_info->properties[i]; + } + + return NULL; +} + +const NMMetaPropertyInfo * +nm_meta_property_info_find_by_name (const char *setting_name, const char *property_name) +{ + const NMMetaSettingInfoEditor *setting_info; + const NMMetaPropertyInfo *property_info; + + setting_info = nm_meta_setting_info_editor_find_by_name (setting_name, FALSE); + if (!setting_info) + return NULL; + + property_info = nm_meta_setting_info_editor_get_property_info (setting_info, property_name); + if (!property_info) + return NULL; + + nm_assert (property_info->setting_info == setting_info); + + return property_info; +} + +const NMMetaPropertyInfo * +nm_meta_property_info_find_by_setting (NMSetting *setting, const char *property_name) +{ + const NMMetaSettingInfoEditor *setting_info; + const NMMetaPropertyInfo *property_info; + + setting_info = nm_meta_setting_info_editor_find_by_setting (setting); + if (!setting_info) + return NULL; + property_info = nm_meta_setting_info_editor_get_property_info (setting_info, property_name); + if (!property_info) + return NULL; + + nm_assert (property_info->setting_info == setting_info); + nm_assert (property_info == nm_meta_property_info_find_by_name (nm_setting_get_name (setting), property_name)); + + return property_info; +} + +NMSetting * +nm_meta_setting_info_editor_new_setting (const NMMetaSettingInfoEditor *setting_info, + NMMetaAccessorSettingInitType init_type) +{ + NMSetting *setting; + + g_return_val_if_fail (setting_info, NULL); + + setting = g_object_new (setting_info->general->get_setting_gtype (), NULL); + + if ( setting_info->setting_init_fcn + && init_type != NM_META_ACCESSOR_SETTING_INIT_TYPE_DEFAULT) { + setting_info->setting_init_fcn (setting_info, + setting, + init_type); + } + + return setting; +} + +/*****************************************************************************/ + +const NMMetaSettingInfoEditor *const* +nm_meta_setting_infos_editor_p (void) +{ + static const NMMetaSettingInfoEditor *cache[_NM_META_SETTING_TYPE_NUM + 1] = { NULL }; + guint i; + + if (G_UNLIKELY (!cache[0])) { + for (i = 0; i < _NM_META_SETTING_TYPE_NUM; i++) + cache[i] = &nm_meta_setting_infos_editor[i]; + } + return cache; +} + +/*****************************************************************************/ + +const char * +nm_meta_abstract_info_get_name (const NMMetaAbstractInfo *abstract_info, gboolean for_header) +{ + const char *n; + + nm_assert (abstract_info); + nm_assert (abstract_info->meta_type); + nm_assert (abstract_info->meta_type->get_name); + n = abstract_info->meta_type->get_name (abstract_info, for_header); + nm_assert (n && n[0]); + return n; +} + +const NMMetaAbstractInfo *const* +nm_meta_abstract_info_get_nested (const NMMetaAbstractInfo *abstract_info, + guint *out_len, + gpointer *nested_to_free) +{ + const NMMetaAbstractInfo *const*nested; + guint l = 0; + gs_free gpointer f = NULL; + + nm_assert (abstract_info); + nm_assert (abstract_info->meta_type); + nm_assert (nested_to_free && !*nested_to_free); + + if (abstract_info->meta_type->get_nested) { + nested = abstract_info->meta_type->get_nested (abstract_info, &l, &f); + nm_assert ((nested ? g_strv_length ((char **) nested) : 0) == l); + if (nested && nested[0]) { + NM_SET_OUT (out_len, l); + *nested_to_free = g_steal_pointer (&f); + return nested; + } + } + NM_SET_OUT (out_len, 0); + return NULL; +} + +gconstpointer +nm_meta_abstract_info_get (const NMMetaAbstractInfo *abstract_info, + const NMMetaEnvironment *environment, + gpointer environment_user_data, + gpointer target, + NMMetaAccessorGetType get_type, + NMMetaAccessorGetFlags get_flags, + NMMetaAccessorGetOutFlags *out_flags, + gpointer *out_to_free) +{ + nm_assert (abstract_info); + nm_assert (abstract_info->meta_type); + nm_assert (!out_to_free || !*out_to_free); + nm_assert (out_flags); + + *out_flags = NM_META_ACCESSOR_GET_OUT_FLAGS_NONE; + + if (!abstract_info->meta_type->get_fcn) + g_return_val_if_reached (NULL); + + return abstract_info->meta_type->get_fcn (abstract_info, + environment, + environment_user_data, + target, + get_type, + get_flags, + out_flags, + out_to_free); +} + +const char *const* +nm_meta_abstract_info_complete (const NMMetaAbstractInfo *abstract_info, + const NMMetaEnvironment *environment, + gpointer environment_user_data, + const NMMetaOperationContext *operation_context, + const char *text, + char ***out_to_free) +{ + const char *const*values; + gsize i, j, text_len; + + nm_assert (abstract_info); + nm_assert (abstract_info->meta_type); + nm_assert (out_to_free && !*out_to_free); + + *out_to_free = NULL; + + if (!abstract_info->meta_type->complete_fcn) + return NULL; + + values = abstract_info->meta_type->complete_fcn (abstract_info, + environment, + environment_user_data, + operation_context, + text, + out_to_free); + + nm_assert (!*out_to_free || values == (const char *const*) *out_to_free); + + if (!values) + return NULL; + + if (!values[0]) { + nm_clear_g_free (out_to_free); + return NULL; + } + + if (!text || !text[0]) + return values; + + /* for convenience, we allow the complete_fcn() implementations to + * ignore "text". We filter out invalid matches here. */ + + text_len = strlen (text); + + if (*out_to_free) { + char **v = *out_to_free; + + for (i = 0, j = 0; v[i]; i++) { + if (strncmp (v[i], text, text_len) != 0) { + g_free (v[i]); + continue; + } + v[j++] = v[i]; + } + if (j) + v[j++] = NULL; + else { + g_free (v); + *out_to_free = v = NULL; + } + return (const char *const*) v; + } else { + const char *const*v = values; + char **r; + + for (i = 0, j = 0; v[i]; i++) { + if (strncmp (v[i], text, text_len) != 0) + continue; + j++; + } + if (j == i) + return values; + else if (!j) + return NULL; + + r = g_new (char *, j + 1); + v = values; + for (i = 0, j = 0; v[i]; i++) { + if (strncmp (v[i], text, text_len) != 0) + continue; + r[j++] = g_strdup (v[i]); + } + r[j++] = NULL; + return (const char *const*) (*out_to_free = r); + } +} + +/*****************************************************************************/ + +char * +nm_meta_abstract_info_get_nested_names_str (const NMMetaAbstractInfo *abstract_info, const char *name_prefix) +{ + gs_free gpointer nested_to_free = NULL; + guint i; + const NMMetaAbstractInfo *const*nested; + GString *allowed_fields; + + nested = nm_meta_abstract_info_get_nested (abstract_info, NULL, &nested_to_free); + if (!nested) + return NULL; + + allowed_fields = g_string_sized_new (256); + + if (!name_prefix) + name_prefix = nm_meta_abstract_info_get_name (abstract_info, FALSE); + + for (i = 0; nested[i]; i++) { + g_string_append_printf (allowed_fields, "%s.%s,", + name_prefix, nm_meta_abstract_info_get_name (nested[i], FALSE)); + } + g_string_truncate (allowed_fields, allowed_fields->len - 1); + return g_string_free (allowed_fields, FALSE); +} + +char * +nm_meta_abstract_infos_get_names_str (const NMMetaAbstractInfo *const*fields_array, const char *name_prefix) +{ + GString *allowed_fields; + guint i; + + if (!fields_array || !fields_array[0]) + return NULL; + + allowed_fields = g_string_sized_new (256); + for (i = 0; fields_array[i]; i++) { + if (name_prefix) + g_string_append_printf (allowed_fields, "%s.", name_prefix); + g_string_append_printf (allowed_fields, "%s,", nm_meta_abstract_info_get_name (fields_array[i], FALSE)); + } + g_string_truncate (allowed_fields, allowed_fields->len - 1); + return g_string_free (allowed_fields, FALSE); +} + +/*****************************************************************************/ + +typedef struct { + guint idx; + gsize self_offset_plus_1; + gsize sub_offset_plus_1; +} OutputSelectionItem; + +static NMMetaSelectionResultList * +_output_selection_pack (const NMMetaAbstractInfo *const* fields_array, + GArray *array, + GString *str) +{ + NMMetaSelectionResultList *result; + guint i; + guint len; + + len = array ? array->len : 0; + + /* re-organize the collected output data in one buffer that can be freed using + * g_free(). This makes allocation more complicated, but saves us from special + * handling for free. */ + result = g_malloc0 (sizeof (NMMetaSelectionResultList) + (len * sizeof (NMMetaSelectionItem)) + (str ? str->len : 0)); + *((guint *) &result->num) = len; + if (len > 0) { + char *pdata = &((char *) result)[sizeof (NMMetaSelectionResultList) + (len * sizeof (NMMetaSelectionItem))]; + + if (str) + memcpy (pdata, str->str, str->len); + for (i = 0; i < len; i++) { + const OutputSelectionItem *a = &g_array_index (array, OutputSelectionItem, i); + NMMetaSelectionItem *p = (NMMetaSelectionItem *) &result->items[i]; + + p->info = fields_array[a->idx]; + p->idx = a->idx; + if (a->self_offset_plus_1 > 0) + p->self_selection = &pdata[a->self_offset_plus_1 - 1]; + if (a->sub_offset_plus_1 > 0) + p->sub_selection = &pdata[a->sub_offset_plus_1 - 1]; + } + } + + return result; +} + +static gboolean +_output_selection_select_one (const NMMetaAbstractInfo *const* fields_array, + const char *fields_prefix, + const char *fields_str, + gboolean validate_nested, + GArray **p_array, + GString **p_str, + GError **error) +{ + guint i, j; + const char *i_name; + const char *right; + gboolean found = FALSE; + const NMMetaAbstractInfo *fields_array_failure = NULL; + gs_free char *fields_str_clone = NULL; + + nm_assert (fields_str); + nm_assert (p_array); + nm_assert (p_str); + nm_assert (!error || !*error); + + right = strchr (fields_str, '.'); + if (right) { + fields_str_clone = g_strdup (fields_str); + fields_str_clone[right - fields_str] = '\0'; + i_name = fields_str_clone; + right = &fields_str_clone[right - fields_str + 1]; + } else + i_name = fields_str; + + if (!fields_array) + goto not_found; + + for (i = 0; fields_array[i]; i++) { + const NMMetaAbstractInfo *fi = fields_array[i]; + const NMMetaAbstractInfo *const*nested; + gs_free gpointer nested_to_free = NULL; + + if (g_ascii_strcasecmp (i_name, nm_meta_abstract_info_get_name (fi, FALSE)) != 0) + continue; + + if (!right || !validate_nested) { + found = TRUE; + break; + } + + nested = nm_meta_abstract_info_get_nested (fi, NULL, &nested_to_free); + if (nested) { + for (j = 0; nested[j]; nested++) { + if (g_ascii_strcasecmp (right, nm_meta_abstract_info_get_name (nested[j], FALSE)) == 0) { + found = TRUE; + break; + } + } + } + fields_array_failure = fields_array[i]; + break; + } + + if (!found) { +not_found: + if ( !right + && !fields_prefix + && ( !g_ascii_strcasecmp (i_name, "all") + || !g_ascii_strcasecmp (i_name, "common"))) + g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, _("field '%s' has to be alone"), i_name); + else { + gs_free char *allowed_fields = NULL; + + if (fields_array_failure) { + gs_free char *p = NULL; + + if (fields_prefix) { + p = g_strdup_printf ("%s.%s", fields_prefix, + nm_meta_abstract_info_get_name (fields_array_failure, FALSE)); + } + allowed_fields = nm_meta_abstract_info_get_nested_names_str (fields_array_failure, p); + } else + allowed_fields = nm_meta_abstract_infos_get_names_str (fields_array, NULL); + + g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, _("invalid field '%s%s%s%s%s'; %s%s%s"), + fields_prefix ?: "", fields_prefix ? "." : "", + i_name, right ? "." : "", right ?: "", + NM_PRINT_FMT_QUOTED (allowed_fields, "allowed fields: ", allowed_fields, "", "no fields")); + } + return FALSE; + } + + { + GString *str; + OutputSelectionItem s = { + .idx = i, + }; + + if (!*p_str) + *p_str = g_string_sized_new (64); + str = *p_str; + + s.self_offset_plus_1 = str->len + 1; + if (fields_prefix) { + g_string_append (str, fields_prefix); + g_string_append_c (str, '.'); + } + g_string_append_len (str, i_name, strlen (i_name) + 1); + + if (right) { + s.sub_offset_plus_1 = str->len + 1; + g_string_append_len (str, right, strlen (right) + 1); + } + + if (!*p_array) + *p_array = g_array_new (FALSE, FALSE, sizeof (OutputSelectionItem)); + g_array_append_val (*p_array, s); + } + + return TRUE; +} + +NMMetaSelectionResultList * +nm_meta_selection_create_all (const NMMetaAbstractInfo *const* fields_array) +{ + gs_unref_array GArray *array = NULL; + guint i; + + if (fields_array) { + array = g_array_new (FALSE, FALSE, sizeof (OutputSelectionItem)); + for (i = 0; fields_array[i]; i++) { + OutputSelectionItem s = { + .idx = i, + }; + + g_array_append_val (array, s); + } + } + + return _output_selection_pack (fields_array, array, NULL); +} + +NMMetaSelectionResultList * +nm_meta_selection_create_parse_one (const NMMetaAbstractInfo *const* fields_array, + const char *fields_prefix, + const char *fields_str, /* one field selector (contains no commas) and is already stripped of spaces. */ + gboolean validate_nested, + GError **error) +{ + gs_unref_array GArray *array = NULL; + nm_auto_free_gstring GString *str = NULL; + + g_return_val_if_fail (!error || !*error, NULL); + nm_assert (fields_str && !strchr (fields_str, ',')); + + if (!_output_selection_select_one (fields_array, + fields_prefix, + fields_str, + validate_nested, + &array, + &str, + error)) + return NULL; + return _output_selection_pack (fields_array, array, str); + +} + +NMMetaSelectionResultList * +nm_meta_selection_create_parse_list (const NMMetaAbstractInfo *const* fields_array, + const char *fields_prefix, + const char *fields_str, /* a comma separated list of selectors */ + gboolean validate_nested, + GError **error) +{ + gs_unref_array GArray *array = NULL; + nm_auto_free_gstring GString *str = NULL; + gs_free char *fields_str_clone = NULL; + char *fields_str_cur; + char *fields_str_next; + + g_return_val_if_fail (!error || !*error, NULL); + + if (!fields_str) + return nm_meta_selection_create_all (fields_array); + + fields_str_clone = g_strdup (fields_str); + for (fields_str_cur = fields_str_clone; fields_str_cur; fields_str_cur = fields_str_next) { + fields_str_cur = nm_str_skip_leading_spaces (fields_str_cur); + fields_str_next = strchr (fields_str_cur, ','); + if (fields_str_next) + *fields_str_next++ = '\0'; + + g_strchomp (fields_str_cur); + if (!fields_str_cur[0]) + continue; + if (!_output_selection_select_one (fields_array, + fields_prefix, + fields_str_cur, + validate_nested, + &array, + &str, + error)) + return NULL; + } + + return _output_selection_pack (fields_array, array, str); +} diff --git a/clients/common/nm-meta-setting-access.h b/clients/common/nm-meta-setting-access.h new file mode 100644 index 00000000..54fc6c84 --- /dev/null +++ b/clients/common/nm-meta-setting-access.h @@ -0,0 +1,101 @@ +/* NetworkManager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright 2010 - 2017 Red Hat, Inc. + */ + +#ifndef _NM_META_SETTING_ACCESS_H__ +#define _NM_META_SETTING_ACCESS_H__ + +#include "nm-meta-setting.h" +#include "nm-meta-setting-desc.h" + +/*****************************************************************************/ + +NMSetting *nm_meta_setting_info_editor_new_setting (const NMMetaSettingInfoEditor *setting_info, + NMMetaAccessorSettingInitType init_type); + +const NMMetaSettingInfoEditor *nm_meta_setting_info_editor_find_by_name (const char *setting_name, gboolean use_alias); +const NMMetaSettingInfoEditor *nm_meta_setting_info_editor_find_by_gtype (GType gtype); +const NMMetaSettingInfoEditor *nm_meta_setting_info_editor_find_by_setting (NMSetting *setting); + +const NMMetaPropertyInfo *nm_meta_setting_info_editor_get_property_info (const NMMetaSettingInfoEditor *setting_info, + const char *property_name); +const NMMetaPropertyInfo *nm_meta_property_info_find_by_name (const char *setting_name, + const char *property_name); +const NMMetaPropertyInfo *nm_meta_property_info_find_by_setting (NMSetting *setting, + const char *property_name); + +/*****************************************************************************/ + +const NMMetaSettingInfoEditor *const*nm_meta_setting_infos_editor_p (void); + +/*****************************************************************************/ + +const char *nm_meta_abstract_info_get_name (const NMMetaAbstractInfo *abstract_info, gboolean for_header); + +const NMMetaAbstractInfo *const*nm_meta_abstract_info_get_nested (const NMMetaAbstractInfo *abstract_info, + guint *out_len, + gpointer *nested_to_free); + +gconstpointer nm_meta_abstract_info_get (const NMMetaAbstractInfo *abstract_info, + const NMMetaEnvironment *environment, + gpointer environment_user_data, + gpointer target, + NMMetaAccessorGetType get_type, + NMMetaAccessorGetFlags get_flags, + NMMetaAccessorGetOutFlags *out_flags, + gpointer *out_to_free); + +const char *const*nm_meta_abstract_info_complete (const NMMetaAbstractInfo *abstract_info, + const NMMetaEnvironment *environment, + gpointer environment_user_data, + const NMMetaOperationContext *operation_context, + const char *text, + char ***out_to_free); + +/*****************************************************************************/ + +char *nm_meta_abstract_info_get_nested_names_str (const NMMetaAbstractInfo *abstract_info, const char *name_prefix); +char *nm_meta_abstract_infos_get_names_str (const NMMetaAbstractInfo *const*fields_array, const char *name_prefix); + +/*****************************************************************************/ + +typedef struct { + const NMMetaAbstractInfo *info; + const char *self_selection; + const char *sub_selection; + guint idx; +} NMMetaSelectionItem; + +typedef struct { + const guint num; + const NMMetaSelectionItem items[]; +} NMMetaSelectionResultList; + +NMMetaSelectionResultList *nm_meta_selection_create_all (const NMMetaAbstractInfo *const* fields_array); +NMMetaSelectionResultList *nm_meta_selection_create_parse_one (const NMMetaAbstractInfo *const* fields_array, + const char *fields_prefix, + const char *fields_str, + gboolean validate_nested, + GError **error); +NMMetaSelectionResultList *nm_meta_selection_create_parse_list (const NMMetaAbstractInfo *const* fields_array, + const char *fields_prefix, + const char *fields_str, + gboolean validate_nested, + GError **error); + +#endif /* _NM_META_SETTING_ACCESS_H__ */ diff --git a/clients/common/nm-meta-setting-desc.c b/clients/common/nm-meta-setting-desc.c new file mode 100644 index 00000000..1ed6b433 --- /dev/null +++ b/clients/common/nm-meta-setting-desc.c @@ -0,0 +1,7284 @@ +/* nmcli - command-line tool to control NetworkManager + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright 2010 - 2017 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-meta-setting-desc.h" + +#include +#include + +#include "nm-common-macros.h" +#include "nm-utils/nm-hash-utils.h" +#include "nm-utils/nm-enum-utils.h" + +#include "NetworkManager.h" +#include "nm-vpn-helpers.h" +#include "nm-client-utils.h" +#include "nm-meta-setting-access.h" + +/*****************************************************************************/ + +static char *secret_flags_to_string (guint32 flags, NMMetaAccessorGetType get_type); + +#define ALL_SECRET_FLAGS \ + (NM_SETTING_SECRET_FLAG_NONE | \ + NM_SETTING_SECRET_FLAG_AGENT_OWNED | \ + NM_SETTING_SECRET_FLAG_NOT_SAVED | \ + NM_SETTING_SECRET_FLAG_NOT_REQUIRED) + +/*****************************************************************************/ + +static GType +_gobject_property_get_gtype (GObject *gobject, const char *property_name) +{ + GParamSpec *param_spec; + + param_spec = g_object_class_find_property (G_OBJECT_GET_CLASS (gobject), property_name); + if (param_spec) + return param_spec->value_type; + g_return_val_if_reached (G_TYPE_INVALID); +} + +static GType +_gtype_property_get_gtype (GType gtype, const char *property_name) +{ + /* given @gtype, a type for a GObject, lookup the property @property_name + * and return its value_type. */ + if (G_TYPE_IS_CLASSED (gtype)) { + GParamSpec *param_spec; + nm_auto_unref_gtypeclass GTypeClass *gtypeclass = g_type_class_ref (gtype); + + if (G_IS_OBJECT_CLASS (gtypeclass)) { + param_spec = g_object_class_find_property (G_OBJECT_CLASS (gtypeclass), property_name); + if (param_spec) + return param_spec->value_type; + } + } + g_return_val_if_reached (G_TYPE_INVALID); +} + +/*****************************************************************************/ + +static NMIPAddress * +_parse_ip_address (int family, const char *address, GError **error) +{ + gs_free char *ip_str = NULL; + const int MAX_PREFIX = (family == AF_INET) ? 32 : 128; + NMIPAddress *addr; + char *plen; + int prefix; + GError *local = NULL; + + g_return_val_if_fail (address, NULL); + g_return_val_if_fail (!error || !*error, NULL); + + ip_str = g_strstrip (g_strdup (address)); + + prefix = MAX_PREFIX; + + plen = strchr (ip_str, '/'); + if (plen) { + *plen++ = '\0'; + if ((prefix = _nm_utils_ascii_str_to_int64 (plen, 10, 1, MAX_PREFIX, -1)) == -1) { + g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_INVALID_ARGUMENT, + _("invalid prefix '%s'; <1-%d> allowed"), plen, MAX_PREFIX); + return NULL; + } + } + + addr = nm_ip_address_new (family, ip_str, prefix, &local); + if (!addr) { + g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_INVALID_ARGUMENT, + _("invalid IP address: %s"), local->message); + g_clear_error (&local); + } + return addr; +} + +static NMIPRoute * +_parse_ip_route (int family, + const char *str, + GError **error) +{ + const int MAX_PREFIX = (family == AF_INET) ? 32 : 128; + char *plen = NULL; + const char *next_hop = NULL; + const char *canon_dest; + int prefix; + NMIPRoute *route = NULL; + GError *local = NULL; + gint64 metric = -1; + guint i; + gs_strfreev char **routev = NULL; + gs_free char *str_clean = NULL; + char *dest; + gs_unref_hashtable GHashTable *attrs = NULL; + GHashTable *tmp_attrs; +#define ROUTE_SYNTAX _("The valid syntax is: 'ip[/prefix] [next-hop] [metric] [attribute=val]... [,ip[/prefix] ...]'") + + nm_assert (NM_IN_SET (family, AF_INET, AF_INET6)); + nm_assert (str); + nm_assert (!error || !*error); + + str_clean = g_strstrip (g_strdup (str)); + routev = nmc_strsplit_set (str_clean, " \t", 0); + if (!routev || !routev[0]) { + g_set_error (error, 1, 0, + "'%s' is not valid. %s", + str, ROUTE_SYNTAX); + return NULL; + } + + dest = routev[0]; + plen = strchr (dest, '/'); /* prefix delimiter */ + if (plen) + *plen++ = '\0'; + prefix = MAX_PREFIX; + if (plen) { + if ((prefix = _nm_utils_ascii_str_to_int64 (plen, 10, 1, MAX_PREFIX, -1)) == -1) { + g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_INVALID_ARGUMENT, + _("invalid prefix '%s'; <1-%d> allowed"), + plen, MAX_PREFIX); + return NULL; + } + } + + for (i = 1; routev[i]; i++) { + gint64 tmp64; + + if (nm_utils_ipaddr_valid (family, routev[i])) { + if (metric != -1 || attrs) { + g_set_error (error, 1, 0, _("the next hop ('%s') must be first"), routev[i]); + return NULL; + } + next_hop = routev[i]; + } else if ((tmp64 = _nm_utils_ascii_str_to_int64 (routev[i], 10, 0, G_MAXUINT32, -1)) != -1) { + if (attrs) { + g_set_error (error, 1, 0, _("the metric ('%s') must be before attributes"), routev[i]); + return NULL; + } + metric = tmp64; + } else if (strchr (routev[i], '=')) { + GHashTableIter iter; + char *iter_key; + GVariant *iter_value; + + tmp_attrs = nm_utils_parse_variant_attributes (routev[i], ' ', '=', FALSE, + nm_ip_route_get_variant_attribute_spec(), + error); + if (!tmp_attrs) { + g_prefix_error (error, "invalid option '%s': ", routev[i]); + return NULL; + } + + if (!attrs) + attrs = g_hash_table_new (nm_str_hash, g_str_equal); + + g_hash_table_iter_init (&iter, tmp_attrs); + while (g_hash_table_iter_next (&iter, (gpointer *) &iter_key, (gpointer *) &iter_value)) { + if (!nm_ip_route_attribute_validate (iter_key, iter_value, family, NULL, error)) { + g_prefix_error (error, "%s: ", iter_key); + g_hash_table_unref (tmp_attrs); + return NULL; + } + g_hash_table_insert (attrs, iter_key, iter_value); + g_hash_table_iter_steal (&iter); + } + g_hash_table_unref (tmp_attrs); + } else { + g_set_error (error, 1, 0, "%s", ROUTE_SYNTAX); + return NULL; + } + } + + route = nm_ip_route_new (family, dest, prefix, next_hop, metric, &local); + if (!route) { + g_set_error (error, 1, 0, + _("invalid route: %s. %s"), local->message, ROUTE_SYNTAX); + g_clear_error (&local); + return NULL; + } + + /* We don't accept default routes as NetworkManager handles it + * itself. But we have to check this after @route has normalized the + * dest string. + */ + canon_dest = nm_ip_route_get_dest (route); + if (!strcmp (canon_dest, "0.0.0.0") || !strcmp (canon_dest, "::")) { + g_set_error_literal (error, NM_UTILS_ERROR, NM_UTILS_ERROR_INVALID_ARGUMENT, + _("default route cannot be added (NetworkManager handles it by itself)")); + g_clear_pointer (&route, nm_ip_route_unref); + return NULL; + } + + if (attrs) { + GHashTableIter iter; + char *name; + GVariant *variant; + + g_hash_table_iter_init (&iter, attrs); + while (g_hash_table_iter_next (&iter, (gpointer *) &name, (gpointer *) &variant)) + nm_ip_route_set_attribute (route, name, variant); + } + + return route; +} + +/* Max priority values from libnm-core/nm-setting-vlan.c */ +#define MAX_SKB_PRIO G_MAXUINT32 +#define MAX_8021P_PRIO 7 /* Max 802.1p priority */ + +/* + * Parse VLAN priority mappings from the following format: 2:1,3:4,7:3 + * and verify if the priority numbers are valid + * + * Return: string array with split maps, or NULL on error + * Caller is responsible for freeing the array. + */ +static char ** +_parse_vlan_priority_maps (const char *priority_map, + NMVlanPriorityMap map_type, + GError **error) +{ + char **mapping = NULL, **iter; + unsigned long from, to, from_max, to_max; + + g_return_val_if_fail (priority_map != NULL, NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + if (map_type == NM_VLAN_INGRESS_MAP) { + from_max = MAX_8021P_PRIO; + to_max = MAX_SKB_PRIO; + } else { + from_max = MAX_SKB_PRIO; + to_max = MAX_8021P_PRIO; + } + + mapping = g_strsplit (priority_map, ",", 0); + for (iter = mapping; iter && *iter; iter++) { + char *left, *right; + + left = g_strstrip (*iter); + right = strchr (left, ':'); + if (!right) { + g_set_error (error, 1, 0, _("invalid priority map '%s'"), *iter); + g_strfreev (mapping); + return NULL; + } + *right++ = '\0'; + + if (!nmc_string_to_uint (left, TRUE, 0, from_max, &from)) { + g_set_error (error, 1, 0, _("priority '%s' is not valid (<0-%ld>)"), + left, from_max); + g_strfreev (mapping); + return NULL; + } + if (!nmc_string_to_uint (right, TRUE, 0, to_max, &to)) { + g_set_error (error, 1, 0, _("priority '%s' is not valid (<0-%ld>)"), + right, to_max); + g_strfreev (mapping); + return NULL; + } + *(right-1) = ':'; /* Put back ':' */ + } + return mapping; +} + +/* + * nmc_proxy_check_script: + * @script: file name with PAC script, or raw PAC Script data + * @out_script: raw PAC Script (with removed new-line characters) + * @error: location to store error, or %NULL + * + * Check PAC Script from @script parameter and return the checked/sanitized + * config in @out_script. + * + * Returns: %TRUE if the script is valid, %FALSE if it is invalid + */ +static gboolean +nmc_proxy_check_script (const char *script, char **out_script, GError **error) +{ + enum { + _PAC_SCRIPT_TYPE_GUESS, + _PAC_SCRIPT_TYPE_FILE, + _PAC_SCRIPT_TYPE_JSON, + } desired_type = _PAC_SCRIPT_TYPE_GUESS; + const char *filename = NULL; + size_t c_len = 0; + gs_free char *script_clone = NULL; + + *out_script = NULL; + + if (!script || !script[0]) + return TRUE; + + if (g_str_has_prefix (script, "file://")) { + script += NM_STRLEN ("file://"); + desired_type = _PAC_SCRIPT_TYPE_FILE; + } else if (g_str_has_prefix (script, "js://")) { + script += NM_STRLEN ("js://"); + desired_type = _PAC_SCRIPT_TYPE_JSON; + } + + if (NM_IN_SET (desired_type, _PAC_SCRIPT_TYPE_FILE, _PAC_SCRIPT_TYPE_GUESS)) { + gs_free char *contents = NULL; + + if (!g_file_get_contents (script, &contents, &c_len, NULL)) { + if (desired_type == _PAC_SCRIPT_TYPE_FILE) { + g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_INVALID_ARGUMENT, + _("cannot read pac-script from file '%s'"), + script); + return FALSE; + } + } else { + if (c_len != strlen (contents)) { + g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_INVALID_ARGUMENT, + _("file '%s' contains non-valid utf-8"), + script); + return FALSE; + } + filename = script; + script = script_clone = g_steal_pointer (&contents); + } + } + + if ( !strstr (script, "FindProxyForURL") + || !g_utf8_validate (script, -1, NULL)) { + if (filename) { + g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_INVALID_ARGUMENT, + _("'%s' does not contain a valid PAC Script"), filename); + } else { + g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_INVALID_ARGUMENT, + _("Not a valid PAC Script")); + } + return FALSE; + } + + *out_script = (script == script_clone) + ? g_steal_pointer (&script_clone) + : g_strdup (script); + return TRUE; +} + +/* + * nmc_team_check_config: + * @config: file name with team config, or raw team JSON config data + * @out_config: raw team JSON config data + * The value must be freed with g_free(). + * @error: location to store error, or %NUL + * + * Check team config from @config parameter and return the checked + * config in @out_config. + * + * Returns: %TRUE if the config is valid, %FALSE if it is invalid + */ +static gboolean +nmc_team_check_config (const char *config, char **out_config, GError **error) +{ + enum { + _TEAM_CONFIG_TYPE_GUESS, + _TEAM_CONFIG_TYPE_FILE, + _TEAM_CONFIG_TYPE_JSON, + } desired_type = _TEAM_CONFIG_TYPE_GUESS; + const char *filename = NULL; + size_t c_len = 0; + gs_free char *config_clone = NULL; + + *out_config = NULL; + + if (!config || !config[0]) + return TRUE; + + if (g_str_has_prefix (config, "file://")) { + config += NM_STRLEN ("file://"); + desired_type = _TEAM_CONFIG_TYPE_FILE; + } else if (g_str_has_prefix (config, "json://")) { + config += NM_STRLEN ("json://"); + desired_type = _TEAM_CONFIG_TYPE_JSON; + } + + if (NM_IN_SET (desired_type, _TEAM_CONFIG_TYPE_FILE, _TEAM_CONFIG_TYPE_GUESS)) { + gs_free char *contents = NULL; + + if (!g_file_get_contents (config, &contents, &c_len, NULL)) { + if (desired_type == _TEAM_CONFIG_TYPE_FILE) { + g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_INVALID_ARGUMENT, + _("cannot read team config from file '%s'"), + config); + return FALSE; + } + } else { + if (c_len != strlen (contents)) { + g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_INVALID_ARGUMENT, + _("team config file '%s' contains non-valid utf-8"), + config); + return FALSE; + } + filename = config; + config = config_clone = g_steal_pointer (&contents); + } + } + + if (!nm_utils_is_json_object (config, NULL)) { + if (filename) { + g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_INVALID_ARGUMENT, + _("'%s' does not contain a valid team configuration"), filename); + } else { + g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_INVALID_ARGUMENT, + _("team configuration must be a JSON object")); + } + return FALSE; + } + + *out_config = (config == config_clone) + ? g_steal_pointer (&config_clone) + : g_strdup (config); + return TRUE; +} + +static const char * +_get_text_hidden (NMMetaAccessorGetType get_type) +{ + if (get_type == NM_META_ACCESSOR_GET_TYPE_PRETTY) + return _(NM_META_TEXT_HIDDEN); + return NM_META_TEXT_HIDDEN; +} + +/*****************************************************************************/ + +G_GNUC_PRINTF (4, 5) +static void +_env_warn_fcn (const NMMetaEnvironment *environment, + gpointer environment_user_data, + NMMetaEnvWarnLevel warn_level, + const char *fmt_l10n, + ...) +{ + va_list ap; + + if (!environment || !environment->warn_fcn) + return; + + va_start (ap, fmt_l10n); + environment->warn_fcn (environment, + environment_user_data, + warn_level, + fmt_l10n, + ap); + va_end (ap); +} + +/*****************************************************************************/ + +#define ARGS_DESCRIBE_FCN \ + const NMMetaPropertyInfo *property_info, char **out_to_free + +#define ARGS_GET_FCN \ + const NMMetaPropertyInfo *property_info, const NMMetaEnvironment *environment, gpointer environment_user_data, NMSetting *setting, NMMetaAccessorGetType get_type, NMMetaAccessorGetFlags get_flags, NMMetaAccessorGetOutFlags *out_flags, gpointer *out_to_free + +#define ARGS_SET_FCN \ + const NMMetaPropertyInfo *property_info, const NMMetaEnvironment *environment, gpointer environment_user_data, NMSetting *setting, const char *value, GError **error + +#define ARGS_REMOVE_FCN \ + const NMMetaPropertyInfo *property_info, const NMMetaEnvironment *environment, gpointer environment_user_data, NMSetting *setting, const char *value, guint32 idx, GError **error + +#define ARGS_COMPLETE_FCN \ + const NMMetaPropertyInfo *property_info, const NMMetaEnvironment *environment, gpointer environment_user_data, const NMMetaOperationContext *operation_context, const char *text, char ***out_to_free + +#define ARGS_VALUES_FCN \ + const NMMetaPropertyInfo *property_info, char ***out_to_free + +#define ARGS_SETTING_INIT_FCN \ + const NMMetaSettingInfoEditor *setting_info, NMSetting *setting, NMMetaAccessorSettingInitType init_type + +#define RETURN_UNSUPPORTED_GET_TYPE() \ + G_STMT_START { \ + if (!NM_IN_SET (get_type, \ + NM_META_ACCESSOR_GET_TYPE_PARSABLE, \ + NM_META_ACCESSOR_GET_TYPE_PRETTY)) { \ + nm_assert_not_reached (); \ + return NULL; \ + } \ + } G_STMT_END; + +#define RETURN_STR_TO_FREE(val) \ + G_STMT_START { \ + char *_val = (val); \ + return ((*(out_to_free)) = _val); \ + } G_STMT_END + +static gconstpointer +_get_fcn_nmc_with_default (ARGS_GET_FCN) +{ + const char *s; + char *s_full; + GValue val = G_VALUE_INIT; + + RETURN_UNSUPPORTED_GET_TYPE (); + + if (property_info->property_typ_data->subtype.get_with_default.fcn (setting)) { + if (get_type == NM_META_ACCESSOR_GET_TYPE_PRETTY) + return _("(default)"); + return ""; + } + + g_value_init (&val, G_TYPE_STRING); + g_object_get_property (G_OBJECT (setting), property_info->property_name, &val); + s = g_value_get_string (&val); + if (get_type == NM_META_ACCESSOR_GET_TYPE_PRETTY) + s_full = s ? g_strdup_printf ("\"%s\"", s) : g_strdup (""); + else + s_full = g_strdup (s && *s ? s : " "); + g_value_unset (&val); + RETURN_STR_TO_FREE (s_full); +} + +static gconstpointer +_get_fcn_gobject_impl (const NMMetaPropertyInfo *property_info, + NMSetting *setting, + NMMetaAccessorGetType get_type, + gpointer *out_to_free) +{ + char *s; + const char *s_c; + GType gtype_prop; + nm_auto_unset_gvalue GValue val = G_VALUE_INIT; + + RETURN_UNSUPPORTED_GET_TYPE (); + + gtype_prop = _gobject_property_get_gtype (G_OBJECT (setting), property_info->property_name); + + if (gtype_prop == G_TYPE_BOOLEAN) { + gboolean b; + + g_value_init (&val, gtype_prop); + g_object_get_property (G_OBJECT (setting), property_info->property_name, &val); + b = g_value_get_boolean (&val); + if (get_type == NM_META_ACCESSOR_GET_TYPE_PRETTY) + s_c = b ? _("yes") : _("no"); + else + s_c = b ? "yes" : "no"; + return s_c; + } else { + g_value_init (&val, G_TYPE_STRING); + g_object_get_property (G_OBJECT (setting), property_info->property_name, &val); + s = g_value_dup_string (&val); + RETURN_STR_TO_FREE (s); + } +} + +static gconstpointer +_get_fcn_gobject (ARGS_GET_FCN) +{ + return _get_fcn_gobject_impl (property_info, setting, get_type, out_to_free); +} + +static gconstpointer +_get_fcn_gobject_int (ARGS_GET_FCN) +{ + const GParamSpec *pspec; + nm_auto_unset_gvalue GValue gval = G_VALUE_INIT; + gint64 v; + const NMMetaUtilsIntValueInfo *value_infos; + + RETURN_UNSUPPORTED_GET_TYPE (); + + pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (G_OBJECT (setting)), property_info->property_name); + if (!G_IS_PARAM_SPEC (pspec)) + g_return_val_if_reached (FALSE); + + g_value_init (&gval, pspec->value_type); + g_object_get_property (G_OBJECT (setting), property_info->property_name, &gval); + switch (pspec->value_type) { + case G_TYPE_INT: + v = g_value_get_int (&gval); + break; + case G_TYPE_UINT: + v = g_value_get_uint (&gval); + break; + case G_TYPE_INT64: + v = g_value_get_int64 (&gval); + break; + default: + g_return_val_if_reached (NULL); + break; + } + + if ( get_type == NM_META_ACCESSOR_GET_TYPE_PRETTY + && property_info->property_typ_data + && (value_infos = property_info->property_typ_data->subtype.gobject_int.value_infos)) { + for (; value_infos->nick; value_infos++) { + if (value_infos->value == v) { + RETURN_STR_TO_FREE (g_strdup_printf ("%lli (%s)", + (long long) v, + value_infos->nick)); + } + } + } + + RETURN_STR_TO_FREE (g_strdup_printf ("%"G_GINT64_FORMAT, v)); +} + +static gconstpointer +_get_fcn_gobject_mtu (ARGS_GET_FCN) +{ + guint32 mtu; + + RETURN_UNSUPPORTED_GET_TYPE (); + + if ( !property_info->property_typ_data + || !property_info->property_typ_data->subtype.mtu.get_fcn) + return _get_fcn_gobject_impl (property_info, setting, get_type, out_to_free); + + mtu = property_info->property_typ_data->subtype.mtu.get_fcn (setting); + if (mtu == 0) { + if (get_type == NM_META_ACCESSOR_GET_TYPE_PRETTY) + return _("auto"); + return "auto"; + } + RETURN_STR_TO_FREE (g_strdup_printf ("%u", (unsigned) mtu)); +} + +static gconstpointer +_get_fcn_gobject_secret_flags (ARGS_GET_FCN) +{ + guint v; + GValue val = G_VALUE_INIT; + + RETURN_UNSUPPORTED_GET_TYPE (); + + g_value_init (&val, G_TYPE_UINT); + g_object_get_property (G_OBJECT (setting), property_info->property_name, &val); + v = g_value_get_uint (&val); + g_value_unset (&val); + RETURN_STR_TO_FREE (secret_flags_to_string (v, get_type)); +} + +static gconstpointer +_get_fcn_gobject_enum (ARGS_GET_FCN) +{ + GType gtype = 0; + GType gtype_prop; + nm_auto_unref_gtypeclass GTypeClass *gtype_class = NULL; + nm_auto_unref_gtypeclass GTypeClass *gtype_prop_class = NULL; + gboolean has_gtype = FALSE; + nm_auto_unset_gvalue GValue gval = G_VALUE_INIT; + gint64 v; + gboolean format_numeric = FALSE; + gboolean format_numeric_hex = FALSE; + gboolean format_numeric_hex_unknown = FALSE; + gboolean format_text = FALSE; + gboolean format_text_l10n = FALSE; + gs_free char *s = NULL; + char s_numeric[64]; + + RETURN_UNSUPPORTED_GET_TYPE (); + + if (property_info->property_typ_data) { + if (property_info->property_typ_data->subtype.gobject_enum.get_gtype) { + gtype = property_info->property_typ_data->subtype.gobject_enum.get_gtype (); + has_gtype = TRUE; + } + } + + if ( property_info->property_typ_data + && get_type == NM_META_ACCESSOR_GET_TYPE_PRETTY + && NM_FLAGS_ANY (property_info->property_typ_data->typ_flags, NM_META_PROPERTY_TYP_FLAG_ENUM_GET_PRETTY_NUMERIC + | NM_META_PROPERTY_TYP_FLAG_ENUM_GET_PRETTY_NUMERIC_HEX + | NM_META_PROPERTY_TYP_FLAG_ENUM_GET_PRETTY_TEXT + | NM_META_PROPERTY_TYP_FLAG_ENUM_GET_PRETTY_TEXT_L10N)) { + format_numeric_hex = NM_FLAGS_HAS (property_info->property_typ_data->typ_flags, NM_META_PROPERTY_TYP_FLAG_ENUM_GET_PRETTY_NUMERIC_HEX); + format_numeric = format_numeric_hex || NM_FLAGS_HAS (property_info->property_typ_data->typ_flags, NM_META_PROPERTY_TYP_FLAG_ENUM_GET_PRETTY_NUMERIC); + format_text_l10n = NM_FLAGS_HAS (property_info->property_typ_data->typ_flags, NM_META_PROPERTY_TYP_FLAG_ENUM_GET_PRETTY_TEXT_L10N); + format_text = format_text_l10n || NM_FLAGS_HAS (property_info->property_typ_data->typ_flags, NM_META_PROPERTY_TYP_FLAG_ENUM_GET_PRETTY_TEXT); + } else if ( property_info->property_typ_data + && get_type != NM_META_ACCESSOR_GET_TYPE_PRETTY + && NM_FLAGS_ANY (property_info->property_typ_data->typ_flags, NM_META_PROPERTY_TYP_FLAG_ENUM_GET_PARSABLE_NUMERIC + | NM_META_PROPERTY_TYP_FLAG_ENUM_GET_PARSABLE_NUMERIC_HEX + | NM_META_PROPERTY_TYP_FLAG_ENUM_GET_PARSABLE_TEXT)) { + format_numeric_hex = NM_FLAGS_HAS (property_info->property_typ_data->typ_flags, NM_META_PROPERTY_TYP_FLAG_ENUM_GET_PARSABLE_NUMERIC_HEX); + format_numeric = format_numeric && NM_FLAGS_HAS (property_info->property_typ_data->typ_flags, NM_META_PROPERTY_TYP_FLAG_ENUM_GET_PARSABLE_NUMERIC); + format_text = NM_FLAGS_HAS (property_info->property_typ_data->typ_flags, NM_META_PROPERTY_TYP_FLAG_ENUM_GET_PARSABLE_TEXT); + } else if (get_type == NM_META_ACCESSOR_GET_TYPE_PRETTY) { + /* by default, output in format "%u (%s)" (with hex for flags and l10n). */ + format_numeric = TRUE; + format_numeric_hex_unknown = TRUE; + format_text = TRUE; + format_text_l10n = TRUE; + } else { + /* by default, output only numeric (with hex for flags). */ + format_numeric = TRUE; + format_numeric_hex_unknown = TRUE; + } + + nm_assert (format_text || format_numeric); + + gtype_prop = _gobject_property_get_gtype (G_OBJECT (setting), property_info->property_name); + + g_value_init (&gval, gtype_prop); + + g_object_get_property (G_OBJECT (setting), property_info->property_name, &gval); + + if ( gtype_prop == G_TYPE_INT + || ( G_TYPE_IS_CLASSED (gtype_prop) + && G_IS_ENUM_CLASS ((gtype_prop_class ?: (gtype_prop_class = g_type_class_ref (gtype_prop)))))) { + if (gtype_prop == G_TYPE_INT) { + if (!has_gtype) + g_return_val_if_reached (NULL); + v = g_value_get_int (&gval); + } else + v = g_value_get_enum (&gval); + } else if ( gtype_prop == G_TYPE_UINT + || ( G_TYPE_IS_CLASSED (gtype_prop) + && G_IS_FLAGS_CLASS ((gtype_prop_class ?: (gtype_prop_class = g_type_class_ref (gtype_prop)))))) { + if (gtype_prop == G_TYPE_UINT) { + if (!has_gtype) + g_return_val_if_reached (NULL); + v = g_value_get_uint (&gval); + } else + v = g_value_get_flags (&gval); + } else + g_return_val_if_reached (NULL); + + if (!has_gtype) { + gtype = gtype_prop; + gtype_class = g_steal_pointer (>ype_prop_class); + } + + nm_assert (({ + nm_auto_unref_gtypeclass GTypeClass *t = NULL; + + ( G_TYPE_IS_CLASSED (gtype) + && (t = g_type_class_ref (gtype)) + && (G_IS_ENUM_CLASS (t) || G_IS_FLAGS_CLASS (t))); + })); + + if (format_numeric && !format_text) { + s = format_numeric_hex + || ( format_numeric_hex_unknown + && !G_IS_ENUM_CLASS (gtype_class ?: (gtype_class = g_type_class_ref (gtype)))) + ? g_strdup_printf ("0x%"G_GINT64_FORMAT, v) + : g_strdup_printf ("%"G_GINT64_FORMAT, v); + RETURN_STR_TO_FREE (g_steal_pointer (&s)); + } + + /* the gobject_enum.value_infos are currently ignored for the getter. They + * only declare additional aliases for the setter. */ + + s = nm_utils_enum_to_str (gtype, (int) v); + + if (!format_numeric) + RETURN_STR_TO_FREE (g_steal_pointer (&s)); + + if ( format_numeric_hex + || ( format_numeric_hex_unknown + && !G_IS_ENUM_CLASS (gtype_class ?: (gtype_class = g_type_class_ref (gtype))))) + nm_sprintf_buf (s_numeric, "0x%"G_GINT64_FORMAT, v); + else + nm_sprintf_buf (s_numeric, "%"G_GINT64_FORMAT, v); + + if (nm_streq0 (s, s_numeric)) + RETURN_STR_TO_FREE (g_steal_pointer (&s)); + + if (format_text_l10n) + RETURN_STR_TO_FREE (g_strdup_printf (_("%s (%s)"), s_numeric, s)); + else + RETURN_STR_TO_FREE (g_strdup_printf ("%s (%s)", s_numeric, s)); +} + +/*****************************************************************************/ + +static gboolean +_set_fcn_gobject_string (ARGS_SET_FCN) +{ + gs_free char *to_free = NULL; + + if (property_info->property_typ_data) { + if (property_info->property_typ_data->subtype.gobject_string.validate_fcn) { + value = property_info->property_typ_data->subtype.gobject_string.validate_fcn (value, &to_free, error); + if (!value) + return FALSE; + } else if (property_info->property_typ_data->values_static) { + value = nmc_string_is_valid (value, + (const char **) property_info->property_typ_data->values_static, + error); + if (!value) + return FALSE; + } + } + g_object_set (setting, property_info->property_name, value, NULL); + return TRUE; +} + +static gboolean +_set_fcn_gobject_bool (ARGS_SET_FCN) +{ + gboolean val_bool; + + if (!nmc_string_to_bool (value, &val_bool, error)) + return FALSE; + + g_object_set (setting, property_info->property_name, val_bool, NULL); + return TRUE; +} + +static gboolean +_set_fcn_gobject_int (ARGS_SET_FCN) +{ + int errsv; + const GParamSpec *pspec; + nm_auto_unset_gvalue GValue gval = G_VALUE_INIT; + gint64 v = 0; + gboolean has_minmax = FALSE; + gint64 min = G_MININT64; + gint64 max = G_MAXINT64; + guint base = 10; + const NMMetaUtilsIntValueInfo *value_infos = NULL; + gboolean has_value = FALSE; + + if (property_info->property_typ_data) { + + if ( value + && (value_infos = property_info->property_typ_data->subtype.gobject_int.value_infos)) { + gs_free char *vv_stripped = NULL; + const char *vv = nm_str_skip_leading_spaces (value); + + if (vv[0] && g_ascii_isspace (vv[strlen (vv) - 1])) { + vv_stripped = g_strstrip (g_strdup (vv)); + vv = vv_stripped; + } + + for (; value_infos->nick; value_infos++) { + if (nm_streq (value_infos->nick, vv)) { + v = value_infos->value; + has_value = TRUE; + break; + } + } + } + + if (property_info->property_typ_data->subtype.gobject_int.base > 0) + base = property_info->property_typ_data->subtype.gobject_int.base; + if ( property_info->property_typ_data->subtype.gobject_int.min + || property_info->property_typ_data->subtype.gobject_int.max) { + min = property_info->property_typ_data->subtype.gobject_int.min; + max = property_info->property_typ_data->subtype.gobject_int.max; + has_minmax = TRUE; + } + } + + pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (G_OBJECT (setting)), property_info->property_name); + if (!G_IS_PARAM_SPEC (pspec)) + g_return_val_if_reached (FALSE); + switch (pspec->value_type) { + case G_TYPE_INT: + if (!has_minmax) { + const GParamSpecInt *p = (GParamSpecInt *) pspec; + + min = p->minimum; + max = p->maximum; + } + break; + case G_TYPE_UINT: + if (!has_minmax) { + const GParamSpecUInt *p = (GParamSpecUInt *) pspec; + + min = p->minimum; + max = p->maximum; + } + break; + case G_TYPE_INT64: + if (!has_minmax) { + const GParamSpecInt64 *p = (GParamSpecInt64 *) pspec; + + min = p->minimum; + max = p->maximum; + } + break; + default: + g_return_val_if_reached (FALSE); + } + + if (!has_value) { + v = _nm_utils_ascii_str_to_int64 (value, base, min, max, 0); + + if ((errsv = errno) != 0) { + if (errsv == ERANGE) { + g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_INVALID_ARGUMENT, + _("'%s' is out of range [%lli, %lli]"), + value, + (long long) min, + (long long) max); + } else { + g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_INVALID_ARGUMENT, + _("'%s' is not a valid number"), value); + } + return FALSE; + } + } + + g_value_init (&gval, pspec->value_type); + switch (pspec->value_type) { + case G_TYPE_INT: + g_value_set_int (&gval, v); + break; + case G_TYPE_UINT: + g_value_set_uint (&gval, v); + break; + case G_TYPE_INT64: + g_value_set_int64 (&gval, v); + break; + default: + nm_assert_not_reached (); + break; + } + + /* Validate the number according to the property spec */ + if (!nm_g_object_set_property (G_OBJECT (setting), + property_info->property_name, + &gval, + error)) + g_return_val_if_reached (FALSE); + + return TRUE; +} + +static gboolean +_set_fcn_gobject_mtu (ARGS_SET_FCN) +{ + nm_auto_unset_gvalue GValue gval = G_VALUE_INIT; + const GParamSpec *pspec; + gint64 v; + + if (nm_streq0 (value, "auto")) + value = "0"; + + pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (G_OBJECT (setting)), + property_info->property_name); + if (!pspec || pspec->value_type != G_TYPE_UINT) + g_return_val_if_reached (FALSE); + + v = _nm_utils_ascii_str_to_int64 (value, 10, 0, G_MAXUINT32, -1); + if (v < 0) { + g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_INVALID_ARGUMENT, + _("'%s' is out of range [0, %u]"), value, (unsigned) G_MAXUINT32); + return FALSE; + } + + g_value_init (&gval, pspec->value_type); + g_value_set_uint (&gval, v); + + if (!nm_g_object_set_property (G_OBJECT (setting), + property_info->property_name, + &gval, + error)) + g_return_val_if_reached (FALSE); + + return TRUE; +} + +static gboolean +_set_fcn_gobject_mac (ARGS_SET_FCN) +{ + NMMetaPropertyTypeMacMode mode; + gboolean valid; + + if (property_info->property_typ_data) + mode = property_info->property_typ_data->subtype.mac.mode; + else + mode = NM_META_PROPERTY_TYPE_MAC_MODE_DEFAULT; + + + if (mode == NM_META_PROPERTY_TYPE_MAC_MODE_INFINIBAND) + valid = nm_utils_hwaddr_valid (value, INFINIBAND_ALEN); + else { + valid = nm_utils_hwaddr_valid (value, ETH_ALEN) + || ( mode == NM_META_PROPERTY_TYPE_MAC_MODE_CLONED + && NM_CLONED_MAC_IS_SPECIAL (value)); + } + + if (!valid) { + g_set_error (error, 1, 0, _("'%s' is not a valid Ethernet MAC"), value); + return FALSE; + } + + g_object_set (setting, property_info->property_name, value, NULL); + return TRUE; +} + +static gboolean +_set_fcn_gobject_secret_flags (ARGS_SET_FCN) +{ + char **strv = NULL, **iter; + unsigned long flags = 0, val_int; + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + strv = nmc_strsplit_set (value, " \t,", 0); + for (iter = strv; iter && *iter; iter++) { + if (!nmc_string_to_uint (*iter, TRUE, 0, ALL_SECRET_FLAGS, &val_int)) { + g_set_error (error, 1, 0, _("'%s' is not a valid flag number; use <0-%d>"), + *iter, ALL_SECRET_FLAGS); + g_strfreev (strv); + return FALSE; + } + flags += val_int; + } + g_strfreev (strv); + + /* Validate the flags number */ + if (flags > ALL_SECRET_FLAGS) { + flags = ALL_SECRET_FLAGS; + _env_warn_fcn (environment, environment_user_data, + NM_META_ENV_WARN_LEVEL_WARN, + N_("'%s' sum is higher than all flags => all flags set"), + value); + } + + g_object_set (setting, property_info->property_name, (guint) flags, NULL); + return TRUE; +} + +static gboolean +_set_fcn_gobject_enum (ARGS_SET_FCN) +{ + GType gtype = 0; + GType gtype_prop; + gboolean has_gtype = FALSE; + nm_auto_unset_gvalue GValue gval = G_VALUE_INIT; + nm_auto_unref_gtypeclass GTypeClass *gtype_class = NULL; + gboolean is_flags; + int v; + + if (property_info->property_typ_data) { + if (property_info->property_typ_data->subtype.gobject_enum.get_gtype) { + gtype = property_info->property_typ_data->subtype.gobject_enum.get_gtype (); + has_gtype = TRUE; + } + } + + gtype_prop = _gobject_property_get_gtype (G_OBJECT (setting), property_info->property_name); + + if ( has_gtype + && NM_IN_SET (gtype_prop, + G_TYPE_INT, + G_TYPE_UINT) + && G_TYPE_IS_CLASSED (gtype) + && (gtype_class = g_type_class_ref (gtype)) + && ( (is_flags = G_IS_FLAGS_CLASS (gtype_class)) + || G_IS_ENUM_CLASS (gtype_class))) { + /* valid */ + } else if ( !has_gtype + && G_TYPE_IS_CLASSED (gtype_prop) + && (gtype_class = g_type_class_ref (gtype_prop)) + && ( (is_flags = G_IS_FLAGS_CLASS (gtype_class)) + || G_IS_ENUM_CLASS (gtype_class))) { + gtype = gtype_prop; + } else + g_return_val_if_reached (FALSE); + + if (!_nm_utils_enum_from_str_full (gtype, value, &v, NULL, + property_info->property_typ_data + ? property_info->property_typ_data->subtype.gobject_enum.value_infos + : NULL)) + goto fail; + + if ( property_info->property_typ_data + && property_info->property_typ_data->subtype.gobject_enum.pre_set_notify) { + property_info->property_typ_data->subtype.gobject_enum.pre_set_notify (property_info, + environment, + environment_user_data, + setting, + v); + } + + g_value_init (&gval, gtype_prop); + if (gtype_prop == G_TYPE_INT) + g_value_set_int (&gval, v); + else if (gtype_prop == G_TYPE_UINT) + g_value_set_uint (&gval, v); + else if (G_IS_ENUM_CLASS (gtype_class)) + g_value_set_enum (&gval, v); + else if (G_IS_FLAGS_CLASS (gtype_class)) + g_value_set_flags (&gval, v); + else + g_return_val_if_reached (FALSE); + + if (!nm_g_object_set_property (G_OBJECT (setting), property_info->property_name, &gval, NULL)) + goto fail; + + return TRUE; + +fail: + if (error) { + gs_free const char **valid_all = NULL; + gs_free const char *valid_str = NULL; + gboolean has_minmax = FALSE; + int min = G_MININT; + int max = G_MAXINT; + + if (property_info->property_typ_data) { + if ( property_info->property_typ_data->subtype.gobject_enum.min + || property_info->property_typ_data->subtype.gobject_enum.max) { + min = property_info->property_typ_data->subtype.gobject_enum.min; + max = property_info->property_typ_data->subtype.gobject_enum.max; + has_minmax = TRUE; + } + } + + if (!has_minmax && is_flags) { + min = 0; + max = (gint) G_MAXUINT; + } + + valid_all = nm_utils_enum_get_values (gtype, min, max); + valid_str = g_strjoinv (",", (char **) valid_all); + if (is_flags) { + g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_INVALID_ARGUMENT, + _("invalid option '%s', use a combination of [%s]"), + value, + valid_str); + } else { + g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_INVALID_ARGUMENT, + _("invalid option '%s', use one of [%s]"), + value, + valid_str); + } + } + return FALSE; +} + +/*****************************************************************************/ + +static const char *const* +_values_fcn_gobject_enum (ARGS_VALUES_FCN) +{ + GType gtype = 0; + gboolean has_gtype = FALSE; + gboolean has_minmax = FALSE; + int min = G_MININT; + int max = G_MAXINT; + char **v, **w; + + if (property_info->property_typ_data) { + if ( property_info->property_typ_data->subtype.gobject_enum.min + || property_info->property_typ_data->subtype.gobject_enum.max) { + min = property_info->property_typ_data->subtype.gobject_enum.min; + max = property_info->property_typ_data->subtype.gobject_enum.max; + has_minmax = TRUE; + } + if (property_info->property_typ_data->subtype.gobject_enum.get_gtype) { + gtype = property_info->property_typ_data->subtype.gobject_enum.get_gtype (); + has_gtype = TRUE; + } + } + + if (!has_gtype) { + gtype = _gtype_property_get_gtype (property_info->setting_info->general->get_setting_gtype (), + property_info->property_name); + } + + if ( !has_minmax + && G_TYPE_IS_CLASSED (gtype)) { + nm_auto_unref_gtypeclass GTypeClass *class = NULL; + + class = g_type_class_ref (gtype); + if (G_IS_FLAGS_CLASS (class)) { + min = 0; + max = (gint) G_MAXUINT; + } + } + + /* the gobject_enum.value_infos are currently ignored for the list of + * values. They only declare additional (hidden) aliases for the setter. */ + + v = (char **) nm_utils_enum_get_values (gtype, min, max); + if (v) { + for (w = v; *w; w++) + *w = g_strdup (*w); + } + return (const char *const*) (*out_to_free = v); +} + +/*****************************************************************************/ + +static const char *const* +_complete_fcn_gobject_bool (ARGS_COMPLETE_FCN) +{ + static const char *const v[] = { + "true", + "false", + "on", + "off", + "1", + "0", + "yes", + "no", + NULL, + }; + + if (!text || !text[0]) + return &v[6]; + return v; +} + +static const char *const* +_complete_fcn_gobject_devices (ARGS_COMPLETE_FCN) +{ + NMDevice *const*devices = NULL; + guint i, j; + guint len = 0; + char **ifnames; + + if ( environment + && environment->get_nm_devices) { + devices = environment->get_nm_devices (environment, + environment_user_data, + &len); + } + + if (len == 0) + return NULL; + + ifnames = g_new (char *, len + 1); + for (i = 0, j = 0; i < len; i++) { + const char *ifname; + + nm_assert (NM_IS_DEVICE (devices[i])); + + ifname = nm_device_get_iface (devices[i]); + if (ifname) + ifnames[j++] = g_strdup (ifname); + } + ifnames[j++] = NULL; + + *out_to_free = ifnames; + return (const char *const*) ifnames; +} + +/*****************************************************************************/ + +static char * +wep_key_type_to_string (NMWepKeyType type) +{ + switch (type) { + case NM_WEP_KEY_TYPE_KEY: + return g_strdup_printf (_("%d (key)"), type); + case NM_WEP_KEY_TYPE_PASSPHRASE: + return g_strdup_printf (_("%d (passphrase)"), type); + case NM_WEP_KEY_TYPE_UNKNOWN: + default: + return g_strdup_printf (_("%d (unknown)"), type); + } +} + +static char * +bytes_to_string (GBytes *bytes) +{ + const guint8 *data; + gsize len; + GString *cert = NULL; + int i; + + if (!bytes) + return NULL; + data = g_bytes_get_data (bytes, &len); + + cert = g_string_new (NULL); + for (i = 0; i < len; i++) + g_string_append_printf (cert, "%02X", data[i]); + + return g_string_free (cert, FALSE); +} + +static char * +vlan_flags_to_string (guint32 flags, NMMetaAccessorGetType get_type) +{ + GString *flag_str; + + if (get_type != NM_META_ACCESSOR_GET_TYPE_PRETTY) + return g_strdup_printf ("%u", flags); + + if (flags == 0) + return g_strdup (_("0 (NONE)")); + + flag_str = g_string_new (NULL); + g_string_printf (flag_str, "%d (", flags); + + if (flags & NM_VLAN_FLAG_REORDER_HEADERS) + g_string_append (flag_str, _("REORDER_HEADERS, ")); + if (flags & NM_VLAN_FLAG_GVRP) + g_string_append (flag_str, _("GVRP, ")); + if (flags & NM_VLAN_FLAG_LOOSE_BINDING) + g_string_append (flag_str, _("LOOSE_BINDING, ")); + if (flags & NM_VLAN_FLAG_MVRP) + g_string_append (flag_str, _("MVRP, ")); + + if (flag_str->str[flag_str->len-1] == '(') + g_string_append (flag_str, _("unknown")); + else + g_string_truncate (flag_str, flag_str->len-2); /* chop off trailing ', ' */ + + g_string_append_c (flag_str, ')'); + + return g_string_free (flag_str, FALSE); +} + +static char * +vlan_priorities_to_string (NMSettingVlan *s_vlan, NMVlanPriorityMap map) +{ + GString *priorities; + int i; + + priorities = g_string_new (NULL); + for (i = 0; i < nm_setting_vlan_get_num_priorities (s_vlan, map); i++) { + guint32 from, to; + + if (nm_setting_vlan_get_priority (s_vlan, map, i, &from, &to)) + g_string_append_printf (priorities, "%d:%d,", from, to); + } + if (priorities->len) + g_string_truncate (priorities, priorities->len-1); /* chop off trailing ',' */ + + return g_string_free (priorities, FALSE); +} + +static char * +ip6_privacy_to_string (NMSettingIP6ConfigPrivacy ip6_privacy, NMMetaAccessorGetType get_type) +{ + if (get_type != NM_META_ACCESSOR_GET_TYPE_PRETTY) + return g_strdup_printf ("%d", ip6_privacy); + + switch (ip6_privacy) { + case NM_SETTING_IP6_CONFIG_PRIVACY_DISABLED: + return g_strdup_printf (_("%d (disabled)"), ip6_privacy); + case NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR: + return g_strdup_printf (_("%d (enabled, prefer public IP)"), ip6_privacy); + case NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR: + return g_strdup_printf (_("%d (enabled, prefer temporary IP)"), ip6_privacy); + default: + return g_strdup_printf (_("%d (unknown)"), ip6_privacy); + } +} + +static char * +secret_flags_to_string (guint32 flags, NMMetaAccessorGetType get_type) +{ + GString *flag_str; + + if (get_type != NM_META_ACCESSOR_GET_TYPE_PRETTY) + return g_strdup_printf ("%u", flags); + + if (flags == 0) + return g_strdup (_("0 (none)")); + + flag_str = g_string_new (NULL); + g_string_printf (flag_str, "%u (", flags); + + if (flags & NM_SETTING_SECRET_FLAG_AGENT_OWNED) + g_string_append (flag_str, _("agent-owned, ")); + if (flags & NM_SETTING_SECRET_FLAG_NOT_SAVED) + g_string_append (flag_str, _("not saved, ")); + if (flags & NM_SETTING_SECRET_FLAG_NOT_REQUIRED) + g_string_append (flag_str, _("not required, ")); + + if (flag_str->str[flag_str->len-1] == '(') + g_string_append (flag_str, _("unknown")); + else + g_string_truncate (flag_str, flag_str->len-2); /* chop off trailing ', ' */ + + g_string_append_c (flag_str, ')'); + + return g_string_free (flag_str, FALSE); +} + +static void +vpn_data_item (const char *key, const char *value, gpointer user_data) +{ + GString *ret_str = (GString *) user_data; + + if (ret_str->len != 0) + g_string_append (ret_str, ", "); + + g_string_append_printf (ret_str, "%s = %s", key, value); +} + +#define DEFINE_SETTER_STR_LIST_MULTI(def_func, s_macro, set_func) \ + static gboolean \ + def_func (NMSetting *setting, \ + const char *prop, \ + const char *value, \ + const char **valid_strv, \ + GError **error) \ + { \ + char **strv = NULL, **iter; \ + const char *item; \ + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); \ + strv = nmc_strsplit_set (value, " \t,", 0); \ + for (iter = strv; iter && *iter; iter++) { \ + if (!(item = nmc_string_is_valid (g_strstrip (*iter), valid_strv, error))) { \ + g_strfreev (strv); \ + return FALSE; \ + } \ + set_func (s_macro (setting), item); \ + } \ + g_strfreev (strv); \ + return TRUE; \ + } + +#define DEFINE_SETTER_OPTIONS(def_func, s_macro, s_type, add_func, valid_func1, valid_func2) \ + static gboolean \ + def_func (ARGS_SET_FCN) \ + { \ + char **strv = NULL, **iter; \ + const char **(*valid_func1_p) (s_type *) = valid_func1; \ + const char * (*valid_func2_p) (const char *, const char *, GError **) = valid_func2; \ + const char *opt_name, *opt_val; \ + \ + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); \ + \ + strv = nmc_strsplit_set (value, ",", 0); \ + for (iter = strv; iter && *iter; iter++) { \ + char *left = g_strstrip (*iter); \ + char *right = strchr (left, '='); \ + if (!right) { \ + g_set_error (error, 1, 0, _("'%s' is not valid; use