diff --git a/man/NetworkManager.conf.xml.in b/man/NetworkManager.conf.xml.in
index 800b0882f8..83143eecaf 100644
--- a/man/NetworkManager.conf.xml.in
+++ b/man/NetworkManager.conf.xml.in
@@ -482,7 +482,7 @@ ipv6.ip6-privacy=1
- 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 [connection] section is always
considered last. In the example above, this order is [connection-wifi-wlan0],
[connection-wlan-other], and [connection].
@@ -495,6 +495,11 @@ ipv6.ip6-privacy=1
"[connection-wifi-wlan0]" matches the device, it does not contain that property
and the search continues.
+
+ 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.
+
diff --git a/src/nm-config-data.c b/src/nm-config-data.c
index 73c36312f3..cd968ea6cc 100644
--- a/src/nm-config-data.c
+++ b/src/nm-config-data.c
@@ -225,58 +225,63 @@ nm_config_data_get_connection_default (const NMConfigData *self,
return NULL;
}
+static void
+_get_connection_info_init (ConnectionInfo *connection_info, GKeyFile *keyfile, char *group)
+{
+ char *value;
+
+ /* pass ownership of @group on... */
+ connection_info->group_name = group;
+
+ value = g_key_file_get_value (keyfile, group, "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, 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], "connection")) {
+ if (groups[i][STRLEN ("connection")] == '\0')
+ connection_tag = groups[i];
+ else
+ groups[j++] = groups[i];
+ } else
+ g_free (groups[i]);
+ }
+ ngroups = j;
+ }
+
+ 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_tag) {
+ /* pass ownership of @connection_tag on... */
+ _get_connection_info_init (&connection_infos[i], keyfile, connection_tag);
}
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);
- }
- 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);
- }
return connection_infos;
}
diff --git a/src/nm-config.c b/src/nm-config.c
index ac5523d817..c297318dbc 100644
--- a/src/nm-config.c
+++ b/src/nm-config.c
@@ -408,6 +408,45 @@ nm_config_create_keyfile ()
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, "connection");
+ b_is_connection = g_str_has_prefix (b, "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
read_config (GKeyFile *keyfile, const char *path, GError **error)
{
@@ -435,6 +474,25 @@ 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);
if (!keys)
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..0c246a02a4 100644
--- a/src/tests/config/conf.d/00-overrides.conf
+++ b/src/tests/config/conf.d/00-overrides.conf
@@ -9,3 +9,25 @@ 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
+
diff --git a/src/tests/config/conf.d/10-more.conf b/src/tests/config/conf.d/10-more.conf
index b1424a4bc8..08b73ddfdc 100644
--- a/src/tests/config/conf.d/10-more.conf
+++ b/src/tests/config/conf.d/10-more.conf
@@ -9,3 +9,21 @@ 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
diff --git a/src/tests/config/test-config.c b/src/tests/config/test-config.c
index 4a23f23fe5..9057f26850 100644
--- a/src/tests/config/test-config.c
+++ b/src/tests/config/test-config.c
@@ -324,6 +324,23 @@ test_config_confdir (void)
g_assert_cmpstr (value, ==, "0");
g_free (value);
+#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");
+
g_object_unref (config);
}