about summary refs log tree commit diff
path: root/src/settings
diff options
context:
space:
mode:
authorSebastien Bacher <seb128@ubuntu.com>2019-01-25 11:26:25 +0100
committerSebastien Bacher <seb128@ubuntu.com>2019-01-25 11:26:25 +0100
commitf54a568880bf47c2ea54c9b30d291156b34d6539 (patch)
treeaab9bb44b306da7651e8caf38bbab5cfeeb81b7d /src/settings
parente8ac7dacdcf4b38addef4f8ac6ab038f9d29582c (diff)
parentbbae86d3d2997a853ca0365e8eb7a3ca7489ee09 (diff)
Update upstream source from tag 'upstream/1.15.2'
Update to upstream version '1.15.2'
with Debian dir b4be4b7cfce96679a31cb9d423107789761e3b49
Diffstat (limited to 'src/settings')
-rw-r--r--src/settings/nm-agent-manager.c46
-rw-r--r--src/settings/nm-settings-connection.c60
-rw-r--r--src/settings/nm-settings.c37
-rw-r--r--src/settings/nm-settings.h2
-rw-r--r--src/settings/plugins/ibft/meson.build6
-rwxr-xr-xsrc/settings/plugins/ibft/tests/iscsiadm-test-bad-dns121
-rwxr-xr-xsrc/settings/plugins/ibft/tests/iscsiadm-test-bad-dns221
-rwxr-xr-xsrc/settings/plugins/ibft/tests/iscsiadm-test-bad-entry20
-rwxr-xr-xsrc/settings/plugins/ibft/tests/iscsiadm-test-bad-gateway21
-rwxr-xr-xsrc/settings/plugins/ibft/tests/iscsiadm-test-bad-ipaddr21
-rwxr-xr-xsrc/settings/plugins/ibft/tests/iscsiadm-test-bad-record18
-rwxr-xr-xsrc/settings/plugins/ibft/tests/iscsiadm-test-dhcp33
-rwxr-xr-xsrc/settings/plugins/ibft/tests/iscsiadm-test-static35
-rwxr-xr-xsrc/settings/plugins/ibft/tests/iscsiadm-test-vlan19
-rw-r--r--src/settings/plugins/ibft/tests/meson.build4
-rw-r--r--src/settings/plugins/ifcfg-rh/meson.build12
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c9
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c48
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c9
-rw-r--r--src/settings/plugins/ifcfg-rh/shvar.c6
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/meson.build4
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Band_A.cexpected2
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Hidden.cexpected2
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_always.cexpected2
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_default.cexpected2
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_missing.cexpected2
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_never.cexpected2
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-42
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4.expected2
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-never-default2
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c12
-rw-r--r--src/settings/plugins/ifupdown/meson.build10
-rw-r--r--src/settings/plugins/ifupdown/tests/meson.build4
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-plugin.c69
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-reader.c9
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-utils.c327
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-utils.h43
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-writer.c6
-rw-r--r--src/settings/plugins/keyfile/tests/meson.build2
-rw-r--r--src/settings/plugins/keyfile/tests/test-keyfile.c121
40 files changed, 703 insertions, 370 deletions
diff --git a/src/settings/nm-agent-manager.c b/src/settings/nm-agent-manager.c
index 953d3ce4..edadee14 100644
--- a/src/settings/nm-agent-manager.c
+++ b/src/settings/nm-agent-manager.c
@@ -1059,49 +1059,6 @@ _con_get_request_start_validated (NMAuthChain *chain,
 }
 
 static void
-has_system_secrets_check (NMSetting *setting,
-                          const char *key,
-                          const GValue *value,
-                          GParamFlags flags,
-                          gpointer user_data)
-{
-	NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE;
-	gboolean *has_system = user_data;
-
-	if (!(flags & NM_SETTING_PARAM_SECRET))
-		return;
-
-	/* Clear out system-owned or always-ask secrets */
-	if (NM_IS_SETTING_VPN (setting) && !strcmp (key, NM_SETTING_VPN_SECRETS)) {
-		GHashTableIter iter;
-		const char *secret_name = NULL;
-
-		/* VPNs are special; need to handle each secret separately */
-		g_hash_table_iter_init (&iter, (GHashTable *) g_value_get_boxed (value));
-		while (g_hash_table_iter_next (&iter, (gpointer *) &secret_name, NULL)) {
-			secret_flags = NM_SETTING_SECRET_FLAG_NONE;
-			nm_setting_get_secret_flags (setting, secret_name, &secret_flags, NULL);
-			if (secret_flags == NM_SETTING_SECRET_FLAG_NONE)
-				*has_system = TRUE;
-		}
-	} else {
-		if (!nm_setting_get_secret_flags (setting, key, &secret_flags, NULL))
-			g_return_if_reached ();
-		if (secret_flags == NM_SETTING_SECRET_FLAG_NONE)
-			*has_system = TRUE;
-	}
-}
-
-static gboolean
-has_system_secrets (NMConnection *connection)
-{
-	gboolean has_system = FALSE;
-
-	nm_connection_for_each_setting_value (connection, has_system_secrets_check, &has_system);
-	return has_system;
-}
-
-static void
 _con_get_request_start (Request *req)
 {
 	NMAgentManager *self;
@@ -1121,7 +1078,8 @@ _con_get_request_start (Request *req)
 	 * unprivileged users.
 	 */
 	if (   (req->con.get.flags != NM_SECRET_AGENT_GET_SECRETS_FLAG_NONE)
-	    && (req->con.get.existing_secrets || has_system_secrets (req->con.connection))) {
+	    && (   req->con.get.existing_secrets
+	        || _nm_connection_aggregate (req->con.connection, NM_CONNECTION_AGGREGATE_ANY_SYSTEM_SECRET_FLAGS, NULL))) {
 		_LOGD (NULL, "("LOG_REQ_FMT") request has system secrets; checking agent %s for MODIFY",
 		       LOG_REQ_ARG (req), agent_dbus_owner);
 
diff --git a/src/settings/nm-settings-connection.c b/src/settings/nm-settings-connection.c
index 4c25d0e9..0beb5ea7 100644
--- a/src/settings/nm-settings-connection.c
+++ b/src/settings/nm-settings-connection.c
@@ -640,7 +640,9 @@ nm_settings_connection_update (NMSettingsConnection *self,
 	gboolean replaced = FALSE;
 	gs_free char *logmsg_change = NULL;
 	GError *local = NULL;
+	gs_unref_object NMConnection *simple = NULL;
 	gs_unref_variant GVariant *con_agent_secrets = NULL;
+	gs_unref_variant GVariant *new_agent_secrets = NULL;
 
 	g_return_val_if_fail (NM_IS_SETTINGS_CONNECTION (self), FALSE);
 
@@ -681,6 +683,16 @@ nm_settings_connection_update (NMSettingsConnection *self,
 
 	replace_connection = reread_connection ?: new_connection;
 
+	/* Save agent-owned secrets from the new connection for later use */
+	if (new_connection) {
+		simple = nm_simple_connection_new_clone (new_connection);
+		nm_connection_clear_secrets_with_flags (simple,
+		                                        secrets_filter_cb,
+		                                        GUINT_TO_POINTER (NM_SETTING_SECRET_FLAG_AGENT_OWNED));
+		new_agent_secrets = nm_connection_to_dbus (simple, NM_CONNECTION_SERIALIZE_ONLY_SECRETS);
+		g_clear_object (&simple);
+	}
+
 	/* Disconnect the changed signal to ensure we don't set Unsaved when
 	 * it's not required.
 	 */
@@ -691,7 +703,6 @@ nm_settings_connection_update (NMSettingsConnection *self,
 	    && !nm_connection_compare (nm_settings_connection_get_connection (self),
 	                               replace_connection,
 	                               NM_SETTING_COMPARE_FLAG_EXACT)) {
-		gs_unref_object NMConnection *simple = NULL;
 
 		if (log_diff_name) {
 			nm_utils_log_connection_diff (replace_connection, nm_settings_connection_get_connection (self), LOGL_DEBUG, LOGD_CORE, log_diff_name, "++ ",
@@ -738,6 +749,15 @@ nm_settings_connection_update (NMSettingsConnection *self,
 			(void) nm_connection_update_secrets (nm_settings_connection_get_connection (self), NULL, con_agent_secrets, NULL);
 	}
 
+	/* Apply agent-owned secrets from the new connection so that
+	 * they can be sent to agents */
+	if (new_agent_secrets) {
+		(void) nm_connection_update_secrets (nm_settings_connection_get_connection (self),
+		                                     NULL,
+		                                     new_agent_secrets,
+		                                     NULL);
+	}
+
 	nm_settings_connection_recheck_visibility (self);
 
 	if (   replaced
@@ -770,7 +790,7 @@ out:
 		else if (new_connection)
 			_LOGI ("write: successfully updated (%s)", logmsg_change);
 		else
-			_LOGI ("write: successfully commited (%s)", logmsg_change);
+			_LOGI ("write: successfully committed (%s)", logmsg_change);
 	}
 	return TRUE;
 }
@@ -1318,7 +1338,7 @@ nm_settings_connection_get_secrets (NMSettingsConnection *self,
 	/* we remember the current version-id of the secret-agents. The version-id is strictly increasing,
 	 * as new agents register the number. We know hence, that this request was made against a certain
 	 * set of secret-agents.
-	 * If after making this request a new secret-agent registeres, the version-id increases.
+	 * If after making this request a new secret-agent registers, the version-id increases.
 	 * Then we know that the this request probably did not yet include the latest secret-agent. */
 	priv->last_secret_agent_version_id = nm_agent_manager_get_agent_version_id (priv->agent_mgr);
 
@@ -1640,38 +1660,6 @@ typedef struct {
 } UpdateInfo;
 
 static void
-has_some_secrets_cb (NMSetting *setting,
-                     const char *key,
-                     const GValue *value,
-                     GParamFlags flags,
-                     gpointer user_data)
-{
-	GParamSpec *pspec;
-
-	if (NM_IS_SETTING_VPN (setting)) {
-		if (nm_setting_vpn_get_num_secrets (NM_SETTING_VPN(setting)))
-			*((gboolean *) user_data) = TRUE;
-		return;
-	}
-
-	pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (G_OBJECT (setting)), key);
-	if (pspec) {
-		if (   (flags & NM_SETTING_PARAM_SECRET)
-		    && !g_param_value_defaults (pspec, (GValue *)value))
-			*((gboolean *) user_data) = TRUE;
-	}
-}
-
-static gboolean
-any_secrets_present (NMConnection *self)
-{
-	gboolean has_secrets = FALSE;
-
-	nm_connection_for_each_setting_value (self, has_some_secrets_cb, &has_secrets);
-	return has_secrets;
-}
-
-static void
 cached_secrets_to_connection (NMSettingsConnection *self, NMConnection *connection)
 {
 	NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
@@ -1738,7 +1726,7 @@ update_auth_cb (NMSettingsConnection *self,
 	}
 
 	if (info->new_settings) {
-		if (!any_secrets_present (info->new_settings)) {
+		if (!_nm_connection_aggregate (info->new_settings, NM_CONNECTION_AGGREGATE_ANY_SECRETS, NULL)) {
 			/* If the new connection has no secrets, we do not want to remove all
 			 * secrets, rather we keep all the existing ones. Do that by merging
 			 * them in to the new connection.
diff --git a/src/settings/nm-settings.c b/src/settings/nm-settings.c
index 2253d56a..74de6cc2 100644
--- a/src/settings/nm-settings.c
+++ b/src/settings/nm-settings.c
@@ -129,6 +129,8 @@ typedef struct {
 
 	NMHostnameManager *hostname_manager;
 
+	NMSettingsConnection *startup_complete_blocked_by;
+
 	guint connections_len;
 
 	bool started:1;
@@ -182,19 +184,23 @@ static void
 check_startup_complete (NMSettings *self)
 {
 	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
-	NMSettingsConnection *conn;
+	NMSettingsConnection *sett_conn;
 
 	if (priv->startup_complete)
 		return;
 
-	c_list_for_each_entry (conn, &priv->connections_lst_head, _connections_lst) {
-		if (!nm_settings_connection_get_ready (conn))
+	c_list_for_each_entry (sett_conn, &priv->connections_lst_head, _connections_lst) {
+		if (!nm_settings_connection_get_ready (sett_conn)) {
+			nm_g_object_ref_set (&priv->startup_complete_blocked_by, sett_conn);
 			return;
+		}
 	}
 
+	g_clear_object (&priv->startup_complete_blocked_by);
+
 	/* the connection_ready_changed signal handler is no longer needed. */
-	c_list_for_each_entry (conn, &priv->connections_lst_head, _connections_lst)
-		g_signal_handlers_disconnect_by_func (conn, G_CALLBACK (connection_ready_changed), self);
+	c_list_for_each_entry (sett_conn, &priv->connections_lst_head, _connections_lst)
+		g_signal_handlers_disconnect_by_func (sett_conn, G_CALLBACK (connection_ready_changed), self);
 
 	priv->startup_complete = TRUE;
 	_notify (self, PROP_STARTUP_COMPLETE);
@@ -372,7 +378,7 @@ _clear_connections_cached_list (NMSettingsPrivate *priv)
  * @out_len: (out): (allow-none): returns the number of returned
  *   connections.
  *
- * Returns: (transfer-none): a list of NMSettingsConnections. The list is
+ * Returns: (transfer none): a list of NMSettingsConnections. The list is
  * unsorted and NULL terminated. The result is never %NULL, in case of no
  * connections, it returns an empty list.
  * The returned list is cached internally, only valid until the next
@@ -840,10 +846,10 @@ connection_removed (NMSettingsConnection *connection, gpointer user_data)
 	if (priv->connections_loaded)
 		g_signal_emit (self, signals[CONNECTION_REMOVED], 0, connection);
 
-	g_object_unref (connection);
-
 	check_startup_complete (self);
 
+	g_object_unref (connection);
+
 	g_object_unref (self);       /* Balanced by a ref in claim_connection() */
 }
 
@@ -1754,12 +1760,17 @@ nm_settings_device_removed (NMSettings *self, NMDevice *device, gboolean quittin
 
 /*****************************************************************************/
 
-gboolean
-nm_settings_get_startup_complete (NMSettings *self)
+const char *
+nm_settings_get_startup_complete_blocked_reason (NMSettings *self)
 {
 	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	const char *uuid = NULL;
 
-	return priv->startup_complete;
+	if (priv->startup_complete)
+		return NULL;
+	if (priv->startup_complete_blocked_by)
+		uuid = nm_settings_connection_get_uuid (priv->startup_complete_blocked_by);
+	return uuid ?: "unknown";
 }
 
 /*****************************************************************************/
@@ -1845,7 +1856,7 @@ get_property (GObject *object, guint prop_id,
 			g_value_set_boxed (value, NULL);
 		break;
 	case PROP_STARTUP_COMPLETE:
-		g_value_set_boolean (value, nm_settings_get_startup_complete (self));
+		g_value_set_boolean (value, !nm_settings_get_startup_complete_blocked_reason (self));
 		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -1878,6 +1889,8 @@ dispose (GObject *object)
 	NMSettings *self = NM_SETTINGS (object);
 	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
 
+	g_clear_object (&priv->startup_complete_blocked_by);
+
 	g_slist_free_full (priv->auths, (GDestroyNotify) nm_auth_chain_destroy);
 	priv->auths = NULL;
 
diff --git a/src/settings/nm-settings.h b/src/settings/nm-settings.h
index 38d8ad4e..eb74c09c 100644
--- a/src/settings/nm-settings.h
+++ b/src/settings/nm-settings.h
@@ -112,6 +112,6 @@ void nm_settings_device_added (NMSettings *self, NMDevice *device);
 
 void nm_settings_device_removed (NMSettings *self, NMDevice *device, gboolean quitting);
 
-gboolean nm_settings_get_startup_complete (NMSettings *self);
+const char *nm_settings_get_startup_complete_blocked_reason (NMSettings *self);
 
 #endif  /* __NM_SETTINGS_H__ */
diff --git a/src/settings/plugins/ibft/meson.build b/src/settings/plugins/ibft/meson.build
index c7dbe459..c33b24cc 100644
--- a/src/settings/plugins/ibft/meson.build
+++ b/src/settings/plugins/ibft/meson.build
@@ -8,7 +8,7 @@ libnms_ibft_core = static_library(
 
 sources = files(
   'nms-ibft-connection.c',
-  'nms-ibft-plugin.c'
+  'nms-ibft-plugin.c',
 )
 
 libnm_settings_plugin_ibft = shared_module(
@@ -19,7 +19,7 @@ libnm_settings_plugin_ibft = shared_module(
   link_args: ldflags_linker_script_settings,
   link_depends: linker_script_settings,
   install: true,
-  install_dir: nm_plugindir
+  install_dir: nm_plugindir,
 )
 
 core_plugins += libnm_settings_plugin_ibft
@@ -29,7 +29,7 @@ core_plugins += libnm_settings_plugin_ibft
 run_target(
   'check-local-symbols-settings-ibft',
   command: [check_so_symbols, libnm_settings_plugin_ibft.full_path()],
-  depends: libnm_settings_plugin_ibft
+  depends: libnm_settings_plugin_ibft,
 )
 
 check-local-symbols-settings-ibft: src/settings/plugins/ibft/libnm-settings-plugin-ibft.la
diff --git a/src/settings/plugins/ibft/tests/iscsiadm-test-bad-dns1 b/src/settings/plugins/ibft/tests/iscsiadm-test-bad-dns1
new file mode 100755
index 00000000..54f02da6
--- /dev/null
+++ b/src/settings/plugins/ibft/tests/iscsiadm-test-bad-dns1
@@ -0,0 +1,21 @@
+#!/bin/bash
+
+cat << EOF
+# BEGIN RECORD
+iface.initiatorname = iqn.pjones6
+iface.hwaddress = 00:33:21:98:b9:f0
+iface.bootproto = STATIC
+iface.ipaddress = 192.168.32.72
+iface.subnet_mask = 255.255.252.0
+iface.gateway = 192.168.35.254
+iface.primary_dns = 10000.500.250.1
+iface.secondary_dns = 10.16.255.3
+iface.vlan_id = 0
+iface.net_ifacename = eth0
+node.name = iqn.0.2008-11.com.blahblah:iscsi0
+node.conn[0].address = 10.16.52.16
+node.conn[0].port = 3260
+node.boot_lun = 00000000
+# END RECORD
+EOF
+
diff --git a/src/settings/plugins/ibft/tests/iscsiadm-test-bad-dns2 b/src/settings/plugins/ibft/tests/iscsiadm-test-bad-dns2
new file mode 100755
index 00000000..ebd7a9ca
--- /dev/null
+++ b/src/settings/plugins/ibft/tests/iscsiadm-test-bad-dns2
@@ -0,0 +1,21 @@
+#!/bin/bash
+
+cat << EOF
+# BEGIN RECORD
+iface.initiatorname = iqn.pjones6
+iface.hwaddress = 00:33:21:98:b9:f0
+iface.bootproto = STATIC
+iface.ipaddress = 192.168.32.72
+iface.subnet_mask = 255.255.252.0
+iface.gateway = 192.168.35.254
+iface.primary_dns = 10.16.255.2
+iface.secondary_dns = blah.foo.bar.baz
+iface.vlan_id = 0
+iface.net_ifacename = eth0
+node.name = iqn.0.2008-11.com.blahblah:iscsi0
+node.conn[0].address = 10.16.52.16
+node.conn[0].port = 3260
+node.boot_lun = 00000000
+# END RECORD
+EOF
+
diff --git a/src/settings/plugins/ibft/tests/iscsiadm-test-bad-entry b/src/settings/plugins/ibft/tests/iscsiadm-test-bad-entry
new file mode 100755
index 00000000..4e326048
--- /dev/null
+++ b/src/settings/plugins/ibft/tests/iscsiadm-test-bad-entry
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+cat << EOF
+# BEGIN RECORD
+iface.initiatorname = iqn.pjones6
+iface.hwaddress = 00:33:21:98:b9:f0
+iface.bootproto = STATIC
+iface.ipaddress 192.168.32.72
+iface.subnet_mask = 255.255.252.0
+iface.gateway = 192.168.35.254
+iface.primary_dns = 10.16.255.2
+iface.secondary_dns = 10.16.255.3
+iface.vlan_id = 0
+iface.net_ifacename = eth0
+node.name = iqn.0.2008-11.com.blahblah:iscsi0
+node.conn[0].address = 10.16.52.16
+node.conn[0].port = 3260
+node.boot_lun = 00000000
+# END RECORD
+
diff --git a/src/settings/plugins/ibft/tests/iscsiadm-test-bad-gateway b/src/settings/plugins/ibft/tests/iscsiadm-test-bad-gateway
new file mode 100755
index 00000000..5390a6c3
--- /dev/null
+++ b/src/settings/plugins/ibft/tests/iscsiadm-test-bad-gateway
@@ -0,0 +1,21 @@
+#!/bin/bash
+
+cat << EOF
+# BEGIN RECORD
+iface.initiatorname = iqn.pjones6
+iface.hwaddress = 00:33:21:98:b9:f0
+iface.bootproto = STATIC
+iface.ipaddress = 192.168.32.72
+iface.subnet_mask = 255.255.252.0
+iface.gateway = bb.cc.dd.ee
+iface.primary_dns = 10.16.255.2
+iface.secondary_dns = 10.16.255.3
+iface.vlan_id = 0
+iface.net_ifacename = eth0
+node.name = iqn.0.2008-11.com.blahblah:iscsi0
+node.conn[0].address = 10.16.52.16
+node.conn[0].port = 3260
+node.boot_lun = 00000000
+# END RECORD
+EOF
+
diff --git a/src/settings/plugins/ibft/tests/iscsiadm-test-bad-ipaddr b/src/settings/plugins/ibft/tests/iscsiadm-test-bad-ipaddr
new file mode 100755
index 00000000..b41cd1f1
--- /dev/null
+++ b/src/settings/plugins/ibft/tests/iscsiadm-test-bad-ipaddr
@@ -0,0 +1,21 @@
+#!/bin/bash
+
+cat << EOF
+# BEGIN RECORD
+iface.initiatorname = iqn.pjones6
+iface.hwaddress = 00:33:21:98:b9:f0
+iface.bootproto = STATIC
+iface.ipaddress = aa.bb.cc.dd
+iface.subnet_mask = 255.255.252.0
+iface.gateway = 192.168.35.254
+iface.primary_dns = 10.16.255.2
+iface.secondary_dns = 10.16.255.3
+iface.vlan_id = 0
+iface.net_ifacename = eth0
+node.name = iqn.0.2008-11.com.blahblah:iscsi0
+node.conn[0].address = 10.16.52.16
+node.conn[0].port = 3260
+node.boot_lun = 00000000
+# END RECORD
+EOF
+
diff --git a/src/settings/plugins/ibft/tests/iscsiadm-test-bad-record b/src/settings/plugins/ibft/tests/iscsiadm-test-bad-record
new file mode 100755
index 00000000..22b34e6f
--- /dev/null
+++ b/src/settings/plugins/ibft/tests/iscsiadm-test-bad-record
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+cat << EOF
+# BEGIN RECORD
+iface.initiatorname = iqn.pjones6
+iface.hwaddress = 00:33:21:98:b9:f0
+iface.bootproto = DHCP
+iface.gateway = 10.16.52.254
+iface.primary_dns = 10.16.255.2
+iface.secondary_dns = 10.16.255.3
+iface.vlan_id = 0
+iface.net_ifacename = eth0
+node.name = iqn.0.2008-11.com.blahblah:iscsi0
+node.conn[0].address = 10.16.52.16
+node.conn[0].port = 3260
+node.boot_lun = 00000000
+EOF
+
diff --git a/src/settings/plugins/ibft/tests/iscsiadm-test-dhcp b/src/settings/plugins/ibft/tests/iscsiadm-test-dhcp
new file mode 100755
index 00000000..556b0586
--- /dev/null
+++ b/src/settings/plugins/ibft/tests/iscsiadm-test-dhcp
@@ -0,0 +1,33 @@
+#!/bin/bash
+
+cat << EOF
+# BEGIN RECORD
+iface.initiatorname = iqn.pjones6
+iface.hwaddress = 00:33:21:98:b9:f0
+iface.bootproto = DHCP
+iface.gateway = 10.16.52.254
+iface.primary_dns = 10.16.255.2
+iface.secondary_dns = 10.16.255.3
+iface.vlan_id = 0
+iface.net_ifacename = eth0
+node.name = iqn.0.2008-11.com.blahblah:iscsi0
+node.conn[0].address = 10.16.52.16
+node.conn[0].port = 3260
+node.boot_lun = 00000000
+# END RECORD
+# BEGIN RECORD
+iface.initiatorname = iqn.pjones6
+iface.hwaddress = 00:33:21:98:b9:f1
+iface.bootproto = DHCP
+iface.gateway = 10.16.52.254
+iface.primary_dns = 10.16.255.2
+iface.secondary_dns = 10.16.255.3
+iface.vlan_id = 0
+iface.net_ifacename = eth1
+node.name = iqn.1.2008-11.com.blahblah:iscsi1
+node.conn[0].address = 10.16.52.16
+node.conn[0].port = 3260
+node.boot_lun = 00000000
+# END RECORD
+EOF
+
diff --git a/src/settings/plugins/ibft/tests/iscsiadm-test-static b/src/settings/plugins/ibft/tests/iscsiadm-test-static
new file mode 100755
index 00000000..51711480
--- /dev/null
+++ b/src/settings/plugins/ibft/tests/iscsiadm-test-static
@@ -0,0 +1,35 @@
+#!/bin/bash
+
+cat << EOF
+# BEGIN RECORD
+iface.initiatorname = iqn.pjones6
+iface.hwaddress = 00:33:21:98:b9:f0
+iface.bootproto = STATIC
+iface.ipaddress = 192.168.32.72
+iface.subnet_mask = 255.255.252.0
+iface.gateway = 192.168.35.254
+iface.primary_dns = 10.16.255.2
+iface.secondary_dns = 10.16.255.3
+iface.vlan_id = 0
+iface.net_ifacename = eth0
+node.name = iqn.0.2008-11.com.blahblah:iscsi0
+node.conn[0].address = 10.16.52.16
+node.conn[0].port = 3260
+node.boot_lun = 00000000
+# END RECORD
+# BEGIN RECORD
+iface.initiatorname = iqn.pjones6
+iface.hwaddress = 00:33:21:98:b9:f1
+iface.bootproto = DHCP
+iface.gateway = 10.16.52.254
+iface.primary_dns = 10.16.255.2
+iface.secondary_dns = 10.16.255.3
+iface.vlan_id = 0
+iface.net_ifacename = eth1
+node.name = iqn.1.2008-11.com.blahblah:iscsi1
+node.conn[0].address = 10.16.52.16
+node.conn[0].port = 3260
+node.boot_lun = 00000000
+# END RECORD
+EOF
+
diff --git a/src/settings/plugins/ibft/tests/iscsiadm-test-vlan b/src/settings/plugins/ibft/tests/iscsiadm-test-vlan
new file mode 100755
index 00000000..59b80bd0
--- /dev/null
+++ b/src/settings/plugins/ibft/tests/iscsiadm-test-vlan
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+cat << EOF
+# BEGIN RECORD 6.2.0.873-21
+iface.initiatorname = iqn.2010-04.org.ipxe:d05faa97-c4be-44f6-a723-efde9aa399a0
+iface.transport_name = tcp
+iface.hwaddress = 00:33:21:98:b9:f0
+iface.bootproto = STATIC
+iface.ipaddress = 192.168.6.200
+iface.subnet_mask = 255.255.255.0
+iface.vlan_id = 123
+iface.net_ifacename = eth0
+node.name = iqn.2003-01.org.x:disk1
+node.conn[0].address = 192.168.6.32
+node.conn[0].port = 3260
+node.boot_lun = 01000000
+# END RECORD
+EOF
+
diff --git a/src/settings/plugins/ibft/tests/meson.build b/src/settings/plugins/ibft/tests/meson.build
index 8b5e143a..e2f9ca7e 100644
--- a/src/settings/plugins/ibft/tests/meson.build
+++ b/src/settings/plugins/ibft/tests/meson.build
@@ -6,11 +6,11 @@ exe = executable(
   test_unit,
   test_unit + '.c',
   dependencies: test_nm_dep,
-  link_with: libnms_ibft_core
+  link_with: libnms_ibft_core,
 )
 
 test(
   'ibft/' + test_unit,
   test_script,
-  args: test_args + [exe.full_path()]
+  args: test_args + [exe.full_path()],
 )
diff --git a/src/settings/plugins/ifcfg-rh/meson.build b/src/settings/plugins/ifcfg-rh/meson.build
index e84ae80c..9024782a 100644
--- a/src/settings/plugins/ifcfg-rh/meson.build
+++ b/src/settings/plugins/ifcfg-rh/meson.build
@@ -1,6 +1,6 @@
 install_data(
   'nm-ifcfg-rh.conf',
-  install_dir: dbus_conf_dir
+  install_dir: dbus_conf_dir,
 )
 
 name = 'nmdbus-ifcfg-rh'
@@ -9,7 +9,7 @@ dbus_sources = gnome.gdbus_codegen(
   name,
   'nm-ifcfg-rh.xml',
   interface_prefix: 'com.redhat',
-  namespace: 'NMDBus'
+  namespace: 'NMDBus',
 )
 
 libnmdbus_ifcfg_rh = static_library(
@@ -23,11 +23,11 @@ core_sources = files(
   'nms-ifcfg-rh-reader.c',
   'nms-ifcfg-rh-utils.c',
   'nms-ifcfg-rh-writer.c',
-  'shvar.c'
+  'shvar.c',
 )
 
 deps = [
-  nm_dep
+  nm_dep,
 ]
 
 libnms_ifcfg_rh_core = static_library(
@@ -46,7 +46,7 @@ libnm_settings_plugin_ifcfg_rh = shared_module(
   link_args: ldflags_linker_script_settings,
   link_depends: linker_script_settings,
   install: true,
-  install_dir: nm_plugindir
+  install_dir: nm_plugindir,
 )
 
 core_plugins += libnm_settings_plugin_ifcfg_rh
@@ -56,7 +56,7 @@ core_plugins += libnm_settings_plugin_ifcfg_rh
 run_target(
   'check-local-symbols-settings-ifcfg-rh',
   command: [check_so_symbols, libnm_settings_plugin_ifcfg_rh.full_path()],
-  depends: libnm_settings_plugin_ifcfg_rh
+  depends: libnm_settings_plugin_ifcfg_rh,
 )
 
 check-local-symbols-settings-ifcfg-rh: src/settings/plugins/ifcfg-rh/libnm-settings-plugin-ifcfg-rh.la
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c
index 6cac8cb6..05d4d738 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c
@@ -539,7 +539,7 @@ read_connections (SettingsPluginIfcfg *plugin)
 	 * iterating over the files.
 	 *
 	 * To have sensible, reproducible behavior, sort the paths by last modification
-	 * time prefering older files.
+	 * time preferring older files.
 	 */
 	paths = _paths_from_connections (priv->connections);
 	g_ptr_array_sort_with_data (filenames, (GCompareDataFunc) _sort_paths, paths);
@@ -602,12 +602,9 @@ load_connection (NMSettingsPlugin *config,
 {
 	SettingsPluginIfcfg *plugin = SETTINGS_PLUGIN_IFCFG (config);
 	NMIfcfgConnection *connection;
-	int dir_len = strlen (IFCFG_DIR);
 	char *ifcfg_path;
 
-	if (   strncmp (filename, IFCFG_DIR, dir_len) != 0
-	    || filename[dir_len] != '/'
-	    || strchr (filename + dir_len + 1, '/') != NULL)
+	if (!nm_utils_file_is_in_path (filename, IFCFG_DIR))
 		return FALSE;
 
 	/* get the real ifcfg-path. This allows us to properly
@@ -987,7 +984,7 @@ config_changed_cb (NMConfig *config,
 	 * won't be offered.
 	 *
 	 * On SIGHUP and SIGUSR1 try to re-connect to D-Bus. So in the unlikely
-	 * event that the D-Bus conneciton is broken, that allows for recovery
+	 * event that the D-Bus connection is broken, that allows for recovery
 	 * without need for restarting NetworkManager. */
 	if (!NM_FLAGS_ANY (changes,   NM_CONFIG_CHANGE_CAUSE_SIGHUP
 	                            | NM_CONFIG_CHANGE_CAUSE_SIGUSR1))
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
index 09a37991..6eb99d3b 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
@@ -189,7 +189,7 @@ _secret_password_raw_to_bytes (const char *ifcfg_key,
 		password_raw += 2;
 
 	secret = nm_secret_buf_new (strlen (password_raw) / 2 + 3);
-	if (!_nm_utils_str2bin_full (password_raw, FALSE, ":", secret->bin, secret->len, &len)) {
+	if (!_nm_utils_hexstr2bin_full (password_raw, FALSE, FALSE, ":", 0, secret->bin, secret->len, &len)) {
 		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 		             "Invalid hex password in %s",
 		             ifcfg_key);
@@ -670,7 +670,7 @@ read_full_ip4_address (shvarFile *ifcfg,
 		                       &has_key, &a, error))
 			return FALSE;
 		if (has_key)
-			*out_gateway = g_strdup (nm_utils_inet4_ntop (a, inet_buf));
+			*out_gateway = nm_utils_inet4_ntop_dup (a);
 	}
 
 	/* Prefix */
@@ -810,7 +810,7 @@ enum {
  * setting, and one for initscript's handle_ip_file(), which takes the lines
  * and passes them to `ip route add`. The modes are similar, but certain properties
  * are not allowed for OPTIONS.
- * The mode is differenciated by having an @options_route argument.
+ * The mode is differentiated by having an @options_route argument.
  *
  * Returns: returns a negative errno on failure. On success, it returns 0
  *   and @out_route.
@@ -873,7 +873,7 @@ parse_route_line (const char *line,
 	};
 
 	nm_assert (line);
-	nm_assert (NM_IN_SET (addr_family, AF_INET, AF_INET6));
+	nm_assert_addr_family (addr_family);
 	nm_assert (!options_route || nm_ip_route_get_family (options_route) == addr_family);
 
 	/* initscripts read the legacy route file line-by-line and
@@ -1018,6 +1018,7 @@ parse_line_type_addr_with_prefix:
 			if (info->type == PARSE_LINE_TYPE_ADDR) {
 				if (!nm_utils_parse_inaddr_bin (addr_family,
 				                                s,
+				                                NULL,
 				                                &info->v.addr.addr)) {
 					if (   info == &infos[PARSE_LINE_ATTR_ROUTE_VIA]
 					    && nm_streq (s, "(null)")) {
@@ -1045,6 +1046,7 @@ parse_line_type_addr_with_prefix:
 					prefix = 0;
 				} else if (!nm_utils_parse_inaddr_prefix_bin (addr_family,
 				                                              s,
+				                                              NULL,
 				                                              &info->v.addr.addr,
 				                                              &prefix)) {
 					g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
@@ -1150,7 +1152,7 @@ next:
 			                                                    : ""));
 			break;
 		case PARSE_LINE_TYPE_FLAG:
-			/* NOTE: the flag (for "onlink") only allows to explictly set "TRUE".
+			/* NOTE: the flag (for "onlink") only allows to explicitly set "TRUE".
 			 * There is no way to express an explicit "FALSE" setting
 			 * of this attribute, hence, the file format cannot encode
 			 * that configuration. */
@@ -1527,7 +1529,6 @@ make_ip4_setting (shvarFile *ifcfg,
 	gboolean never_default;
 	gint64 timeout;
 	int priority;
-	char inet_buf[NM_UTILS_INET_ADDRSTRLEN];
 	const char *const *item;
 	guint32 route_table;
 
@@ -1679,7 +1680,7 @@ make_ip4_setting (shvarFile *ifcfg,
 					PARSE_WARNING ("ignoring GATEWAY (/etc/sysconfig/network) for %s "
 					               "because the connection has no static addresses", f);
 				} else
-					gateway = g_strdup (nm_utils_inet4_ntop (a, inet_buf));
+					gateway = nm_utils_inet4_ntop_dup (a);
 			}
 		}
 	}
@@ -2226,20 +2227,19 @@ make_sriov_setting (shvarFile *ifcfg)
 {
 	gs_unref_hashtable GHashTable *keys = NULL;
 	gs_unref_ptrarray GPtrArray *vfs = NULL;
-	NMTernary autoprobe_drivers;
+	int autoprobe_drivers;
 	NMSettingSriov *s_sriov;
-	int total_vfs;
+	gint64 total_vfs;
 
-	total_vfs = svGetValueInt64 (ifcfg, "SRIOV_TOTAL_VFS", 10, 0, G_MAXINT32, 0);
-	if (!total_vfs)
-		return NULL;
+
+	total_vfs = svGetValueInt64 (ifcfg, "SRIOV_TOTAL_VFS", 10, 0, G_MAXUINT32, -1);
 
 	autoprobe_drivers = svGetValueInt64 (ifcfg,
 	                                     "SRIOV_AUTOPROBE_DRIVERS",
 	                                     10,
-	                                     NM_TERNARY_FALSE,
+	                                     NM_TERNARY_DEFAULT,
 	                                     NM_TERNARY_TRUE,
-	                                     NM_TERNARY_DEFAULT);
+	                                     -2);
 
 	keys = svGetKeys (ifcfg, SV_KEY_TYPE_SRIOV_VF);
 	if (keys) {
@@ -2261,7 +2261,7 @@ make_sriov_setting (shvarFile *ifcfg)
 
 			key += NM_STRLEN ("SRIOV_VF");
 
-			vf = _nm_utils_sriov_vf_from_strparts (key, value, &error);
+			vf = _nm_utils_sriov_vf_from_strparts (key, value, TRUE, &error);
 			if (!vf) {
 				PARSE_WARNING ("ignoring invalid SR-IOV VF '%s %s': %s",
 				               key, value, error->message);
@@ -2273,11 +2273,21 @@ make_sriov_setting (shvarFile *ifcfg)
 		}
 	}
 
+	/* Create the setting when at least one key is set */
+	if (   total_vfs < 0
+	    && !vfs
+	    && autoprobe_drivers < NM_TERNARY_DEFAULT)
+		return NULL;
+
 	s_sriov = (NMSettingSriov *) nm_setting_sriov_new ();
+
+	autoprobe_drivers = NM_MAX (autoprobe_drivers, NM_TERNARY_DEFAULT);
+	total_vfs = NM_MAX (total_vfs, 0);
+
 	g_object_set (s_sriov,
-	              NM_SETTING_SRIOV_TOTAL_VFS, total_vfs,
+	              NM_SETTING_SRIOV_TOTAL_VFS, (guint) total_vfs,
 	              NM_SETTING_SRIOV_VFS, vfs,
-	              NM_SETTING_SRIOV_AUTOPROBE_DRIVERS, (int) autoprobe_drivers,
+	              NM_SETTING_SRIOV_AUTOPROBE_DRIVERS, autoprobe_drivers,
 	              NULL);
 
 	return (NMSetting *) s_sriov;
@@ -3151,7 +3161,7 @@ eap_tls_reader (const char *eap_method,
 	/* FIXME: writer does not actually write IEEE_8021X_CLIENT_CERT_PASSWORD and other
 	 * certificate related passwords. It should, because otherwise persisting such profiles
 	 * to ifcfg looses information. As this currently only matters for PKCS11 URIs, it seems
-	 * a seldomly used feature so that it is not fixed yet. */
+	 * a seldom used feature so that it is not fixed yet. */
 	_secret_set_from_ifcfg (s_8021x,
 	                        ifcfg,
 	                        keys_ifcfg,
@@ -3526,7 +3536,7 @@ fill_8021x (shvarFile *ifcfg,
 				goto next;
 
 			/* Some EAP methods don't provide keying material, thus they
-			 * cannot be used with WiFi unless they are an inner method
+			 * cannot be used with Wi-Fi unless they are an inner method
 			 * used with TTLS or PEAP or whatever.
 			 */
 			if (wifi && eap->wifi_phase2_only) {
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
index b70690cc..f5be7520 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
@@ -2223,16 +2223,15 @@ write_sriov_setting (NMConnection *connection, shvarFile *ifcfg)
 
 	svUnsetAll (ifcfg, SV_KEY_TYPE_SRIOV_VF);
 
-	s_sriov = NM_SETTING_SRIOV (nm_connection_get_setting (connection, NM_TYPE_SETTING_SRIOV));
-	if (s_sriov)
-		num = nm_setting_sriov_get_total_vfs (s_sriov);
-	if (num == 0) {
+	s_sriov = NM_SETTING_SRIOV (nm_connection_get_setting (connection,
+	                                                       NM_TYPE_SETTING_SRIOV));
+	if (!s_sriov) {
 		svUnsetValue (ifcfg, "SRIOV_TOTAL_VFS");
 		svUnsetValue (ifcfg, "SRIOV_AUTOPROBE_DRIVERS");
 		return;
 	}
 
-	svSetValueInt64 (ifcfg, "SRIOV_TOTAL_VFS", num);
+	svSetValueInt64 (ifcfg, "SRIOV_TOTAL_VFS", nm_setting_sriov_get_total_vfs (s_sriov));
 
 	b = nm_setting_sriov_get_autoprobe_drivers (s_sriov);
 	if (b != NM_TERNARY_DEFAULT)
diff --git a/src/settings/plugins/ifcfg-rh/shvar.c b/src/settings/plugins/ifcfg-rh/shvar.c
index fe82fbdd..3259d936 100644
--- a/src/settings/plugins/ifcfg-rh/shvar.c
+++ b/src/settings/plugins/ifcfg-rh/shvar.c
@@ -330,7 +330,7 @@ _gstr_init (GString **str, const char *value, gsize i)
 		 * Unescaping usually does not extend the length of a string,
 		 * so we might be tempted to allocate a fixed buffer of length
 		 * (strlen(value)+CONST).
-		 * However, due to $'\Ux' escapes, the maxium length is some
+		 * However, due to $'\Ux' escapes, the maximum length is some
 		 * (FACTOR*strlen(value) + CONST), which is non trivial to get
 		 * right in all cases. Also, we would have to provision for the
 		 * very unlikely extreme case.
@@ -453,7 +453,7 @@ svUnescape (const char *value, char **to_free)
 					if (NM_IN_SET (value[i], '$', '`', '"', '\\')) {
 						/* Drop the backslash. */
 					} else if (NM_IN_SET (value[i], '\'', '~')) {
-						/* '\'' and '~' in double qoutes are not handled special by shell.
+						/* '\'' and '~' in double quotes are not handled special by shell.
 						 * However, old versions of svEscape() would wrongly use double-quoting
 						 * with backslash escaping for these characters (expecting svUnescape()
 						 * to remove the backslash).
@@ -649,7 +649,7 @@ void
 _nmtst_svFileSetName (shvarFile *s, const char *fileName)
 {
 	/* changing the file name is not supported for regular
-	 * operation. Only allowed to use in tests, othewise,
+	 * operation. Only allowed to use in tests, otherwise,
 	 * the filename is immutable. */
 	g_free (s->fileName);
 	s->fileName = g_strdup (fileName);
diff --git a/src/settings/plugins/ifcfg-rh/tests/meson.build b/src/settings/plugins/ifcfg-rh/tests/meson.build
index 29bc9699..f65494bb 100644
--- a/src/settings/plugins/ifcfg-rh/tests/meson.build
+++ b/src/settings/plugins/ifcfg-rh/tests/meson.build
@@ -6,12 +6,12 @@ exe = executable(
   test_unit,
   test_unit + '.c',
   dependencies: test_nm_dep,
-  link_with: libnms_ifcfg_rh_core
+  link_with: libnms_ifcfg_rh_core,
 )
 
 test(
   'ifcfg-rh/' + test_unit,
   test_script,
   timeout: 90,
-  args: test_args + [exe.full_path()]
+  args: test_args + [exe.full_path()],
 )
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Band_A.cexpected b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Band_A.cexpected
index a95a58db..ddbd986f 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Band_A.cexpected
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Band_A.cexpected
@@ -13,6 +13,6 @@ IPV6_AUTOCONF=yes
 IPV6_DEFROUTE=yes
 IPV6_FAILURE_FATAL=no
 IPV6_ADDR_GEN_MODE=stable-privacy
-NAME="Test Write WiFi Band A"
+NAME="Test Write Wi-Fi Band A"
 UUID=${UUID}
 ONBOOT=yes
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Hidden.cexpected b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Hidden.cexpected
index cf325f35..495a24d3 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Hidden.cexpected
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_Hidden.cexpected
@@ -12,6 +12,6 @@ IPV6_AUTOCONF=yes
 IPV6_DEFROUTE=yes
 IPV6_FAILURE_FATAL=no
 IPV6_ADDR_GEN_MODE=stable-privacy
-NAME="Test Write WiFi Hidden"
+NAME="Test Write Wi-Fi Hidden"
 UUID=${UUID}
 ONBOOT=yes
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_always.cexpected b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_always.cexpected
index f3704f10..aec6918a 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_always.cexpected
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_always.cexpected
@@ -13,6 +13,6 @@ IPV6_AUTOCONF=yes
 IPV6_DEFROUTE=yes
 IPV6_FAILURE_FATAL=no
 IPV6_ADDR_GEN_MODE=stable-privacy
-NAME="Test Write WiFi MAC always"
+NAME="Test Write Wi-Fi MAC always"
 UUID=${UUID}
 ONBOOT=yes
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_default.cexpected b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_default.cexpected
index 005c6179..9d47163f 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_default.cexpected
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_default.cexpected
@@ -12,6 +12,6 @@ IPV6_AUTOCONF=yes
 IPV6_DEFROUTE=yes
 IPV6_FAILURE_FATAL=no
 IPV6_ADDR_GEN_MODE=stable-privacy
-NAME="Test Write WiFi MAC default"
+NAME="Test Write Wi-Fi MAC default"
 UUID=${UUID}
 ONBOOT=yes
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_missing.cexpected b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_missing.cexpected
index dff17ef2..43c07ddb 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_missing.cexpected
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_missing.cexpected
@@ -13,6 +13,6 @@ IPV6_AUTOCONF=yes
 IPV6_DEFROUTE=yes
 IPV6_FAILURE_FATAL=no
 IPV6_ADDR_GEN_MODE=stable-privacy
-NAME="Test Write WiFi MAC missing"
+NAME="Test Write Wi-Fi MAC missing"
 UUID=${UUID}
 ONBOOT=yes
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_never.cexpected b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_never.cexpected
index 94274cf9..21f2e2de 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_never.cexpected
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_WiFi_MAC_never.cexpected
@@ -13,6 +13,6 @@ IPV6_AUTOCONF=yes
 IPV6_DEFROUTE=yes
 IPV6_FAILURE_FATAL=no
 IPV6_ADDR_GEN_MODE=stable-privacy
-NAME="Test Write WiFi MAC never"
+NAME="Test Write Wi-Fi MAC never"
 UUID=${UUID}
 ONBOOT=yes
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4 b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4
index a7156e33..2c1b7fb4 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4
@@ -6,7 +6,7 @@
 # expected.
 #
 # Also note that setting NAME will replace the last
-# occurance, and delete all previous once.
+# occurrence, and delete all previous once.
 
 #L1
 NAME=l2
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4.expected b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4.expected
index 674df840..cf3f45bd 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4.expected
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4.expected
@@ -6,7 +6,7 @@
 # expected.
 #
 # Also note that setting NAME will replace the last
-# occurance, and delete all previous once.
+# occurrence, and delete all previous once.
 
 #L1
 
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-never-default b/src/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-never-default
index 9a292679..4347405e 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-never-default
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/network-test-wired-never-default
@@ -1,4 +1,4 @@
 GATEWAYDEV=eth0
-# when devices in IPV6_DEFAULTDEV and IPV6_DEFAULTGW don't match the one in IPV6_DEFAULTGW is prefered
+# when devices in IPV6_DEFAULTDEV and IPV6_DEFAULTGW don't match the one in IPV6_DEFAULTGW is preferred
 IPV6_DEFAULTDEV=eth4
 IPV6_DEFAULTGW=2001::1234%eth0
diff --git a/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c b/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
index 472bb8a6..d135ea43 100644
--- a/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
+++ b/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
@@ -2374,7 +2374,7 @@ test_read_wifi_open (void)
 	g_assert_cmpstr (nm_setting_wireless_get_mode (s_wireless), ==, "infrastructure");
 	g_assert_cmpint (nm_setting_wireless_get_channel (s_wireless), ==, 1);
 
-	/* ===== WiFi SECURITY SETTING ===== */
+	/* ===== Wi-Fi SECURITY SETTING ===== */
 	s_wsec = nm_connection_get_setting_wireless_security (connection);
 	g_assert (s_wsec == NULL);
 
@@ -3275,7 +3275,7 @@ test_read_wifi_dynamic_wep_leap (void)
 	s_wifi = nm_connection_get_setting_wireless (connection);
 	g_assert (s_wifi);
 
-	/* ===== WiFi SECURITY SETTING ===== */
+	/* ===== Wi-Fi SECURITY SETTING ===== */
 	s_wsec = nm_connection_get_setting_wireless_security (connection);
 	g_assert (s_wsec);
 
@@ -3501,7 +3501,7 @@ test_write_wifi_hidden (void)
 	nm_connection_add_setting (connection, NM_SETTING (s_con));
 
 	g_object_set (s_con,
-	              NM_SETTING_CONNECTION_ID, "Test Write WiFi Hidden",
+	              NM_SETTING_CONNECTION_ID, "Test Write Wi-Fi Hidden",
 	              NM_SETTING_CONNECTION_UUID, nm_utils_uuid_generate_a (),
 	              NM_SETTING_CONNECTION_TYPE, NM_SETTING_WIRELESS_SETTING_NAME,
 	              NULL);
@@ -3584,7 +3584,7 @@ test_write_wifi_mac_random (gconstpointer user_data)
 	s_con = (NMSettingConnection *) nm_setting_connection_new ();
 	nm_connection_add_setting (connection, NM_SETTING (s_con));
 
-	val = g_strdup_printf ("Test Write WiFi MAC %s", name);
+	val = g_strdup_printf ("Test Write Wi-Fi MAC %s", name);
 	g_object_set (s_con,
 	              NM_SETTING_CONNECTION_ID, val,
 	              NM_SETTING_CONNECTION_UUID, nm_utils_uuid_generate_a (),
@@ -3803,7 +3803,7 @@ test_write_wifi_band_a (void)
 	nm_connection_add_setting (connection, NM_SETTING (s_con));
 
 	g_object_set (s_con,
-	              NM_SETTING_CONNECTION_ID, "Test Write WiFi Band A",
+	              NM_SETTING_CONNECTION_ID, "Test Write Wi-Fi Band A",
 	              NM_SETTING_CONNECTION_UUID, nm_utils_uuid_generate_a (),
 	              NM_SETTING_CONNECTION_TYPE, NM_SETTING_WIRELESS_SETTING_NAME,
 	              NULL);
@@ -7402,8 +7402,6 @@ test_write_mobile_broadband (gconstpointer data)
 		/* GSM setting */
 		s_gsm = (NMSettingGsm *) nm_setting_gsm_new ();
 		nm_connection_add_setting (connection, NM_SETTING (s_gsm));
-
-		g_object_set (s_gsm, NM_SETTING_GSM_NUMBER, "*99#", NULL);
 	} else {
 		/* CDMA setting */
 		s_cdma = (NMSettingCdma *) nm_setting_cdma_new ();
diff --git a/src/settings/plugins/ifupdown/meson.build b/src/settings/plugins/ifupdown/meson.build
index 826c7458..42edd438 100644
--- a/src/settings/plugins/ifupdown/meson.build
+++ b/src/settings/plugins/ifupdown/meson.build
@@ -1,11 +1,11 @@
 sources = files(
   'nms-ifupdown-interface-parser.c',
-  'nms-ifupdown-parser.c'
+  'nms-ifupdown-parser.c',
 )
 
 deps = [
   libudev_dep,
-  nm_dep
+  nm_dep,
 ]
 
 libnms_ifupdown_core = static_library(
@@ -16,7 +16,7 @@ libnms_ifupdown_core = static_library(
 
 sources = files(
   'nms-ifupdown-connection.c',
-  'nms-ifupdown-plugin.c'
+  'nms-ifupdown-plugin.c',
 )
 
 libnm_settings_plugin_ifupdown = shared_module(
@@ -27,7 +27,7 @@ libnm_settings_plugin_ifupdown = shared_module(
   link_args: ldflags_linker_script_settings,
   link_depends: linker_script_settings,
   install: true,
-  install_dir: nm_plugindir
+  install_dir: nm_plugindir,
 )
 
 core_plugins += libnm_settings_plugin_ifupdown
@@ -37,7 +37,7 @@ core_plugins += libnm_settings_plugin_ifupdown
 run_target(
   'check-local-symbols-settings-ifupdown',
   command: [check_so_symbols, libnm_settings_plugin_ifupdown.full_path()],
-  depends: libnm_settings_plugin_ifupdown
+  depends: libnm_settings_plugin_ifupdown,
 )
 
 check-local-symbols-settings-ifupdown: src/settings/plugins/ifupdown/libnm-settings-plugin-ifupdown.la
diff --git a/src/settings/plugins/ifupdown/tests/meson.build b/src/settings/plugins/ifupdown/tests/meson.build
index 5a2383d9..9b844c75 100644
--- a/src/settings/plugins/ifupdown/tests/meson.build
+++ b/src/settings/plugins/ifupdown/tests/meson.build
@@ -4,11 +4,11 @@ exe = executable(
   test_unit,
   test_unit + '.c',
   dependencies: test_nm_dep,
-  link_with: libnms_ifupdown_core
+  link_with: libnms_ifupdown_core,
 )
 
 test(
   'ifupdown/' + test_unit,
   test_script,
-  args: test_args + [exe.full_path()]
+  args: test_args + [exe.full_path()],
 )
diff --git a/src/settings/plugins/keyfile/nms-keyfile-plugin.c b/src/settings/plugins/keyfile/nms-keyfile-plugin.c
index 346b78c0..ae9bea13 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-plugin.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-plugin.c
@@ -36,6 +36,7 @@
 #include "nm-utils.h"
 #include "nm-config.h"
 #include "nm-core-internal.h"
+#include "nm-keyfile-internal.h"
 
 #include "settings/nm-settings-plugin.h"
 
@@ -171,7 +172,6 @@ update_connection (NMSKeyfilePlugin *self,
 	NMSKeyfileConnection *connection_by_uuid;
 	GError *local = NULL;
 	const char *uuid;
-	int dir_len;
 
 	g_return_val_if_fail (!source || NM_IS_CONNECTION (source), NULL);
 	g_return_val_if_fail (full_path || source, NULL);
@@ -179,17 +179,8 @@ update_connection (NMSKeyfilePlugin *self,
 	if (full_path)
 		_LOGD ("loading from file \"%s\"...", full_path);
 
-	if (g_str_has_prefix (full_path, nms_keyfile_utils_get_path ())) {
-		dir_len = strlen (nms_keyfile_utils_get_path ());
-	} else if (g_str_has_prefix (full_path, NM_CONFIG_KEYFILE_PATH_IN_MEMORY)) {
-		dir_len = NM_STRLEN (NM_CONFIG_KEYFILE_PATH_IN_MEMORY);
-	} else {
-		/* Just make sure the file name is not going go pass the following check. */
-		dir_len = strlen (full_path);
-	}
-
-	if (   full_path[dir_len] != '/'
-	    || strchr (full_path + dir_len + 1, '/') != NULL) {
+	if (   !nm_utils_file_is_in_path (full_path, nms_keyfile_utils_get_path ())
+	    && !nm_utils_file_is_in_path (full_path, NM_KEYFILE_PATH_NAME_RUN)) {
 		g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
 		                     "File not in recognized system-connections directory");
 		return FALSE;
@@ -322,7 +313,7 @@ dir_changed (GFileMonitor *monitor,
 	gboolean exists;
 
 	full_path = g_file_get_path (file);
-	if (nms_keyfile_utils_should_ignore_file (full_path, FALSE)) {
+	if (nm_keyfile_utils_ignore_filename (full_path, FALSE)) {
 		g_free (full_path);
 		return;
 	}
@@ -444,7 +435,7 @@ _read_dir (GPtrArray *filenames,
 	}
 
 	while ((item = g_dir_read_name (dir))) {
-		if (nms_keyfile_utils_should_ignore_file (item, require_extension))
+		if (nm_keyfile_utils_ignore_filename (item, require_extension))
 			continue;
 		g_ptr_array_add (filenames, g_build_filename (path, item, NULL));
 	}
@@ -467,7 +458,7 @@ read_connections (NMSettingsPlugin *config)
 
 	filenames = g_ptr_array_new_with_free_func (g_free);
 
-	_read_dir (filenames, NM_CONFIG_KEYFILE_PATH_IN_MEMORY, TRUE);
+	_read_dir (filenames, NM_KEYFILE_PATH_NAME_RUN, TRUE);
 	_read_dir (filenames, nms_keyfile_utils_get_path (), FALSE);
 
 	alive_connections = g_hash_table_new (nm_direct_hash, NULL);
@@ -476,7 +467,7 @@ read_connections (NMSettingsPlugin *config)
 	 * iterating over the files.
 	 *
 	 * To have sensible, reproducible behavior, sort the paths by last modification
-	 * time prefering older files.
+	 * time preferring older files.
 	 */
 	paths = _paths_from_connections (priv->connections);
 	g_ptr_array_sort_with_data (filenames, (GCompareDataFunc) _sort_paths, paths);
@@ -523,35 +514,6 @@ get_connections (NMSettingsPlugin *config)
 }
 
 static gboolean
-_file_is_in_path (const char *abs_filename,
-                  const char *abs_path)
-{
-	gsize l;
-
-	/* FIXME: ensure that both paths are at least normalized (coalescing ".",
-	 * duplicate '/', and trailing '/'). */
-
-	nm_assert (abs_filename && abs_filename[0] == '/');
-	nm_assert (abs_path && abs_path[0] == '/');
-
-	l = strlen (abs_path);
-	if (strncmp (abs_filename, abs_path, l) != 0)
-		return FALSE;
-
-	abs_filename += l;
-	while (abs_filename[0] == '/')
-		abs_filename++;
-
-	if (!abs_filename[0])
-		return FALSE;
-
-	if (strchr (abs_filename, '/'))
-		return FALSE;
-
-	return TRUE;
-}
-
-static gboolean
 load_connection (NMSettingsPlugin *config,
                  const char *filename)
 {
@@ -559,25 +521,14 @@ load_connection (NMSettingsPlugin *config,
 	NMSKeyfileConnection *connection;
 	gboolean require_extension;
 
-	/* the test whether to require a file extension tries to figure out whether
-	 * the provided filename is inside /etc or /run.
-	 *
-	 * However, on Posix a filename just resolves to an Inode, and there can
-	 * be any kind of paths that point to the same Inode. It's not generally possible
-	 * to check for that (unless, we would stat all files in the target directory
-	 * and see whether their inode matches).
-	 *
-	 * So, when loading the file do something simpler: require that the path
-	 * starts with the well-known prefix. This rejects symlinks or hard links
-	 * which would actually also point to the same file. */
-	if (_file_is_in_path (filename, nms_keyfile_utils_get_path ()))
+	if (nm_utils_file_is_in_path (filename, nms_keyfile_utils_get_path ()))
 		require_extension = FALSE;
-	else if (_file_is_in_path (filename, NM_CONFIG_KEYFILE_PATH_IN_MEMORY))
+	else if (nm_utils_file_is_in_path (filename, NM_KEYFILE_PATH_NAME_RUN))
 		require_extension = TRUE;
 	else
 		return FALSE;
 
-	if (nms_keyfile_utils_should_ignore_file (filename, require_extension))
+	if (nm_keyfile_utils_ignore_filename (filename, require_extension))
 		return FALSE;
 
 	connection = update_connection (self, NULL, filename, find_by_path (self, filename), TRUE, NULL, NULL);
diff --git a/src/settings/plugins/keyfile/nms-keyfile-reader.c b/src/settings/plugins/keyfile/nms-keyfile-reader.c
index 580a857a..314b1033 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-reader.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-reader.c
@@ -142,11 +142,11 @@ nms_keyfile_reader_from_keyfile (GKeyFile *key_file,
 	if (!connection)
 		return NULL;
 
-	if (g_str_has_suffix (filename, NMS_KEYFILE_PATH_SUFFIX_NMCONNECTION)) {
+	if (g_str_has_suffix (filename, NM_KEYFILE_PATH_SUFFIX_NMCONNECTION)) {
 		gsize l = strlen (filename);
 
-		if (l > NM_STRLEN (NMS_KEYFILE_PATH_SUFFIX_NMCONNECTION))
-			filename_id = g_strndup (filename, l - NM_STRLEN (NMS_KEYFILE_PATH_SUFFIX_NMCONNECTION));
+		if (l > NM_STRLEN (NM_KEYFILE_PATH_SUFFIX_NMCONNECTION))
+			filename_id = g_strndup (filename, l - NM_STRLEN (NM_KEYFILE_PATH_SUFFIX_NMCONNECTION));
 	}
 
 	nm_keyfile_read_ensure_id (connection, filename_id ?: filename);
@@ -172,7 +172,8 @@ nms_keyfile_reader_from_file (const char *full_filename,
 	nm_assert (full_filename && full_filename[0] == '/');
 	nm_assert (!profile_dir || profile_dir[0] == '/');
 
-	if (!nms_keyfile_utils_check_file_permissions (full_filename,
+	if (!nms_keyfile_utils_check_file_permissions (NMS_KEYFILE_FILETYPE_KEYFILE,
+	                                               full_filename,
 	                                               NULL,
 	                                               error))
 		return NULL;
diff --git a/src/settings/plugins/keyfile/nms-keyfile-utils.c b/src/settings/plugins/keyfile/nms-keyfile-utils.c
index c3bfcdee..8d4ec943 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-utils.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-utils.c
@@ -26,108 +26,221 @@
 #include <string.h>
 #include <sys/stat.h>
 
+#include "nm-keyfile-internal.h"
+#include "nm-utils.h"
 #include "nm-setting-wired.h"
 #include "nm-setting-wireless.h"
 #include "nm-setting-wireless-security.h"
 #include "nm-config.h"
 
-#define NM_CONFIG_KEYFILE_PATH_DEFAULT NMCONFDIR "/system-connections"
-
 /*****************************************************************************/
 
-static const char temp_letters[] =
-"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
-
-/*
- * Check '.[a-zA-Z0-9]{6}' file suffix used for temporary files by g_file_set_contents() (mkstemp()).
- */
-static gboolean
-check_mkstemp_suffix (const char *path)
+char *
+nms_keyfile_loaded_uuid_filename (const char *dirname,
+                                  const char *uuid,
+                                  gboolean temporary)
 {
-	const char *ptr;
-
-	g_return_val_if_fail (path != NULL, FALSE);
+	char filename[250];
+
+	nm_assert (dirname && dirname[0] == '/');
+	nm_assert (uuid && nm_utils_is_uuid (uuid) && !strchr (uuid, '/'));
+
+	if (g_snprintf (filename,
+	                sizeof (filename),
+	                "%s%s%s%s",
+	                NM_KEYFILE_PATH_PREFIX_NMLOADED,
+	                uuid,
+	                NM_KEYFILE_PATH_SUFFIX_NMCONNECTION,
+	                temporary ? "~" : "") >= sizeof (filename)) {
+		/* valid uuids are limited in length. The buffer should always be large
+		 * enough. */
+		nm_assert_not_reached ();
+		return NULL;
+	}
 
-	/* Matches *.[a-zA-Z0-9]{6} suffix of mkstemp()'s temporary files */
-	ptr = strrchr (path, '.');
-	if (ptr && (strspn (ptr + 1, temp_letters) == 6) && (! ptr[7]))
-		return TRUE;
-	return FALSE;
+	return g_build_filename (dirname, filename, NULL);
 }
 
-static gboolean
-check_prefix_dot (const char *base)
+gboolean
+nms_keyfile_loaded_uuid_read (const char *dirname,
+                              const char *filename,
+                              char **out_full_filename,
+                              char **out_uuid,
+                              char **out_loaded_path)
 {
-	nm_assert (base && base[0]);
+	const char *uuid;
+	const char *tmp;
+	gsize len;
+	gs_free char *full_filename = NULL;
+	gs_free char *ln = NULL;
+
+	nm_assert (dirname && dirname[0] == '/');
+	nm_assert (filename && filename[0] && !strchr (filename, '/'));
+
+	if (filename[0] != '.') {
+		/* the hidden-uuid filename must start with '.'. That is,
+		 * so that it does not conflict with regular keyfiles according
+		 * to nm_keyfile_utils_ignore_filename(). */
+		return FALSE;
+	}
+
+	len = strlen (filename);
+	if (   len <= NM_STRLEN (NM_KEYFILE_PATH_PREFIX_NMLOADED)
+	    || memcmp (filename, NM_KEYFILE_PATH_PREFIX_NMLOADED, NM_STRLEN (NM_KEYFILE_PATH_PREFIX_NMLOADED)) != 0) {
+		/* the filename does not have the right prefix. */
+		return FALSE;
+	}
+
+	tmp = &filename[NM_STRLEN (NM_KEYFILE_PATH_PREFIX_NMLOADED)];
+	len -= NM_STRLEN (NM_KEYFILE_PATH_PREFIX_NMLOADED);
+
+	if (   len <= NM_STRLEN (NM_KEYFILE_PATH_SUFFIX_NMCONNECTION)
+	    || memcmp (&tmp[len - NM_STRLEN (NM_KEYFILE_PATH_SUFFIX_NMCONNECTION)],
+	               NM_KEYFILE_PATH_SUFFIX_NMCONNECTION,
+	               NM_STRLEN (NM_KEYFILE_PATH_SUFFIX_NMCONNECTION)) != 0) {
+		/* the file does not have the right suffix. */
+		return FALSE;
+	}
+	len -= NM_STRLEN (NM_KEYFILE_PATH_SUFFIX_NMCONNECTION);
+
+	if (!NM_IN_SET (len, 36, 40)) {
+		/* the remaining part of the filename has not the right length to
+		 * contain a UUID (according to nm_utils_is_uuid()). */
+		return FALSE;
+	}
+
+	uuid = nm_strndup_a (100, tmp, len, NULL);
+	if (!nm_utils_is_uuid (uuid))
+		return FALSE;
 
-	return base[0] == '.';
+	full_filename = g_build_filename (dirname, filename, NULL);
+
+	if (!nms_keyfile_utils_check_file_permissions (NMS_KEYFILE_FILETYPE_NMLOADED,
+	                                               full_filename,
+	                                               NULL,
+	                                               NULL))
+		return FALSE;
+
+	ln = nm_utils_read_link_absolute (full_filename, NULL);
+	if (!ln)
+		return FALSE;
+
+	NM_SET_OUT (out_uuid, g_strdup (uuid));
+	NM_SET_OUT (out_full_filename, g_steal_pointer (&full_filename));
+	NM_SET_OUT (out_loaded_path, g_steal_pointer (&ln));
+	return TRUE;
 }
 
-static gboolean
-check_suffix (const char *base, const char *tag)
+gboolean
+nms_keyfile_loaded_uuid_read_from_file (const char *full_filename,
+                                        char **out_dirname,
+                                        char **out_filename,
+                                        char **out_uuid,
+                                        char **out_loaded_path)
 {
-	int len, tag_len;
+	gs_free char *dirname = NULL;
+	gs_free char *filename = NULL;
 
-	g_return_val_if_fail (base != NULL, TRUE);
-	g_return_val_if_fail (tag != NULL, TRUE);
+	nm_assert (full_filename && full_filename[0] == '/');
 
-	len = strlen (base);
-	tag_len = strlen (tag);
-	if ((len > tag_len) && !g_ascii_strcasecmp (base + len - tag_len, tag))
-		return TRUE;
-	return FALSE;
-}
+	filename = g_path_get_basename (full_filename);
+	dirname = g_path_get_dirname (full_filename);
 
-#define SWP_TAG ".swp"
-#define SWPX_TAG ".swpx"
-#define PEM_TAG ".pem"
-#define DER_TAG ".der"
+	if (!nms_keyfile_loaded_uuid_read (dirname,
+	                                   filename,
+	                                   NULL,
+	                                   out_uuid,
+	                                   out_loaded_path))
+		return FALSE;
+
+	NM_SET_OUT (out_dirname, g_steal_pointer (&dirname));
+	NM_SET_OUT (out_filename, g_steal_pointer (&filename));
+	return TRUE;
+}
 
 gboolean
-nms_keyfile_utils_should_ignore_file (const char *filename, gboolean require_extension)
+nms_keyfile_loaded_uuid_write (const char *dirname,
+                               const char *uuid,
+                               const char *loaded_path,
+                               gboolean allow_relative,
+                               char **out_full_filename)
 {
-	gs_free char *base = NULL;
-
-	g_return_val_if_fail (filename != NULL, TRUE);
-
-	base = g_path_get_basename (filename);
-	g_return_val_if_fail (base != NULL, TRUE);
-
-	/* Ignore hidden and backup files */
-	/* should_ignore_file() must mirror escape_filename() */
-	if (check_prefix_dot (base) || check_suffix (base, "~"))
-		return TRUE;
-	/* Ignore temporary files */
-	if (check_mkstemp_suffix (base))
-		return TRUE;
-	/* Ignore 802.1x certificates and keys */
-	if (check_suffix (base, PEM_TAG) || check_suffix (base, DER_TAG))
-		return TRUE;
-
-	if (require_extension) {
-		gsize l = strlen (base);
-
-		if (   l <= NM_STRLEN (NMS_KEYFILE_PATH_SUFFIX_NMCONNECTION)
-		    || !g_str_has_suffix (base, NMS_KEYFILE_PATH_SUFFIX_NMCONNECTION))
-			return TRUE;
+	gs_free char *full_filename_tmp = NULL;
+	gs_free char *full_filename = NULL;
+
+	nm_assert (dirname && dirname[0] == '/');
+	nm_assert (uuid && nm_utils_is_uuid (uuid) && !strchr (uuid, '/'));
+	nm_assert (!loaded_path || loaded_path[0] == '/');
+
+	full_filename_tmp = nms_keyfile_loaded_uuid_filename (dirname, uuid, TRUE);
+
+	nm_assert (g_str_has_suffix (full_filename_tmp, "~"));
+	nm_assert (nm_utils_file_is_in_path (full_filename_tmp, dirname));
+
+	(void) unlink (full_filename_tmp);
+
+	if (!loaded_path) {
+		gboolean success = TRUE;
+
+		full_filename_tmp[strlen (full_filename_tmp) - 1] = '\0';
+		if (unlink (full_filename_tmp) != 0)
+			success = NM_IN_SET (errno, ENOENT);
+		NM_SET_OUT (out_full_filename, g_steal_pointer (&full_filename_tmp));
+		return success;
+	}
+
+	if (allow_relative) {
+		const char *f;
+
+		f = nm_utils_file_is_in_path (loaded_path, dirname);
+		if (f) {
+			/* @loaded_path points to a file directly in @dirname.
+			 * Don't use absolute paths. */
+			loaded_path = f;
+		}
+	}
+
+	if (symlink (loaded_path, full_filename_tmp) != 0) {
+		full_filename_tmp[strlen (full_filename_tmp) - 1] = '\0';
+		NM_SET_OUT (out_full_filename, g_steal_pointer (&full_filename_tmp));
+		return FALSE;
 	}
 
-	return FALSE;
+	full_filename = g_strdup (full_filename_tmp);
+	full_filename[strlen (full_filename) - 1] = '\0';
+	if (rename (full_filename_tmp, full_filename) != 0) {
+		(void) unlink (full_filename_tmp);
+		NM_SET_OUT (out_full_filename, g_steal_pointer (&full_filename));
+		return FALSE;
+	}
+
+	NM_SET_OUT (out_full_filename, g_steal_pointer (&full_filename));
+	return TRUE;
 }
 
 /*****************************************************************************/
 
 gboolean
-nms_keyfile_utils_check_file_permissions_stat (const struct stat *st,
+nms_keyfile_utils_check_file_permissions_stat (NMSKeyfileFiletype filetype,
+                                               const struct stat *st,
                                                GError **error)
 {
 	g_return_val_if_fail (st, FALSE);
 
-	if (!S_ISREG (st->st_mode)) {
-		g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-		                     "file is not a regular file");
-		return FALSE;
-	}
+	if (filetype == NMS_KEYFILE_FILETYPE_KEYFILE) {
+		if (!S_ISREG (st->st_mode)) {
+			g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
+			                     "file is not a regular file");
+			return FALSE;
+		}
+	} else if (filetype == NMS_KEYFILE_FILETYPE_NMLOADED) {
+		if (!S_ISLNK (st->st_mode)) {
+			g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
+			                     "file is not a slink");
+			return FALSE;
+		}
+	} else
+		g_return_val_if_reached (FALSE);
 
 	if (!NM_FLAGS_HAS (nm_utils_get_testing (), NM_UTILS_TEST_NO_KEYFILE_OWNER_CHECK)) {
 		if (st->st_uid != 0) {
@@ -137,7 +250,8 @@ nms_keyfile_utils_check_file_permissions_stat (const struct stat *st,
 			return FALSE;
 		}
 
-		if (st->st_mode & 0077) {
+		if (   filetype == NMS_KEYFILE_FILETYPE_KEYFILE
+		    && (st->st_mode & 0077)) {
 			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 			             "File permissions (%03o) are insecure",
 			             st->st_mode);
@@ -149,7 +263,8 @@ nms_keyfile_utils_check_file_permissions_stat (const struct stat *st,
 }
 
 gboolean
-nms_keyfile_utils_check_file_permissions (const char *filename,
+nms_keyfile_utils_check_file_permissions (NMSKeyfileFiletype filetype,
+                                          const char *filename,
                                           struct stat *out_st,
                                           GError **error)
 {
@@ -158,14 +273,24 @@ nms_keyfile_utils_check_file_permissions (const char *filename,
 
 	g_return_val_if_fail (filename && filename[0] == '/', FALSE);
 
-	if (stat (filename, &st) != 0) {
-		errsv = errno;
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-		             "cannot access file: %s", g_strerror (errsv));
-		return FALSE;
-	}
+	if (filetype == NMS_KEYFILE_FILETYPE_KEYFILE) {
+		if (stat (filename, &st) != 0) {
+			errsv = errno;
+			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
+			             "cannot access file: %s", g_strerror (errsv));
+			return FALSE;
+		}
+	} else if (filetype == NMS_KEYFILE_FILETYPE_NMLOADED) {
+		if (lstat (filename, &st) != 0) {
+			errsv = errno;
+			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
+			             "cannot access file: %s", g_strerror (errsv));
+			return FALSE;
+		}
+	} else
+		g_return_val_if_reached (FALSE);
 
-	if (!nms_keyfile_utils_check_file_permissions_stat (&st, error))
+	if (!nms_keyfile_utils_check_file_permissions_stat (filetype, &st, error))
 		return FALSE;
 
 	NM_SET_OUT (out_st, st);
@@ -174,50 +299,6 @@ nms_keyfile_utils_check_file_permissions (const char *filename,
 
 /*****************************************************************************/
 
-char *
-nms_keyfile_utils_escape_filename (const char *filename,
-                                   gboolean with_extension)
-{
-	GString *str;
-	const char *f = filename;
-	/* keyfile used to escape with '*', do not change that behavior.
-	 *
-	 * But for newly added escapings, use '_' instead.
-	 * Also, @with_extension is new-style. */
-	const char ESCAPE_CHAR = with_extension ? '_' : '*';
-	const char ESCAPE_CHAR2 = '_';
-
-	g_return_val_if_fail (filename && filename[0], NULL);
-
-	str = g_string_sized_new (60);
-
-	/* Convert '/' to ESCAPE_CHAR */
-	for (f = filename; f[0]; f++) {
-		if (f[0] == '/')
-			g_string_append_c (str, ESCAPE_CHAR);
-		else
-			g_string_append_c (str, f[0]);
-	}
-
-	/* escape_filename() must avoid anything that should_ignore_file() would reject.
-	 * We can escape here more aggressivly then what we would read back. */
-	if (check_prefix_dot (str->str))
-		str->str[0] = ESCAPE_CHAR2;
-	if (check_suffix (str->str, "~"))
-		str->str[str->len - 1] = ESCAPE_CHAR2;
-	if (   check_mkstemp_suffix (str->str)
-	    || check_suffix (str->str, PEM_TAG)
-	    || check_suffix (str->str, DER_TAG))
-		g_string_append_c (str, ESCAPE_CHAR2);
-
-	if (with_extension)
-		g_string_append (str, NMS_KEYFILE_PATH_SUFFIX_NMCONNECTION);
-
-	return g_string_free (str, FALSE);;
-}
-
-/*****************************************************************************/
-
 const char *
 nms_keyfile_utils_get_path (void)
 {
@@ -229,7 +310,7 @@ nms_keyfile_utils_get_path (void)
 		                                 NM_CONFIG_KEYFILE_KEY_KEYFILE_PATH,
 		                                 NM_CONFIG_GET_VALUE_STRIP | NM_CONFIG_GET_VALUE_NO_EMPTY);
 		if (!path)
-			path = g_strdup (""NM_CONFIG_KEYFILE_PATH_DEFAULT"");
+			path = g_strdup (""NM_KEYFILE_PATH_NAME_ETC_DEFAULT"");
 	}
 	return path;
 }
diff --git a/src/settings/plugins/keyfile/nms-keyfile-utils.h b/src/settings/plugins/keyfile/nms-keyfile-utils.h
index 297dd4ea..bc601dad 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-utils.h
+++ b/src/settings/plugins/keyfile/nms-keyfile-utils.h
@@ -23,27 +23,52 @@
 
 #include "NetworkManagerUtils.h"
 
-#define NM_CONFIG_KEYFILE_PATH_IN_MEMORY NMRUNDIR "/system-connections"
-
-#define NMS_KEYFILE_PATH_SUFFIX_NMCONNECTION      ".nmconnection"
-
 #define NMS_KEYFILE_CONNECTION_LOG_PATH(path)  ((path) ?: "in-memory")
 #define NMS_KEYFILE_CONNECTION_LOG_FMT         "%s (%s,\"%s\")"
 #define NMS_KEYFILE_CONNECTION_LOG_ARG(con)    NMS_KEYFILE_CONNECTION_LOG_PATH (nm_settings_connection_get_filename ((NMSettingsConnection *) (con))), nm_settings_connection_get_uuid ((NMSettingsConnection *) (con)), nm_settings_connection_get_id ((NMSettingsConnection *) (con))
 #define NMS_KEYFILE_CONNECTION_LOG_FMTD        "%s (%s,\"%s\",%p)"
 #define NMS_KEYFILE_CONNECTION_LOG_ARGD(con)   NMS_KEYFILE_CONNECTION_LOG_PATH (nm_settings_connection_get_filename ((NMSettingsConnection *) (con))), nm_settings_connection_get_uuid ((NMSettingsConnection *) (con)), nm_settings_connection_get_id ((NMSettingsConnection *) (con)), (con)
 
-gboolean nms_keyfile_utils_should_ignore_file (const char *filename, gboolean require_extension);
-
-char *nms_keyfile_utils_escape_filename (const char *filename, gboolean with_extension);
+typedef enum {
+	NMS_KEYFILE_FILETYPE_KEYFILE,
+	NMS_KEYFILE_FILETYPE_NMLOADED,
+} NMSKeyfileFiletype;
 
 const char *nms_keyfile_utils_get_path (void);
 
+/*****************************************************************************/
+
+char *nms_keyfile_loaded_uuid_filename (const char *dirname,
+                                        const char *uuid,
+                                        gboolean temporary);
+
+gboolean nms_keyfile_loaded_uuid_read (const char *dirname,
+                                       const char *filename,
+                                       char **out_full_filename,
+                                       char **out_uuid,
+                                       char **out_loaded_path);
+
+gboolean nms_keyfile_loaded_uuid_read_from_file (const char *full_filename,
+                                                 char **out_dirname,
+                                                 char **out_filename,
+                                                 char **out_uuid,
+                                                 char **out_loaded_path);
+
+gboolean nms_keyfile_loaded_uuid_write (const char *dirname,
+                                        const char *uuid,
+                                        const char *loaded_path,
+                                        gboolean allow_relative,
+                                        char **out_full_filename);
+
+/*****************************************************************************/
+
 struct stat;
-gboolean nms_keyfile_utils_check_file_permissions_stat (const struct stat *st,
+gboolean nms_keyfile_utils_check_file_permissions_stat (NMSKeyfileFiletype filetype,
+                                                        const struct stat *st,
                                                         GError **error);
 
-gboolean nms_keyfile_utils_check_file_permissions (const char *filename,
+gboolean nms_keyfile_utils_check_file_permissions (NMSKeyfileFiletype filetype,
+                                                   const char *filename,
                                                    struct stat *out_st,
                                                    GError **error);
 
diff --git a/src/settings/plugins/keyfile/nms-keyfile-writer.c b/src/settings/plugins/keyfile/nms-keyfile-writer.c
index df26ea60..23a6a77c 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-writer.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-writer.c
@@ -230,7 +230,7 @@ _internal_write_connection (NMConnection *connection,
 	if (existing_path != NULL && !rename) {
 		path = g_strdup (existing_path);
 	} else {
-		char *filename_escaped = nms_keyfile_utils_escape_filename (id, with_extension);
+		char *filename_escaped = nm_keyfile_utils_create_filename (id, with_extension);
 
 		path = g_build_filename (keyfile_dir, filename_escaped, NULL);
 		g_free (filename_escaped);
@@ -256,7 +256,7 @@ _internal_write_connection (NMConnection *connection,
 			else
 				filename = g_strdup_printf ("%s-%s-%u", id, nm_connection_get_uuid (connection), i);
 
-			filename_escaped = nms_keyfile_utils_escape_filename (filename, with_extension);
+			filename_escaped = nm_keyfile_utils_create_filename (filename, with_extension);
 
 			g_free (path);
 			path = g_strdup_printf ("%s/%s", keyfile_dir, filename_escaped);
@@ -356,7 +356,7 @@ nms_keyfile_writer_connection (NMConnection *connection,
 	if (save_to_disk)
 		keyfile_dir = nms_keyfile_utils_get_path ();
 	else
-		keyfile_dir = NM_CONFIG_KEYFILE_PATH_IN_MEMORY;
+		keyfile_dir = NM_KEYFILE_PATH_NAME_RUN;
 
 	return _internal_write_connection (connection,
 	                                   keyfile_dir,
diff --git a/src/settings/plugins/keyfile/tests/meson.build b/src/settings/plugins/keyfile/tests/meson.build
index 8b94b256..4253fe3c 100644
--- a/src/settings/plugins/keyfile/tests/meson.build
+++ b/src/settings/plugins/keyfile/tests/meson.build
@@ -11,5 +11,5 @@ exe = executable(
 test(
   'keyfile/' + test_unit,
   test_script,
-  args: test_args + [exe.full_path()]
+  args: test_args + [exe.full_path()],
 )
diff --git a/src/settings/plugins/keyfile/tests/test-keyfile.c b/src/settings/plugins/keyfile/tests/test-keyfile.c
index 4a0e01b3..cdc9bfb0 100644
--- a/src/settings/plugins/keyfile/tests/test-keyfile.c
+++ b/src/settings/plugins/keyfile/tests/test-keyfile.c
@@ -1177,7 +1177,6 @@ test_write_bt_dun_connection (void)
 	              NM_SETTING_GSM_APN, "internet2.voicestream.com",
 	              NM_SETTING_GSM_USERNAME, "george.clinton",
 	              NM_SETTING_GSM_PASSWORD, "parliament",
-	              NM_SETTING_GSM_NUMBER,  "*99#",
 	              NULL);
 
 	write_test_connection_and_reread (connection, TRUE);
@@ -1259,7 +1258,6 @@ test_write_gsm_connection (void)
 	              NM_SETTING_GSM_APN, "internet2.voicestream.com",
 	              NM_SETTING_GSM_USERNAME, "george.clinton.again",
 	              NM_SETTING_GSM_PASSWORD, "parliament2",
-	              NM_SETTING_GSM_NUMBER,  "*99#",
 	              NM_SETTING_GSM_PIN, "123456",
 	              NM_SETTING_GSM_NETWORK_ID, "254098",
 	              NM_SETTING_GSM_HOME_ONLY, TRUE,
@@ -2073,7 +2071,7 @@ test_write_new_wireless_group_names (void)
 	              NM_SETTING_CONNECTION_TYPE, NM_SETTING_WIRELESS_SETTING_NAME,
 	              NULL);
 
-	/* WiFi setting */
+	/* Wi-Fi setting */
 	s_wifi = (NMSettingWireless *) nm_setting_wireless_new ();
 	nm_connection_add_setting (connection, NM_SETTING (s_wifi));
 
@@ -2084,7 +2082,7 @@ test_write_new_wireless_group_names (void)
 	              NULL);
 	g_bytes_unref (ssid);
 
-	/* WiFi security setting */
+	/* Wi-Fi security setting */
 	s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new ();
 	nm_connection_add_setting (connection, NM_SETTING (s_wsec));
 	g_object_set (s_wsec,
@@ -2333,7 +2331,6 @@ test_write_flags_property (void)
 	s_gsm = nm_setting_gsm_new ();
 	nm_connection_add_setting (connection, s_gsm);
 	g_object_set (s_gsm,
-	              NM_SETTING_GSM_NUMBER, "#99*",
 	              NM_SETTING_GSM_APN, "myapn",
 	              NM_SETTING_GSM_USERNAME, "adfasdfasdf",
 	              NM_SETTING_GSM_PASSWORD_FLAGS, NM_SETTING_SECRET_FLAG_NOT_SAVED | NM_SETTING_SECRET_FLAG_NOT_REQUIRED,
@@ -2466,18 +2463,18 @@ _escape_filename (gboolean with_extension, const char *filename, gboolean would_
 
 	g_assert (filename && filename[0]);
 
-	if (!!would_be_ignored != !!nms_keyfile_utils_should_ignore_file (filename, with_extension)) {
+	if (!!would_be_ignored != !!nm_keyfile_utils_ignore_filename (filename, with_extension)) {
 		if (would_be_ignored)
 			g_error ("We expect filename \"%s\" to be ignored, but it isn't", filename);
 		else
 			g_error ("We expect filename \"%s\" not to be ignored, but it is", filename);
 	}
 
-	esc = nms_keyfile_utils_escape_filename (filename, with_extension);
+	esc = nm_keyfile_utils_create_filename (filename, with_extension);
 	g_assert (esc && esc[0]);
 	g_assert (!strchr (esc, '/'));
 
-	if (nms_keyfile_utils_should_ignore_file (esc, with_extension))
+	if (nm_keyfile_utils_ignore_filename (esc, with_extension))
 		g_error ("Escaping filename \"%s\" yielded \"%s\", but this is ignored", filename, esc);
 }
 
@@ -2503,12 +2500,117 @@ test_nm_keyfile_plugin_utils_escape_filename (void)
 	_escape_filename (FALSE, ".#emacs-locking", TRUE);
 	_escape_filename (FALSE, "file-with-tilde~", TRUE);
 	_escape_filename (FALSE, ".file-with-dot", TRUE);
+	_escape_filename (FALSE, "/some/path/with/trailing/slash/", TRUE);
+	_escape_filename (FALSE, "/some/path/without/trailing/slash", FALSE);
 
 	_escape_filename (TRUE, "lala", TRUE);
 }
 
 /*****************************************************************************/
 
+static void
+_assert_keyfile_loaded_uuid (const char *dirname,
+                             const char *uuid,
+                             const char *loaded_path,
+                             gboolean allow_relative,
+                             const char *exp_full_filename,
+                             const char *exp_uuid,
+                             const char *exp_symlink_target,
+                             const char *exp_loaded_path)
+{
+	gs_free char *full_filename = NULL;
+	gs_free char *symlink_target = NULL;
+	gs_free char *uuid2 = NULL;
+	gs_free char *loaded_path2 = NULL;
+	gs_free char *dirname3 = NULL;
+	gs_free char *filename3 = NULL;
+	gs_free char *uuid3 = NULL;
+	gs_free char *loaded_path3 = NULL;
+	gboolean success;
+	gs_free char *filename = NULL;
+
+	g_assert (dirname && dirname[0] == '/');
+	g_assert (exp_full_filename && exp_full_filename[0]);
+	g_assert (!exp_loaded_path || exp_loaded_path[0] == '/');
+
+	filename = g_path_get_basename (exp_full_filename);
+
+	full_filename = nms_keyfile_loaded_uuid_filename (dirname, uuid, FALSE);
+	g_assert_cmpstr (full_filename, ==, full_filename);
+	nm_clear_g_free (&full_filename);
+
+
+	g_assert (nms_keyfile_loaded_uuid_write (dirname, uuid, loaded_path, allow_relative, &full_filename));
+	g_assert_cmpstr (full_filename, ==, exp_full_filename);
+	nm_clear_g_free (&full_filename);
+
+	if (exp_symlink_target)
+		g_assert (g_file_test (exp_full_filename, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_SYMLINK));
+	else
+		g_assert (!g_file_test (exp_full_filename, G_FILE_TEST_EXISTS));
+	symlink_target = g_file_read_link (exp_full_filename, NULL);
+	g_assert_cmpstr (symlink_target, ==, exp_symlink_target);
+
+
+	success = nms_keyfile_loaded_uuid_read (dirname, filename, &full_filename, &uuid2, &loaded_path2);
+	g_assert_cmpint (!!exp_uuid, ==, success);
+	if (success)
+		g_assert_cmpstr (full_filename, ==, exp_full_filename);
+	else
+		g_assert_cmpstr (full_filename, ==, NULL);
+	nm_clear_g_free (&full_filename);
+	g_assert_cmpstr (uuid2, ==, exp_uuid);
+	g_assert_cmpstr (loaded_path2, ==, exp_loaded_path);
+
+
+	success = nms_keyfile_loaded_uuid_read_from_file (exp_full_filename, &dirname3, &filename3, &uuid3, &loaded_path3);
+	g_assert_cmpint (!!exp_uuid, ==, success);
+	if (success) {
+		g_assert_cmpstr (dirname3, ==, dirname);
+		g_assert_cmpstr (filename3, ==, filename);
+	} else {
+		g_assert_cmpstr (dirname3, ==, NULL);
+		g_assert_cmpstr (filename3, ==, NULL);
+	}
+	g_assert_cmpstr (uuid3, ==, exp_uuid);
+	g_assert_cmpstr (loaded_path3, ==, exp_loaded_path);
+}
+
+static void
+test_loaded_uuid (void)
+{
+	const char *uuid = "3c03fd17-ddc3-4100-a954-88b6fafff959";
+	gs_free char *filename = g_strdup_printf ("%s%s%s",
+	                                          NM_KEYFILE_PATH_PREFIX_NMLOADED,
+	                                          uuid,
+	                                          NM_KEYFILE_PATH_SUFFIX_NMCONNECTION);
+	gs_free char *full_filename = g_strdup_printf ("%s/%s",
+	                                               TEST_SCRATCH_DIR,
+	                                               filename);
+	const char *loaded_path0 = NM_KEYFILE_PATH_NMLOADED_NULL;
+	const char *loaded_path1 = "/some/where/but/not/scratch/dir";
+	const char *filename2 = "foo1";
+	gs_free char *loaded_path2 = g_strdup_printf ("%s/%s",
+	                                              TEST_SCRATCH_DIR,
+	                                              filename2);
+
+	_assert_keyfile_loaded_uuid (TEST_SCRATCH_DIR, uuid, NULL,         FALSE, full_filename, NULL, NULL,         NULL);
+	_assert_keyfile_loaded_uuid (TEST_SCRATCH_DIR, uuid, NULL,         TRUE,  full_filename, NULL, NULL,         NULL);
+
+	_assert_keyfile_loaded_uuid (TEST_SCRATCH_DIR, uuid, loaded_path0, FALSE, full_filename, uuid, loaded_path0, loaded_path0);
+	_assert_keyfile_loaded_uuid (TEST_SCRATCH_DIR, uuid, loaded_path0, TRUE,  full_filename, uuid, loaded_path0, loaded_path0);
+
+	_assert_keyfile_loaded_uuid (TEST_SCRATCH_DIR, uuid, loaded_path1, FALSE, full_filename, uuid, loaded_path1, loaded_path1);
+	_assert_keyfile_loaded_uuid (TEST_SCRATCH_DIR, uuid, loaded_path1, TRUE,  full_filename, uuid, loaded_path1, loaded_path1);
+
+	_assert_keyfile_loaded_uuid (TEST_SCRATCH_DIR, uuid, loaded_path2, FALSE, full_filename, uuid, loaded_path2, loaded_path2);
+	_assert_keyfile_loaded_uuid (TEST_SCRATCH_DIR, uuid, loaded_path2, TRUE,  full_filename, uuid, filename2,    loaded_path2);
+
+	(void) unlink (full_filename);
+}
+
+/*****************************************************************************/
+
 NMTST_DEFINE ();
 
 int main (int argc, char **argv)
@@ -2591,6 +2693,7 @@ int main (int argc, char **argv)
 
 	g_test_add_func ("/keyfile/test_nm_keyfile_plugin_utils_escape_filename", test_nm_keyfile_plugin_utils_escape_filename);
 
+	g_test_add_func ("/keyfile/test_loaded_uuid", test_loaded_uuid);
+
 	return g_test_run ();
 }
-