diff options
36 files changed, 1333 insertions, 397 deletions
diff --git a/include/nm-glib-compat.h b/include/nm-glib-compat.h index 741d610319..dc39d2b934 100644 --- a/include/nm-glib-compat.h +++ b/include/nm-glib-compat.h @@ -213,4 +213,41 @@ _nm_g_ptr_array_insert (GPtrArray *array, #endif +#if !GLIB_CHECK_VERSION (2, 40, 0) +inline static gboolean +_g_key_file_save_to_file (GKeyFile *key_file, + const gchar *filename, + GError **error) +{ + gchar *contents; + gboolean success; + gsize length; + + g_return_val_if_fail (key_file != NULL, FALSE); + g_return_val_if_fail (filename != NULL, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + contents = g_key_file_to_data (key_file, &length, NULL); + g_assert (contents != NULL); + + success = g_file_set_contents (filename, contents, length, error); + g_free (contents); + + return success; +} +#define g_key_file_save_to_file(key_file, filename, error) \ + _g_key_file_save_to_file (key_file, filename, error) +#else +#define g_key_file_save_to_file(key_file, filename, error) \ + ({ \ + gboolean _success; \ + \ + G_GNUC_BEGIN_IGNORE_DEPRECATIONS \ + _success = g_key_file_save_to_file (key_file, filename, error); \ + G_GNUC_END_IGNORE_DEPRECATIONS \ + _success; \ + }) +#endif + + #endif /* __NM_GLIB_COMPAT_H__ */ diff --git a/include/nm-macros-internal.h b/include/nm-macros-internal.h index 3767a13397..405051f382 100644 --- a/include/nm-macros-internal.h +++ b/include/nm-macros-internal.h @@ -236,4 +236,13 @@ nm_clear_g_source (guint *id) /*****************************************************************************/ +static inline char * +nm_strstrip (char *str) +{ + /* g_strstrip doesn't like NULL. */ + return str ? g_strstrip (str) : NULL; +} + +/*****************************************************************************/ + #endif /* __NM_MACROS_INTERNAL_H__ */ diff --git a/include/nm-test-utils.h b/include/nm-test-utils.h index 0cde9a4b91..4610ee62a6 100644 --- a/include/nm-test-utils.h +++ b/include/nm-test-utils.h @@ -123,6 +123,14 @@ nmtst_assert_error (GError *error, } } +inline static void +_nmtst_assert_success (gboolean success, GError *error, const char *file, int line) +{ + if (!success || error) + g_error ("(%s:%d) FAILURE success=%d, error=%s", file, line, success, error && error->message ? error->message : "(no error)"); +} +#define nmtst_assert_success(success, error) _nmtst_assert_success ((success), (error), __FILE__, __LINE__) + /*******************************************************************************/ struct __nmtst_internal diff --git a/libnm-core/nm-core-internal.h b/libnm-core/nm-core-internal.h index ab147b9f7a..dd5151e088 100644 --- a/libnm-core/nm-core-internal.h +++ b/libnm-core/nm-core-internal.h @@ -119,10 +119,21 @@ gboolean _nm_utils_string_in_list (const char *str, gssize _nm_utils_strv_find_first (char **list, gssize len, const char *needle); +char **_nm_utils_strv_cleanup (char **strv, + gboolean strip_whitespace, + gboolean skip_empty, + gboolean skip_repeated); + char ** _nm_utils_strsplit_set (const char *str, const char *delimiters, int max_tokens); +GSList * _nm_utils_strv_to_slist (char **strv, gboolean deep_copy); +char ** _nm_utils_slist_to_strv (GSList *slist, gboolean deep_copy); + +GPtrArray * _nm_utils_strv_to_ptrarray (char **strv); +char ** _nm_utils_ptrarray_to_strv (GPtrArray *ptrarray); + #define NM_UTILS_UUID_TYPE_LEGACY 0 #define NM_UTILS_UUID_TYPE_VARIANT3 1 diff --git a/libnm-core/nm-keyfile-internal.h b/libnm-core/nm-keyfile-internal.h index 90af562cdf..f4bb079637 100644 --- a/libnm-core/nm-keyfile-internal.h +++ b/libnm-core/nm-keyfile-internal.h @@ -162,5 +162,10 @@ GKeyFile *nm_keyfile_write (NMConnection *connection, char *nm_keyfile_plugin_kf_get_string (GKeyFile *kf, const char *group, const char *key, GError **error); void nm_keyfile_plugin_kf_set_string (GKeyFile *kf, const char *group, const char *key, const char *value); +void _nm_keyfile_copy (GKeyFile *dst, GKeyFile *src); +gboolean _nm_keyfile_a_contains_all_in_b (GKeyFile *kf_a, GKeyFile *kf_b); +gboolean _nm_keyfile_equals (GKeyFile *kf_a, GKeyFile *kf_b, gboolean consider_order); +gboolean _nm_keyfile_has_values (GKeyFile *keyfile); + #endif /* __NM_KEYFILE_INTERNAL_H__ */ diff --git a/libnm-core/nm-keyfile-reader.c b/libnm-core/nm-keyfile-reader.c index df568a8ebd..353804f97c 100644 --- a/libnm-core/nm-keyfile-reader.c +++ b/libnm-core/nm-keyfile-reader.c @@ -1577,6 +1577,8 @@ nm_keyfile_read (GKeyFile *keyfile, info.user_data = user_data; groups = g_key_file_get_groups (keyfile, &length); + if (!groups) + length = 0; for (i = 0; i < length; i++) { /* Only read out secrets when needed */ if (!strcmp (groups[i], VPN_SECRETS_GROUP)) { diff --git a/libnm-core/nm-keyfile-utils.c b/libnm-core/nm-keyfile-utils.c index 61b30ab9c8..d04a3d1bef 100644 --- a/libnm-core/nm-keyfile-utils.c +++ b/libnm-core/nm-keyfile-utils.c @@ -24,6 +24,7 @@ #include <stdlib.h> #include <string.h> +#include "gsystem-local-alloc.h" #include "nm-keyfile-utils.h" #include "nm-keyfile-internal.h" #include "nm-setting-wired.h" @@ -204,4 +205,138 @@ nm_keyfile_plugin_kf_has_key (GKeyFile *kf, return has; } +/************************************************************************/ +void +_nm_keyfile_copy (GKeyFile *dst, GKeyFile *src) +{ + gs_strfreev char **groups = NULL; + guint g, k; + + groups = g_key_file_get_groups (src, NULL); + for (g = 0; groups && groups[g]; g++) { + const char *group = groups[g]; + gs_strfreev char **keys = NULL; + + keys = g_key_file_get_keys (src, group, NULL, NULL); + if (!keys) + continue; + + for (k = 0; keys[k]; k++) { + const char *key = keys[k]; + gs_free char *value = NULL; + + value = g_key_file_get_value (src, group, key, NULL); + if (value) + g_key_file_set_value (dst, group, key, value); + else + g_key_file_remove_key (dst, group, key, NULL); + } + } +} + +/************************************************************************/ + +gboolean +_nm_keyfile_a_contains_all_in_b (GKeyFile *kf_a, GKeyFile *kf_b) +{ + gs_strfreev char **groups = NULL; + guint i, j; + + if (kf_a == kf_b) + return TRUE; + if (!kf_a || !kf_b) + return FALSE; + + groups = g_key_file_get_groups (kf_a, NULL); + for (i = 0; groups && groups[i]; i++) { + gs_strfreev char **keys = NULL; + + keys = g_key_file_get_keys (kf_a, groups[i], NULL, NULL); + if (!keys) + continue; + + for (j = 0; keys[j]; j++) { + gs_free char *key_a = g_key_file_get_value (kf_a, groups[i], keys[j], NULL); + gs_free char *key_b = g_key_file_get_value (kf_b, groups[i], keys[j], NULL); + + if (g_strcmp0 (key_a, key_b) != 0) + return FALSE; + } + } + return TRUE; +} + + +static gboolean +_nm_keyfile_equals_ordered (GKeyFile *kf_a, GKeyFile *kf_b) +{ + gs_strfreev char **groups = NULL; + gs_strfreev char **groups_b = NULL; + guint i, j; + + if (kf_a == kf_b) + return TRUE; + if (!kf_a || !kf_b) + return FALSE; + + groups = g_key_file_get_groups (kf_a, NULL); + groups_b = g_key_file_get_groups (kf_b, NULL); + if (!groups && !groups_b) + return TRUE; + if (!groups || !groups_b) + return FALSE; + for (i = 0; groups[i] && groups_b[i] && !strcmp (groups[i], groups_b[i]); i++) + ; + if (groups[i] || groups_b[i]) + return FALSE; + + for (i = 0; groups[i]; i++) { + gs_strfreev char **keys = NULL; + gs_strfreev char **keys_b = NULL; + + keys = g_key_file_get_keys (kf_a, groups[i], NULL, NULL); + keys_b = g_key_file_get_keys (kf_b, groups[i], NULL, NULL); + + if ((!keys) != (!keys_b)) + return FALSE; + if (!keys) + continue; + + for (j = 0; keys[j] && keys_b[j] && !strcmp (keys[j], keys_b[j]); j++) + ; + if (keys[j] || keys_b[j]) + return FALSE; + + for (j = 0; keys[j]; j++) { + gs_free char *key_a = g_key_file_get_value (kf_a, groups[i], keys[j], NULL); + gs_free char *key_b = g_key_file_get_value (kf_b, groups[i], keys[j], NULL); + + if (g_strcmp0 (key_a, key_b) != 0) + return FALSE; + } + } + return TRUE; +} + +gboolean +_nm_keyfile_equals (GKeyFile *kf_a, GKeyFile *kf_b, gboolean consider_order) +{ + if (!consider_order) { + return _nm_keyfile_a_contains_all_in_b (kf_a, kf_b) + && _nm_keyfile_a_contains_all_in_b (kf_b, kf_a); + } else { + return _nm_keyfile_equals_ordered (kf_a, kf_b); + } +} + +gboolean +_nm_keyfile_has_values (GKeyFile *keyfile) +{ + gs_strfreev char **groups = NULL; + + g_return_val_if_fail (keyfile, FALSE); + + groups = g_key_file_get_groups (keyfile, NULL); + return groups && groups[0]; +} diff --git a/libnm-core/nm-setting-8021x.c b/libnm-core/nm-setting-8021x.c index 02f9c9039d..e50ad4eae5 100644 --- a/libnm-core/nm-setting-8021x.c +++ b/libnm-core/nm-setting-8021x.c @@ -2888,7 +2888,7 @@ set_property (GObject *object, guint prop_id, switch (prop_id) { case PROP_EAP: g_slist_free_full (priv->eap, g_free); - priv->eap = _nm_utils_strv_to_slist (g_value_get_boxed (value)); + priv->eap = _nm_utils_strv_to_slist (g_value_get_boxed (value), TRUE); break; case PROP_IDENTITY: g_free (priv->identity); @@ -2922,7 +2922,7 @@ set_property (GObject *object, guint prop_id, break; case PROP_ALTSUBJECT_MATCHES: g_slist_free_full (priv->altsubject_matches, g_free); - priv->altsubject_matches = _nm_utils_strv_to_slist (g_value_get_boxed (value)); + priv->altsubject_matches = _nm_utils_strv_to_slist (g_value_get_boxed (value), TRUE); break; case PROP_CLIENT_CERT: if (priv->client_cert) @@ -2974,7 +2974,7 @@ set_property (GObject *object, guint prop_id, break; case PROP_PHASE2_ALTSUBJECT_MATCHES: g_slist_free_full (priv->phase2_altsubject_matches, g_free); - priv->phase2_altsubject_matches = _nm_utils_strv_to_slist (g_value_get_boxed (value)); + priv->phase2_altsubject_matches = _nm_utils_strv_to_slist (g_value_get_boxed (value), TRUE); break; case PROP_PHASE2_CLIENT_CERT: if (priv->phase2_client_cert) @@ -3060,7 +3060,7 @@ get_property (GObject *object, guint prop_id, switch (prop_id) { case PROP_EAP: - g_value_take_boxed (value, _nm_utils_slist_to_strv (priv->eap)); + g_value_take_boxed (value, _nm_utils_slist_to_strv (priv->eap, TRUE)); break; case PROP_IDENTITY: g_value_set_string (value, priv->identity); @@ -3081,7 +3081,7 @@ get_property (GObject *object, guint prop_id, g_value_set_string (value, priv->subject_match); break; case PROP_ALTSUBJECT_MATCHES: - g_value_take_boxed (value, _nm_utils_slist_to_strv (priv->altsubject_matches)); + g_value_take_boxed (value, _nm_utils_slist_to_strv (priv->altsubject_matches, TRUE)); break; case PROP_CLIENT_CERT: g_value_set_boxed (value, priv->client_cert); @@ -3111,7 +3111,7 @@ get_property (GObject *object, guint prop_id, g_value_set_string (value, priv->phase2_subject_match); break; case PROP_PHASE2_ALTSUBJECT_MATCHES: - g_value_take_boxed (value, _nm_utils_slist_to_strv (priv->phase2_altsubject_matches)); + g_value_take_boxed (value, _nm_utils_slist_to_strv (priv->phase2_altsubject_matches, TRUE)); break; case PROP_PHASE2_CLIENT_CERT: g_value_set_boxed (value, priv->phase2_client_cert); diff --git a/libnm-core/nm-setting-connection.c b/libnm-core/nm-setting-connection.c index 6f66e674a0..07b3401d1e 100644 --- a/libnm-core/nm-setting-connection.c +++ b/libnm-core/nm-setting-connection.c @@ -1149,7 +1149,7 @@ set_property (GObject *object, guint prop_id, break; case PROP_SECONDARIES: g_slist_free_full (priv->secondaries, g_free); - priv->secondaries = _nm_utils_strv_to_slist (g_value_get_boxed (value)); + priv->secondaries = _nm_utils_strv_to_slist (g_value_get_boxed (value), TRUE); break; case PROP_GATEWAY_PING_TIMEOUT: priv->gateway_ping_timeout = g_value_get_uint (value); @@ -1222,7 +1222,7 @@ get_property (GObject *object, guint prop_id, g_value_set_enum (value, nm_setting_connection_get_autoconnect_slaves (setting)); break; case PROP_SECONDARIES: - g_value_take_boxed (value, _nm_utils_slist_to_strv (priv->secondaries)); + g_value_take_boxed (value, _nm_utils_slist_to_strv (priv->secondaries, TRUE)); break; case PROP_GATEWAY_PING_TIMEOUT: g_value_set_uint (value, priv->gateway_ping_timeout); diff --git a/libnm-core/nm-setting-wireless-security.c b/libnm-core/nm-setting-wireless-security.c index a56a8e9757..6df5b46176 100644 --- a/libnm-core/nm-setting-wireless-security.c +++ b/libnm-core/nm-setting-wireless-security.c @@ -1132,15 +1132,15 @@ set_property (GObject *object, guint prop_id, break; case PROP_PROTO: g_slist_free_full (priv->proto, g_free); - priv->proto = _nm_utils_strv_to_slist (g_value_get_boxed (value)); + priv->proto = _nm_utils_strv_to_slist (g_value_get_boxed (value), TRUE); break; case PROP_PAIRWISE: g_slist_free_full (priv->pairwise, g_free); - priv->pairwise = _nm_utils_strv_to_slist (g_value_get_boxed (value)); + priv->pairwise = _nm_utils_strv_to_slist (g_value_get_boxed (value), TRUE); break; case PROP_GROUP: g_slist_free_full (priv->group, g_free); - priv->group = _nm_utils_strv_to_slist (g_value_get_boxed (value)); + priv->group = _nm_utils_strv_to_slist (g_value_get_boxed (value), TRUE); break; case PROP_LEAP_USERNAME: g_free (priv->leap_username); @@ -1206,13 +1206,13 @@ get_property (GObject *object, guint prop_id, g_value_set_string (value, priv->auth_alg); break; case PROP_PROTO: - g_value_take_boxed (value, _nm_utils_slist_to_strv (priv->proto)); + g_value_take_boxed (value, _nm_utils_slist_to_strv (priv->proto, TRUE)); break; case PROP_PAIRWISE: - g_value_take_boxed (value, _nm_utils_slist_to_strv (priv->pairwise)); + g_value_take_boxed (value, _nm_utils_slist_to_strv (priv->pairwise, TRUE)); break; case PROP_GROUP: - g_value_take_boxed (value, _nm_utils_slist_to_strv (priv->group)); + g_value_take_boxed (value, _nm_utils_slist_to_strv (priv->group, TRUE)); break; case PROP_LEAP_USERNAME: g_value_set_string (value, priv->leap_username); diff --git a/libnm-core/nm-setting-wireless.c b/libnm-core/nm-setting-wireless.c index f8d22c2d00..ce3cf9a7e9 100644 --- a/libnm-core/nm-setting-wireless.c +++ b/libnm-core/nm-setting-wireless.c @@ -911,7 +911,7 @@ set_property (GObject *object, guint prop_id, break; case PROP_SEEN_BSSIDS: g_slist_free_full (priv->seen_bssids, g_free); - priv->seen_bssids = _nm_utils_strv_to_slist (g_value_get_boxed (value)); + priv->seen_bssids = _nm_utils_strv_to_slist (g_value_get_boxed (value), TRUE); break; case PROP_HIDDEN: priv->hidden = g_value_get_boolean (value); @@ -964,7 +964,7 @@ get_property (GObject *object, guint prop_id, g_value_set_uint (value, nm_setting_wireless_get_mtu (setting)); break; case PROP_SEEN_BSSIDS: - g_value_take_boxed (value, _nm_utils_slist_to_strv (priv->seen_bssids)); + g_value_take_boxed (value, _nm_utils_slist_to_strv (priv->seen_bssids, TRUE)); break; case PROP_HIDDEN: g_value_set_boolean (value, nm_setting_wireless_get_hidden (setting)); diff --git a/libnm-core/nm-utils-private.h b/libnm-core/nm-utils-private.h index 3854d67951..ffdc631566 100644 --- a/libnm-core/nm-utils-private.h +++ b/libnm-core/nm-utils-private.h @@ -40,12 +40,6 @@ GVariant * _nm_utils_bytes_to_dbus (const GValue *prop_value); void _nm_utils_bytes_from_dbus (GVariant *dbus_value, GValue *prop_value); -GSList * _nm_utils_strv_to_slist (char **strv); -char ** _nm_utils_slist_to_strv (GSList *slist); - -GPtrArray * _nm_utils_strv_to_ptrarray (char **strv); -char ** _nm_utils_ptrarray_to_strv (GPtrArray *ptrarray); - char * _nm_utils_hwaddr_canonical_or_invalid (const char *mac, gssize length); #endif diff --git a/libnm-core/nm-utils.c b/libnm-core/nm-utils.c index 16561175da..14686ed1fd 100644 --- a/libnm-core/nm-utils.c +++ b/libnm-core/nm-utils.c @@ -483,6 +483,36 @@ _nm_utils_strv_find_first (char **list, gssize len, const char *needle) return -1; } +char ** +_nm_utils_strv_cleanup (char **strv, + gboolean strip_whitespace, + gboolean skip_empty, + gboolean skip_repeated) +{ + guint i, j; + + if (!strv || !*strv) + return strv; + + if (strip_whitespace) { + for (i = 0; strv[i]; i++) + g_strstrip (strv[i]); + } + if (!skip_empty && !skip_repeated) + return strv; + j = 0; + for (i = 0; strv[i]; i++) { + if ( (skip_empty && !*strv[i]) + || (skip_repeated && _nm_utils_strv_find_first (strv, j, strv[i]) >= 0)) + g_free (strv[i]); + else + strv[j++] = strv[i]; + } + strv[j] = NULL; + return strv; +} + + gboolean _nm_utils_string_slist_validate (GSList *list, const char **valid_values) { @@ -725,21 +755,26 @@ _nm_utils_bytes_from_dbus (GVariant *dbus_value, } GSList * -_nm_utils_strv_to_slist (char **strv) +_nm_utils_strv_to_slist (char **strv, gboolean deep_copy) { int i; GSList *list = NULL; if (strv) { - for (i = 0; strv[i]; i++) - list = g_slist_prepend (list, g_strdup (strv[i])); + if (deep_copy) { + for (i = 0; strv[i]; i++) + list = g_slist_prepend (list, g_strdup (strv[i])); + } else { + for (i = 0; strv[i]; i++) + list = g_slist_prepend (list, strv[i]); + } } return g_slist_reverse (list); } char ** -_nm_utils_slist_to_strv (GSList *slist) +_nm_utils_slist_to_strv (GSList *slist, gboolean deep_copy) { GSList *iter; char **strv; @@ -748,8 +783,13 @@ _nm_utils_slist_to_strv (GSList *slist) len = g_slist_length (slist); strv = g_new (char *, len + 1); - for (i = 0, iter = slist; iter; iter = iter->next, i++) - strv[i] = g_strdup (iter->data); + if (deep_copy) { + for (i = 0, iter = slist; iter; iter = iter->next, i++) + strv[i] = g_strdup (iter->data); + } else { + for (i = 0, iter = slist; iter; iter = iter->next, i++) + strv[i] = iter->data; + } strv[i] = NULL; return strv; diff --git a/libnm-core/tests/test-keyfile.c b/libnm-core/tests/test-keyfile.c index 28d659f985..99f88ac543 100644 --- a/libnm-core/tests/test-keyfile.c +++ b/libnm-core/tests/test-keyfile.c @@ -81,39 +81,6 @@ _keyfile_load_from_data (const char *str) return keyfile; } -static gboolean -_keyfile_a_contains_all_in_b (GKeyFile *kf_a, GKeyFile *kf_b) -{ - gs_strfreev char **groups = NULL; - guint i, j; - - if (kf_a == kf_b) - return TRUE; - - groups = g_key_file_get_groups (kf_a, NULL); - for (i = 0; groups && groups[i]; i++) { - gs_strfreev char **keys = NULL; - - keys = g_key_file_get_keys (kf_a, groups[i], NULL, NULL); - if (keys) { - for (j = 0; keys[j]; j++) { - gs_free char *key_a = g_key_file_get_value (kf_a, groups[i], keys[j], NULL); - gs_free char *key_b = g_key_file_get_value (kf_b, groups[i], keys[j], NULL); - - if (g_strcmp0 (key_a, key_b) != 0) - return FALSE; - } - } - } - return TRUE; -} - -static gboolean -_keyfile_equals (GKeyFile *kf_a, GKeyFile *kf_b) -{ - return _keyfile_a_contains_all_in_b (kf_a, kf_b) && _keyfile_a_contains_all_in_b (kf_b, kf_a); -} - static GKeyFile * _nm_keyfile_write (NMConnection *connection, NMKeyfileWriteHandler handler, @@ -185,7 +152,7 @@ _keyfile_convert (NMConnection **con, c0_k1_c2 = _nm_keyfile_read (c0_k1, keyfile_name, base_dir, read_handler, read_data, FALSE); c0_k1_c2_k3 = _nm_keyfile_write (c0_k1_c2, write_handler, write_data); - _keyfile_equals (c0_k1, c0_k1_c2_k3); + g_assert (_nm_keyfile_equals (c0_k1, c0_k1_c2_k3, TRUE)); } if (k0) { NMSetting8021x *s1, *s2; @@ -247,7 +214,7 @@ _keyfile_convert (NMConnection **con, else { /* finally, if both a keyfile and a connection are given, assert that they are equal * after a round of conversion. */ - _keyfile_equals (c0_k1, k0_c1_k2); + g_assert (_nm_keyfile_equals (c0_k1, k0_c1_k2, TRUE)); nmtst_assert_connection_equals (k0_c1, FALSE, c0_k1_c2, FALSE); } } diff --git a/man/NetworkManager.conf.xml.in b/man/NetworkManager.conf.xml.in index bd187ff57c..40fa49dd7a 100644 --- a/man/NetworkManager.conf.xml.in +++ b/man/NetworkManager.conf.xml.in @@ -62,7 +62,8 @@ Copyright 2010 - 2014 Red Hat, Inc. <para> For keys that take a list of devices as their value, you can specify devices by their MAC addresses or interface names, or - "*" to specify all devices. + "*" to specify all devices. See <xref linkend="device-spec"/> + below. </para> <para> Minimal system settings configuration file looks like this: @@ -76,6 +77,7 @@ Copyright 2010 - 2014 Red Hat, Inc. append a value to a previously-set list-valued key by doing: <programlisting> plugins+=another-plugin + plugins-=remove-me </programlisting> </para> </refsect1> @@ -465,7 +467,7 @@ ipv6.ip6-privacy=1 </para> <para> - The sections are considered in order of appearance, with the + The sections within one file are considered in order of appearance, with the exception that the <literal>[connection]</literal> section is always considered last. In the example above, this order is <literal>[connection-wifi-wlan0]</literal>, <literal>[connection-wlan-other]</literal>, and <literal>[connection]</literal>. @@ -478,6 +480,11 @@ ipv6.ip6-privacy=1 "[connection-wifi-wlan0]" matches the device, it does not contain that property and the search continues. </para> + <para> + When having different sections in multiple files, sections from files that are read + later have higher priority. So within one file the priority of the sections is + top-to-bottom. Across multiple files later definitions take precedence. + </para> <para> <variablelist> diff --git a/src/NetworkManagerUtils.c b/src/NetworkManagerUtils.c index 6cd937da99..26ec1b92d7 100644 --- a/src/NetworkManagerUtils.c +++ b/src/NetworkManagerUtils.c @@ -1310,11 +1310,32 @@ nm_match_spec_s390_subchannels (const GSList *specs, const char *subchannels) return match; } +/** + * nm_match_spec_split: + * @value: the string of device specs + * + * Splits the specs from the string and returns them as individual + * entires in a #GSList. + * + * It does not validate any specs, it basically just does a special + * strsplit with ',' or ';' as separators and supporting '\\' as + * escape character. + * + * Leading and trailing spaces of each entry are removed. But the user + * can preserve them by specifying "\\s has 2 leading" or "has 2 trailing \\s". + * + * Specs can have a qualifier like "interface-name:". We still don't strip + * any whitespace after the colon, so "interface-name: X" matches an interface + * named " X". + * + * Returns: (transfer-full): the list of device specs. + */ GSList * nm_match_spec_split (const char *value) { char *string_value, *p, *q0, *q; GSList *pieces = NULL; + int trailing_ws; if (!value || !*value) return NULL; @@ -1325,7 +1346,13 @@ nm_match_spec_split (const char *value) string_value = g_new (gchar, strlen (value) + 1); p = (gchar *) value; + + /* skip over leading whitespace */ + while (g_ascii_isspace (*p)) + p++; + q0 = q = string_value; + trailing_ws = 0; while (*p) { if (*p == '\\') { p++; @@ -1357,27 +1384,117 @@ nm_match_spec_split (const char *value) } break; } + if (*p == '\0') + break; + p++; + trailing_ws = 0; } else { *q = *p; - if (NM_IN_SET (*p, ',', ';')) { - if (q0 < q) - pieces = g_slist_prepend (pieces, g_strndup (q0, q - q0)); + if (*p == '\0') + break; + if (g_ascii_isspace (*p)) { + trailing_ws++; + p++; + } else if (NM_IN_SET (*p, ',', ';')) { + if (q0 < q - trailing_ws) + pieces = g_slist_prepend (pieces, g_strndup (q0, (q - q0) - trailing_ws)); q0 = q + 1; - } + p++; + trailing_ws = 0; + while (g_ascii_isspace (*p)) + p++; + } else + p++; } - if (*p == '\0') - break; q++; - p++; } *q = '\0'; - if (q0 < q) - pieces = g_slist_prepend (pieces, g_strndup (q0, q - q0)); + if (q0 < q - trailing_ws) + pieces = g_slist_prepend (pieces, g_strndup (q0, (q - q0) - trailing_ws)); g_free (string_value); return g_slist_reverse (pieces); } +/** + * nm_match_spec_join: + * @specs: the device specs to join + * + * This is based on g_key_file_parse_string_as_value(), analog to + * nm_match_spec_split() which is based on g_key_file_parse_value_as_string(). + * + * Returns: (transfer-full): a joined list of device specs that can be + * split again with nm_match_spec_split(). Note that + * nm_match_spec_split (nm_match_spec_join (specs)) yields the original + * result (which is not true the other way around because there are multiple + * ways to encode the same joined specs string). + */ +char * +nm_match_spec_join (GSList *specs) +{ + const char *p; + GString *str; + + str = g_string_new (""); + + for (; specs; specs = specs->next) { + p = specs->data; + + if (!p || !*p) + continue; + + if (str->len > 0) + g_string_append_c (str, ','); + + /* escape leading whitespace */ + switch (*p) { + case ' ': + g_string_append (str, "\\s"); + p++; + break; + case '\t': + g_string_append (str, "\\t"); + p++; + break; + } + + for (; *p; p++) { + switch (*p) { + case '\n': + g_string_append (str, "\\n"); + break; + case '\r': + g_string_append (str, "\\r"); + break; + case '\\': + g_string_append (str, "\\\\"); + break; + case ',': + g_string_append (str, "\\,"); + break; + case ';': + g_string_append (str, "\\;"); + break; + default: + g_string_append_c (str, *p); + break; + } + } + + /* escape trailing whitespaces */ + switch (str->str[str->len - 1]) { + case ' ': + g_string_overwrite (str, str->len - 1, "\\s"); + break; + case '\t': + g_string_overwrite (str, str->len - 1, "\\t"); + break; + } + } + + return g_string_free (str, FALSE); +} + const char * nm_utils_get_shared_wifi_permission (NMConnection *connection) { diff --git a/src/NetworkManagerUtils.h b/src/NetworkManagerUtils.h index f8e759142e..1864547f56 100644 --- a/src/NetworkManagerUtils.h +++ b/src/NetworkManagerUtils.h @@ -98,6 +98,7 @@ NMMatchSpecMatchType nm_match_spec_hwaddr (const GSList *specs, const char *hwad NMMatchSpecMatchType nm_match_spec_s390_subchannels (const GSList *specs, const char *subchannels); NMMatchSpecMatchType nm_match_spec_interface_name (const GSList *specs, const char *interface_name); GSList *nm_match_spec_split (const char *value); +char *nm_match_spec_join (GSList *specs); const char *nm_utils_get_shared_wifi_permission (NMConnection *connection); diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c index 40802c6e72..f34ed7ef1f 100644 --- a/src/devices/nm-device.c +++ b/src/devices/nm-device.c @@ -784,7 +784,7 @@ _get_ipx_route_metric (NMDevice *self, /* use the current NMConfigData, which makes this configuration reloadable. * Note that that means that the route-metric might change between SIGHUP. * You must cache the returned value if that is a problem. */ - value = nm_config_data_get_connection_default (nm_config_get_data (nm_config_get ()), + value = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA, is_v4 ? "ipv4.route-metric" : "ipv6.route-metric", self); if (value) { route_metric = _nm_utils_ascii_str_to_int64 (value, 10, 0, G_MAXUINT32, -1); @@ -2301,7 +2301,7 @@ nm_device_generate_connection (NMDevice *self, NMDevice *master) && g_strcmp0 (ip6_method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL) == 0 && !nm_setting_connection_get_master (NM_SETTING_CONNECTION (s_con)) && !priv->slaves - && !nm_config_data_get_assume_ipv6ll_only (nm_config_get_data (nm_config_get ()), self)) { + && !nm_config_data_get_assume_ipv6ll_only (NM_CONFIG_GET_DATA, self)) { _LOGD (LOGD_DEVICE, "ignoring generated connection (IPv6LL-only and not in master-slave relationship)"); g_object_unref (connection); connection = NULL; @@ -4859,7 +4859,7 @@ _ip6_privacy_get (NMDevice *self) } } - value = nm_config_data_get_connection_default (nm_config_get_data (nm_config_get ()), + value = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA, "ipv6.ip6-privacy", self); /* 2.) use the default value from the configuration. */ @@ -8359,7 +8359,7 @@ _set_state_full (NMDevice *self, if (nm_device_has_capability (self, NM_DEVICE_CAP_CARRIER_DETECT)) { /* We cache the ignore_carrier state to not react on config-reloads while the connection * is active. But on deactivating, reset the ignore-carrier flag to the current state. */ - priv->ignore_carrier = nm_config_data_get_ignore_carrier (nm_config_get_data (nm_config_get ()), self); + priv->ignore_carrier = nm_config_data_get_ignore_carrier (NM_CONFIG_GET_DATA, self); } if (quitting) { diff --git a/src/main.c b/src/main.c index 8a44db31d3..dfc307b5db 100644 --- a/src/main.c +++ b/src/main.c @@ -240,8 +240,8 @@ do_early_setup (int *argc, char **argv[], NMConfigCmdLineOptions *config_cli) N_("Log domains separated by ',': any combination of [%s]"), "PLATFORM,RFKILL,WIFI" }, { "g-fatal-warnings", 0, 0, G_OPTION_ARG_NONE, &global_opt.g_fatal_warnings, N_("Make all warnings fatal"), NULL }, - { "pid-file", 'p', 0, G_OPTION_ARG_FILENAME, &global_opt.pidfile, N_("Specify the location of a PID file"), N_("filename") }, - { "state-file", 0, 0, G_OPTION_ARG_FILENAME, &global_opt.state_file, N_("State file location"), N_("/path/to/state.file") }, + { "pid-file", 'p', 0, G_OPTION_ARG_FILENAME, &global_opt.pidfile, N_("Specify the location of a PID file"), N_(NM_DEFAULT_PID_FILE) }, + { "state-file", 0, 0, G_OPTION_ARG_FILENAME, &global_opt.state_file, N_("State file location"), N_(NM_DEFAULT_SYSTEM_STATE_FILE) }, { "run-from-build-dir", 0, 0, G_OPTION_ARG_NONE, &global_opt.run_from_build_dir, "Run from build directory", NULL }, {NULL} }; @@ -423,6 +423,7 @@ main (int argc, char *argv[]) dbus_glib_global_set_disable_legacy_property_access (); nm_log_info (LOGD_CORE, "Read config: %s", nm_config_data_get_config_description (nm_config_get_data (config))); + nm_config_data_log (nm_config_get_data (config), "CONFIG: "); nm_log_dbg (LOGD_CORE, "WEXT support is %s", #if HAVE_WEXT "enabled" diff --git a/src/nm-config-data.c b/src/nm-config-data.c index a4effbe0ce..dc20064b19 100644 --- a/src/nm-config-data.c +++ b/src/nm-config-data.c @@ -27,7 +27,9 @@ #include "nm-device.h" #include "gsystem-local-alloc.h" #include "nm-core-internal.h" +#include "nm-keyfile-internal.h" #include "nm-macros-internal.h" +#include "nm-logging.h" typedef struct { char *group_name; @@ -60,6 +62,7 @@ typedef struct { struct { char **arr; GSList *specs; + GSList *specs_config; } no_auto_default; GSList *ignore_carrier; @@ -104,12 +107,55 @@ nm_config_data_get_config_description (const NMConfigData *self) return NM_CONFIG_DATA_GET_PRIVATE (self)->config_description; } +gboolean +nm_config_data_has_group (const NMConfigData *self, const char *group) +{ + g_return_val_if_fail (NM_IS_CONFIG_DATA (self), FALSE); + g_return_val_if_fail (group && *group, FALSE); + + return g_key_file_has_group (NM_CONFIG_DATA_GET_PRIVATE (self)->keyfile, group); +} + char * -nm_config_data_get_value (const NMConfigData *self, const char *group, const char *key, GError **error) +nm_config_data_get_value (const NMConfigData *self, const char *group, const char *key, NMConfigGetValueFlags flags) { - g_return_val_if_fail (self, NULL); + g_return_val_if_fail (NM_IS_CONFIG_DATA (self), NULL); + g_return_val_if_fail (group && *group, NULL); + g_return_val_if_fail (key && *key, NULL); + + return nm_config_keyfile_get_value (NM_CONFIG_DATA_GET_PRIVATE (self)->keyfile, group, key, flags); +} + +gboolean +nm_config_data_has_value (const NMConfigData *self, const char *group, const char *key, NMConfigGetValueFlags flags) +{ + gs_free char *value = NULL; - return g_key_file_get_string (NM_CONFIG_DATA_GET_PRIVATE (self)->keyfile, group, key, error); + g_return_val_if_fail (NM_IS_CONFIG_DATA (self), FALSE); + g_return_val_if_fail (group && *group, FALSE); + g_return_val_if_fail (key && *key, FALSE); + + value = nm_config_keyfile_get_value (NM_CONFIG_DATA_GET_PRIVATE (self)->keyfile, group, key, flags); + return !!value; +} + +gint +nm_config_data_get_value_boolean (const NMConfigData *self, const char *group, const char *key, gint default_value) +{ + char *str; + gint value = default_value; + + g_return_val_if_fail (NM_IS_CONFIG_DATA (self), default_value); + g_return_val_if_fail (group && *group, default_value); + g_return_val_if_fail (key && *key, default_value); + + /* when parsing the boolean, base it on the raw value from g_key_file_get_value(). */ + str = g_key_file_get_value (NM_CONFIG_DATA_GET_PRIVATE (self)->keyfile, group, key, NULL); + if (str) { + value = nm_config_parse_boolean (str, default_value); + g_free (str); + } + return value; } const char * @@ -144,12 +190,17 @@ nm_config_data_get_no_auto_default (const NMConfigData *self) return (const char *const*) NM_CONFIG_DATA_GET_PRIVATE (self)->no_auto_default.arr; } -const GSList * -nm_config_data_get_no_auto_default_list (const NMConfigData *self) +gboolean +nm_config_data_get_no_auto_default_for_device (const NMConfigData *self, NMDevice *device) { - g_return_val_if_fail (self, NULL); + NMConfigDataPrivate *priv; + + g_return_val_if_fail (NM_IS_CONFIG_DATA (self), FALSE); + g_return_val_if_fail (NM_IS_DEVICE (device), FALSE); - return NM_CONFIG_DATA_GET_PRIVATE (self)->no_auto_default.specs; + priv = NM_CONFIG_DATA_GET_PRIVATE (self); + return nm_device_spec_match_list (device, priv->no_auto_default.specs) + || nm_device_spec_match_list (device, priv->no_auto_default.specs_config); } const char * @@ -180,6 +231,121 @@ nm_config_data_get_assume_ipv6ll_only (const NMConfigData *self, NMDevice *devic /************************************************************************/ +/** + * nm_config_data_get_groups: + * @self: the #NMConfigData instance + * + * Returns: (transfer-full): the list of groups in the configuration. The order + * of the section is undefined, as the configuration gets merged from multiple + * sources. + */ +char ** +nm_config_data_get_groups (const NMConfigData *self) +{ + g_return_val_if_fail (NM_IS_CONFIG_DATA (self), NULL); + + return g_key_file_get_groups (NM_CONFIG_DATA_GET_PRIVATE (self)->keyfile, NULL); +} + +char ** +nm_config_data_get_keys (const NMConfigData *self, const char *group) +{ + g_return_val_if_fail (NM_IS_CONFIG_DATA (self), NULL); + g_return_val_if_fail (group && *group, NULL); + + return g_key_file_get_keys (NM_CONFIG_DATA_GET_PRIVATE (self)->keyfile, group, NULL, NULL); +} + +/************************************************************************/ + +static int +_nm_config_data_log_sort (const char **pa, const char **pb, gpointer dummy) +{ + gboolean a_is_connection, b_is_connection; + const char *a = *pa; + const char *b = *pb; + + /* we sort connection groups before intern groups (to the end). */ + a_is_connection = a && g_str_has_prefix (a, NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION); + b_is_connection = b && g_str_has_prefix (b, NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION); + + if (a_is_connection && b_is_connection) { + /* if both are connection groups, we want the explicit [connection] group first. */ + a_is_connection = a[STRLEN (NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION)] == '\0'; + b_is_connection = b[STRLEN (NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION)] == '\0'; + + if (a_is_connection != b_is_connection) { + if (a_is_connection) + return -1; + return 1; + } + /* the sections are ordered lowest-priority first. Reverse their order. */ + return pa < pb ? 1 : -1; + } + if (a_is_connection && !b_is_connection) + return 1; + if (b_is_connection && !a_is_connection) + return -1; + + /* no reordering. */ + return 0; +} + +void +nm_config_data_log (const NMConfigData *self, const char *prefix) +{ + NMConfigDataPrivate *priv; + gs_strfreev char **groups = NULL; + gsize ngroups; + guint g, k; + + g_return_if_fail (NM_IS_CONFIG_DATA (self)); + + if (!nm_logging_enabled (LOGL_DEBUG, LOGD_CORE)) + return; + + if (!prefix) + prefix = ""; + +#define _LOG(...) _nm_log (LOGL_DEBUG, LOGD_CORE, 0, "%s"_NM_UTILS_MACRO_FIRST(__VA_ARGS__), prefix _NM_UTILS_MACRO_REST (__VA_ARGS__)) + + priv = NM_CONFIG_DATA_GET_PRIVATE (self); + + groups = g_key_file_get_groups (priv->keyfile, &ngroups); + if (!groups) + ngroups = 0; + + if (groups && groups[0]) { + g_qsort_with_data (groups, ngroups, + sizeof (char *), + (GCompareDataFunc) _nm_config_data_log_sort, + NULL); + } + + _LOG ("config-data[%p]: %lu groups", self, (unsigned long) ngroups); + + for (g = 0; g < ngroups; g++) { + const char *group = groups[g]; + gs_strfreev char **keys = NULL; + + _LOG (""); + _LOG ("[%s]", group); + + keys = g_key_file_get_keys (priv->keyfile, group, NULL, NULL); + for (k = 0; keys && keys[k]; k++) { + const char *key = keys[k]; + gs_free char *value = NULL; + + value = g_key_file_get_value (priv->keyfile, group, key, NULL); + _LOG (" %s=%s", key, value); + } + } + +#undef _LOG +} + +/************************************************************************/ + char * nm_config_data_get_connection_default (const NMConfigData *self, const char *property, @@ -201,7 +367,14 @@ nm_config_data_get_connection_default (const NMConfigData *self, char *value; gboolean match; - value = g_key_file_get_value (priv->keyfile, connection_info->group_name, property, NULL); + /* FIXME: Here we use g_key_file_get_string(). This should be in sync with what keyfile-reader + * does. + * + * Unfortunately that is currently not possible because keyfile-reader does the two steps + * string_to_value(keyfile_to_string(keyfile)) in one. Optimally, keyfile library would + * expose both functions, and we would return here keyfile_to_string(keyfile). + * The caller then could convert the string to the proper value via string_to_value(value). */ + value = g_key_file_get_string (priv->keyfile, connection_info->group_name, property, NULL); if (!value && !connection_info->stop_match) continue; @@ -216,58 +389,59 @@ nm_config_data_get_connection_default (const NMConfigData *self, return NULL; } +static void +_get_connection_info_init (ConnectionInfo *connection_info, GKeyFile *keyfile, char *group) +{ + /* pass ownership of @group on... */ + connection_info->group_name = group; + + connection_info->match_device.spec = nm_config_get_device_match_spec (keyfile, + group, + "match-device", + &connection_info->match_device.has); + connection_info->stop_match = nm_config_keyfile_get_boolean (keyfile, group, "stop-match", FALSE); +} + static ConnectionInfo * _get_connection_infos (GKeyFile *keyfile) { char **groups; - guint i; + gsize i, j, ngroups; char *connection_tag = NULL; - GSList *connection_groups = NULL; ConnectionInfo *connection_infos = NULL; /* get the list of existing [connection.\+] sections that we consider - * for nm_config_data_get_connection_default(). Also, get them - * in the right order. */ - groups = g_key_file_get_groups (keyfile, NULL); - for (i = 0; groups && groups[i]; i++) { - if (g_str_has_prefix (groups[i], "connection")) { - if (strlen (groups[i]) == STRLEN ("connection")) - connection_tag = groups[i]; - else - connection_groups = g_slist_prepend (connection_groups, groups[i]); - } else - g_free (groups[i]); + * for nm_config_data_get_connection_default(). + * + * We expect the sections in their right order, with lowest priority + * first. Only exception is the (literal) [connection] section, which + * we will always reorder to the end. */ + groups = g_key_file_get_groups (keyfile, &ngroups); + if (!groups) + ngroups = 0; + else if (ngroups > 0) { + for (i = 0, j = 0; i < ngroups; i++) { + if (g_str_has_prefix (groups[i], NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION)) { + if (groups[i][STRLEN (NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION)] == '\0') + connection_tag = groups[i]; + else + groups[j++] = groups[i]; + } else + g_free (groups[i]); + } + ngroups = j; } - g_free (groups); - if (connection_tag) { - /* We want the group "connection" checked at last, so that - * all other "connection.\+" have preference. Those other - * groups are checked in order of appearance. */ - connection_groups = g_slist_prepend (connection_groups, connection_tag); + + connection_infos = g_new0 (ConnectionInfo, ngroups + 1 + (connection_tag ? 1 : 0)); + for (i = 0; i < ngroups; i++) { + /* pass ownership of @group on... */ + _get_connection_info_init (&connection_infos[i], keyfile, groups[ngroups - i - 1]); } - if (connection_groups) { - guint len = g_slist_length (connection_groups); - GSList *iter; - - connection_infos = g_new0 (ConnectionInfo, len + 1); - for (iter = connection_groups; iter; iter = iter->next) { - ConnectionInfo *connection_info; - char *value; - - nm_assert (len >= 1); - connection_info = &connection_infos[--len]; - connection_info->group_name = iter->data; - - value = g_key_file_get_value (keyfile, iter->data, "match-device", NULL); - if (value) { - connection_info->match_device.has = TRUE; - connection_info->match_device.spec = nm_match_spec_split (value); - g_free (value); - } - connection_info->stop_match = nm_config_keyfile_get_boolean (keyfile, iter->data, "stop-match", FALSE); - } - g_slist_free (connection_groups); + if (connection_tag) { + /* pass ownership of @connection_tag on... */ + _get_connection_info_init (&connection_infos[i], keyfile, connection_tag); } + g_free (groups); return connection_infos; } @@ -275,30 +449,13 @@ _get_connection_infos (GKeyFile *keyfile) /************************************************************************/ static gboolean -_keyfile_a_contains_all_in_b (GKeyFile *kf_a, GKeyFile *kf_b) +_slist_str_equals (GSList *a, GSList *b) { - gs_strfreev char **groups = NULL; - guint i, j; - - if (kf_a == kf_b) - return TRUE; - - groups = g_key_file_get_groups (kf_a, NULL); - for (i = 0; groups && groups[i]; i++) { - gs_strfreev char **keys = NULL; - - keys = g_key_file_get_keys (kf_a, groups[i], NULL, NULL); - if (keys) { - for (j = 0; keys[j]; j++) { - gs_free char *key_a = g_key_file_get_value (kf_a, groups[i], keys[j], NULL); - gs_free char *key_b = g_key_file_get_value (kf_b, groups[i], keys[j], NULL); - - if (g_strcmp0 (key_a, key_b) != 0) - return FALSE; - } - } + while (a && b && g_strcmp0 (a->data, b->data) == 0) { + a = a->next; + b = b->next; } - return TRUE; + return !a && !b; } NMConfigChangeFlags @@ -306,7 +463,6 @@ nm_config_data_diff (NMConfigData *old_data, NMConfigData *new_data) { NMConfigChangeFlags changes = NM_CONFIG_CHANGE_NONE; NMConfigDataPrivate *priv_old, *priv_new; - GSList *spec_old, *spec_new; g_return_val_if_fail (NM_IS_CONFIG_DATA (old_data), NM_CONFIG_CHANGE_NONE); g_return_val_if_fail (NM_IS_CONFIG_DATA (new_data), NM_CONFIG_CHANGE_NONE); @@ -314,8 +470,7 @@ nm_config_data_diff (NMConfigData *old_data, NMConfigData *new_data) priv_old = NM_CONFIG_DATA_GET_PRIVATE (old_data); priv_new = NM_CONFIG_DATA_GET_PRIVATE (new_data); - if ( !_keyfile_a_contains_all_in_b (priv_old->keyfile, priv_new->keyfile) - || !_keyfile_a_contains_all_in_b (priv_new->keyfile, priv_old->keyfile)) + if (!_nm_keyfile_equals (priv_old->keyfile, priv_new->keyfile, TRUE)) changes |= NM_CONFIG_CHANGE_VALUES; if ( g_strcmp0 (nm_config_data_get_config_main_file (old_data), nm_config_data_get_config_main_file (new_data)) != 0 @@ -327,13 +482,8 @@ nm_config_data_diff (NMConfigData *old_data, NMConfigData *new_data) || g_strcmp0 (nm_config_data_get_connectivity_response (old_data), nm_config_data_get_connectivity_response (new_data))) changes |= NM_CONFIG_CHANGE_CONNECTIVITY; - spec_old = priv_old->no_auto_default.specs; - spec_new = priv_new->no_auto_default.specs; - while (spec_old && spec_new && strcmp (spec_old->data, spec_new->data) == 0) { - spec_old = spec_old->next; - spec_new = spec_new->next; - } - if (spec_old || spec_new) + if ( !_slist_str_equals (priv_old->no_auto_default.specs, priv_new->no_auto_default.specs) + || !_slist_str_equals (priv_old->no_auto_default.specs_config, priv_new->no_auto_default.specs_config)) changes |= NM_CONFIG_CHANGE_NO_AUTO_DEFAULT; if (g_strcmp0 (nm_config_data_get_dns_mode (old_data), nm_config_data_get_dns_mode (new_data))) @@ -359,9 +509,6 @@ get_property (GObject *object, case PROP_CONFIG_DESCRIPTION: g_value_set_string (value, nm_config_data_get_config_description (self)); break; - case PROP_NO_AUTO_DEFAULT: - g_value_take_boxed (value, g_strdupv ((char **) nm_config_data_get_no_auto_default (self))); - break; case PROP_CONNECTIVITY_URI: g_value_set_string (value, nm_config_data_get_connectivity_uri (self)); break; @@ -385,7 +532,6 @@ set_property (GObject *object, { NMConfigData *self = NM_CONFIG_DATA (object); NMConfigDataPrivate *priv = NM_CONFIG_DATA_GET_PRIVATE (self); - guint i; /* This type is immutable. All properties are construct only. */ switch (prop_id) { @@ -401,12 +547,24 @@ set_property (GObject *object, priv->keyfile = nm_config_create_keyfile (); break; case PROP_NO_AUTO_DEFAULT: - priv->no_auto_default.arr = g_strdupv (g_value_get_boxed (value)); - if (!priv->no_auto_default.arr) - priv->no_auto_default.arr = g_new0 (char *, 1); - for (i = 0; priv->no_auto_default.arr[i]; i++) - priv->no_auto_default.specs = g_slist_prepend (priv->no_auto_default.specs, priv->no_auto_default.arr[i]); - priv->no_auto_default.specs = g_slist_reverse (priv->no_auto_default.specs); + { + char **value_arr = g_value_get_boxed (value); + guint i, j = 0; + + priv->no_auto_default.arr = g_new (char *, g_strv_length (value_arr) + 1); + priv->no_auto_default.specs = NULL; + + for (i = 0; value_arr && value_arr[i]; i++) { + if ( *value_arr[i] + && nm_utils_hwaddr_valid (value_arr[i], -1) + && _nm_utils_strv_find_first (value_arr, i, value_arr[i]) < 0) { + priv->no_auto_default.arr[j++] = g_strdup (value_arr[i]); + priv->no_auto_default.specs = g_slist_prepend (priv->no_auto_default.specs, g_strdup_printf ("mac:%s", value_arr[i])); + } + } + priv->no_auto_default.arr[j++] = NULL; + priv->no_auto_default.specs = g_slist_reverse (priv->no_auto_default.specs); + } break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -431,7 +589,8 @@ finalize (GObject *gobject) g_free (priv->connectivity.uri); g_free (priv->connectivity.response); - g_slist_free (priv->no_auto_default.specs); + g_slist_free_full (priv->no_auto_default.specs, g_free); + g_slist_free_full (priv->no_auto_default.specs_config, g_free); g_strfreev (priv->no_auto_default.arr); g_free (priv->dns_mode); @@ -466,21 +625,23 @@ constructed (GObject *object) priv->connection_infos = _get_connection_infos (priv->keyfile); - priv->connectivity.uri = g_key_file_get_value (priv->keyfile, "connectivity", "uri", NULL); - priv->connectivity.response = g_key_file_get_value (priv->keyfile, "connectivity", "response", NULL); + priv->connectivity.uri = nm_strstrip (g_key_file_get_string (priv->keyfile, NM_CONFIG_KEYFILE_GROUP_CONNECTIVITY, "uri", NULL)); + priv->connectivity.response = g_key_file_get_string (priv->keyfile, NM_CONFIG_KEYFILE_GROUP_CONNECTIVITY, "response", NULL); /* On missing config value, fallback to 300. On invalid value, disable connectivity checking by setting * the interval to zero. */ - interval = g_key_file_get_value (priv->keyfile, "connectivity", "interval", NULL); + interval = g_key_file_get_string (priv->keyfile, NM_CONFIG_KEYFILE_GROUP_CONNECTIVITY, "interval", NULL); priv->connectivity.interval = interval ? _nm_utils_ascii_str_to_int64 (interval, 10, 0, G_MAXUINT, 0) : NM_CONFIG_DEFAULT_CONNECTIVITY_INTERVAL; g_free (interval); - priv->dns_mode = g_key_file_get_value (priv->keyfile, "main", "dns", NULL); + priv->dns_mode = nm_strstrip (g_key_file_get_string (priv->keyfile, NM_CONFIG_KEYFILE_GROUP_MAIN, "dns", NULL)); + + priv->ignore_carrier = nm_config_get_device_match_spec (priv->keyfile, NM_CONFIG_KEYFILE_GROUP_MAIN, "ignore-carrier", NULL); + priv->assume_ipv6ll_only = nm_config_get_device_match_spec (priv->keyfile, NM_CONFIG_KEYFILE_GROUP_MAIN, "assume-ipv6ll-only", NULL); - priv->ignore_carrier = nm_config_get_device_match_spec (priv->keyfile, "main", "ignore-carrier"); - priv->assume_ipv6ll_only = nm_config_get_device_match_spec (priv->keyfile, "main", "assume-ipv6ll-only"); + priv->no_auto_default.specs_config = nm_config_get_device_match_spec (priv->keyfile, NM_CONFIG_KEYFILE_GROUP_MAIN, "no-auto-default", NULL); G_OBJECT_CLASS (nm_config_data_parent_class)->constructed (object); } @@ -575,7 +736,7 @@ nm_config_data_class_init (NMConfigDataClass *config_class) (object_class, PROP_NO_AUTO_DEFAULT, g_param_spec_boxed (NM_CONFIG_DATA_NO_AUTO_DEFAULT, "", "", G_TYPE_STRV, - G_PARAM_READWRITE | + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); diff --git a/src/nm-config-data.h b/src/nm-config-data.h index 64ad372b3b..cc7f95e211 100644 --- a/src/nm-config-data.h +++ b/src/nm-config-data.h @@ -45,6 +45,23 @@ G_BEGIN_DECLS #define NM_CONFIG_DATA_NO_AUTO_DEFAULT "no-auto-default" #define NM_CONFIG_DATA_DNS_MODE "dns" +typedef enum { /*<flags >*/ + NM_CONFIG_GET_VALUE_NONE = 0, + + /* use g_key_file_get_value() instead of g_key_file_get_string(). */ + NM_CONFIG_GET_VALUE_RAW = (1LL << 0), + + /* strip whitespaces */ + NM_CONFIG_GET_VALUE_STRIP = (1LL << 1), + + /* if the returned string would be the empty word, return NULL. */ + NM_CONFIG_GET_VALUE_NO_EMPTY = (1LL << 2), + + /* special flag to read device spec. You want to use this before passing the + * value to nm_match_spec_split(). */ + NM_CONFIG_GET_VALUE_TYPE_SPEC = NM_CONFIG_GET_VALUE_RAW, +} NMConfigGetValueFlags; + typedef enum { /*< flags >*/ NM_CONFIG_CHANGE_NONE = 0, @@ -80,17 +97,22 @@ NMConfigData *nm_config_data_new_update_no_auto_default (const NMConfigData *bas NMConfigChangeFlags nm_config_data_diff (NMConfigData *old_data, NMConfigData *new_data); +void nm_config_data_log (const NMConfigData *config_data, const char *prefix); + const char *nm_config_data_get_config_main_file (const NMConfigData *config_data); const char *nm_config_data_get_config_description (const NMConfigData *config_data); -char *nm_config_data_get_value (const NMConfigData *config_data, const char *group, const char *key, GError **error); +gboolean nm_config_data_has_group (const NMConfigData *self, const char *group); +gboolean nm_config_data_has_value (const NMConfigData *self, const char *group, const char *key, NMConfigGetValueFlags flags); +char *nm_config_data_get_value (const NMConfigData *config_data, const char *group, const char *key, NMConfigGetValueFlags flags); +gint nm_config_data_get_value_boolean (const NMConfigData *self, const char *group, const char *key, gint default_value); const char *nm_config_data_get_connectivity_uri (const NMConfigData *config_data); const guint nm_config_data_get_connectivity_interval (const NMConfigData *config_data); const char *nm_config_data_get_connectivity_response (const NMConfigData *config_data); const char *const*nm_config_data_get_no_auto_default (const NMConfigData *config_data); -const GSList * nm_config_data_get_no_auto_default_list (const NMConfigData *config_data); +gboolean nm_config_data_get_no_auto_default_for_device (const NMConfigData *self, NMDevice *device); const char *nm_config_data_get_dns_mode (const NMConfigData *self); @@ -101,6 +123,9 @@ char *nm_config_data_get_connection_default (const NMConfigData *self, const char *property, NMDevice *device); +char **nm_config_data_get_groups (const NMConfigData *self); +char **nm_config_data_get_keys (const NMConfigData *self, const char *group); + G_END_DECLS #endif /* NM_CONFIG_DATA_H */ diff --git a/src/nm-config.c b/src/nm-config.c index 7520c18e3b..c318eea790 100644 --- a/src/nm-config.c +++ b/src/nm-config.c @@ -37,10 +37,10 @@ #include <gio/gio.h> #include <glib/gi18n.h> -#define NM_DEFAULT_SYSTEM_CONF_FILE NMCONFDIR "/NetworkManager.conf" -#define NM_DEFAULT_SYSTEM_CONF_DIR NMCONFDIR "/conf.d" -#define NM_OLD_SYSTEM_CONF_FILE NMCONFDIR "/nm-system-settings.conf" -#define NM_NO_AUTO_DEFAULT_STATE_FILE NMSTATEDIR "/no-auto-default.state" +#define DEFAULT_CONFIG_MAIN_FILE NMCONFDIR "/NetworkManager.conf" +#define DEFAULT_CONFIG_DIR NMCONFDIR "/conf.d" +#define DEFAULT_CONFIG_MAIN_FILE_OLD NMCONFDIR "/nm-system-settings.conf" +#define DEFAULT_NO_AUTO_DEFAULT_FILE NMSTATEDIR "/no-auto-default.state" struct NMConfigCmdLineOptions { char *config_main_file; @@ -108,39 +108,115 @@ static void _set_config_data (NMConfig *self, NMConfigData *new_data, int signal /************************************************************************/ -gboolean +gint +nm_config_parse_boolean (const char *str, + gint default_value) +{ + gsize len; + char *s = NULL; + + if (!str) + return default_value; + + while (str[0] && g_ascii_isspace (str[0])) + str++; + + if (!str[0]) + return default_value; + + len = strlen (str); + if (g_ascii_isspace (str[len - 1])) { + s = g_strdup (str); + g_strchomp (s); + str = s; + } + + if (!g_ascii_strcasecmp (str, "true") || !g_ascii_strcasecmp (str, "yes") || !g_ascii_strcasecmp (str, "on") || !g_ascii_strcasecmp (str, "1")) + default_value = TRUE; + else if (!g_ascii_strcasecmp (str, "false") || !g_ascii_strcasecmp (str, "no") || !g_ascii_strcasecmp (str, "off") || !g_ascii_strcasecmp (str, "0")) + default_value = FALSE; + if (s) + g_free (s); + return default_value; +} + +gint nm_config_keyfile_get_boolean (GKeyFile *keyfile, const char *section, const char *key, - gboolean default_value) + gint default_value) { - gboolean value = default_value; - char *str; + gs_free char *str = NULL; g_return_val_if_fail (keyfile != NULL, default_value); g_return_val_if_fail (section != NULL, default_value); g_return_val_if_fail (key != NULL, default_value); str = g_key_file_get_value (keyfile, section, key, NULL); - if (!str) - return default_value; + return nm_config_parse_boolean (str, default_value); +} - g_strstrip (str); - if (str[0]) { - if (!g_ascii_strcasecmp (str, "true") || !g_ascii_strcasecmp (str, "yes") || !g_ascii_strcasecmp (str, "on") || !g_ascii_strcasecmp (str, "1")) - value = TRUE; - else if (!g_ascii_strcasecmp (str, "false") || !g_ascii_strcasecmp (str, "no") || !g_ascii_strcasecmp (str, "off") || !g_ascii_strcasecmp (str, "0")) - value = FALSE; - else { - nm_log_warn (LOGD_CORE, "Unrecognized value for %s.%s: '%s'. Assuming '%s'", - section, key, str, default_value ? "true" : "false"); - } +char * +nm_config_keyfile_get_value (GKeyFile *keyfile, + const char *section, + const char *key, + NMConfigGetValueFlags flags) +{ + char *value; + + if (NM_FLAGS_HAS (flags, NM_CONFIG_GET_VALUE_RAW)) + value = g_key_file_get_value (keyfile, section, key, NULL); + else + value = g_key_file_get_string (keyfile, section, key, NULL); + + if (!value) + return NULL; + + if (NM_FLAGS_HAS (flags, NM_CONFIG_GET_VALUE_STRIP)) + g_strstrip (value); + + if ( NM_FLAGS_HAS (flags, NM_CONFIG_GET_VALUE_NO_EMPTY) + && !*value) { + g_free (value); + return NULL; } - g_free (str); return value; } +void +nm_config_keyfile_set_string_list (GKeyFile *keyfile, + const char *group, + const char *key, + const char *const* strv, + gssize len) +{ + gsize l; + char *new_value; + + if (len < 0) + len = strv ? g_strv_length ((char **) strv) : 0; + + g_key_file_set_string_list (keyfile, group, key, strv, len); + + /* g_key_file_set_string_list() appends a trailing separator to the value. + * We don't like that, get rid of it. */ + + new_value = g_key_file_get_value (keyfile, group, key, NULL); + if (!new_value) + return; + + l = strlen (new_value); + if (l > 0 && new_value[l - 1] == NM_CONFIG_KEYFILE_LIST_SEPARATOR) { + /* Maybe we should check that value doesn't end with "\\,", i.e. + * with an escaped separator. But the way g_key_file_set_string_list() + * is implemented (currently), it always adds a trailing separator. */ + new_value[l - 1] = '\0'; + g_key_file_set_value (keyfile, group, key, new_value); + } + g_free (new_value); +} + /************************************************************************/ NMConfigData * @@ -227,94 +303,99 @@ nm_config_get_configure_and_quit (NMConfig *config) /************************************************************************/ static char ** -no_auto_default_merge_from_file (const char *no_auto_default_file, const char *const* no_auto_default) +no_auto_default_from_file (const char *no_auto_default_file) { - GPtrArray *updated; + GPtrArray *no_auto_default_new; char **list; - int i, j; + guint i; char *data; - updated = g_ptr_array_new (); - if (no_auto_default) { - for (i = 0; no_auto_default[i]; i++) - g_ptr_array_add (updated, g_strdup (no_auto_default[i])); - } + no_auto_default_new = g_ptr_array_new (); if ( no_auto_default_file && g_file_get_contents (no_auto_default_file, &data, NULL, NULL)) { list = g_strsplit (data, "\n", -1); for (i = 0; list[i]; i++) { - if (!*list[i]) + if ( *list[i] + && nm_utils_hwaddr_valid (list[i], -1) + && _nm_utils_strv_find_first (list, i, list[i]) < 0) + g_ptr_array_add (no_auto_default_new, list[i]); + else g_free (list[i]); - else { - for (j = 0; j < updated->len; j++) { - if (!strcmp (list[i], updated->pdata[j])) - break; - } - if (j == updated->len) - g_ptr_array_add (updated, list[i]); - else - g_free (list[i]); - } } g_free (list); g_free (data); } - g_ptr_array_add (updated, NULL); - return (char **) g_ptr_array_free (updated, FALSE); + g_ptr_array_add (no_auto_default_new, NULL); + return (char **) g_ptr_array_free (no_auto_default_new, FALSE); +} + +static gboolean +no_auto_default_to_file (const char *no_auto_default_file, const char *const*no_auto_default, GError **error) +{ + GString *data; + gboolean success; + guint i; + + data = g_string_new (""); + for (i = 0; no_auto_default && no_auto_default[i]; i++) { + g_string_append (data, no_auto_default[i]); + g_string_append_c (data, '\n'); + } + success = g_file_set_contents (no_auto_default_file, data->str, data->len, error); + g_string_free (data, TRUE); + return success; } gboolean nm_config_get_no_auto_default_for_device (NMConfig *self, NMDevice *device) { - NMConfigData *config_data; - g_return_val_if_fail (NM_IS_CONFIG (self), FALSE); - g_return_val_if_fail (NM_IS_DEVICE (device), FALSE); - config_data = NM_CONFIG_GET_PRIVATE (self)->config_data; - return nm_device_spec_match_list (device, nm_config_data_get_no_auto_default_list (config_data)); + return nm_config_data_get_no_auto_default_for_device (NM_CONFIG_GET_PRIVATE (self)->config_data, device); } void nm_config_set_no_auto_default_for_device (NMConfig *self, NMDevice *device) { NMConfigPrivate *priv = NM_CONFIG_GET_PRIVATE (self); - char *current; - GString *updated; GError *error = NULL; - char **no_auto_default; NMConfigData *new_data = NULL; + const char *hw_address; + const char *const*no_auto_default_current; + GPtrArray *no_auto_default_new = NULL; + guint i; g_return_if_fail (NM_IS_CONFIG (self)); g_return_if_fail (NM_IS_DEVICE (device)); - if (nm_config_get_no_auto_default_for_device (self, device)) - return; + hw_address = nm_device_get_hw_address (device); + + no_auto_default_current = nm_config_data_get_no_auto_default (priv->config_data); - updated = g_string_new (NULL); - if (g_file_get_contents (priv->no_auto_default_file, ¤t, NULL, NULL)) { - g_string_append (updated, current); - g_free (current); - if (updated->str[updated->len - 1] != '\n') - g_string_append_c (updated, '\n'); + if (_nm_utils_strv_find_first ((char **) no_auto_default_current, -1, hw_address) >= 0) { + /* @hw_address is already blocked. We don't have to update our in-memory representation. + * Maybe we should write to no_auto_default_file anew, but let's save that too. */ + return; } - g_string_append (updated, nm_device_get_hw_address (device)); - g_string_append_c (updated, '\n'); + no_auto_default_new = g_ptr_array_new (); + for (i = 0; no_auto_default_current && no_auto_default_current[i]; i++) + g_ptr_array_add (no_auto_default_new, (char *) no_auto_default_current[i]); + g_ptr_array_add (no_auto_default_new, (char *) hw_address); + g_ptr_array_add (no_auto_default_new, NULL); - if (!g_file_set_contents (priv->no_auto_default_file, updated->str, updated->len, &error)) { + if (!no_auto_default_to_file (priv->no_auto_default_file, (const char *const*) no_auto_default_new->pdata, &error)) { nm_log_warn (LOGD_SETTINGS, "Could not update no-auto-default.state file: %s", error->message); g_error_free (error); } - g_string_free (updated, TRUE); + new_data = nm_config_data_new_update_no_auto_default (priv->config_data, (const char *const*) no_auto_default_new->pdata); - no_auto_default = no_auto_default_merge_from_file (priv->no_auto_default_file, nm_config_data_get_no_auto_default (priv->config_data)); - new_data = nm_config_data_new_update_no_auto_default (priv->config_data, (const char *const*) no_auto_default); - g_strfreev (no_auto_default); + /* unref no_auto_default_set here. Note that _set_config_data() probably invalidates the content of the array. */ + g_ptr_array_unref (no_auto_default_new); _set_config_data (self, new_data, 0); } @@ -379,16 +460,16 @@ nm_config_cmd_line_options_add_to_entries (NMConfigCmdLineOptions *cli, { GOptionEntry config_options[] = { - { "config", 0, 0, G_OPTION_ARG_FILENAME, &cli->config_main_file, N_("Config file location"), N_("/path/to/config.file") }, - { "config-dir", 0, 0, G_OPTION_ARG_FILENAME, &cli->config_dir, N_("Config directory location"), N_("/path/to/config/dir") }, - { "no-auto-default", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_FILENAME, &cli->no_auto_default_file, "no-auto-default.state location", NULL }, - { "plugins", 0, 0, G_OPTION_ARG_STRING, &cli->plugins, N_("List of plugins separated by ','"), N_("plugin1,plugin2") }, + { "config", 0, 0, G_OPTION_ARG_FILENAME, &cli->config_main_file, N_("Config file location"), N_(DEFAULT_CONFIG_MAIN_FILE) }, + { "config-dir", 0, 0, G_OPTION_ARG_FILENAME, &cli->config_dir, N_("Config directory location"), N_(DEFAULT_CONFIG_DIR) }, + { "no-auto-default", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_FILENAME, &cli->no_auto_default_file, N_("State file for no-auto-default devices"), N_(DEFAULT_NO_AUTO_DEFAULT_FILE) }, + { "plugins", 0, 0, G_OPTION_ARG_STRING, &cli->plugins, N_("List of plugins separated by ','"), N_(CONFIG_PLUGINS_DEFAULT) }, { "configure-and-quit", 0, 0, G_OPTION_ARG_NONE, &cli->configure_and_quit, N_("Quit after initial configuration"), NULL }, /* These three are hidden for now, and should eventually just go away. */ { "connectivity-uri", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &cli->connectivity_uri, N_("An http(s) address for checking internet connectivity"), "http://example.com" }, - { "connectivity-interval", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_INT, &cli->connectivity_interval, N_("The interval between connectivity checks (in seconds)"), "60" }, - { "connectivity-response", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &cli->connectivity_response, N_("The expected start of the response"), N_("Bingo!") }, + { "connectivity-interval", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_INT, &cli->connectivity_interval, N_("The interval between connectivity checks (in seconds)"), G_STRINGIFY (NM_CONFIG_DEFAULT_CONNECTIVITY_INTERVAL) }, + { "connectivity-response", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &cli->connectivity_response, N_("The expected start of the response"), N_(NM_CONFIG_DEFAULT_CONNECTIVITY_RESPONSE) }, { 0 }, }; @@ -404,10 +485,70 @@ nm_config_create_keyfile () GKeyFile *keyfile; keyfile = g_key_file_new (); - g_key_file_set_list_separator (keyfile, ','); + g_key_file_set_list_separator (keyfile, NM_CONFIG_KEYFILE_LIST_SEPARATOR); return keyfile; } +static int +_sort_groups_cmp (const char **pa, const char **pb, gpointer dummy) +{ + const char *a, *b; + gboolean a_is_connection, b_is_connection; + + /* basic NULL checking... */ + if (pa == pb) + return 0; + if (!pa) + return -1; + if (!pb) + return 1; + + a = *pa; + b = *pb; + + a_is_connection = g_str_has_prefix (a, NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION); + b_is_connection = g_str_has_prefix (b, NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION); + + if (a_is_connection != b_is_connection) { + /* one is a [connection*] entry, the other not. We sort [connection*] entires + * after. */ + if (a_is_connection) + return 1; + return -1; + } + if (!a_is_connection) { + /* both are non-connection entries. Don't reorder. */ + return 0; + } + + /* both are [connection.\+] entires. Reverse their order. + * One of the sections might be literally [connection]. That section + * is special and it's order will be fixed later. It doesn't actually + * matter here how it compares with [connection.\+] sections. */ + return pa > pb ? -1 : 1; +} + +static gboolean +_setting_is_device_spec (const char *group, const char *key) +{ +#define _IS(group_v, key_v) (strcmp (group, (""group_v)) == 0 && strcmp (key, (""key_v)) == 0) + return _IS (NM_CONFIG_KEYFILE_GROUP_MAIN, "no-auto-default") + || _IS (NM_CONFIG_KEYFILE_GROUP_MAIN, "ignore-carrier") + || _IS (NM_CONFIG_KEYFILE_GROUP_MAIN, "assume-ipv6ll-only") + || _IS (NM_CONFIG_KEYFILE_GROUP_KEYFILE, "unmanaged-devices") + || (g_str_has_prefix (group, NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION) && !strcmp (key, "match-device")); +} + +static gboolean +_setting_is_string_list (const char *group, const char *key) +{ + return _IS (NM_CONFIG_KEYFILE_GROUP_MAIN, "plugins") + || _IS (NM_CONFIG_KEYFILE_GROUP_MAIN, "debug") + || _IS (NM_CONFIG_KEYFILE_GROUP_LOGGING, "domains") + || g_str_has_prefix (group, NM_CONFIG_KEYFILE_GROUPPREFIX_TEST_APPEND_STRINGLIST); +#undef _IS +} + static gboolean read_config (GKeyFile *keyfile, const char *path, GError **error) { @@ -435,36 +576,116 @@ read_config (GKeyFile *keyfile, const char *path, GError **error) /* Override the current settings with the new ones */ groups = g_key_file_get_groups (kf, &ngroups); + if (!groups) + ngroups = 0; + + /* Within one file we reverse the order of the '[connection.\+] sections. + * Here we merge the current file (@kf) into @keyfile. As we merge multiple + * files, earlier sections (with lower priority) will be added first. + * But within one file, we want a top-to-bottom order. This means we + * must reverse the order within each file. + * At the very end, we will revert the order of all sections again and + * get thus the right behavior. This final reversing is done in + * NMConfigData:_get_connection_infos(). */ + if (ngroups > 1) { + g_qsort_with_data (groups, + ngroups, + sizeof (char *), + (GCompareDataFunc) _sort_groups_cmp, + NULL); + } + for (g = 0; groups[g]; g++) { - keys = g_key_file_get_keys (kf, groups[g], &nkeys, NULL); + const char *group = groups[g]; + + keys = g_key_file_get_keys (kf, group, &nkeys, NULL); if (!keys) continue; for (k = 0; keys[k]; k++) { - int len = strlen (keys[k]); - char *v; - - if (keys[k][len - 1] == '+') { - char *base_key = g_strndup (keys[k], len - 1); - char *old_val = g_key_file_get_value (keyfile, groups[g], base_key, NULL); - char *new_val = g_key_file_get_value (kf, groups[g], keys[k], NULL); - - if (old_val && *old_val) { - char *combined = g_strconcat (old_val, ",", new_val, NULL); - - g_key_file_set_value (keyfile, groups[g], base_key, combined); - g_free (combined); - } else - g_key_file_set_value (keyfile, groups[g], base_key, new_val); - - g_free (base_key); - g_free (old_val); - g_free (new_val); + const char *key; + char *new_value; + char last_char; + gsize key_len; + + key = keys[k]; + g_assert (key && *key); + key_len = strlen (key); + last_char = key[key_len - 1]; + if ( key_len > 1 + && (last_char == '+' || last_char == '-')) { + gs_free char *base_key = g_strndup (key, key_len - 1); + gboolean is_string_list; + + is_string_list = _setting_is_string_list (group, base_key); + + if ( is_string_list + || _setting_is_device_spec (group, base_key)) { + gs_unref_ptrarray GPtrArray *new = g_ptr_array_new_with_free_func (g_free); + char **iter_val; + gs_strfreev char **old_val = NULL; + gs_free char **new_val = NULL; + + if (is_string_list) { + old_val = g_key_file_get_string_list (keyfile, group, base_key, NULL, NULL); + new_val = g_key_file_get_string_list (kf, group, key, NULL, NULL); + } else { + gs_free char *old_sval = nm_config_keyfile_get_value (keyfile, group, base_key, NM_CONFIG_GET_VALUE_TYPE_SPEC); + gs_free char *new_sval = nm_config_keyfile_get_value (kf, group, key, NM_CONFIG_GET_VALUE_TYPE_SPEC); + gs_free_slist GSList *old_specs = nm_match_spec_split (old_sval); + gs_free_slist GSList *new_specs = nm_match_spec_split (new_sval); + + /* the key is a device spec. This is a special kind of string-list, that + * we must split differently. */ + old_val = _nm_utils_slist_to_strv (old_specs, FALSE); + new_val = _nm_utils_slist_to_strv (new_specs, FALSE); + } + + /* merge the string lists, by omiting duplicates. */ + + for (iter_val = old_val; iter_val && *iter_val; iter_val++) { + if ( last_char != '-' + || _nm_utils_strv_find_first (new_val, -1, *iter_val) < 0) + g_ptr_array_add (new, g_strdup (*iter_val)); + } + for (iter_val = new_val; iter_val && *iter_val; iter_val++) { + /* don't add duplicates. That means an "option=a,b"; "option+=a,c" results in "option=a,b,c" */ + if ( last_char == '+' + && _nm_utils_strv_find_first (old_val, -1, *iter_val) < 0) + g_ptr_array_add (new, *iter_val); + else + g_free (*iter_val); + } + + if (new->len > 0) { + if (is_string_list) + nm_config_keyfile_set_string_list (keyfile, group, base_key, (const char *const*) new->pdata, new->len); + else { + gs_free_slist GSList *specs = NULL; + gs_free char *specs_joined = NULL; + + g_ptr_array_add (new, NULL); + specs = _nm_utils_strv_to_slist ((char **) new->pdata, FALSE); + + specs_joined = nm_match_spec_join (specs); + + g_key_file_set_value (keyfile, group, base_key, specs_joined); + } + } else { + if (is_string_list) + g_key_file_remove_key (keyfile, group, base_key, NULL); + else + g_key_file_set_value (keyfile, group, base_key, ""); + } + } else { + /* For any other settings we don't support extending the option with +/-. + * Just drop the key. */ + } continue; } - g_key_file_set_value (keyfile, groups[g], keys[k], - v = g_key_file_get_value (kf, groups[g], keys[k], NULL)); - g_free (v); + new_value = g_key_file_get_value (kf, group, key, NULL); + g_key_file_set_value (keyfile, group, key, new_value); + g_free (new_value); } g_strfreev (keys); } @@ -504,27 +725,27 @@ read_base_config (GKeyFile *keyfile, */ /* Try deprecated nm-system-settings.conf first */ - if (read_config (keyfile, NM_OLD_SYSTEM_CONF_FILE, &my_error)) { - *out_config_main_file = g_strdup (NM_OLD_SYSTEM_CONF_FILE); + if (read_config (keyfile, DEFAULT_CONFIG_MAIN_FILE_OLD, &my_error)) { + *out_config_main_file = g_strdup (DEFAULT_CONFIG_MAIN_FILE_OLD); return TRUE; } if (!g_error_matches (my_error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_NOT_FOUND)) { nm_log_warn (LOGD_CORE, "Old default config file %s invalid: %s\n", - NM_OLD_SYSTEM_CONF_FILE, + DEFAULT_CONFIG_MAIN_FILE_OLD, my_error->message); } g_clear_error (&my_error); /* Try the standard config file location next */ - if (read_config (keyfile, NM_DEFAULT_SYSTEM_CONF_FILE, &my_error)) { - *out_config_main_file = g_strdup (NM_DEFAULT_SYSTEM_CONF_FILE); + if (read_config (keyfile, DEFAULT_CONFIG_MAIN_FILE, &my_error)) { + *out_config_main_file = g_strdup (DEFAULT_CONFIG_MAIN_FILE); return TRUE; } if (!g_error_matches (my_error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_NOT_FOUND)) { nm_log_warn (LOGD_CORE, "Default config file %s invalid: %s\n", - NM_DEFAULT_SYSTEM_CONF_FILE, + DEFAULT_CONFIG_MAIN_FILE, my_error->message); g_propagate_error (error, my_error); return FALSE; @@ -534,9 +755,9 @@ read_base_config (GKeyFile *keyfile, /* If for some reason no config file exists, use the default * config file path. */ - *out_config_main_file = g_strdup (NM_DEFAULT_SYSTEM_CONF_FILE); + *out_config_main_file = g_strdup (DEFAULT_CONFIG_MAIN_FILE); nm_log_info (LOGD_CORE, "No config file found or given; using %s\n", - NM_DEFAULT_SYSTEM_CONF_FILE); + DEFAULT_CONFIG_MAIN_FILE); return TRUE; } @@ -642,23 +863,23 @@ read_entire_config (const NMConfigCmdLineOptions *cli, * config files. */ if (cli && cli->plugins && cli->plugins[0]) - g_key_file_set_value (keyfile, "main", "plugins", cli->plugins); - plugins_tmp = g_key_file_get_string_list (keyfile, "main", "plugins", NULL, NULL); + g_key_file_set_value (keyfile, NM_CONFIG_KEYFILE_GROUP_MAIN, "plugins", cli->plugins); + plugins_tmp = g_key_file_get_string_list (keyfile, NM_CONFIG_KEYFILE_GROUP_MAIN, "plugins", NULL, NULL); if (!plugins_tmp) { if (STRLEN (CONFIG_PLUGINS_DEFAULT) > 0) - g_key_file_set_value (keyfile, "main", "plugins", CONFIG_PLUGINS_DEFAULT); + g_key_file_set_value (keyfile, NM_CONFIG_KEYFILE_GROUP_MAIN, "plugins", CONFIG_PLUGINS_DEFAULT); } else g_strfreev (plugins_tmp); if (cli && cli->configure_and_quit) - g_key_file_set_value (keyfile, "main", "configure-and-quit", "true"); + g_key_file_set_boolean (keyfile, NM_CONFIG_KEYFILE_GROUP_MAIN, "configure-and-quit", TRUE); if (cli && cli->connectivity_uri && cli->connectivity_uri[0]) - g_key_file_set_value (keyfile, "connectivity", "uri", cli->connectivity_uri); + g_key_file_set_string (keyfile, NM_CONFIG_KEYFILE_GROUP_CONNECTIVITY, "uri", cli->connectivity_uri); if (cli && cli->connectivity_interval >= 0) - g_key_file_set_integer (keyfile, "connectivity", "interval", cli->connectivity_interval); + g_key_file_set_integer (keyfile, NM_CONFIG_KEYFILE_GROUP_CONNECTIVITY, "interval", cli->connectivity_interval); if (cli && cli->connectivity_response && cli->connectivity_response[0]) - g_key_file_set_value (keyfile, "connectivity", "response", cli->connectivity_response); + g_key_file_set_string (keyfile, NM_CONFIG_KEYFILE_GROUP_CONNECTIVITY, "response", cli->connectivity_response); *out_config_main_file = o_config_main_file; *out_config_description = o_config_description; @@ -666,11 +887,16 @@ read_entire_config (const NMConfigCmdLineOptions *cli, } GSList * -nm_config_get_device_match_spec (const GKeyFile *keyfile, const char *group, const char *key) +nm_config_get_device_match_spec (const GKeyFile *keyfile, const char *group, const char *key, gboolean *out_has_key) { gs_free char *value = NULL; - value = g_key_file_get_string ((GKeyFile *) keyfile, group, key, NULL); + /* nm_match_spec_split() already supports full escaping and is basically + * a modified version of g_key_file_parse_value_as_string(). So we first read + * the raw value (g_key_file_get_value()), and do the parsing ourselves. */ + value = g_key_file_get_value ((GKeyFile *) keyfile, group, key, NULL); + if (out_has_key) + *out_has_key = !!value; return nm_match_spec_split (value); } @@ -685,6 +911,7 @@ nm_config_reload (NMConfig *self, int signal) NMConfigData *new_data = NULL; char *config_main_file = NULL; char *config_description = NULL; + gs_strfreev char **no_auto_default = NULL; g_return_if_fail (NM_IS_CONFIG (self)); @@ -710,7 +937,9 @@ nm_config_reload (NMConfig *self, int signal) _set_config_data (self, NULL, signal); return; } - new_data = nm_config_data_new (config_main_file, config_description, nm_config_data_get_no_auto_default (priv->config_data), keyfile); + no_auto_default = no_auto_default_from_file (priv->no_auto_default_file); + + new_data = nm_config_data_new (config_main_file, config_description, (const char *const*) no_auto_default, keyfile); g_free (config_main_file); g_free (config_description); g_key_file_unref (keyfile); @@ -799,6 +1028,7 @@ _set_config_data (NMConfig *self, NMConfigData *new_data, int signal) if (new_data) { nm_log_info (LOGD_CORE, "config: update %s (%s)", nm_config_data_get_config_description (new_data), (log_str = nm_config_change_flags_to_string (changes))); + nm_config_data_log (new_data, "CONFIG: "); priv->config_data = new_data; } else if (had_new_data) nm_log_info (LOGD_CORE, "config: signal %s (no changes from disk)", (log_str = nm_config_change_flags_to_string (changes))); @@ -840,9 +1070,7 @@ init_sync (GInitable *initable, GCancellable *cancellable, GError **error) GKeyFile *keyfile; char *config_main_file = NULL; char *config_description = NULL; - char **no_auto_default; - GSList *no_auto_default_orig_list; - GPtrArray *no_auto_default_orig; + gs_strfreev char **no_auto_default = NULL; if (priv->config_dir) { /* Object is already initialized. */ @@ -855,7 +1083,7 @@ init_sync (GInitable *initable, GCancellable *cancellable, GError **error) if (priv->cli.config_dir) priv->config_dir = g_strdup (priv->cli.config_dir); else - priv->config_dir = g_strdup (NM_DEFAULT_SYSTEM_CONF_DIR); + priv->config_dir = g_strdup (DEFAULT_CONFIG_DIR); keyfile = read_entire_config (&priv->cli, priv->config_dir, @@ -870,38 +1098,30 @@ init_sync (GInitable *initable, GCancellable *cancellable, GError **error) if (priv->cli.no_auto_default_file) priv->no_auto_default_file = g_strdup (priv->cli.no_auto_default_file); else - priv->no_auto_default_file = g_strdup (NM_NO_AUTO_DEFAULT_STATE_FILE); + priv->no_auto_default_file = g_strdup (DEFAULT_NO_AUTO_DEFAULT_FILE); - priv->plugins = g_key_file_get_string_list (keyfile, "main", "plugins", NULL, NULL); + priv->plugins = _nm_utils_strv_cleanup (g_key_file_get_string_list (keyfile, NM_CONFIG_KEYFILE_GROUP_MAIN, "plugins", NULL, NULL), + TRUE, TRUE, TRUE); if (!priv->plugins) priv->plugins = g_new0 (char *, 1); - priv->monitor_connection_files = nm_config_keyfile_get_boolean (keyfile, "main", "monitor-connection-files", FALSE); - - priv->auth_polkit = nm_config_keyfile_get_boolean (keyfile, "main", "auth-polkit", NM_CONFIG_DEFAULT_AUTH_POLKIT); + priv->monitor_connection_files = nm_config_keyfile_get_boolean (keyfile, NM_CONFIG_KEYFILE_GROUP_MAIN, "monitor-connection-files", FALSE); - priv->dhcp_client = g_key_file_get_value (keyfile, "main", "dhcp", NULL); + priv->auth_polkit = nm_config_keyfile_get_boolean (keyfile, NM_CONFIG_KEYFILE_GROUP_MAIN, "auth-polkit", NM_CONFIG_DEFAULT_AUTH_POLKIT); - priv->log_level = g_key_file_get_value (keyfile, "logging", "level", NULL); - priv->log_domains = g_key_file_get_value (keyfile, "logging", "domains", NULL); + priv->dhcp_client = nm_strstrip (g_key_file_get_string (keyfile, NM_CONFIG_KEYFILE_GROUP_MAIN, "dhcp", NULL)); - priv->debug = g_key_file_get_value (keyfile, "main", "debug", NULL); + priv->log_level = nm_strstrip (g_key_file_get_string (keyfile, NM_CONFIG_KEYFILE_GROUP_LOGGING, "level", NULL)); + priv->log_domains = nm_strstrip (g_key_file_get_string (keyfile, NM_CONFIG_KEYFILE_GROUP_LOGGING, "domains", NULL)); - priv->configure_and_quit = nm_config_keyfile_get_boolean (keyfile, "main", "configure-and-quit", FALSE); + priv->debug = g_key_file_get_string (keyfile, NM_CONFIG_KEYFILE_GROUP_MAIN, "debug", NULL); - no_auto_default_orig_list = nm_config_get_device_match_spec (keyfile, "main", "no-auto-default"); + priv->configure_and_quit = nm_config_keyfile_get_boolean (keyfile, NM_CONFIG_KEYFILE_GROUP_MAIN, "configure-and-quit", FALSE); - no_auto_default_orig = _nm_utils_copy_slist_to_array (no_auto_default_orig_list, NULL, NULL); - g_ptr_array_add (no_auto_default_orig, NULL); - no_auto_default = no_auto_default_merge_from_file (priv->no_auto_default_file, (const char *const *) no_auto_default_orig->pdata); - g_ptr_array_unref (no_auto_default_orig); - - g_slist_free_full (no_auto_default_orig_list, g_free); + no_auto_default = no_auto_default_from_file (priv->no_auto_default_file); priv->config_data_orig = nm_config_data_new (config_main_file, config_description, (const char *const*) no_auto_default, keyfile); - g_strfreev (no_auto_default); - priv->config_data = g_object_ref (priv->config_data_orig); g_free (config_main_file); diff --git a/src/nm-config.h b/src/nm-config.h index d4f5e94f3c..5b096f165d 100644 --- a/src/nm-config.h +++ b/src/nm-config.h @@ -44,6 +44,24 @@ G_BEGIN_DECLS #define NM_CONFIG_SIGNAL_CONFIG_CHANGED "config-changed" #define NM_CONFIG_DEFAULT_CONNECTIVITY_INTERVAL 300 +#define NM_CONFIG_DEFAULT_CONNECTIVITY_RESPONSE "NetworkManager is online" /* NOT LOCALIZED */ + +#define NM_CONFIG_KEYFILE_LIST_SEPARATOR ',' + +#define NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION "connection" +#define NM_CONFIG_KEYFILE_GROUPPREFIX_TEST_APPEND_STRINGLIST ".test-append-stringlist" + +#define NM_CONFIG_KEYFILE_GROUP_MAIN "main" +#define NM_CONFIG_KEYFILE_GROUP_LOGGING "logging" +#define NM_CONFIG_KEYFILE_GROUP_CONNECTIVITY "connectivity" + +#define NM_CONFIG_KEYFILE_GROUP_KEYFILE "keyfile" +#define NM_CONFIG_KEYFILE_GROUP_IFUPDOWN "ifupdown" +#define NM_CONFIG_KEYFILE_GROUP_IFNET "ifnet" + +#define NM_CONFIG_KEYFILE_KEY_IFNET_AUTO_REFRESH "auto_refresh" +#define NM_CONFIG_KEYFILE_KEY_IFNET_MANAGED "managed" +#define NM_CONFIG_KEYFILE_KEY_IFUPDOWN_MANAGED "managed" typedef struct NMConfigCmdLineOptions NMConfigCmdLineOptions; @@ -66,6 +84,10 @@ char *nm_config_change_flags_to_string (NMConfigChangeFlags flags); NMConfigData *nm_config_get_data (NMConfig *config); NMConfigData *nm_config_get_data_orig (NMConfig *config); + +#define NM_CONFIG_GET_DATA (nm_config_get_data (nm_config_get ())) +#define NM_CONFIG_GET_DATA_ORIG (nm_config_get_data_orig (nm_config_get ())) + const char **nm_config_get_plugins (NMConfig *config); gboolean nm_config_get_monitor_connection_files (NMConfig *config); gboolean nm_config_get_auth_polkit (NMConfig *config); @@ -88,12 +110,23 @@ NMConfig *nm_config_new (const NMConfigCmdLineOptions *cli, GError **error); NMConfig *nm_config_setup (const NMConfigCmdLineOptions *cli, GError **error); void nm_config_reload (NMConfig *config, int signal); +gint nm_config_parse_boolean (const char *str, gint default_value); + GKeyFile *nm_config_create_keyfile (void); -gboolean nm_config_keyfile_get_boolean (GKeyFile *keyfile, - const char *section, +gint nm_config_keyfile_get_boolean (GKeyFile *keyfile, + const char *section, + const char *key, + gint default_value); +char *nm_config_keyfile_get_value (GKeyFile *keyfile, + const char *section, + const char *key, + NMConfigGetValueFlags flags); +void nm_config_keyfile_set_string_list (GKeyFile *keyfile, + const char *group, const char *key, - gboolean default_value); -GSList *nm_config_get_device_match_spec (const GKeyFile *keyfile, const char *group, const char *key); + const char *const* strv, + gssize len); +GSList *nm_config_get_device_match_spec (const GKeyFile *keyfile, const char *group, const char *key, gboolean *out_has_key); G_END_DECLS diff --git a/src/nm-connectivity.c b/src/nm-connectivity.c index f7e7576c22..81ac8f9de4 100644 --- a/src/nm-connectivity.c +++ b/src/nm-connectivity.c @@ -37,8 +37,6 @@ G_DEFINE_TYPE (NMConnectivity, nm_connectivity, G_TYPE_OBJECT) #define NM_CONNECTIVITY_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_CONNECTIVITY, NMConnectivityPrivate)) -#define DEFAULT_RESPONSE "NetworkManager is online" /* NOT LOCALIZED */ - #define _LOG_DEFAULT_DOMAIN LOGD_CONCHECK #define _LOG(level, domain, ...) \ @@ -140,7 +138,7 @@ nm_connectivity_check_cb (SoupSession *session, SoupMessage *msg, gpointer user_ NMConnectivityState new_state; const char *nm_header; const char *uri = cb_data->uri; - const char *response = cb_data->response ? cb_data->response : DEFAULT_RESPONSE; + const char *response = cb_data->response ? cb_data->response : NM_CONFIG_DEFAULT_CONNECTIVITY_RESPONSE; self = NM_CONNECTIVITY (g_async_result_get_source_object (G_ASYNC_RESULT (simple))); /* it is safe to unref @self here, @simple holds yet another reference. */ @@ -401,7 +399,7 @@ set_property (GObject *object, guint property_id, case PROP_RESPONSE: response = g_value_get_string (value); if (g_strcmp0 (response, priv->response) != 0) { - /* a response %NULL means, DEFAULT_RESPONSE. Any other response + /* a response %NULL means, NM_CONFIG_DEFAULT_CONNECTIVITY_RESPONSE. Any other response * (including "") is accepted. */ g_free (priv->response); priv->response = g_strdup (response); @@ -432,7 +430,7 @@ get_property (GObject *object, guint property_id, if (priv->response) g_value_set_string (value, priv->response); else - g_value_set_static_string (value, DEFAULT_RESPONSE); + g_value_set_static_string (value, NM_CONFIG_DEFAULT_CONNECTIVITY_RESPONSE); break; case PROP_STATE: g_value_set_uint (value, priv->state); @@ -510,7 +508,7 @@ nm_connectivity_class_init (NMConnectivityClass *klass) g_object_class_install_property (object_class, PROP_RESPONSE, g_param_spec_string (NM_CONNECTIVITY_RESPONSE, "", "", - DEFAULT_RESPONSE, + NM_CONFIG_DEFAULT_CONNECTIVITY_RESPONSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); diff --git a/src/nm-manager.c b/src/nm-manager.c index e6640ab3c9..080cdb8b24 100644 --- a/src/nm-manager.c +++ b/src/nm-manager.c @@ -2487,7 +2487,7 @@ should_connect_slaves (NMConnection *connection, NMDevice *device) goto out; /* Check configuration default for autoconnect-slaves property */ - value = nm_config_data_get_connection_default (nm_config_get_data (nm_config_get ()), + value = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA, "connection.autoconnect-slaves", device); if (value) autoconnect_slaves = _nm_utils_ascii_str_to_int64 (value, 10, 0, 1, -1); diff --git a/src/settings/nm-settings.c b/src/settings/nm-settings.c index 9e4c441fbc..cb3b6b4386 100644 --- a/src/settings/nm-settings.c +++ b/src/settings/nm-settings.c @@ -685,17 +685,13 @@ load_plugins (NMSettings *self, const char **plugins, GError **error) GModule *plugin; gs_free char *full_name = NULL; gs_free char *path = NULL; - gs_free char *pname = NULL; + const char *pname; GObject *obj; GObject * (*factory_func) (void); struct stat st; int errsv; - pname = g_strdup (*iter); - g_strstrip (pname); - - if (!*pname) - continue; + pname = *iter; if (!*pname || strchr (pname, '/')) { LOG (LOGL_WARN, "ignore invalid plugin \"%s\"", pname); diff --git a/src/settings/plugins/ifnet/net_parser.h b/src/settings/plugins/ifnet/net_parser.h index 005207adfe..d10979cca6 100644 --- a/src/settings/plugins/ifnet/net_parser.h +++ b/src/settings/plugins/ifnet/net_parser.h @@ -25,7 +25,6 @@ #include <glib.h> #define CONF_NET_FILE SYSCONFDIR "/conf.d/net" -#define IFNET_KEY_FILE_GROUP "ifnet" gboolean ifnet_init (gchar * config_file); void ifnet_destroy (void); diff --git a/src/settings/plugins/ifnet/plugin.c b/src/settings/plugins/ifnet/plugin.c index 7f1eb4ff93..5eab31abfd 100644 --- a/src/settings/plugins/ifnet/plugin.c +++ b/src/settings/plugins/ifnet/plugin.c @@ -47,7 +47,6 @@ #define IFNET_PLUGIN_INFO "(C) 1999-2010 Gentoo Foundation, Inc. To report bugs please use bugs.gentoo.org with [networkmanager] or [qiaomuf] prefix." #define IFNET_SYSTEM_HOSTNAME_FILE "/etc/conf.d/hostname" #define IFNET_MANAGE_WELL_KNOWN_DEFAULT TRUE -#define IFNET_KEY_FILE_KEY_MANAGED "managed" typedef struct { GHashTable *connections; /* uuid::connection */ @@ -121,17 +120,9 @@ write_system_hostname (NMSystemConfigInterface * config, static gboolean is_managed_plugin (void) { - char *result = NULL; - - result = nm_config_data_get_value (nm_config_get_data_orig (nm_config_get ()), - IFNET_KEY_FILE_GROUP, IFNET_KEY_FILE_KEY_MANAGED, - NULL); - if (result) { - gboolean ret = is_true (result); - g_free (result); - return ret; - } - return IFNET_MANAGE_WELL_KNOWN_DEFAULT; + return nm_config_data_get_value_boolean (NM_CONFIG_GET_DATA_ORIG, + NM_CONFIG_KEYFILE_GROUP_IFNET, NM_CONFIG_KEYFILE_KEY_IFNET_MANAGED, + IFNET_MANAGE_WELL_KNOWN_DEFAULT); } static void @@ -246,8 +237,7 @@ reload_connections (NMSystemConfigInterface *config) SCPluginIfnet *self = SC_PLUGIN_IFNET (config); SCPluginIfnetPrivate *priv = SC_PLUGIN_IFNET_GET_PRIVATE (self); GList *conn_names = NULL, *n_iter = NULL; - gboolean auto_refresh = FALSE; - char *str_auto_refresh; + gboolean auto_refresh; GError *error = NULL; /* save names for removing unused connections */ @@ -264,12 +254,9 @@ reload_connections (NMSystemConfigInterface *config) nm_log_info (LOGD_SETTINGS, "Loading connections"); - str_auto_refresh = nm_config_data_get_value (nm_config_get_data_orig (nm_config_get ()), - IFNET_KEY_FILE_GROUP, "auto_refresh", - NULL); - if (str_auto_refresh && is_true (str_auto_refresh)) - auto_refresh = TRUE; - g_free (str_auto_refresh); + auto_refresh = nm_config_data_get_value_boolean (NM_CONFIG_GET_DATA_ORIG, + NM_CONFIG_KEYFILE_GROUP_IFNET, NM_CONFIG_KEYFILE_KEY_IFNET_AUTO_REFRESH, + FALSE); new_connections = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_object_unref); diff --git a/src/settings/plugins/ifupdown/plugin.c b/src/settings/plugins/ifupdown/plugin.c index 1cbff29b3d..8652dd8dd8 100644 --- a/src/settings/plugins/ifupdown/plugin.c +++ b/src/settings/plugins/ifupdown/plugin.c @@ -63,8 +63,6 @@ #define IFUPDOWN_PLUGIN_INFO "(C) 2008 Canonical Ltd. To report bugs please use the NetworkManager mailing list." #define IFUPDOWN_SYSTEM_HOSTNAME_FILE "/etc/hostname" -#define IFUPDOWN_KEY_FILE_GROUP "ifupdown" -#define IFUPDOWN_KEY_FILE_KEY_MANAGED "managed" #define IFUPDOWN_UNMANAGE_WELL_KNOWN_DEFAULT TRUE /* #define ALWAYS_UNMANAGE TRUE */ @@ -327,8 +325,6 @@ SCPluginIfupdown_init (NMSystemConfigInterface *config) GHashTable *auto_ifaces; if_block *block = NULL; NMInotifyHelper *inotify_helper; - char *value; - GError *error = NULL; GList *keys, *iter; GHashTableIter con_iter; const char *block_name; @@ -354,8 +350,6 @@ SCPluginIfupdown_init (NMSystemConfigInterface *config) } else g_signal_connect (priv->client, "uevent", G_CALLBACK (handle_uevent), self); - priv->unmanage_well_known = IFUPDOWN_UNMANAGE_WELL_KNOWN_DEFAULT; - inotify_helper = nm_inotify_helper_get (); priv->inotify_event_id = g_signal_connect (inotify_helper, "event", @@ -456,21 +450,10 @@ SCPluginIfupdown_init (NMSystemConfigInterface *config) g_hash_table_destroy (auto_ifaces); /* Check the config file to find out whether to manage interfaces */ - value = nm_config_data_get_value (nm_config_get_data_orig (nm_config_get ()), - IFUPDOWN_KEY_FILE_GROUP, IFUPDOWN_KEY_FILE_KEY_MANAGED, - &error); - if (error) { - nm_log_info (LOGD_SETTINGS, "loading system config file (%s) caused error: %s", - nm_config_data_get_config_main_file (nm_config_get_data (nm_config_get ())), - error->message); - } else { - gboolean manage_well_known; - error = NULL; - - manage_well_known = !g_strcmp0 (value, "true") || !g_strcmp0 (value, "1"); - priv->unmanage_well_known = !manage_well_known; - g_free (value); - } + priv->unmanage_well_known = !nm_config_data_get_value_boolean (NM_CONFIG_GET_DATA_ORIG, + NM_CONFIG_KEYFILE_GROUP_IFUPDOWN, + NM_CONFIG_KEYFILE_KEY_IFUPDOWN_MANAGED, + !IFUPDOWN_UNMANAGE_WELL_KNOWN_DEFAULT); nm_log_info (LOGD_SETTINGS, "management mode: %s", priv->unmanage_well_known ? "unmanaged" : "managed"); /* Add well-known interfaces */ @@ -630,7 +613,7 @@ sc_plugin_ifupdown_init (SCPluginIfupdown *plugin) static void GObject__get_property (GObject *object, guint prop_id, - GValue *value, GParamSpec *pspec) + GValue *value, GParamSpec *pspec) { NMSystemConfigInterface *self = NM_SYSTEM_CONFIG_INTERFACE (object); diff --git a/src/settings/plugins/keyfile/plugin.c b/src/settings/plugins/keyfile/plugin.c index 611192baa5..7f21e71e67 100644 --- a/src/settings/plugins/keyfile/plugin.c +++ b/src/settings/plugins/keyfile/plugin.c @@ -591,7 +591,7 @@ get_unmanaged_specs (NMSystemConfigInterface *config) key_file = nm_config_create_keyfile (); if (parse_key_file_allow_none (priv, key_file, &error)) - specs = nm_config_get_device_match_spec (key_file, "keyfile", "unmanaged-devices"); + specs = nm_config_get_device_match_spec (key_file, NM_CONFIG_KEYFILE_GROUP_KEYFILE, "unmanaged-devices", NULL); if (error) { nm_log_warn (LOGD_SETTINGS, "keyfile: error getting unmanaged specs: %s", error->message); @@ -617,7 +617,7 @@ plugin_get_hostname (SCPluginKeyfile *plugin) if (!parse_key_file_allow_none (priv, key_file, &error)) goto out; - hostname = g_key_file_get_value (key_file, "keyfile", "hostname", NULL); + hostname = g_key_file_get_value (key_file, NM_CONFIG_KEYFILE_GROUP_KEYFILE, "hostname", NULL); out: if (error) { @@ -653,7 +653,7 @@ plugin_set_hostname (SCPluginKeyfile *plugin, const char *hostname) if (!parse_key_file_allow_none (priv, key_file, &error)) goto out; - g_key_file_set_string (key_file, "keyfile", "hostname", hostname); + g_key_file_set_string (key_file, NM_CONFIG_KEYFILE_GROUP_KEYFILE, "hostname", hostname); data = g_key_file_to_data (key_file, &len, &error); if (!data) diff --git a/src/tests/config/NetworkManager.conf b/src/tests/config/NetworkManager.conf index 36113661f2..a750c801ee 100644 --- a/src/tests/config/NetworkManager.conf +++ b/src/tests/config/NetworkManager.conf @@ -22,6 +22,17 @@ ipv6.ip6_privacy=0 dummy.test1=no dummy.test2=no +ord.key00=A-0.0.00 +ord.key01=A-0.0.01 +ord.key02=A-0.0.02 +ord.key03=A-0.0.03 +ord.key04=A-0.0.04 +ord.key05=A-0.0.05 +ord.key06=A-0.0.06 +ord.key07=A-0.0.07 +ord.key08=A-0.0.08 +ord.key09=A-0.0.09 + [connection.dev51] match-device=mac:00:00:00:00:00:51 stop-match=yes @@ -37,3 +48,36 @@ match-device=interface-name:wlan1 # match-wifi is not yet implemented. Just an idea what could be useful. match-wifi=ssid:*[Ss]tarbucks*|*University* ipv6.ip6_privacy=2 + + +# the following sections are tested for their order across +# multiple files. +[connection.ord.0.1] +ord.key03=A-0.1.03 +ord.key04=A-0.1.04 +ord.key05=A-0.1.05 +ord.key06=A-0.1.06 +ord.key07=A-0.1.07 +ord.key08=A-0.1.08 +ord.key09=A-0.1.09 +ord.ovw01=A-0.1.ovw01 +[connection.ord.0.2] +ord.key02=A-0.2.02 +ord.key03=A-0.2.03 +ord.key04=A-0.2.04 +ord.key05=A-0.2.05 +ord.key06=A-0.2.06 +ord.key07=A-0.2.07 +ord.key08=A-0.2.08 +ord.key09=A-0.2.09 +[connection.ord.0.3] +ord.key01=A-0.3.01 +ord.key02=A-0.3.02 +ord.key03=A-0.3.03 +ord.key04=A-0.3.04 +ord.key05=A-0.3.05 +ord.key06=A-0.3.06 +ord.key07=A-0.3.07 +ord.key08=A-0.3.08 +ord.key09=A-0.3.09 +ord.ovw01=A-0.3.ovw01 diff --git a/src/tests/config/conf.d/00-overrides.conf b/src/tests/config/conf.d/00-overrides.conf index 0aa19d484c..f26ed93b9a 100644 --- a/src/tests/config/conf.d/00-overrides.conf +++ b/src/tests/config/conf.d/00-overrides.conf @@ -1,11 +1,57 @@ [main] dhcp=dhcpcd +no-auto-default=spec1,spec2 +ignore-carrier=\s space1 \s + [logging] domains=PLATFORM,DNS,WIFI +[appendable-test] +non-appendable-key1+=i-will-be-dropped +non-appendable-key2-=i-will-be-dropped + [order] a=0 b=0 c=0 + +# the following sections are tested for their order across +# multiple files. +[connection.ord.1.1] +ord.key06=B-1.1.06 +ord.key07=B-1.1.07 +ord.key08=B-1.1.08 +ord.key09=B-1.1.09 +[connection.ord.1.2] +ord.key05=B-1.2.05 +ord.key06=B-1.2.06 +ord.key07=B-1.2.07 +ord.key08=B-1.2.08 +ord.key09=B-1.2.09 +[connection.ord.1.3] +ord.key04=B-1.3.04 +ord.key05=B-1.3.05 +ord.key06=B-1.3.06 +ord.key07=B-1.3.07 +ord.key08=B-1.3.08 +ord.key09=B-1.3.09 + + +[.test-append-stringlist.1] +val1=a,b + +val2-=VAL2 +val2=VAL2 + +val3=VAL3 +val3-=VAL3 + +val4=VAL4 +val4+=VAL4,va,vb,va,vb +val4-=VAL4,va + +val5=VAL5 +val5-=VAL5 +val5+=VAL5 diff --git a/src/tests/config/conf.d/10-more.conf b/src/tests/config/conf.d/10-more.conf index b1424a4bc8..eadb7f96f6 100644 --- a/src/tests/config/conf.d/10-more.conf +++ b/src/tests/config/conf.d/10-more.conf @@ -1,5 +1,12 @@ [main] extra=hello + +no-auto-default-=spec1 +no-auto-default+=spec3 + +ignore-carrier+=\sspace2\t + +[.test-append-stringlist.0] new+=something [connectivity] @@ -9,3 +16,25 @@ uri=http://example.net a=10 b=10 +# the following sections are tested for their order across +# multiple files. +[connection.ord.2.1] +ord.key09=C-2.1.09 +[connection.ord.2.2] +ord.key08=C-2.2.08 +ord.key09=C-2.2.09 +[connection.ord.2.3] +ord.key07=C-2.3.07 +ord.key08=C-2.3.08 +ord.key09=C-2.3.09 + +# you can overwrite individual settings in a file loaded +# previously. But note that this does not bump the priority +# of the section, i.e. [connection.ord.0.1] still has a pretty +# low priority and is shadowed by [connection.ord.2.1]. +[connection.ord.0.1] +ord.ovw01=C-0.1.ovw01 + + +[.test-append-stringlist.1] +val1-=b diff --git a/src/tests/config/conf.d/90-last.conf b/src/tests/config/conf.d/90-last.conf index dc1de394f1..7d078788de 100644 --- a/src/tests/config/conf.d/90-last.conf +++ b/src/tests/config/conf.d/90-last.conf @@ -3,3 +3,6 @@ plugins+=one,two [order] a=90 + +[.test-append-stringlist.1] +val1+=c,a diff --git a/src/tests/config/test-config.c b/src/tests/config/test-config.c index 7ce3c1ee7c..8b8028e63a 100644 --- a/src/tests/config/test-config.c +++ b/src/tests/config/test-config.c @@ -91,7 +91,6 @@ static void test_config_simple (void) { NMConfig *config; - GError *error = NULL; const char **plugins; char *value; gs_unref_object NMDevice *dev50 = nm_test_device_new ("00:00:00:00:00:50"); @@ -111,25 +110,21 @@ test_config_simple (void) g_assert_cmpstr (plugins[1], ==, "bar"); g_assert_cmpstr (plugins[2], ==, "baz"); - value = nm_config_data_get_value (nm_config_get_data_orig (config), "extra-section", "extra-key", NULL); + value = nm_config_data_get_value (nm_config_get_data_orig (config), "extra-section", "extra-key", NM_CONFIG_GET_VALUE_NONE); g_assert_cmpstr (value, ==, "some value"); g_free (value); - value = nm_config_data_get_value (nm_config_get_data_orig (config), "extra-section", "no-key", &error); + value = nm_config_data_get_value (nm_config_get_data_orig (config), "extra-section", "no-key", NM_CONFIG_GET_VALUE_NONE); g_assert (!value); - g_assert_error (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_KEY_NOT_FOUND); - g_clear_error (&error); - value = nm_config_data_get_value (nm_config_get_data_orig (config), "no-section", "no-key", &error); + value = nm_config_data_get_value (nm_config_get_data_orig (config), "no-section", "no-key", NM_CONFIG_GET_VALUE_NONE); g_assert (!value); - g_assert_error (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND); - g_clear_error (&error); - value = nm_config_data_get_value (nm_config_get_data_orig (config), "connection", "ipv6.ip6_privacy", NULL); + value = nm_config_data_get_value (nm_config_get_data_orig (config), "connection", "ipv6.ip6_privacy", NM_CONFIG_GET_VALUE_NONE); g_assert_cmpstr (value, ==, "0"); g_free (value); - value = nm_config_data_get_value (nm_config_get_data_orig (config), "connection.dev51", "ipv4.route-metric", NULL); + value = nm_config_data_get_value (nm_config_get_data_orig (config), "connection.dev51", "ipv4.route-metric", NM_CONFIG_GET_VALUE_NONE); g_assert_cmpstr (value, ==, "51"); g_free (value); @@ -288,6 +283,7 @@ test_config_confdir (void) NMConfig *config; const char **plugins; char *value; + GSList *specs; config = setup_config (NULL, SRCDIR "/NetworkManager.conf", SRCDIR "/conf.d", NULL); @@ -306,24 +302,86 @@ test_config_confdir (void) g_assert_cmpstr (plugins[3], ==, "one"); g_assert_cmpstr (plugins[4], ==, "two"); - value = nm_config_data_get_value (nm_config_get_data_orig (config), "main", "extra", NULL); + value = nm_config_data_get_value (nm_config_get_data_orig (config), "main", "extra", NM_CONFIG_GET_VALUE_NONE); g_assert_cmpstr (value, ==, "hello"); g_free (value); - value = nm_config_data_get_value (nm_config_get_data_orig (config), "main", "new", NULL); + value = nm_config_data_get_value (nm_config_get_data_orig (config), "main", "no-auto-default", NM_CONFIG_GET_VALUE_TYPE_SPEC); + specs = nm_match_spec_split (value); + g_free (value); + g_assert_cmpint (g_slist_length (specs), ==, 2); + g_assert_cmpstr (g_slist_nth_data (specs, 0), ==, "spec2"); + g_assert_cmpstr (g_slist_nth_data (specs, 1), ==, "spec3"); + g_slist_free_full (specs, g_free); + + value = nm_config_data_get_value (nm_config_get_data_orig (config), "main", "ignore-carrier", NM_CONFIG_GET_VALUE_TYPE_SPEC); + specs = nm_match_spec_split (value); + g_free (value); + g_assert_cmpint (g_slist_length (specs), ==, 2); + g_assert_cmpstr (g_slist_nth_data (specs, 0), ==, " space1 "); + g_assert_cmpstr (g_slist_nth_data (specs, 1), ==, " space2\t"); + g_slist_free_full (specs, g_free); + + value = nm_config_data_get_value (nm_config_get_data_orig (config), NM_CONFIG_KEYFILE_GROUPPREFIX_TEST_APPEND_STRINGLIST".0", "new", NM_CONFIG_GET_VALUE_NONE); g_assert_cmpstr (value, ==, "something"); /* not ",something" */ g_free (value); - value = nm_config_data_get_value (nm_config_get_data_orig (config), "order", "a", NULL); + value = nm_config_data_get_value (nm_config_get_data_orig (config), "order", "a", NM_CONFIG_GET_VALUE_NONE); g_assert_cmpstr (value, ==, "90"); g_free (value); - value = nm_config_data_get_value (nm_config_get_data_orig (config), "order", "b", NULL); + value = nm_config_data_get_value (nm_config_get_data_orig (config), "order", "b", NM_CONFIG_GET_VALUE_NONE); g_assert_cmpstr (value, ==, "10"); g_free (value); - value = nm_config_data_get_value (nm_config_get_data_orig (config), "order", "c", NULL); + value = nm_config_data_get_value (nm_config_get_data_orig (config), "order", "c", NM_CONFIG_GET_VALUE_NONE); g_assert_cmpstr (value, ==, "0"); g_free (value); + g_assert (!nm_config_data_has_value (nm_config_get_data_orig (config), "appendable-test", "non-appendable-key1", NM_CONFIG_GET_VALUE_RAW)); + g_assert (!nm_config_data_has_value (nm_config_get_data_orig (config), "appendable-test", "non-appendable-key1+", NM_CONFIG_GET_VALUE_RAW)); + g_assert (!nm_config_data_has_value (nm_config_get_data_orig (config), "appendable-test", "non-appendable-key1-", NM_CONFIG_GET_VALUE_RAW)); + g_assert (!nm_config_data_has_value (nm_config_get_data_orig (config), "appendable-test", "non-appendable-key2", NM_CONFIG_GET_VALUE_RAW)); + g_assert (!nm_config_data_has_value (nm_config_get_data_orig (config), "appendable-test", "non-appendable-key2+", NM_CONFIG_GET_VALUE_RAW)); + g_assert (!nm_config_data_has_value (nm_config_get_data_orig (config), "appendable-test", "non-appendable-key2-", NM_CONFIG_GET_VALUE_RAW)); + +#define ASSERT_GET_CONN_DEFAULT(xconfig, xname, xvalue) \ + G_STMT_START { \ + gs_free char *_value = nm_config_data_get_connection_default (nm_config_get_data_orig (xconfig), (xname), NULL); \ + g_assert_cmpstr (_value, ==, (xvalue)); \ + } G_STMT_END + ASSERT_GET_CONN_DEFAULT (config, "ord.key00", "A-0.0.00"); + ASSERT_GET_CONN_DEFAULT (config, "ord.key01", "A-0.3.01"); + ASSERT_GET_CONN_DEFAULT (config, "ord.key02", "A-0.2.02"); + ASSERT_GET_CONN_DEFAULT (config, "ord.key03", "A-0.1.03"); + ASSERT_GET_CONN_DEFAULT (config, "ord.key04", "B-1.3.04"); + ASSERT_GET_CONN_DEFAULT (config, "ord.key05", "B-1.2.05"); + ASSERT_GET_CONN_DEFAULT (config, "ord.key06", "B-1.1.06"); + ASSERT_GET_CONN_DEFAULT (config, "ord.key07", "C-2.3.07"); + ASSERT_GET_CONN_DEFAULT (config, "ord.key08", "C-2.2.08"); + ASSERT_GET_CONN_DEFAULT (config, "ord.key09", "C-2.1.09"); + ASSERT_GET_CONN_DEFAULT (config, "ord.ovw01", "C-0.1.ovw01"); + + value = nm_config_data_get_value (nm_config_get_data_orig (config), NM_CONFIG_KEYFILE_GROUPPREFIX_TEST_APPEND_STRINGLIST".1", "val1", NM_CONFIG_GET_VALUE_NONE); + g_assert_cmpstr (value, ==, "a,c"); + g_free (value); + + value = nm_config_data_get_value (nm_config_get_data_orig (config), NM_CONFIG_KEYFILE_GROUPPREFIX_TEST_APPEND_STRINGLIST".1", "val2", NM_CONFIG_GET_VALUE_NONE); + g_assert_cmpstr (value, ==, "VAL2"); + g_free (value); + + value = nm_config_data_get_value (nm_config_get_data_orig (config), NM_CONFIG_KEYFILE_GROUPPREFIX_TEST_APPEND_STRINGLIST".1", "val3", NM_CONFIG_GET_VALUE_NONE); + g_assert_cmpstr (value, ==, NULL); + g_free (value); + + value = nm_config_data_get_value (nm_config_get_data_orig (config), NM_CONFIG_KEYFILE_GROUPPREFIX_TEST_APPEND_STRINGLIST".1", "val4", NM_CONFIG_GET_VALUE_NONE); + g_assert_cmpstr (value, ==, "vb,vb"); + g_free (value); + + value = nm_config_data_get_value (nm_config_get_data_orig (config), NM_CONFIG_KEYFILE_GROUPPREFIX_TEST_APPEND_STRINGLIST".1", "val5", NM_CONFIG_GET_VALUE_NONE); + g_assert_cmpstr (value, ==, "VAL5"); + g_free (value); + + nm_config_data_log (nm_config_get_data_orig (config), ">>> TEST: "); + g_object_unref (config); } diff --git a/src/tests/test-general.c b/src/tests/test-general.c index dae872c86d..de65d50fd9 100644 --- a/src/tests/test-general.c +++ b/src/tests/test-general.c @@ -630,12 +630,29 @@ static void test_match_spec_ifname (const char *spec_str, const char **matches, const char **neg_matches) { const char *m; - GSList *specs, *specs_reverse = NULL; + GSList *specs, *specs_reverse = NULL, *specs_resplit, *specs_i, *specs_j; guint i; + gs_free char *specs_joined = NULL; g_assert (spec_str); specs = nm_match_spec_split (spec_str); + + /* assert that split(join(specs)) == specs */ + specs_joined = nm_match_spec_join (specs); + specs_resplit = nm_match_spec_split (specs_joined); + specs_i = specs; + specs_j = specs_resplit; + while (specs_i && specs_j && g_strcmp0 (specs_i->data, specs_j->data) == 0) { + specs_i = specs_i->next; + specs_j = specs_j->next; + } + g_assert (!specs_i); + g_assert (!specs_j); + g_slist_free_full (specs_resplit, g_free); + + /* also check the matches in the reverse order. They must yield the same result because + * matches are inclusive -- except "except:" which always wins. */ specs_reverse = g_slist_reverse (g_slist_copy (specs)); for (i = 0; matches && matches[i]; i++) { @@ -702,9 +719,12 @@ test_nm_match_spec_interface_name (void) test_match_spec_ifname ("interface-name:em\\;1,em\\,2,\\,,\\\\,,em\\\\x", S ("em;1", "em,2", ",", "\\", "em\\x"), NULL); - test_match_spec_ifname (" , interface-name:a, ,", + test_match_spec_ifname ("\\s\\s,\\sinterface-name:a,\\s,", S (" ", " ", " interface-name:a"), NULL); + test_match_spec_ifname (" aa ; bb ; cc\\;dd ;e , ; \t\\t , ", + S ("aa", "bb", "cc;dd", "e", "\t"), + NULL); #undef S } |