NetworkManager/libnm-core/nm-setting.c
Beniamino Galvani f4ced16791 libnm-core,cli: add VRF setting
Add new VRF setting and connection types to libnm-core and support
them in nmcli.
2020-01-14 09:49:01 +01:00

2684 lines
89 KiB
C

// SPDX-License-Identifier: LGPL-2.1+
/*
* Copyright (C) 2007 - 2011 Red Hat, Inc.
* Copyright (C) 2007 - 2008 Novell, Inc.
*/
#include "nm-default.h"
#include "nm-setting.h"
#include "nm-setting-private.h"
#include "nm-utils.h"
#include "nm-core-internal.h"
#include "nm-utils-private.h"
#include "nm-property-compare.h"
/**
* SECTION:nm-setting
* @short_description: Describes related configuration information
*
* Each #NMSetting contains properties that describe configuration that applies
* to a specific network layer (like IPv4 or IPv6 configuration) or device type
* (like Ethernet, or Wi-Fi). A collection of individual settings together
* make up an #NMConnection. Each property is strongly typed and usually has
* a number of allowed values. See each #NMSetting subclass for a description
* of properties and allowed values.
*/
/*****************************************************************************/
typedef struct {
GHashTable *hash;
const char **names;
GVariant **values;
} GenData;
typedef struct {
const char *name;
GType type;
NMSettingPriority priority;
} SettingInfo;
NM_GOBJECT_PROPERTIES_DEFINE (NMSetting,
PROP_NAME,
);
typedef struct {
GenData *gendata;
} NMSettingPrivate;
G_DEFINE_ABSTRACT_TYPE (NMSetting, nm_setting, G_TYPE_OBJECT)
#define NM_SETTING_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_SETTING, NMSettingPrivate))
/*****************************************************************************/
static GenData *_gendata_hash (NMSetting *setting, gboolean create_if_necessary);
/*****************************************************************************/
static NMSettingPriority
_get_base_type_priority (const NMMetaSettingInfo *setting_info,
GType gtype)
{
/* Historical oddity: PPPoE is a base-type even though it's not
* priority 1. It needs to be sorted *after* lower-level stuff like
* Wi-Fi security or 802.1x for secrets, but it's still allowed as a
* base type.
*/
if (setting_info) {
if ( NM_IN_SET (setting_info->setting_priority,
NM_SETTING_PRIORITY_HW_BASE,
NM_SETTING_PRIORITY_HW_NON_BASE)
|| gtype == NM_TYPE_SETTING_PPPOE)
return setting_info->setting_priority;
}
return NM_SETTING_PRIORITY_INVALID;
}
NMSettingPriority
_nm_setting_get_setting_priority (NMSetting *setting)
{
const NMMetaSettingInfo *setting_info;
g_return_val_if_fail (NM_IS_SETTING (setting), NM_SETTING_PRIORITY_INVALID);
setting_info = NM_SETTING_GET_CLASS (setting)->setting_info;
return setting_info ? setting_info->setting_priority : NM_SETTING_PRIORITY_INVALID;
}
NMSettingPriority
_nm_setting_type_get_base_type_priority (GType type)
{
return _get_base_type_priority (nm_meta_setting_infos_by_gtype (type),
type);
}
NMSettingPriority
_nm_setting_get_base_type_priority (NMSetting *setting)
{
g_return_val_if_fail (NM_IS_SETTING (setting), NM_SETTING_PRIORITY_INVALID);
return _get_base_type_priority (NM_SETTING_GET_CLASS (setting)->setting_info,
G_OBJECT_TYPE (setting));
}
/**
* nm_setting_lookup_type:
* @name: a setting name
*
* Returns the #GType of the setting's class for a given setting name.
*
* Returns: the #GType of the setting's class, or %G_TYPE_INVALID if
* @name is not recognized.
**/
GType
nm_setting_lookup_type (const char *name)
{
const NMMetaSettingInfo *setting_info;
g_return_val_if_fail (name, G_TYPE_INVALID);
setting_info = nm_meta_setting_infos_by_name (name);
return setting_info ? setting_info->get_setting_gtype () : G_TYPE_INVALID;
}
int
_nm_setting_compare_priority (gconstpointer a, gconstpointer b)
{
NMSettingPriority prio_a, prio_b;
prio_a = _nm_setting_get_setting_priority ((NMSetting *) a);
prio_b = _nm_setting_get_setting_priority ((NMSetting *) b);
if (prio_a < prio_b)
return -1;
else if (prio_a == prio_b)
return 0;
return 1;
}
/*****************************************************************************/
gboolean
_nm_setting_slave_type_is_valid (const char *slave_type, const char **out_port_type)
{
const char *port_type = NULL;
gboolean found = TRUE;
if (!slave_type)
found = FALSE;
else if (NM_IN_STRSET (slave_type,
NM_SETTING_BOND_SETTING_NAME,
NM_SETTING_VRF_SETTING_NAME)) {
/* pass */
}
else if (nm_streq (slave_type, NM_SETTING_BRIDGE_SETTING_NAME))
port_type = NM_SETTING_BRIDGE_PORT_SETTING_NAME;
else if (nm_streq (slave_type, NM_SETTING_OVS_BRIDGE_SETTING_NAME))
port_type = NM_SETTING_OVS_PORT_SETTING_NAME;
else if (nm_streq (slave_type, NM_SETTING_OVS_PORT_SETTING_NAME))
port_type = NM_SETTING_OVS_INTERFACE_SETTING_NAME;
else if (nm_streq (slave_type, NM_SETTING_TEAM_SETTING_NAME))
port_type = NM_SETTING_TEAM_PORT_SETTING_NAME;
else
found = FALSE;
if (out_port_type)
*out_port_type = port_type;
return found;
}
/*****************************************************************************/
static const NMSettInfoProperty *
_nm_sett_info_property_find_in_array (const NMSettInfoProperty *properties, guint len, const char *name)
{
guint i;
for (i = 0; i < len; i++) {
if (nm_streq (name, properties[i].name))
return &properties[i];
}
return NULL;
}
static GVariant *
_gprop_to_dbus_fcn_bytes (const GValue *val)
{
nm_assert (G_VALUE_HOLDS (val, G_TYPE_BYTES));
return nm_utils_gbytes_to_variant_ay (g_value_get_boxed (val));
}
static GVariant *
_gprop_to_dbus_fcn_enum (const GValue *val)
{
return g_variant_new_int32 (g_value_get_enum (val));
}
static GVariant *
_gprop_to_dbus_fcn_flags (const GValue *val)
{
return g_variant_new_uint32 (g_value_get_flags (val));
}
gboolean
_nm_properties_override_assert (const NMSettInfoProperty *prop_info)
{
nm_assert (prop_info);
nm_assert ((!!prop_info->name) != (!!prop_info->param_spec));
nm_assert (!prop_info->param_spec || !prop_info->name || nm_streq0 (prop_info->name, prop_info->param_spec->name));
#define _PROPERT_EXTRA(prop_info, member) \
({ \
const NMSettInfoProperty *_prop_info = (prop_info); \
\
(_prop_info->property_type ? _prop_info->property_type->member : 0); \
})
nm_assert (!_PROPERT_EXTRA (prop_info, gprop_from_dbus_fcn) || _PROPERT_EXTRA (prop_info, dbus_type));
nm_assert (!_PROPERT_EXTRA (prop_info, from_dbus_fcn) || _PROPERT_EXTRA (prop_info, dbus_type));
nm_assert (!_PROPERT_EXTRA (prop_info, to_dbus_fcn) || _PROPERT_EXTRA (prop_info, dbus_type));
nm_assert (!_PROPERT_EXTRA (prop_info, to_dbus_fcn) || !_PROPERT_EXTRA (prop_info, gprop_to_dbus_fcn));
nm_assert (!_PROPERT_EXTRA (prop_info, from_dbus_fcn) || !_PROPERT_EXTRA (prop_info, gprop_from_dbus_fcn));
nm_assert (!_PROPERT_EXTRA (prop_info, gprop_to_dbus_fcn) || prop_info->param_spec);
nm_assert (!_PROPERT_EXTRA (prop_info, gprop_from_dbus_fcn) || prop_info->param_spec);
#undef _PROPERT_EXTRA
return TRUE;
}
static NMSettInfoSetting _sett_info_settings[_NM_META_SETTING_TYPE_NUM];
const NMSettInfoSetting *
nmtst_sett_info_settings (void)
{
return _sett_info_settings;
}
static int
_property_infos_sort_cmp_setting_connection (gconstpointer p_a,
gconstpointer p_b,
gpointer user_data)
{
const NMSettInfoProperty *a = *((const NMSettInfoProperty *const*) p_a);
const NMSettInfoProperty *b = *((const NMSettInfoProperty *const*) p_b);
int c_name;
c_name = strcmp (a->name, b->name);
nm_assert (c_name != 0);
#define CMP_AND_RETURN(n_a, n_b, name) \
G_STMT_START { \
gboolean _is = nm_streq (n_a, ""name); \
\
if ( _is \
|| nm_streq (n_b, ""name)) \
return _is ? -1 : 1; \
} G_STMT_END
/* for [connection], report first id, uuid, type in that order. */
if (c_name != 0) {
CMP_AND_RETURN (a->name, b->name, NM_SETTING_CONNECTION_ID);
CMP_AND_RETURN (a->name, b->name, NM_SETTING_CONNECTION_UUID);
CMP_AND_RETURN (a->name, b->name, NM_SETTING_CONNECTION_TYPE);
}
#undef CMP_AND_RETURN
return c_name;
}
static const NMSettInfoProperty *const*
_property_infos_sort (const NMSettInfoProperty *property_infos,
guint property_infos_len,
NMSettingClass *setting_class)
{
const NMSettInfoProperty **arr;
guint i;
#if NM_MORE_ASSERTS > 5
/* assert that the property names are all unique and sorted. */
for (i = 0; i < property_infos_len; i++) {
if (property_infos[i].param_spec)
nm_assert (nm_streq (property_infos[i].name, property_infos[i].param_spec->name));
if (i > 0)
nm_assert (strcmp (property_infos[i - 1].name, property_infos[i].name) < 0);
}
#endif
if (property_infos_len <= 1)
return NULL;
if (G_TYPE_FROM_CLASS (setting_class) != NM_TYPE_SETTING_CONNECTION) {
/* we only do something special for certain setting types. This one,
* has just alphabetical sorting. */
return NULL;
}
arr = g_new (const NMSettInfoProperty *, property_infos_len);
for (i = 0; i < property_infos_len; i++)
arr[i] = &property_infos[i];
g_qsort_with_data (arr,
property_infos_len,
sizeof (const NMSettInfoProperty *),
_property_infos_sort_cmp_setting_connection,
NULL);
return arr;
}
void
_nm_setting_class_commit_full (NMSettingClass *setting_class,
NMMetaSettingType meta_type,
const NMSettInfoSettDetail *detail,
GArray *properties_override)
{
NMSettInfoSetting *sett_info;
gs_free GParamSpec **property_specs = NULL;
guint i, n_property_specs, override_len;
nm_assert (NM_IS_SETTING_CLASS (setting_class));
nm_assert (!setting_class->setting_info);
nm_assert (meta_type < G_N_ELEMENTS (_sett_info_settings));
sett_info = &_sett_info_settings[meta_type];
nm_assert (!sett_info->setting_class);
nm_assert (!sett_info->property_infos_len);
nm_assert (!sett_info->property_infos);
if (!properties_override) {
override_len = 0;
properties_override = _nm_sett_info_property_override_create_array ();
} else
override_len = properties_override->len;
property_specs = g_object_class_list_properties (G_OBJECT_CLASS (setting_class),
&n_property_specs);
for (i = 0; i < properties_override->len; i++) {
NMSettInfoProperty *p = &g_array_index (properties_override, NMSettInfoProperty, i);
nm_assert ((!!p->name) != (!!p->param_spec));
if (!p->name) {
nm_assert (p->param_spec);
p->name = p->param_spec->name;
} else
nm_assert (!p->param_spec);
}
#if NM_MORE_ASSERTS > 10
/* assert that properties_override is constructed consistently. */
for (i = 0; i < override_len; i++) {
const NMSettInfoProperty *p = &g_array_index (properties_override, NMSettInfoProperty, i);
gboolean found = FALSE;
guint j;
nm_assert (!_nm_sett_info_property_find_in_array ((NMSettInfoProperty *) properties_override->data,
i,
p->name));
for (j = 0; j < n_property_specs; j++) {
if (!nm_streq (property_specs[j]->name, p->name))
continue;
nm_assert (!found);
found = TRUE;
nm_assert (p->param_spec == property_specs[j]);
}
nm_assert (found == (p->param_spec != NULL));
}
#endif
for (i = 0; i < n_property_specs; i++) {
const char *name = property_specs[i]->name;
NMSettInfoProperty *p;
if (_nm_sett_info_property_find_in_array ((NMSettInfoProperty *) properties_override->data,
override_len,
name))
continue;
g_array_set_size (properties_override, properties_override->len + 1);
p = &g_array_index (properties_override, NMSettInfoProperty, properties_override->len - 1);
memset (p, 0, sizeof (*p));
p->name = name;
p->param_spec = property_specs[i];
}
for (i = 0; i < properties_override->len; i++) {
NMSettInfoProperty *p = &g_array_index (properties_override, NMSettInfoProperty, i);
GType vtype;
if (p->property_type)
goto has_property_type;
nm_assert (p->param_spec);
vtype = p->param_spec->value_type;
if (vtype == G_TYPE_BOOLEAN)
p->property_type = NM_SETT_INFO_PROPERT_TYPE (.dbus_type = G_VARIANT_TYPE_BOOLEAN);
else if (vtype == G_TYPE_UCHAR)
p->property_type = NM_SETT_INFO_PROPERT_TYPE (.dbus_type = G_VARIANT_TYPE_BYTE);
else if (vtype == G_TYPE_INT)
p->property_type = &nm_sett_info_propert_type_plain_i;
else if (vtype == G_TYPE_UINT)
p->property_type = &nm_sett_info_propert_type_plain_u;
else if (vtype == G_TYPE_INT64)
p->property_type = NM_SETT_INFO_PROPERT_TYPE (.dbus_type = G_VARIANT_TYPE_INT64);
else if (vtype == G_TYPE_UINT64)
p->property_type = NM_SETT_INFO_PROPERT_TYPE (.dbus_type = G_VARIANT_TYPE_UINT64);
else if (vtype == G_TYPE_STRING)
p->property_type = NM_SETT_INFO_PROPERT_TYPE (.dbus_type = G_VARIANT_TYPE_STRING);
else if (vtype == G_TYPE_DOUBLE)
p->property_type = NM_SETT_INFO_PROPERT_TYPE (.dbus_type = G_VARIANT_TYPE_DOUBLE);
else if (vtype == G_TYPE_STRV)
p->property_type = NM_SETT_INFO_PROPERT_TYPE (.dbus_type = G_VARIANT_TYPE_STRING_ARRAY);
else if (vtype == G_TYPE_BYTES) {
p->property_type = NM_SETT_INFO_PROPERT_TYPE (.dbus_type = G_VARIANT_TYPE_BYTESTRING,
.gprop_to_dbus_fcn = _gprop_to_dbus_fcn_bytes);
} else if (g_type_is_a (vtype, G_TYPE_ENUM)) {
p->property_type = NM_SETT_INFO_PROPERT_TYPE (.dbus_type = G_VARIANT_TYPE_INT32,
.gprop_to_dbus_fcn = _gprop_to_dbus_fcn_enum);
} else if (g_type_is_a (vtype, G_TYPE_FLAGS)) {
p->property_type = NM_SETT_INFO_PROPERT_TYPE (.dbus_type = G_VARIANT_TYPE_UINT32,
.gprop_to_dbus_fcn = _gprop_to_dbus_fcn_flags);
} else
nm_assert_not_reached ();
has_property_type:
nm_assert (p->property_type);
nm_assert (p->property_type->dbus_type);
nm_assert (g_variant_type_string_is_valid ((const char *) p->property_type->dbus_type));
}
G_STATIC_ASSERT_EXPR (G_STRUCT_OFFSET (NMSettInfoProperty, name) == 0);
g_array_sort (properties_override, nm_strcmp_p);
setting_class->setting_info = &nm_meta_setting_infos[meta_type];
sett_info->setting_class = setting_class;
if (detail)
sett_info->detail = *detail;
nm_assert (properties_override->len > 0);
sett_info->property_infos_len = properties_override->len;
sett_info->property_infos = nm_memdup (properties_override->data, sizeof (NMSettInfoProperty) * properties_override->len);
sett_info->property_infos_sorted = _property_infos_sort (sett_info->property_infos,
sett_info->property_infos_len,
setting_class);
g_array_free (properties_override, TRUE);
}
const NMSettInfoProperty *
_nm_sett_info_setting_get_property_info (const NMSettInfoSetting *sett_info,
const char *property_name)
{
const NMSettInfoProperty *property;
gssize idx;
nm_assert (property_name);
if (!sett_info)
return NULL;
G_STATIC_ASSERT_EXPR (G_STRUCT_OFFSET (NMSettInfoProperty, name) == 0);
idx = nm_utils_array_find_binary_search (sett_info->property_infos,
sizeof (NMSettInfoProperty),
sett_info->property_infos_len,
&property_name,
nm_strcmp_p_with_data,
NULL);
if (idx < 0)
return NULL;
property = &sett_info->property_infos[idx];
nm_assert (idx == 0 || strcmp (property[-1].name, property[0].name) < 0);
nm_assert (idx == sett_info->property_infos_len - 1 || strcmp (property[0].name, property[1].name) < 0);
return property;
}
const NMSettInfoSetting *
_nm_setting_class_get_sett_info (NMSettingClass *setting_class)
{
const NMSettInfoSetting *sett_info;
if ( !NM_IS_SETTING_CLASS (setting_class)
|| !setting_class->setting_info)
return NULL;
nm_assert (setting_class->setting_info->meta_type < G_N_ELEMENTS (_sett_info_settings));
sett_info = &_sett_info_settings[setting_class->setting_info->meta_type];
nm_assert (sett_info->setting_class == setting_class);
return sett_info;
}
/*****************************************************************************/
void
_nm_setting_emit_property_changed (NMSetting *setting)
{
/* Some settings have "properties" that are not implemented as GObject properties.
*
* For example:
*
* - gendata-base settings like NMSettingEthtool. Here properties are just
* GVariant values in the gendata hash.
*
* - NMSettingWireGuard's peers are not backed by a GObject property. Instead
* there is C-API to access/modify peers.
*
* We still want to emit property-changed notifications for such properties,
* in particular because NMConnection registers to such signals to re-emit
* it as NM_CONNECTION_CHANGED signal. In fact, there are unlikely any other
* uses of such a property-changed signal, because generally it doesn't make
* too much sense.
*
* So, instead of adding yet another (artificial) signal "setting-changed",
* hijack the "notify" signal and just notify about changes of the "name".
* Of course, the "name" doesn't really ever change, because it's tied to
* the GObject's type.
*/
_notify (setting, PROP_NAME);
}
/*****************************************************************************/
gboolean
_nm_setting_use_legacy_property (NMSetting *setting,
GVariant *connection_dict,
const char *legacy_property,
const char *new_property)
{
GVariant *setting_dict, *value;
setting_dict = g_variant_lookup_value (connection_dict, nm_setting_get_name (NM_SETTING (setting)), NM_VARIANT_TYPE_SETTING);
g_return_val_if_fail (setting_dict != NULL, FALSE);
/* If the new property isn't set, we have to use the legacy property. */
value = g_variant_lookup_value (setting_dict, new_property, NULL);
if (!value) {
g_variant_unref (setting_dict);
return TRUE;
}
g_variant_unref (value);
/* Otherwise, clients always prefer new properties sent from the daemon. */
if (!_nm_utils_is_manager_process) {
g_variant_unref (setting_dict);
return FALSE;
}
/* The daemon prefers the legacy property if it exists. */
value = g_variant_lookup_value (setting_dict, legacy_property, NULL);
g_variant_unref (setting_dict);
if (value) {
g_variant_unref (value);
return TRUE;
} else
return FALSE;
}
/*****************************************************************************/
static GVariant *
property_to_dbus (const NMSettInfoSetting *sett_info,
guint property_idx,
NMConnection *connection,
NMSetting *setting,
NMConnectionSerializationFlags flags,
const NMConnectionSerializationOptions *options,
gboolean ignore_flags,
gboolean ignore_default)
{
const NMSettInfoProperty *property = &sett_info->property_infos[property_idx];
GVariant *variant;
nm_assert (property->property_type->dbus_type);
if (!property->param_spec) {
if (!property->property_type->to_dbus_fcn)
return NULL;
} else if ( !ignore_flags
&& !NM_FLAGS_HAS (property->param_spec->flags, NM_SETTING_PARAM_TO_DBUS_IGNORE_FLAGS)) {
if (!NM_FLAGS_HAS (property->param_spec->flags, G_PARAM_WRITABLE))
return NULL;
if ( NM_FLAGS_HAS (property->param_spec->flags, NM_SETTING_PARAM_LEGACY)
&& !_nm_utils_is_manager_process)
return NULL;
if (NM_FLAGS_HAS (property->param_spec->flags, NM_SETTING_PARAM_SECRET)) {
if (NM_FLAGS_HAS (flags, NM_CONNECTION_SERIALIZE_NO_SECRETS))
return NULL;
if (NM_FLAGS_HAS (flags, NM_CONNECTION_SERIALIZE_WITH_SECRETS_AGENT_OWNED)) {
NMSettingSecretFlags f;
/* see also _nm_connection_serialize_secrets() */
if (!nm_setting_get_secret_flags (setting, property->param_spec->name, &f, NULL))
return NULL;
if (!NM_FLAGS_HAS (f, NM_SETTING_SECRET_FLAG_AGENT_OWNED))
return NULL;
}
} else {
if (NM_FLAGS_HAS (flags, NM_CONNECTION_SERIALIZE_ONLY_SECRETS))
return NULL;
}
}
if (property->property_type->to_dbus_fcn) {
variant = property->property_type->to_dbus_fcn (sett_info, property_idx, connection, setting, flags, options);
nm_g_variant_take_ref (variant);
} else {
nm_auto_unset_gvalue GValue prop_value = { 0, };
nm_assert (property->param_spec);
g_value_init (&prop_value, property->param_spec->value_type);
g_object_get_property (G_OBJECT (setting), property->param_spec->name, &prop_value);
if ( ignore_default
&& g_param_value_defaults (property->param_spec, &prop_value))
return NULL;
if (property->property_type->gprop_to_dbus_fcn) {
variant = property->property_type->gprop_to_dbus_fcn (&prop_value);
nm_g_variant_take_ref (variant);
} else
variant = g_dbus_gvalue_to_gvariant (&prop_value, property->property_type->dbus_type);
}
nm_assert (!variant || !g_variant_is_floating (variant));
nm_assert (!variant || g_variant_is_of_type (variant, property->property_type->dbus_type));
return variant;
}
static gboolean
set_property_from_dbus (const NMSettInfoProperty *property,
GVariant *src_value,
GValue *dst_value)
{
nm_assert (property->param_spec);
nm_assert (property->property_type->dbus_type);
if (property->property_type->gprop_from_dbus_fcn) {
if (!g_variant_type_equal (g_variant_get_type (src_value), property->property_type->dbus_type))
return FALSE;
property->property_type->gprop_from_dbus_fcn (src_value, dst_value);
} else if (dst_value->g_type == G_TYPE_BYTES) {
if (!g_variant_is_of_type (src_value, G_VARIANT_TYPE_BYTESTRING))
return FALSE;
_nm_utils_bytes_from_dbus (src_value, dst_value);
} else {
GValue tmp = G_VALUE_INIT;
g_dbus_gvariant_to_gvalue (src_value, &tmp);
if (G_VALUE_TYPE (&tmp) == G_VALUE_TYPE (dst_value))
*dst_value = tmp;
else {
gboolean success;
success = g_value_transform (&tmp, dst_value);
g_value_unset (&tmp);
if (!success)
return FALSE;
}
}
return TRUE;
}
/**
* _nm_setting_to_dbus:
* @setting: the #NMSetting
* @connection: the #NMConnection containing @setting
* @flags: hash flags, e.g. %NM_CONNECTION_SERIALIZE_ALL
* @options: the #NMConnectionSerializationOptions options to control
* what/how gets serialized.
*
* Converts the #NMSetting into a #GVariant of type #NM_VARIANT_TYPE_SETTING
* mapping each setting property name to a value describing that property,
* suitable for marshalling over D-Bus or serializing.
*
* Returns: (transfer none): a new floating #GVariant describing the setting's
* properties
**/
GVariant *
_nm_setting_to_dbus (NMSetting *setting,
NMConnection *connection,
NMConnectionSerializationFlags flags,
const NMConnectionSerializationOptions *options)
{
NMSettingPrivate *priv;
GVariantBuilder builder;
const NMSettInfoSetting *sett_info;
guint n_properties, i;
const char *const*gendata_keys;
g_return_val_if_fail (NM_IS_SETTING (setting), NULL);
priv = NM_SETTING_GET_PRIVATE (setting);
g_variant_builder_init (&builder, NM_VARIANT_TYPE_SETTING);
n_properties = _nm_setting_gendata_get_all (setting, &gendata_keys, NULL);
for (i = 0; i < n_properties; i++) {
g_variant_builder_add (&builder,
"{sv}",
gendata_keys[i],
g_hash_table_lookup (priv->gendata->hash, gendata_keys[i]));
}
sett_info = _nm_setting_class_get_sett_info (NM_SETTING_GET_CLASS (setting));
for (i = 0; i < sett_info->property_infos_len; i++) {
gs_unref_variant GVariant *dbus_value = NULL;
dbus_value = property_to_dbus (sett_info, i, connection, setting, flags, options, FALSE, TRUE);
if (dbus_value) {
g_variant_builder_add (&builder,
"{sv}",
sett_info->property_infos[i].name,
dbus_value);
}
}
return g_variant_builder_end (&builder);
}
/**
* _nm_setting_new_from_dbus:
* @setting_type: the #NMSetting type which the hash contains properties for
* @setting_dict: the #GVariant containing an %NM_VARIANT_TYPE_SETTING dictionary
* mapping property names to values
* @connection_dict: the #GVariant containing an %NM_VARIANT_TYPE_CONNECTION
* dictionary mapping setting names to dictionaries.
* @parse_flags: flags to determine behavior during parsing.
* @error: location to store error, or %NULL
*
* Creates a new #NMSetting object and populates that object with the properties
* contained in @setting_dict, using each key as the property to set, and each
* value as the value to set that property to. Setting properties are strongly
* typed, thus the #GVariantType of the dict value must be correct. See the
* documentation on each #NMSetting object subclass for the correct property
* names and value types.
*
* Returns: a new #NMSetting object populated with the properties from the
* hash table, or %NULL if @setting_hash could not be deserialized.
**/
NMSetting *
_nm_setting_new_from_dbus (GType setting_type,
GVariant *setting_dict,
GVariant *connection_dict,
NMSettingParseFlags parse_flags,
GError **error)
{
gs_unref_ptrarray GPtrArray *keys_keep_variant = NULL;
gs_unref_object NMSetting *setting = NULL;
gs_unref_hashtable GHashTable *keys = NULL;
g_return_val_if_fail (G_TYPE_IS_INSTANTIATABLE (setting_type), NULL);
g_return_val_if_fail (g_variant_is_of_type (setting_dict, NM_VARIANT_TYPE_SETTING), NULL);
nm_assert (!NM_FLAGS_ANY (parse_flags, ~NM_SETTING_PARSE_FLAGS_ALL));
nm_assert (!NM_FLAGS_ALL (parse_flags, NM_SETTING_PARSE_FLAGS_STRICT | NM_SETTING_PARSE_FLAGS_BEST_EFFORT));
/* connection_dict is not technically optional, but some tests in test-general
* don't bother with it in cases where they know it's not needed.
*/
if (connection_dict)
g_return_val_if_fail (g_variant_is_of_type (connection_dict, NM_VARIANT_TYPE_CONNECTION), NULL);
/* Build the setting object from the properties we know about; we assume
* that any propreties in @setting_dict that we don't know about can
* either be ignored or else has a backward-compatibility equivalent
* that we do know about.
*/
setting = (NMSetting *) g_object_new (setting_type, NULL);
if (NM_FLAGS_HAS (parse_flags, NM_SETTING_PARSE_FLAGS_STRICT)) {
GVariantIter iter;
GVariant *entry, *entry_key;
const char *key;
keys_keep_variant = g_ptr_array_new_with_free_func ((GDestroyNotify) g_variant_unref);
keys = g_hash_table_new (nm_str_hash, g_str_equal);
g_variant_iter_init (&iter, setting_dict);
while ((entry = g_variant_iter_next_value (&iter))) {
entry_key = g_variant_get_child_value (entry, 0);
g_ptr_array_add (keys_keep_variant, entry_key);
g_variant_unref (entry);
key = g_variant_get_string (entry_key, NULL);
if (!g_hash_table_add (keys, (char *) key)) {
g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_SETTING,
_("duplicate property"));
g_prefix_error (error, "%s.%s: ", nm_setting_get_name (setting), key);
return NULL;
}
}
}
if (!NM_SETTING_GET_CLASS (setting)->init_from_dbus (setting,
keys,
setting_dict,
connection_dict,
parse_flags,
error))
return NULL;
if ( NM_FLAGS_HAS (parse_flags, NM_SETTING_PARSE_FLAGS_STRICT)
&& g_hash_table_size (keys) > 0) {
GHashTableIter iter;
const char *key;
g_hash_table_iter_init (&iter, keys);
if (g_hash_table_iter_next (&iter, (gpointer *) &key, NULL)) {
g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
_("unknown property"));
g_prefix_error (error, "%s.%s: ", nm_setting_get_name (setting), key);
return NULL;
}
}
return g_steal_pointer (&setting);
}
static gboolean
init_from_dbus (NMSetting *setting,
GHashTable *keys,
GVariant *setting_dict,
GVariant *connection_dict,
guint /* NMSettingParseFlags */ parse_flags,
GError **error)
{
const NMSettInfoSetting *sett_info;
guint i;
nm_assert (NM_IS_SETTING (setting));
nm_assert (!NM_FLAGS_ANY (parse_flags, ~NM_SETTING_PARSE_FLAGS_ALL));
nm_assert (!NM_FLAGS_ALL (parse_flags, NM_SETTING_PARSE_FLAGS_STRICT | NM_SETTING_PARSE_FLAGS_BEST_EFFORT));
sett_info = _nm_setting_class_get_sett_info (NM_SETTING_GET_CLASS (setting));
if (sett_info->detail.gendata_info) {
GHashTable *hash;
GVariantIter iter;
char *key;
GVariant *val;
hash = _gendata_hash (setting, TRUE)->hash;
g_variant_iter_init (&iter, setting_dict);
while (g_variant_iter_next (&iter, "{sv}", &key, &val)) {
g_hash_table_insert (hash,
key,
val);
if (keys)
g_hash_table_remove (keys, key);
}
_nm_setting_gendata_notify (setting, TRUE);
return TRUE;
}
for (i = 0; i < sett_info->property_infos_len; i++) {
const NMSettInfoProperty *property_info = &sett_info->property_infos[i];
gs_unref_variant GVariant *value = NULL;
gs_free_error GError *local = NULL;
if ( property_info->param_spec
&& !(property_info->param_spec->flags & G_PARAM_WRITABLE))
continue;
value = g_variant_lookup_value (setting_dict, property_info->name, NULL);
if ( value
&& keys)
g_hash_table_remove (keys, property_info->name);
if ( value
&& property_info->property_type->from_dbus_fcn) {
if (!g_variant_type_equal (g_variant_get_type (value), property_info->property_type->dbus_type)) {
/* for backward behavior, fail unless best-effort is chosen. */
if (NM_FLAGS_HAS (parse_flags, NM_SETTING_PARSE_FLAGS_BEST_EFFORT))
continue;
g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
_("can't set property of type '%s' from value of type '%s'"),
property_info->property_type->dbus_type ?
g_variant_type_peek_string (property_info->property_type->dbus_type) :
property_info->param_spec ?
g_type_name (property_info->param_spec->value_type) : "(unknown)",
g_variant_get_type_string (value));
g_prefix_error (error, "%s.%s: ", nm_setting_get_name (setting), property_info->name);
return FALSE;
}
if (!property_info->property_type->from_dbus_fcn (setting,
connection_dict,
property_info->name,
value,
parse_flags,
&local)) {
if (!NM_FLAGS_HAS (parse_flags, NM_SETTING_PARSE_FLAGS_STRICT))
continue;
g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
_("failed to set property: %s"),
local->message);
g_prefix_error (error, "%s.%s: ", nm_setting_get_name (setting), property_info->name);
return FALSE;
}
} else if ( !value
&& property_info->property_type->missing_from_dbus_fcn) {
if (!property_info->property_type->missing_from_dbus_fcn (setting,
connection_dict,
property_info->name,
parse_flags,
&local)) {
if (!NM_FLAGS_HAS (parse_flags, NM_SETTING_PARSE_FLAGS_STRICT))
continue;
g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
_("failed to set property: %s"),
local->message);
g_prefix_error (error, "%s.%s: ", nm_setting_get_name (setting), property_info->name);
return FALSE;
}
} else if ( value
&& property_info->param_spec) {
nm_auto_unset_gvalue GValue object_value = G_VALUE_INIT;
g_value_init (&object_value, property_info->param_spec->value_type);
if (!set_property_from_dbus (property_info, value, &object_value)) {
/* for backward behavior, fail unless best-effort is chosen. */
if (NM_FLAGS_HAS (parse_flags, NM_SETTING_PARSE_FLAGS_BEST_EFFORT))
continue;
g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
_("can't set property of type '%s' from value of type '%s'"),
property_info->property_type->dbus_type
? g_variant_type_peek_string (property_info->property_type->dbus_type)
: ( property_info->param_spec
? g_type_name (property_info->param_spec->value_type)
: "(unknown)"),
g_variant_get_type_string (value));
g_prefix_error (error, "%s.%s: ", nm_setting_get_name (setting), property_info->name);
return FALSE;
}
if (!nm_g_object_set_property (G_OBJECT (setting), property_info->param_spec->name, &object_value, &local)) {
if (!NM_FLAGS_HAS (parse_flags, NM_SETTING_PARSE_FLAGS_STRICT))
continue;
g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
_("can not set property: %s"),
local->message);
g_prefix_error (error, "%s.%s: ", nm_setting_get_name (setting), property_info->name);
return FALSE;
}
}
}
return TRUE;
}
/**
* nm_setting_get_dbus_property_type:
* @setting: an #NMSetting
* @property_name: the property of @setting to get the type of
*
* Gets the D-Bus marshalling type of a property. @property_name is a D-Bus
* property name, which may not necessarily be a #GObject property.
*
* Returns: the D-Bus marshalling type of @property on @setting.
*/
const GVariantType *
nm_setting_get_dbus_property_type (NMSetting *setting,
const char *property_name)
{
const NMSettInfoProperty *property;
g_return_val_if_fail (NM_IS_SETTING (setting), NULL);
g_return_val_if_fail (property_name != NULL, NULL);
property = _nm_setting_class_get_property_info (NM_SETTING_GET_CLASS (setting), property_name);
g_return_val_if_fail (property != NULL, NULL);
nm_assert (property->property_type);
nm_assert (g_variant_type_string_is_valid ((const char *) property->property_type->dbus_type));
return property->property_type->dbus_type;
}
gboolean
_nm_setting_get_property (NMSetting *setting, const char *property_name, GValue *value)
{
const NMSettInfoSetting *sett_info;
const NMSettInfoProperty *property_info;
g_return_val_if_fail (NM_IS_SETTING (setting), FALSE);
g_return_val_if_fail (property_name, FALSE);
g_return_val_if_fail (value, FALSE);
sett_info = _nm_setting_class_get_sett_info (NM_SETTING_GET_CLASS (setting));
if (sett_info->detail.gendata_info) {
GVariant *variant;
GenData *gendata = _gendata_hash (setting, FALSE);
variant = gendata ? g_hash_table_lookup (gendata->hash, property_name) : NULL;
if (!variant) {
g_value_unset (value);
return FALSE;
}
g_value_init (value, G_TYPE_VARIANT);
g_value_set_variant (value, variant);
return TRUE;
}
property_info = _nm_sett_info_setting_get_property_info (sett_info, property_name);
if ( !property_info
|| !property_info->param_spec) {
g_value_unset (value);
return FALSE;
}
g_value_init (value, property_info->param_spec->value_type);
g_object_get_property (G_OBJECT (setting), property_name, value);
return TRUE;
}
static void
_gobject_copy_property (GObject *src,
GObject *dst,
const char *property_name,
GType gtype)
{
nm_auto_unset_gvalue GValue value = G_VALUE_INIT;
nm_assert (G_IS_OBJECT (src));
nm_assert (G_IS_OBJECT (dst));
g_value_init (&value, gtype);
g_object_get_property (src, property_name, &value);
g_object_set_property (dst, property_name, &value);
}
static void
duplicate_copy_properties (const NMSettInfoSetting *sett_info,
NMSetting *src,
NMSetting *dst)
{
if (sett_info->detail.gendata_info) {
GenData *gendata = _gendata_hash (src, FALSE);
nm_assert (!_gendata_hash (dst, FALSE));
if ( gendata
&& g_hash_table_size (gendata->hash) > 0) {
GHashTableIter iter;
GHashTable *h = _gendata_hash (dst, TRUE)->hash;
const char *key;
GVariant *val;
g_hash_table_iter_init (&iter, gendata->hash);
while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &val)) {
g_hash_table_insert (h,
g_strdup (key),
g_variant_ref (val));
}
}
}
if (sett_info->property_infos_len > 0) {
gboolean frozen = FALSE;
guint i;
for (i = 0; i < sett_info->property_infos_len; i++) {
const NMSettInfoProperty *property_info = &sett_info->property_infos[i];
if (property_info->param_spec) {
if ((property_info->param_spec->flags & (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)) != G_PARAM_WRITABLE)
continue;
if (!frozen) {
g_object_freeze_notify (G_OBJECT (dst));
frozen = TRUE;
}
_gobject_copy_property (G_OBJECT (src),
G_OBJECT (dst),
property_info->param_spec->name,
G_PARAM_SPEC_VALUE_TYPE (property_info->param_spec));
continue;
}
}
if (frozen)
g_object_thaw_notify (G_OBJECT (dst));
}
}
/**
* nm_setting_duplicate:
* @setting: the #NMSetting to duplicate
*
* Duplicates a #NMSetting.
*
* Returns: (transfer full): a new #NMSetting containing the same properties and values as the
* source #NMSetting
**/
NMSetting *
nm_setting_duplicate (NMSetting *setting)
{
const NMSettInfoSetting *sett_info;
NMSettingClass *klass;
NMSetting *dst;
g_return_val_if_fail (NM_IS_SETTING (setting), NULL);
klass = NM_SETTING_GET_CLASS (setting);
nm_assert (NM_IS_SETTING_CLASS (klass));
nm_assert (klass->duplicate_copy_properties);
dst = g_object_new (G_TYPE_FROM_CLASS (klass), NULL);
sett_info = _nm_setting_class_get_sett_info (klass);
nm_assert (sett_info);
klass->duplicate_copy_properties (sett_info, setting, dst);
return dst;
}
/**
* nm_setting_get_name:
* @setting: the #NMSetting
*
* Returns the type name of the #NMSetting object
*
* Returns: a string containing the type name of the #NMSetting object,
* like 'ppp' or 'wireless' or 'wired'.
**/
const char *
nm_setting_get_name (NMSetting *setting)
{
const NMMetaSettingInfo *setting_info;
g_return_val_if_fail (NM_IS_SETTING (setting), NULL);
setting_info = NM_SETTING_GET_CLASS (setting)->setting_info;
return setting_info ? setting_info->setting_name : NULL;
}
/**
* nm_setting_verify:
* @setting: the #NMSetting to verify
* @connection: (allow-none): the #NMConnection that @setting came from, or
* %NULL if @setting is being verified in isolation.
* @error: location to store error, or %NULL
*
* Validates the setting. Each setting's properties have allowed values, and
* some are dependent on other values (hence the need for @connection). The
* returned #GError contains information about which property of the setting
* failed validation, and in what way that property failed validation.
*
* Returns: %TRUE if the setting is valid, %FALSE if it is not
**/
gboolean
nm_setting_verify (NMSetting *setting, NMConnection *connection, GError **error)
{
NMSettingVerifyResult result = _nm_setting_verify (setting, connection, error);
if (result == NM_SETTING_VERIFY_NORMALIZABLE)
g_clear_error (error);
return result == NM_SETTING_VERIFY_SUCCESS || result == NM_SETTING_VERIFY_NORMALIZABLE;
}
NMSettingVerifyResult
_nm_setting_verify (NMSetting *setting, NMConnection *connection, GError **error)
{
g_return_val_if_fail (NM_IS_SETTING (setting), NM_SETTING_VERIFY_ERROR);
g_return_val_if_fail (!connection || NM_IS_CONNECTION (connection), NM_SETTING_VERIFY_ERROR);
g_return_val_if_fail (!error || *error == NULL, NM_SETTING_VERIFY_ERROR);
if (NM_SETTING_GET_CLASS (setting)->verify)
return NM_SETTING_GET_CLASS (setting)->verify (setting, connection, error);
return NM_SETTING_VERIFY_SUCCESS;
}
/**
* nm_setting_verify_secrets:
* @setting: the #NMSetting to verify secrets in
* @connection: (allow-none): the #NMConnection that @setting came from, or
* %NULL if @setting is being verified in isolation.
* @error: location to store error, or %NULL
*
* Verifies the secrets in the setting.
* The returned #GError contains information about which secret of the setting
* failed validation, and in what way that secret failed validation.
* The secret validation is done separately from main setting validation, because
* in some cases connection failure is not desired just for the secrets.
*
* Returns: %TRUE if the setting secrets are valid, %FALSE if they are not
*
* Since: 1.2
**/
gboolean
nm_setting_verify_secrets (NMSetting *setting, NMConnection *connection, GError **error)
{
g_return_val_if_fail (NM_IS_SETTING (setting), NM_SETTING_VERIFY_ERROR);
g_return_val_if_fail (!connection || NM_IS_CONNECTION (connection), NM_SETTING_VERIFY_ERROR);
g_return_val_if_fail (!error || *error == NULL, NM_SETTING_VERIFY_ERROR);
if (NM_SETTING_GET_CLASS (setting)->verify_secrets)
return NM_SETTING_GET_CLASS (setting)->verify_secrets (setting, connection, error);
return NM_SETTING_VERIFY_SUCCESS;
}
gboolean
_nm_setting_verify_secret_string (const char *str,
const char *setting_name,
const char *property,
GError **error)
{
if (str && !*str) {
g_set_error_literal (error,
NM_CONNECTION_ERROR,
NM_CONNECTION_ERROR_INVALID_PROPERTY,
_("property is empty"));
g_prefix_error (error, "%s.%s: ", setting_name, property);
return FALSE;
}
return TRUE;
}
gboolean
_nm_setting_should_compare_secret_property (NMSetting *setting,
NMSetting *other,
const char *secret_name,
NMSettingCompareFlags flags)
{
NMSettingSecretFlags a_secret_flags = NM_SETTING_SECRET_FLAG_NONE;
NMSettingSecretFlags b_secret_flags = NM_SETTING_SECRET_FLAG_NONE;
nm_assert (NM_IS_SETTING (setting));
nm_assert (!other || G_OBJECT_TYPE (setting) == G_OBJECT_TYPE (other));
/* secret_name must be a valid secret for @setting. */
nm_assert (nm_setting_get_secret_flags (setting, secret_name, NULL, NULL));
if (!NM_FLAGS_ANY (flags, NM_SETTING_COMPARE_FLAG_IGNORE_AGENT_OWNED_SECRETS
| NM_SETTING_COMPARE_FLAG_IGNORE_NOT_SAVED_SECRETS))
return TRUE;
nm_setting_get_secret_flags (setting, secret_name, &a_secret_flags, NULL);
if (other) {
if (!nm_setting_get_secret_flags (other, secret_name, &b_secret_flags, NULL)) {
/* secret-name may not be a valid secret for @other. That is fine, we ignore that
* and treat @b_secret_flags as NM_SETTING_SECRET_FLAG_NONE.
*
* This can happen with VPN secrets, where the caller knows that @secret_name
* is a secret for setting, but it may not be a secret for @other. Accept that.
*
* Mark @other as missing. */
other = NULL;
}
}
/* when @setting has the secret-flags that should be ignored,
* we skip the comparison if:
*
* - @other is not present,
* - @other does not have a secret named @secret_name
* - @other also has the secret flat to be ignored.
*
* This makes the check symmetric (aside the fact that @setting must
* have the secret while @other may not -- which is asymmetric). */
if ( NM_FLAGS_HAS (flags, NM_SETTING_COMPARE_FLAG_IGNORE_AGENT_OWNED_SECRETS)
&& NM_FLAGS_HAS (a_secret_flags, NM_SETTING_SECRET_FLAG_AGENT_OWNED)
&& ( !other
|| NM_FLAGS_HAS (b_secret_flags, NM_SETTING_SECRET_FLAG_AGENT_OWNED)))
return FALSE;
if ( NM_FLAGS_HAS (flags, NM_SETTING_COMPARE_FLAG_IGNORE_NOT_SAVED_SECRETS)
&& NM_FLAGS_HAS (a_secret_flags, NM_SETTING_SECRET_FLAG_NOT_SAVED)
&& ( !other
|| NM_FLAGS_HAS (b_secret_flags, NM_SETTING_SECRET_FLAG_NOT_SAVED)))
return FALSE;
return TRUE;
}
static NMTernary
compare_property (const NMSettInfoSetting *sett_info,
guint property_idx,
NMConnection *con_a,
NMSetting *set_a,
NMConnection *con_b,
NMSetting *set_b,
NMSettingCompareFlags flags)
{
const NMSettInfoProperty *property_info = &sett_info->property_infos[property_idx];
const GParamSpec *param_spec = property_info->param_spec;
if (!param_spec)
return NM_TERNARY_DEFAULT;
if ( NM_FLAGS_HAS (flags, NM_SETTING_COMPARE_FLAG_FUZZY)
&& NM_FLAGS_ANY (param_spec->flags, NM_SETTING_PARAM_FUZZY_IGNORE | NM_SETTING_PARAM_SECRET))
return NM_TERNARY_DEFAULT;
if ( NM_FLAGS_HAS (flags, NM_SETTING_COMPARE_FLAG_INFERRABLE)
&& !NM_FLAGS_HAS (param_spec->flags, NM_SETTING_PARAM_INFERRABLE))
return NM_TERNARY_DEFAULT;
if ( NM_FLAGS_HAS (flags, NM_SETTING_COMPARE_FLAG_IGNORE_REAPPLY_IMMEDIATELY)
&& NM_FLAGS_HAS (param_spec->flags, NM_SETTING_PARAM_REAPPLY_IMMEDIATELY))
return NM_TERNARY_DEFAULT;
if ( NM_FLAGS_HAS (flags, NM_SETTING_COMPARE_FLAG_IGNORE_SECRETS)
&& NM_FLAGS_HAS (param_spec->flags, NM_SETTING_PARAM_SECRET))
return NM_TERNARY_DEFAULT;
if (nm_streq (param_spec->name, NM_SETTING_NAME))
return NM_TERNARY_DEFAULT;
if ( NM_FLAGS_HAS (param_spec->flags, NM_SETTING_PARAM_SECRET)
&& !_nm_setting_should_compare_secret_property (set_a,
set_b,
param_spec->name,
flags))
return NM_TERNARY_DEFAULT;
if (set_b) {
gs_unref_variant GVariant *value1 = NULL;
gs_unref_variant GVariant *value2 = NULL;
value1 = property_to_dbus (sett_info, property_idx, con_a, set_a, NM_CONNECTION_SERIALIZE_ALL, NULL, TRUE, TRUE);
value2 = property_to_dbus (sett_info, property_idx, con_b, set_b, NM_CONNECTION_SERIALIZE_ALL, NULL, TRUE, TRUE);
if (nm_property_compare (value1, value2) != 0)
return NM_TERNARY_FALSE;
}
return NM_TERNARY_TRUE;
}
static NMTernary
_compare_property (const NMSettInfoSetting *sett_info,
guint property_idx,
NMConnection *con_a,
NMSetting *set_a,
NMConnection *con_b,
NMSetting *set_b,
NMSettingCompareFlags flags)
{
NMTernary compare_result;
nm_assert (sett_info);
nm_assert (NM_IS_SETTING_CLASS (sett_info->setting_class));
nm_assert (property_idx < sett_info->property_infos_len);
nm_assert (NM_SETTING_GET_CLASS (set_a) == sett_info->setting_class);
nm_assert (!set_b || NM_SETTING_GET_CLASS (set_b) == sett_info->setting_class);
compare_result = NM_SETTING_GET_CLASS (set_a)->compare_property (sett_info,
property_idx,
con_a,
set_a,
con_b,
set_b,
flags);
nm_assert (NM_IN_SET (compare_result, NM_TERNARY_DEFAULT,
NM_TERNARY_FALSE,
NM_TERNARY_TRUE));
/* check that the inferable flag and the GObject property flag corresponds. */
nm_assert ( !NM_FLAGS_HAS (flags, NM_SETTING_COMPARE_FLAG_INFERRABLE)
|| !sett_info->property_infos[property_idx].param_spec
|| NM_FLAGS_HAS (sett_info->property_infos[property_idx].param_spec->flags, NM_SETTING_PARAM_INFERRABLE)
|| compare_result == NM_TERNARY_DEFAULT);
#if NM_MORE_ASSERTS > 10
/* assert that compare_property() is symeric. */
nm_assert ( !set_b
|| compare_result == NM_SETTING_GET_CLASS (set_a)->compare_property (sett_info,
property_idx,
con_b,
set_b,
con_a,
set_a,
flags));
#endif
return compare_result;
}
/**
* nm_setting_compare:
* @a: a #NMSetting
* @b: a second #NMSetting to compare with the first
* @flags: compare flags, e.g. %NM_SETTING_COMPARE_FLAG_EXACT
*
* Compares two #NMSetting objects for similarity, with comparison behavior
* modified by a set of flags. See the documentation for #NMSettingCompareFlags
* for a description of each flag's behavior.
*
* Returns: %TRUE if the comparison succeeds, %FALSE if it does not
**/
gboolean
nm_setting_compare (NMSetting *a,
NMSetting *b,
NMSettingCompareFlags flags)
{
return _nm_setting_compare (NULL, a, NULL, b, flags);
}
gboolean
_nm_setting_compare (NMConnection *con_a,
NMSetting *a,
NMConnection *con_b,
NMSetting *b,
NMSettingCompareFlags flags)
{
const NMSettInfoSetting *sett_info;
guint i;
g_return_val_if_fail (NM_IS_SETTING (a), FALSE);
g_return_val_if_fail (NM_IS_SETTING (b), FALSE);
nm_assert (!con_a || NM_IS_CONNECTION (con_a));
nm_assert (!con_b || NM_IS_CONNECTION (con_b));
/* First check that both have the same type */
if (G_OBJECT_TYPE (a) != G_OBJECT_TYPE (b))
return FALSE;
sett_info = _nm_setting_class_get_sett_info (NM_SETTING_GET_CLASS (a));
if (sett_info->detail.gendata_info) {
GenData *a_gendata = _gendata_hash (a, FALSE);
GenData *b_gendata = _gendata_hash (b, FALSE);
return nm_utils_hash_table_equal (a_gendata ? a_gendata->hash : NULL,
b_gendata ? b_gendata->hash : NULL,
TRUE,
g_variant_equal);
}
for (i = 0; i < sett_info->property_infos_len; i++) {
if (_compare_property (sett_info, i, con_a, a, con_b, b, flags) == NM_TERNARY_FALSE)
return FALSE;
}
return TRUE;
}
static void
_setting_diff_add_result (GHashTable *results, const char *prop_name, NMSettingDiffResult r)
{
void *p;
if (r == NM_SETTING_DIFF_RESULT_UNKNOWN)
return;
if (g_hash_table_lookup_extended (results, prop_name, NULL, &p)) {
if (!NM_FLAGS_ALL ((guint) r, GPOINTER_TO_UINT (p)))
g_hash_table_insert (results, g_strdup (prop_name), GUINT_TO_POINTER (((guint) r) | GPOINTER_TO_UINT (p)));
} else
g_hash_table_insert (results, g_strdup (prop_name), GUINT_TO_POINTER (r));
}
/**
* nm_setting_diff:
* @a: a #NMSetting
* @b: a second #NMSetting to compare with the first
* @flags: compare flags, e.g. %NM_SETTING_COMPARE_FLAG_EXACT
* @invert_results: this parameter is used internally by libnm and should
* be set to %FALSE. If %TRUE inverts the meaning of the #NMSettingDiffResult.
* @results: (inout) (transfer full) (element-type utf8 guint32): if the
* settings differ, on return a hash table mapping the differing keys to one or
* more %NMSettingDiffResult values OR-ed together. If the settings do not
* differ, any hash table passed in is unmodified. If no hash table is passed
* in and the settings differ, a new one is created and returned.
*
* Compares two #NMSetting objects for similarity, with comparison behavior
* modified by a set of flags. See the documentation for #NMSettingCompareFlags
* for a description of each flag's behavior. If the settings differ, the keys
* of each setting that differ from the other are added to @results, mapped to
* one or more #NMSettingDiffResult values.
*
* Returns: %TRUE if the settings contain the same values, %FALSE if they do not
**/
gboolean
nm_setting_diff (NMSetting *a,
NMSetting *b,
NMSettingCompareFlags flags,
gboolean invert_results,
GHashTable **results)
{
return _nm_setting_diff (NULL, a, NULL, b, flags, invert_results, results);
}
gboolean
_nm_setting_diff (NMConnection *con_a,
NMSetting *a,
NMConnection *con_b,
NMSetting *b,
NMSettingCompareFlags flags,
gboolean invert_results,
GHashTable **results)
{
const NMSettInfoSetting *sett_info;
guint i;
NMSettingDiffResult a_result = NM_SETTING_DIFF_RESULT_IN_A;
NMSettingDiffResult b_result = NM_SETTING_DIFF_RESULT_IN_B;
NMSettingDiffResult a_result_default = NM_SETTING_DIFF_RESULT_IN_A_DEFAULT;
NMSettingDiffResult b_result_default = NM_SETTING_DIFF_RESULT_IN_B_DEFAULT;
gboolean results_created = FALSE;
gboolean compared_any = FALSE;
gboolean diff_found = FALSE;
g_return_val_if_fail (results != NULL, FALSE);
g_return_val_if_fail (NM_IS_SETTING (a), FALSE);
if (b) {
g_return_val_if_fail (NM_IS_SETTING (b), FALSE);
g_return_val_if_fail (G_OBJECT_TYPE (a) == G_OBJECT_TYPE (b), FALSE);
}
nm_assert (!con_a || NM_IS_CONNECTION (con_a));
nm_assert (!con_b || NM_IS_CONNECTION (con_b));
if ((flags & (NM_SETTING_COMPARE_FLAG_DIFF_RESULT_WITH_DEFAULT | NM_SETTING_COMPARE_FLAG_DIFF_RESULT_NO_DEFAULT)) ==
(NM_SETTING_COMPARE_FLAG_DIFF_RESULT_WITH_DEFAULT | NM_SETTING_COMPARE_FLAG_DIFF_RESULT_NO_DEFAULT)) {
/* conflicting flags: default to WITH_DEFAULT (clearing NO_DEFAULT). */
flags &= ~NM_SETTING_COMPARE_FLAG_DIFF_RESULT_NO_DEFAULT;
}
/* If the caller is calling this function in a pattern like this to get
* complete diffs:
*
* nm_setting_diff (A, B, FALSE, &results);
* nm_setting_diff (B, A, TRUE, &results);
*
* and wants us to invert the results so that the second invocation comes
* out correctly, do that here.
*/
if (invert_results) {
a_result = NM_SETTING_DIFF_RESULT_IN_B;
b_result = NM_SETTING_DIFF_RESULT_IN_A;
a_result_default = NM_SETTING_DIFF_RESULT_IN_B_DEFAULT;
b_result_default = NM_SETTING_DIFF_RESULT_IN_A_DEFAULT;
}
if (*results == NULL) {
*results = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, NULL);
results_created = TRUE;
}
sett_info = _nm_setting_class_get_sett_info (NM_SETTING_GET_CLASS (a));
if (sett_info->detail.gendata_info) {
const char *key;
GVariant *val, *val2;
GHashTableIter iter;
GenData *a_gendata = _gendata_hash (a, FALSE);
GenData *b_gendata = b ? _gendata_hash (b, FALSE) : NULL;
if (!a_gendata || !b_gendata) {
if (a_gendata || b_gendata) {
NMSettingDiffResult one_sided_result;
one_sided_result = a_gendata ? a_result : b_result;
g_hash_table_iter_init (&iter, a_gendata ? a_gendata->hash : b_gendata->hash);
while (g_hash_table_iter_next (&iter, (gpointer *) &key, NULL)) {
diff_found = TRUE;
_setting_diff_add_result (*results, key, one_sided_result);
}
}
} else {
g_hash_table_iter_init (&iter, a_gendata->hash);
while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &val)) {
val2 = g_hash_table_lookup (b_gendata->hash, key);
compared_any = TRUE;
if ( !val2
|| !g_variant_equal (val, val2)) {
diff_found = TRUE;
_setting_diff_add_result (*results, key, a_result);
}
}
g_hash_table_iter_init (&iter, b_gendata->hash);
while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &val)) {
val2 = g_hash_table_lookup (a_gendata->hash, key);
compared_any = TRUE;
if ( !val2
|| !g_variant_equal (val, val2)) {
diff_found = TRUE;
_setting_diff_add_result (*results, key, b_result);
}
}
}
} else {
for (i = 0; i < sett_info->property_infos_len; i++) {
NMSettingDiffResult r = NM_SETTING_DIFF_RESULT_UNKNOWN;
const NMSettInfoProperty *property_info;
NMTernary compare_result;
GParamSpec *prop_spec;
compare_result = _compare_property (sett_info, i, con_a, a, con_b, b, flags);
if (compare_result == NM_TERNARY_DEFAULT)
continue;
if ( NM_FLAGS_ANY (flags, NM_SETTING_COMPARE_FLAG_IGNORE_AGENT_OWNED_SECRETS
| NM_SETTING_COMPARE_FLAG_IGNORE_NOT_SAVED_SECRETS)
&& b
&& compare_result == NM_TERNARY_FALSE) {
/* we have setting @b and the property is not the same. But we also are instructed
* to ignore secrets based on the flags.
*
* Note that compare_property() called with two settings will ignore secrets
* based on the flags, but it will do so if *both* settings have the flag we
* look for. So that is symmetric behavior and good.
*
* But for the purpose of diff(), we do a asymmetric comparison because and
* we want to skip testing the property if setting @a alone indicates to do
* so.
*
* We need to double-check whether the property should be ignored by
* looking at @a alone. */
if (_compare_property (sett_info, i, con_a, a, NULL, NULL, flags) == NM_TERNARY_DEFAULT)
continue;
}
compared_any = TRUE;
property_info = &sett_info->property_infos[i];
prop_spec = property_info->param_spec;
if (b) {
if (compare_result == NM_TERNARY_FALSE) {
if (prop_spec) {
gboolean a_is_default, b_is_default;
GValue value = G_VALUE_INIT;
g_value_init (&value, prop_spec->value_type);
g_object_get_property (G_OBJECT (a), prop_spec->name, &value);
a_is_default = g_param_value_defaults (prop_spec, &value);
g_value_reset (&value);
g_object_get_property (G_OBJECT (b), prop_spec->name, &value);
b_is_default = g_param_value_defaults (prop_spec, &value);
g_value_unset (&value);
if (!NM_FLAGS_HAS (flags, NM_SETTING_COMPARE_FLAG_DIFF_RESULT_WITH_DEFAULT)) {
if (!a_is_default)
r |= a_result;
if (!b_is_default)
r |= b_result;
} else {
r |= a_result | b_result;
if (a_is_default)
r |= a_result_default;
if (b_is_default)
r |= b_result_default;
}
} else
r |= a_result | b_result;
}
} else if ((flags & (NM_SETTING_COMPARE_FLAG_DIFF_RESULT_WITH_DEFAULT | NM_SETTING_COMPARE_FLAG_DIFF_RESULT_NO_DEFAULT)) == 0)
r = a_result; /* only in A */
else {
if (prop_spec) {
GValue value = G_VALUE_INIT;
g_value_init (&value, prop_spec->value_type);
g_object_get_property (G_OBJECT (a), prop_spec->name, &value);
if (!g_param_value_defaults (prop_spec, &value))
r |= a_result;
else if (flags & NM_SETTING_COMPARE_FLAG_DIFF_RESULT_WITH_DEFAULT)
r |= a_result | a_result_default;
g_value_unset (&value);
} else
r |= a_result;
}
if (r != NM_SETTING_DIFF_RESULT_UNKNOWN) {
diff_found = TRUE;
_setting_diff_add_result (*results, property_info->name, r);
}
}
}
if (!compared_any && !b) {
/* special case: the setting has no properties, and the opposite
* setting @b is not given. The settings differ, and we signal that
* by returning an empty results hash. */
diff_found = TRUE;
}
if (diff_found) {
/* if there is a difference, we always return FALSE. It also means, we might
* have allocated a new @results hash, and return it to the caller. */
return FALSE;
} else {
if (results_created) {
/* the allocated hash is unused. Clear it again. */
g_hash_table_destroy (*results);
*results = NULL;
} else {
/* we found no diff, and return false. However, the input
* @result is returned unmodified. */
}
return TRUE;
}
}
static void
enumerate_values (const NMSettInfoProperty *property_info,
NMSetting *setting,
NMSettingValueIterFn func,
gpointer user_data)
{
GValue value = G_VALUE_INIT;
if (!property_info->param_spec)
return;
g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (property_info->param_spec));
g_object_get_property (G_OBJECT (setting), property_info->param_spec->name, &value);
func (setting,
property_info->param_spec->name,
&value,
property_info->param_spec->flags,
user_data);
g_value_unset (&value);
}
/**
* nm_setting_enumerate_values:
* @setting: the #NMSetting
* @func: (scope call): user-supplied function called for each property of the setting
* @user_data: user data passed to @func at each invocation
*
* Iterates over each property of the #NMSetting object, calling the supplied
* user function for each property.
**/
void
nm_setting_enumerate_values (NMSetting *setting,
NMSettingValueIterFn func,
gpointer user_data)
{
const NMSettInfoSetting *sett_info;
guint i;
g_return_if_fail (NM_IS_SETTING (setting));
g_return_if_fail (func != NULL);
sett_info = _nm_setting_class_get_sett_info (NM_SETTING_GET_CLASS (setting));
if (sett_info->detail.gendata_info) {
const char *const*names;
guint n_properties;
/* the properties of this setting are not real GObject properties.
* Hence, this API makes little sense (or does it?). Still, call
* @func with each value. */
n_properties = _nm_setting_gendata_get_all (setting, &names, NULL);
if (n_properties > 0) {
gs_strfreev char **keys = g_strdupv ((char **) names);
GHashTable *h = _gendata_hash (setting, FALSE)->hash;
for (i = 0; i < n_properties; i++) {
GValue value = G_VALUE_INIT;
GVariant *val = g_hash_table_lookup (h, keys[i]);
if (!val) {
/* was deleted in the meantime? Skip */
continue;
}
g_value_init (&value, G_TYPE_VARIANT);
g_value_set_variant (&value, val);
/* call it will GParamFlags 0. It shall indicate that this
* is not a "real" GObject property. */
func (setting, keys[i], &value, 0, user_data);
g_value_unset (&value);
}
}
return;
}
for (i = 0; i < sett_info->property_infos_len; i++) {
NM_SETTING_GET_CLASS (setting)->enumerate_values (_nm_sett_info_property_info_get_sorted (sett_info, i),
setting,
func,
user_data);
}
}
static gboolean
aggregate (NMSetting *setting,
int type_i,
gpointer arg)
{
NMConnectionAggregateType type = type_i;
const NMSettInfoSetting *sett_info;
guint i;
nm_assert (NM_IN_SET (type, NM_CONNECTION_AGGREGATE_ANY_SECRETS,
NM_CONNECTION_AGGREGATE_ANY_SYSTEM_SECRET_FLAGS));
sett_info = _nm_setting_class_get_sett_info (NM_SETTING_GET_CLASS (setting));
for (i = 0; i < sett_info->property_infos_len; i++) {
const NMSettInfoProperty *property_info = &sett_info->property_infos[i];
GParamSpec *prop_spec = property_info->param_spec;
nm_auto_unset_gvalue GValue value = G_VALUE_INIT;
NMSettingSecretFlags secret_flags;
if ( !prop_spec
|| !NM_FLAGS_HAS (prop_spec->flags, NM_SETTING_PARAM_SECRET)) {
nm_assert (!nm_setting_get_secret_flags (setting, property_info->name, NULL, NULL));
continue;
}
/* for the moment, all aggregate types only care about secrets. */
nm_assert (nm_setting_get_secret_flags (setting, property_info->name, NULL, NULL));
switch (type) {
case NM_CONNECTION_AGGREGATE_ANY_SECRETS:
g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (prop_spec));
g_object_get_property (G_OBJECT (setting), prop_spec->name, &value);
if (!g_param_value_defaults (prop_spec, &value)) {
*((gboolean *) arg) = TRUE;
return TRUE;
}
break;
case NM_CONNECTION_AGGREGATE_ANY_SYSTEM_SECRET_FLAGS:
if (!nm_setting_get_secret_flags (setting, prop_spec->name, &secret_flags, NULL))
nm_assert_not_reached ();
if (secret_flags == NM_SETTING_SECRET_FLAG_NONE) {
*((gboolean *) arg) = TRUE;
return TRUE;
}
break;
}
}
return FALSE;
}
/**
* _nm_setting_aggregate:
* @setting: the #NMSetting to aggregate.
* @type: the #NMConnectionAggregateType aggregate type.
* @arg: the in/out arguments for aggregation. They depend on @type.
*
* This is the implementation detail of _nm_connection_aggregate(). It
* makes no sense to call this function directly outside of _nm_connection_aggregate().
*
* Returns: %TRUE if afterwards the aggregation is complete. That means,
* the only caller _nm_connection_aggregate() will not visit other settings
* after a setting returns %TRUE (indicating that there is nothing further
* to aggregate). Note that is very different from the boolean return
* argument of _nm_connection_aggregate(), which serves a different purpose.
*/
gboolean
_nm_setting_aggregate (NMSetting *setting,
NMConnectionAggregateType type,
gpointer arg)
{
g_return_val_if_fail (NM_IS_SETTING (setting), FALSE);
g_return_val_if_fail (arg, FALSE);
g_return_val_if_fail (NM_IN_SET (type, NM_CONNECTION_AGGREGATE_ANY_SECRETS,
NM_CONNECTION_AGGREGATE_ANY_SYSTEM_SECRET_FLAGS),
FALSE);
return NM_SETTING_GET_CLASS (setting)->aggregate (setting, type, arg);
}
static gboolean
clear_secrets (const NMSettInfoSetting *sett_info,
guint property_idx,
NMSetting *setting,
NMSettingClearSecretsWithFlagsFn func,
gpointer user_data)
{
NMSettingSecretFlags flags = NM_SETTING_SECRET_FLAG_NONE;
GParamSpec *param_spec = sett_info->property_infos[property_idx].param_spec;
if (!param_spec)
return FALSE;
if (!NM_FLAGS_HAS (param_spec->flags, NM_SETTING_PARAM_SECRET))
return FALSE;
if (func) {
if (!nm_setting_get_secret_flags (setting, param_spec->name, &flags, NULL))
nm_assert_not_reached ();
if (!func (setting, param_spec->name, flags, user_data))
return FALSE;
} else
nm_assert (nm_setting_get_secret_flags (setting, param_spec->name, NULL, NULL));
{
nm_auto_unset_gvalue GValue value = G_VALUE_INIT;
g_value_init (&value, param_spec->value_type);
g_object_get_property (G_OBJECT (setting), param_spec->name, &value);
if (g_param_value_defaults (param_spec, &value))
return FALSE;
g_param_value_set_default (param_spec, &value);
g_object_set_property (G_OBJECT (setting), param_spec->name, &value);
}
return TRUE;
}
/**
* _nm_setting_clear_secrets:
* @setting: the #NMSetting
* @func: (scope call): function to be called to determine whether a
* specific secret should be cleared or not
* @user_data: caller-supplied data passed to @func
*
* Clears and frees secrets determined by @func.
*
* Returns: %TRUE if the setting changed at all
**/
gboolean
_nm_setting_clear_secrets (NMSetting *setting,
NMSettingClearSecretsWithFlagsFn func,
gpointer user_data)
{
const NMSettInfoSetting *sett_info;
gboolean changed = FALSE;
guint i;
gboolean (*my_clear_secrets) (const struct _NMSettInfoSetting *sett_info,
guint property_idx,
NMSetting *setting,
NMSettingClearSecretsWithFlagsFn func,
gpointer user_data);
g_return_val_if_fail (NM_IS_SETTING (setting), FALSE);
my_clear_secrets = NM_SETTING_GET_CLASS (setting)->clear_secrets;
sett_info = _nm_setting_class_get_sett_info (NM_SETTING_GET_CLASS (setting));
for (i = 0; i < sett_info->property_infos_len; i++) {
changed |= my_clear_secrets (sett_info,
i,
setting,
func,
user_data);
}
return changed;
}
/**
* _nm_setting_need_secrets:
* @setting: the #NMSetting
*
* Returns an array of property names for each secret which may be required
* to make a successful connection. The returned hints are only intended as a
* guide to what secrets may be required, because in some circumstances, there
* is no way to conclusively determine exactly which secrets are needed.
*
* Returns: (transfer container) (element-type utf8): a #GPtrArray containing
* the property names of secrets of the #NMSetting which may be required; the
* caller owns the array and must free it with g_ptr_array_free(), but must not
* free the elements.
**/
GPtrArray *
_nm_setting_need_secrets (NMSetting *setting)
{
GPtrArray *secrets = NULL;
g_return_val_if_fail (NM_IS_SETTING (setting), NULL);
if (NM_SETTING_GET_CLASS (setting)->need_secrets)
secrets = NM_SETTING_GET_CLASS (setting)->need_secrets (setting);
return secrets;
}
static int
update_one_secret (NMSetting *setting, const char *key, GVariant *value, GError **error)
{
const NMSettInfoProperty *property;
GParamSpec *prop_spec;
GValue prop_value = { 0, };
property = _nm_setting_class_get_property_info (NM_SETTING_GET_CLASS (setting), key);
if (!property) {
g_set_error_literal (error,
NM_CONNECTION_ERROR,
NM_CONNECTION_ERROR_PROPERTY_NOT_FOUND,
_("secret not found"));
g_prefix_error (error, "%s.%s: ", nm_setting_get_name (setting), key);
return NM_SETTING_UPDATE_SECRET_ERROR;
}
/* Silently ignore non-secrets */
prop_spec = property->param_spec;
if (!prop_spec || !(prop_spec->flags & NM_SETTING_PARAM_SECRET))
return NM_SETTING_UPDATE_SECRET_SUCCESS_UNCHANGED;
if ( g_variant_is_of_type (value, G_VARIANT_TYPE_STRING)
&& G_IS_PARAM_SPEC_STRING (prop_spec)) {
/* String is expected to be a common case. Handle it specially and check
* whether the value is already set. Otherwise, we just reset the
* property and assume the value got modified.
*/
char *v;
g_object_get (G_OBJECT (setting), prop_spec->name, &v, NULL);
if (g_strcmp0 (v, g_variant_get_string (value, NULL)) == 0) {
g_free (v);
return NM_SETTING_UPDATE_SECRET_SUCCESS_UNCHANGED;
}
g_free (v);
}
g_value_init (&prop_value, prop_spec->value_type);
set_property_from_dbus (property, value, &prop_value);
g_object_set_property (G_OBJECT (setting), prop_spec->name, &prop_value);
g_value_unset (&prop_value);
return NM_SETTING_UPDATE_SECRET_SUCCESS_MODIFIED;
}
/**
* _nm_setting_update_secrets:
* @setting: the #NMSetting
* @secrets: a #GVariant of type #NM_VARIANT_TYPE_SETTING, mapping property
* names to secrets.
* @error: location to store error, or %NULL
*
* Update the setting's secrets, given a dictionary of secrets intended for that
* setting (deserialized from D-Bus for example).
*
* Returns: an #NMSettingUpdateSecretResult
**/
NMSettingUpdateSecretResult
_nm_setting_update_secrets (NMSetting *setting, GVariant *secrets, GError **error)
{
GVariantIter iter;
const char *secret_key;
GVariant *secret_value;
GError *tmp_error = NULL;
NMSettingUpdateSecretResult result = NM_SETTING_UPDATE_SECRET_SUCCESS_UNCHANGED;
g_return_val_if_fail (NM_IS_SETTING (setting), NM_SETTING_UPDATE_SECRET_ERROR);
g_return_val_if_fail (g_variant_is_of_type (secrets, NM_VARIANT_TYPE_SETTING), NM_SETTING_UPDATE_SECRET_ERROR);
if (error)
g_return_val_if_fail (*error == NULL, NM_SETTING_UPDATE_SECRET_ERROR);
g_variant_iter_init (&iter, secrets);
while (g_variant_iter_next (&iter, "{&sv}", &secret_key, &secret_value)) {
int success;
success = NM_SETTING_GET_CLASS (setting)->update_one_secret (setting, secret_key, secret_value, &tmp_error);
nm_assert (!((success == NM_SETTING_UPDATE_SECRET_ERROR) ^ (!!tmp_error)));
g_variant_unref (secret_value);
if (success == NM_SETTING_UPDATE_SECRET_ERROR) {
g_propagate_error (error, tmp_error);
return NM_SETTING_UPDATE_SECRET_ERROR;
}
if (success == NM_SETTING_UPDATE_SECRET_SUCCESS_MODIFIED)
result = NM_SETTING_UPDATE_SECRET_SUCCESS_MODIFIED;
}
return result;
}
static void
for_each_secret (NMSetting *setting,
const char *secret_name,
GVariant *val,
gboolean remove_non_secrets,
_NMConnectionForEachSecretFunc callback,
gpointer callback_data,
GVariantBuilder *setting_builder)
{
NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE;
if (!nm_setting_get_secret_flags (setting, secret_name, &secret_flags, NULL)) {
if (!remove_non_secrets)
g_variant_builder_add (setting_builder, "{sv}", secret_name, val);
return;
}
if (callback (secret_flags, callback_data))
g_variant_builder_add (setting_builder, "{sv}", secret_name, val);
}
static void
_set_error_secret_property_not_found (GError **error,
NMSetting *setting,
const char *secret_name)
{
g_set_error_literal (error,
NM_CONNECTION_ERROR,
NM_CONNECTION_ERROR_PROPERTY_NOT_FOUND,
_("not a secret property"));
g_prefix_error (error, "%s.%s: ", nm_setting_get_name (setting), secret_name);
}
gboolean
_nm_setting_property_is_regular_secret (NMSetting *setting,
const char *secret_name)
{
const NMSettInfoProperty *property;
nm_assert (NM_IS_SETTING (setting));
nm_assert (secret_name);
property = _nm_setting_class_get_property_info (NM_SETTING_GET_CLASS (setting), secret_name);
return property
&& property->param_spec
&& NM_FLAGS_HAS (property->param_spec->flags, NM_SETTING_PARAM_SECRET);
}
gboolean
_nm_setting_property_is_regular_secret_flags (NMSetting *setting,
const char *secret_flags_name)
{
const NMSettInfoProperty *property;
nm_assert (NM_IS_SETTING (setting));
nm_assert (secret_flags_name);
property = _nm_setting_class_get_property_info (NM_SETTING_GET_CLASS (setting), secret_flags_name);
return property
&& property->param_spec
&& !NM_FLAGS_HAS (property->param_spec->flags, NM_SETTING_PARAM_SECRET)
&& G_PARAM_SPEC_VALUE_TYPE (property->param_spec) == NM_TYPE_SETTING_SECRET_FLAGS;
}
static gboolean
get_secret_flags (NMSetting *setting,
const char *secret_name,
NMSettingSecretFlags *out_flags,
GError **error)
{
gs_free char *secret_flags_name_free = NULL;
const char *secret_flags_name;
NMSettingSecretFlags flags;
if (!_nm_setting_property_is_regular_secret (setting,
secret_name)) {
_set_error_secret_property_not_found (error, setting, secret_name);
NM_SET_OUT (out_flags, NM_SETTING_SECRET_FLAG_NONE);
return FALSE;
}
secret_flags_name = nm_construct_name_a ("%s-flags", secret_name, &secret_flags_name_free);
nm_assert (_nm_setting_property_is_regular_secret_flags (setting, secret_flags_name));
g_object_get (G_OBJECT (setting),
secret_flags_name,
&flags,
NULL);
NM_SET_OUT (out_flags, flags);
return TRUE;
}
/**
* nm_setting_get_secret_flags:
* @setting: the #NMSetting
* @secret_name: the secret key name to get flags for
* @out_flags: on success, the #NMSettingSecretFlags for the secret
* @error: location to store error, or %NULL
*
* For a given secret, retrieves the #NMSettingSecretFlags describing how to
* handle that secret.
*
* Returns: %TRUE on success (if the given secret name was a valid property of
* this setting, and if that property is secret), %FALSE if not
**/
gboolean
nm_setting_get_secret_flags (NMSetting *setting,
const char *secret_name,
NMSettingSecretFlags *out_flags,
GError **error)
{
g_return_val_if_fail (NM_IS_SETTING (setting), FALSE);
g_return_val_if_fail (secret_name != NULL, FALSE);
return NM_SETTING_GET_CLASS (setting)->get_secret_flags (setting, secret_name, out_flags, error);
}
static gboolean
set_secret_flags (NMSetting *setting,
const char *secret_name,
NMSettingSecretFlags flags,
GError **error)
{
gs_free char *secret_flags_name_free = NULL;
const char *secret_flags_name;
if (!_nm_setting_property_is_regular_secret (setting,
secret_name)) {
_set_error_secret_property_not_found (error, setting, secret_name);
return FALSE;
}
secret_flags_name = nm_construct_name_a ("%s-flags", secret_name, &secret_flags_name_free);
nm_assert (_nm_setting_property_is_regular_secret_flags (setting, secret_flags_name));
if (!nm_g_object_set_property_flags (G_OBJECT (setting),
secret_flags_name,
NM_TYPE_SETTING_SECRET_FLAGS,
flags,
error))
g_return_val_if_reached (FALSE);
return TRUE;
}
/**
* nm_setting_set_secret_flags:
* @setting: the #NMSetting
* @secret_name: the secret key name to set flags for
* @flags: the #NMSettingSecretFlags for the secret
* @error: location to store error, or %NULL
*
* For a given secret, stores the #NMSettingSecretFlags describing how to
* handle that secret.
*
* Returns: %TRUE on success (if the given secret name was a valid property of
* this setting, and if that property is secret), %FALSE if not
**/
gboolean
nm_setting_set_secret_flags (NMSetting *setting,
const char *secret_name,
NMSettingSecretFlags flags,
GError **error)
{
g_return_val_if_fail (NM_IS_SETTING (setting), FALSE);
g_return_val_if_fail (secret_name != NULL, FALSE);
g_return_val_if_fail (_nm_setting_secret_flags_valid (flags), FALSE);
return NM_SETTING_GET_CLASS (setting)->set_secret_flags (setting, secret_name, flags, error);
}
/**
* nm_setting_to_string:
* @setting: the #NMSetting
*
* Convert the setting (including secrets!) into a string. For debugging
* purposes ONLY, should NOT be used for serialization of the setting,
* or machine-parsed in any way. The output format is not guaranteed to
* be stable and may change at any time.
*
* Returns: an allocated string containing a textual representation of the
* setting's properties and values, which the caller should
* free with g_free()
**/
char *
nm_setting_to_string (NMSetting *setting)
{
GString *string;
gs_unref_variant GVariant *variant = NULL;
GVariant *child;
GVariantIter iter;
string = g_string_new (nm_setting_get_name (setting));
g_string_append_c (string, '\n');
variant = _nm_setting_to_dbus (setting, NULL, NM_CONNECTION_SERIALIZE_ALL, NULL);
g_variant_iter_init (&iter, variant);
while ((child = g_variant_iter_next_value (&iter))) {
gs_free char *name = NULL;
gs_free char *value_str = NULL;
gs_unref_variant GVariant *value = NULL;
g_variant_get (child, "{sv}", &name, &value);
value_str = g_variant_print (value, FALSE);
g_string_append_printf (string, "\t%s : %s\n", name, value_str);
}
return g_string_free (string, FALSE);
}
static GVariant *
_nm_setting_get_deprecated_virtual_interface_name (const NMSettInfoSetting *sett_info,
guint property_idx,
NMConnection *connection,
NMSetting *setting,
NMConnectionSerializationFlags flags,
const NMConnectionSerializationOptions *options)
{
NMSettingConnection *s_con;
if (!connection)
return NULL;
s_con = nm_connection_get_setting_connection (connection);
if (!s_con)
return NULL;
if (nm_setting_connection_get_interface_name (s_con))
return g_variant_new_string (nm_setting_connection_get_interface_name (s_con));
else
return NULL;
}
const NMSettInfoPropertType nm_sett_info_propert_type_deprecated_interface_name = {
.dbus_type = G_VARIANT_TYPE_STRING,
.to_dbus_fcn = _nm_setting_get_deprecated_virtual_interface_name,
};
const NMSettInfoPropertType nm_sett_info_propert_type_deprecated_ignore_i = {
.dbus_type = G_VARIANT_TYPE_INT32,
/* No functions set. This property type is to silently ignore the value on D-Bus. */
};
const NMSettInfoPropertType nm_sett_info_propert_type_deprecated_ignore_u = {
.dbus_type = G_VARIANT_TYPE_UINT32,
/* No functions set. This property type is to silently ignore the value on D-Bus. */
};
const NMSettInfoPropertType nm_sett_info_propert_type_plain_i = {
.dbus_type = G_VARIANT_TYPE_INT32,
};
const NMSettInfoPropertType nm_sett_info_propert_type_plain_u = {
.dbus_type = G_VARIANT_TYPE_UINT32,
};
/*****************************************************************************/
static GenData *
_gendata_hash (NMSetting *setting, gboolean create_if_necessary)
{
NMSettingPrivate *priv;
nm_assert (NM_IS_SETTING (setting));
priv = NM_SETTING_GET_PRIVATE (setting);
if (G_UNLIKELY (!priv->gendata)) {
if (!create_if_necessary)
return NULL;
priv->gendata = g_slice_new (GenData);
priv->gendata->hash = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, (GDestroyNotify) g_variant_unref);
priv->gendata->names = NULL;
priv->gendata->values = NULL;
}
return priv->gendata;
}
GHashTable *
_nm_setting_gendata_hash (NMSetting *setting, gboolean create_if_necessary)
{
GenData *gendata;
gendata = _gendata_hash (setting, create_if_necessary);
return gendata ? gendata->hash : NULL;
}
void
_nm_setting_gendata_notify (NMSetting *setting,
gboolean names_changed)
{
GenData *gendata;
gendata = _gendata_hash (setting, FALSE);
if (!gendata)
goto out;
nm_clear_g_free (&gendata->values);
if (names_changed) {
/* if only the values changed, it's sufficient to invalidate the
* values cache. Otherwise, the names cache must be invalidated too. */
nm_clear_g_free (&gendata->names);
}
/* Note, currently there is no way to notify the subclass when gendata changed.
* gendata is only changed in two situations:
* 1) from within NMSetting itself, for example when creating a NMSetting instance
* from keyfile or a D-Bus GVariant.
* 2) actively from the subclass itself
* For 2), we don't need the notification, because the subclass knows that something
* changed.
* For 1), we currently don't need the notification either, because all that the subclass
* currently would do, is emit a g_object_notify() signal. However, 1) only happens when
* the setting instance is newly created, at that point, nobody listens to the signal.
*
* If we ever need it, then we would need to call a virtual function to notify the subclass
* that gendata changed. */
out:
_nm_setting_emit_property_changed (setting);
}
GVariant *
nm_setting_gendata_get (NMSetting *setting,
const char *name)
{
GenData *gendata;
g_return_val_if_fail (NM_IS_SETTING (setting), NULL);
g_return_val_if_fail (name, NULL);
gendata = _gendata_hash (setting, FALSE);
return gendata ? g_hash_table_lookup (gendata->hash, name) : NULL;
}
guint
_nm_setting_gendata_get_all (NMSetting *setting,
const char *const**out_names,
GVariant *const**out_values)
{
GenData *gendata;
GHashTable *hash;
guint i, len;
nm_assert (NM_IS_SETTING (setting));
gendata = _gendata_hash (setting, FALSE);
if (!gendata)
goto out_zero;
hash = gendata->hash;
len = g_hash_table_size (hash);
if (len == 0)
goto out_zero;
if (!out_names && !out_values)
return len;
if (G_UNLIKELY (!gendata->names)) {
gendata->names = nm_utils_strdict_get_keys (hash,
TRUE,
NULL);
}
if (out_values) {
if (G_UNLIKELY (!gendata->values)) {
gendata->values = g_new (GVariant *, len + 1);
for (i = 0; i < len; i++)
gendata->values[i] = g_hash_table_lookup (hash, gendata->names[i]);
gendata->values[i] = NULL;
}
*out_values = gendata->values;
}
NM_SET_OUT (out_names, (const char *const*) gendata->names);
return len;
out_zero:
NM_SET_OUT (out_names, NULL);
NM_SET_OUT (out_values, NULL);
return 0;
}
/**
* nm_setting_gendata_get_all_names:
* @setting: the #NMSetting
* @out_len: (allow-none) (out):
*
* Gives the number of generic data elements and optionally returns all their
* key names and values. This API is low level access and unless you know what you
* are doing, it might not be what you want.
*
* Returns: (array length=out_len zero-terminated=1) (transfer none):
* A %NULL terminated array of key names. If no names are present, this returns
* %NULL. The returned array and the names are owned by %NMSetting and might be invalidated
* soon.
*
* Since: 1.14
**/
const char *const*
nm_setting_gendata_get_all_names (NMSetting *setting,
guint *out_len)
{
const char *const*names;
guint len;
g_return_val_if_fail (NM_IS_SETTING (setting), NULL);
len = _nm_setting_gendata_get_all (setting, &names, NULL);
NM_SET_OUT (out_len, len);
return names;
}
/**
* nm_setting_gendata_get_all_values:
* @setting: the #NMSetting
*
* Gives the number of generic data elements and optionally returns all their
* key names and values. This API is low level access and unless you know what you
* are doing, it might not be what you want.
*
* Returns: (array zero-terminated=1) (transfer none):
* A %NULL terminated array of #GVariant. If no data is present, this returns
* %NULL. The returned array and the variants are owned by %NMSetting and might be invalidated
* soon. The sort order of nm_setting_gendata_get_all_names() and nm_setting_gendata_get_all_values()
* is consistent. That means, the nth value has the nth name returned by nm_setting_gendata_get_all_names().
*
* Since: 1.14
**/
GVariant *const*
nm_setting_gendata_get_all_values (NMSetting *setting)
{
GVariant *const*values;
g_return_val_if_fail (NM_IS_SETTING (setting), NULL);
_nm_setting_gendata_get_all (setting, NULL, &values);
return values;
}
void
_nm_setting_gendata_to_gvalue (NMSetting *setting,
GValue *value)
{
GenData *gendata;
GHashTable *new;
const char *key;
GVariant *val;
GHashTableIter iter;
nm_assert (NM_IS_SETTING (setting));
nm_assert (value);
nm_assert (G_TYPE_CHECK_VALUE_TYPE ((value), G_TYPE_HASH_TABLE));
new = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, (GDestroyNotify) g_variant_unref);
gendata = _gendata_hash (setting, FALSE);
if (gendata) {
g_hash_table_iter_init (&iter, gendata->hash);
while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &val))
g_hash_table_insert (new, g_strdup (key), g_variant_ref (val));
}
g_value_take_boxed (value, new);
}
gboolean
_nm_setting_gendata_reset_from_hash (NMSetting *setting,
GHashTable *new)
{
GenData *gendata;
GHashTableIter iter;
const char *key;
GVariant *val;
guint num;
nm_assert (NM_IS_SETTING (setting));
nm_assert (new);
num = new ? g_hash_table_size (new) : 0;
gendata = _gendata_hash (setting, num > 0);
if (num == 0) {
if ( !gendata
|| g_hash_table_size (gendata->hash) == 0)
return FALSE;
g_hash_table_remove_all (gendata->hash);
_nm_setting_gendata_notify (setting, TRUE);
return TRUE;
}
/* let's not bother to find out whether the new hash has any different
* content the current gendata. Just replace it. */
g_hash_table_remove_all (gendata->hash);
if (num > 0) {
g_hash_table_iter_init (&iter, new);
while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &val))
g_hash_table_insert (gendata->hash, g_strdup (key), g_variant_ref (val));
}
_nm_setting_gendata_notify (setting, TRUE);
return TRUE;
}
/*****************************************************************************/
static void
get_property (GObject *object, guint prop_id,
GValue *value, GParamSpec *pspec)
{
NMSetting *setting = NM_SETTING (object);
switch (prop_id) {
case PROP_NAME:
g_value_set_string (value, nm_setting_get_name (setting));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
/*****************************************************************************/
static void
nm_setting_init (NMSetting *setting)
{
}
static void
finalize (GObject *object)
{
NMSettingPrivate *priv = NM_SETTING_GET_PRIVATE (object);
if (priv->gendata) {
g_free (priv->gendata->names);
g_free (priv->gendata->values);
g_hash_table_unref (priv->gendata->hash);
g_slice_free (GenData, priv->gendata);
}
G_OBJECT_CLASS (nm_setting_parent_class)->finalize (object);
}
static void
nm_setting_class_init (NMSettingClass *setting_class)
{
GObjectClass *object_class = G_OBJECT_CLASS (setting_class);
g_type_class_add_private (setting_class, sizeof (NMSettingPrivate));
object_class->get_property = get_property;
object_class->finalize = finalize;
setting_class->update_one_secret = update_one_secret;
setting_class->get_secret_flags = get_secret_flags;
setting_class->set_secret_flags = set_secret_flags;
setting_class->compare_property = compare_property;
setting_class->clear_secrets = clear_secrets;
setting_class->for_each_secret = for_each_secret;
setting_class->duplicate_copy_properties = duplicate_copy_properties;
setting_class->enumerate_values = enumerate_values;
setting_class->aggregate = aggregate;
setting_class->init_from_dbus = init_from_dbus;
/**
* NMSetting:name:
*
* The setting's name, which uniquely identifies the setting within the
* connection. Each setting type has a name unique to that type, for
* example "ppp" or "802-11-wireless" or "802-3-ethernet".
**/
obj_properties[PROP_NAME] =
g_param_spec_string (NM_SETTING_NAME, "", "",
NULL,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
}