2020-12-23 22:21:36 +01:00
|
|
|
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
2017-03-24 12:41:04 +01:00
|
|
|
/*
|
2019-10-01 09:20:35 +02:00
|
|
|
* Copyright (C) 2017 Red Hat, Inc.
|
2017-03-24 12:41:04 +01:00
|
|
|
*/
|
|
|
|
|
|
2021-02-12 15:01:09 +01:00
|
|
|
#include "libnm-core-impl/nm-default-libnm-core.h"
|
2017-03-24 12:41:04 +01:00
|
|
|
|
|
|
|
|
#include "nm-setting-user.h"
|
|
|
|
|
|
|
|
|
|
#include "nm-setting.h"
|
|
|
|
|
#include "nm-setting-private.h"
|
|
|
|
|
#include "nm-utils-private.h"
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* SECTION:nm-setting-user
|
|
|
|
|
* @short_description: Describes user properties
|
|
|
|
|
*
|
|
|
|
|
* The #NMSettingUser object is a #NMSetting subclass that allow to attach
|
|
|
|
|
* arbitrary user data to #NMConnection objects.
|
|
|
|
|
**/
|
|
|
|
|
|
2017-05-04 14:44:18 +02:00
|
|
|
#define MAX_NUM_KEYS 256
|
|
|
|
|
|
2017-03-24 12:41:04 +01:00
|
|
|
/*****************************************************************************/
|
|
|
|
|
|
|
|
|
|
NM_GOBJECT_PROPERTIES_DEFINE(NMSettingUser, PROP_DATA, );
|
|
|
|
|
|
|
|
|
|
typedef struct {
|
2021-11-09 13:28:54 +01:00
|
|
|
GHashTable *data;
|
|
|
|
|
GHashTable *data_invalid;
|
2017-03-24 12:41:04 +01:00
|
|
|
const char **keys;
|
|
|
|
|
} NMSettingUserPrivate;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* NMSettingUser:
|
|
|
|
|
*
|
|
|
|
|
* General User Profile Settings
|
2022-11-07 21:13:28 +01:00
|
|
|
*
|
|
|
|
|
* Since: 1.8
|
2017-03-24 12:41:04 +01:00
|
|
|
*/
|
|
|
|
|
struct _NMSettingUser {
|
|
|
|
|
NMSetting parent;
|
|
|
|
|
NMSettingUserPrivate _priv;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct _NMSettingUserClass {
|
|
|
|
|
NMSettingClass parent;
|
|
|
|
|
};
|
|
|
|
|
|
libnm: use NMMetaSettingInfo for tracking setting priority
Previously, each (non abstract) NMSetting class had to register
its name and priority via _nm_register_setting().
Note, that libnm-core.la already links against "nm-meta-setting.c",
which also redundantly keeps track of the settings name and gtype
as well.
Re-use NMMetaSettingInfo also in libnm-core.la, to track this meta
data.
The goal is to get rid of private data structures that track
meta data about NMSetting classes. In this case, "registered_settings"
hash. Instead, we should have one place where all this meta data
is tracked. This was, it is also accessible as internal API,
which can be useful (for keyfile).
Note that NMSettingClass has some overlap with NMMetaSettingInfo.
One difference is, that NMMetaSettingInfo is const, while NMSettingClass
is only initialized during the class_init() method. Appart from that,
it's mostly a matter of taste, whether we attach meta data to
NMSettingClass, to NMMetaSettingInfo, or to a static-array indexed
by NMMetaSettingType.
Note, that previously, _nm_register_setting() was private API. That
means, no user could subclass a functioning NMSetting instance. The same
is still true: NMMetaSettingInfo is internal API and users cannot access
it to create their own NMSetting subclasses. But that is almost desired.
libnm is not designed, to be extensible via subclassing, nor is it
clear why that would be a useful thing to do. One day, we should remove
the NMSetting and NMSettingClass definitions from public headers. Their
only use is subclassing the types, which however does not work.
While libnm-core was linking already against nm-meta-setting.c,
nm_meta_setting_infos was unreferenced. So, this change increases
the binary size of libnm and NetworkManager (1032 bytes). Note however
that roughly the same information was previously allocated at runtime.
2018-07-27 14:08:14 +02:00
|
|
|
G_DEFINE_TYPE(NMSettingUser, nm_setting_user, NM_TYPE_SETTING)
|
2017-03-24 12:41:04 +01:00
|
|
|
|
|
|
|
|
#define NM_SETTING_USER_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMSettingUser, NM_IS_SETTING_USER)
|
|
|
|
|
|
|
|
|
|
/*****************************************************************************/
|
|
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
|
_key_char_is_regular(char ch)
|
|
|
|
|
{
|
|
|
|
|
/* allow words of printable characters, plus some
|
|
|
|
|
* special characters, for example to support base64 encoding. */
|
|
|
|
|
return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9')
|
|
|
|
|
|| NM_IN_SET(ch, '-', '_', '+', '/', '=');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* nm_setting_user_check_key:
|
|
|
|
|
* @key: the key to check
|
|
|
|
|
* @error: a #GError, %NULL to ignore.
|
|
|
|
|
*
|
|
|
|
|
* Checks whether @key is a valid user data key. This means,
|
|
|
|
|
* key is not %NULL, not too large and valid ASCII. Also,
|
|
|
|
|
* only digits and numbers are allowed with a few special
|
|
|
|
|
* characters. The key must contain at least one '.' and
|
|
|
|
|
* look like a fully qualified DNS name.
|
|
|
|
|
*
|
|
|
|
|
* Since: 1.8
|
|
|
|
|
*
|
|
|
|
|
* Returns: %TRUE if @key is a valid user data key.
|
|
|
|
|
*/
|
|
|
|
|
gboolean
|
|
|
|
|
nm_setting_user_check_key(const char *key, GError **error)
|
|
|
|
|
{
|
|
|
|
|
gsize len;
|
|
|
|
|
gboolean has_dot;
|
|
|
|
|
char ch;
|
2020-09-28 16:03:33 +02:00
|
|
|
|
2017-03-24 12:41:04 +01:00
|
|
|
g_return_val_if_fail(!error || !*error, FALSE);
|
2020-09-28 16:03:33 +02:00
|
|
|
|
2017-03-24 12:41:04 +01:00
|
|
|
if (!key || !key[0]) {
|
|
|
|
|
g_set_error_literal(error,
|
|
|
|
|
NM_CONNECTION_ERROR,
|
|
|
|
|
NM_CONNECTION_ERROR_INVALID_PROPERTY,
|
|
|
|
|
_("missing key"));
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
len = strlen(key);
|
|
|
|
|
if (len > 255) {
|
|
|
|
|
g_set_error_literal(error,
|
|
|
|
|
NM_CONNECTION_ERROR,
|
|
|
|
|
NM_CONNECTION_ERROR_INVALID_PROPERTY,
|
|
|
|
|
_("key is too long"));
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
if (!g_utf8_validate(key, len, NULL)) {
|
|
|
|
|
g_set_error_literal(error,
|
|
|
|
|
NM_CONNECTION_ERROR,
|
|
|
|
|
NM_CONNECTION_ERROR_INVALID_PROPERTY,
|
|
|
|
|
_("key must be UTF8"));
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
2020-09-28 16:03:33 +02:00
|
|
|
|
2017-03-24 12:41:04 +01:00
|
|
|
has_dot = FALSE;
|
|
|
|
|
while (TRUE) {
|
|
|
|
|
ch = (key++)[0];
|
2020-09-28 16:03:33 +02:00
|
|
|
|
2017-03-24 12:41:04 +01:00
|
|
|
/* Allow something that looks like a FQN, separating namespaces by a single '.'
|
|
|
|
|
* We want to print the keys nicely in nmcli requiring escaping.
|
|
|
|
|
*
|
|
|
|
|
* If a user really has to encode special values in the name, he may base64 encode it. */
|
|
|
|
|
|
|
|
|
|
if (!_key_char_is_regular(ch))
|
|
|
|
|
break;
|
2020-09-28 16:03:33 +02:00
|
|
|
|
2017-03-24 12:41:04 +01:00
|
|
|
while (_key_char_is_regular(key[0]))
|
|
|
|
|
key++;
|
2020-09-28 16:03:33 +02:00
|
|
|
|
2017-03-24 12:41:04 +01:00
|
|
|
ch = key[0];
|
|
|
|
|
if (ch == '\0') {
|
|
|
|
|
if (!has_dot) {
|
|
|
|
|
g_set_error_literal(error,
|
|
|
|
|
NM_CONNECTION_ERROR,
|
|
|
|
|
NM_CONNECTION_ERROR_INVALID_PROPERTY,
|
|
|
|
|
_("key requires a '.' for a namespace"));
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
2020-09-28 16:03:33 +02:00
|
|
|
|
2017-03-24 12:41:04 +01:00
|
|
|
if (ch != '.')
|
|
|
|
|
break;
|
2020-09-28 16:03:33 +02:00
|
|
|
|
2017-03-24 12:41:04 +01:00
|
|
|
has_dot = TRUE;
|
|
|
|
|
ch = (++key)[0];
|
|
|
|
|
if (ch == '.') {
|
|
|
|
|
g_set_error_literal(error,
|
|
|
|
|
NM_CONNECTION_ERROR,
|
|
|
|
|
NM_CONNECTION_ERROR_INVALID_PROPERTY,
|
|
|
|
|
_("key cannot contain \"..\""));
|
|
|
|
|
return FALSE;
|
2020-09-28 16:03:33 +02:00
|
|
|
}
|
2017-03-24 12:41:04 +01:00
|
|
|
}
|
|
|
|
|
g_set_error_literal(error,
|
|
|
|
|
NM_CONNECTION_ERROR,
|
|
|
|
|
NM_CONNECTION_ERROR_INVALID_PROPERTY,
|
|
|
|
|
_("key contains invalid characters"));
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* nm_setting_user_check_val:
|
|
|
|
|
* @val: the value to check
|
|
|
|
|
* @error: a #GError, %NULL to ignore.
|
|
|
|
|
*
|
|
|
|
|
* Checks whether @val is a valid user data value. This means,
|
|
|
|
|
* value is not %NULL, not too large and valid UTF-8.
|
|
|
|
|
*
|
|
|
|
|
* Since: 1.8
|
|
|
|
|
*
|
|
|
|
|
* Returns: %TRUE if @val is a valid user data value.
|
|
|
|
|
*/
|
|
|
|
|
gboolean
|
|
|
|
|
nm_setting_user_check_val(const char *val, GError **error)
|
|
|
|
|
{
|
|
|
|
|
gsize len;
|
2020-09-28 16:03:33 +02:00
|
|
|
|
2017-03-24 12:41:04 +01:00
|
|
|
g_return_val_if_fail(!error || !*error, FALSE);
|
2020-09-28 16:03:33 +02:00
|
|
|
|
2017-03-24 12:41:04 +01:00
|
|
|
if (!val) {
|
|
|
|
|
g_set_error_literal(error,
|
|
|
|
|
NM_CONNECTION_ERROR,
|
|
|
|
|
NM_CONNECTION_ERROR_INVALID_PROPERTY,
|
|
|
|
|
_("value is missing"));
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
2020-09-28 16:03:33 +02:00
|
|
|
|
2017-03-24 12:41:04 +01:00
|
|
|
len = strlen(val);
|
|
|
|
|
if (len > 8 * 1024) {
|
|
|
|
|
g_set_error_literal(error,
|
|
|
|
|
NM_CONNECTION_ERROR,
|
|
|
|
|
NM_CONNECTION_ERROR_INVALID_PROPERTY,
|
|
|
|
|
_("value is too large"));
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
2020-09-28 16:03:33 +02:00
|
|
|
|
2017-03-24 12:41:04 +01:00
|
|
|
if (!g_utf8_validate(val, len, NULL)) {
|
|
|
|
|
g_set_error_literal(error,
|
|
|
|
|
NM_CONNECTION_ERROR,
|
|
|
|
|
NM_CONNECTION_ERROR_INVALID_PROPERTY,
|
|
|
|
|
_("value is not valid UTF8"));
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
2020-09-28 16:03:33 +02:00
|
|
|
|
2017-03-24 12:41:04 +01:00
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*****************************************************************************/
|
|
|
|
|
|
|
|
|
|
static GHashTable *
|
|
|
|
|
_create_data_hash(void)
|
|
|
|
|
{
|
2017-11-15 16:06:43 +01:00
|
|
|
return g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, g_free);
|
2017-03-24 12:41:04 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* nm_setting_user_get_keys:
|
|
|
|
|
* @setting: the #NMSettingUser
|
|
|
|
|
* @out_len: (out): the length of the returned array
|
|
|
|
|
*
|
|
|
|
|
* Returns: (array length=out_len) (transfer none): a
|
|
|
|
|
* %NULL-terminated array containing each key from the table.
|
2022-11-07 21:13:28 +01:00
|
|
|
*
|
|
|
|
|
* Since: 1.8
|
2017-03-24 12:41:04 +01:00
|
|
|
**/
|
|
|
|
|
const char *const *
|
|
|
|
|
nm_setting_user_get_keys(NMSettingUser *setting, guint *out_len)
|
|
|
|
|
{
|
2021-11-09 13:28:54 +01:00
|
|
|
NMSettingUser *self = setting;
|
2017-03-24 12:41:04 +01:00
|
|
|
NMSettingUserPrivate *priv;
|
|
|
|
|
|
|
|
|
|
g_return_val_if_fail(NM_IS_SETTING_USER(self), NULL);
|
|
|
|
|
|
|
|
|
|
priv = NM_SETTING_USER_GET_PRIVATE(self);
|
|
|
|
|
|
|
|
|
|
if (priv->keys) {
|
|
|
|
|
NM_SET_OUT(out_len, g_hash_table_size(priv->data));
|
|
|
|
|
return priv->keys;
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-30 09:15:38 +02:00
|
|
|
priv->keys = nm_strdict_get_keys(priv->data, TRUE, out_len);
|
2017-03-24 12:41:04 +01:00
|
|
|
|
2018-03-25 14:16:17 +02:00
|
|
|
/* don't return %NULL, but hijack the @keys fields as a pseudo
|
|
|
|
|
* empty strv array. */
|
|
|
|
|
return priv->keys ?: ((const char **) &priv->keys);
|
2017-03-24 12:41:04 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*****************************************************************************/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* nm_setting_user_get_data:
|
|
|
|
|
* @setting: the #NMSettingUser instance
|
|
|
|
|
* @key: the key to lookup
|
|
|
|
|
*
|
|
|
|
|
* Since: 1.8
|
|
|
|
|
*
|
|
|
|
|
* Returns: (transfer none): the value associated with @key or %NULL if no such
|
|
|
|
|
* value exists.
|
|
|
|
|
*/
|
|
|
|
|
const char *
|
|
|
|
|
nm_setting_user_get_data(NMSettingUser *setting, const char *key)
|
|
|
|
|
{
|
2021-07-26 19:14:12 +02:00
|
|
|
g_return_val_if_fail(NM_IS_SETTING_USER(setting), NULL);
|
2017-03-24 12:41:04 +01:00
|
|
|
g_return_val_if_fail(key, NULL);
|
|
|
|
|
|
2021-07-26 19:14:12 +02:00
|
|
|
return nm_g_hash_table_lookup(NM_SETTING_USER_GET_PRIVATE(setting)->data, key);
|
2017-03-24 12:41:04 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* nm_setting_user_set_data:
|
|
|
|
|
* @setting: the #NMSettingUser instance
|
|
|
|
|
* @key: the key to set
|
2023-03-01 01:21:38 +01:00
|
|
|
* @val: (nullable): the value to set or %NULL to clear a key.
|
|
|
|
|
* @error: optional error argument
|
2017-03-24 12:41:04 +01:00
|
|
|
*
|
|
|
|
|
* Since: 1.8
|
|
|
|
|
*
|
|
|
|
|
* Returns: %TRUE if the operation was successful. The operation
|
|
|
|
|
* can fail if @key or @val are not valid strings according
|
|
|
|
|
* to nm_setting_user_check_key() and nm_setting_user_check_val().
|
|
|
|
|
*/
|
|
|
|
|
gboolean
|
|
|
|
|
nm_setting_user_set_data(NMSettingUser *setting, const char *key, const char *val, GError **error)
|
|
|
|
|
{
|
2021-11-09 13:28:54 +01:00
|
|
|
NMSettingUser *self = setting;
|
2017-03-24 12:41:04 +01:00
|
|
|
NMSettingUserPrivate *priv;
|
2017-05-04 14:44:18 +02:00
|
|
|
gboolean changed = FALSE;
|
2020-09-28 16:03:33 +02:00
|
|
|
|
2020-11-02 18:27:50 +01:00
|
|
|
g_return_val_if_fail(NM_IS_SETTING_USER(self), FALSE);
|
2017-03-24 12:41:04 +01:00
|
|
|
g_return_val_if_fail(!error || !*error, FALSE);
|
2020-09-28 16:03:33 +02:00
|
|
|
|
2017-03-24 12:41:04 +01:00
|
|
|
if (!nm_setting_user_check_key(key, error))
|
|
|
|
|
return FALSE;
|
2020-09-28 16:03:33 +02:00
|
|
|
|
2017-05-04 14:44:18 +02:00
|
|
|
if (val && !nm_setting_user_check_val(val, error))
|
|
|
|
|
return FALSE;
|
2020-09-28 16:03:33 +02:00
|
|
|
|
2017-03-24 12:41:04 +01:00
|
|
|
priv = NM_SETTING_USER_GET_PRIVATE(self);
|
2020-09-28 16:03:33 +02:00
|
|
|
|
2017-03-24 12:41:04 +01:00
|
|
|
if (!val) {
|
|
|
|
|
if (priv->data && g_hash_table_remove(priv->data, key)) {
|
|
|
|
|
nm_clear_g_free(&priv->keys);
|
2017-05-04 14:44:18 +02:00
|
|
|
changed = TRUE;
|
2017-03-24 12:41:04 +01:00
|
|
|
}
|
2017-05-04 14:44:18 +02:00
|
|
|
goto out;
|
2017-03-24 12:41:04 +01:00
|
|
|
}
|
2020-09-28 16:03:33 +02:00
|
|
|
|
2017-05-04 14:44:18 +02:00
|
|
|
if (priv->data) {
|
2017-03-24 12:41:04 +01:00
|
|
|
const char *key2, *val2;
|
2020-09-28 16:03:33 +02:00
|
|
|
|
2017-03-24 12:41:04 +01:00
|
|
|
if (g_hash_table_lookup_extended(priv->data, key, (gpointer *) &key2, (gpointer *) &val2)) {
|
|
|
|
|
if (nm_streq(val, val2))
|
2017-05-04 14:44:18 +02:00
|
|
|
goto out;
|
|
|
|
|
} else {
|
|
|
|
|
if (g_hash_table_size(priv->data) >= MAX_NUM_KEYS) {
|
|
|
|
|
/* limit the number of valid keys */
|
|
|
|
|
g_set_error_literal(error,
|
|
|
|
|
NM_CONNECTION_ERROR,
|
|
|
|
|
NM_CONNECTION_ERROR_INVALID_PROPERTY,
|
|
|
|
|
_("maximum number of user data entries reached"));
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
2020-09-28 16:03:33 +02:00
|
|
|
|
2017-03-24 12:41:04 +01:00
|
|
|
nm_clear_g_free(&priv->keys);
|
2017-05-04 14:44:18 +02:00
|
|
|
}
|
|
|
|
|
} else
|
|
|
|
|
priv->data = _create_data_hash();
|
2020-09-28 16:03:33 +02:00
|
|
|
|
2017-03-24 12:41:04 +01:00
|
|
|
g_hash_table_insert(priv->data, g_strdup(key), g_strdup(val));
|
2017-05-04 14:44:18 +02:00
|
|
|
changed = TRUE;
|
|
|
|
|
|
|
|
|
|
out:
|
|
|
|
|
if (priv->data_invalid) {
|
|
|
|
|
/* setting a value purges all invalid values that were set
|
|
|
|
|
* via GObject property. */
|
|
|
|
|
changed = TRUE;
|
all: use nm_clear_pointer() instead of g_clear_pointer()
g_clear_pointer() would always cast the destroy notify function
pointer to GDestroyNotify. That means, it lost some type safety, like
GPtrArray *ptr_arr = ...
g_clear_pointer (&ptr_arr, g_array_unref);
Since glib 2.58 ([1]), g_clear_pointer() is also more type safe. But
this is not used by NetworkManager, because we don't set
GLIB_VERSION_MIN_REQUIRED to 2.58.
[1] https://gitlab.gnome.org/GNOME/glib/-/commit/f9a9902aac826ab4aecc25f6eb533a418a4fa559
We have nm_clear_pointer() to avoid this issue for a long time (pre
1.12.0). Possibly we should redefine in our source tree g_clear_pointer()
as nm_clear_pointer(). However, I don't like to patch glib functions
with our own variant. Arguably, we do patch g_clear_error() in
such a manner. But there the point is to make the function inlinable.
Also, nm_clear_pointer() returns a boolean that indicates whether
anything was cleared. That is sometimes useful. I think we should
just consistently use nm_clear_pointer() instead, which does always
the preferable thing.
Replace:
sed 's/\<g_clear_pointer *(\([^;]*\), *\([a-z_A-Z0-9]\+\) *)/nm_clear_pointer (\1, \2)/g' $(git grep -l g_clear_pointer) -i
2020-03-23 11:09:24 +01:00
|
|
|
nm_clear_pointer(&priv->data_invalid, g_hash_table_unref);
|
2017-05-04 14:44:18 +02:00
|
|
|
}
|
|
|
|
|
if (changed)
|
|
|
|
|
_notify(self, PROP_DATA);
|
2017-03-24 12:41:04 +01:00
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*****************************************************************************/
|
|
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
|
verify(NMSetting *setting, NMConnection *connection, GError **error)
|
|
|
|
|
{
|
2021-11-09 13:28:54 +01:00
|
|
|
NMSettingUser *self = NM_SETTING_USER(setting);
|
2017-05-04 14:44:18 +02:00
|
|
|
NMSettingUserPrivate *priv = NM_SETTING_USER_GET_PRIVATE(self);
|
2020-09-28 16:03:33 +02:00
|
|
|
|
2017-05-04 14:44:18 +02:00
|
|
|
if (priv->data_invalid) {
|
2021-11-09 13:28:54 +01:00
|
|
|
const char *key, *val;
|
|
|
|
|
GHashTableIter iter;
|
2017-05-04 14:44:18 +02:00
|
|
|
gs_free_error GError *local = NULL;
|
2020-09-28 16:03:33 +02:00
|
|
|
|
2017-05-04 14:44:18 +02:00
|
|
|
g_hash_table_iter_init(&iter, priv->data_invalid);
|
|
|
|
|
while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &val)) {
|
|
|
|
|
if (!nm_setting_user_check_key(key, &local)) {
|
|
|
|
|
g_set_error(error,
|
|
|
|
|
NM_CONNECTION_ERROR,
|
|
|
|
|
NM_CONNECTION_ERROR_FAILED,
|
|
|
|
|
_("invalid key \"%s\": %s"),
|
|
|
|
|
key,
|
|
|
|
|
local->message);
|
|
|
|
|
} else if (!nm_setting_user_check_val(val, &local)) {
|
|
|
|
|
g_set_error(error,
|
|
|
|
|
NM_CONNECTION_ERROR,
|
|
|
|
|
NM_CONNECTION_ERROR_FAILED,
|
|
|
|
|
_("invalid value for \"%s\": %s"),
|
|
|
|
|
key,
|
|
|
|
|
local->message);
|
|
|
|
|
} else {
|
|
|
|
|
nm_assert_not_reached();
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
g_prefix_error(error, "%s.%s: ", NM_SETTING_USER_SETTING_NAME, NM_SETTING_USER_DATA);
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
nm_assert_not_reached();
|
|
|
|
|
}
|
2020-09-28 16:03:33 +02:00
|
|
|
|
2017-05-04 14:44:18 +02:00
|
|
|
if (priv->data && g_hash_table_size(priv->data) > MAX_NUM_KEYS) {
|
|
|
|
|
g_set_error(error,
|
|
|
|
|
NM_CONNECTION_ERROR,
|
|
|
|
|
NM_CONNECTION_ERROR_INVALID_PROPERTY,
|
|
|
|
|
_("maximum number of user data entries reached (%u instead of %u)"),
|
|
|
|
|
g_hash_table_size(priv->data),
|
|
|
|
|
(unsigned) MAX_NUM_KEYS);
|
|
|
|
|
g_prefix_error(error, "%s.%s: ", NM_SETTING_USER_SETTING_NAME, NM_SETTING_USER_DATA);
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
2020-09-28 16:03:33 +02:00
|
|
|
|
2017-05-04 14:44:18 +02:00
|
|
|
return TRUE;
|
2017-03-24 12:41:04 +01:00
|
|
|
}
|
|
|
|
|
|
libnm: rework compare_property() implementation for NMSetting
NMSetting's compare_property() has and had two callers:
nm_setting_compare() and nm_setting_diff().
compare_property() accepts a NMSettingCompareFlags argument, but
at the same time, both callers have another complex (and
inconsistent!) set of pre-checks for shortcuting the call of
compare_property(): should_compare_prop().
Merge should_compare_prop() into compare_property(). This way,
nm_setting_compare() and nm_setting_diff() has less additional
code, and are simpler to follow. Especially nm_setting_compare()
is now trivial. And nm_setting_diff() is still complicated, but
not related to the question how the property compares or whether
it should be compared at all.
If you want to know whether it should be compared, all you need to do
now is follow NMSettingClass.compare_property().
This changes function pointer NMSettingClass.compare_property(),
which is public API. However, no user can actually use this (and shall
not!), because _nm_setting_class_commit_full() etc. is private API. A
user outside of libnm-core cannot create his/her own subclasses of
NMSetting, and never could in the past. So, this API/ABI change doesn't
matter.
2019-01-09 09:08:39 +01:00
|
|
|
static NMTernary
|
libnm: use macros function arguments for NMSettInfoPropertType
These functions tend to have many arguments. They are also quite som
boilerplate to implement the hundereds of properties we have, while
we want that properties have common behaviors and similarities.
Instead of repeatedly spelling out the function arguments, use a macro.
Advantages:
- the usage of a _NM_SETT_INFO_PROP_*_FCN_ARGS macro signals that this
is an implementation of a property. You can now grep for these macros
to find all implementation. That was previously rather imprecise, you
could only `git grep '\.to_dbus_fcn'` to find the uses, but not the
implementations.
As the goal is to keep properties "similar", there is a desire to
reduce the number of similar implementations and to find them.
- changing the arguments now no longer will require you to go through
all implementations. At least not, if you merely add an argument that
has a reasonable default behavior and does not require explicit
handling by most implementation.
- it's convenient to be able to patch the argument list to let the
compiler help to reason about something. For example, the
"connection_dict" argument to from_dbus_fcn() is usually unused.
If you'd like to find who uses it, rename the parameter, and
review the (few) compiler errors.
- it does save 573 LOC of boilerplate with no actual logic or useful
information. I argue, that this simplifies the code and review, by
increasing the relative amount of actually meaningful code.
Disadvantages:
- the user no longer directly sees the argument list. They would need
cscope/ctags or an IDE to jump to the macro definition and conveniently
see all arguments.
Also use _nm_nil, so that clang-format interprets this as a function
parameter list. Otherwise, it formats the function differently.
2021-07-26 23:45:31 +02:00
|
|
|
compare_fcn_data(_NM_SETT_INFO_PROP_COMPARE_FCN_ARGS _nm_nil)
|
2017-03-24 12:41:04 +01:00
|
|
|
{
|
2021-06-29 14:37:16 +02:00
|
|
|
NMSettingUserPrivate *priv;
|
|
|
|
|
NMSettingUserPrivate *pri2;
|
2020-09-28 16:03:33 +02:00
|
|
|
|
2021-06-29 14:37:16 +02:00
|
|
|
if (NM_FLAGS_HAS(flags, NM_SETTING_COMPARE_FLAG_INFERRABLE))
|
|
|
|
|
return NM_TERNARY_DEFAULT;
|
2020-09-28 16:03:33 +02:00
|
|
|
|
2021-06-29 14:37:16 +02:00
|
|
|
if (!set_b)
|
|
|
|
|
return TRUE;
|
2020-09-28 16:03:33 +02:00
|
|
|
|
2021-06-29 14:37:16 +02:00
|
|
|
priv = NM_SETTING_USER_GET_PRIVATE(NM_SETTING_USER(set_a));
|
|
|
|
|
pri2 = NM_SETTING_USER_GET_PRIVATE(NM_SETTING_USER(set_b));
|
|
|
|
|
return nm_utils_hashtable_equal(priv->data, pri2->data, TRUE, g_str_equal)
|
|
|
|
|
&& nm_utils_hashtable_equal(priv->data_invalid, pri2->data_invalid, TRUE, g_str_equal);
|
2017-03-24 12:41:04 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*****************************************************************************/
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
|
|
|
|
|
{
|
2021-11-09 13:28:54 +01:00
|
|
|
NMSettingUser *self = NM_SETTING_USER(object);
|
2017-03-24 12:41:04 +01:00
|
|
|
NMSettingUserPrivate *priv = NM_SETTING_USER_GET_PRIVATE(self);
|
|
|
|
|
GHashTableIter iter;
|
2021-11-09 13:28:54 +01:00
|
|
|
GHashTable *data;
|
|
|
|
|
const char *key, *val;
|
2020-09-28 16:03:33 +02:00
|
|
|
|
2017-03-24 12:41:04 +01:00
|
|
|
switch (prop_id) {
|
|
|
|
|
case PROP_DATA:
|
|
|
|
|
data = _create_data_hash();
|
|
|
|
|
if (priv->data) {
|
|
|
|
|
g_hash_table_iter_init(&iter, priv->data);
|
|
|
|
|
while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &val))
|
|
|
|
|
g_hash_table_insert(data, g_strdup(key), g_strdup(val));
|
|
|
|
|
}
|
2017-05-04 14:44:18 +02:00
|
|
|
if (priv->data_invalid) {
|
|
|
|
|
g_hash_table_iter_init(&iter, priv->data_invalid);
|
|
|
|
|
while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &val))
|
|
|
|
|
g_hash_table_insert(data, g_strdup(key), g_strdup(val));
|
|
|
|
|
}
|
2017-03-24 12:41:04 +01:00
|
|
|
g_value_take_boxed(value, data);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
|
|
|
|
|
{
|
2021-11-09 13:28:54 +01:00
|
|
|
NMSettingUser *self = NM_SETTING_USER(object);
|
2017-03-24 12:41:04 +01:00
|
|
|
NMSettingUserPrivate *priv = NM_SETTING_USER_GET_PRIVATE(self);
|
|
|
|
|
GHashTableIter iter;
|
2021-11-09 13:28:54 +01:00
|
|
|
GHashTable *data;
|
|
|
|
|
const char *key, *val;
|
2020-09-28 16:03:33 +02:00
|
|
|
|
2017-03-24 12:41:04 +01:00
|
|
|
switch (prop_id) {
|
|
|
|
|
case PROP_DATA:
|
2017-05-04 14:44:18 +02:00
|
|
|
nm_clear_g_free(&priv->keys);
|
2020-09-28 16:03:33 +02:00
|
|
|
|
2017-03-24 12:41:04 +01:00
|
|
|
data = g_value_get_boxed(value);
|
|
|
|
|
if (!data || !g_hash_table_size(data)) {
|
all: use nm_clear_pointer() instead of g_clear_pointer()
g_clear_pointer() would always cast the destroy notify function
pointer to GDestroyNotify. That means, it lost some type safety, like
GPtrArray *ptr_arr = ...
g_clear_pointer (&ptr_arr, g_array_unref);
Since glib 2.58 ([1]), g_clear_pointer() is also more type safe. But
this is not used by NetworkManager, because we don't set
GLIB_VERSION_MIN_REQUIRED to 2.58.
[1] https://gitlab.gnome.org/GNOME/glib/-/commit/f9a9902aac826ab4aecc25f6eb533a418a4fa559
We have nm_clear_pointer() to avoid this issue for a long time (pre
1.12.0). Possibly we should redefine in our source tree g_clear_pointer()
as nm_clear_pointer(). However, I don't like to patch glib functions
with our own variant. Arguably, we do patch g_clear_error() in
such a manner. But there the point is to make the function inlinable.
Also, nm_clear_pointer() returns a boolean that indicates whether
anything was cleared. That is sometimes useful. I think we should
just consistently use nm_clear_pointer() instead, which does always
the preferable thing.
Replace:
sed 's/\<g_clear_pointer *(\([^;]*\), *\([a-z_A-Z0-9]\+\) *)/nm_clear_pointer (\1, \2)/g' $(git grep -l g_clear_pointer) -i
2020-03-23 11:09:24 +01:00
|
|
|
nm_clear_pointer(&priv->data, g_hash_table_unref);
|
|
|
|
|
nm_clear_pointer(&priv->data_invalid, g_hash_table_unref);
|
2017-03-24 12:41:04 +01:00
|
|
|
return;
|
|
|
|
|
}
|
2020-09-28 16:03:33 +02:00
|
|
|
|
2017-03-24 12:41:04 +01:00
|
|
|
if (priv->data)
|
|
|
|
|
g_hash_table_remove_all(priv->data);
|
|
|
|
|
else
|
|
|
|
|
priv->data = _create_data_hash();
|
2020-09-28 16:03:33 +02:00
|
|
|
|
2017-05-04 14:44:18 +02:00
|
|
|
if (priv->data_invalid)
|
|
|
|
|
g_hash_table_remove_all(priv->data_invalid);
|
2020-09-28 16:03:33 +02:00
|
|
|
|
2017-05-04 14:44:18 +02:00
|
|
|
g_hash_table_iter_init(&iter, data);
|
|
|
|
|
while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &val)) {
|
|
|
|
|
if (nm_setting_user_check_key(key, NULL) && nm_setting_user_check_val(val, NULL))
|
|
|
|
|
g_hash_table_insert(priv->data, g_strdup(key), g_strdup(val));
|
|
|
|
|
else {
|
|
|
|
|
if (!priv->data_invalid)
|
|
|
|
|
priv->data_invalid = _create_data_hash();
|
|
|
|
|
g_hash_table_insert(priv->data_invalid, g_strdup(key), g_strdup(val));
|
|
|
|
|
}
|
2020-09-28 16:03:33 +02:00
|
|
|
}
|
2017-05-04 14:44:18 +02:00
|
|
|
if (priv->data_invalid && !g_hash_table_size(priv->data_invalid))
|
all: use nm_clear_pointer() instead of g_clear_pointer()
g_clear_pointer() would always cast the destroy notify function
pointer to GDestroyNotify. That means, it lost some type safety, like
GPtrArray *ptr_arr = ...
g_clear_pointer (&ptr_arr, g_array_unref);
Since glib 2.58 ([1]), g_clear_pointer() is also more type safe. But
this is not used by NetworkManager, because we don't set
GLIB_VERSION_MIN_REQUIRED to 2.58.
[1] https://gitlab.gnome.org/GNOME/glib/-/commit/f9a9902aac826ab4aecc25f6eb533a418a4fa559
We have nm_clear_pointer() to avoid this issue for a long time (pre
1.12.0). Possibly we should redefine in our source tree g_clear_pointer()
as nm_clear_pointer(). However, I don't like to patch glib functions
with our own variant. Arguably, we do patch g_clear_error() in
such a manner. But there the point is to make the function inlinable.
Also, nm_clear_pointer() returns a boolean that indicates whether
anything was cleared. That is sometimes useful. I think we should
just consistently use nm_clear_pointer() instead, which does always
the preferable thing.
Replace:
sed 's/\<g_clear_pointer *(\([^;]*\), *\([a-z_A-Z0-9]\+\) *)/nm_clear_pointer (\1, \2)/g' $(git grep -l g_clear_pointer) -i
2020-03-23 11:09:24 +01:00
|
|
|
nm_clear_pointer(&priv->data_invalid, g_hash_table_unref);
|
2020-09-28 16:03:33 +02:00
|
|
|
|
2017-03-24 12:41:04 +01:00
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*****************************************************************************/
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
nm_setting_user_init(NMSettingUser *self)
|
|
|
|
|
{}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* nm_setting_user_new:
|
|
|
|
|
*
|
|
|
|
|
* Creates a new #NMSettingUser object with default values.
|
|
|
|
|
*
|
|
|
|
|
* Returns: the new empty #NMSettingUser object
|
2022-11-07 21:13:28 +01:00
|
|
|
*
|
|
|
|
|
* Since: 1.8
|
2017-03-24 12:41:04 +01:00
|
|
|
**/
|
|
|
|
|
NMSetting *
|
|
|
|
|
nm_setting_user_new(void)
|
|
|
|
|
{
|
2020-11-12 15:57:06 +01:00
|
|
|
return g_object_new(NM_TYPE_SETTING_USER, NULL);
|
2017-03-24 12:41:04 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
finalize(GObject *object)
|
|
|
|
|
{
|
2021-11-09 13:28:54 +01:00
|
|
|
NMSettingUser *self = NM_SETTING_USER(object);
|
2017-03-24 12:41:04 +01:00
|
|
|
NMSettingUserPrivate *priv = NM_SETTING_USER_GET_PRIVATE(self);
|
|
|
|
|
|
|
|
|
|
g_free(priv->keys);
|
|
|
|
|
if (priv->data)
|
|
|
|
|
g_hash_table_unref(priv->data);
|
2017-05-04 14:44:18 +02:00
|
|
|
if (priv->data_invalid)
|
|
|
|
|
g_hash_table_unref(priv->data_invalid);
|
2017-03-24 12:41:04 +01:00
|
|
|
|
|
|
|
|
G_OBJECT_CLASS(nm_setting_user_parent_class)->finalize(object);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
nm_setting_user_class_init(NMSettingUserClass *klass)
|
|
|
|
|
{
|
2021-11-09 13:28:54 +01:00
|
|
|
GObjectClass *object_class = G_OBJECT_CLASS(klass);
|
2017-03-24 12:41:04 +01:00
|
|
|
NMSettingClass *setting_class = NM_SETTING_CLASS(klass);
|
2021-11-09 13:28:54 +01:00
|
|
|
GArray *properties_override = _nm_sett_info_property_override_create_array();
|
2017-03-24 12:41:04 +01:00
|
|
|
|
|
|
|
|
object_class->get_property = get_property;
|
2019-01-11 08:32:54 +01:00
|
|
|
object_class->set_property = set_property;
|
2017-03-24 12:41:04 +01:00
|
|
|
object_class->finalize = finalize;
|
libnm/trivial: cleanup variable names in settings' class-init functions
- Don't use @parent_class name. This local variable (and @object_class) is
the class instance up-cast to the pointer types of the parents. The point
here is not that it is the direct parent. The point is, that it's the
NMSettingClass type.
Also, it can only be used inconsistently, in face of NMSettingIP4Config,
who's parent type is NMSettingIPConfig. Clearly, inside
nm-setting-ip4-config.c we wouldn't want to use the "parent_class"
name. Consistently rename @parent_class to @setting_class.
- Also rename the pointer to the own class to @klass. "setting_class" is also the
wrong name for that, because the right name would be something like
"setting_6lowpan_class".
However, "klass" is preferred over the latter, because we commonly create new
GObject implementations by copying an existing one. Generic names like "klass"
and "self" inside a type implementation make that simpler.
- drop useless comments like
/* virtual functions */
/* Properties */
It's better to logically and visually structure the code, and avoid trival
remarks about that. They only end up being used inconsistently. If you
even need a stronger visual separator, then an 80 char /****/ line
should be preferred.
2018-07-28 10:43:21 +02:00
|
|
|
|
2021-06-29 14:37:16 +02:00
|
|
|
setting_class->verify = verify;
|
2017-03-24 12:41:04 +01:00
|
|
|
|
|
|
|
|
/**
|
2018-03-24 15:18:21 +00:00
|
|
|
* NMSettingUser:data: (type GHashTable(utf8,utf8))
|
2017-03-24 12:41:04 +01:00
|
|
|
*
|
|
|
|
|
* A dictionary of key/value pairs with user data. This data is ignored by NetworkManager
|
|
|
|
|
* and can be used at the users discretion. The keys only support a strict ascii format,
|
|
|
|
|
* but the values can be arbitrary UTF8 strings up to a certain length.
|
|
|
|
|
*
|
|
|
|
|
* Since: 1.8
|
|
|
|
|
**/
|
2017-09-05 09:24:29 +02:00
|
|
|
/* ---ifcfg-rh---
|
|
|
|
|
* property: data
|
|
|
|
|
* variable: NM_USER_*
|
|
|
|
|
* description: each key/value pair is stored as a separate variable with
|
|
|
|
|
* name composed by concatenating NM_USER_ with the encoded key. The key is
|
|
|
|
|
* encoded by substituting lowercase letters with uppercase and prepending
|
|
|
|
|
* uppercase letters with an underscore. A dot is encoded as a double
|
|
|
|
|
* underscore. Remaining characters are encoded as underscore followed by a
|
|
|
|
|
* 3 digit octal representation of the character.
|
|
|
|
|
* example: NM_USER_FOO__BAR=something
|
|
|
|
|
* ---end---
|
|
|
|
|
*/
|
2017-03-24 12:41:04 +01:00
|
|
|
obj_properties[PROP_DATA] = g_param_spec_boxed(NM_SETTING_USER_DATA,
|
|
|
|
|
"",
|
|
|
|
|
"",
|
|
|
|
|
G_TYPE_HASH_TABLE,
|
|
|
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
2021-06-29 14:37:16 +02:00
|
|
|
_nm_properties_override_gobj(
|
|
|
|
|
properties_override,
|
|
|
|
|
obj_properties[PROP_DATA],
|
|
|
|
|
NM_SETT_INFO_PROPERT_TYPE_GPROP(NM_G_VARIANT_TYPE("a{ss}"),
|
2021-06-30 00:05:49 +02:00
|
|
|
.typdata_from_dbus.gprop_fcn = _nm_utils_strdict_from_dbus,
|
2021-06-29 14:37:16 +02:00
|
|
|
.typdata_to_dbus.gprop_type =
|
|
|
|
|
NM_SETTING_PROPERTY_TO_DBUS_FCN_GPROP_TYPE_STRDICT,
|
2021-06-30 00:05:49 +02:00
|
|
|
.compare_fcn = compare_fcn_data,
|
|
|
|
|
.from_dbus_fcn = _nm_setting_property_from_dbus_fcn_gprop,
|
|
|
|
|
.from_dbus_is_full = TRUE));
|
2020-09-28 16:03:33 +02:00
|
|
|
|
2019-01-11 08:32:54 +01:00
|
|
|
g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);
|
2020-09-28 16:03:33 +02:00
|
|
|
|
2021-06-28 14:53:16 +02:00
|
|
|
_nm_setting_class_commit(setting_class,
|
|
|
|
|
NM_META_SETTING_TYPE_USER,
|
|
|
|
|
NULL,
|
|
|
|
|
properties_override,
|
2022-01-17 09:53:27 +01:00
|
|
|
0);
|
2017-03-24 12:41:04 +01:00
|
|
|
}
|