Files
NetworkManager/libnm-core/nm-setting.c
Thomas Haller 4199c976da libnm: fix normalizing and verifying OVS connections
Normalizing can be complicated, as settings depend on each other and possibly
conflict.

That is, because verify() must exactly anticipate whether normalization will
succeed and how the result will look like. That is because we only want to
modify the connection, if we are sure that the result will verify.

Hence, verify() and normalize() are strongly related. The implementation
should not be spread out between NMSettingOvsInterface:verify(),
NMSettingOvsPatch:verify() and _normalize_ovs_interface_type().

Also, add some unit-tests.
2017-10-30 21:46:55 +01:00

2030 lines
66 KiB
C

/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
/*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
* Copyright 2007 - 2011 Red Hat, Inc.
* Copyright 2007 - 2008 Novell, Inc.
*/
#include "nm-default.h"
#include "nm-setting.h"
#include <string.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"
#include "nm-setting-connection.h"
#include "nm-setting-bond.h"
#include "nm-setting-bridge.h"
#include "nm-setting-bridge-port.h"
#include "nm-setting-pppoe.h"
#include "nm-setting-team.h"
#include "nm-setting-team-port.h"
#include "nm-setting-vpn.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.
*/
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))
typedef struct {
const char *name;
GType type;
NMSettingPriority priority;
} SettingInfo;
typedef struct {
const SettingInfo *info;
} NMSettingPrivate;
enum {
PROP_0,
PROP_NAME,
PROP_LAST
};
/*****************************************************************************/
static GHashTable *registered_settings = NULL;
static GHashTable *registered_settings_by_type = NULL;
static gboolean
_nm_gtype_equal (gconstpointer v1, gconstpointer v2)
{
return *((const GType *) v1) == *((const GType *) v2);
}
static guint
_nm_gtype_hash (gconstpointer v)
{
return *((const GType *) v);
}
static void
_ensure_registered (void)
{
if (G_UNLIKELY (registered_settings == NULL)) {
nm_g_type_init ();
registered_settings = g_hash_table_new (g_str_hash, g_str_equal);
registered_settings_by_type = g_hash_table_new (_nm_gtype_hash, _nm_gtype_equal);
}
}
static void __attribute__((constructor))
_ensure_registered_constructor (void)
{
_ensure_registered ();
}
#define _ensure_setting_info(self, priv) \
G_STMT_START { \
NMSettingPrivate *_priv_esi = (priv); \
if (G_UNLIKELY (!_priv_esi->info)) { \
_priv_esi->info = _nm_setting_lookup_setting_by_type (G_OBJECT_TYPE (self)); \
g_assert (_priv_esi->info); \
} \
} G_STMT_END
/*****************************************************************************/
/*
* _nm_register_setting_impl:
* @name: the name of the #NMSetting object to register
* @type: the #GType of the #NMSetting
* @priority: the sort priority of the setting, see #NMSettingPriority
*
* INTERNAL ONLY: registers a setting's internal properties with libnm.
*/
void
_nm_register_setting_impl (const char *name,
GType type,
NMSettingPriority priority)
{
SettingInfo *info;
nm_assert (name && *name);
nm_assert (!NM_IN_SET (type, G_TYPE_INVALID, G_TYPE_NONE));
nm_assert (priority != NM_SETTING_PRIORITY_INVALID);
_ensure_registered ();
nm_assert (!g_hash_table_lookup (registered_settings, name));
nm_assert (!g_hash_table_lookup (registered_settings_by_type, &type));
nm_assert ( priority != NM_SETTING_PRIORITY_CONNECTION
|| nm_streq (name, NM_SETTING_CONNECTION_SETTING_NAME));
info = g_slice_new0 (SettingInfo);
info->type = type;
info->priority = priority;
info->name = name;
g_hash_table_insert (registered_settings, (void *) info->name, info);
g_hash_table_insert (registered_settings_by_type, &info->type, info);
}
static const SettingInfo *
_nm_setting_lookup_setting_by_type (GType type)
{
_ensure_registered ();
return g_hash_table_lookup (registered_settings_by_type, &type);
}
static NMSettingPriority
_get_setting_type_priority (GType type)
{
const SettingInfo *info;
g_return_val_if_fail (g_type_is_a (type, NM_TYPE_SETTING), G_MAXUINT32);
info = _nm_setting_lookup_setting_by_type (type);
return info->priority;
}
NMSettingPriority
_nm_setting_get_setting_priority (NMSetting *setting)
{
NMSettingPrivate *priv;
g_return_val_if_fail (NM_IS_SETTING (setting), G_MAXUINT32);
priv = NM_SETTING_GET_PRIVATE (setting);
_ensure_setting_info (setting, priv);
return priv->info->priority;
}
NMSettingPriority
_nm_setting_type_get_base_type_priority (GType type)
{
NMSettingPriority priority;
/* 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.
*/
priority = _get_setting_type_priority (type);
if ( NM_IN_SET (priority,
NM_SETTING_PRIORITY_HW_BASE,
NM_SETTING_PRIORITY_HW_NON_BASE)
|| type == NM_TYPE_SETTING_PPPOE)
return priority;
else
return NM_SETTING_PRIORITY_INVALID;
}
NMSettingPriority
_nm_setting_get_base_type_priority (NMSetting *setting)
{
return _nm_setting_type_get_base_type_priority (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 SettingInfo *info;
g_return_val_if_fail (name, G_TYPE_INVALID);
_ensure_registered ();
info = g_hash_table_lookup (registered_settings, name);
return info ? info->type : G_TYPE_INVALID;
}
gint
_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 (!strcmp (slave_type, NM_SETTING_BOND_SETTING_NAME))
;
else if (!strcmp (slave_type, NM_SETTING_BRIDGE_SETTING_NAME))
port_type = NM_SETTING_BRIDGE_PORT_SETTING_NAME;
else if (!strcmp (slave_type, NM_SETTING_OVS_BRIDGE_SETTING_NAME))
port_type = NM_SETTING_OVS_PORT_SETTING_NAME;
else if (!strcmp (slave_type, NM_SETTING_OVS_PORT_SETTING_NAME))
port_type = NM_SETTING_OVS_INTERFACE_SETTING_NAME;
else if (!strcmp (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;
}
/*****************************************************************************/
typedef struct {
const char *name;
GParamSpec *param_spec;
const GVariantType *dbus_type;
NMSettingPropertyGetFunc get_func;
NMSettingPropertySynthFunc synth_func;
NMSettingPropertySetFunc set_func;
NMSettingPropertyNotSetFunc not_set_func;
NMSettingPropertyTransformToFunc to_dbus;
NMSettingPropertyTransformFromFunc from_dbus;
} NMSettingProperty;
static NM_CACHED_QUARK_FCN ("nm-setting-property-overrides", setting_property_overrides_quark)
static NM_CACHED_QUARK_FCN ("nm-setting-properties", setting_properties_quark)
static NMSettingProperty *
find_property (GArray *properties, const char *name)
{
NMSettingProperty *property;
int i;
if (!properties)
return NULL;
for (i = 0; i < properties->len; i++) {
property = &g_array_index (properties, NMSettingProperty, i);
if (strcmp (name, property->name) == 0)
return property;
}
return NULL;
}
static void
add_property_override (NMSettingClass *setting_class,
const char *property_name,
GParamSpec *param_spec,
const GVariantType *dbus_type,
NMSettingPropertyGetFunc get_func,
NMSettingPropertySynthFunc synth_func,
NMSettingPropertySetFunc set_func,
NMSettingPropertyNotSetFunc not_set_func,
NMSettingPropertyTransformToFunc to_dbus,
NMSettingPropertyTransformFromFunc from_dbus)
{
GType setting_type = G_TYPE_FROM_CLASS (setting_class);
GArray *overrides;
NMSettingProperty override;
g_return_if_fail (g_type_get_qdata (setting_type, setting_properties_quark ()) == NULL);
memset (&override, 0, sizeof (override));
override.name = property_name;
override.param_spec = param_spec;
override.dbus_type = dbus_type;
override.get_func = get_func;
override.synth_func = synth_func;
override.set_func = set_func;
override.not_set_func = not_set_func;
override.to_dbus = to_dbus;
override.from_dbus = from_dbus;
overrides = g_type_get_qdata (setting_type, setting_property_overrides_quark ());
if (!overrides) {
overrides = g_array_new (FALSE, FALSE, sizeof (NMSettingProperty));
g_type_set_qdata (setting_type, setting_property_overrides_quark (), overrides);
}
g_return_if_fail (find_property (overrides, property_name) == NULL);
g_array_append_val (overrides, override);
}
/**
* _nm_setting_class_add_dbus_only_property:
* @setting_class: the setting class
* @property_name: the name of the property to override
* @dbus_type: the type of the property (in its D-Bus representation)
* @synth_func: (allow-none): function to call to synthesize a value for the property
* @set_func: (allow-none): function to call to set the value of the property
*
* Registers a property named @property_name, which will be used in the D-Bus
* serialization of objects of @setting_class, but which does not correspond to
* a #GObject property.
*
* When serializing a setting to D-Bus, @synth_func will be called to synthesize
* a value for the property. (If it returns %NULL, no value will be added to the
* serialization. If @synth_func is %NULL, the property will always be omitted
* in the serialization.)
*
* When deserializing a D-Bus representation into a setting, if @property_name
* is present, then @set_func will be called to set it. (If @set_func is %NULL
* then the property will be ignored when deserializing.)
*/
void
_nm_setting_class_add_dbus_only_property (NMSettingClass *setting_class,
const char *property_name,
const GVariantType *dbus_type,
NMSettingPropertySynthFunc synth_func,
NMSettingPropertySetFunc set_func)
{
g_return_if_fail (NM_IS_SETTING_CLASS (setting_class));
g_return_if_fail (property_name != NULL);
/* Must not match any GObject property. */
g_return_if_fail (!g_object_class_find_property (G_OBJECT_CLASS (setting_class), property_name));
add_property_override (setting_class,
property_name, NULL, dbus_type,
NULL, synth_func, set_func, NULL,
NULL, NULL);
}
/**
* _nm_setting_class_override_property:
* @setting_class: the setting class
* @property_name: the name of the property to override
* @dbus_type: the type of the property (in its D-Bus representation)
* @get_func: (allow-none): function to call to get the value of the property
* @set_func: (allow-none): function to call to set the value of the property
* @not_set_func: (allow-none): function to call to indicate the property was not set
*
* Overrides the D-Bus representation of the #GObject property named
* @property_name on @setting_class.
*
* When serializing a setting to D-Bus, if @get_func is non-%NULL, then it will
* be called to get the property's value. If it returns a #GVariant, the
* property will be added to the hash, and if it returns %NULL, the property
* will be omitted. (If @get_func is %NULL, the property will be read normally
* with g_object_get_property(), and added to the hash if it is not the default
* value.)
*
* When deserializing a D-Bus representation into a setting, if @property_name
* is present, then @set_func will be called to set it. (If @set_func is %NULL
* then the property will be set normally with g_object_set_property().)
*
* If @not_set_func is non-%NULL, then it will be called when deserializing a
* representation that does NOT contain @property_name. This can be used, eg, if
* a new property needs to be initialized from some older deprecated property
* when it is not present.
*/
void
_nm_setting_class_override_property (NMSettingClass *setting_class,
const char *property_name,
const GVariantType *dbus_type,
NMSettingPropertyGetFunc get_func,
NMSettingPropertySetFunc set_func,
NMSettingPropertyNotSetFunc not_set_func)
{
GParamSpec *param_spec;
param_spec = g_object_class_find_property (G_OBJECT_CLASS (setting_class), property_name);
g_return_if_fail (param_spec != NULL);
add_property_override (setting_class,
property_name, param_spec, dbus_type,
get_func, NULL, set_func, not_set_func,
NULL, NULL);
}
/**
* _nm_setting_class_transform_property:
* @setting_class: the setting class
* @property: the name of the property to transform
* @dbus_type: the type of the property (in its D-Bus representation)
* @to_dbus: function to convert from object to D-Bus format
* @from_dbus: function to convert from D-Bus to object format
*
* Indicates that @property on @setting_class does not have the same format as
* its corresponding D-Bus representation, and so must be transformed when
* serializing/deserializing.
*
* The transformation will also be used by nm_setting_compare(), meaning that
* the underlying object property does not need to be of a type that
* nm_property_compare() recognizes, as long as it recognizes @dbus_type.
*/
void
_nm_setting_class_transform_property (NMSettingClass *setting_class,
const char *property,
const GVariantType *dbus_type,
NMSettingPropertyTransformToFunc to_dbus,
NMSettingPropertyTransformFromFunc from_dbus)
{
GParamSpec *param_spec;
param_spec = g_object_class_find_property (G_OBJECT_CLASS (setting_class), property);
g_return_if_fail (param_spec != NULL);
add_property_override (setting_class,
property, param_spec, dbus_type,
NULL, NULL, NULL, NULL,
to_dbus, from_dbus);
}
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 GArray *
nm_setting_class_ensure_properties (NMSettingClass *setting_class)
{
GType type = G_TYPE_FROM_CLASS (setting_class), otype;
NMSettingProperty property, *override;
GArray *overrides, *type_overrides, *properties;
GParamSpec **property_specs;
guint n_property_specs, i;
properties = g_type_get_qdata (type, setting_properties_quark ());
if (properties)
return properties;
/* Build overrides array from @setting_class and its superclasses */
overrides = g_array_new (FALSE, FALSE, sizeof (NMSettingProperty));
for (otype = type; otype != G_TYPE_OBJECT; otype = g_type_parent (otype)) {
type_overrides = g_type_get_qdata (otype, setting_property_overrides_quark ());
if (type_overrides)
g_array_append_vals (overrides, (NMSettingProperty *)type_overrides->data, type_overrides->len);
}
/* Build the properties array from the GParamSpecs, obeying overrides */
properties = g_array_new (FALSE, FALSE, sizeof (NMSettingProperty));
property_specs = g_object_class_list_properties (G_OBJECT_CLASS (setting_class),
&n_property_specs);
for (i = 0; i < n_property_specs; i++) {
override = find_property (overrides, property_specs[i]->name);
if (override)
property = *override;
else {
memset (&property, 0, sizeof (property));
property.name = property_specs[i]->name;
property.param_spec = property_specs[i];
}
g_array_append_val (properties, property);
}
g_free (property_specs);
/* Add any remaining overrides not corresponding to GObject properties */
for (i = 0; i < overrides->len; i++) {
override = &g_array_index (overrides, NMSettingProperty, i);
if (!g_object_class_find_property (G_OBJECT_CLASS (setting_class), override->name))
g_array_append_val (properties, *override);
}
g_array_unref (overrides);
g_type_set_qdata (type, setting_properties_quark (), properties);
return properties;
}
static const NMSettingProperty *
nm_setting_class_get_properties (NMSettingClass *setting_class, guint *n_properties)
{
GArray *properties;
properties = nm_setting_class_ensure_properties (setting_class);
*n_properties = properties->len;
return (NMSettingProperty *) properties->data;
}
static const NMSettingProperty *
nm_setting_class_find_property (NMSettingClass *setting_class, const char *property_name)
{
GArray *properties;
properties = nm_setting_class_ensure_properties (setting_class);
return find_property (properties, property_name);
}
/*****************************************************************************/
static const GVariantType *
variant_type_for_gtype (GType type)
{
if (type == G_TYPE_BOOLEAN)
return G_VARIANT_TYPE_BOOLEAN;
else if (type == G_TYPE_UCHAR)
return G_VARIANT_TYPE_BYTE;
else if (type == G_TYPE_INT)
return G_VARIANT_TYPE_INT32;
else if (type == G_TYPE_UINT)
return G_VARIANT_TYPE_UINT32;
else if (type == G_TYPE_INT64)
return G_VARIANT_TYPE_INT64;
else if (type == G_TYPE_UINT64)
return G_VARIANT_TYPE_UINT64;
else if (type == G_TYPE_STRING)
return G_VARIANT_TYPE_STRING;
else if (type == G_TYPE_DOUBLE)
return G_VARIANT_TYPE_DOUBLE;
else if (type == G_TYPE_STRV)
return G_VARIANT_TYPE_STRING_ARRAY;
else if (type == G_TYPE_BYTES)
return G_VARIANT_TYPE_BYTESTRING;
else if (g_type_is_a (type, G_TYPE_ENUM))
return G_VARIANT_TYPE_INT32;
else if (g_type_is_a (type, G_TYPE_FLAGS))
return G_VARIANT_TYPE_UINT32;
else
g_assert_not_reached ();
}
static GVariant *
get_property_for_dbus (NMSetting *setting,
const NMSettingProperty *property,
gboolean ignore_default)
{
GValue prop_value = { 0, };
GVariant *dbus_value;
if (property->get_func)
return property->get_func (setting, property->name);
else
g_return_val_if_fail (property->param_spec != NULL, NULL);
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)) {
g_value_unset (&prop_value);
return NULL;
}
if (property->to_dbus)
dbus_value = property->to_dbus (&prop_value);
else if (property->dbus_type)
dbus_value = g_dbus_gvalue_to_gvariant (&prop_value, property->dbus_type);
else if (g_type_is_a (prop_value.g_type, G_TYPE_ENUM))
dbus_value = g_variant_new_int32 (g_value_get_enum (&prop_value));
else if (g_type_is_a (prop_value.g_type, G_TYPE_FLAGS))
dbus_value = g_variant_new_uint32 (g_value_get_flags (&prop_value));
else if (prop_value.g_type == G_TYPE_BYTES)
dbus_value = _nm_utils_bytes_to_dbus (&prop_value);
else
dbus_value = g_dbus_gvalue_to_gvariant (&prop_value, variant_type_for_gtype (prop_value.g_type));
g_value_unset (&prop_value);
return dbus_value;
}
static gboolean
set_property_from_dbus (const NMSettingProperty *property,
GVariant *src_value,
GValue *dst_value)
{
g_return_val_if_fail (property->param_spec != NULL, FALSE);
if (property->from_dbus) {
if (!g_variant_type_equal (g_variant_get_type (src_value), property->dbus_type))
return FALSE;
property->from_dbus (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
*
* 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)
{
GVariantBuilder builder;
GVariant *dbus_value;
const NMSettingProperty *properties;
guint n_properties, i;
g_return_val_if_fail (NM_IS_SETTING (setting), NULL);
properties = nm_setting_class_get_properties (NM_SETTING_GET_CLASS (setting), &n_properties);
g_variant_builder_init (&builder, NM_VARIANT_TYPE_SETTING);
for (i = 0; i < n_properties; i++) {
const NMSettingProperty *property = &properties[i];
GParamSpec *prop_spec = property->param_spec;
if (!prop_spec && !property->synth_func) {
/* D-Bus-only property with no synth_func, so we skip it. */
continue;
}
if (prop_spec && !(prop_spec->flags & G_PARAM_WRITABLE))
continue;
if ( prop_spec && (prop_spec->flags & NM_SETTING_PARAM_LEGACY)
&& !_nm_utils_is_manager_process)
continue;
if ( (flags & NM_CONNECTION_SERIALIZE_NO_SECRETS)
&& (prop_spec && (prop_spec->flags & NM_SETTING_PARAM_SECRET)))
continue;
if ( (flags & NM_CONNECTION_SERIALIZE_ONLY_SECRETS)
&& !(prop_spec && (prop_spec->flags & NM_SETTING_PARAM_SECRET)))
continue;
if (property->synth_func)
dbus_value = property->synth_func (setting, connection, property->name);
else
dbus_value = get_property_for_dbus (setting, property, TRUE);
if (dbus_value) {
/* Allow dbus_value to be either floating or not. */
g_variant_take_ref (dbus_value);
g_variant_builder_add (&builder, "{sv}", property->name, dbus_value);
g_variant_unref (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_object NMSetting *setting = NULL;
gs_unref_hashtable GHashTable *keys = NULL;
const NMSettingProperty *properties;
guint i, n_properties;
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;
char *key;
keys = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
g_variant_iter_init (&iter, setting_dict);
while ((entry = g_variant_iter_next_value (&iter))) {
entry_key = g_variant_get_child_value (entry, 0);
key = g_strdup (g_variant_get_string (entry_key, NULL));
g_variant_unref (entry_key);
g_variant_unref (entry);
if (!nm_g_hash_table_add (keys, 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;
}
}
}
properties = nm_setting_class_get_properties (NM_SETTING_GET_CLASS (setting), &n_properties);
for (i = 0; i < n_properties; i++) {
const NMSettingProperty *property = &properties[i];
gs_unref_variant GVariant *value = NULL;
gs_free_error GError *local = NULL;
if (property->param_spec && !(property->param_spec->flags & G_PARAM_WRITABLE))
continue;
value = g_variant_lookup_value (setting_dict, property->name, NULL);
if (value && keys)
g_hash_table_remove (keys, property->name);
if (value && property->set_func) {
if (!g_variant_type_equal (g_variant_get_type (value), property->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->dbus_type ?
g_variant_type_peek_string (property->dbus_type) :
property->param_spec ?
g_type_name (property->param_spec->value_type) : "(unknown)",
g_variant_get_type_string (value));
g_prefix_error (error, "%s.%s: ", nm_setting_get_name (setting), property->name);
return NULL;
}
if (!property->set_func (setting,
connection_dict,
property->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->name);
return NULL;
}
} else if (!value && property->not_set_func) {
if (!property->not_set_func (setting,
connection_dict,
property->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->name);
return NULL;
}
} else if (value && property->param_spec) {
nm_auto_unset_gvalue GValue object_value = G_VALUE_INIT;
g_value_init (&object_value, property->param_spec->value_type);
if (!set_property_from_dbus (property, 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->dbus_type ?
g_variant_type_peek_string (property->dbus_type) :
property->param_spec ?
g_type_name (property->param_spec->value_type) : "(unknown)",
g_variant_get_type_string (value));
g_prefix_error (error, "%s.%s: ", nm_setting_get_name (setting), property->name);
return NULL;
}
if (!nm_g_object_set_property (G_OBJECT (setting), property->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->name);
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);
}
/**
* 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 NMSettingProperty *property;
g_return_val_if_fail (NM_IS_SETTING (setting), NULL);
g_return_val_if_fail (property_name != NULL, NULL);
property = nm_setting_class_find_property (NM_SETTING_GET_CLASS (setting), property_name);
g_return_val_if_fail (property != NULL, NULL);
if (property->dbus_type)
return property->dbus_type;
else
return variant_type_for_gtype (property->param_spec->value_type);
}
gboolean
_nm_setting_get_property (NMSetting *setting, const char *property_name, GValue *value)
{
GParamSpec *prop_spec;
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);
prop_spec = g_object_class_find_property (G_OBJECT_GET_CLASS (setting), property_name);
if (!prop_spec) {
g_value_unset (value);
return FALSE;
}
g_value_init (value, prop_spec->value_type);
g_object_get_property (G_OBJECT (setting), property_name, value);
return TRUE;
}
static void
duplicate_setting (NMSetting *setting,
const char *name,
const GValue *value,
GParamFlags flags,
gpointer user_data)
{
if ((flags & (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)) == G_PARAM_WRITABLE)
g_object_set_property (G_OBJECT (user_data), name, value);
}
/**
* 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)
{
GObject *dup;
g_return_val_if_fail (NM_IS_SETTING (setting), NULL);
dup = g_object_new (G_OBJECT_TYPE (setting), NULL);
g_object_freeze_notify (dup);
nm_setting_enumerate_values (setting, duplicate_setting, dup);
g_object_thaw_notify (dup);
return NM_SETTING (dup);
}
/**
* 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)
{
NMSettingPrivate *priv;
g_return_val_if_fail (NM_IS_SETTING (setting), NULL);
priv = NM_SETTING_GET_PRIVATE (setting);
_ensure_setting_info (setting, priv);
return priv->info->name;
}
/**
* 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;
}
static gboolean
compare_property (NMSetting *setting,
NMSetting *other,
const GParamSpec *prop_spec,
NMSettingCompareFlags flags)
{
const NMSettingProperty *property;
GVariant *value1, *value2;
int cmp;
/* Handle compare flags */
if (prop_spec->flags & NM_SETTING_PARAM_SECRET) {
NMSettingSecretFlags a_secret_flags = NM_SETTING_SECRET_FLAG_NONE;
NMSettingSecretFlags b_secret_flags = NM_SETTING_SECRET_FLAG_NONE;
g_return_val_if_fail (!NM_IS_SETTING_VPN (setting), FALSE);
if (!nm_setting_get_secret_flags (setting, prop_spec->name, &a_secret_flags, NULL))
g_return_val_if_reached (FALSE);
if (!nm_setting_get_secret_flags (other, prop_spec->name, &b_secret_flags, NULL))
g_return_val_if_reached (FALSE);
/* If the secret flags aren't the same the settings aren't the same */
if (a_secret_flags != b_secret_flags)
return FALSE;
/* Check for various secret flags that might cause us to ignore comparing
* this property.
*/
if ( (flags & NM_SETTING_COMPARE_FLAG_IGNORE_AGENT_OWNED_SECRETS)
&& (a_secret_flags & NM_SETTING_SECRET_FLAG_AGENT_OWNED))
return TRUE;
if ( (flags & NM_SETTING_COMPARE_FLAG_IGNORE_NOT_SAVED_SECRETS)
&& (a_secret_flags & NM_SETTING_SECRET_FLAG_NOT_SAVED))
return TRUE;
}
property = nm_setting_class_find_property (NM_SETTING_GET_CLASS (setting), prop_spec->name);
g_return_val_if_fail (property != NULL, FALSE);
value1 = get_property_for_dbus (setting, property, TRUE);
value2 = get_property_for_dbus (other, property, TRUE);
cmp = nm_property_compare (value1, value2);
if (value1)
g_variant_unref (value1);
if (value2)
g_variant_unref (value2);
return cmp == 0;
}
/**
* 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)
{
GParamSpec **property_specs;
guint n_property_specs;
gint same = TRUE;
guint i;
g_return_val_if_fail (NM_IS_SETTING (a), FALSE);
g_return_val_if_fail (NM_IS_SETTING (b), FALSE);
/* First check that both have the same type */
if (G_OBJECT_TYPE (a) != G_OBJECT_TYPE (b))
return FALSE;
/* And now all properties */
property_specs = g_object_class_list_properties (G_OBJECT_GET_CLASS (a), &n_property_specs);
for (i = 0; i < n_property_specs && same; i++) {
GParamSpec *prop_spec = property_specs[i];
/* Fuzzy compare ignores secrets and properties defined with the FUZZY_IGNORE flag */
if ( NM_FLAGS_HAS (flags, NM_SETTING_COMPARE_FLAG_FUZZY)
&& !NM_FLAGS_ANY (prop_spec->flags, NM_SETTING_PARAM_FUZZY_IGNORE | NM_SETTING_PARAM_SECRET))
continue;
if ( NM_FLAGS_HAS (flags, NM_SETTING_COMPARE_FLAG_INFERRABLE)
&& !NM_FLAGS_HAS (prop_spec->flags, NM_SETTING_PARAM_INFERRABLE))
continue;
if ( NM_FLAGS_HAS (flags, NM_SETTING_COMPARE_FLAG_IGNORE_REAPPLY_IMMEDIATELY)
&& NM_FLAGS_HAS (prop_spec->flags, NM_SETTING_PARAM_REAPPLY_IMMEDIATELY))
continue;
if ( NM_FLAGS_HAS (flags, NM_SETTING_COMPARE_FLAG_IGNORE_SECRETS)
&& NM_FLAGS_HAS (prop_spec->flags, NM_SETTING_PARAM_SECRET))
continue;
same = NM_SETTING_GET_CLASS (a)->compare_property (a, b, prop_spec, flags);
}
g_free (property_specs);
return same;
}
static inline gboolean
should_compare_prop (NMSetting *setting,
const char *prop_name,
NMSettingCompareFlags comp_flags,
GParamFlags prop_flags)
{
/* Fuzzy compare ignores secrets and properties defined with the FUZZY_IGNORE flag */
if ( (comp_flags & NM_SETTING_COMPARE_FLAG_FUZZY)
&& (prop_flags & (NM_SETTING_PARAM_FUZZY_IGNORE | NM_SETTING_PARAM_SECRET)))
return FALSE;
if ((comp_flags & NM_SETTING_COMPARE_FLAG_INFERRABLE) && !(prop_flags & NM_SETTING_PARAM_INFERRABLE))
return FALSE;
if ((comp_flags & NM_SETTING_COMPARE_FLAG_IGNORE_REAPPLY_IMMEDIATELY) && !(prop_flags & NM_SETTING_PARAM_REAPPLY_IMMEDIATELY))
return FALSE;
if (prop_flags & NM_SETTING_PARAM_SECRET) {
NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE;
if (comp_flags & NM_SETTING_COMPARE_FLAG_IGNORE_SECRETS)
return FALSE;
if ( NM_IS_SETTING_VPN (setting)
&& g_strcmp0 (prop_name, NM_SETTING_VPN_SECRETS) == 0) {
/* FIXME: NMSettingVPN:NM_SETTING_VPN_SECRETS has NM_SETTING_PARAM_SECRET.
* nm_setting_get_secret_flags() quite possibly fails, but it might succeed if the
* setting accidently uses a key "secrets". */
return TRUE;
}
if (!nm_setting_get_secret_flags (setting, prop_name, &secret_flags, NULL))
g_return_val_if_reached (FALSE);
if ( (comp_flags & NM_SETTING_COMPARE_FLAG_IGNORE_AGENT_OWNED_SECRETS)
&& (secret_flags & NM_SETTING_SECRET_FLAG_AGENT_OWNED))
return FALSE;
if ( (comp_flags & NM_SETTING_COMPARE_FLAG_IGNORE_NOT_SAVED_SECRETS)
&& (secret_flags & NM_SETTING_SECRET_FLAG_NOT_SAVED))
return FALSE;
}
if ( (comp_flags & NM_SETTING_COMPARE_FLAG_IGNORE_ID)
&& NM_IS_SETTING_CONNECTION (setting)
&& !strcmp (prop_name, NM_SETTING_CONNECTION_ID))
return FALSE;
if ( (comp_flags & NM_SETTING_COMPARE_FLAG_IGNORE_TIMESTAMP)
&& NM_IS_SETTING_CONNECTION (setting)
&& !strcmp (prop_name, NM_SETTING_CONNECTION_TIMESTAMP))
return FALSE;
return TRUE;
}
/**
* 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)
{
GParamSpec **property_specs;
guint n_property_specs;
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);
}
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 (g_str_hash, g_str_equal, g_free, NULL);
results_created = TRUE;
}
/* And now all properties */
property_specs = g_object_class_list_properties (G_OBJECT_GET_CLASS (a), &n_property_specs);
for (i = 0; i < n_property_specs; i++) {
GParamSpec *prop_spec = property_specs[i];
NMSettingDiffResult r = NM_SETTING_DIFF_RESULT_UNKNOWN;
/* Handle compare flags */
if (!should_compare_prop (a, prop_spec->name, flags, prop_spec->flags))
continue;
if (strcmp (prop_spec->name, NM_SETTING_NAME) == 0)
continue;
compared_any = TRUE;
if (b) {
gboolean different;
different = !NM_SETTING_GET_CLASS (a)->compare_property (a, b, prop_spec, flags);
if (different) {
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 ((flags & NM_SETTING_COMPARE_FLAG_DIFF_RESULT_WITH_DEFAULT) == 0) {
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 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 {
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);
}
if (r != NM_SETTING_DIFF_RESULT_UNKNOWN) {
void *p;
diff_found = TRUE;
if (g_hash_table_lookup_extended (*results, prop_spec->name, NULL, &p)) {
if ((r & GPOINTER_TO_UINT (p)) != r)
g_hash_table_insert (*results, g_strdup (prop_spec->name), GUINT_TO_POINTER (r | GPOINTER_TO_UINT (p)));
} else
g_hash_table_insert (*results, g_strdup (prop_spec->name), GUINT_TO_POINTER (r));
}
}
g_free (property_specs);
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 if 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;
}
}
#define CMP_AND_RETURN(n_a, n_b, name) \
G_STMT_START { \
gboolean _is = (strcmp (n_a, ""name) == 0); \
\
if (_is || (strcmp (n_b, ""name) == 0)) \
return _is ? -1 : 1; \
} G_STMT_END
static int
_enumerate_values_sort (GParamSpec **p_a, GParamSpec **p_b, GType *p_type)
{
const char *n_a = (*p_a)->name;
const char *n_b = (*p_b)->name;
int c = strcmp (n_a, n_b);
if (c) {
if (*p_type == NM_TYPE_SETTING_CONNECTION) {
/* for [connection], report first id, uuid, type in that order. */
CMP_AND_RETURN (n_a, n_b, NM_SETTING_CONNECTION_ID);
CMP_AND_RETURN (n_a, n_b, NM_SETTING_CONNECTION_UUID);
CMP_AND_RETURN (n_a, n_b, NM_SETTING_CONNECTION_TYPE);
}
}
return c;
}
#undef CMP_AND_RETURN
/**
* 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)
{
GParamSpec **property_specs;
guint n_property_specs;
int i;
GType type;
g_return_if_fail (NM_IS_SETTING (setting));
g_return_if_fail (func != NULL);
property_specs = g_object_class_list_properties (G_OBJECT_GET_CLASS (setting), &n_property_specs);
/* sort the properties. This has an effect on the order in which keyfile
* prints them. */
type = G_OBJECT_TYPE (setting);
g_qsort_with_data (property_specs, n_property_specs, sizeof (gpointer),
(GCompareDataFunc) _enumerate_values_sort, &type);
for (i = 0; i < n_property_specs; i++) {
GParamSpec *prop_spec = property_specs[i];
GValue value = G_VALUE_INIT;
g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (prop_spec));
g_object_get_property (G_OBJECT (setting), prop_spec->name, &value);
func (setting, prop_spec->name, &value, prop_spec->flags, user_data);
g_value_unset (&value);
}
g_free (property_specs);
}
/**
* _nm_setting_clear_secrets:
* @setting: the #NMSetting
*
* Resets and clears any secrets in the setting. Secrets should be added to the
* setting only when needed, and cleared immediately after use to prevent
* leakage of information.
*
* Returns: %TRUE if the setting changed at all
**/
gboolean
_nm_setting_clear_secrets (NMSetting *setting)
{
GParamSpec **property_specs;
guint n_property_specs;
guint i;
gboolean changed = FALSE;
g_return_val_if_fail (NM_IS_SETTING (setting), FALSE);
property_specs = g_object_class_list_properties (G_OBJECT_GET_CLASS (setting), &n_property_specs);
for (i = 0; i < n_property_specs; i++) {
GParamSpec *prop_spec = property_specs[i];
if (prop_spec->flags & NM_SETTING_PARAM_SECRET) {
GValue value = G_VALUE_INIT;
g_value_init (&value, prop_spec->value_type);
g_object_get_property (G_OBJECT (setting), prop_spec->name, &value);
if (!g_param_value_defaults (prop_spec, &value)) {
g_param_value_set_default (prop_spec, &value);
g_object_set_property (G_OBJECT (setting), prop_spec->name, &value);
changed = TRUE;
}
g_value_unset (&value);
}
}
g_free (property_specs);
return changed;
}
static gboolean
clear_secrets_with_flags (NMSetting *setting,
GParamSpec *pspec,
NMSettingClearSecretsWithFlagsFn func,
gpointer user_data)
{
NMSettingSecretFlags flags = NM_SETTING_SECRET_FLAG_NONE;
gboolean changed = FALSE;
g_return_val_if_fail (!NM_IS_SETTING_VPN (setting), FALSE);
/* Clear the secret if the user function says to do so */
if (!nm_setting_get_secret_flags (setting, pspec->name, &flags, NULL))
g_return_val_if_reached (FALSE);
if (func (setting, pspec->name, flags, user_data) == TRUE) {
GValue value = G_VALUE_INIT;
g_value_init (&value, pspec->value_type);
g_object_get_property (G_OBJECT (setting), pspec->name, &value);
if (!g_param_value_defaults (pspec, &value)) {
g_param_value_set_default (pspec, &value);
g_object_set_property (G_OBJECT (setting), pspec->name, &value);
changed = TRUE;
}
g_value_unset (&value);
}
return changed;
}
/**
* _nm_setting_clear_secrets_with_flags:
* @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_with_flags (NMSetting *setting,
NMSettingClearSecretsWithFlagsFn func,
gpointer user_data)
{
GParamSpec **property_specs;
guint n_property_specs;
guint i;
gboolean changed = FALSE;
g_return_val_if_fail (setting, FALSE);
g_return_val_if_fail (NM_IS_SETTING (setting), FALSE);
g_return_val_if_fail (func != NULL, FALSE);
property_specs = g_object_class_list_properties (G_OBJECT_GET_CLASS (setting), &n_property_specs);
for (i = 0; i < n_property_specs; i++) {
if (property_specs[i]->flags & NM_SETTING_PARAM_SECRET) {
changed |= NM_SETTING_GET_CLASS (setting)->clear_secrets_with_flags (setting,
property_specs[i],
func,
user_data);
}
}
g_free (property_specs);
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 NMSettingProperty *property;
GParamSpec *prop_spec;
GValue prop_value = { 0, };
property = nm_setting_class_find_property (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);
g_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 gboolean
is_secret_prop (NMSetting *setting, const char *secret_name, GError **error)
{
const NMSettingProperty *property;
GParamSpec *pspec;
property = nm_setting_class_find_property (NM_SETTING_GET_CLASS (setting), secret_name);
if (!property) {
g_set_error_literal (error,
NM_CONNECTION_ERROR,
NM_CONNECTION_ERROR_PROPERTY_NOT_FOUND,
_("secret is not set"));
g_prefix_error (error, "%s.%s: ", nm_setting_get_name (setting), secret_name);
return FALSE;
}
pspec = property->param_spec;
if (!pspec || !(pspec->flags & NM_SETTING_PARAM_SECRET)) {
g_set_error_literal (error,
NM_CONNECTION_ERROR,
NM_CONNECTION_ERROR_PROPERTY_NOT_SECRET,
_("not a secret property"));
g_prefix_error (error, "%s.%s: ", nm_setting_get_name (setting), secret_name);
return FALSE;
}
return TRUE;
}
static gboolean
get_secret_flags (NMSetting *setting,
const char *secret_name,
gboolean verify_secret,
NMSettingSecretFlags *out_flags,
GError **error)
{
char *flags_prop;
NMSettingSecretFlags flags = NM_SETTING_SECRET_FLAG_NONE;
if (verify_secret && !is_secret_prop (setting, secret_name, error)) {
if (out_flags)
*out_flags = NM_SETTING_SECRET_FLAG_NONE;
return FALSE;
}
flags_prop = g_strdup_printf ("%s-flags", secret_name);
g_object_get (G_OBJECT (setting), flags_prop, &flags, NULL);
g_free (flags_prop);
if (out_flags)
*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, TRUE, out_flags, error);
}
static gboolean
set_secret_flags (NMSetting *setting,
const char *secret_name,
gboolean verify_secret,
NMSettingSecretFlags flags,
GError **error)
{
char *flags_prop;
if (verify_secret)
g_return_val_if_fail (is_secret_prop (setting, secret_name, error), FALSE);
flags_prop = g_strdup_printf ("%s-flags", secret_name);
g_object_set (G_OBJECT (setting), flags_prop, flags, NULL);
g_free (flags_prop);
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 (flags <= NM_SETTING_SECRET_FLAGS_ALL, FALSE);
return NM_SETTING_GET_CLASS (setting)->set_secret_flags (setting, secret_name, TRUE, flags, error);
}
/**
* nm_setting_to_string:
* @setting: the #NMSetting
*
* Convert the setting 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 (including secrets!), which the caller should
* free with g_free()
**/
char *
nm_setting_to_string (NMSetting *setting)
{
GString *string;
GParamSpec **property_specs;
guint n_property_specs;
guint i;
g_return_val_if_fail (NM_IS_SETTING (setting), NULL);
property_specs = g_object_class_list_properties (G_OBJECT_GET_CLASS (setting), &n_property_specs);
string = g_string_new (nm_setting_get_name (setting));
g_string_append_c (string, '\n');
for (i = 0; i < n_property_specs; i++) {
GParamSpec *prop_spec = property_specs[i];
GValue value = G_VALUE_INIT;
char *value_str;
gboolean is_default;
if (strcmp (prop_spec->name, NM_SETTING_NAME) == 0)
continue;
g_value_init (&value, prop_spec->value_type);
g_object_get_property (G_OBJECT (setting), prop_spec->name, &value);
value_str = g_strdup_value_contents (&value);
g_string_append_printf (string, "\t%s : %s", prop_spec->name, value_str);
g_free (value_str);
is_default = g_param_value_defaults (prop_spec, &value);
g_value_unset (&value);
g_string_append (string, " (");
g_string_append_c (string, 's');
if (is_default)
g_string_append_c (string, 'd');
g_string_append_c (string, ')');
g_string_append_c (string, '\n');
}
g_free (property_specs);
g_string_append_c (string, '\n');
return g_string_free (string, FALSE);
}
GVariant *
_nm_setting_get_deprecated_virtual_interface_name (NMSetting *setting,
NMConnection *connection,
const char *property)
{
NMSettingConnection *s_con;
s_con = nm_connection_get_setting_connection (connection);
g_return_val_if_fail (s_con != NULL, 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;
}
/*****************************************************************************/
static void
nm_setting_init (NMSetting *setting)
{
}
static void
constructed (GObject *object)
{
_ensure_setting_info (object, NM_SETTING_GET_PRIVATE (object));
G_OBJECT_CLASS (nm_setting_parent_class)->constructed (object);
}
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_class_init (NMSettingClass *setting_class)
{
GObjectClass *object_class = G_OBJECT_CLASS (setting_class);
g_type_class_add_private (setting_class, sizeof (NMSettingPrivate));
/* virtual methods */
object_class->constructed = constructed;
object_class->get_property = get_property;
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_with_flags = clear_secrets_with_flags;
/* Properties */
/**
* 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 "wireless" or "wired".
**/
g_object_class_install_property
(object_class, PROP_NAME,
g_param_spec_string (NM_SETTING_NAME, "", "",
NULL,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS));
}