/* 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-glib-aux/nm-uuid.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) const NMUtilsEnumValueInfo GOBJECT_ENUM_VALUE_INFOS_GET_FROM_SETTER[1]; /*****************************************************************************/ 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(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_strsplit_set_full(value, ESCAPED_TOKENS_DELIMITERS, NM_STRSPLIT_SET_FLAGS_STRSTRIP); break; case VALUE_STRSPLIT_MODE_MULTILIST: strv = nm_strsplit_set_full(value, ESCAPED_TOKENS_WITH_SPACES_DELIMTERS, NM_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, 0, MAX_PREFIX, -1)) == -1) { nm_utils_error_set(error, NM_UTILS_ERROR_INVALID_ARGUMENT, _("invalid prefix '%s'; <0-%d> allowed"), plen, MAX_PREFIX); return NULL; } } addr = nm_ip_address_new(family, ip_str, prefix, &local); if (!addr) { nm_utils_error_set(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_strsplit_set(str_clean, " \t"); if (!routev) { nm_utils_error_set(error, NM_UTILS_ERROR_UNKNOWN, "'%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) { nm_utils_error_set(error, NM_UTILS_ERROR_INVALID_ARGUMENT, _("invalid prefix '%s'; <0-%d> allowed"), plen, MAX_PREFIX); return NULL; } } for (i = 1; routev[i]; i++) { gint64 tmp64; if (nm_inet_is_valid(family, routev[i])) { if (metric != -1 || attrs) { nm_utils_error_set(error, NM_UTILS_ERROR_UNKNOWN, _("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) { nm_utils_error_set(error, NM_UTILS_ERROR_UNKNOWN, _("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 { nm_utils_error_set(error, NM_UTILS_ERROR_UNKNOWN, "%s", ROUTE_SYNTAX); return NULL; } } route = nm_ip_route_new(family, dest, prefix, next_hop, metric, &local); if (!route) { nm_utils_error_set(error, NM_UTILS_ERROR_UNKNOWN, _("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) { nm_utils_error_set(error, NM_UTILS_ERROR_INVALID_ARGUMENT, _("cannot read pac-script from file '%s'"), script); return FALSE; } } else { if (c_len != strlen(contents)) { nm_utils_error_set(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) { nm_utils_error_set(error, NM_UTILS_ERROR_INVALID_ARGUMENT, _("'%s' does not contain a valid PAC Script"), filename); } else { nm_utils_error_set(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) { nm_utils_error_set(error, NM_UTILS_ERROR_INVALID_ARGUMENT, _("cannot read team config from file '%s'"), config); return FALSE; } } else { if (c_len != strlen(contents)) { nm_utils_error_set(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_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 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)); } if (property_info->property_typ_data) { value_infos = property_info->property_typ_data->subtype.gobject_enum.value_infos_get; if (value_infos == GOBJECT_ENUM_VALUE_INFOS_GET_FROM_SETTER) value_infos = property_info->property_typ_data->subtype.gobject_enum.value_infos; } 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) { nm_utils_error_set( error, NM_UTILS_ERROR_INVALID_ARGUMENT, _("'%s' is out of range [%" G_GUINT64_FORMAT ", %" G_GUINT64_FORMAT "]"), value, min.u64, max.u64); } else { nm_utils_error_set( error, NM_UTILS_ERROR_INVALID_ARGUMENT, _("'%s' is out of range [%" G_GINT64_FORMAT ", %" G_GINT64_FORMAT "]"), value, min.i64, max.i64); } } else { nm_utils_error_set(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) { nm_utils_error_set(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) { nm_utils_error_set(error, NM_UTILS_ERROR_UNKNOWN, _("'%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) { nm_utils_error_set(error, NM_UTILS_ERROR_INVALID_ARGUMENT, _("invalid option '%s', use a combination of [%s]"), value, valid_str); } else { nm_utils_error_set(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) { const NMUtilsEnumValueInfo *value_infos = NULL; 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; } } /* There is a problem. For flags, we don't expand to all the values that we could * complete for. We only expand to a single flag "FLAG1", but if the property * is already set to "FLAG2", we should also expand to "FLAG1,FLAG2". */ v = nm_strv_make_deep_copied(nm_utils_enum_get_values(gtype, min, max)); if (property_info->property_typ_data && (value_infos = property_info->property_typ_data->subtype.gobject_enum.value_infos)) { const guint V_N = NM_PTRARRAY_LEN(v); guint n; guint i; nm_assert(value_infos[0].nick); for (n = 0; value_infos[n].nick;) n++; v = g_realloc(v, (V_N + n + 1) * sizeof(char *)); for (i = 0; i < n; i++) v[V_N + i] = g_strdup(value_infos[i].nick); v[V_N + n] = NULL; } 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