summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Haller <thaller@redhat.com>2018-07-27 10:05:40 +0200
committerThomas Haller <thaller@redhat.com>2018-08-10 10:38:19 +0200
commit4e0f1b16b9472318b2e96b7357ce97ab58d7873f (patch)
treec26b483acfa47299b9ce7f38989f4fed2ac42fc1
parent3793804314c32b7199311736a8b1638c8f28d9fc (diff)
libnm: add generic-data for implementing NMSetting
Add a new way how NMSetting subclasses can be implemented. Currently, most NMSetting implementations realize all their properties via GObject properties. That has some downsides: - the biggest one, is the large effort to add new properties. Most of them are implemented on a one-by-one basis and they come with additional API (like native getter functions). It makes it cumbersome to add more properties. - for certain properties, it's hard to encode them entirely in a GObject property. That results in unusable API like NM_SETTING_IP_CONFIG_ADDRESSES, NM_SETTING_BOND_OPTIONS, NM_SETTING_USER_DATA. These complex valued properties only exist, because we currently always need GObject properties to even implement simple functionality. For example, nm_setting_duplicate() is entirely implemented via nm_setting_enumerate_values(), which can only iterate GObject properies. There is no reason why this is necessary. Note also how nmcli badly handles bond options and VPN data. That is only a shortcoming of nmcli and wouldn't need to be that way. But it happend, because we didn't keep an open mind that settings might be more than just accessing GObject properties. - a major point of NMSetting is to convert to/from a GVariant from the D-Bus API. As NMSetting needs to squeeze all values into the static GObject structure, there is no place to encode invalid or unknown properties. Optimally, _nm_setting_new_from_dbus() does not loose any information and a subsequent _nm_setting_to_dbus() can restore the original variant. That is interesting, because we want that an older libnm client can talk to a newer NetworkManager version. The client needs to handle unknown properties gracefully to stay forward compatible. However, it also should not just drop the properties on the floor. Note however, optimally we want that nm_setting_verify() still can reject settings that have such unknown/invalid values. So, it should be possible to create an NMSetting instance without error or loosing information. But verify() should be usable to identify such settings as invalid. They also have a few upsides. - libnm is heavily oriented around GObject. So, we generate our nm-settings manual based on the gtk-doc. Note however, how we fail to generate a useful manual for bond.options. Also note, that there is no reason we couldn't generate great documentation, even if the properties are not GObject properties. - GObject properties do give some functionality like meta-data, data binding and notification. However, the meta-data is not sufficient on its own. Note how keyfile and nmcli need extensive descriptor tables on top of GObject properties, to make this useful. Note how GObject notifications for NMSetting instances are usually not useful, aside for data binding like nmtui does. Also note how NMSettingBond already follows a different paradigm than using GObject properties. Nowdays, NMSettingBond is considered a mistake (related bug rh#1032808). Many ideas of NMSettingBond are flawed, like exposing an inferiour API that reduces everything to a string hash. Also, it only implemented the options hash inside NMSettingBond. That means, if we would consider this a good style, we would have to duplicate this approach in each new setting implementation. Add a new style to track data for NMSetting subclasses. It keeps an internal hash table with all GVariant properies. Also, the functionality is hooked into NMSetting base class, so all future subclasses that follow this way, can benefit from this. This approach has a few similiarties with NMSettingBond, but avoids its flaws. With this, we also no longer need GObject properties (if we would also implement generating useful documentation based on non-gkt-doc). They may be added as accessors if they are useful, but there is no need for them. Also, handling the properties as a hash of variants invites for a more generic approach when handling them. While we still could add accessors that operate on a one-by-one bases, this leads to a more generic usage where we apply common functionality to a set of properties. Also, this is for the moment entirely internal and an implementation detail. It's entirely up to the NMSetting subclass to make use of this new style. Also, there are little hooks for the subclass available. If they turn out to be necessary, they might be added. However, for the moment, the functionality is restricted to what is useful and necessary.
-rw-r--r--libnm-core/nm-core-internal.h52
-rw-r--r--libnm-core/nm-keyfile.c147
-rw-r--r--libnm-core/nm-setting-private.h11
-rw-r--r--libnm-core/nm-setting.c586
-rw-r--r--libnm-core/nm-setting.h8
5 files changed, 708 insertions, 96 deletions
diff --git a/libnm-core/nm-core-internal.h b/libnm-core/nm-core-internal.h
index 21c45ab425..7338cd27af 100644
--- a/libnm-core/nm-core-internal.h
+++ b/libnm-core/nm-core-internal.h
@@ -173,6 +173,34 @@ NMSettingPriority _nm_setting_get_setting_priority (NMSetting *setting);
gboolean _nm_setting_get_property (NMSetting *setting, const char *name, GValue *value);
+/*****************************************************************************/
+
+GHashTable *_nm_setting_gendata_hash (NMSetting *setting,
+ gboolean create_if_necessary);
+
+void _nm_setting_gendata_notify (NMSetting *setting,
+ gboolean keys_changed);
+
+guint _nm_setting_gendata_get_all (NMSetting *setting,
+ const char *const**out_names,
+ GVariant *const**out_values);
+
+gboolean _nm_setting_gendata_reset_from_hash (NMSetting *setting,
+ GHashTable *new);
+
+void _nm_setting_gendata_to_gvalue (NMSetting *setting,
+ GValue *value);
+
+GVariant *nm_setting_gendata_get (NMSetting *setting,
+ const char *name);
+
+const char *const*nm_setting_gendata_get_all_names (NMSetting *setting,
+ guint *out_len);
+
+GVariant *const*nm_setting_gendata_get_all_values (NMSetting *setting);
+
+/*****************************************************************************/
+
#define NM_UTILS_HWADDR_LEN_MAX_STR (NM_UTILS_HWADDR_LEN_MAX * 3)
guint8 *_nm_utils_hwaddr_aton (const char *asc, gpointer buffer, gsize buffer_length, gsize *out_length);
@@ -481,6 +509,8 @@ gboolean _nm_setting_sriov_sort_vfs (NMSettingSriov *setting);
/*****************************************************************************/
+typedef struct _NMSettInfoSetting NMSettInfoSetting;
+
typedef GVariant *(*NMSettingPropertyGetFunc) (NMSetting *setting,
const char *property);
typedef GVariant *(*NMSettingPropertySynthFunc) (NMSetting *setting,
@@ -516,14 +546,32 @@ typedef struct {
} NMSettInfoProperty;
typedef struct {
-} NMSettInfoSettDetail;
+ const GVariantType *(*get_variant_type) (const struct _NMSettInfoSetting *sett_info,
+ const char *name,
+ GError **error);
+} NMSettInfoSettGendata;
typedef struct {
+ /* if set, then this setting class has no own fields. Instead, its
+ * data is entirely based on gendata. Meaning: it tracks all data
+ * as native GVariants.
+ * It might have some GObject properties, but these are merely accessors
+ * to the underlying gendata.
+ *
+ * Note, that at the moment there are few hooks, to customize the behavior
+ * of the setting further. They are currently unneeded. This is desired,
+ * but could be added when there is a good reason.
+ *
+ * However, a few hooks there are... see NMSettInfoSettGendata. */
+ const NMSettInfoSettGendata *gendata_info;
+} NMSettInfoSettDetail;
+
+struct _NMSettInfoSetting {
NMSettingClass *setting_class;
const NMSettInfoProperty *property_infos;
guint property_infos_len;
NMSettInfoSettDetail detail;
-} NMSettInfoSetting;
+};
const NMSettInfoSetting *_nm_sett_info_setting_get (NMSettingClass *setting_class);
diff --git a/libnm-core/nm-keyfile.c b/libnm-core/nm-keyfile.c
index 6ea42d13bc..3f9d60bbb7 100644
--- a/libnm-core/nm-keyfile.c
+++ b/libnm-core/nm-keyfile.c
@@ -2684,6 +2684,8 @@ read_one_setting_value (NMSetting *setting,
static NMSetting *
read_setting (KeyfileReaderInfo *info)
{
+ const NMSettInfoSetting *sett_info;
+ gs_unref_object NMSetting *setting = NULL;
const char *alias;
GType type;
@@ -2692,22 +2694,92 @@ read_setting (KeyfileReaderInfo *info)
alias = info->group;
type = nm_setting_lookup_type (alias);
- if (type) {
- NMSetting *setting = g_object_new (type, NULL);
-
- info->setting = setting;
- nm_setting_enumerate_values (setting, read_one_setting_value, info);
- info->setting = NULL;
- if (!info->error)
- return setting;
-
- g_object_unref (setting);
- } else {
+ if (!type) {
handle_warn (info, NULL, NM_KEYFILE_WARN_SEVERITY_WARN,
_("invalid setting name '%s'"), info->group);
+ return NULL;
}
- return NULL;
+ setting = g_object_new (type, NULL);
+
+ info->setting = setting;
+
+ sett_info = _nm_sett_info_setting_get (NM_SETTING_GET_CLASS (setting));
+
+ if (sett_info->detail.gendata_info) {
+ gs_free char **keys = NULL;
+ gsize i, n_keys;
+
+ keys = g_key_file_get_keys (info->keyfile, info->group, &n_keys, NULL);
+ if (n_keys > 0) {
+ GHashTable *h = _nm_setting_gendata_hash (setting, TRUE);
+
+ nm_utils_strv_sort (keys, n_keys);
+ for (i = 0; i < n_keys; i++) {
+ gs_free char *key = keys[i];
+ gs_free_error GError *local = NULL;
+ const GVariantType *variant_type;
+ GVariant *variant;
+
+ /* a GKeyfile can return duplicate keys, there is just no API to make sense
+ * of them. Skip them. */
+ if ( i + 1 < n_keys
+ && nm_streq (key, keys[i + 1]))
+ continue;
+
+ /* currently, the API is very simple. The setting class just returns
+ * the desired variant type, and keyfile reader will try to parse
+ * it accordingly. Note, that this does currently not allow, that
+ * a particular key can contain different variant types, nor is it
+ * very flexible in general.
+ *
+ * We add flexibility when we need it. Keep it simple for now. */
+ variant_type = sett_info->detail.gendata_info->get_variant_type (sett_info,
+ key,
+ &local);
+ if (!variant_type) {
+ if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+ _("invalid key '%s.%s'"),
+ info->group, key))
+ break;
+ continue;
+ }
+
+ if (g_variant_type_equal (variant_type, G_VARIANT_TYPE_BOOLEAN)) {
+ gboolean v;
+
+ v = g_key_file_get_boolean (info->keyfile,
+ info->group,
+ key,
+ &local);
+ if (local) {
+ if (!handle_warn (info, key, NM_KEYFILE_WARN_SEVERITY_WARN,
+ _("key '%s.%s' is not boolean"),
+ info->group, key))
+ break;
+ continue;
+ }
+ variant = g_variant_new_boolean (v);
+ } else {
+ nm_assert_not_reached ();
+ continue;
+ }
+
+ g_hash_table_insert (h,
+ g_steal_pointer (&key),
+ g_variant_take_ref (variant));
+ }
+ for (; i < n_keys; i++)
+ g_free (keys[i]);
+ }
+ } else
+ nm_setting_enumerate_values (setting, read_one_setting_value, info);
+
+ info->setting = NULL;
+
+ if (info->error)
+ return NULL;
+ return g_steal_pointer (&setting);
}
static void
@@ -2998,6 +3070,8 @@ nm_keyfile_write (NMConnection *connection,
GError **error)
{
KeyfileWriterInfo info = { 0 };
+ gs_free NMSetting **settings = NULL;
+ guint i, length = 0;
g_return_val_if_fail (NM_IS_CONNECTION (connection), NULL);
g_return_val_if_fail (!error || !*error, NULL);
@@ -3010,12 +3084,59 @@ nm_keyfile_write (NMConnection *connection,
info.error = NULL;
info.handler = handler;
info.user_data = user_data;
- nm_connection_for_each_setting_value (connection, write_setting_value, &info);
+
+ settings = nm_connection_get_settings (connection, &length);
+ for (i = 0; i < length; i++) {
+ const NMSettInfoSetting *sett_info;
+ NMSetting *setting = settings[i];
+
+ sett_info = _nm_sett_info_setting_get (NM_SETTING_GET_CLASS (setting));
+
+ if (sett_info->detail.gendata_info) {
+ guint k, n_keys;
+ const char *const*keys;
+
+ nm_assert (!nm_keyfile_plugin_get_alias_for_setting_name (sett_info->setting_class->setting_info->setting_name));
+
+ n_keys = _nm_setting_gendata_get_all (setting, &keys, NULL);
+
+ if (n_keys > 0) {
+ const char *setting_name = sett_info->setting_class->setting_info->setting_name;
+ GHashTable *h = _nm_setting_gendata_hash (setting, FALSE);
+
+ for (k = 0; k < n_keys; k++) {
+ const char *key = keys[k];
+ GVariant *v;
+
+ v = g_hash_table_lookup (h, key);
+
+ if (g_variant_is_of_type (v, G_VARIANT_TYPE_BOOLEAN)) {
+ g_key_file_set_boolean (info.keyfile,
+ setting_name,
+ key,
+ g_variant_get_boolean (v));
+ } else {
+ /* BUG: The variant type is not implemented. Since the connection
+ * verifies, this can only mean we either wrongly didn't reject
+ * the connection as invalid, or we didn't properly implement the
+ * variant type. */
+ nm_assert_not_reached ();
+ continue;
+ }
+ }
+ }
+ } else
+ nm_setting_enumerate_values (setting, write_setting_value, &info);
+
+ if (info.error)
+ break;
+ }
if (info.error) {
g_propagate_error (error, info.error);
g_key_file_unref (info.keyfile);
return NULL;
}
+
return info.keyfile;
}
diff --git a/libnm-core/nm-setting-private.h b/libnm-core/nm-setting-private.h
index 99c7b69ccd..1e25226ede 100644
--- a/libnm-core/nm-setting-private.h
+++ b/libnm-core/nm-setting-private.h
@@ -80,6 +80,8 @@ gboolean _nm_setting_clear_secrets_with_flags (NMSetting *setting,
*/
#define NM_SETTING_PARAM_REAPPLY_IMMEDIATELY (1 << (6 + G_PARAM_USER_SHIFT))
+#define NM_SETTING_PARAM_GENDATA_BACKED (1 << (7 + G_PARAM_USER_SHIFT))
+
GVariant *_nm_setting_get_deprecated_virtual_interface_name (NMSetting *setting,
NMConnection *connection,
const char *property);
@@ -127,6 +129,15 @@ _nm_setting_class_commit (NMSettingClass *setting_class,
_nm_setting_class_commit_full (setting_class, meta_type, NULL, NULL);
}
+#define NM_SETT_INFO_SETT_GENDATA(...) \
+ ({ \
+ static const NMSettInfoSettGendata _g = { \
+ __VA_ARGS__ \
+ }; \
+ \
+ &_g; \
+ })
+
#define NM_SETT_INFO_SETT_DETAIL(...) \
(&((const NMSettInfoSettDetail) { \
__VA_ARGS__ \
diff --git a/libnm-core/nm-setting.c b/libnm-core/nm-setting.c
index 2e884de7a0..53a2a22a16 100644
--- a/libnm-core/nm-setting.c
+++ b/libnm-core/nm-setting.c
@@ -56,6 +56,12 @@
/*****************************************************************************/
typedef struct {
+ GHashTable *hash;
+ const char **names;
+ GVariant **values;
+} GenData;
+
+typedef struct {
const char *name;
GType type;
NMSettingPriority priority;
@@ -69,7 +75,7 @@ enum {
};
typedef struct {
- int dummy;
+ GenData *gendata;
} NMSettingPrivate;
G_DEFINE_ABSTRACT_TYPE (NMSetting, nm_setting, G_TYPE_OBJECT)
@@ -78,6 +84,10 @@ G_DEFINE_ABSTRACT_TYPE (NMSetting, nm_setting, G_TYPE_OBJECT)
/*****************************************************************************/
+static GenData *_gendata_hash (NMSetting *setting, gboolean create_if_necessary);
+
+/*****************************************************************************/
+
static NMSettingPriority
_get_base_type_priority (const NMMetaSettingInfo *setting_info,
GType gtype)
@@ -623,15 +633,27 @@ set_property_from_dbus (const NMSettInfoProperty *property,
GVariant *
_nm_setting_to_dbus (NMSetting *setting, NMConnection *connection, NMConnectionSerializationFlags flags)
{
+ NMSettingPrivate *priv;
GVariantBuilder builder;
GVariant *dbus_value;
const NMSettInfoSetting *sett_info;
- guint i;
+ 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_sett_info_setting_get (NM_SETTING_GET_CLASS (setting));
for (i = 0; i < sett_info->property_infos_len; i++) {
const NMSettInfoProperty *property = &sett_info->property_infos[i];
@@ -647,6 +669,9 @@ _nm_setting_to_dbus (NMSetting *setting, NMConnection *connection, NMConnectionS
if (!(prop_spec->flags & G_PARAM_WRITABLE))
continue;
+ if (NM_FLAGS_ANY (prop_spec->flags, NM_SETTING_PARAM_GENDATA_BACKED))
+ continue;
+
if ( (prop_spec->flags & NM_SETTING_PARAM_LEGACY)
&& !_nm_utils_is_manager_process)
continue;
@@ -751,6 +776,26 @@ _nm_setting_new_from_dbus (GType setting_type,
}
sett_info = _nm_sett_info_setting_get (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);
+ }
+
+ _nm_setting_gendata_notify (setting, TRUE);
+ return g_steal_pointer (&setting);
+ }
+
for (i = 0; i < sett_info->property_infos_len; i++) {
const NMSettInfoProperty *property = &sett_info->property_infos[i];
gs_unref_variant GVariant *value = NULL;
@@ -888,14 +933,32 @@ nm_setting_get_dbus_property_type (NMSetting *setting,
gboolean
_nm_setting_get_property (NMSetting *setting, const char *property_name, GValue *value)
{
+ const NMSettInfoSetting *sett_info;
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);
+ sett_info = _nm_sett_info_setting_get (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;
+ }
+
+ prop_spec = g_object_class_find_property (G_OBJECT_GET_CLASS (setting), property_name);
if (!prop_spec) {
g_value_unset (value);
return FALSE;
@@ -929,16 +992,37 @@ duplicate_setting (NMSetting *setting,
NMSetting *
nm_setting_duplicate (NMSetting *setting)
{
+ const NMSettInfoSetting *sett_info;
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);
+ sett_info = _nm_sett_info_setting_get (NM_SETTING_GET_CLASS (setting));
+ if (sett_info->detail.gendata_info) {
+ GenData *gendata = _gendata_hash (setting, FALSE);
+
+ if ( gendata
+ && g_hash_table_size (gendata->hash) > 0) {
+ GHashTableIter iter;
+ GHashTable *h = _gendata_hash (NM_SETTING (dup), 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));
+ }
+ }
+ } else {
+ g_object_freeze_notify (dup);
+ nm_setting_enumerate_values (setting, duplicate_setting, dup);
+ g_object_thaw_notify (dup);
+ }
return NM_SETTING (dup);
}
@@ -1118,6 +1202,7 @@ nm_setting_compare (NMSetting *a,
NMSetting *b,
NMSettingCompareFlags flags)
{
+ const NMSettInfoSetting *sett_info;
GParamSpec **property_specs;
guint n_property_specs;
int same = TRUE;
@@ -1130,6 +1215,18 @@ nm_setting_compare (NMSetting *a,
if (G_OBJECT_TYPE (a) != G_OBJECT_TYPE (b))
return FALSE;
+ sett_info = _nm_sett_info_setting_get (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);
+ }
+
/* 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++) {
@@ -1215,6 +1312,21 @@ should_compare_prop (NMSetting *setting,
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
@@ -1243,8 +1355,7 @@ nm_setting_diff (NMSetting *a,
gboolean invert_results,
GHashTable **results)
{
- GParamSpec **property_specs;
- guint n_property_specs;
+ const NMSettInfoSetting *sett_info;
guint i;
NMSettingDiffResult a_result = NM_SETTING_DIFF_RESULT_IN_A;
NMSettingDiffResult b_result = NM_SETTING_DIFF_RESULT_IN_B;
@@ -1288,78 +1399,117 @@ nm_setting_diff (NMSetting *a,
results_created = TRUE;
}
- /* And now all properties */
- property_specs = g_object_class_list_properties (G_OBJECT_GET_CLASS (a), &n_property_specs);
+ sett_info = _nm_sett_info_setting_get (NM_SETTING_GET_CLASS (a));
- for (i = 0; i < n_property_specs; i++) {
- GParamSpec *prop_spec = property_specs[i];
- NMSettingDiffResult r = NM_SETTING_DIFF_RESULT_UNKNOWN;
+ 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 = b_gendata ? g_hash_table_lookup (b_gendata->hash, key) : NULL;
+ 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 = a_gendata ? g_hash_table_lookup (a_gendata->hash, key) : NULL;
+ compared_any = TRUE;
+ if ( !val2
+ || !g_variant_equal (val, val2)) {
+ diff_found = TRUE;
+ _setting_diff_add_result (*results, key, b_result);
+ }
+ }
+ }
+ } else {
+ gs_free GParamSpec **property_specs = NULL;
+ guint n_property_specs;
- /* 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;
+ property_specs = g_object_class_list_properties (G_OBJECT_GET_CLASS (a), &n_property_specs);
- compared_any = TRUE;
+ for (i = 0; i < n_property_specs; i++) {
+ GParamSpec *prop_spec = property_specs[i];
+ NMSettingDiffResult r = NM_SETTING_DIFF_RESULT_UNKNOWN;
- if (b) {
- gboolean different;
+ /* 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;
- different = !NM_SETTING_GET_CLASS (a)->compare_property (a, b, prop_spec, flags);
- if (different) {
- gboolean a_is_default, b_is_default;
+ 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);
- 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);
+ 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 ((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));
+ if (r != NM_SETTING_DIFF_RESULT_UNKNOWN) {
+ diff_found = TRUE;
+ _setting_diff_add_result (*results, prop_spec->name, r);
+ }
}
}
- g_free (property_specs);
if (!compared_any && !b) {
/* special case: the setting has no properties, and the opposite
@@ -1370,7 +1520,7 @@ nm_setting_diff (NMSetting *a,
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. */
+ * have allocated a new @results hash, and return it to the caller. */
return FALSE;
} else {
if (results_created) {
@@ -1426,23 +1576,57 @@ nm_setting_enumerate_values (NMSetting *setting,
NMSettingValueIterFn func,
gpointer user_data)
{
+ const NMSettInfoSetting *sett_info;
GParamSpec **property_specs;
- guint n_property_specs;
- int i;
+ guint n_properties;
+ guint 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);
+ sett_info = _nm_sett_info_setting_get (NM_SETTING_GET_CLASS (setting));
+
+ if (sett_info->detail.gendata_info) {
+ const char *const*names;
+
+ /* 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;
+ }
+
+ property_specs = g_object_class_list_properties (G_OBJECT_GET_CLASS (setting), &n_properties);
/* 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),
+ g_qsort_with_data (property_specs, n_properties, sizeof (gpointer),
(GCompareDataFunc) _enumerate_values_sort, &type);
- for (i = 0; i < n_property_specs; i++) {
+ for (i = 0; i < n_properties; i++) {
GParamSpec *prop_spec = property_specs[i];
GValue value = G_VALUE_INIT;
@@ -1468,7 +1652,7 @@ nm_setting_enumerate_values (NMSetting *setting,
gboolean
_nm_setting_clear_secrets (NMSetting *setting)
{
- GParamSpec **property_specs;
+ gs_free GParamSpec **property_specs = NULL;
guint n_property_specs;
guint i;
gboolean changed = FALSE;
@@ -1476,7 +1660,6 @@ _nm_setting_clear_secrets (NMSetting *setting)
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];
@@ -1493,9 +1676,6 @@ _nm_setting_clear_secrets (NMSetting *setting)
g_value_unset (&value);
}
}
-
- g_free (property_specs);
-
return changed;
}
@@ -1546,7 +1726,7 @@ _nm_setting_clear_secrets_with_flags (NMSetting *setting,
NMSettingClearSecretsWithFlagsFn func,
gpointer user_data)
{
- GParamSpec **property_specs;
+ gs_free GParamSpec **property_specs = NULL;
guint n_property_specs;
guint i;
gboolean changed = FALSE;
@@ -1564,8 +1744,6 @@ _nm_setting_clear_secrets_with_flags (NMSetting *setting,
user_data);
}
}
-
- g_free (property_specs);
return changed;
}
@@ -1870,6 +2048,240 @@ _nm_setting_get_deprecated_virtual_interface_name (NMSetting *setting,
/*****************************************************************************/
+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)
+ return;
+
+ 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);
+ }
+}
+
+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 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
nm_setting_init (NMSetting *setting)
{
@@ -1892,6 +2304,21 @@ get_property (GObject *object, guint prop_id,
}
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);
@@ -1914,6 +2341,7 @@ nm_setting_class_init (NMSettingClass *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;
diff --git a/libnm-core/nm-setting.h b/libnm-core/nm-setting.h
index d06527cdd7..a7a0f81f40 100644
--- a/libnm-core/nm-setting.h
+++ b/libnm-core/nm-setting.h
@@ -287,7 +287,8 @@ void nm_setting_enumerate_values (NMSetting *setting,
char *nm_setting_to_string (NMSetting *setting);
-/* Secrets */
+/*****************************************************************************/
+
gboolean nm_setting_get_secret_flags (NMSetting *setting,
const char *secret_name,
NMSettingSecretFlags *out_flags,
@@ -298,10 +299,13 @@ gboolean nm_setting_set_secret_flags (NMSetting *setting,
NMSettingSecretFlags flags,
GError **error);
-/* Properties */
+/*****************************************************************************/
+
const GVariantType *nm_setting_get_dbus_property_type (NMSetting *setting,
const char *property_name);
+/*****************************************************************************/
+
G_END_DECLS
#endif /* __NM_SETTING_H__ */