From e74c568b07b50b97873fb4ee1d776dedefbd54d6 Mon Sep 17 00:00:00 2001 From: Michael Biebl Date: Fri, 1 Oct 2021 23:05:04 +0200 Subject: New upstream version 1.32.12 --- src/libnmc-setting/nm-meta-setting-desc.c | 8718 +++++++++++++++++++++++++++++ 1 file changed, 8718 insertions(+) create mode 100644 src/libnmc-setting/nm-meta-setting-desc.c (limited to 'src/libnmc-setting/nm-meta-setting-desc.c') diff --git a/src/libnmc-setting/nm-meta-setting-desc.c b/src/libnmc-setting/nm-meta-setting-desc.c new file mode 100644 index 00000000..1cc5f9c1 --- /dev/null +++ b/src/libnmc-setting/nm-meta-setting-desc.c @@ -0,0 +1,8718 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2010 - 2018 Red Hat, Inc. + */ + +#include "libnm-client-aux-extern/nm-default-client.h" + +#include "nm-meta-setting-desc.h" + +#include +#include +#include +#include + +#include "libnm-core-aux-intern/nm-common-macros.h" +#include "libnm-glib-aux/nm-enum-utils.h" +#include "libnm-glib-aux/nm-secret-utils.h" +#include "libnm-core-aux-intern/nm-libnm-core-utils.h" +#include "libnm-core-aux-extern/nm-libnm-core-aux.h" + +#include "libnmc-base/nm-vpn-helpers.h" +#include "libnmc-base/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 char * +bytes_to_string(GBytes *bytes) +{ + const guint8 *data; + gsize len; + + if (!bytes) + return NULL; + + data = g_bytes_get_data(bytes, &len); + return nm_utils_bin2hexstr_full(data, len, '\0', TRUE, NULL); +} + +/*****************************************************************************/ + +static int +_int64_cmp_desc(gconstpointer a, gconstpointer b, gpointer user_data) +{ + NM_CMP_DIRECT(*((const gint64 *) b), *((const gint64 *) a)); + return 0; +} + +static gint64 * +_value_str_as_index_list(const char *value, gsize *out_len) +{ + gs_free char *str_clone_free = NULL; + gboolean str_cloned = FALSE; + char * str; + gsize i, j; + gsize n_alloc; + gsize len; + gs_free gint64 *arr = NULL; + + *out_len = 0; + + if (!value) + return NULL; + + str = (char *) value; + n_alloc = 0; + len = 0; + while (TRUE) { + gint64 i64; + const char *s; + gsize good; + + good = strcspn(str, "," NM_ASCII_SPACES); + if (good == 0) { + if (str[0] == '\0') + break; + str++; + continue; + } + if (str[good] == '\0') { + s = str; + str += good; + } else { + if (!str_cloned) { + /* we use alloca() inside a loop here, but it is guarded to happen at most once. */ + str_cloned = TRUE; + str = nm_strndup_a(/* lgtm [cpp/alloca-in-loop] */ + 200, + str, + strlen(str), + &str_clone_free); + } + s = str; + str[good] = '\0'; + str += good + 1; + } + + i64 = _nm_utils_ascii_str_to_int64(s, 10, 0, G_MAXINT64, -1); + if (i64 == -1) + return NULL; + + if (len >= n_alloc) { + if (n_alloc > 0) { + n_alloc = n_alloc * 2; + arr = g_realloc(arr, n_alloc * sizeof(gint64)); + } else { + n_alloc = 4; + arr = g_new(gint64, n_alloc); + } + } + arr[len++] = i64; + } + + if (len > 1) { + /* sort the list of indexes descendingly, and drop duplicates. */ + g_qsort_with_data(arr, len, sizeof(gint64), _int64_cmp_desc, NULL); + j = 1; + for (i = 1; i < len; i++) { + nm_assert(arr[i - 1] >= arr[i]); + if (arr[i - 1] > arr[i]) + arr[j++] = arr[i]; + } + len = j; + } + + *out_len = len; + return g_steal_pointer(&arr); +} + +#define ESCAPED_TOKENS_WITH_SPACES_DELIMTER ' ' +#define ESCAPED_TOKENS_WITH_SPACES_DELIMTERS NM_ASCII_SPACES "," + +#define ESCAPED_TOKENS_DELIMITER ',' +#define ESCAPED_TOKENS_DELIMITERS "," + +typedef enum { + VALUE_STRSPLIT_MODE_OBJLIST, + VALUE_STRSPLIT_MODE_MULTILIST, + VALUE_STRSPLIT_MODE_ESCAPED_TOKENS, + VALUE_STRSPLIT_MODE_ESCAPED_TOKENS_WITH_SPACES, +} ValueStrsplitMode; + +static const char ** +_value_strsplit(const char *value, ValueStrsplitMode split_mode, gsize *out_len) +{ + gs_free const char **strv = NULL; + + /* FIXME: some modes should support backslash escaping. + * In particular, to distinguish from _value_str_as_index_list(), which + * does not accept '\\'. */ + + /* note that all modes remove empty tokens (",", "a,,b", ",,"). */ + switch (split_mode) { + case VALUE_STRSPLIT_MODE_OBJLIST: + strv = nm_utils_strsplit_set_full(value, + ESCAPED_TOKENS_DELIMITERS, + NM_UTILS_STRSPLIT_SET_FLAGS_STRSTRIP); + break; + case VALUE_STRSPLIT_MODE_MULTILIST: + strv = nm_utils_strsplit_set_full(value, + ESCAPED_TOKENS_WITH_SPACES_DELIMTERS, + NM_UTILS_STRSPLIT_SET_FLAGS_STRSTRIP); + break; + case VALUE_STRSPLIT_MODE_ESCAPED_TOKENS: + strv = nm_utils_escaped_tokens_split(value, ESCAPED_TOKENS_DELIMITERS); + break; + case VALUE_STRSPLIT_MODE_ESCAPED_TOKENS_WITH_SPACES: + strv = nm_utils_escaped_tokens_split(value, ESCAPED_TOKENS_WITH_SPACES_DELIMTERS); + break; + } + + NM_SET_OUT(out_len, NM_PTRARRAY_LEN(strv)); + return g_steal_pointer(&strv); +} + +static gboolean +_value_strsplit_assert_unsplitable(const char *str) +{ +#if NM_MORE_ASSERTS > 5 + gs_free const char **strv_test = NULL; + gsize j, l; + + /* Assert that we cannot split the token and that it + * has no unescaped delimiters. */ + + strv_test = _value_strsplit(str, VALUE_STRSPLIT_MODE_ESCAPED_TOKENS, NULL); + nm_assert(NM_PTRARRAY_LEN(strv_test) == 1); + + for (j = 0; str[j] != '\0';) { + if (str[j] == '\\') { + j++; + nm_assert(str[j] != '\0'); + } else + nm_assert(!NM_IN_SET(str[j], '\0', ',')); + j++; + } + l = j; + nm_assert(!g_ascii_isspace(str[l - 1]) || (l >= 2 && str[l - 2] == '\\')); +#endif + + return TRUE; +} + +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; + const char * next_hop = NULL; + int prefix; + NMIPRoute * route = NULL; + GError * local = NULL; + gint64 metric = -1; + guint i; + gs_free const char **routev = NULL; + gs_free char * str_clean_free = NULL; + const char * str_clean; + gs_free char * dest_clone = NULL; + const char * dest; + const char * plen; + gs_unref_hashtable GHashTable *attrs = NULL; +#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 = nm_strstrip_avoid_copy_a(300, str, &str_clean_free); + routev = nm_utils_strsplit_set(str_clean, " \t"); + if (!routev) { + 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) { + dest_clone = g_strdup(dest); + plen = &dest_clone[plen - dest]; + dest = dest_clone; + *((char *) plen) = '\0'; + plen++; + } + prefix = MAX_PREFIX; + if (plen) { + if ((prefix = _nm_utils_ascii_str_to_int64(plen, 10, 0, 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_is_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; + gs_unref_hashtable GHashTable *tmp_attrs = NULL; + + 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_full(nm_str_hash, + g_str_equal, + g_free, + (GDestroyNotify) g_variant_unref); + } + + g_hash_table_iter_init(&iter, tmp_attrs); + while ( + g_hash_table_iter_next(&iter, (gpointer *) &iter_key, (gpointer *) &iter_value)) { + /* need to sink the reference, because nm_utils_parse_variant_attributes() returns + * floating refs. */ + g_variant_ref_sink(iter_value); + + if (!nm_ip_route_attribute_validate(iter_key, iter_value, family, NULL, error)) { + g_prefix_error(error, "%s: ", iter_key); + return NULL; + } + g_hash_table_insert(attrs, iter_key, iter_value); + g_hash_table_iter_steal(&iter); + } + } 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; + } + + 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 */ + +/* + * 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; + 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; + } + config = config_clone = g_steal_pointer(&contents); + } + } + + *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, \ + gboolean *out_is_default, gpointer *out_to_free + +#define ARGS_SET_FCN \ + const NMMetaPropertyInfo *property_info, const NMMetaEnvironment *environment, \ + gpointer environment_user_data, NMSetting *setting, NMMetaAccessorModifier modifier, \ + 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, GError **error + +#define ARGS_COMPLETE_FCN \ + const NMMetaPropertyInfo *property_info, const NMMetaEnvironment *environment, \ + gpointer environment_user_data, const NMMetaOperationContext *operation_context, \ + const char *text, gboolean *out_complete_filename, 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 + +static gboolean _set_fcn_optionlist(ARGS_SET_FCN); + +static gboolean +_SET_FCN_DO_RESET_DEFAULT(const NMMetaPropertyInfo *property_info, + NMMetaAccessorModifier modifier, + const char * value) +{ + nm_assert(property_info); + nm_assert(!property_info->property_type->set_supports_remove); + nm_assert(NM_IN_SET(modifier, NM_META_ACCESSOR_MODIFIER_SET, NM_META_ACCESSOR_MODIFIER_ADD)); + nm_assert(value || modifier == NM_META_ACCESSOR_MODIFIER_SET); + + return value == NULL; +} + +static gboolean +_SET_FCN_DO_RESET_DEFAULT_WITH_SUPPORTS_REMOVE(const NMMetaPropertyInfo *property_info, + NMMetaAccessorModifier modifier, + const char * value) +{ + nm_assert(property_info); + nm_assert(property_info->property_type->set_supports_remove); + nm_assert(NM_IN_SET(modifier, + NM_META_ACCESSOR_MODIFIER_SET, + NM_META_ACCESSOR_MODIFIER_ADD, + NM_META_ACCESSOR_MODIFIER_DEL)); + nm_assert(value || modifier == NM_META_ACCESSOR_MODIFIER_SET); + + return value == NULL; +} + +static gboolean +_SET_FCN_DO_SET_ALL(NMMetaAccessorModifier modifier, const char *value) +{ + nm_assert(NM_IN_SET(modifier, + NM_META_ACCESSOR_MODIFIER_SET, + NM_META_ACCESSOR_MODIFIER_ADD, + NM_META_ACCESSOR_MODIFIER_DEL)); + nm_assert(value); + + return modifier == NM_META_ACCESSOR_MODIFIER_SET; +} + +static gboolean +_SET_FCN_DO_REMOVE(NMMetaAccessorModifier modifier, const char *value) +{ + nm_assert(NM_IN_SET(modifier, + NM_META_ACCESSOR_MODIFIER_SET, + NM_META_ACCESSOR_MODIFIER_ADD, + NM_META_ACCESSOR_MODIFIER_DEL)); + nm_assert(value); + + return modifier == NM_META_ACCESSOR_MODIFIER_DEL; +} + +#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 + +#define RETURN_STR_TEMPORARY(val) \ + G_STMT_START \ + { \ + const char *_val = (val); \ + \ + if (_val == NULL) \ + return NULL; \ + if (_val[0] == '\0') \ + return ""; \ + return ((*(out_to_free)) = g_strdup(_val)); \ + } \ + G_STMT_END + +static gboolean +_gobject_property_is_default(NMSetting *setting, const char *prop_name) +{ + nm_auto_unset_gvalue GValue v = G_VALUE_INIT; + GParamSpec * pspec; + GHashTable * ht; + char ** strv; + + pspec = g_object_class_find_property(G_OBJECT_GET_CLASS(G_OBJECT(setting)), prop_name); + if (!G_IS_PARAM_SPEC(pspec)) + g_return_val_if_reached(FALSE); + + g_value_init(&v, pspec->value_type); + g_object_get_property(G_OBJECT(setting), prop_name, &v); + + if (pspec->value_type == G_TYPE_STRV) { + strv = g_value_get_boxed(&v); + return !strv || !strv[0]; + } else if (pspec->value_type == G_TYPE_HASH_TABLE) { + ht = g_value_get_boxed(&v); + return !ht || !g_hash_table_size(ht); + } + + return g_param_value_defaults(pspec, &v); +} + +static gboolean +_gobject_property_reset(NMSetting *setting, const char *prop_name, gboolean reset_default) +{ + nm_auto_unset_gvalue GValue v = G_VALUE_INIT; + GParamSpec * pspec; + + pspec = g_object_class_find_property(G_OBJECT_GET_CLASS(G_OBJECT(setting)), prop_name); + if (!G_IS_PARAM_SPEC(pspec)) + g_return_val_if_reached(FALSE); + + g_value_init(&v, pspec->value_type); + if (reset_default) + g_param_value_set_default(pspec, &v); + g_object_set_property(G_OBJECT(setting), prop_name, &v); + return TRUE; +} + +static gboolean +_gobject_property_reset_default(NMSetting *setting, const char *prop_name) +{ + return _gobject_property_reset(setting, prop_name, TRUE); +} + +static const char * +_coerce_str_emptyunset(NMMetaAccessorGetType get_type, + gboolean is_default, + const char * cstr, + char ** out_str) +{ + nm_assert(out_str && !*out_str); + nm_assert((!is_default && cstr && cstr[0] != '\0') || NM_IN_STRSET(cstr, NULL, "")); + + if (get_type == NM_META_ACCESSOR_GET_TYPE_PRETTY) { + if (!cstr || cstr[0] == '\0') { + if (is_default) + return ""; + else + return "\"\""; + } + nm_assert(!is_default); + return (*out_str = g_strdup_printf("\"%s\"", cstr)); + } + + /* we coerce NULL/"" to either "" or " ". */ + if (!cstr || cstr[0] == '\0') { + if (is_default) + return ""; + else + return " "; + } + nm_assert(!is_default); + return cstr; +} + +#define RETURN_STR_EMPTYUNSET(get_type, is_default, cstr) \ + G_STMT_START \ + { \ + char * _str = NULL; \ + const char *_cstr; \ + \ + _cstr = _coerce_str_emptyunset((get_type), (is_default), (cstr), &_str); \ + if (_str) \ + RETURN_STR_TO_FREE(_str); \ + RETURN_STR_TEMPORARY(_cstr); \ + } \ + G_STMT_END + +static gboolean +_is_default(const NMMetaPropertyInfo *property_info, NMSetting *setting) +{ + if (property_info->property_typ_data && property_info->property_typ_data->is_default_fcn) + return !!(property_info->property_typ_data->is_default_fcn(setting)); + + return _gobject_property_is_default(setting, property_info->property_name); +} + +static gconstpointer +_get_fcn_gobject_impl(const NMMetaPropertyInfo *property_info, + NMSetting * setting, + NMMetaAccessorGetType get_type, + gboolean handle_emptyunset, + gboolean * out_is_default, + gpointer * out_to_free) +{ + const char * cstr; + GType gtype_prop; + nm_auto_unset_gvalue GValue val = G_VALUE_INIT; + gboolean is_default; + gboolean glib_handles_str_transform; + + RETURN_UNSUPPORTED_GET_TYPE(); + + is_default = _is_default(property_info, setting); + + NM_SET_OUT(out_is_default, is_default); + + gtype_prop = _gobject_property_get_gtype(G_OBJECT(setting), property_info->property_name); + + glib_handles_str_transform = + !NM_IN_SET(gtype_prop, G_TYPE_BOOLEAN, G_TYPE_STRV, G_TYPE_BYTES, G_TYPE_HASH_TABLE); + + if (glib_handles_str_transform) { + /* We rely on the type conversion of the gobject property to string. */ + g_value_init(&val, G_TYPE_STRING); + } else + g_value_init(&val, gtype_prop); + + g_object_get_property(G_OBJECT(setting), property_info->property_name, &val); + + /* Currently, only one particular property asks us to "handle_emptyunset". + * So, don't implement it (yet) for the other types, where it's unneeded. */ + nm_assert(!handle_emptyunset || (gtype_prop == G_TYPE_STRV && !glib_handles_str_transform)); + + if (gtype_prop == G_TYPE_STRING) { + nm_assert(glib_handles_str_transform); + nm_assert(!handle_emptyunset); + if (property_info->property_typ_data + && property_info->property_typ_data->subtype.gobject_string.handle_emptyunset) { + /* This string property can both be empty and NULL. We need to + * signal them differently. */ + cstr = g_value_get_string(&val); + nm_assert((!!is_default) == (cstr == NULL)); + RETURN_STR_EMPTYUNSET(get_type, is_default, cstr); + } + } + + if (glib_handles_str_transform) + RETURN_STR_TEMPORARY(g_value_get_string(&val)); + + if (gtype_prop == G_TYPE_BOOLEAN) { + gboolean b; + + b = g_value_get_boolean(&val); + if (get_type == NM_META_ACCESSOR_GET_TYPE_PRETTY) + cstr = b ? _("yes") : _("no"); + else + cstr = b ? "yes" : "no"; + return cstr; + } + + if (gtype_prop == G_TYPE_STRV) { + const char *const *strv; + + strv = g_value_get_boxed(&val); + if (strv && strv[0]) + RETURN_STR_TO_FREE(g_strjoinv(",", (char **) strv)); + + if (handle_emptyunset) { + /* we need to express empty lists from unset lists differently. */ + RETURN_STR_EMPTYUNSET(get_type, is_default, NULL); + } + + return ""; + } + + if (gtype_prop == G_TYPE_BYTES) { + char *str; + + str = bytes_to_string(g_value_get_boxed(&val)); + NM_SET_OUT(out_is_default, !str || !str[0]); + RETURN_STR_TO_FREE(str); + } + + if (gtype_prop == G_TYPE_HASH_TABLE) { + GHashTable * strdict; + gs_free const char **keys = NULL; + GString * str; + gsize i; + + nm_assert(property_info->setting_info + == &nm_meta_setting_infos_editor[NM_META_SETTING_TYPE_WIRED] + && NM_IN_STRSET(property_info->property_name, NM_SETTING_WIRED_S390_OPTIONS)); + nm_assert(property_info->property_type->set_fcn == _set_fcn_optionlist); + + strdict = g_value_get_boxed(&val); + keys = nm_utils_strdict_get_keys(strdict, TRUE, NULL); + if (!keys) + return NULL; + + str = g_string_new(NULL); + for (i = 0; keys[i]; i++) { + const char * key = keys[i]; + const char * v = g_hash_table_lookup(strdict, key); + gs_free char *escaped_key = NULL; + gs_free char *escaped_val = NULL; + + if (str->len > 0) + g_string_append_c(str, ','); + g_string_append(str, nm_utils_escaped_tokens_options_escape_key(key, &escaped_key)); + g_string_append_c(str, '='); + g_string_append(str, nm_utils_escaped_tokens_options_escape_val(v, &escaped_val)); + } + RETURN_STR_TO_FREE(g_string_free(str, FALSE)); + } + + nm_assert_not_reached(); + return NULL; +} + +static gconstpointer _get_fcn_gobject(ARGS_GET_FCN) +{ + return _get_fcn_gobject_impl(property_info, + setting, + get_type, + FALSE, + out_is_default, + out_to_free); +} + +static gconstpointer _get_fcn_gobject_int(ARGS_GET_FCN) +{ + GParamSpec * pspec; + nm_auto_unset_gvalue GValue gval = G_VALUE_INIT; + gboolean is_uint64 = FALSE; + NMMetaSignUnsignInt64 v; + guint base = 10; + const NMMetaUtilsIntValueInfo *value_infos; + char * return_str; + + 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); + NM_SET_OUT(out_is_default, g_param_value_defaults(pspec, &gval)); + switch (pspec->value_type) { + case G_TYPE_INT: + v.i64 = g_value_get_int(&gval); + break; + case G_TYPE_UINT: + v.u64 = g_value_get_uint(&gval); + is_uint64 = TRUE; + break; + case G_TYPE_INT64: + v.i64 = g_value_get_int64(&gval); + break; + case G_TYPE_UINT64: + v.u64 = g_value_get_uint64(&gval); + is_uint64 = TRUE; + break; + default: + g_return_val_if_reached(NULL); + break; + } + + if (property_info->property_typ_data + && property_info->property_typ_data->subtype.gobject_int.base > 0) { + base = property_info->property_typ_data->subtype.gobject_int.base; + } + + switch (base) { + case 10: + if (is_uint64) + return_str = g_strdup_printf("%" G_GUINT64_FORMAT, v.u64); + else + return_str = g_strdup_printf("%" G_GINT64_FORMAT, v.i64); + break; + case 16: + if (is_uint64) + return_str = g_strdup_printf("0x%" G_GINT64_MODIFIER "x", v.u64); + else + return_str = g_strdup_printf("0x%" G_GINT64_MODIFIER "x", (guint64) v.i64); + break; + default: + return_str = NULL; + g_assert_not_reached(); + } + + 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 ((is_uint64 && value_infos->value.u64 == v.u64) + || (!is_uint64 && value_infos->value.i64 == v.i64)) { + gs_free char *old_str = return_str; + + return_str = g_strdup_printf("%s (%s)", old_str, value_infos->nick); + break; + } + } + } + + RETURN_STR_TO_FREE(return_str); +} + +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, + FALSE, + out_is_default, + out_to_free); + + mtu = property_info->property_typ_data->subtype.mtu.get_fcn(setting); + if (mtu == 0) { + NM_SET_OUT(out_is_default, TRUE); + 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; + nm_auto_unref_gtypeclass GTypeClass *gtype_class = NULL; + nm_auto_unref_gtypeclass GTypeClass *gtype_prop_class = NULL; + const struct _NMUtilsEnumValueInfo * value_infos = 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]; + GParamSpec * pspec; + + 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); + + pspec = g_object_class_find_property(G_OBJECT_GET_CLASS(setting), property_info->property_name); + g_return_val_if_fail(pspec, NULL); + + g_value_init(&gval, pspec->value_type); + g_object_get_property(G_OBJECT(setting), property_info->property_name, &gval); + NM_SET_OUT(out_is_default, g_param_value_defaults(pspec, &gval)); + + if (pspec->value_type == G_TYPE_INT + || (G_TYPE_IS_CLASSED(pspec->value_type) + && G_IS_ENUM_CLASS( + (gtype_prop_class ?: (gtype_prop_class = g_type_class_ref(pspec->value_type)))))) { + if (pspec->value_type == 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 (pspec->value_type == G_TYPE_UINT + || (G_TYPE_IS_CLASSED(pspec->value_type) + && G_IS_FLAGS_CLASS( + (gtype_prop_class + ?: (gtype_prop_class = g_type_class_ref(pspec->value_type)))))) { + if (pspec->value_type == 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 = pspec->value_type; + 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_MODIFIER "x", 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. */ + + if (property_info->property_typ_data) + value_infos = property_info->property_typ_data->subtype.gobject_enum.value_infos_get; + s = _nm_utils_enum_to_str_full(gtype, (int) v, ", ", value_infos); + + 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_MODIFIER "x", 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 (_SET_FCN_DO_RESET_DEFAULT(property_info, modifier, value)) + return _gobject_property_reset_default(setting, property_info->property_name); + + if (property_info->property_typ_data) { + if (property_info->property_typ_data->subtype.gobject_string.handle_emptyunset) { + if (value && value[0] && NM_STRCHAR_ALL(value, ch, ch == ' ')) { + /* this string property can both be %NULL and empty. To express that, we coerce + * a value of all whitespaces to dropping the first whitespace. That means, + * " " gives "", " " gives " ", and so on. + * + * This way the user can set the string value to "" (meaning NULL) and to + * " " (meaning ""), and any other string. + * + * This is and non-obvious escaping mechanism. But out of all the possible + * solutions, it seems the most sensible one. */ + value++; + } + } + 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_impl(const NMMetaPropertyInfo *property_info, + NMSetting * setting, + NMMetaAccessorModifier modifier, + const char * value, + GError ** error) +{ + gboolean val_bool; + + if (_SET_FCN_DO_RESET_DEFAULT(property_info, modifier, value)) + return _gobject_property_reset_default(setting, property_info->property_name); + + 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_bool(ARGS_SET_FCN) +{ + return _set_fcn_gobject_bool_impl(property_info, setting, modifier, value, error); +} + +static gboolean _set_fcn_gobject_ternary(ARGS_SET_FCN) +{ + NMTernary val; + + nm_assert(_gobject_property_get_gtype(G_OBJECT(setting), property_info->property_name) + == NM_TYPE_TERNARY); + + if (_SET_FCN_DO_RESET_DEFAULT(property_info, modifier, value)) + return _gobject_property_reset_default(setting, property_info->property_name); + + if (!nmc_string_to_ternary(value, &val, error)) + return FALSE; + + g_object_set(setting, property_info->property_name, (int) val, 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; + gboolean is_uint64; + NMMetaSignUnsignInt64 v; + gboolean has_minmax = FALSE; + NMMetaSignUnsignInt64 min = {0}; + NMMetaSignUnsignInt64 max = {0}; + guint base = 10; + const NMMetaUtilsIntValueInfo *value_infos; + + if (_SET_FCN_DO_RESET_DEFAULT(property_info, modifier, value)) + return _gobject_property_reset_default(setting, property_info->property_name); + + 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); + + is_uint64 = NM_IN_SET(pspec->value_type, G_TYPE_UINT, G_TYPE_UINT64); + + if (property_info->property_typ_data) { + if (value + && (value_infos = property_info->property_typ_data->subtype.gobject_int.value_infos)) { + gs_free char *vv_free = NULL; + const char * vv; + + vv = nm_strstrip_avoid_copy_a(300, value, &vv_free); + for (; value_infos->nick; value_infos++) { + if (nm_streq(value_infos->nick, vv)) { + v = value_infos->value; + goto have_value_from_nick; + } + } + } + + if (property_info->property_typ_data->subtype.gobject_int.base > 0) + base = property_info->property_typ_data->subtype.gobject_int.base; + + if ((is_uint64 + && (property_info->property_typ_data->subtype.gobject_int.min.u64 + || property_info->property_typ_data->subtype.gobject_int.max.u64)) + || (!is_uint64 + && (property_info->property_typ_data->subtype.gobject_int.min.i64 + || property_info->property_typ_data->subtype.gobject_int.max.i64))) { + min = property_info->property_typ_data->subtype.gobject_int.min; + max = property_info->property_typ_data->subtype.gobject_int.max; + has_minmax = TRUE; + } + } + + if (!has_minmax) { + switch (pspec->value_type) { + case G_TYPE_INT: + { + const GParamSpecInt *p = (GParamSpecInt *) pspec; + + min.i64 = p->minimum; + max.i64 = p->maximum; + } break; + case G_TYPE_UINT: + { + const GParamSpecUInt *p = (GParamSpecUInt *) pspec; + + min.u64 = p->minimum; + max.u64 = p->maximum; + } break; + case G_TYPE_INT64: + { + const GParamSpecInt64 *p = (GParamSpecInt64 *) pspec; + + min.i64 = p->minimum; + max.i64 = p->maximum; + } break; + case G_TYPE_UINT64: + { + const GParamSpecUInt64 *p = (GParamSpecUInt64 *) pspec; + + min.u64 = p->minimum; + max.u64 = p->maximum; + } break; + default: + g_return_val_if_reached(FALSE); + } + } + + if (is_uint64) + v.u64 = _nm_utils_ascii_str_to_uint64(value, base, min.u64, max.u64, 0); + else + v.i64 = _nm_utils_ascii_str_to_int64(value, base, min.i64, max.i64, 0); + + if ((errsv = errno) != 0) { + if (errsv == ERANGE) { + if (is_uint64) { + g_set_error( + error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_INVALID_ARGUMENT, + _("'%s' is out of range [%" G_GUINT64_FORMAT ", %" G_GUINT64_FORMAT "]"), + value, + min.u64, + max.u64); + } else { + g_set_error(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_INVALID_ARGUMENT, + _("'%s' is out of range [%" G_GINT64_FORMAT ", %" G_GINT64_FORMAT "]"), + value, + min.i64, + max.i64); + } + } else { + g_set_error(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_INVALID_ARGUMENT, + _("'%s' is not a valid number"), + value); + } + return FALSE; + } + +have_value_from_nick: + + g_value_init(&gval, pspec->value_type); + switch (pspec->value_type) { + case G_TYPE_INT: + g_value_set_int(&gval, v.i64); + break; + case G_TYPE_UINT: + g_value_set_uint(&gval, v.u64); + break; + case G_TYPE_INT64: + g_value_set_int64(&gval, v.i64); + break; + case G_TYPE_UINT64: + g_value_set_uint64(&gval, v.u64); + break; + default: + g_return_val_if_reached(FALSE); + 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 (_SET_FCN_DO_RESET_DEFAULT(property_info, modifier, value)) + return _gobject_property_reset_default(setting, property_info->property_name); + + if (nm_streq(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; +} + +/* Ideally we'll be able to get this from a public header. */ +#ifndef IEEE802154_ADDR_LEN + #define IEEE802154_ADDR_LEN 8 +#endif + +static gboolean _set_fcn_gobject_mac(ARGS_SET_FCN) +{ + NMMetaPropertyTypeMacMode mode; + gboolean valid; + + if (_SET_FCN_DO_RESET_DEFAULT(property_info, modifier, value)) + return _gobject_property_reset_default(setting, property_info->property_name); + + 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 if (mode == NM_META_PROPERTY_TYPE_MAC_MODE_WPAN) { + valid = nm_utils_hwaddr_valid(value, IEEE802154_ADDR_LEN); + } 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_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_prop_class = NULL; + nm_auto_unref_gtypeclass GTypeClass *gtype_class = NULL; + gboolean is_flags; + int v; + + if (_SET_FCN_DO_RESET_DEFAULT_WITH_SUPPORTS_REMOVE(property_info, modifier, value)) + return _gobject_property_reset_default(setting, property_info->property_name); + + 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_prop_class = g_type_class_ref(gtype)) + && ((is_flags = G_IS_FLAGS_CLASS(gtype_prop_class)) || G_IS_ENUM_CLASS(gtype_prop_class))) { + /* valid */ + } else if (!has_gtype && G_TYPE_IS_CLASSED(gtype_prop) + && (gtype_prop_class = g_type_class_ref(gtype_prop)) + && ((is_flags = G_IS_FLAGS_CLASS(gtype_prop_class)) + || G_IS_ENUM_CLASS(gtype_prop_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); + } + + gtype_class = g_type_class_ref(gtype); + + if (G_IS_FLAGS_CLASS(gtype_class) && !_SET_FCN_DO_SET_ALL(modifier, value)) { + nm_auto_unset_gvalue GValue int_value = {}; + guint v_flag; + + g_value_init(&int_value, G_TYPE_UINT); + g_object_get_property(G_OBJECT(setting), property_info->property_name, &int_value); + v_flag = g_value_get_uint(&int_value); + + if (_SET_FCN_DO_REMOVE(modifier, value)) + v = (int) (v_flag & ~((guint) v)); + else + v = (int) (v_flag | ((guint) 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 (is_flags) { + nm_assert(G_IS_FLAGS_CLASS(gtype_prop_class)); + g_value_set_flags(&gval, v); + } else { + nm_assert(G_IS_ENUM_CLASS(gtype_prop_class)); + g_value_set_enum(&gval, v); + } + + 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 = (int) 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; + + 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 = (int) 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 = nm_utils_strv_make_deep_copied(nm_utils_enum_get_values(gtype, min, max)); + 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_ternary(ARGS_COMPLETE_FCN) +{ + static const char *const v[] = { + "on", + "off", + "1", + "0", + "-1", + "yes", + "no", + "unknown", + "true", + "false", + "default", + NULL, + }; + + if (!text || !text[0]) + return &v[8]; + 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 * +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 * +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 const char * +_multilist_do_validate(const NMMetaPropertyInfo *property_info, + NMSetting * setting, + const char * item, + GError ** error) +{ + if (property_info->property_typ_data->values_static) { + nm_assert(!property_info->property_typ_data->subtype.multilist.validate_fcn); + return nmc_string_is_valid(item, + (const char **) property_info->property_typ_data->values_static, + error); + } + if (property_info->property_typ_data->subtype.multilist.validate_fcn) { + return property_info->property_typ_data->subtype.multilist.validate_fcn(item, error); + } + if (property_info->property_typ_data->subtype.multilist.validate2_fcn) { + return property_info->property_typ_data->subtype.multilist.validate2_fcn(setting, + item, + error); + } + + return item; +} + +static gconstpointer _get_fcn_multilist(ARGS_GET_FCN) +{ + return _get_fcn_gobject_impl( + property_info, + setting, + get_type, + property_info->property_typ_data->subtype.multilist.clear_emptyunset_fcn != NULL, + out_is_default, + out_to_free); +} + +static gboolean +_multilist_clear_property(const NMMetaPropertyInfo *property_info, + NMSetting * setting, + gboolean is_set /* or else set default */) +{ + if (property_info->property_typ_data->subtype.multilist.clear_emptyunset_fcn) { + property_info->property_typ_data->subtype.multilist.clear_emptyunset_fcn(setting, is_set); + return TRUE; + } + if (property_info->property_typ_data->subtype.multilist.clear_all_fcn) { + property_info->property_typ_data->subtype.multilist.clear_all_fcn(setting); + return TRUE; + } + return _gobject_property_reset(setting, property_info->property_name, FALSE); +} + +static gboolean _set_fcn_multilist(ARGS_SET_FCN) +{ + gs_free const char **strv = NULL; + gsize i, j, nstrv; + + if (_SET_FCN_DO_RESET_DEFAULT_WITH_SUPPORTS_REMOVE(property_info, modifier, value)) + return _multilist_clear_property(property_info, setting, FALSE); + + if (_SET_FCN_DO_REMOVE(modifier, value) + && (property_info->property_typ_data->subtype.multilist.remove_by_idx_fcn_u32 + || property_info->property_typ_data->subtype.multilist.remove_by_idx_fcn_s + || property_info->property_typ_data->subtype.multilist.remove_by_idx_fcn_u)) { + gs_free gint64 *indexes = NULL; + + indexes = _value_str_as_index_list(value, &nstrv); + if (indexes) { + gint64 num; + + if (property_info->property_typ_data->subtype.multilist.get_num_fcn_u32) + num = property_info->property_typ_data->subtype.multilist.get_num_fcn_u32(setting); + else + num = property_info->property_typ_data->subtype.multilist.get_num_fcn_u(setting); + for (i = 0; i < nstrv; i++) { + gint64 idx = indexes[i]; + + if (idx >= num) + continue; + + if (property_info->property_typ_data->subtype.multilist.remove_by_idx_fcn_u32) + property_info->property_typ_data->subtype.multilist.remove_by_idx_fcn_u32( + setting, + idx); + else if (property_info->property_typ_data->subtype.multilist.remove_by_idx_fcn_s) + property_info->property_typ_data->subtype.multilist.remove_by_idx_fcn_s(setting, + idx); + else + property_info->property_typ_data->subtype.multilist.remove_by_idx_fcn_u(setting, + idx); + } + return TRUE; + } + } + + if (_SET_FCN_DO_SET_ALL(modifier, value) + && property_info->property_typ_data->subtype.multilist.clear_emptyunset_fcn + && value[0] == '\0') + return _multilist_clear_property(property_info, setting, FALSE); + + strv = _value_strsplit( + value, + property_info->property_typ_data->subtype.multilist.strsplit_plain + ? VALUE_STRSPLIT_MODE_MULTILIST + : (property_info->property_typ_data->subtype.multilist.strsplit_with_spaces + ? VALUE_STRSPLIT_MODE_ESCAPED_TOKENS_WITH_SPACES + : VALUE_STRSPLIT_MODE_ESCAPED_TOKENS), + &nstrv); + + j = 0; + for (i = 0; i < nstrv; i++) { + const char *item = strv[i]; + + item = _multilist_do_validate(property_info, setting, item, error); + if (!item) + return FALSE; + strv[j++] = item; + } + nstrv = j; + + if (_SET_FCN_DO_SET_ALL(modifier, value)) + _multilist_clear_property(property_info, setting, TRUE); + else if (property_info->property_typ_data->subtype.multilist.clear_emptyunset_fcn + && _is_default(property_info, setting)) { + /* the property is already the default. But we hav here a '+' / '-' modifier, so + * that always makes it non-default (empty) first. */ + _multilist_clear_property(property_info, setting, TRUE); + } + + for (i = 0; i < nstrv; i++) { + if (_SET_FCN_DO_REMOVE(modifier, value)) { + property_info->property_typ_data->subtype.multilist.remove_by_value_fcn(setting, + strv[i]); + } else { + if (property_info->property_typ_data->subtype.multilist.add2_fcn) + property_info->property_typ_data->subtype.multilist.add2_fcn(setting, strv[i]); + else + property_info->property_typ_data->subtype.multilist.add_fcn(setting, strv[i]); + } + } + return TRUE; +} + +static gboolean _set_fcn_optionlist(ARGS_SET_FCN) +{ + gs_free const char **strv = NULL; + gs_free const char **strv_val = NULL; + gsize strv_len; + gsize i, nstrv; + + nm_assert(!error || !*error); + + if (_SET_FCN_DO_RESET_DEFAULT_WITH_SUPPORTS_REMOVE(property_info, modifier, value)) + return _gobject_property_reset_default(setting, property_info->property_name); + + nstrv = 0; + strv = nm_utils_escaped_tokens_options_split_list(value); + if (strv) { + strv_len = NM_PTRARRAY_LEN(strv); + + strv_val = g_new(const char *, strv_len); + for (i = 0; strv[i]; i++) { + const char *opt_name; + const char *opt_value; + + nm_utils_escaped_tokens_options_split((char *) strv[i], &opt_name, &opt_value); + + if (property_info->property_type->values_fcn + || property_info->property_typ_data->values_static) { + gs_strfreev char **valid_options_to_free = NULL; + const char *const *valid_options; + + if (property_info->property_type->values_fcn) + valid_options = + property_info->property_type->values_fcn(property_info, + &valid_options_to_free); + else + valid_options = property_info->property_typ_data->values_static; + + opt_name = nmc_string_is_valid(opt_name, (const char **) valid_options, error); + if (!opt_name) + return FALSE; + } + + if (opt_value) { + if (_SET_FCN_DO_REMOVE(modifier, value)) + opt_value = NULL; + } else { + if (!_SET_FCN_DO_REMOVE(modifier, value)) { + nm_utils_error_set(error, + NM_UTILS_ERROR_INVALID_ARGUMENT, + _("'%s' is not valid; use