summary refs log tree commit diff
path: root/src/settings/plugins/keyfile
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2019-03-26 23:25:23 +0100
committerMichael Biebl <biebl@debian.org>2019-03-26 23:25:23 +0100
commit9a6dcbf895f9da01768e64b73cec88c16157d91e (patch)
treea359958930d731e9f1b59344642e10754419fe84 /src/settings/plugins/keyfile
parent964ae8cc391520440cf5aa13e2b9cc34850ea6c2 (diff)
New upstream version 1.16.0 upstream/1.16.0
Diffstat (limited to 'src/settings/plugins/keyfile')
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-connection.c1
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-plugin.c71
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-reader.c10
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-utils.c328
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-utils.h43
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-writer.c77
-rw-r--r--src/settings/plugins/keyfile/tests/meson.build3
-rw-r--r--src/settings/plugins/keyfile/tests/test-keyfile.c131
8 files changed, 416 insertions, 248 deletions
diff --git a/src/settings/plugins/keyfile/nms-keyfile-connection.c b/src/settings/plugins/keyfile/nms-keyfile-connection.c
index 7511f206..3b362978 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-connection.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-connection.c
@@ -23,7 +23,6 @@
 
 #include "nms-keyfile-connection.h"
 
-#include <string.h>
 #include <glib/gstdio.h>
 
 #include "nm-dbus-interface.h"
diff --git a/src/settings/plugins/keyfile/nms-keyfile-plugin.c b/src/settings/plugins/keyfile/nms-keyfile-plugin.c
index 346b78c0..c13cc1ff 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-plugin.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-plugin.c
@@ -26,8 +26,6 @@
 #include <sys/stat.h>
 #include <unistd.h>
 #include <sys/types.h>
-#include <string.h>
-
 #include <glib/gstdio.h>
 
 #include "nm-connection.h"
@@ -36,6 +34,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 +170,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 +177,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 +311,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 +433,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 +456,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 +465,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 +512,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 +519,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..5778f13c 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-reader.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-reader.c
@@ -23,7 +23,6 @@
 #include "nms-keyfile-reader.h"
 
 #include <sys/stat.h>
-#include <string.h>
 
 #include "nm-keyfile-internal.h"
 
@@ -142,11 +141,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 +171,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..3c4b0288 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-utils.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-utils.c
@@ -23,111 +23,223 @@
 #include "nms-keyfile-utils.h"
 
 #include <stdlib.h>
-#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 +249,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 +262,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 +272,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", nm_strerror_native (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", nm_strerror_native (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 +298,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 +309,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..8c75d8c7 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-writer.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-writer.c
@@ -26,8 +26,6 @@
 #include <stdlib.h>
 #include <sys/stat.h>
 #include <unistd.h>
-#include <errno.h>
-#include <string.h>
 
 #include "nm-keyfile-internal.h"
 
@@ -177,27 +175,30 @@ _internal_write_connection (NMConnection *connection,
                             uid_t owner_uid,
                             pid_t owner_grp,
                             const char *existing_path,
+                            gboolean existing_path_read_only,
                             gboolean force_rename,
                             char **out_path,
                             NMConnection **out_reread,
                             gboolean *out_reread_same,
                             GError **error)
 {
-	gs_unref_keyfile GKeyFile *key_file = NULL;
-	gs_free char *data = NULL;
-	gsize len;
+	gs_unref_keyfile GKeyFile *kf_file = NULL;
+	gs_free char *kf_content_buf = NULL;
+	gsize kf_content_len;
 	gs_free char *path = NULL;
 	const char *id;
 	WriteInfo info = { 0 };
 	GError *local_err = NULL;
 	int errsv;
-	gboolean rename = force_rename;
+	gboolean rename;
 
 	g_return_val_if_fail (!out_path || !*out_path, FALSE);
 	g_return_val_if_fail (keyfile_dir && keyfile_dir[0] == '/', FALSE);
 
-	if (existing_path && !g_str_has_prefix (existing_path, keyfile_dir))
-		rename = TRUE;
+	rename =    force_rename
+	         || existing_path_read_only
+	         || (   existing_path
+	             && !nm_utils_file_is_in_path (existing_path, keyfile_dir));
 
 	switch (_nm_connection_verify (connection, error)) {
 	case NM_SETTING_VERIFY_NORMALIZABLE:
@@ -214,11 +215,11 @@ _internal_write_connection (NMConnection *connection,
 
 	info.keyfile_dir = keyfile_dir;
 
-	key_file = nm_keyfile_write (connection, _handler_write, &info, error);
-	if (!key_file)
+	kf_file = nm_keyfile_write (connection, _handler_write, &info, error);
+	if (!kf_file)
 		return FALSE;
-	data = g_key_file_to_data (key_file, &len, error);
-	if (!data)
+	kf_content_buf = g_key_file_to_data (kf_file, &kf_content_len, error);
+	if (!kf_content_buf)
 		return FALSE;
 
 	if (!g_file_test (keyfile_dir, G_FILE_TEST_IS_DIR))
@@ -227,13 +228,14 @@ _internal_write_connection (NMConnection *connection,
 	/* If we have existing file path, use it. Else generate one from
 	 * connection's ID.
 	 */
-	if (existing_path != NULL && !rename) {
+	if (   existing_path
+	    && !rename)
 		path = g_strdup (existing_path);
-	} else {
-		char *filename_escaped = nms_keyfile_utils_escape_filename (id, with_extension);
+	else {
+		gs_free char *filename_escaped = NULL;
 
+		filename_escaped = nm_keyfile_utils_create_filename (id, with_extension);
 		path = g_build_filename (keyfile_dir, filename_escaped, NULL);
-		g_free (filename_escaped);
 	}
 
 	/* If a file with this path already exists (but isn't the existing path
@@ -243,32 +245,34 @@ _internal_write_connection (NMConnection *connection,
 	 * there's a race here, but there's not a lot we can do about it, and
 	 * we shouldn't get more than one connection with the same UUID either.
 	 */
-	if (g_strcmp0 (path, existing_path) != 0 && g_file_test (path, G_FILE_TEST_EXISTS)) {
+	if (   !nm_streq0 (path, existing_path)
+	    && g_file_test (path, G_FILE_TEST_EXISTS)) {
 		guint i;
 		gboolean name_found = FALSE;
 
 		/* A keyfile with this connection's ID already exists. Pick another name. */
 		for (i = 0; i < 100; i++) {
-			char *filename, *filename_escaped;
+			gs_free char *filename_escaped = NULL;
+			gs_free char *filename = NULL;
 
 			if (i == 0)
 				filename = g_strdup_printf ("%s-%s", id, nm_connection_get_uuid (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);
-			g_free (filename);
-			g_free (filename_escaped);
-			if (g_strcmp0 (path, existing_path) == 0 || !g_file_test (path, G_FILE_TEST_EXISTS)) {
+
+			if (   nm_streq0 (path, existing_path)
+			    || !g_file_test (path, G_FILE_TEST_EXISTS)) {
 				name_found = TRUE;
 				break;
 			}
 		}
 		if (!name_found) {
-			if (existing_path == NULL) {
+			if (existing_path_read_only || !existing_path) {
 				/* this really should not happen, we tried hard to find an unused name... bail out. */
 				g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
 				                    "could not find suitable keyfile file name (%s already used)", path);
@@ -281,13 +285,7 @@ _internal_write_connection (NMConnection *connection,
 		}
 	}
 
-	/* In case of updating the connection and changing the file path,
-	 * we need to remove the old one, not to end up with two connections.
-	 */
-	if (existing_path != NULL && strcmp (path, existing_path) != 0)
-		unlink (existing_path);
-
-	nm_utils_file_set_contents (path, data, len, 0600, &local_err);
+	nm_utils_file_set_contents (path, kf_content_buf, kf_content_len, 0600, &local_err);
 	if (local_err) {
 		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
 		             "error writing to file '%s': %s",
@@ -300,17 +298,24 @@ _internal_write_connection (NMConnection *connection,
 		errsv = errno;
 		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
 		             "error chowning '%s': %s (%d)",
-		             path, g_strerror (errsv), errsv);
+		             path, nm_strerror_native (errsv), errsv);
 		unlink (path);
 		return FALSE;
 	}
 
-	if (out_reread || out_reread_same)
-	{
+	/* In case of updating the connection and changing the file path,
+	 * we need to remove the old one, not to end up with two connections.
+	 */
+	if (   existing_path
+	    && !existing_path_read_only
+	    && !nm_streq (path, existing_path))
+		unlink (existing_path);
+
+	if (out_reread || out_reread_same) {
 		gs_unref_object NMConnection *reread = NULL;
 		gboolean reread_same = FALSE;
 
-		reread = nms_keyfile_reader_from_keyfile (key_file, path, NULL, profile_dir, FALSE, NULL);
+		reread = nms_keyfile_reader_from_keyfile (kf_file, path, NULL, profile_dir, FALSE, NULL);
 
 		nm_assert (NM_IS_CONNECTION (reread));
 
@@ -356,7 +361,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,
@@ -365,6 +370,7 @@ nms_keyfile_writer_connection (NMConnection *connection,
 	                                   0,
 	                                   0,
 	                                   existing_path,
+	                                   FALSE,
 	                                   force_rename,
 	                                   out_path,
 	                                   out_reread,
@@ -390,6 +396,7 @@ nms_keyfile_writer_test_connection (NMConnection *connection,
 	                                   owner_grp,
 	                                   NULL,
 	                                   FALSE,
+	                                   FALSE,
 	                                   out_path,
 	                                   out_reread,
 	                                   out_reread_same,
diff --git a/src/settings/plugins/keyfile/tests/meson.build b/src/settings/plugins/keyfile/tests/meson.build
index 8b94b256..752b6d7b 100644
--- a/src/settings/plugins/keyfile/tests/meson.build
+++ b/src/settings/plugins/keyfile/tests/meson.build
@@ -11,5 +11,6 @@ exe = executable(
 test(
   'keyfile/' + test_unit,
   test_script,
-  args: test_args + [exe.full_path()]
+  args: test_args + [exe.full_path()],
+  timeout: default_test_timeout,
 )
diff --git a/src/settings/plugins/keyfile/tests/test-keyfile.c b/src/settings/plugins/keyfile/tests/test-keyfile.c
index 4a0e01b3..baecac13 100644
--- a/src/settings/plugins/keyfile/tests/test-keyfile.c
+++ b/src/settings/plugins/keyfile/tests/test-keyfile.c
@@ -23,7 +23,6 @@
 #include <stdio.h>
 #include <stdarg.h>
 #include <unistd.h>
-#include <string.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
 #include <sys/socket.h>
@@ -1177,7 +1176,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 +1257,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 +2070,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 +2081,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 +2330,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 +2462,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,21 +2499,131 @@ 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)
 {
+	int errsv;
+
 	_nm_utils_set_testing (NM_UTILS_TEST_NO_KEYFILE_OWNER_CHECK);
+
 	nmtst_init_assert_logging (&argc, &argv, "INFO", "DEFAULT");
 
-	if (g_mkdir_with_parents (TEST_SCRATCH_DIR, 0755) != 0)
-		g_error ("failure to create test directory \"%s\": %s", TEST_SCRATCH_DIR, g_strerror (errno));
+	if (g_mkdir_with_parents (TEST_SCRATCH_DIR, 0755) != 0) {
+		errsv = errno;
+		g_error ("failure to create test directory \"%s\": %s", TEST_SCRATCH_DIR, nm_strerror_native (errsv));
+	}
 
 	/* The tests */
 	g_test_add_func ("/keyfile/test_read_valid_wired_connection", test_read_valid_wired_connection);
@@ -2591,6 +2697,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 ();
 }
-