summary refs log tree commit diff
path: root/src/settings/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'src/settings/plugins')
-rw-r--r--src/settings/plugins/ibft/meson.build41
-rw-r--r--src/settings/plugins/ibft/nms-ibft-connection.c77
-rw-r--r--src/settings/plugins/ibft/nms-ibft-connection.h41
-rw-r--r--src/settings/plugins/ibft/nms-ibft-plugin.c155
-rw-r--r--src/settings/plugins/ibft/nms-ibft-plugin.h36
-rw-r--r--src/settings/plugins/ibft/nms-ibft-reader.c535
-rw-r--r--src/settings/plugins/ibft/nms-ibft-reader.h42
-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.build16
-rw-r--r--src/settings/plugins/ibft/tests/test-ibft.c292
-rw-r--r--src/settings/plugins/ifcfg-rh/meson.build3
-rw-r--r--src/settings/plugins/ifcfg-rh/nm-inotify-helper.c213
-rw-r--r--src/settings/plugins/ifcfg-rh/nm-inotify-helper.h59
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-common.h1
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-connection.c511
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-connection.h54
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c1374
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.h25
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c428
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.h9
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.c198
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.h93
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c52
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h5
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c262
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.h7
-rw-r--r--src/settings/plugins/ifcfg-rh/shvar.c1
-rw-r--r--src/settings/plugins/ifcfg-rh/shvar.h1
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bond_Main.cexpected1
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_VLAN_reorder_hdr.cexpected1
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Vlan_test-vlan-interface.cexpected1
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-team-slave-enp31s0f1-142.cexpected1
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-ip6-disabled.cexpected11
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-p12-no-client-cert13
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/test_client.p12bin0 -> 2848 bytes
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c122
-rw-r--r--src/settings/plugins/ifupdown/meson.build1
-rw-r--r--src/settings/plugins/ifupdown/nms-ifupdown-connection.c106
-rw-r--r--src/settings/plugins/ifupdown/nms-ifupdown-connection.h45
-rw-r--r--src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c1
-rw-r--r--src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.h1
-rw-r--r--src/settings/plugins/ifupdown/nms-ifupdown-parser.c43
-rw-r--r--src/settings/plugins/ifupdown/nms-ifupdown-parser.h16
-rw-r--r--src/settings/plugins/ifupdown/nms-ifupdown-plugin.c358
-rw-r--r--src/settings/plugins/ifupdown/nms-ifupdown-plugin.h26
-rw-r--r--src/settings/plugins/ifupdown/tests/test-ifupdown.c77
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-connection.c186
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-connection.h44
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-plugin.c1502
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-plugin.h38
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-reader.c31
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-reader.h8
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-storage.c277
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-storage.h264
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-utils.c282
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-utils.h75
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-writer.c173
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-writer.h14
-rw-r--r--src/settings/plugins/keyfile/tests/meson.build6
-rw-r--r--src/settings/plugins/keyfile/tests/test-keyfile-settings.c (renamed from src/settings/plugins/keyfile/tests/test-keyfile.c)61
-rw-r--r--src/settings/plugins/meson.build4
69 files changed, 3994 insertions, 4535 deletions
diff --git a/src/settings/plugins/ibft/meson.build b/src/settings/plugins/ibft/meson.build
deleted file mode 100644
index c33b24cc..00000000
--- a/src/settings/plugins/ibft/meson.build
+++ /dev/null
@@ -1,41 +0,0 @@
-name = 'nm-settings-plugin-ibft'
-
-libnms_ibft_core = static_library(
-  'nms-ibft-core',
-  'nms-ibft-reader.c',
-  dependencies: nm_dep,
-)
-
-sources = files(
-  'nms-ibft-connection.c',
-  'nms-ibft-plugin.c',
-)
-
-libnm_settings_plugin_ibft = shared_module(
-  name,
-  sources: sources,
-  dependencies: nm_dep,
-  link_with: libnms_ibft_core,
-  link_args: ldflags_linker_script_settings,
-  link_depends: linker_script_settings,
-  install: true,
-  install_dir: nm_plugindir,
-)
-
-core_plugins += libnm_settings_plugin_ibft
-
-# FIXME: check_so_symbols replacement
-'''
-run_target(
-  'check-local-symbols-settings-ibft',
-  command: [check_so_symbols, libnm_settings_plugin_ibft.full_path()],
-  depends: libnm_settings_plugin_ibft,
-)
-
-check-local-symbols-settings-ibft: src/settings/plugins/ibft/libnm-settings-plugin-ibft.la
-  $(call check_so_symbols,$(builddir)/src/settings/plugins/ibft/.libs/libnm-settings-plugin-ibft.so)
-'''
-
-if enable_tests
-  subdir('tests')
-endif
diff --git a/src/settings/plugins/ibft/nms-ibft-connection.c b/src/settings/plugins/ibft/nms-ibft-connection.c
deleted file mode 100644
index a36d8a31..00000000
--- a/src/settings/plugins/ibft/nms-ibft-connection.c
+++ /dev/null
@@ -1,77 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/* NetworkManager system settings service
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright 2014 Red Hat, Inc.
- */
-
-#include "nm-default.h"
-
-#include "nms-ibft-connection.h"
-
-#include <net/ethernet.h>
-#include <netinet/ether.h>
-#include <glib/gstdio.h>
-
-#include "nms-ibft-reader.h"
-
-/*****************************************************************************/
-
-struct _NMSIbftConnection {
-	NMSettingsConnection parent;
-};
-
-struct _NMSIbftConnectionClass {
-	NMSettingsConnectionClass parent;
-};
-
-G_DEFINE_TYPE (NMSIbftConnection, nms_ibft_connection, NM_TYPE_SETTINGS_CONNECTION)
-
-/*****************************************************************************/
-
-static void
-nms_ibft_connection_init (NMSIbftConnection *connection)
-{
-}
-
-NMSIbftConnection *
-nms_ibft_connection_new (const GPtrArray *block, GError **error)
-{
-	NMConnection *source;
-	GObject *object;
-
-	source = nms_ibft_reader_get_connection_from_block (block, error);
-	if (!source)
-		return NULL;
-
-	object = g_object_new (NMS_TYPE_IBFT_CONNECTION, NULL);
-	/* Update settings with what was read from iscsiadm */
-	if (!nm_settings_connection_update (NM_SETTINGS_CONNECTION (object),
-	                                    source,
-	                                    NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP_SAVED,
-	                                    NM_SETTINGS_CONNECTION_COMMIT_REASON_NONE,
-	                                    NULL,
-	                                    error))
-		g_clear_object (&object);
-
-	return (NMSIbftConnection *) object;
-}
-
-static void
-nms_ibft_connection_class_init (NMSIbftConnectionClass *ibft_connection_class)
-{
-}
-
diff --git a/src/settings/plugins/ibft/nms-ibft-connection.h b/src/settings/plugins/ibft/nms-ibft-connection.h
deleted file mode 100644
index de050662..00000000
--- a/src/settings/plugins/ibft/nms-ibft-connection.h
+++ /dev/null
@@ -1,41 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/* NetworkManager system settings service
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright 2014 Red Hat, Inc.
- */
-
-#ifndef __NMS_IBFT_CONNECTION_H__
-#define __NMS_IBFT_CONNECTION_H__
-
-#include "settings/nm-settings-connection.h"
-
-#define NMS_TYPE_IBFT_CONNECTION            (nms_ibft_connection_get_type ())
-#define NMS_IBFT_CONNECTION(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NMS_TYPE_IBFT_CONNECTION, NMSIbftConnection))
-#define NMS_IBFT_CONNECTION_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NMS_TYPE_IBFT_CONNECTION, NMSIbftConnectionClass))
-#define NMS_IS_IBFT_CONNECTION(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NMS_TYPE_IBFT_CONNECTION))
-#define NMS_IS_IBFT_CONNECTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NMS_TYPE_IBFT_CONNECTION))
-#define NMS_IBFT_CONNECTION_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NMS_TYPE_IBFT_CONNECTION, NMSIbftConnectionClass))
-
-typedef struct _NMSIbftConnection NMSIbftConnection;
-typedef struct _NMSIbftConnectionClass NMSIbftConnectionClass;
-
-GType nms_ibft_connection_get_type (void);
-
-NMSIbftConnection *nms_ibft_connection_new (const GPtrArray *block,
-                                           GError **error);
-
-#endif /* __NMS_IBFT_CONNECTION_H__ */
diff --git a/src/settings/plugins/ibft/nms-ibft-plugin.c b/src/settings/plugins/ibft/nms-ibft-plugin.c
deleted file mode 100644
index 47051995..00000000
--- a/src/settings/plugins/ibft/nms-ibft-plugin.c
+++ /dev/null
@@ -1,155 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/* NetworkManager system settings service
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright 2014 Red Hat, Inc.
- */
-
-#include "nm-default.h"
-
-#include "nms-ibft-plugin.h"
-
-#include <unistd.h>
-#include <gmodule.h>
-
-#include "nm-setting-connection.h"
-#include "settings/nm-settings-plugin.h"
-#include "NetworkManagerUtils.h"
-
-#include "nms-ibft-reader.h"
-#include "nms-ibft-connection.h"
-
-/*****************************************************************************/
-
-typedef struct {
-	GHashTable *connections;  /* uuid::connection */
-	gboolean initialized;
-} NMSIbftPluginPrivate;
-
-struct _NMSIbftPlugin {
-	NMSettingsPlugin parent;
-	NMSIbftPluginPrivate _priv;
-};
-
-struct _NMSIbftPluginClass {
-	NMSettingsPluginClass parent;
-};
-
-G_DEFINE_TYPE (NMSIbftPlugin, nms_ibft_plugin, NM_TYPE_SETTINGS_PLUGIN);
-
-#define NMS_IBFT_PLUGIN_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMSIbftPlugin, NMS_IS_IBFT_PLUGIN)
-
-/*****************************************************************************/
-
-static NMSIbftPlugin *nms_ibft_plugin_get (void);
-
-NM_DEFINE_SINGLETON_GETTER (NMSIbftPlugin, nms_ibft_plugin_get, NMS_TYPE_IBFT_PLUGIN);
-
-/*****************************************************************************/
-
-static void
-read_connections (NMSIbftPlugin *self)
-{
-	NMSIbftPluginPrivate *priv = NMS_IBFT_PLUGIN_GET_PRIVATE (self);
-	nm_auto_free_ibft_blocks GSList *blocks = NULL;
-	GSList *iter;
-	gs_free_error GError *error = NULL;
-	NMSIbftConnection *connection;
-
-	if (!nms_ibft_reader_load_blocks ("/sbin/iscsiadm", &blocks, &error)) {
-		nm_log_dbg (LOGD_SETTINGS, "ibft: failed to read iscsiadm records: %s", error->message);
-		return;
-	}
-
-	for (iter = blocks; iter; iter = iter->next) {
-		connection = nms_ibft_connection_new (iter->data, &error);
-		if (!connection) {
-			nm_log_warn (LOGD_SETTINGS, "ibft: failed to read iscsiadm record: %s", error->message);
-			g_clear_error (&error);
-			continue;
-		}
-
-		nm_log_info (LOGD_SETTINGS, "ibft: read connection '%s'",
-		             nm_settings_connection_get_id (NM_SETTINGS_CONNECTION (connection)));
-		g_hash_table_insert (priv->connections,
-		                     g_strdup (nm_settings_connection_get_uuid (NM_SETTINGS_CONNECTION (connection))),
-		                     connection);
-	}
-}
-
-static GSList *
-get_connections (NMSettingsPlugin *config)
-{
-	NMSIbftPlugin *self = NMS_IBFT_PLUGIN (config);
-	NMSIbftPluginPrivate *priv = NMS_IBFT_PLUGIN_GET_PRIVATE (self);
-	GSList *list = NULL;
-	GHashTableIter iter;
-	NMSIbftConnection *connection;
-
-	if (!priv->initialized) {
-		read_connections (self);
-		priv->initialized = TRUE;
-	}
-
-	g_hash_table_iter_init (&iter, priv->connections);
-	while (g_hash_table_iter_next (&iter, NULL, (gpointer) &connection))
-		list = g_slist_prepend (list, connection);
-
-	return list;
-}
-
-/*****************************************************************************/
-
-static void
-nms_ibft_plugin_init (NMSIbftPlugin *self)
-{
-	NMSIbftPluginPrivate *priv = NMS_IBFT_PLUGIN_GET_PRIVATE (self);
-
-	priv->connections = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, g_object_unref);
-}
-
-static void
-dispose (GObject *object)
-{
-	NMSIbftPlugin *self = NMS_IBFT_PLUGIN (object);
-	NMSIbftPluginPrivate *priv = NMS_IBFT_PLUGIN_GET_PRIVATE (self);
-
-	if (priv->connections) {
-		g_hash_table_destroy (priv->connections);
-		priv->connections = NULL;
-	}
-
-	G_OBJECT_CLASS (nms_ibft_plugin_parent_class)->dispose (object);
-}
-
-static void
-nms_ibft_plugin_class_init (NMSIbftPluginClass *klass)
-{
-	GObjectClass *object_class = G_OBJECT_CLASS (klass);
-	NMSettingsPluginClass *plugin_class = NM_SETTINGS_PLUGIN_CLASS (klass);
-
-	object_class->dispose = dispose;
-
-	plugin_class->get_connections = get_connections;
-}
-
-/*****************************************************************************/
-
-G_MODULE_EXPORT NMSettingsPlugin *
-nm_settings_plugin_factory (void)
-{
-	return NM_SETTINGS_PLUGIN (g_object_ref (nms_ibft_plugin_get ()));
-}
diff --git a/src/settings/plugins/ibft/nms-ibft-plugin.h b/src/settings/plugins/ibft/nms-ibft-plugin.h
deleted file mode 100644
index 33a7620b..00000000
--- a/src/settings/plugins/ibft/nms-ibft-plugin.h
+++ /dev/null
@@ -1,36 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/* NetworkManager system settings service
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright 2014 Red Hat, Inc.
- */
-
-#ifndef __NMS_IBFT_PLUGIN_H__
-#define __NMS_IBFT_PLUGIN_H__
-
-#define NMS_TYPE_IBFT_PLUGIN            (nms_ibft_plugin_get_type ())
-#define NMS_IBFT_PLUGIN(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NMS_TYPE_IBFT_PLUGIN, NMSIbftPlugin))
-#define NMS_IBFT_PLUGIN_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NMS_TYPE_IBFT_PLUGIN, NMSIbftPluginClass))
-#define NMS_IS_IBFT_PLUGIN(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NMS_TYPE_IBFT_PLUGIN))
-#define NMS_IS_IBFT_PLUGIN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NMS_TYPE_IBFT_PLUGIN))
-#define NMS_IBFT_PLUGIN_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NMS_TYPE_IBFT_PLUGIN, NMSIbftPluginClass))
-
-typedef struct _NMSIbftPlugin NMSIbftPlugin;
-typedef struct _NMSIbftPluginClass NMSIbftPluginClass;
-
-GType nms_ibft_plugin_get_type (void);
-
-#endif /* __NMS_IBFT_PLUGIN_H__ */
diff --git a/src/settings/plugins/ibft/nms-ibft-reader.c b/src/settings/plugins/ibft/nms-ibft-reader.c
deleted file mode 100644
index fde30383..00000000
--- a/src/settings/plugins/ibft/nms-ibft-reader.c
+++ /dev/null
@@ -1,535 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/* NetworkManager system settings service
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright 2014 Red Hat, Inc.
- */
-
-#include "nm-default.h"
-
-#include "nms-ibft-reader.h"
-
-#include <stdlib.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <arpa/inet.h>
-#include <sys/wait.h>
-#include <sys/inotify.h>
-#include <sys/ioctl.h>
-#include <unistd.h>
-
-#include "nm-core-internal.h"
-#include "platform/nm-platform.h"
-#include "NetworkManagerUtils.h"
-
-/*****************************************************************************/
-
-#define PARSE_WARNING(msg...) nm_log_warn (LOGD_SETTINGS, "    " msg)
-
-/* Removes trailing whitespace and whitespace before and immediately after the '=' */
-static char *
-remove_most_whitespace (const char *src)
-{
-	char *s_new, *s2;
-	const char *svalue;
-
-	src = nm_str_skip_leading_spaces (src);
-
-	svalue = strchr (src, '=');
-	if (!svalue || svalue == src)
-		return NULL;
-
-	s_new = g_new (char, strlen (src) + 1);
-
-	memcpy (s_new, src, svalue - src);
-	s_new[svalue - src] = '\0';
-	g_strchomp (s_new);
-
-	svalue++;
-	while (*svalue && g_ascii_isspace (*svalue))
-		svalue++;
-
-	s2 = strchr (s_new, '\0');
-	s2[0] = '=';
-	strcpy (++s2, svalue);
-	g_strchomp (s2);
-
-	return s_new;
-}
-
-#define TAG_BEGIN "# BEGIN RECORD"
-#define TAG_END   "# END RECORD"
-
-/**
- * nms_ibft_reader_load_blocks:
- * @iscsiadm_path: path to iscsiadm program
- * @out_blocks: on return if successful, a #GSList of #GPtrArray, or %NULL on
- * failure
- * @error: location for an error on failure
- *
- * Parses iscsiadm output and returns a #GSList of #GPtrArray in the @out_blocks
- * argument on success, otherwise @out_blocks is set to %NULL.  Each #GPtrArray
- * in @out_blocks contains the lines from an iscsiadm interface block.
- *
- * Returns: %TRUE on success, %FALSE on errors
- */
-gboolean
-nms_ibft_reader_load_blocks (const char *iscsiadm_path,
-                             GSList **out_blocks,
-                             GError **error)
-{
-	const char *argv[4] = { iscsiadm_path, "-m", "fw", NULL };
-	const char *envp[1] = { NULL };
-	nm_auto_free_ibft_blocks GSList *blocks = NULL;
-	gs_free char *out = NULL;
-	gs_free char *err = NULL;
-	gs_free const char **lines = NULL;
-	GPtrArray *block_lines = NULL;
-	gsize i;
-	int status = 0;
-
-	g_return_val_if_fail (iscsiadm_path != NULL, FALSE);
-	g_return_val_if_fail (out_blocks != NULL && *out_blocks == NULL, FALSE);
-
-	if (!g_spawn_sync ("/", (char **) argv, (char **) envp, 0,
-	                   NULL, NULL, &out, &err, &status, error))
-		return FALSE;
-
-	if (!WIFEXITED (status)) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
-		             "iBFT: %s exited abnormally.", iscsiadm_path);
-		return FALSE;
-	}
-
-	if (WEXITSTATUS (status) != 0) {
-		if (err) {
-			char *nl;
-
-			/* the error message contains newlines. concatenate the lines with whitespace */
-			for (nl = err; *nl; nl++) {
-				if (*nl == '\n')
-					*nl = ' ';
-			}
-		}
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
-		             "iBFT: %s exited with error %d.  Message: '%s'",
-		             iscsiadm_path, WEXITSTATUS (status), err ?: "(none)");
-		return FALSE;
-	}
-
-	nm_log_dbg (LOGD_SETTINGS, "iBFT records:\n%s", out);
-
-	lines = nm_utils_strsplit_set (out, "\n\r");
-	for (i = 0; lines && lines[i]; i++) {
-		const char *ss = lines[i];
-
-		if (!g_ascii_strncasecmp (ss, TAG_BEGIN, NM_STRLEN (TAG_BEGIN))) {
-			if (block_lines) {
-				PARSE_WARNING ("malformed iscsiadm record: missing END RECORD.");
-				nm_clear_pointer (&block_lines, g_ptr_array_unref);
-			}
-			/* Start new record */
-			block_lines = g_ptr_array_new_full (15, g_free);
-		} else if (!g_ascii_strncasecmp (ss, TAG_END, NM_STRLEN (TAG_END))) {
-			if (block_lines) {
-				if (block_lines->len)
-					blocks = g_slist_prepend (blocks, g_steal_pointer (&block_lines));
-				else
-					g_ptr_array_unref (g_steal_pointer (&block_lines));
-			}
-		} else if (block_lines) {
-			char *s = remove_most_whitespace (ss);
-
-			if (!s) {
-				PARSE_WARNING ("malformed iscsiadm record: no = in '%s'.", ss);
-				nm_clear_pointer (&block_lines, g_ptr_array_unref);
-			} else
-				g_ptr_array_add (block_lines, s);
-		}
-	}
-
-	if (block_lines) {
-		PARSE_WARNING ("malformed iscsiadm record: missing # END RECORD.");
-		nm_clear_pointer (&block_lines, g_ptr_array_unref);
-	}
-
-	*out_blocks = g_steal_pointer (&blocks);
-	return TRUE;
-}
-
-#define ISCSI_HWADDR_TAG     "iface.hwaddress"
-#define ISCSI_BOOTPROTO_TAG  "iface.bootproto"
-#define ISCSI_IPADDR_TAG     "iface.ipaddress"
-#define ISCSI_SUBNET_TAG     "iface.subnet_mask"
-#define ISCSI_GATEWAY_TAG    "iface.gateway"
-#define ISCSI_DNS1_TAG       "iface.primary_dns"
-#define ISCSI_DNS2_TAG       "iface.secondary_dns"
-#define ISCSI_VLAN_ID_TAG    "iface.vlan_id"
-#define ISCSI_IFACE_TAG      "iface.net_ifacename"
-
-static const char *
-match_iscsiadm_tag (const char *line, const char *tag)
-{
-	gsize taglen = strlen (tag);
-
-	if (g_ascii_strncasecmp (line, tag, taglen) != 0)
-		return NULL;
-	if (line[taglen] != '=')
-		return NULL;
-	return line + taglen + 1;
-}
-
-/**
- * nms_ibft_reader_parse_block:
- * @block: an array of iscsiadm interface block lines
- * @error: return location for errors
- * @...: pairs of key (const char *) : location (const char **) indicating the
- * key to look for and the location to store the retrieved value in
- *
- * Parses an iscsiadm interface block into variables requested by the caller.
- * Callers should verify the returned data is complete and valid.  Returned
- * strings are owned by @block and should not be used after @block is freed.
- *
- * Returns: %TRUE if at least , %FALSE on failure
- */
-gboolean
-nms_ibft_reader_parse_block (const GPtrArray *block, GError **error, ...)
-{
-	gboolean success = FALSE;
-	const char **out_value, *p;
-	va_list ap;
-	const char *key;
-	guint i;
-
-	g_return_val_if_fail (block != NULL, FALSE);
-	g_return_val_if_fail (block->len > 0, FALSE);
-
-	/* Find requested keys and populate return values */
-	va_start (ap, error);
-	while ((key = va_arg (ap, const char *))) {
-		out_value = va_arg (ap, const char **);
-		*out_value = NULL;
-		for (i = 0; i < block->len; i++) {
-			p = match_iscsiadm_tag (g_ptr_array_index (block, i), key);
-			if (p) {
-				*out_value = p;
-				success = TRUE;
-				break;
-			}
-		}
-	}
-	va_end (ap);
-
-	if (!success) {
-		g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-		                     "iBFT: failed to match at least one iscsiadm block field");
-	}
-	return success;
-}
-
-static gboolean
-ip4_setting_add_from_block (const GPtrArray *block,
-                            NMConnection *connection,
-                            GError **error)
-{
-	NMSettingIPConfig *s_ip4 = NULL;
-	NMIPAddress *addr;
-	const char *s_method = NULL;
-	const char *s_ipaddr = NULL;
-	const char *s_gateway = NULL;
-	const char *s_dns1 = NULL;
-	const char *s_dns2 = NULL;
-	const char *s_netmask = NULL;
-	guint32 netmask = 0;
-	guint32 prefix;
-
-	g_assert (block);
-
-	if (!nms_ibft_reader_parse_block (block, error,
-	                                  ISCSI_BOOTPROTO_TAG, &s_method,
-	                                  ISCSI_IPADDR_TAG,    &s_ipaddr,
-	                                  ISCSI_SUBNET_TAG,    &s_netmask,
-	                                  ISCSI_GATEWAY_TAG,   &s_gateway,
-	                                  ISCSI_DNS1_TAG,      &s_dns1,
-	                                  ISCSI_DNS2_TAG,      &s_dns2,
-	                                  NULL))
-		goto error;
-
-	if (!s_method) {
-		g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-		                     "iBFT: malformed iscsiadm record: missing " ISCSI_BOOTPROTO_TAG);
-		goto error;
-	}
-
-	s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new ();
-
-	if (!g_ascii_strcasecmp (s_method, "dhcp")) {
-		g_object_set (s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
-		goto success;
-	} else if (g_ascii_strcasecmp (s_method, "static") != 0) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-		             "iBFT: malformed iscsiadm record: unknown " ISCSI_BOOTPROTO_TAG " '%s'.",
-		             s_method);
-		goto error;
-	}
-
-	/* Static configuration stuff */
-	g_object_set (s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_MANUAL, NULL);
-
-	/* IP address */
-	if (!s_ipaddr || !nm_utils_ipaddr_valid (AF_INET, s_ipaddr)) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-		             "iBFT: malformed iscsiadm record: invalid IP address '%s'.",
-		             s_ipaddr);
-		goto error;
-	}
-
-	/* Subnet/prefix */
-	if (!s_netmask || inet_pton (AF_INET, s_netmask, &netmask) != 1) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-		             "iBFT: malformed iscsiadm record: invalid subnet mask '%s'.",
-		             s_netmask);
-		goto error;
-	}
-	prefix = nm_utils_ip4_netmask_to_prefix (netmask);
-
-	if (s_gateway && !nm_utils_ipaddr_valid (AF_INET, s_gateway)) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-		             "iBFT: malformed iscsiadm record: invalid IP gateway '%s'.",
-		             s_gateway);
-		goto error;
-	}
-
-	if (s_dns1 && !nm_utils_ipaddr_valid (AF_INET, s_dns1)) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-		             "iBFT: malformed iscsiadm record: invalid DNS1 address '%s'.",
-		             s_dns1);
-		goto error;
-	}
-
-	if (s_dns2 && !nm_utils_ipaddr_valid (AF_INET, s_dns2)) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-		             "iBFT: malformed iscsiadm record: invalid DNS2 address '%s'.",
-		             s_dns2);
-		goto error;
-	}
-
-	addr = nm_ip_address_new (AF_INET, s_ipaddr, prefix, error);
-	if (!addr) {
-		g_prefix_error (error, "iBFT: malformed iscsiadm record: ");
-		goto error;
-	}
-
-	nm_setting_ip_config_add_address (s_ip4, addr);
-	nm_ip_address_unref (addr);
-
-	g_object_set (s_ip4, NM_SETTING_IP_CONFIG_GATEWAY, s_gateway, NULL);
-
-	if (s_dns1)
-		nm_setting_ip_config_add_dns (s_ip4, s_dns1);
-	if (s_dns2)
-		nm_setting_ip_config_add_dns (s_ip4, s_dns2);
-
-success:
-	nm_connection_add_setting (connection, NM_SETTING (s_ip4));
-	return TRUE;
-
-error:
-	g_clear_object (&s_ip4);
-	return FALSE;
-}
-
-static gboolean
-connection_setting_add (const GPtrArray *block,
-                        NMConnection *connection,
-                        const char *type,
-                        const char *prefix,
-                        const char *iface,
-                        GError **error)
-{
-	NMSetting *s_con;
-	char *id, *uuid;
-	const char *s_hwaddr = NULL, *s_ip4addr = NULL, *s_vlanid;
-
-	if (!nms_ibft_reader_parse_block (block, error,
-	                                  ISCSI_VLAN_ID_TAG, &s_vlanid,
-	                                  ISCSI_HWADDR_TAG,  &s_hwaddr,
-	                                  ISCSI_IPADDR_TAG,  &s_ip4addr,
-	                                  NULL))
-		return FALSE;
-	if (!s_hwaddr) {
-		g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-		                     "iBFT: malformed iscsiadm record: missing " ISCSI_HWADDR_TAG);
-		return FALSE;
-	}
-
-	id = g_strdup_printf ("iBFT%s%s %s",
-	                      prefix ? " " : "",
-	                      prefix ?: "",
-	                      iface);
-
-	uuid = _nm_utils_uuid_generate_from_strings ("ibft",
-	                                             s_hwaddr,
-	                                             s_vlanid ? "V" : "v",
-	                                             s_vlanid ?: "",
-	                                             s_ip4addr ? "A" : "DHCP",
-	                                             s_ip4addr ?: "",
-	                                             NULL);
-
-	s_con = nm_setting_connection_new ();
-	g_object_set (s_con,
-	              NM_SETTING_CONNECTION_TYPE, type,
-	              NM_SETTING_CONNECTION_UUID, uuid,
-	              NM_SETTING_CONNECTION_ID, id,
-	              NM_SETTING_CONNECTION_READ_ONLY, TRUE,
-	              NULL);
-
-	g_free (uuid);
-	g_free (id);
-
-	nm_connection_add_setting (connection, NM_SETTING (s_con));
-	return TRUE;
-}
-
-static gboolean
-is_ibft_vlan_device (const GPtrArray *block)
-{
-	char *s_vlan_id = NULL;
-
-	if (nms_ibft_reader_parse_block (block, NULL, ISCSI_VLAN_ID_TAG, &s_vlan_id, NULL)) {
-		g_assert (s_vlan_id);
-
-		/* VLAN 0 is normally a valid VLAN ID, but in the iBFT case it
-		 * means "no VLAN".
-		 */
-		if (_nm_utils_ascii_str_to_int64 (s_vlan_id, 10, 1, 4095, -1) != -1)
-			return TRUE;
-	}
-	return FALSE;
-}
-
-static gboolean
-vlan_setting_add_from_block (const GPtrArray *block,
-                             NMConnection *connection,
-                             GError **error)
-{
-	NMSetting *s_vlan = NULL;
-	const char *vlan_id_str = NULL;
-	gint64 vlan_id = -1;
-	gboolean success;
-
-	g_assert (block);
-	g_assert (connection);
-
-	/* This won't fail since this function shouldn't be called unless the
-	 * iBFT VLAN ID exists and is > 0.
-	 */
-	success = nms_ibft_reader_parse_block (block, NULL, ISCSI_VLAN_ID_TAG, &vlan_id_str, NULL);
-	g_assert (success);
-	g_assert (vlan_id_str);
-
-	/* VLAN 0 is normally a valid VLAN ID, but in the iBFT case it means "no VLAN" */
-	vlan_id = _nm_utils_ascii_str_to_int64 (vlan_id_str, 10, 1, 4095, -1);
-	if (vlan_id == -1) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-		             "Invalid VLAN_ID '%s'", vlan_id_str);
-		return FALSE;
-	}
-
-	s_vlan = nm_setting_vlan_new ();
-	g_object_set (s_vlan, NM_SETTING_VLAN_ID, (guint32) vlan_id, NULL);
-	nm_connection_add_setting (connection, NM_SETTING (s_vlan));
-
-	return TRUE;
-}
-
-static gboolean
-wired_setting_add_from_block (const GPtrArray *block,
-                              NMConnection *connection,
-                              GError **error)
-{
-	NMSetting *s_wired = NULL;
-	const char *hwaddr = NULL;
-
-	g_assert (block);
-	g_assert (connection);
-
-	if (!nms_ibft_reader_parse_block (block, NULL, ISCSI_HWADDR_TAG, &hwaddr, NULL)) {
-		g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-		                     "iBFT: malformed iscsiadm record: missing " ISCSI_HWADDR_TAG);
-		return FALSE;
-	}
-
-	if (!nm_utils_hwaddr_valid (hwaddr, ETH_ALEN)) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-		             "iBFT: malformed iscsiadm record: invalid " ISCSI_HWADDR_TAG " '%s'.",
-		             hwaddr);
-		return FALSE;
-	}
-
-	s_wired = nm_setting_wired_new ();
-	g_object_set (s_wired, NM_SETTING_WIRED_MAC_ADDRESS, hwaddr, NULL);
-
-	nm_connection_add_setting (connection, s_wired);
-	return TRUE;
-}
-
-NMConnection *
-nms_ibft_reader_get_connection_from_block (const GPtrArray *block, GError **error)
-{
-	NMConnection *connection = NULL;
-	gboolean is_vlan = FALSE;
-	const char *iface = NULL;
-
-	g_assert (block);
-
-	if (!nms_ibft_reader_parse_block (block, error, ISCSI_IFACE_TAG, &iface, NULL)) {
-		g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-		                     "iBFT: malformed iscsiadm record: missing " ISCSI_IFACE_TAG);
-		return NULL;
-	}
-
-	connection = nm_simple_connection_new ();
-
-	is_vlan = is_ibft_vlan_device (block);
-	if (is_vlan && !vlan_setting_add_from_block (block, connection, error))
-		goto error;
-
-	/* Always have a wired setting; for VLAN it defines the parent */
-	if (!wired_setting_add_from_block (block, connection, error))
-		goto error;
-
-	if (!ip4_setting_add_from_block (block, connection, error))
-		goto error;
-
-	if (!connection_setting_add (block,
-	                             connection,
-	                             is_vlan ? NM_SETTING_VLAN_SETTING_NAME : NM_SETTING_WIRED_SETTING_NAME,
-	                             is_vlan ? "VLAN" : NULL,
-	                             iface,
-	                             error))
-		goto error;
-
-	if (!nm_connection_normalize (connection, NULL, NULL, error))
-		goto error;
-
-	return connection;
-
-error:
-	g_object_unref (connection);
-	return NULL;
-}
-
diff --git a/src/settings/plugins/ibft/nms-ibft-reader.h b/src/settings/plugins/ibft/nms-ibft-reader.h
deleted file mode 100644
index baa81e99..00000000
--- a/src/settings/plugins/ibft/nms-ibft-reader.h
+++ /dev/null
@@ -1,42 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/* NetworkManager system settings service
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright 2014 Red Hat, Inc.
- */
-
-#ifndef __NMS_IBFT_READER_H__
-#define __NMS_IBFT_READER_H__
-
-#include "nm-connection.h"
-
-static inline void
-_nm_auto_free_ibft_blocks (GSList **p_blocks)
-{
-	if (*p_blocks)
-		g_slist_free_full (*p_blocks, (GDestroyNotify) g_ptr_array_unref);
-}
-#define nm_auto_free_ibft_blocks nm_auto (_nm_auto_free_ibft_blocks)
-
-gboolean nms_ibft_reader_load_blocks (const char *iscsiadm_path,
-                                      GSList **out_blocks,
-                                      GError **error);
-
-NMConnection *nms_ibft_reader_get_connection_from_block (const GPtrArray *block, GError **error);
-
-gboolean nms_ibft_reader_parse_block (const GPtrArray *block, GError **error, ...) G_GNUC_NULL_TERMINATED;
-
-#endif  /* __NMS_IBFT_READER_H__ */
diff --git a/src/settings/plugins/ibft/tests/iscsiadm-test-bad-dns1 b/src/settings/plugins/ibft/tests/iscsiadm-test-bad-dns1
deleted file mode 100755
index 54f02da6..00000000
--- a/src/settings/plugins/ibft/tests/iscsiadm-test-bad-dns1
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/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
deleted file mode 100755
index ebd7a9ca..00000000
--- a/src/settings/plugins/ibft/tests/iscsiadm-test-bad-dns2
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/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
deleted file mode 100755
index 4e326048..00000000
--- a/src/settings/plugins/ibft/tests/iscsiadm-test-bad-entry
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/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
deleted file mode 100755
index 5390a6c3..00000000
--- a/src/settings/plugins/ibft/tests/iscsiadm-test-bad-gateway
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/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
deleted file mode 100755
index b41cd1f1..00000000
--- a/src/settings/plugins/ibft/tests/iscsiadm-test-bad-ipaddr
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/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
deleted file mode 100755
index 22b34e6f..00000000
--- a/src/settings/plugins/ibft/tests/iscsiadm-test-bad-record
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/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
deleted file mode 100755
index 556b0586..00000000
--- a/src/settings/plugins/ibft/tests/iscsiadm-test-dhcp
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/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
deleted file mode 100755
index 51711480..00000000
--- a/src/settings/plugins/ibft/tests/iscsiadm-test-static
+++ /dev/null
@@ -1,35 +0,0 @@
-#!/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
deleted file mode 100755
index 59b80bd0..00000000
--- a/src/settings/plugins/ibft/tests/iscsiadm-test-vlan
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/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
deleted file mode 100644
index e2f9ca7e..00000000
--- a/src/settings/plugins/ibft/tests/meson.build
+++ /dev/null
@@ -1,16 +0,0 @@
-test_unit = 'test-ibft'
-
-test_ibft_dir = meson.current_source_dir()
-
-exe = executable(
-  test_unit,
-  test_unit + '.c',
-  dependencies: test_nm_dep,
-  link_with: libnms_ibft_core,
-)
-
-test(
-  'ibft/' + test_unit,
-  test_script,
-  args: test_args + [exe.full_path()],
-)
diff --git a/src/settings/plugins/ibft/tests/test-ibft.c b/src/settings/plugins/ibft/tests/test-ibft.c
deleted file mode 100644
index f5b584a1..00000000
--- a/src/settings/plugins/ibft/tests/test-ibft.c
+++ /dev/null
@@ -1,292 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/* NetworkManager system settings service
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright 2014 Red Hat, Inc.
- */
-
-#include "nm-default.h"
-
-#include <stdio.h>
-#include <stdarg.h>
-#include <unistd.h>
-#include <netinet/ether.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#include <sys/socket.h>
-
-#include "nm-core-internal.h"
-#include "NetworkManagerUtils.h"
-
-#include "settings/plugins/ibft/nms-ibft-reader.h"
-
-#include "nm-test-utils-core.h"
-
-#define TEST_DIR       NM_BUILD_SRCDIR"/src/settings/plugins/ibft/tests"
-
-static GPtrArray *
-read_block (const char *iscsiadm_path, const char *expected_mac)
-{
-	nm_auto_free_ibft_blocks GSList *blocks = NULL;
-	GSList *iter;
-	GPtrArray *block = NULL;
-	GError *error = NULL;
-	gboolean success;
-
-	success = nms_ibft_reader_load_blocks (iscsiadm_path, &blocks, &error);
-	nmtst_assert_success (success, error);
-
-	g_assert (blocks);
-	for (iter = blocks; iter; iter = iter->next) {
-		const char *s_hwaddr = NULL;
-
-		if (!nms_ibft_reader_parse_block (iter->data, NULL, "iface.hwaddress", &s_hwaddr, NULL))
-			continue;
-		g_assert (s_hwaddr);
-		if (nm_utils_hwaddr_matches (s_hwaddr, -1, expected_mac, -1)) {
-			block = g_ptr_array_ref (iter->data);
-			break;
-		}
-	}
-	g_assert (block);
-
-	return block;
-}
-
-static void
-test_read_ibft_dhcp (void)
-{
-	NMConnection *connection;
-	NMSettingConnection *s_con;
-	NMSettingWired *s_wired;
-	NMSettingIPConfig *s_ip4;
-	GError *error = NULL;
-	const char *mac_address;
-	const char *expected_mac_address = "00:33:21:98:b9:f1";
-	GPtrArray *block;
-
-	block = read_block (TEST_DIR "/iscsiadm-test-dhcp", expected_mac_address);
-
-	connection = nms_ibft_reader_get_connection_from_block (block, &error);
-	g_assert_no_error (error);
-	nmtst_assert_connection_verifies_without_normalization (connection);
-
-	g_assert (!nm_connection_get_setting_vlan (connection));
-
-	/* ===== CONNECTION SETTING ===== */
-	s_con = nm_connection_get_setting_connection (connection);
-	g_assert (s_con);
-	g_assert_cmpstr (nm_setting_connection_get_connection_type (s_con), ==, NM_SETTING_WIRED_SETTING_NAME);
-	g_assert_cmpstr (nm_setting_connection_get_id (s_con), ==, "iBFT eth1");
-	g_assert_cmpint (nm_setting_connection_get_timestamp (s_con), ==, 0);
-	g_assert (nm_setting_connection_get_autoconnect (s_con));
-	g_assert (nm_setting_connection_get_read_only (s_con));
-
-	/* ===== WIRED SETTING ===== */
-	s_wired = nm_connection_get_setting_wired (connection);
-	g_assert (s_wired);
-	mac_address = nm_setting_wired_get_mac_address (s_wired);
-	g_assert (mac_address);
-	g_assert (nm_utils_hwaddr_matches (mac_address, -1, expected_mac_address, -1));
-	g_assert_cmpint (nm_setting_wired_get_mtu (s_wired), ==, 0);
-
-	/* ===== IPv4 SETTING ===== */
-	s_ip4 = nm_connection_get_setting_ip4_config (connection);
-	g_assert (s_ip4);
-	g_assert_cmpstr (nm_setting_ip_config_get_method (s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
-
-	g_object_unref (connection);
-	g_ptr_array_unref (block);
-}
-
-static void
-test_read_ibft_static (void)
-{
-	NMConnection *connection;
-	NMSettingConnection *s_con;
-	NMSettingWired *s_wired;
-	NMSettingIPConfig *s_ip4;
-	GError *error = NULL;
-	const char *mac_address;
-	const char *expected_mac_address = "00:33:21:98:b9:f0";
-	NMIPAddress *ip4_addr;
-	GPtrArray *block;
-
-	block = read_block (TEST_DIR "/iscsiadm-test-static", expected_mac_address);
-
-	connection = nms_ibft_reader_get_connection_from_block (block, &error);
-	g_assert_no_error (error);
-	nmtst_assert_connection_verifies_without_normalization (connection);
-
-	g_assert (!nm_connection_get_setting_vlan (connection));
-
-	/* ===== CONNECTION SETTING ===== */
-	s_con = nm_connection_get_setting_connection (connection);
-	g_assert (s_con);
-	g_assert_cmpstr (nm_setting_connection_get_connection_type (s_con), ==, NM_SETTING_WIRED_SETTING_NAME);
-	g_assert_cmpstr (nm_setting_connection_get_id (s_con), ==, "iBFT eth0");
-	g_assert_cmpint (nm_setting_connection_get_timestamp (s_con), ==, 0);
-	g_assert (nm_setting_connection_get_autoconnect (s_con));
-	g_assert (nm_setting_connection_get_read_only (s_con));
-
-	/* ===== WIRED SETTING ===== */
-	s_wired = nm_connection_get_setting_wired (connection);
-	g_assert (s_wired);
-	mac_address = nm_setting_wired_get_mac_address (s_wired);
-	g_assert (mac_address);
-	g_assert (nm_utils_hwaddr_matches (mac_address, -1, expected_mac_address, -1));
-	g_assert_cmpint (nm_setting_wired_get_mtu (s_wired), ==, 0);
-
-	/* ===== IPv4 SETTING ===== */
-	s_ip4 = nm_connection_get_setting_ip4_config (connection);
-	g_assert (s_ip4);
-	g_assert_cmpstr (nm_setting_ip_config_get_method (s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_MANUAL);
-
-	g_assert_cmpint (nm_setting_ip_config_get_num_dns (s_ip4), ==, 2);
-	g_assert_cmpstr (nm_setting_ip_config_get_dns (s_ip4, 0), ==, "10.16.255.2");
-	g_assert_cmpstr (nm_setting_ip_config_get_dns (s_ip4, 1), ==, "10.16.255.3");
-
-	g_assert_cmpint (nm_setting_ip_config_get_num_addresses (s_ip4), ==, 1);
-	ip4_addr = nm_setting_ip_config_get_address (s_ip4, 0);
-	g_assert (ip4_addr);
-	g_assert_cmpstr (nm_ip_address_get_address (ip4_addr), ==, "192.168.32.72");
-	g_assert_cmpint (nm_ip_address_get_prefix (ip4_addr), ==, 22);
-
-	g_assert_cmpstr (nm_setting_ip_config_get_gateway (s_ip4), ==, "192.168.35.254");
-
-	g_object_unref (connection);
-	g_ptr_array_unref (block);
-}
-
-static void
-test_read_ibft_malformed (gconstpointer user_data)
-{
-	const char *iscsiadm_path = user_data;
-	nm_auto_free_ibft_blocks GSList *blocks = NULL;
-	GError *error = NULL;
-	gboolean success;
-
-	g_assert (g_file_test (iscsiadm_path, G_FILE_TEST_EXISTS));
-
-	NMTST_EXPECT_NM_WARN ("*malformed iscsiadm record*");
-
-	success = nms_ibft_reader_load_blocks (iscsiadm_path, &blocks, &error);
-	nmtst_assert_success (success, error);
-
-	g_assert (!blocks);
-
-	g_test_assert_expected_messages ();
-}
-
-static void
-test_read_ibft_bad_address (gconstpointer user_data)
-{
-	const char *iscsiadm_path = user_data;
-	NMConnection *connection;
-	const char *expected_mac_address = "00:33:21:98:b9:f0";
-	GPtrArray *block;
-	GError *error = NULL;
-
-	g_assert (g_file_test (iscsiadm_path, G_FILE_TEST_EXISTS));
-
-	block = read_block (iscsiadm_path, expected_mac_address);
-
-	connection = nms_ibft_reader_get_connection_from_block (block, &error);
-	g_assert_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION);
-	g_assert (strstr (error->message, "iBFT: malformed iscsiadm record: invalid"));
-	g_clear_error (&error);
-	g_assert (connection == NULL);
-
-	g_ptr_array_unref (block);
-}
-
-static void
-test_read_ibft_vlan (void)
-{
-	NMConnection *connection;
-	NMSettingConnection *s_con;
-	NMSettingWired *s_wired;
-	NMSettingVlan *s_vlan;
-	NMSettingIPConfig *s_ip4;
-	const char *mac_address;
-	const char *expected_mac_address = "00:33:21:98:b9:f0";
-	NMIPAddress *ip4_addr;
-	GError *error = NULL;
-	GPtrArray *block;
-
-	block = read_block (TEST_DIR "/iscsiadm-test-vlan", expected_mac_address);
-
-	connection = nms_ibft_reader_get_connection_from_block (block, &error);
-	g_assert_no_error (error);
-	nmtst_assert_connection_verifies_without_normalization (connection);
-
-	s_con = nm_connection_get_setting_connection (connection);
-	g_assert (s_con);
-	g_assert_cmpstr (nm_setting_connection_get_connection_type (s_con), ==, NM_SETTING_VLAN_SETTING_NAME);
-
-	/* ===== WIRED SETTING ===== */
-	s_wired = nm_connection_get_setting_wired (connection);
-	g_assert (s_wired);
-	mac_address = nm_setting_wired_get_mac_address (s_wired);
-	g_assert (mac_address);
-	g_assert (nm_utils_hwaddr_matches (mac_address, -1, expected_mac_address, -1));
-
-	/* ===== VLAN SETTING ===== */
-	s_vlan = nm_connection_get_setting_vlan (connection);
-	g_assert (s_vlan);
-	g_assert_cmpint (nm_setting_vlan_get_id (s_vlan), ==, 123);
-	g_assert_cmpstr (nm_setting_vlan_get_parent (s_vlan), ==, NULL);
-
-	/* ===== IPv4 SETTING ===== */
-	s_ip4 = nm_connection_get_setting_ip4_config (connection);
-	g_assert (s_ip4);
-	g_assert_cmpstr (nm_setting_ip_config_get_method (s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_MANUAL);
-
-	g_assert_cmpint (nm_setting_ip_config_get_num_dns (s_ip4), ==, 0);
-
-	g_assert_cmpint (nm_setting_ip_config_get_num_addresses (s_ip4), ==, 1);
-	ip4_addr = nm_setting_ip_config_get_address (s_ip4, 0);
-	g_assert (ip4_addr);
-	g_assert_cmpstr (nm_ip_address_get_address (ip4_addr), ==, "192.168.6.200");
-	g_assert_cmpint (nm_ip_address_get_prefix (ip4_addr), ==, 24);
-
-	g_assert_cmpstr (nm_setting_ip_config_get_gateway (s_ip4), ==, NULL);
-
-	g_object_unref (connection);
-	g_ptr_array_unref (block);
-}
-
-NMTST_DEFINE ();
-
-#define TPATH "/settings/plugins/ibft/"
-
-int main (int argc, char **argv)
-{
-	nmtst_init_assert_logging (&argc, &argv, "INFO", "DEFAULT");
-
-	g_test_add_func (TPATH "ibft/dhcp", test_read_ibft_dhcp);
-	g_test_add_func (TPATH "ibft/static", test_read_ibft_static);
-	g_test_add_func (TPATH "ibft/vlan", test_read_ibft_vlan);
-	g_test_add_data_func (TPATH "ibft/bad-record-read", TEST_DIR "/iscsiadm-test-bad-record", test_read_ibft_malformed);
-	g_test_add_data_func (TPATH "ibft/bad-entry-read", TEST_DIR "/iscsiadm-test-bad-entry", test_read_ibft_malformed);
-	g_test_add_data_func (TPATH "ibft/bad-ipaddr-read", TEST_DIR "/iscsiadm-test-bad-ipaddr", test_read_ibft_bad_address);
-	g_test_add_data_func (TPATH "ibft/bad-gateway-read", TEST_DIR "/iscsiadm-test-bad-gateway", test_read_ibft_bad_address);
-	g_test_add_data_func (TPATH "ibft/bad-dns1-read", TEST_DIR "/iscsiadm-test-bad-dns1", test_read_ibft_bad_address);
-	g_test_add_data_func (TPATH "ibft/bad-dns2-read", TEST_DIR "/iscsiadm-test-bad-dns2", test_read_ibft_bad_address);
-
-	return g_test_run ();
-}
-
diff --git a/src/settings/plugins/ifcfg-rh/meson.build b/src/settings/plugins/ifcfg-rh/meson.build
index 9024782a..58acdcfc 100644
--- a/src/settings/plugins/ifcfg-rh/meson.build
+++ b/src/settings/plugins/ifcfg-rh/meson.build
@@ -19,7 +19,6 @@ libnmdbus_ifcfg_rh = static_library(
 )
 
 core_sources = files(
-  'nm-inotify-helper.c',
   'nms-ifcfg-rh-reader.c',
   'nms-ifcfg-rh-utils.c',
   'nms-ifcfg-rh-writer.c',
@@ -36,7 +35,7 @@ libnms_ifcfg_rh_core = static_library(
   dependencies: deps,
 )
 
-sources = [dbus_sources] + core_sources + files('nms-ifcfg-rh-connection.c', 'nms-ifcfg-rh-plugin.c')
+sources = [dbus_sources] + core_sources + files('nms-ifcfg-rh-storage.c', 'nms-ifcfg-rh-plugin.c')
 
 libnm_settings_plugin_ifcfg_rh = shared_module(
   'nm-settings-plugin-ifcfg-rh',
diff --git a/src/settings/plugins/ifcfg-rh/nm-inotify-helper.c b/src/settings/plugins/ifcfg-rh/nm-inotify-helper.c
deleted file mode 100644
index 04cbb5bc..00000000
--- a/src/settings/plugins/ifcfg-rh/nm-inotify-helper.c
+++ /dev/null
@@ -1,213 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/* NetworkManager system settings service
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * (C) Copyright 2008 - 2017 Red Hat, Inc.
- */
-
-#include "nm-default.h"
-
-#include "nm-inotify-helper.h"
-
-#include <unistd.h>
-#include <sys/inotify.h>
-
-#include "NetworkManagerUtils.h"
-
-/* NOTE: this code should be killed once we depend on a new enough glib to
- * include the patches from https://bugzilla.gnome.org/show_bug.cgi?id=532815
- */
-
-/*****************************************************************************/
-
-enum {
-	EVENT,
-	LAST_SIGNAL
-};
-
-static guint signals[LAST_SIGNAL] = { 0 };
-
-typedef struct {
-	int ifd;
-	guint inotify_id;
-	GHashTable *wd_refs;
-} NMInotifyHelperPrivate;
-
-struct _NMInotifyHelper {
-	GObject parent;
-	NMInotifyHelperPrivate _priv;
-};
-
-struct _NMInotifyHelperClass {
-	GObjectClass parent;
-};
-
-G_DEFINE_TYPE (NMInotifyHelper, nm_inotify_helper, G_TYPE_OBJECT)
-
-#define NM_INOTIFY_HELPER_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMInotifyHelper, NM_IS_INOTIFY_HELPER)
-
-/*****************************************************************************/
-
-NM_DEFINE_SINGLETON_GETTER (NMInotifyHelper, nm_inotify_helper_get, NM_TYPE_INOTIFY_HELPER);
-
-/*****************************************************************************/
-
-int
-nm_inotify_helper_add_watch (NMInotifyHelper *self, const char *path)
-{
-	NMInotifyHelperPrivate *priv = NM_INOTIFY_HELPER_GET_PRIVATE (self);
-	int wd;
-	guint refcount;
-
-	if (priv->ifd < 0)
-		return -1;
-
-	/* We only care about modifications since we're just trying to get change
-	 * notifications on hardlinks.
-	 */
-
-	wd = inotify_add_watch (priv->ifd, path, IN_CLOSE_WRITE);
-	if (wd < 0)
-		return -1;
-
-	refcount = GPOINTER_TO_UINT (g_hash_table_lookup (priv->wd_refs, GINT_TO_POINTER (wd)));
-	refcount++;
-	g_hash_table_replace (priv->wd_refs, GINT_TO_POINTER (wd), GUINT_TO_POINTER (refcount));
-
-	return wd;
-}
-
-void
-nm_inotify_helper_remove_watch (NMInotifyHelper *self, int wd)
-{
-	NMInotifyHelperPrivate *priv = NM_INOTIFY_HELPER_GET_PRIVATE (self);
-	guint refcount;
-
-	if (priv->ifd < 0)
-		return;
-
-	refcount = GPOINTER_TO_UINT (g_hash_table_lookup (priv->wd_refs, GINT_TO_POINTER (wd)));
-	if (!refcount)
-		return;
-
-	refcount--;
-	if (!refcount) {
-		g_hash_table_remove (priv->wd_refs, GINT_TO_POINTER (wd));
-		inotify_rm_watch (priv->ifd, wd);
-	} else
-		g_hash_table_replace (priv->wd_refs, GINT_TO_POINTER (wd), GUINT_TO_POINTER (refcount));
-}
-
-static gboolean
-inotify_event_handler (GIOChannel *channel, GIOCondition cond, gpointer user_data)
-{
-	NMInotifyHelper *self = NM_INOTIFY_HELPER (user_data);
-	struct inotify_event evt;
-
-	/* read the notifications from the watch descriptor */
-	while (g_io_channel_read_chars (channel, (char *) &evt, sizeof (struct inotify_event), NULL, NULL) == G_IO_STATUS_NORMAL) {
-		char filename[PATH_MAX + 1];
-
-		filename[0] = '\0';
-		if (evt.len > 0) {
-			g_io_channel_read_chars (channel,
-			                        filename,
-			                        evt.len > PATH_MAX ? PATH_MAX : evt.len,
-			                        NULL, NULL);
-		}
-
-		if (!(evt.mask & IN_IGNORED))
-			g_signal_emit (self, signals[EVENT], 0, &evt, &filename[0]);
-	}
-
-	return TRUE;
-}
-
-static gboolean
-init_inotify (NMInotifyHelper *self)
-{
-	NMInotifyHelperPrivate *priv = NM_INOTIFY_HELPER_GET_PRIVATE (self);
-	GIOChannel *channel;
-	int errsv;
-
-	priv->ifd = inotify_init1 (IN_CLOEXEC);
-	if (priv->ifd == -1) {
-		errsv = errno;
-		nm_log_warn (LOGD_SETTINGS, "couldn't initialize inotify: %s (%d)", nm_strerror_native (errsv), errsv);
-		return FALSE;
-	}
-
-	/* Watch the inotify descriptor for file/directory change events */
-	channel = g_io_channel_unix_new (priv->ifd);
-	g_io_channel_set_flags (channel, G_IO_FLAG_NONBLOCK, NULL);
-	g_io_channel_set_encoding (channel, NULL, NULL);
-
-	priv->inotify_id = g_io_add_watch (channel,
-	                                   G_IO_IN | G_IO_ERR,
-	                                   (GIOFunc) inotify_event_handler,
-	                                   (gpointer) self);
-	g_io_channel_unref (channel);
-	return TRUE;
-}
-
-/*****************************************************************************/
-
-static void
-nm_inotify_helper_init (NMInotifyHelper *self)
-{
-	NMInotifyHelperPrivate *priv = NM_INOTIFY_HELPER_GET_PRIVATE (self);
-
-	priv->wd_refs = g_hash_table_new (nm_direct_hash, NULL);
-}
-
-static void
-constructed (GObject *object)
-{
-	G_OBJECT_CLASS (nm_inotify_helper_parent_class)->constructed (object);
-
-	init_inotify (NM_INOTIFY_HELPER (object));
-}
-
-static void
-finalize (GObject *object)
-{
-	NMInotifyHelperPrivate *priv = NM_INOTIFY_HELPER_GET_PRIVATE ((NMInotifyHelper *) object);
-
-	nm_clear_g_source (&priv->inotify_id);
-	nm_close (priv->ifd);
-
-	g_hash_table_destroy (priv->wd_refs);
-
-	G_OBJECT_CLASS (nm_inotify_helper_parent_class)->finalize (object);
-}
-
-static void
-nm_inotify_helper_class_init (NMInotifyHelperClass *klass)
-{
-	GObjectClass *object_class = G_OBJECT_CLASS (klass);
-
-	object_class->constructed = constructed;
-	object_class->finalize = finalize;
-
-	signals[EVENT] =
-	    g_signal_new ("event",
-	                  G_OBJECT_CLASS_TYPE (object_class),
-	                  G_SIGNAL_RUN_LAST,
-	                  0,
-	                  NULL, NULL, NULL,
-	                  G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_STRING);
-}
-
diff --git a/src/settings/plugins/ifcfg-rh/nm-inotify-helper.h b/src/settings/plugins/ifcfg-rh/nm-inotify-helper.h
deleted file mode 100644
index b887ae37..00000000
--- a/src/settings/plugins/ifcfg-rh/nm-inotify-helper.h
+++ /dev/null
@@ -1,59 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/* NetworkManager system settings service
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * (C) Copyright 2008 Red Hat, Inc.
- */
-
-#ifndef __NM_INOTIFY_HELPER_H__
-#define __NM_INOTIFY_HELPER_H__
-
-/* NOTE: this code should be killed once we depend on a new enough glib to
- * include the patches from https://bugzilla.gnome.org/show_bug.cgi?id=532815
- */
-
-#define NM_TYPE_INOTIFY_HELPER            (nm_inotify_helper_get_type ())
-#define NM_INOTIFY_HELPER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_INOTIFY_HELPER, NMInotifyHelper))
-#define NM_INOTIFY_HELPER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_INOTIFY_HELPER, NMInotifyHelperClass))
-#define NM_IS_INOTIFY_HELPER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_INOTIFY_HELPER))
-#define NM_IS_INOTIFY_HELPER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_INOTIFY_HELPER))
-#define NM_INOTIFY_HELPER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_INOTIFY_HELPER, NMInotifyHelperClass))
-
-typedef struct _NMInotifyHelper NMInotifyHelper;
-typedef struct _NMInotifyHelperClass NMInotifyHelperClass;
-
-GType nm_inotify_helper_get_type (void);
-
-NMInotifyHelper * nm_inotify_helper_get (void);
-
-int nm_inotify_helper_add_watch (NMInotifyHelper *helper, const char *path);
-
-void nm_inotify_helper_remove_watch (NMInotifyHelper *helper, int wd);
-
-static inline gboolean
-nm_inotify_helper_clear_watch (NMInotifyHelper *helper, int *wd)
-{
-	int x;
-
-	if (wd && ((x = *wd) >= 0)) {
-		*wd = -1;
-		nm_inotify_helper_remove_watch (helper, x);
-		return TRUE;
-	}
-	return FALSE;
-}
-
-#endif  /* __NM_INOTIFY_HELPER_H__ */
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-common.h b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-common.h
index eaff2db4..ff01fc7a 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-common.h
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-common.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager system settings service
  *
  * This program is free software; you can redistribute it and/or modify
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-connection.c b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-connection.c
deleted file mode 100644
index 4f769c5f..00000000
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-connection.c
+++ /dev/null
@@ -1,511 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/* NetworkManager system settings service
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright (C) 2008 - 2011 Red Hat, Inc.
- */
-
-#include "nm-default.h"
-
-#include "nms-ifcfg-rh-connection.h"
-
-#include <sys/inotify.h>
-#include <glib/gstdio.h>
-
-#include "nm-dbus-interface.h"
-#include "nm-setting-connection.h"
-#include "nm-setting-wired.h"
-#include "nm-setting-wireless.h"
-#include "nm-setting-gsm.h"
-#include "nm-setting-cdma.h"
-#include "nm-setting-pppoe.h"
-#include "nm-setting-wireless-security.h"
-#include "nm-setting-8021x.h"
-#include "platform/nm-platform.h"
-#include "nm-config.h"
-
-#include "nms-ifcfg-rh-common.h"
-#include "nms-ifcfg-rh-reader.h"
-#include "nms-ifcfg-rh-writer.h"
-#include "nms-ifcfg-rh-utils.h"
-#include "nm-inotify-helper.h"
-
-/*****************************************************************************/
-
-NM_GOBJECT_PROPERTIES_DEFINE_BASE (
-	PROP_UNMANAGED_SPEC,
-	PROP_UNRECOGNIZED_SPEC,
-);
-
-enum {
-	IFCFG_CHANGED,
-	LAST_SIGNAL
-};
-
-static guint signals[LAST_SIGNAL] = { 0 };
-
-typedef struct {
-	gulong ih_event_id;
-
-	int file_wd;
-
-	char *keyfile;
-	int keyfile_wd;
-
-	char *routefile;
-	int routefile_wd;
-
-	char *route6file;
-	int route6file_wd;
-
-	char *unmanaged_spec;
-	char *unrecognized_spec;
-
-	gulong devtimeout_link_changed_handler;
-	guint devtimeout_timeout_id;
-
-	NMInotifyHelper *inotify_helper;
-} NMIfcfgConnectionPrivate;
-
-struct _NMIfcfgConnection {
-	NMSettingsConnection parent;
-	NMIfcfgConnectionPrivate _priv;
-};
-
-struct _NMIfcfgConnectionClass {
-	NMSettingsConnectionClass parent;
-};
-
-G_DEFINE_TYPE (NMIfcfgConnection, nm_ifcfg_connection, NM_TYPE_SETTINGS_CONNECTION)
-
-#define NM_IFCFG_CONNECTION_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMIfcfgConnection, NM_IS_IFCFG_CONNECTION)
-
-/*****************************************************************************/
-
-static gboolean
-devtimeout_ready (gpointer user_data)
-{
-	NMIfcfgConnection *self = user_data;
-	NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE (self);
-
-	priv->devtimeout_timeout_id = 0;
-	nm_settings_connection_set_ready (NM_SETTINGS_CONNECTION (self), TRUE);
-	return FALSE;
-}
-
-static void
-link_changed (NMPlatform *platform, int obj_type_i, int ifindex, const NMPlatformLink *link,
-              int change_type_i,
-              NMConnection *self)
-{
-	const NMPlatformSignalChangeType change_type = change_type_i;
-	NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE ((NMIfcfgConnection *) self);
-	const char *ifname;
-
-	ifname = nm_connection_get_interface_name (self);
-	if (g_strcmp0 (link->name, ifname) != 0)
-		return;
-
-	if (change_type == NM_PLATFORM_SIGNAL_REMOVED)
-		return;
-
-	nm_log_info (LOGD_SETTINGS, "Device %s appeared; connection '%s' now ready",
-	             ifname, nm_connection_get_id (self));
-
-	g_signal_handler_disconnect (platform, priv->devtimeout_link_changed_handler);
-	priv->devtimeout_link_changed_handler = 0;
-	g_source_remove (priv->devtimeout_timeout_id);
-
-	/* Don't declare the connection ready right away, since NMManager may not have
-	 * started processing the device yet.
-	 */
-	priv->devtimeout_timeout_id = g_idle_add (devtimeout_ready, self);
-}
-
-static gboolean
-devtimeout_expired (gpointer user_data)
-{
-	NMIfcfgConnection *self = user_data;
-	NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE (self);
-
-	nm_log_info (LOGD_SETTINGS, "Device for connection '%s' did not appear before timeout",
-	             nm_settings_connection_get_id (NM_SETTINGS_CONNECTION (self)));
-
-	g_signal_handler_disconnect (NM_PLATFORM_GET, priv->devtimeout_link_changed_handler);
-	priv->devtimeout_link_changed_handler = 0;
-	priv->devtimeout_timeout_id = 0;
-
-	nm_settings_connection_set_ready (NM_SETTINGS_CONNECTION (self), TRUE);
-	return FALSE;
-}
-
-static void
-nm_ifcfg_connection_check_devtimeout (NMIfcfgConnection *self)
-{
-	NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE (self);
-	NMSettingConnection *s_con;
-	const char *ifname;
-	const char *filename;
-	guint devtimeout;
-	const NMPlatformLink *pllink;
-
-	s_con = nm_connection_get_setting_connection (nm_settings_connection_get_connection (NM_SETTINGS_CONNECTION (self)));
-
-	if (!nm_setting_connection_get_autoconnect (s_con))
-		return;
-	ifname = nm_setting_connection_get_interface_name (s_con);
-	if (!ifname)
-		return;
-	filename = nm_settings_connection_get_filename (NM_SETTINGS_CONNECTION (self));
-	if (!filename)
-		return;
-
-	pllink = nm_platform_link_get_by_ifname (NM_PLATFORM_GET, ifname);
-	if (pllink && pllink->initialized)
-		return;
-
-	devtimeout = devtimeout_from_file (filename);
-	if (!devtimeout)
-		return;
-
-	/* ONBOOT=yes, DEVICE and DEVTIMEOUT are set, but device is not present */
-	nm_settings_connection_set_ready (NM_SETTINGS_CONNECTION (self), FALSE);
-
-	nm_log_info (LOGD_SETTINGS, "Waiting %u seconds for %s to appear for connection '%s'",
-	             devtimeout, ifname, nm_settings_connection_get_id (NM_SETTINGS_CONNECTION (self)));
-
-	priv->devtimeout_link_changed_handler =
-	    g_signal_connect (NM_PLATFORM_GET, NM_PLATFORM_SIGNAL_LINK_CHANGED,
-	                      G_CALLBACK (link_changed), self);
-	priv->devtimeout_timeout_id = g_timeout_add_seconds (devtimeout, devtimeout_expired, self);
-}
-
-static void
-files_changed_cb (NMInotifyHelper *ih,
-                  struct inotify_event *evt,
-                  const char *path,
-                  gpointer user_data)
-{
-	NMIfcfgConnection *self = NM_IFCFG_CONNECTION (user_data);
-	NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE (self);
-
-	if (   (evt->wd != priv->file_wd)
-	    && (evt->wd != priv->keyfile_wd)
-	    && (evt->wd != priv->routefile_wd)
-	    && (evt->wd != priv->route6file_wd))
-		return;
-
-	/* push the event up to the plugin */
-	g_signal_emit (self, signals[IFCFG_CHANGED], 0);
-}
-
-static void
-path_watch_stop (NMIfcfgConnection *self)
-{
-	NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE (self);
-
-	nm_clear_g_signal_handler (priv->inotify_helper, &priv->ih_event_id);
-
-	nm_inotify_helper_clear_watch (priv->inotify_helper, &priv->file_wd);
-	nm_inotify_helper_clear_watch (priv->inotify_helper, &priv->keyfile_wd);
-	nm_inotify_helper_clear_watch (priv->inotify_helper, &priv->routefile_wd);
-	nm_inotify_helper_clear_watch (priv->inotify_helper, &priv->route6file_wd);
-
-	nm_clear_g_free (&priv->keyfile);
-	nm_clear_g_free (&priv->routefile);
-	nm_clear_g_free (&priv->route6file);
-}
-
-static void
-filename_changed (GObject *object,
-                  GParamSpec *pspec,
-                  gpointer user_data)
-{
-	NMIfcfgConnection *self = NM_IFCFG_CONNECTION (object);
-	NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE (self);
-	const char *ifcfg_path;
-
-	path_watch_stop (self);
-
-	ifcfg_path = nm_settings_connection_get_filename (NM_SETTINGS_CONNECTION (self));
-	if (!ifcfg_path)
-		return;
-
-	priv->keyfile = utils_get_keys_path (ifcfg_path);
-	priv->routefile = utils_get_route_path (ifcfg_path);
-	priv->route6file = utils_get_route6_path (ifcfg_path);
-
-	if (nm_config_get_monitor_connection_files (nm_config_get ())) {
-		NMInotifyHelper *ih;
-
-		if (!priv->inotify_helper)
-			priv->inotify_helper = g_object_ref (nm_inotify_helper_get ());
-		ih = priv->inotify_helper;
-
-		priv->ih_event_id = g_signal_connect (ih, "event", G_CALLBACK (files_changed_cb), self);
-		priv->file_wd = nm_inotify_helper_add_watch (ih, ifcfg_path);
-		priv->keyfile_wd = nm_inotify_helper_add_watch (ih, priv->keyfile);
-		priv->routefile_wd = nm_inotify_helper_add_watch (ih, priv->routefile);
-		priv->route6file_wd = nm_inotify_helper_add_watch (ih, priv->route6file);
-	}
-}
-
-const char *
-nm_ifcfg_connection_get_unmanaged_spec (NMIfcfgConnection *self)
-{
-	g_return_val_if_fail (NM_IS_IFCFG_CONNECTION (self), NULL);
-
-	return NM_IFCFG_CONNECTION_GET_PRIVATE (self)->unmanaged_spec;
-}
-
-const char *
-nm_ifcfg_connection_get_unrecognized_spec (NMIfcfgConnection *self)
-{
-	g_return_val_if_fail (NM_IS_IFCFG_CONNECTION (self), NULL);
-
-	return NM_IFCFG_CONNECTION_GET_PRIVATE (self)->unrecognized_spec;
-}
-
-static gboolean
-commit_changes (NMSettingsConnection *connection,
-                NMConnection *new_connection,
-                NMSettingsConnectionCommitReason commit_reason,
-                NMConnection **out_reread_connection,
-                char **out_logmsg_change,
-                GError **error)
-{
-	const char *filename;
-	gs_unref_object NMConnection *reread = NULL;
-	gboolean reread_same = TRUE;
-	const char *operation_message;
-	gs_free char *ifcfg_path = NULL;
-
-	nm_assert (out_reread_connection && !*out_reread_connection);
-	nm_assert (!out_logmsg_change || !*out_logmsg_change);
-
-	filename = nm_settings_connection_get_filename (connection);
-	if (!nms_ifcfg_rh_writer_write_connection (new_connection,
-	                                           IFCFG_DIR,
-	                                           filename,
-	                                           &ifcfg_path,
-	                                           &reread,
-	                                           &reread_same,
-	                                           error))
-		return FALSE;
-
-	nm_assert ((!filename && ifcfg_path) || (filename && !ifcfg_path));
-	if (ifcfg_path) {
-		nm_settings_connection_set_filename (connection, ifcfg_path);
-		operation_message = "persist";
-	} else
-		operation_message = "update";
-
-	if (reread && !reread_same)
-		*out_reread_connection = g_steal_pointer (&reread);
-
-	NM_SET_OUT (out_logmsg_change,
-	            g_strdup_printf ("ifcfg-rh: %s %s",
-	                             operation_message, filename));
-	return TRUE;
-}
-
-static gboolean
-delete (NMSettingsConnection *connection,
-        GError **error)
-{
-	NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE ((NMIfcfgConnection *) connection);
-	const char *filename;
-
-	filename = nm_settings_connection_get_filename (connection);
-	if (filename) {
-		g_unlink (filename);
-		if (priv->keyfile)
-			g_unlink (priv->keyfile);
-		if (priv->routefile)
-			g_unlink (priv->routefile);
-		if (priv->route6file)
-			g_unlink (priv->route6file);
-	}
-
-	return TRUE;
-}
-
-/*****************************************************************************/
-
-static void
-get_property (GObject *object, guint prop_id,
-              GValue *value, GParamSpec *pspec)
-{
-	NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE ((NMIfcfgConnection *) object);
-
-	switch (prop_id) {
-	case PROP_UNMANAGED_SPEC:
-		g_value_set_string (value, priv->unmanaged_spec);
-		break;
-	case PROP_UNRECOGNIZED_SPEC:
-		g_value_set_string (value, priv->unrecognized_spec);
-		break;
-	default:
-		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-		break;
-	}
-}
-
-static void
-set_property (GObject *object, guint prop_id,
-              const GValue *value, GParamSpec *pspec)
-{
-	NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE ((NMIfcfgConnection *) object);
-
-	switch (prop_id) {
-	case PROP_UNMANAGED_SPEC:
-		priv->unmanaged_spec = g_value_dup_string (value);
-		break;
-	case PROP_UNRECOGNIZED_SPEC:
-		priv->unrecognized_spec = g_value_dup_string (value);
-		break;
-	default:
-		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-		break;
-	}
-}
-
-/*****************************************************************************/
-
-static void
-nm_ifcfg_connection_init (NMIfcfgConnection *connection)
-{
-	NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE (connection);
-
-	priv->file_wd = -1;
-	priv->keyfile_wd = -1;
-	priv->routefile_wd = -1;
-	priv->route6file_wd = -1;
-
-	g_signal_connect (connection, "notify::" NM_SETTINGS_CONNECTION_FILENAME,
-	                  G_CALLBACK (filename_changed), NULL);
-}
-
-NMIfcfgConnection *
-nm_ifcfg_connection_new (NMConnection *source,
-                         const char *full_path,
-                         GError **error,
-                         gboolean *out_ignore_error)
-{
-	GObject *object;
-	NMConnection *tmp;
-	char *unhandled_spec = NULL;
-	const char *unmanaged_spec = NULL, *unrecognized_spec = NULL;
-
-	g_assert (source || full_path);
-
-	if (out_ignore_error)
-		*out_ignore_error = FALSE;
-
-	/* If we're given a connection already, prefer that instead of re-reading */
-	if (source)
-		tmp = g_object_ref (source);
-	else {
-		tmp = connection_from_file (full_path,
-		                            &unhandled_spec,
-		                            error,
-		                            out_ignore_error);
-		if (!tmp)
-			return NULL;
-	}
-
-	if (unhandled_spec && g_str_has_prefix (unhandled_spec, "unmanaged:"))
-		unmanaged_spec = unhandled_spec + strlen ("unmanaged:");
-	else if (unhandled_spec && g_str_has_prefix (unhandled_spec, "unrecognized:"))
-		unrecognized_spec = unhandled_spec + strlen ("unrecognized:");
-
-	object = (GObject *) g_object_new (NM_TYPE_IFCFG_CONNECTION,
-	                                   NM_SETTINGS_CONNECTION_FILENAME, full_path,
-	                                   NM_IFCFG_CONNECTION_UNMANAGED_SPEC, unmanaged_spec,
-	                                   NM_IFCFG_CONNECTION_UNRECOGNIZED_SPEC, unrecognized_spec,
-	                                   NULL);
-	/* Update our settings with what was read from the file */
-	if (nm_settings_connection_update (NM_SETTINGS_CONNECTION (object),
-	                                   tmp,
-	                                   full_path
-	                                     ? NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP_SAVED
-	                                     : NM_SETTINGS_CONNECTION_PERSIST_MODE_UNSAVED,
-	                                   NM_SETTINGS_CONNECTION_COMMIT_REASON_NONE,
-	                                   NULL,
-	                                   error))
-		nm_ifcfg_connection_check_devtimeout (NM_IFCFG_CONNECTION (object));
-	else
-		g_clear_object (&object);
-
-	g_object_unref (tmp);
-	g_free (unhandled_spec);
-	return (NMIfcfgConnection *) object;
-}
-
-static void
-dispose (GObject *object)
-{
-	NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE ((NMIfcfgConnection *) object);
-
-	path_watch_stop (NM_IFCFG_CONNECTION (object));
-
-	nm_clear_g_signal_handler (NM_PLATFORM_GET, &priv->devtimeout_link_changed_handler);
-	nm_clear_g_source (&priv->devtimeout_timeout_id);
-
-	g_clear_object (&priv->inotify_helper);
-
-	g_clear_pointer (&priv->unmanaged_spec, g_free);
-	g_clear_pointer (&priv->unrecognized_spec, g_free);
-
-	G_OBJECT_CLASS (nm_ifcfg_connection_parent_class)->dispose (object);
-}
-
-static void
-nm_ifcfg_connection_class_init (NMIfcfgConnectionClass *ifcfg_connection_class)
-{
-	GObjectClass *object_class = G_OBJECT_CLASS (ifcfg_connection_class);
-	NMSettingsConnectionClass *settings_class = NM_SETTINGS_CONNECTION_CLASS (ifcfg_connection_class);
-
-	object_class->set_property = set_property;
-	object_class->get_property = get_property;
-	object_class->dispose      = dispose;
-
-	settings_class->delete = delete;
-	settings_class->commit_changes = commit_changes;
-
-	obj_properties[PROP_UNMANAGED_SPEC] =
-	     g_param_spec_string (NM_IFCFG_CONNECTION_UNMANAGED_SPEC, "", "",
-	                          NULL,
-	                          G_PARAM_READWRITE |
-	                          G_PARAM_STATIC_STRINGS);
-
-	obj_properties[PROP_UNRECOGNIZED_SPEC] =
-	     g_param_spec_string (NM_IFCFG_CONNECTION_UNRECOGNIZED_SPEC, "", "",
-	                          NULL,
-	                          G_PARAM_READWRITE |
-	                          G_PARAM_STATIC_STRINGS);
-
-	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
-
-	signals[IFCFG_CHANGED] =
-	    g_signal_new ("ifcfg-changed",
-	                  G_OBJECT_CLASS_TYPE (object_class),
-	                  G_SIGNAL_RUN_LAST,
-	                  0, NULL, NULL,
-	                  g_cclosure_marshal_VOID__VOID,
-	                  G_TYPE_NONE, 0);
-}
-
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-connection.h b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-connection.h
deleted file mode 100644
index fd68f2d3..00000000
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-connection.h
+++ /dev/null
@@ -1,54 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/* NetworkManager system settings service
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright (C) 2008 - 2011 Red Hat, Inc.
- */
-
-#ifndef __NETWORKMANAGER_IFCFG_CONNECTION_H__
-#define __NETWORKMANAGER_IFCFG_CONNECTION_H__
-
-#include "nm-dbus-interface.h"
-#include "settings/nm-settings-connection.h"
-
-#define NM_TYPE_IFCFG_CONNECTION            (nm_ifcfg_connection_get_type ())
-#define NM_IFCFG_CONNECTION(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_IFCFG_CONNECTION, NMIfcfgConnection))
-#define NM_IFCFG_CONNECTION_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_IFCFG_CONNECTION, NMIfcfgConnectionClass))
-#define NM_IS_IFCFG_CONNECTION(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_IFCFG_CONNECTION))
-#define NM_IS_IFCFG_CONNECTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_IFCFG_CONNECTION))
-#define NM_IFCFG_CONNECTION_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_IFCFG_CONNECTION, NMIfcfgConnectionClass))
-
-#define NM_IFCFG_CONNECTION_UNMANAGED_SPEC    "unmanaged-spec"
-#define NM_IFCFG_CONNECTION_UNRECOGNIZED_SPEC "unrecognized-spec"
-
-typedef struct _NMIfcfgConnection NMIfcfgConnection;
-typedef struct _NMIfcfgConnectionClass NMIfcfgConnectionClass;
-
-GType nm_ifcfg_connection_get_type (void);
-
-NMIfcfgConnection *nm_ifcfg_connection_new (NMConnection *source,
-                                            const char *full_path,
-                                            GError **error,
-                                            gboolean *out_ignore_error);
-
-const char *nm_ifcfg_connection_get_unmanaged_spec (NMIfcfgConnection *self);
-const char *nm_ifcfg_connection_get_unrecognized_spec (NMIfcfgConnection *self);
-
-gboolean nm_ifcfg_connection_update (NMIfcfgConnection *self,
-                                     GHashTable *new_settings,
-                                     GError **error);
-
-#endif /* __NETWORKMANAGER_IFCFG_CONNECTION_H__ */
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 5160dbf0..cc4fe4ce 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager system settings service
  *
  * Dan Williams <dcbw@redhat.com>
@@ -25,23 +24,26 @@
 
 #include "nms-ifcfg-rh-plugin.h"
 
-#include <unistd.h>
 #include <sys/types.h>
 #include <sys/stat.h>
-#include <gmodule.h>
+#include <unistd.h>
 
+#include "nm-std-aux/c-list-util.h"
+#include "nm-glib-aux/nm-c-list.h"
+#include "nm-glib-aux/nm-io-utils.h"
 #include "nm-std-aux/nm-dbus-compat.h"
-#include "nm-setting-connection.h"
-#include "settings/nm-settings-plugin.h"
+#include "nm-utils.h"
+#include "nm-core-internal.h"
 #include "nm-config.h"
+#include "settings/nm-settings-plugin.h"
+#include "settings/nm-settings-utils.h"
 #include "NetworkManagerUtils.h"
 
-#include "nms-ifcfg-rh-connection.h"
+#include "nms-ifcfg-rh-storage.h"
 #include "nms-ifcfg-rh-common.h"
+#include "nms-ifcfg-rh-utils.h"
 #include "nms-ifcfg-rh-reader.h"
 #include "nms-ifcfg-rh-writer.h"
-#include "nms-ifcfg-rh-utils.h"
-#include "shvar.h"
 
 #define IFCFGRH1_BUS_NAME                               "com.redhat.ifcfgrh1"
 #define IFCFGRH1_OBJECT_PATH                            "/com/redhat/ifcfgrh1"
@@ -60,31 +62,25 @@ typedef struct {
 		guint regist_id;
 	} dbus;
 
-	GHashTable *connections;  /* uuid::connection */
-	gboolean initialized;
+	NMSettUtilStorages storages;
 
-	GFileMonitor *ifcfg_monitor;
-	gulong ifcfg_monitor_id;
-} SettingsPluginIfcfgPrivate;
+	GHashTable *unmanaged_specs;
+	GHashTable *unrecognized_specs;
 
-struct _SettingsPluginIfcfg {
+} NMSIfcfgRHPluginPrivate;
+
+struct _NMSIfcfgRHPlugin {
 	NMSettingsPlugin parent;
-	SettingsPluginIfcfgPrivate _priv;
+	NMSIfcfgRHPluginPrivate _priv;
 };
 
-struct _SettingsPluginIfcfgClass {
+struct _NMSIfcfgRHPluginClass {
 	NMSettingsPluginClass parent;
 };
 
-G_DEFINE_TYPE (SettingsPluginIfcfg, settings_plugin_ifcfg, NM_TYPE_SETTINGS_PLUGIN)
+G_DEFINE_TYPE (NMSIfcfgRHPlugin, nms_ifcfg_rh_plugin, NM_TYPE_SETTINGS_PLUGIN)
 
-#define SETTINGS_PLUGIN_IFCFG_GET_PRIVATE(self) _NM_GET_PRIVATE (self, SettingsPluginIfcfg, SETTINGS_IS_PLUGIN_IFCFG)
-
-/*****************************************************************************/
-
-static SettingsPluginIfcfg *settings_plugin_ifcfg_get (void);
-
-NM_DEFINE_SINGLETON_GETTER (SettingsPluginIfcfg, settings_plugin_ifcfg_get, SETTINGS_TYPE_PLUGIN_IFCFG);
+#define NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMSIfcfgRHPlugin, NMS_IS_IFCFG_RH_PLUGIN, NMSettingsPlugin)
 
 /*****************************************************************************/
 
@@ -99,610 +95,811 @@ NM_DEFINE_SINGLETON_GETTER (SettingsPluginIfcfg, settings_plugin_ifcfg_get, SETT
 
 /*****************************************************************************/
 
-static NMIfcfgConnection *update_connection (SettingsPluginIfcfg *plugin,
-                                             NMConnection *source,
-                                             const char *full_path,
-                                             NMIfcfgConnection *connection,
-                                             gboolean protect_existing_connection,
-                                             GHashTable *protected_connections,
-                                             GError **error);
+static void _unhandled_specs_reset (NMSIfcfgRHPlugin *self);
+
+static void _unhandled_specs_merge_storages (NMSIfcfgRHPlugin *self,
+                                             NMSettUtilStorages *storages);
 
 /*****************************************************************************/
 
 static void
-connection_ifcfg_changed (NMIfcfgConnection *connection, gpointer user_data)
+nm_assert_self (NMSIfcfgRHPlugin *self, gboolean unhandled_specs_consistent)
 {
-	SettingsPluginIfcfg *self = SETTINGS_PLUGIN_IFCFG (user_data);
-	SettingsPluginIfcfgPrivate *priv = SETTINGS_PLUGIN_IFCFG_GET_PRIVATE (self);
-	const char *path;
+	nm_assert (NMS_IS_IFCFG_RH_PLUGIN (self));
 
-	path = nm_settings_connection_get_filename (NM_SETTINGS_CONNECTION (connection));
-	g_return_if_fail (path != NULL);
+#if NM_MORE_ASSERTS > 5
+	{
+		NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE (self);
+		NMSIfcfgRHStorage *storage;
+		gsize n_uuid;
+		gs_unref_hashtable GHashTable *h_unmanaged = NULL;
+		gs_unref_hashtable GHashTable *h_unrecognized = NULL;
 
-	if (!priv->ifcfg_monitor) {
-		_LOGD ("connection_ifcfg_changed("NM_IFCFG_CONNECTION_LOG_FMTD"): %s", NM_IFCFG_CONNECTION_LOG_ARGD (connection), "ignore event");
-		return;
-	}
+		nm_assert (g_hash_table_size (priv->storages.idx_by_filename) == c_list_length (&priv->storages._storage_lst_head));
+
+		h_unmanaged = g_hash_table_new (nm_str_hash, g_str_equal);
+		h_unrecognized = g_hash_table_new (nm_str_hash, g_str_equal);
+
+		n_uuid = 0;
+
+		c_list_for_each_entry (storage, &priv->storages._storage_lst_head, parent._storage_lst) {
+			const char *uuid;
+			const char *filename;
+
+			filename = nms_ifcfg_rh_storage_get_filename (storage);
+
+			nm_assert (filename && NM_STR_HAS_PREFIX (filename, IFCFG_DIR"/"));
+
+			uuid = nms_ifcfg_rh_storage_get_uuid_opt (storage);
+
+			nm_assert ((!!uuid) + (!!storage->unmanaged_spec) + (!!storage->unrecognized_spec) == 1);
 
-	_LOGD ("connection_ifcfg_changed("NM_IFCFG_CONNECTION_LOG_FMTD"): %s", NM_IFCFG_CONNECTION_LOG_ARGD (connection), "reload");
+			nm_assert (storage == nm_sett_util_storages_lookup_by_filename (&priv->storages, filename));
+
+			if (uuid) {
+				NMSettUtilStorageByUuidHead *sbuh;
+				NMSettUtilStorageByUuidHead *sbuh2;
+
+				if (storage->connection)
+					nm_assert (nm_streq0 (nm_connection_get_uuid (storage->connection), uuid));
+
+				if (!g_hash_table_lookup_extended (priv->storages.idx_by_uuid, &uuid, (gpointer *) &sbuh, (gpointer *) &sbuh2))
+					nm_assert_not_reached ();
+
+				nm_assert (sbuh);
+				nm_assert (nm_streq (uuid, sbuh->uuid));
+				nm_assert (sbuh == sbuh2);
+				nm_assert (c_list_contains (&sbuh->_storage_by_uuid_lst_head, &storage->parent._storage_by_uuid_lst));
+
+				if (c_list_first (&sbuh->_storage_by_uuid_lst_head) == &storage->parent._storage_by_uuid_lst)
+					n_uuid++;
+			} else if (storage->unmanaged_spec) {
+				nm_assert (strlen (storage->unmanaged_spec) > 0);
+				g_hash_table_add (h_unmanaged, storage->unmanaged_spec);
+			} else if (storage->unrecognized_spec) {
+				nm_assert (strlen (storage->unrecognized_spec) > 0);
+				g_hash_table_add (h_unrecognized, storage->unrecognized_spec);
+			} else
+				nm_assert_not_reached ();
+
+			nm_assert (!storage->connection);
+		}
 
-	update_connection (self, NULL, path, connection, TRUE, NULL, NULL);
+		nm_assert (g_hash_table_size (priv->storages.idx_by_uuid) == n_uuid);
+
+		if (unhandled_specs_consistent) {
+			nm_assert (nm_utils_hashtable_same_keys (h_unmanaged, priv->unmanaged_specs));
+			nm_assert (nm_utils_hashtable_same_keys (h_unrecognized, priv->unrecognized_specs));
+		}
+	}
+#endif
 }
 
-static void
-connection_removed_cb (NMSettingsConnection *obj, gpointer user_data)
+/*****************************************************************************/
+
+static NMSIfcfgRHStorage *
+_load_file (NMSIfcfgRHPlugin *self,
+            const char *filename,
+            GError **error)
 {
-	g_hash_table_remove (SETTINGS_PLUGIN_IFCFG_GET_PRIVATE ((SettingsPluginIfcfg *) user_data)->connections,
-	                     nm_settings_connection_get_uuid (NM_SETTINGS_CONNECTION (obj)));
+	gs_unref_object NMConnection *connection = NULL;
+	gs_free_error GError *load_error = NULL;
+	gs_free char *unhandled_spec = NULL;
+	gboolean load_error_ignore;
+	struct stat st;
+
+	if (stat (filename, &st) != 0) {
+		int errsv = errno;
+
+		if (error) {
+			nm_utils_error_set_errno (error, errsv,
+			                          "failure to stat file \%s\": %s",
+			                          filename);
+		} else
+			_LOGT ("load[%s]: failure to stat file: %s", filename, nm_strerror_native (errsv));
+		return NULL;
+	}
+
+	connection = connection_from_file (filename,
+	                                   &unhandled_spec,
+	                                   &load_error,
+	                                   &load_error_ignore);
+	if (load_error) {
+		if (error) {
+			nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN,
+			                    "failure to read file \"%s\": %s",
+			                    filename, load_error->message);
+		} else {
+			_NMLOG (load_error_ignore ? LOGL_TRACE : LOGL_WARN,
+			        "load[%s]: failure to read file: %s", filename, load_error->message);
+		}
+		return NULL;
+	}
+
+	if (unhandled_spec) {
+		const char *unmanaged_spec;
+		const char *unrecognized_spec;
+
+		if (!nms_ifcfg_rh_util_parse_unhandled_spec (unhandled_spec,
+		                                             &unmanaged_spec,
+		                                             &unrecognized_spec)) {
+			nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN,
+			                    "invalid unhandled spec \"%s\"",
+			                    unhandled_spec);
+			nm_assert_not_reached ();
+			return NULL;
+		}
+		return nms_ifcfg_rh_storage_new_unhandled (self,
+		                                           filename,
+		                                           unmanaged_spec,
+		                                           unrecognized_spec);
+	}
+
+	return nms_ifcfg_rh_storage_new_connection (self,
+	                                            filename,
+	                                            g_steal_pointer (&connection),
+	                                            &st.st_mtim);
 }
 
 static void
-remove_connection (SettingsPluginIfcfg *self, NMIfcfgConnection *connection)
+_load_dir (NMSIfcfgRHPlugin *self,
+           NMSettUtilStorages *storages)
 {
-	SettingsPluginIfcfgPrivate *priv = SETTINGS_PLUGIN_IFCFG_GET_PRIVATE (self);
-	gboolean unmanaged, unrecognized;
+	gs_unref_hashtable GHashTable *dupl_filenames = NULL;
+	gs_free_error GError *local = NULL;
+	const char *f_filename;
+	GDir *dir;
 
-	g_return_if_fail (self != NULL);
-	g_return_if_fail (connection != NULL);
+	dir = g_dir_open (IFCFG_DIR, 0, &local);
+	if (!dir) {
+		_LOGT ("Could not read directory '%s': %s", IFCFG_DIR, local->message);
+		return;
+	}
 
-	_LOGI ("remove "NM_IFCFG_CONNECTION_LOG_FMT, NM_IFCFG_CONNECTION_LOG_ARG (connection));
+	dupl_filenames = g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, g_free);
 
-	unmanaged = !!nm_ifcfg_connection_get_unmanaged_spec (connection);
-	unrecognized = !!nm_ifcfg_connection_get_unrecognized_spec (connection);
+	while ((f_filename = g_dir_read_name (dir))) {
+		gs_free char *full_path = NULL;
+		NMSIfcfgRHStorage *storage;
+		char *full_filename;
 
-	g_object_ref (connection);
-	g_hash_table_remove (priv->connections, nm_settings_connection_get_uuid (NM_SETTINGS_CONNECTION (connection)));
-	if (!unmanaged && !unrecognized)
-		nm_settings_connection_signal_remove (NM_SETTINGS_CONNECTION (connection));
-	g_object_unref (connection);
+		full_path = g_build_filename (IFCFG_DIR, f_filename, NULL);
+		full_filename = utils_detect_ifcfg_path (full_path, TRUE);
+		if (!full_filename)
+			continue;
 
-	/* Emit changes _after_ removing the connection */
-	if (unmanaged)
-		_nm_settings_plugin_emit_signal_unmanaged_specs_changed (NM_SETTINGS_PLUGIN (self));
-	if (unrecognized)
-		_nm_settings_plugin_emit_signal_unrecognized_specs_changed (NM_SETTINGS_PLUGIN (self));
+		if (!g_hash_table_add (dupl_filenames, full_filename))
+			continue;
+
+		nm_assert (!nm_sett_util_storages_lookup_by_filename (storages, full_filename));
+
+		storage = _load_file (self,
+		                      full_filename,
+		                      NULL);
+		if (storage)
+			nm_sett_util_storages_add_take (storages, storage);
+	}
+	g_dir_close (dir);
 }
 
-static NMIfcfgConnection *
-find_by_path (SettingsPluginIfcfg *self, const char *path)
+static void
+_storages_consolidate (NMSIfcfgRHPlugin *self,
+                       NMSettUtilStorages *storages_new,
+                       gboolean replace_all,
+                       GHashTable *storages_replaced,
+                       NMSettingsPluginConnectionLoadCallback callback,
+                       gpointer user_data)
 {
-	SettingsPluginIfcfgPrivate *priv = SETTINGS_PLUGIN_IFCFG_GET_PRIVATE (self);
-	GHashTableIter iter;
-	NMSettingsConnection *candidate = NULL;
+	NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE (self);
+	CList lst_conn_info_deleted = C_LIST_INIT (lst_conn_info_deleted);
+	gs_unref_ptrarray GPtrArray *storages_modified = NULL;
+	CList storages_deleted;
+	NMSIfcfgRHStorage *storage_safe;
+	NMSIfcfgRHStorage *storage_new;
+	NMSIfcfgRHStorage *storage_old;
+	NMSIfcfgRHStorage *storage;
+	guint i;
 
-	g_return_val_if_fail (path != NULL, NULL);
+	/* when we reload all files, we must signal add/update/modify of profiles one-by-one.
+	 * NMSettings then goes ahead and emits further signals and a lot of things happen.
+	 *
+	 * So, first, emit an update of the unmanaged/unrecognized specs that contains *all*
+	 * the unmanaged/unrecognized devices from before and after. Since both unmanaged/unrecognized
+	 * specs have the meaning of "not doing something", it makes sense that we temporarily
+	 * disable that action for the sum of before and after. */
+	_unhandled_specs_merge_storages (self, storages_new);
+
+	storages_modified = g_ptr_array_new_with_free_func (g_object_unref);
+	c_list_init (&storages_deleted);
+
+	c_list_for_each_entry (storage_old, &priv->storages._storage_lst_head, parent._storage_lst)
+		storage_old->dirty = TRUE;
+
+	c_list_for_each_entry_safe (storage_new, storage_safe, &storages_new->_storage_lst_head, parent._storage_lst) {
+		storage_old = nm_sett_util_storages_lookup_by_filename (&priv->storages, nms_ifcfg_rh_storage_get_filename (storage_new));
+
+		nm_sett_util_storages_steal (storages_new, storage_new);
+
+		if (   !storage_old
+		    || !nms_ifcfg_rh_storage_equal_type (storage_new, storage_old)) {
+			if (storage_old) {
+				nm_sett_util_storages_steal (&priv->storages, storage_old);
+				if (nms_ifcfg_rh_storage_get_uuid_opt (storage_old))
+					c_list_link_tail (&storages_deleted, &storage_old->parent._storage_lst);
+				else
+					nms_ifcfg_rh_storage_destroy (storage_old);
+			}
+			storage_new->dirty = FALSE;
+			nm_sett_util_storages_add_take (&priv->storages, storage_new);
+			g_ptr_array_add (storages_modified, g_object_ref (storage_new));
+			continue;
+		}
 
-	g_hash_table_iter_init (&iter, priv->connections);
-	while (g_hash_table_iter_next (&iter, NULL, (gpointer) &candidate)) {
-		if (g_strcmp0 (path, nm_settings_connection_get_filename (candidate)) == 0)
-			return NM_IFCFG_CONNECTION (candidate);
+		storage_old->dirty = FALSE;
+		nms_ifcfg_rh_storage_copy_content (storage_old, storage_new);
+		nms_ifcfg_rh_storage_destroy (storage_new);
+		g_ptr_array_add (storages_modified, g_object_ref (storage_old));
 	}
-	return NULL;
-}
 
-static NMIfcfgConnection *
-update_connection (SettingsPluginIfcfg *self,
-                   NMConnection *source,
-                   const char *full_path,
-                   NMIfcfgConnection *connection,
-                   gboolean protect_existing_connection,
-                   GHashTable *protected_connections,
-                   GError **error)
-{
-	SettingsPluginIfcfgPrivate *priv = SETTINGS_PLUGIN_IFCFG_GET_PRIVATE (self);
-	NMIfcfgConnection *connection_new;
-	NMIfcfgConnection *connection_by_uuid;
-	GError *local = NULL;
-	const char *new_unmanaged = NULL, *old_unmanaged = NULL;
-	const char *new_unrecognized = NULL, *old_unrecognized = NULL;
-	gboolean unmanaged_changed = FALSE, unrecognized_changed = FALSE;
-	const char *uuid;
-	gboolean ignore_error = FALSE;
-
-	g_return_val_if_fail (!source || NM_IS_CONNECTION (source), NULL);
-	g_return_val_if_fail (full_path || source, NULL);
-
-	if (full_path)
-		_LOGD ("loading from file \"%s\"...", full_path);
-
-	/* Create a NMIfcfgConnection instance, either by reading from @full_path or
-	 * based on @source. */
-	connection_new = nm_ifcfg_connection_new (source, full_path, &local, &ignore_error);
-	if (!connection_new) {
-		/* Unexpected failure. Probably the file is invalid? */
-		if (   connection
-		    && !protect_existing_connection
-		    && (!protected_connections || !g_hash_table_contains (protected_connections, connection)))
-			remove_connection (self, connection);
-		if (!source) {
-			_NMLOG (ignore_error ? LOGL_DEBUG : LOGL_WARN,
-			        "loading \"%s\" fails: %s", full_path, local ? local->message : "(unknown reason)");
+	c_list_for_each_entry_safe (storage_old, storage_safe, &priv->storages._storage_lst_head, parent._storage_lst) {
+		if (!storage_old->dirty)
+			continue;
+		if (   replace_all
+		    || (   storages_replaced
+		        && g_hash_table_contains (storages_replaced, storage_old))) {
+			nm_sett_util_storages_steal (&priv->storages, storage_old);
+			if (nms_ifcfg_rh_storage_get_uuid_opt (storage_old))
+				c_list_link_tail (&storages_deleted, &storage_old->parent._storage_lst);
+			else
+				nms_ifcfg_rh_storage_destroy (storage_old);
 		}
-		g_propagate_error (error, local);
-		return NULL;
 	}
 
-	uuid = nm_settings_connection_get_uuid (NM_SETTINGS_CONNECTION (connection_new));
-	connection_by_uuid = g_hash_table_lookup (priv->connections, uuid);
+	/* raise events. */
 
-	if (   connection
-	    && connection != connection_by_uuid) {
+	for (i = 0; i < storages_modified->len; i++) {
+		storage = storages_modified->pdata[i];
+		storage->dirty = TRUE;
+	}
 
-		if (   (protect_existing_connection && connection_by_uuid != NULL)
-		    || (protected_connections && g_hash_table_contains (protected_connections, connection))) {
-			NMIfcfgConnection *conflicting = (protect_existing_connection && connection_by_uuid != NULL) ? connection_by_uuid : connection;
+	for (i = 0; i < storages_modified->len; i++) {
+		gs_unref_object NMConnection *connection = NULL;
+		storage = storages_modified->pdata[i];
 
-			if (source)
-				_LOGW ("cannot update protected connection "NM_IFCFG_CONNECTION_LOG_FMT" due to conflicting UUID %s", NM_IFCFG_CONNECTION_LOG_ARG (conflicting), uuid);
-			else
-				_LOGW ("cannot load %s due to conflicting UUID for "NM_IFCFG_CONNECTION_LOG_FMT, full_path, NM_IFCFG_CONNECTION_LOG_ARG (conflicting));
-			g_object_unref (connection_new);
-			g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
-			                     "Cannot update protected connection due to conflicting UUID");
-			return NULL;
+		if (!storage->dirty) {
+			/* the entry is no longer dirty. In the meantime we already emited
+			 * another signal for it. */
+			continue;
+		}
+		storage->dirty = FALSE;
+		if (storage != nm_sett_util_storages_lookup_by_filename (&priv->storages, nms_ifcfg_rh_storage_get_filename (storage))) {
+			/* hm? The profile was deleted in the meantime? That is only possible
+			 * if the signal handler called again into the plugin. In any case, the event
+			 * was already emitted. Skip. */
+			continue;
 		}
 
-		/* The new connection has a different UUID then the original one that we
-		 * are about to update. Remove @connection. */
-		remove_connection (self, connection);
+		connection = nms_ifcfg_rh_storage_steal_connection (storage);
+		if (!connection) {
+			nm_assert (!nms_ifcfg_rh_storage_get_uuid_opt (storage));
+			continue;
+		}
+
+		nm_assert (NM_IS_CONNECTION (connection));
+		nm_assert (nms_ifcfg_rh_storage_get_uuid_opt (storage));
+		callback (NM_SETTINGS_PLUGIN (self),
+		          NM_SETTINGS_STORAGE (storage),
+		          connection,
+		          user_data);
 	}
 
-	/* Check if the found connection with the same UUID is not protected from updating. */
-	if (   connection_by_uuid
-	    && (   (!connection && protect_existing_connection)
-	        || (protected_connections && g_hash_table_contains (protected_connections, connection_by_uuid)))) {
-		if (source)
-			_LOGW ("cannot update connection due to conflicting UUID for "NM_IFCFG_CONNECTION_LOG_FMT, NM_IFCFG_CONNECTION_LOG_ARG (connection_by_uuid));
-		else
-			_LOGW ("cannot load %s due to conflicting UUID for "NM_IFCFG_CONNECTION_LOG_FMT, full_path, NM_IFCFG_CONNECTION_LOG_ARG (connection_by_uuid));
-		g_object_unref (connection_new);
-		g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
-		                      "Skip updating protected connection during reload");
-		return NULL;
+	while ((storage = c_list_first_entry (&storages_deleted, NMSIfcfgRHStorage, parent._storage_lst))) {
+		c_list_unlink (&storage->parent._storage_lst);
+		callback (NM_SETTINGS_PLUGIN (self),
+		          NM_SETTINGS_STORAGE (storage),
+		          NULL,
+		          user_data);
+		nms_ifcfg_rh_storage_destroy (storage);
 	}
+}
 
-	/* Evaluate unmanaged/unrecognized flags. */
-	if (connection_by_uuid)
-		old_unmanaged = nm_ifcfg_connection_get_unmanaged_spec (connection_by_uuid);
-	new_unmanaged = nm_ifcfg_connection_get_unmanaged_spec (connection_new);
-	unmanaged_changed = g_strcmp0 (old_unmanaged, new_unmanaged);
-
-	if (connection_by_uuid)
-		old_unrecognized = nm_ifcfg_connection_get_unrecognized_spec (connection_by_uuid);
-	new_unrecognized = nm_ifcfg_connection_get_unrecognized_spec (connection_new);
-	unrecognized_changed = g_strcmp0 (old_unrecognized, new_unrecognized);
-
-	if (connection_by_uuid) {
-		const char *old_path;
-
-		old_path = nm_settings_connection_get_filename (NM_SETTINGS_CONNECTION (connection_by_uuid));
-
-		if (   !unmanaged_changed
-		    && !unrecognized_changed
-		    && nm_connection_compare (nm_settings_connection_get_connection (NM_SETTINGS_CONNECTION (connection_by_uuid)),
-		                              nm_settings_connection_get_connection (NM_SETTINGS_CONNECTION (connection_new)),
-		                              NM_SETTING_COMPARE_FLAG_IGNORE_AGENT_OWNED_SECRETS |
-		                              NM_SETTING_COMPARE_FLAG_IGNORE_NOT_SAVED_SECRETS)) {
-			if (   old_path
-			    && !nm_streq0 (old_path, full_path)) {
-				_LOGI ("rename \"%s\" to "NM_IFCFG_CONNECTION_LOG_FMT" without other changes",
-				       nm_settings_connection_get_filename (NM_SETTINGS_CONNECTION (connection_by_uuid)),
-				       NM_IFCFG_CONNECTION_LOG_ARG (connection_new));
-			}
-		} else {
+/*****************************************************************************/
+
+static void
+load_connections (NMSettingsPlugin *plugin,
+                  NMSettingsPluginConnectionLoadEntry *entries,
+                  gsize n_entries,
+                  NMSettingsPluginConnectionLoadCallback callback,
+                  gpointer user_data)
+{
+	NMSIfcfgRHPlugin *self = NMS_IFCFG_RH_PLUGIN (plugin);
+	NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE (self);
+	nm_auto_clear_sett_util_storages NMSettUtilStorages storages_new = NM_SETT_UTIL_STORAGES_INIT (storages_new, nms_ifcfg_rh_storage_destroy);
+	gs_unref_hashtable GHashTable *dupl_filenames = NULL;
+	gs_unref_hashtable GHashTable *storages_replaced = NULL;
+	gs_unref_hashtable GHashTable *loaded_uuids = NULL;
+	const char *loaded_uuid;
+	GHashTableIter h_iter;
+	gsize i;
+
+	if (n_entries == 0)
+		return;
 
-			/*******************************************************
-			 * UPDATE
-			 *******************************************************/
+	dupl_filenames = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, NULL);
 
-			if (source)
-				_LOGI ("update "NM_IFCFG_CONNECTION_LOG_FMT" from %s", NM_IFCFG_CONNECTION_LOG_ARG (connection_new), NM_IFCFG_CONNECTION_LOG_PATH (old_path));
-			else if (nm_streq0 (old_path, nm_settings_connection_get_filename (NM_SETTINGS_CONNECTION (connection_new))))
-				_LOGI ("update "NM_IFCFG_CONNECTION_LOG_FMT, NM_IFCFG_CONNECTION_LOG_ARG (connection_new));
-			else if (old_path)
-				_LOGI ("rename \"%s\" to "NM_IFCFG_CONNECTION_LOG_FMT, old_path, NM_IFCFG_CONNECTION_LOG_ARG (connection_new));
-			else
-				_LOGI ("update and persist "NM_IFCFG_CONNECTION_LOG_FMT, NM_IFCFG_CONNECTION_LOG_ARG (connection_new));
-
-			g_object_set (connection_by_uuid,
-			              NM_IFCFG_CONNECTION_UNMANAGED_SPEC, new_unmanaged,
-			              NM_IFCFG_CONNECTION_UNRECOGNIZED_SPEC, new_unrecognized,
-			              NULL);
-
-			if (!nm_settings_connection_update (NM_SETTINGS_CONNECTION (connection_by_uuid),
-			                                    nm_settings_connection_get_connection (NM_SETTINGS_CONNECTION (connection_new)),
-			                                    NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP_SAVED,
-			                                    NM_SETTINGS_CONNECTION_COMMIT_REASON_NONE,
-			                                    "ifcfg-update",
-			                                    &local)) {
-				/* Shouldn't ever get here as 'connection_new' was verified by the reader already
-				 * and the UUID did not change. */
-				g_assert_not_reached ();
-			}
-			g_assert_no_error (local);
-
-			if (new_unmanaged || new_unrecognized) {
-				if (!old_unmanaged && !old_unrecognized) {
-					/* ref connection first, because we put it into priv->connections below.
-					 * Emitting signal-removed might otherwise delete it. */
-					g_object_ref (connection_by_uuid);
-
-					/* Unexport the connection by telling the settings service it's
-					 * been removed.
-					 */
-					nm_settings_connection_signal_remove (NM_SETTINGS_CONNECTION (connection_by_uuid));
-
-					/* signal_remove() will end up removing the connection from our hash,
-					 * so add it back now.
-					 */
-					g_hash_table_insert (priv->connections,
-					                     g_strdup (nm_settings_connection_get_uuid (NM_SETTINGS_CONNECTION (connection_by_uuid))),
-					                     connection_by_uuid /* we took reference above and pass it on */);
-				}
-			} else {
-				if (old_unmanaged /* && !new_unmanaged */) {
-					_LOGI ("Managing connection "NM_IFCFG_CONNECTION_LOG_FMT" and its device because NM_CONTROLLED was true.",
-					       NM_IFCFG_CONNECTION_LOG_ARG (connection_new));
-					_nm_settings_plugin_emit_signal_connection_added (NM_SETTINGS_PLUGIN (self),
-					                                                  NM_SETTINGS_CONNECTION (connection_by_uuid));
-				} else if (old_unrecognized /* && !new_unrecognized */) {
-					_LOGI ("Managing connection "NM_IFCFG_CONNECTION_LOG_FMT" because it is now a recognized type.",
-					       NM_IFCFG_CONNECTION_LOG_ARG (connection_new));
-					_nm_settings_plugin_emit_signal_connection_added (NM_SETTINGS_PLUGIN (self),
-					                                                  NM_SETTINGS_CONNECTION (connection_by_uuid));
-				}
-			}
+	loaded_uuids = g_hash_table_new (nm_str_hash, g_str_equal);
+
+	storages_replaced = g_hash_table_new_full (nm_direct_hash, NULL, g_object_unref, NULL);
+
+	for (i = 0; i < n_entries; i++) {
+		NMSettingsPluginConnectionLoadEntry *const entry = &entries[i];
+		gs_free_error GError *local = NULL;
+		const char *full_filename;
+		const char *uuid;
+		gs_free char *full_filename_keep = NULL;
+		NMSettingsPluginConnectionLoadEntry *dupl_content_entry;
+		gs_unref_object NMSIfcfgRHStorage *storage = NULL;
+
+		if (entry->handled)
+			continue;
 
-			if (unmanaged_changed)
-				_nm_settings_plugin_emit_signal_unmanaged_specs_changed (NM_SETTINGS_PLUGIN (self));
-			if (unrecognized_changed)
-				_nm_settings_plugin_emit_signal_unrecognized_specs_changed (NM_SETTINGS_PLUGIN (self));
+		if (entry->filename[0] != '/')
+			continue;
+
+		full_filename_keep = utils_detect_ifcfg_path (entry->filename, FALSE);
+
+		if (!full_filename_keep) {
+			if (nm_utils_file_is_in_path (entry->filename, IFCFG_DIR)) {
+				nm_utils_error_set (&entry->error,
+				                    NM_UTILS_ERROR_UNKNOWN,
+				                    ("path is not a valid name for an ifcfg-rh file"));
+				entry->handled = TRUE;
+			}
+			continue;
 		}
-		nm_settings_connection_set_filename (NM_SETTINGS_CONNECTION (connection_by_uuid), full_path);
-		g_object_unref (connection_new);
-		return connection_by_uuid;
-	} else {
 
-		/*******************************************************
-		 * ADD
-		 *******************************************************/
+		if ((dupl_content_entry = g_hash_table_lookup (dupl_filenames, full_filename_keep))) {
+			/* we already visited this file. */
+			entry->handled = dupl_content_entry->handled;
+			if (dupl_content_entry->error) {
+				g_set_error_literal (&entry->error,
+				                     dupl_content_entry->error->domain,
+				                     dupl_content_entry->error->code,
+				                     dupl_content_entry->error->message);
+			}
+			continue;
+		}
 
-		if (source)
-			_LOGI ("add connection "NM_IFCFG_CONNECTION_LOG_FMT, NM_IFCFG_CONNECTION_LOG_ARG (connection_new));
-		else
-			_LOGI ("new connection "NM_IFCFG_CONNECTION_LOG_FMT, NM_IFCFG_CONNECTION_LOG_ARG (connection_new));
-		g_hash_table_insert (priv->connections,
-		                     g_strdup (uuid),
-		                     connection_new /* take reference */);
-
-		g_signal_connect (connection_new, NM_SETTINGS_CONNECTION_REMOVED,
-		                  G_CALLBACK (connection_removed_cb),
-		                  self);
-
-		if (nm_ifcfg_connection_get_unmanaged_spec (connection_new)) {
-			_LOGI ("Ignoring connection "NM_IFCFG_CONNECTION_LOG_FMT" due to NM_CONTROLLED=no. Unmanaged: %s.",
-			       NM_IFCFG_CONNECTION_LOG_ARG (connection_new),
-			       nm_ifcfg_connection_get_unmanaged_spec (connection_new));
-		} else if (nm_ifcfg_connection_get_unrecognized_spec (connection_new))
-			_LOGW ("Ignoring connection "NM_IFCFG_CONNECTION_LOG_FMT" of unrecognized type.", NM_IFCFG_CONNECTION_LOG_ARG (connection_new));
-
-		/* watch changes of ifcfg hardlinks */
-		g_signal_connect (G_OBJECT (connection_new), "ifcfg-changed",
-		                  G_CALLBACK (connection_ifcfg_changed), self);
-
-		if (!source) {
-			/* Only raise the signal if we were called without source, i.e. if we read the connection from file.
-			 * Otherwise, we were called by add_connection() which does not expect the signal. */
-			if (nm_ifcfg_connection_get_unmanaged_spec (connection_new))
-				_nm_settings_plugin_emit_signal_unmanaged_specs_changed (NM_SETTINGS_PLUGIN (self));
-			else if (nm_ifcfg_connection_get_unrecognized_spec (connection_new))
-				_nm_settings_plugin_emit_signal_unrecognized_specs_changed (NM_SETTINGS_PLUGIN (self));
-			else {
-				_nm_settings_plugin_emit_signal_connection_added (NM_SETTINGS_PLUGIN (self),
-				                                                  NM_SETTINGS_CONNECTION (connection_new));
+		entry->handled = TRUE;
+
+		full_filename = full_filename_keep;
+		if (!g_hash_table_insert (dupl_filenames, g_steal_pointer (&full_filename_keep), entry))
+			nm_assert_not_reached ();
+
+		storage = _load_file (self,
+		                      full_filename,
+		                      &local);
+		if (!storage) {
+			if (nm_utils_file_stat (full_filename, NULL) == -ENOENT) {
+				NMSIfcfgRHStorage *storage2;
+
+				/* the file does not exist. We take that as indication to unload the file
+				 * that was previously loaded... */
+				storage2 = nm_sett_util_storages_lookup_by_filename (&priv->storages, full_filename);
+				if (storage2)
+					g_hash_table_add (storages_replaced, g_object_ref (storage2));
+				continue;
 			}
+			g_propagate_error (&entry->error, g_steal_pointer (&local));
+			continue;
 		}
-		return connection_new;
+
+		uuid = nms_ifcfg_rh_storage_get_uuid_opt (storage);
+		if (uuid)
+			g_hash_table_add (loaded_uuids, (char *) uuid);
+
+		nm_sett_util_storages_add_take (&storages_new, g_steal_pointer (&storage));
 	}
-}
 
-static void
-ifcfg_dir_changed (GFileMonitor *monitor,
-                   GFile *file,
-                   GFile *other_file,
-                   GFileMonitorEvent event_type,
-                   gpointer user_data)
-{
-	SettingsPluginIfcfg *plugin = SETTINGS_PLUGIN_IFCFG (user_data);
-	char *path, *ifcfg_path;
-	NMIfcfgConnection *connection;
-
-	path = g_file_get_path (file);
-
-	ifcfg_path = utils_detect_ifcfg_path (path, FALSE);
-	_LOGD ("ifcfg_dir_changed(%s) = %d // %s", path, event_type, ifcfg_path ?: "(none)");
-	if (ifcfg_path) {
-		connection = find_by_path (plugin, ifcfg_path);
-		switch (event_type) {
-		case G_FILE_MONITOR_EVENT_DELETED:
-			if (connection)
-				remove_connection (plugin, connection);
-			break;
-		case G_FILE_MONITOR_EVENT_CREATED:
-		case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
-			/* Update or new */
-			update_connection (plugin, NULL, ifcfg_path, connection, TRUE, NULL, NULL);
-			break;
-		default:
-			break;
+	/* now we visit all UUIDs that are about to change... */
+	g_hash_table_iter_init (&h_iter, loaded_uuids);
+	while (g_hash_table_iter_next (&h_iter, (gpointer *) &loaded_uuid, NULL)) {
+		NMSIfcfgRHStorage *storage;
+		NMSettUtilStorageByUuidHead *sbuh;
+
+		sbuh = nm_sett_util_storages_lookup_by_uuid (&priv->storages, loaded_uuid);
+		if (!sbuh)
+			continue;
+
+		c_list_for_each_entry (storage, &sbuh->_storage_by_uuid_lst_head, parent._storage_by_uuid_lst) {
+			const char *full_filename = nms_ifcfg_rh_storage_get_filename (storage);
+			gs_unref_object NMSIfcfgRHStorage *storage_new = NULL;
+			gs_free_error GError *local = NULL;
+
+			if (g_hash_table_contains (dupl_filenames, full_filename)) {
+				/* already re-loaded. */
+				continue;
+			}
+
+			/* @storage has a UUID that was just loaded from disk, but we have an entry in cache.
+			 * Reload that file too despite not being told to do so. The reason is to get
+			 * the latest file timestamp so that we get the priorities right. */
+
+			storage_new = _load_file (self,
+			                          full_filename,
+			                          &local);
+			if (   storage_new
+			    && !nm_streq0 (loaded_uuid, nms_ifcfg_rh_storage_get_uuid_opt (storage_new))) {
+				/* the file now references a different UUID. We are not told to reload
+				 * that file, so this means the existing storage (with the previous
+				 * filename and UUID tuple) is no longer valid. */
+				g_clear_object (&storage_new);
+			}
+
+			g_hash_table_add (storages_replaced, g_object_ref (storage));
+			if (storage_new)
+				nm_sett_util_storages_add_take (&storages_new, g_steal_pointer (&storage_new));
 		}
-		g_free (ifcfg_path);
 	}
-	g_free (path);
+
+	nm_clear_pointer (&loaded_uuids, g_hash_table_destroy);
+	nm_clear_pointer (&dupl_filenames, g_hash_table_destroy);
+
+	_storages_consolidate (self,
+	                       &storages_new,
+	                       FALSE,
+	                       storages_replaced,
+	                       callback,
+	                       user_data);
 }
 
 static void
-setup_ifcfg_monitoring (SettingsPluginIfcfg *plugin)
+reload_connections (NMSettingsPlugin *plugin,
+                    NMSettingsPluginConnectionLoadCallback callback,
+                    gpointer user_data)
 {
-	SettingsPluginIfcfgPrivate *priv = SETTINGS_PLUGIN_IFCFG_GET_PRIVATE (plugin);
-	GFile *file;
-	GFileMonitor *monitor;
-
-	file = g_file_new_for_path (IFCFG_DIR "/");
-	monitor = g_file_monitor_directory (file, G_FILE_MONITOR_NONE, NULL, NULL);
-	g_object_unref (file);
-
-	if (monitor) {
-		priv->ifcfg_monitor_id = g_signal_connect (monitor, "changed",
-		                                           G_CALLBACK (ifcfg_dir_changed), plugin);
-		priv->ifcfg_monitor = monitor;
-	}
-}
+	NMSIfcfgRHPlugin *self = NMS_IFCFG_RH_PLUGIN (plugin);
+	nm_auto_clear_sett_util_storages NMSettUtilStorages storages_new = NM_SETT_UTIL_STORAGES_INIT (storages_new, nms_ifcfg_rh_storage_destroy);
 
-static GHashTable *
-_paths_from_connections (GHashTable *connections)
-{
-	GHashTableIter iter;
-	NMIfcfgConnection *connection;
-	GHashTable *paths = g_hash_table_new (nm_str_hash, g_str_equal);
+	nm_assert_self (self, TRUE);
 
-	g_hash_table_iter_init (&iter, connections);
-	while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &connection)) {
-		const char *path = nm_settings_connection_get_filename (NM_SETTINGS_CONNECTION (connection));
+	_load_dir (self, &storages_new);
 
-		if (path)
-			g_hash_table_add (paths, (void *) path);
-	}
-	return paths;
+	_storages_consolidate (self,
+	                       &storages_new,
+	                       TRUE,
+	                       NULL,
+	                       callback,
+	                       user_data);
+
+	nm_assert_self (self, FALSE);
 }
 
-static int
-_sort_paths (const char **f1, const char **f2, GHashTable *paths)
+static void
+load_connections_done (NMSettingsPlugin *plugin)
 {
-	struct stat st;
-	gboolean c1, c2;
-	gint64 m1, m2;
-
-	c1 = !!g_hash_table_contains (paths, *f1);
-	c2 = !!g_hash_table_contains (paths, *f2);
-	if (c1 != c2)
-		return c1 ? -1 : 1;
+	NMSIfcfgRHPlugin *self = NMS_IFCFG_RH_PLUGIN (plugin);
 
-	m1 = stat (*f1, &st) == 0 ? (gint64) st.st_mtime : G_MININT64;
-	m2 = stat (*f2, &st) == 0 ? (gint64) st.st_mtime : G_MININT64;
-	if (m1 != m2)
-		return m1 > m2 ? -1 : 1;
+	/* at the beginning of a load, we emit a change signal for unmanaged/unrecognized
+	 * specs that contain the sum of before and after (_unhandled_specs_merge_storages()).
+	 *
+	 * The idea is that while we emit signals about changes to connection, we have
+	 * the sum of all unmanaged/unrecognized devices from before and after.
+	 *
+	 * This if triggered at the end, to reset the specs. */
+	_unhandled_specs_reset (self);
 
-	return strcmp (*f1, *f2);
+	nm_assert_self (self, TRUE);
 }
 
-static void
-read_connections (SettingsPluginIfcfg *plugin)
-{
-	SettingsPluginIfcfgPrivate *priv = SETTINGS_PLUGIN_IFCFG_GET_PRIVATE (plugin);
-	GDir *dir;
-	GError *err = NULL;
-	const char *item;
-	GHashTable *alive_connections;
-	GHashTableIter iter;
-	NMIfcfgConnection *connection;
-	GPtrArray *dead_connections = NULL;
-	guint i;
-	GPtrArray *filenames;
-	GHashTable *paths;
+/*****************************************************************************/
 
-	dir = g_dir_open (IFCFG_DIR, 0, &err);
-	if (!dir) {
-		_LOGW ("Could not read directory '%s': %s", IFCFG_DIR, err->message);
-		g_error_free (err);
-		return;
+static gboolean
+add_connection (NMSettingsPlugin *plugin,
+                NMConnection *connection,
+                NMSettingsStorage **out_storage,
+                NMConnection **out_connection,
+                GError **error)
+{
+	NMSIfcfgRHPlugin *self = NMS_IFCFG_RH_PLUGIN (plugin);
+	NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE (self);
+	gs_unref_object NMSIfcfgRHStorage *storage = NULL;
+	gs_unref_object NMConnection *reread = NULL;
+	gs_free char *full_filename = NULL;
+	GError *local = NULL;
+	gboolean reread_same;
+	struct timespec mtime;
+
+	nm_assert_self (self, TRUE);
+	nm_assert (NM_IS_CONNECTION (connection));
+	nm_assert (out_storage && !*out_storage);
+	nm_assert (out_connection && !*out_connection);
+
+	if (!nms_ifcfg_rh_writer_write_connection (connection,
+	                                           IFCFG_DIR,
+	                                           NULL,
+	                                           nm_sett_util_allow_filename_cb,
+	                                           NM_SETT_UTIL_ALLOW_FILENAME_DATA (&priv->storages, NULL),
+	                                           &full_filename,
+	                                           &reread,
+	                                           &reread_same,
+	                                           &local)) {
+		_LOGT ("commit: %s (%s): failed to add: %s",
+		       nm_connection_get_uuid (connection),
+		       nm_connection_get_id (connection),
+		       local->message);
+		g_propagate_error (error, local);
+		return FALSE;
 	}
 
-	alive_connections = g_hash_table_new (nm_direct_hash, NULL);
+	if (   !reread
+	    || reread_same)
+		nm_g_object_ref_set (&reread, connection);
 
-	filenames = g_ptr_array_new_with_free_func (g_free);
-	while ((item = g_dir_read_name (dir))) {
-		char *full_path, *real_path;
+	nm_assert (full_filename && full_filename[0] == '/');
 
-		full_path = g_build_filename (IFCFG_DIR, item, NULL);
-		real_path = utils_detect_ifcfg_path (full_path, TRUE);
+	_LOGT ("commit: %s (%s) added as \"%s\"",
+	       nm_connection_get_uuid (reread),
+	       nm_connection_get_id (reread),
+	       full_filename);
 
-		if (real_path)
-			g_ptr_array_add (filenames, real_path);
-		g_free (full_path);
-	}
-	g_dir_close (dir);
+	storage = nms_ifcfg_rh_storage_new_connection (self,
+	                                               full_filename,
+	                                               g_steal_pointer (&reread),
+	                                               nm_sett_util_stat_mtime (full_filename, FALSE, &mtime));
 
-	/* While reloading, we don't replace connections that we already loaded while
-	 * iterating over the files.
-	 *
-	 * To have sensible, reproducible behavior, sort the paths by last modification
-	 * time preferring older files.
-	 */
-	paths = _paths_from_connections (priv->connections);
-	g_ptr_array_sort_with_data (filenames, (GCompareDataFunc) _sort_paths, paths);
-	g_hash_table_destroy (paths);
-
-	for (i = 0; i < filenames->len; i++) {
-		connection = update_connection (plugin, NULL, filenames->pdata[i], NULL, FALSE, alive_connections, NULL);
-		if (connection)
-			g_hash_table_add (alive_connections, connection);
-	}
-	g_ptr_array_free (filenames, TRUE);
-
-	g_hash_table_iter_init (&iter, priv->connections);
-	while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &connection)) {
-		if (   !g_hash_table_contains (alive_connections, connection)
-		    && nm_settings_connection_get_filename (NM_SETTINGS_CONNECTION (connection))) {
-			if (!dead_connections)
-				dead_connections = g_ptr_array_new ();
-			g_ptr_array_add (dead_connections, connection);
-		}
-	}
-	g_hash_table_destroy (alive_connections);
+	nm_sett_util_storages_add_take (&priv->storages, g_object_ref (storage));
 
-	if (dead_connections) {
-		for (i = 0; i < dead_connections->len; i++)
-			remove_connection (plugin, dead_connections->pdata[i]);
-		g_ptr_array_free (dead_connections, TRUE);
-	}
+	*out_connection = nms_ifcfg_rh_storage_steal_connection (storage);
+	*out_storage = NM_SETTINGS_STORAGE (g_steal_pointer (&storage));
+
+	nm_assert_self (self, TRUE);
+
+	return TRUE;
 }
 
-static GSList *
-get_connections (NMSettingsPlugin *config)
+static gboolean
+update_connection (NMSettingsPlugin *plugin,
+                   NMSettingsStorage *storage_x,
+                   NMConnection *connection,
+                   NMSettingsStorage **out_storage,
+                   NMConnection **out_connection,
+                   GError **error)
 {
-	SettingsPluginIfcfg *plugin = SETTINGS_PLUGIN_IFCFG (config);
-	SettingsPluginIfcfgPrivate *priv = SETTINGS_PLUGIN_IFCFG_GET_PRIVATE (plugin);
-	GSList *list = NULL;
-	GHashTableIter iter;
-	NMIfcfgConnection *connection;
-
-	if (!priv->initialized) {
-		if (nm_config_get_monitor_connection_files (nm_config_get ()))
-			setup_ifcfg_monitoring (plugin);
-		read_connections (plugin);
-		priv->initialized = TRUE;
+	NMSIfcfgRHPlugin *self = NMS_IFCFG_RH_PLUGIN (plugin);
+	NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE (self);
+	NMSIfcfgRHStorage *storage = NMS_IFCFG_RH_STORAGE (storage_x);
+	const char *full_filename;
+	const char *uuid;
+	GError *local = NULL;
+	gs_unref_object NMConnection *reread = NULL;
+	gboolean reread_same;
+	struct timespec mtime;
+
+	nm_assert_self (self, TRUE);
+	nm_assert (NM_IS_CONNECTION (connection));
+	nm_assert (NMS_IS_IFCFG_RH_STORAGE (storage));
+	nm_assert (_nm_connection_verify (connection, NULL) == NM_SETTING_VERIFY_SUCCESS);
+	nm_assert (!error || !*error);
+
+	uuid = nms_ifcfg_rh_storage_get_uuid_opt (storage);
+
+	nm_assert (uuid && nm_streq0 (uuid, nm_connection_get_uuid (connection)));
+
+	full_filename = nms_ifcfg_rh_storage_get_filename (storage);
+
+	nm_assert (full_filename);
+	nm_assert (storage == nm_sett_util_storages_lookup_by_filename (&priv->storages, full_filename));
+
+	if (!nms_ifcfg_rh_writer_write_connection (connection,
+	                                           IFCFG_DIR,
+	                                           full_filename,
+	                                           nm_sett_util_allow_filename_cb,
+	                                           NM_SETT_UTIL_ALLOW_FILENAME_DATA (&priv->storages, full_filename),
+	                                           NULL,
+	                                           &reread,
+	                                           &reread_same,
+	                                           &local)) {
+		_LOGT ("commit: failure to write %s (%s) to \"%s\": %s",
+		       nm_connection_get_uuid (connection),
+		       nm_connection_get_id (connection),
+		       full_filename,
+		       local->message);
+		g_propagate_error (error, local);
+		return FALSE;
 	}
 
-	g_hash_table_iter_init (&iter, priv->connections);
-	while (g_hash_table_iter_next (&iter, NULL, (gpointer) &connection)) {
-		if (   !nm_ifcfg_connection_get_unmanaged_spec (connection)
-		    && !nm_ifcfg_connection_get_unrecognized_spec (connection))
-			list = g_slist_prepend (list, connection);
-	}
+	if (   !reread
+	    || reread_same)
+		nm_g_object_ref_set (&reread, connection);
 
-	return list;
+	_LOGT ("commit: \"%s\": profile %s (%s) written",
+	       full_filename,
+	       uuid,
+	       nm_connection_get_id (connection));
+
+	storage->stat_mtime = *nm_sett_util_stat_mtime (full_filename, FALSE, &mtime);
+
+	*out_storage = NM_SETTINGS_STORAGE (g_object_ref (storage));
+	*out_connection = g_steal_pointer (&reread);
+
+	nm_assert_self (self, TRUE);
+
+	return TRUE;
 }
 
 static gboolean
-load_connection (NMSettingsPlugin *config,
-                 const char *filename)
+delete_connection (NMSettingsPlugin *plugin,
+                   NMSettingsStorage *storage_x,
+                   GError **error)
 {
-	SettingsPluginIfcfg *plugin = SETTINGS_PLUGIN_IFCFG (config);
-	NMIfcfgConnection *connection;
-	char *ifcfg_path;
+	NMSIfcfgRHPlugin *self = NMS_IFCFG_RH_PLUGIN (plugin);
+	NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE (self);
+	NMSIfcfgRHStorage *storage = NMS_IFCFG_RH_STORAGE (storage_x);
+	const char *operation_message;
+	const char *full_filename;
 
-	if (!nm_utils_file_is_in_path (filename, IFCFG_DIR))
-		return FALSE;
+	nm_assert_self (self, TRUE);
+	nm_assert (!error || !*error);
+	nm_assert (NMS_IS_IFCFG_RH_STORAGE (storage));
 
-	/* get the real ifcfg-path. This allows us to properly
-	 * handle load command using a route-* file etc. */
-	ifcfg_path = utils_detect_ifcfg_path (filename, FALSE);
-	if (!ifcfg_path)
-		return FALSE;
+	full_filename = nms_ifcfg_rh_storage_get_filename (storage);
+	nm_assert (full_filename);
+
+	nm_assert (nms_ifcfg_rh_storage_get_uuid_opt (storage));
+
+	nm_assert (storage == nm_sett_util_storages_lookup_by_filename (&priv->storages, full_filename));
+
+	{
+		gs_free char *keyfile = utils_get_keys_path (full_filename);
+		gs_free char *routefile = utils_get_route_path (full_filename);
+		gs_free char *route6file = utils_get_route6_path (full_filename);
+		const char *const files[] = { full_filename, keyfile, routefile, route6file };
+		gboolean any_deleted = FALSE;
+		gboolean any_failure = FALSE;
+		int i;
+
+		for (i = 0; i < G_N_ELEMENTS (files); i++) {
+			int errsv;
+
+			if (unlink (files[i]) == 0) {
+				any_deleted = TRUE;
+				continue;
+			}
+			errsv = errno;
+			if (errsv == ENOENT)
+				continue;
+
+			_LOGW ("commit: failure to delete file \"%s\": %s",
+			       files[i],
+			       nm_strerror_native (errsv));
+			any_failure = TRUE;
+		}
+		if (any_failure)
+			operation_message = "failed to delete files from disk";
+		else if (any_deleted)
+			operation_message = "deleted from disk";
+		else
+			operation_message = "does not exist on disk";
+	}
+
+	_LOGT ("commit: deleted \"%s\", profile %s (%s)",
+	       full_filename,
+	       nms_ifcfg_rh_storage_get_uuid_opt (storage),
+	       operation_message);
 
-	connection = find_by_path (plugin, ifcfg_path);
-	update_connection (plugin, NULL, ifcfg_path, connection, TRUE, NULL, NULL);
-	if (!connection)
-		connection = find_by_path (plugin, ifcfg_path);
+	nm_sett_util_storages_steal (&priv->storages, storage);
+	nms_ifcfg_rh_storage_destroy (storage);
 
-	g_free (ifcfg_path);
-	return (connection != NULL);
+	nm_assert_self (self, TRUE);
+
+	return TRUE;
 }
 
+/*****************************************************************************/
+
 static void
-reload_connections (NMSettingsPlugin *config)
+_unhandled_specs_reset (NMSIfcfgRHPlugin *self)
 {
-	SettingsPluginIfcfg *plugin = SETTINGS_PLUGIN_IFCFG (config);
+	NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE (self);
+	gs_unref_hashtable GHashTable *unmanaged_specs = NULL;
+	gs_unref_hashtable GHashTable *unrecognized_specs = NULL;
+	NMSIfcfgRHStorage *storage;
+
+	unmanaged_specs = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, NULL);
+	unrecognized_specs = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, NULL);
+
+	c_list_for_each_entry (storage, &priv->storages._storage_lst_head, parent._storage_lst) {
+		if (storage->unmanaged_spec)
+			g_hash_table_add (unmanaged_specs, g_strdup (storage->unmanaged_spec));
+		if (storage->unrecognized_spec)
+			g_hash_table_add (unrecognized_specs, g_strdup (storage->unrecognized_spec));
+	}
 
-	read_connections (plugin);
+	if (!nm_utils_hashtable_same_keys (unmanaged_specs, priv->unmanaged_specs)) {
+		g_hash_table_unref (priv->unmanaged_specs);
+		priv->unmanaged_specs = g_steal_pointer (&unmanaged_specs);
+	}
+	if (!nm_utils_hashtable_same_keys (unrecognized_specs, priv->unrecognized_specs)) {
+		g_hash_table_unref (priv->unrecognized_specs);
+		priv->unrecognized_specs = g_steal_pointer (&unrecognized_specs);
+	}
+
+	if (!unmanaged_specs)
+		_nm_settings_plugin_emit_signal_unmanaged_specs_changed (NM_SETTINGS_PLUGIN (self));
+	if (!unrecognized_specs)
+		_nm_settings_plugin_emit_signal_unrecognized_specs_changed (NM_SETTINGS_PLUGIN (self));
 }
 
-static GSList *
-get_unhandled_specs (NMSettingsPlugin *config,
-                     const char *property)
+static void
+_unhandled_specs_merge_storages (NMSIfcfgRHPlugin *self,
+                                 NMSettUtilStorages *storages)
 {
-	SettingsPluginIfcfgPrivate *priv = SETTINGS_PLUGIN_IFCFG_GET_PRIVATE ((SettingsPluginIfcfg *) config);
-	GSList *list = NULL, *list_iter;
-	GHashTableIter iter;
-	gpointer connection;
-	char *spec;
-	gboolean found;
-
-	g_hash_table_iter_init (&iter, priv->connections);
-	while (g_hash_table_iter_next (&iter, NULL, &connection)) {
-		g_object_get (connection, property, &spec, NULL);
-		if (spec) {
-			/* Ignore duplicates */
-			for (list_iter = list, found = FALSE; list_iter; list_iter = g_slist_next (list_iter)) {
-				if (g_str_equal (list_iter->data, spec)) {
-					found = TRUE;
-					break;
-				}
-			}
-			if (found)
-				g_free (spec);
-			else
-				list = g_slist_prepend (list, spec);
+	NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE (self);
+	gboolean unmanaged_changed = FALSE;
+	gboolean unrecognized_changed = FALSE;
+	NMSIfcfgRHStorage *storage;
+
+	c_list_for_each_entry (storage, &storages->_storage_lst_head, parent._storage_lst) {
+		if (   storage->unmanaged_spec
+		    && !g_hash_table_contains (priv->unmanaged_specs, storage->unmanaged_spec)) {
+			unmanaged_changed = TRUE;
+			g_hash_table_add (priv->unmanaged_specs, g_strdup (storage->unmanaged_spec));
+		}
+		if (   storage->unrecognized_spec
+		    && !g_hash_table_contains (priv->unrecognized_specs, storage->unrecognized_spec)) {
+			unrecognized_changed = TRUE;
+			g_hash_table_add (priv->unrecognized_specs, g_strdup (storage->unrecognized_spec));
 		}
 	}
-	return list;
+
+	if (unmanaged_changed)
+		_nm_settings_plugin_emit_signal_unmanaged_specs_changed (NM_SETTINGS_PLUGIN (self));
+	if (unrecognized_changed)
+		_nm_settings_plugin_emit_signal_unrecognized_specs_changed (NM_SETTINGS_PLUGIN (self));
 }
 
 static GSList *
-get_unmanaged_specs (NMSettingsPlugin *config)
+_unhandled_specs_from_hashtable (GHashTable *hash)
 {
-	return get_unhandled_specs (config, NM_IFCFG_CONNECTION_UNMANAGED_SPEC);
+	gs_free const char **keys = NULL;
+	GSList *list = NULL;
+	guint i, l;
+
+	keys = nm_utils_strdict_get_keys (hash, TRUE, &l);
+	for (i = l; i > 0; ) {
+		i--;
+		list = g_slist_prepend (list, g_strdup (keys[i]));
+	}
+	return list;
 }
 
 static GSList *
-get_unrecognized_specs (NMSettingsPlugin *config)
+get_unmanaged_specs (NMSettingsPlugin *plugin)
 {
-	return get_unhandled_specs (config, NM_IFCFG_CONNECTION_UNRECOGNIZED_SPEC);
+	return _unhandled_specs_from_hashtable (NMS_IFCFG_RH_PLUGIN_GET_PRIVATE (plugin)->unmanaged_specs);
 }
 
-static NMSettingsConnection *
-add_connection (NMSettingsPlugin *config,
-                NMConnection *connection,
-                gboolean save_to_disk,
-                GError **error)
+static GSList *
+get_unrecognized_specs (NMSettingsPlugin *plugin)
 {
-	SettingsPluginIfcfg *self = SETTINGS_PLUGIN_IFCFG (config);
-	gs_free char *path = NULL;
-	gs_unref_object NMConnection *reread = NULL;
-
-	if (save_to_disk) {
-		if (!nms_ifcfg_rh_writer_write_connection (connection, IFCFG_DIR, NULL, &path, &reread, NULL, error))
-			return NULL;
-	} else {
-		if (!nms_ifcfg_rh_writer_can_write_connection (connection, error))
-			return NULL;
-	}
-	return NM_SETTINGS_CONNECTION (update_connection (self, reread ?: connection, path, NULL, FALSE, NULL, error));
+	return _unhandled_specs_from_hashtable (NMS_IFCFG_RH_PLUGIN_GET_PRIVATE (plugin)->unrecognized_specs);
 }
 
+/*****************************************************************************/
+
 static void
-impl_ifcfgrh_get_ifcfg_details (SettingsPluginIfcfg *plugin,
+impl_ifcfgrh_get_ifcfg_details (NMSIfcfgRHPlugin *self,
                                 GDBusMethodInvocation *context,
                                 const char *in_ifcfg)
 {
-	NMIfcfgConnection *connection;
-	NMSettingConnection *s_con;
+	NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE (self);
+	gs_free char *ifcfg_path = NULL;
+	NMSIfcfgRHStorage *storage;
 	const char *uuid;
 	const char *path;
-	gs_free char *ifcfg_path = NULL;
 
-	if (!g_path_is_absolute (in_ifcfg)) {
+	if (in_ifcfg[0] != '/') {
 		g_dbus_method_invocation_return_error (context,
 		                                       NM_SETTINGS_ERROR,
 		                                       NM_SETTINGS_ERROR_INVALID_CONNECTION,
@@ -719,10 +916,8 @@ impl_ifcfgrh_get_ifcfg_details (SettingsPluginIfcfg *plugin,
 		return;
 	}
 
-	connection = find_by_path (plugin, ifcfg_path);
-	if (   !connection
-	    || nm_ifcfg_connection_get_unmanaged_spec (connection)
-	    || nm_ifcfg_connection_get_unrecognized_spec (connection)) {
+	storage = nm_sett_util_storages_lookup_by_filename (&priv->storages, ifcfg_path);
+	if (!storage) {
 		g_dbus_method_invocation_return_error (context,
 		                                       NM_SETTINGS_ERROR,
 		                                       NM_SETTINGS_ERROR_INVALID_CONNECTION,
@@ -730,25 +925,23 @@ impl_ifcfgrh_get_ifcfg_details (SettingsPluginIfcfg *plugin,
 		return;
 	}
 
-	s_con = nm_connection_get_setting_connection (nm_settings_connection_get_connection (NM_SETTINGS_CONNECTION (connection)));
-	if (!s_con) {
-		g_dbus_method_invocation_return_error (context,
-		                                       NM_SETTINGS_ERROR,
-		                                       NM_SETTINGS_ERROR_FAILED,
-		                                       "unable to retrieve the connection setting");
-		return;
-	}
-
-	uuid = nm_setting_connection_get_uuid (s_con);
+	uuid = nms_ifcfg_rh_storage_get_uuid_opt (storage);
 	if (!uuid) {
 		g_dbus_method_invocation_return_error (context,
 		                                       NM_SETTINGS_ERROR,
-		                                       NM_SETTINGS_ERROR_FAILED,
-		                                       "unable to get the UUID");
+		                                       NM_SETTINGS_ERROR_INVALID_CONNECTION,
+		                                       "ifcfg file '%s' not managed by NetworkManager", in_ifcfg);
 		return;
 	}
 
-	path = nm_dbus_object_get_path (NM_DBUS_OBJECT (connection));
+	/* It is ugly that the ifcfg-rh plugin needs to call back into NMSettings this
+	 * way.
+	 * There are alternatives (like invoking a signal), but they are all significant
+	 * extra code (and performance overhead). So the quick and dirty solution here
+	 * is likely to be simpler than getting this right (also from point of readability!).
+	 */
+	path = nm_settings_get_dbus_path_for_uuid (nm_settings_get (), uuid);
+
 	if (!path) {
 		g_dbus_method_invocation_return_error (context,
 		                                       NM_SETTINGS_ERROR,
@@ -764,9 +957,9 @@ impl_ifcfgrh_get_ifcfg_details (SettingsPluginIfcfg *plugin,
 /*****************************************************************************/
 
 static void
-_dbus_clear (SettingsPluginIfcfg *self)
+_dbus_clear (NMSIfcfgRHPlugin *self)
 {
-	SettingsPluginIfcfgPrivate *priv = SETTINGS_PLUGIN_IFCFG_GET_PRIVATE (self);
+	NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE (self);
 	guint id;
 
 	nm_clear_g_signal_handler (priv->dbus.connection, &priv->dbus.signal_id);
@@ -788,7 +981,7 @@ _dbus_connection_closed (GDBusConnection *connection,
                          gpointer         user_data)
 {
 	_LOGW ("dbus: %s bus closed", IFCFGRH1_BUS_NAME);
-	_dbus_clear (SETTINGS_PLUGIN_IFCFG (user_data));
+	_dbus_clear (NMS_IFCFG_RH_PLUGIN (user_data));
 
 	/* Retry or recover? */
 }
@@ -803,25 +996,27 @@ _method_call (GDBusConnection *connection,
               GDBusMethodInvocation *invocation,
               gpointer user_data)
 {
-	SettingsPluginIfcfg *self = SETTINGS_PLUGIN_IFCFG (user_data);
-	const char *ifcfg;
-
-	if (   !nm_streq (interface_name, IFCFGRH1_IFACE1_NAME)
-	    || !nm_streq (method_name, IFCFGRH1_IFACE1_METHOD_GET_IFCFG_DETAILS)) {
-		g_dbus_method_invocation_return_error (invocation,
-		                                       G_DBUS_ERROR,
-		                                       G_DBUS_ERROR_UNKNOWN_METHOD,
-		                                       "Unknown method %s",
-		                                       method_name);
-		return;
+	NMSIfcfgRHPlugin *self = NMS_IFCFG_RH_PLUGIN (user_data);
+
+	if (nm_streq (interface_name, IFCFGRH1_IFACE1_NAME)) {
+		if (nm_streq (method_name, IFCFGRH1_IFACE1_METHOD_GET_IFCFG_DETAILS)) {
+			const char *ifcfg;
+
+			g_variant_get (parameters, "(&s)", &ifcfg);
+			impl_ifcfgrh_get_ifcfg_details (self, invocation, ifcfg);
+			return;
+		}
 	}
 
-	g_variant_get (parameters, "(&s)", &ifcfg);
-	impl_ifcfgrh_get_ifcfg_details (self, invocation, ifcfg);
+	g_dbus_method_invocation_return_error (invocation,
+	                                       G_DBUS_ERROR,
+	                                       G_DBUS_ERROR_UNKNOWN_METHOD,
+	                                       "Unknown method %s",
+	                                       method_name);
 }
 
 static GDBusInterfaceInfo *const interface_info = NM_DEFINE_GDBUS_INTERFACE_INFO (
-	IFCFGRH1_BUS_NAME,
+	IFCFGRH1_IFACE1_NAME,
 	.methods = NM_DEFINE_GDBUS_METHOD_INFOS (
 		NM_DEFINE_GDBUS_METHOD_INFO (
 			IFCFGRH1_IFACE1_METHOD_GET_IFCFG_DETAILS,
@@ -842,8 +1037,8 @@ _dbus_request_name_done (GObject *source_object,
                          gpointer user_data)
 {
 	GDBusConnection *connection = G_DBUS_CONNECTION (source_object);
-	SettingsPluginIfcfg *self;
-	SettingsPluginIfcfgPrivate *priv;
+	NMSIfcfgRHPlugin *self;
+	NMSIfcfgRHPluginPrivate *priv;
 	gs_free_error GError *error = NULL;
 	gs_unref_variant GVariant *ret = NULL;
 	guint32 result;
@@ -852,8 +1047,8 @@ _dbus_request_name_done (GObject *source_object,
 	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
 		return;
 
-	self = SETTINGS_PLUGIN_IFCFG (user_data);
-	priv = SETTINGS_PLUGIN_IFCFG_GET_PRIVATE (self);
+	self = NMS_IFCFG_RH_PLUGIN (user_data);
+	priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE (self);
 
 	g_clear_object (&priv->dbus.cancellable);
 
@@ -900,8 +1095,8 @@ _dbus_create_done (GObject *source_object,
                    GAsyncResult *res,
                    gpointer user_data)
 {
-	SettingsPluginIfcfg *self;
-	SettingsPluginIfcfgPrivate *priv;
+	NMSIfcfgRHPlugin *self;
+	NMSIfcfgRHPluginPrivate *priv;
 	gs_free_error GError *error = NULL;
 	GDBusConnection *connection;
 
@@ -909,8 +1104,8 @@ _dbus_create_done (GObject *source_object,
 	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
 		return;
 
-	self = SETTINGS_PLUGIN_IFCFG (user_data);
-	priv = SETTINGS_PLUGIN_IFCFG_GET_PRIVATE (self);
+	self = NMS_IFCFG_RH_PLUGIN (user_data);
+	priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE (self);
 
 	g_clear_object (&priv->dbus.cancellable);
 
@@ -944,9 +1139,9 @@ _dbus_create_done (GObject *source_object,
 }
 
 static void
-_dbus_setup (SettingsPluginIfcfg *self)
+_dbus_setup (NMSIfcfgRHPlugin *self)
 {
-	SettingsPluginIfcfgPrivate *priv = SETTINGS_PLUGIN_IFCFG_GET_PRIVATE (self);
+	NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE (self);
 	gs_free char *address = NULL;
 	gs_free_error GError *error = NULL;
 
@@ -974,9 +1169,9 @@ config_changed_cb (NMConfig *config,
                    NMConfigData *config_data,
                    NMConfigChangeFlags changes,
                    NMConfigData *old_data,
-                   SettingsPluginIfcfg *self)
+                   NMSIfcfgRHPlugin *self)
 {
-	SettingsPluginIfcfgPrivate *priv;
+	NMSIfcfgRHPluginPrivate *priv;
 
 	/* If the dbus connection for some reason is borked the D-Bus service
 	 * won't be offered.
@@ -988,7 +1183,7 @@ config_changed_cb (NMConfig *config,
 	                            | NM_CONFIG_CHANGE_CAUSE_SIGUSR1))
 		return;
 
-	priv = SETTINGS_PLUGIN_IFCFG_GET_PRIVATE (self);
+	priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE (self);
 	if (   !priv->dbus.connection
 	    && !priv->dbus.cancellable)
 		_dbus_setup (self);
@@ -997,23 +1192,26 @@ config_changed_cb (NMConfig *config,
 /*****************************************************************************/
 
 static void
-settings_plugin_ifcfg_init (SettingsPluginIfcfg *plugin)
+nms_ifcfg_rh_plugin_init (NMSIfcfgRHPlugin *self)
 {
-	SettingsPluginIfcfgPrivate *priv = SETTINGS_PLUGIN_IFCFG_GET_PRIVATE ((SettingsPluginIfcfg *) plugin);
+	NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE (self);
+
+	priv->config = g_object_ref (nm_config_get ());
+
+	priv->unmanaged_specs = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, NULL);
+	priv->unrecognized_specs = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, NULL);
 
-	priv->connections = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, g_object_unref);
+	priv->storages = (NMSettUtilStorages) NM_SETT_UTIL_STORAGES_INIT (priv->storages, nms_ifcfg_rh_storage_destroy);
 }
 
 static void
 constructed (GObject *object)
 {
-	SettingsPluginIfcfg *self = SETTINGS_PLUGIN_IFCFG (object);
-	SettingsPluginIfcfgPrivate *priv = SETTINGS_PLUGIN_IFCFG_GET_PRIVATE (self);
+	NMSIfcfgRHPlugin *self = NMS_IFCFG_RH_PLUGIN (object);
+	NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE (self);
 
-	G_OBJECT_CLASS (settings_plugin_ifcfg_parent_class)->constructed (object);
+	G_OBJECT_CLASS (nms_ifcfg_rh_plugin_parent_class)->constructed (object);
 
-	priv->config = nm_config_get ();
-	g_object_add_weak_pointer ((GObject *) priv->config, (gpointer *) &priv->config);
 	g_signal_connect (priv->config,
 	                  NM_CONFIG_SIGNAL_CONFIG_CHANGED,
 	                  G_CALLBACK (config_changed_cb),
@@ -1025,48 +1223,44 @@ constructed (GObject *object)
 static void
 dispose (GObject *object)
 {
-	SettingsPluginIfcfg *self = SETTINGS_PLUGIN_IFCFG (object);
-	SettingsPluginIfcfgPrivate *priv = SETTINGS_PLUGIN_IFCFG_GET_PRIVATE (self);
+	NMSIfcfgRHPlugin *self = NMS_IFCFG_RH_PLUGIN (object);
+	NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE (self);
 
-	if (priv->config) {
-		g_object_remove_weak_pointer ((GObject *) priv->config, (gpointer *) &priv->config);
+	if (priv->config)
 		g_signal_handlers_disconnect_by_func (priv->config, config_changed_cb, self);
-		priv->config = NULL;
-	}
 
+	/* FIXME(shutdown) we need a stop method so that we can unregistering the D-Bus service
+	 * when NMSettings is shutting down, and not when the instance gets destroyed. */
 	_dbus_clear (self);
 
-	if (priv->connections) {
-		g_hash_table_destroy (priv->connections);
-		priv->connections = NULL;
-	}
+	nm_sett_util_storages_clear (&priv->storages);
 
-	if (priv->ifcfg_monitor) {
-		if (priv->ifcfg_monitor_id)
-			g_signal_handler_disconnect (priv->ifcfg_monitor, priv->ifcfg_monitor_id);
+	g_clear_object (&priv->config);
 
-		g_file_monitor_cancel (priv->ifcfg_monitor);
-		g_object_unref (priv->ifcfg_monitor);
-	}
+	G_OBJECT_CLASS (nms_ifcfg_rh_plugin_parent_class)->dispose (object);
 
-	G_OBJECT_CLASS (settings_plugin_ifcfg_parent_class)->dispose (object);
+	nm_clear_pointer (&priv->unmanaged_specs, g_hash_table_destroy);
+	nm_clear_pointer (&priv->unrecognized_specs, g_hash_table_destroy);
 }
 
 static void
-settings_plugin_ifcfg_class_init (SettingsPluginIfcfgClass *klass)
+nms_ifcfg_rh_plugin_class_init (NMSIfcfgRHPluginClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 	NMSettingsPluginClass *plugin_class = NM_SETTINGS_PLUGIN_CLASS (klass);
 
 	object_class->constructed = constructed;
-	object_class->dispose = dispose;
+	object_class->dispose     = dispose;
 
-	plugin_class->get_connections = get_connections;
-	plugin_class->add_connection = add_connection;
-	plugin_class->load_connection = load_connection;
-	plugin_class->reload_connections = reload_connections;
-	plugin_class->get_unmanaged_specs = get_unmanaged_specs;
+	plugin_class->plugin_name            = "ifcfg-rh";
+	plugin_class->get_unmanaged_specs    = get_unmanaged_specs;
 	plugin_class->get_unrecognized_specs = get_unrecognized_specs;
+	plugin_class->reload_connections     = reload_connections;
+	plugin_class->load_connections       = load_connections;
+	plugin_class->load_connections_done  = load_connections_done;
+	plugin_class->add_connection         = add_connection;
+	plugin_class->update_connection      = update_connection;
+	plugin_class->delete_connection      = delete_connection;
 }
 
 /*****************************************************************************/
@@ -1074,5 +1268,5 @@ settings_plugin_ifcfg_class_init (SettingsPluginIfcfgClass *klass)
 G_MODULE_EXPORT NMSettingsPlugin *
 nm_settings_plugin_factory (void)
 {
-	return NM_SETTINGS_PLUGIN (g_object_ref (settings_plugin_ifcfg_get ()));
+	return g_object_new (NMS_TYPE_IFCFG_RH_PLUGIN, NULL);
 }
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.h b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.h
index d815cc51..1db36083 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.h
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager system settings service
  *
  * Dan Williams <dcbw@redhat.com>
@@ -21,19 +20,19 @@
  * Copyright (C) 2007 - 2008 Red Hat, Inc.
  */
 
-#ifndef _PLUGIN_H_
-#define _PLUGIN_H_
+#ifndef __NMS_IFCFG_RH_PLUGIN_H__
+#define __NMS_IFCFG_RH_PLUGIN_H__
 
-#define SETTINGS_TYPE_PLUGIN_IFCFG            (settings_plugin_ifcfg_get_type ())
-#define SETTINGS_PLUGIN_IFCFG(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), SETTINGS_TYPE_PLUGIN_IFCFG, SettingsPluginIfcfg))
-#define SETTINGS_PLUGIN_IFCFG_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), SETTINGS_TYPE_PLUGIN_IFCFG, SettingsPluginIfcfgClass))
-#define SETTINGS_IS_PLUGIN_IFCFG(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SETTINGS_TYPE_PLUGIN_IFCFG))
-#define SETTINGS_IS_PLUGIN_IFCFG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SETTINGS_TYPE_PLUGIN_IFCFG))
-#define SETTINGS_PLUGIN_IFCFG_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), SETTINGS_TYPE_PLUGIN_IFCFG, SettingsPluginIfcfgClass))
+#define NMS_TYPE_IFCFG_RH_PLUGIN            (nms_ifcfg_rh_plugin_get_type ())
+#define NMS_IFCFG_RH_PLUGIN(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NMS_TYPE_IFCFG_RH_PLUGIN, NMSIfcfgRHPlugin))
+#define NMS_IFCFG_RH_PLUGIN_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NMS_TYPE_IFCFG_RH_PLUGIN, NMSIfcfgRHPluginClass))
+#define NMS_IS_IFCFG_RH_PLUGIN(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NMS_TYPE_IFCFG_RH_PLUGIN))
+#define NMS_IS_IFCFG_RH_PLUGIN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NMS_TYPE_IFCFG_RH_PLUGIN))
+#define NMS_IFCFG_RH_PLUGIN_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NMS_TYPE_IFCFG_RH_PLUGIN, NMSIfcfgRHPluginClass))
 
-typedef struct _SettingsPluginIfcfg SettingsPluginIfcfg;
-typedef struct _SettingsPluginIfcfgClass SettingsPluginIfcfgClass;
+typedef struct _NMSIfcfgRHPlugin NMSIfcfgRHPlugin;
+typedef struct _NMSIfcfgRHPluginClass NMSIfcfgRHPluginClass;
 
-GType settings_plugin_ifcfg_get_type (void);
+GType nms_ifcfg_rh_plugin_get_type (void);
 
-#endif /* _PLUGIN_H_ */
+#endif /* __NMS_IFCFG_RH_PLUGIN_H__ */
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 e5423b18..900a3fc1 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager system settings service
  *
  * This program is free software; you can redistribute it and/or modify
@@ -543,6 +542,37 @@ make_connection_setting (const char *file,
 	vint64 = svGetValueInt64 (ifcfg, "AUTH_RETRIES", 10, -1, G_MAXINT32, -1);
 	g_object_set (s_con, NM_SETTING_CONNECTION_AUTH_RETRIES, (int) vint64, NULL);
 
+	nm_clear_g_free (&value);
+	v = svGetValueStr (ifcfg, "DEVTIMEOUT", &value);
+	if (v) {
+		vint64 = _nm_utils_ascii_str_to_int64 (v, 10, 0, ((gint64) G_MAXINT32) / 1000, -1);
+		if (vint64 != -1)
+			vint64 *= 1000;
+		else {
+			char *endptr;
+			double d;
+
+			d = g_ascii_strtod (v, &endptr);
+			if (   errno == 0
+			    && endptr[0] == '\0'
+			    && d >= 0.0) {
+				d *= 1000.0;
+
+				/* We round. Yes, this is not correct to round IEEE 754 floats in general,
+				 * but sufficient for our case where we know that NetworkManager wrote the
+				 * setting with up to 3 digits for the milliseconds. */
+				d += 0.5;
+				if (   d >= 0.0
+				    && d <= (double) G_MAXINT32)
+					vint64 = (gint64) d;
+			}
+		}
+		if (vint64 == -1)
+			PARSE_WARNING ("invalid DEVTIMEOUT setting");
+		else
+			g_object_set (s_con, NM_SETTING_CONNECTION_WAIT_DEVICE_TIMEOUT, (int) vint64, NULL);
+	}
+
 	i_val = NM_SETTING_CONNECTION_MDNS_DEFAULT;
 	if (!svGetValueEnum (ifcfg, "MDNS",
 	                     nm_setting_connection_mdns_get_type (),
@@ -1906,7 +1936,10 @@ make_ip6_setting (shvarFile *ifcfg,
 	gs_unref_object NMSettingIPConfig *s_ip6 = NULL;
 	const char *v;
 	gs_free char *value = NULL;
-	gboolean ipv6init, ipv6forwarding, dhcp6 = FALSE;
+	gboolean ipv6init;
+	gboolean ipv6forwarding;
+	gboolean disabled;
+	gboolean dhcp6 = FALSE;
 	char *method = NM_SETTING_IP6_CONFIG_METHOD_MANUAL;
 	const char *ipv6addr, *ipv6addr_secondaries;
 	gs_free char *ipv6addr_to_free = NULL;
@@ -1964,6 +1997,7 @@ make_ip6_setting (shvarFile *ifcfg,
 
 	/* Find out method property */
 	/* Is IPV6 enabled? Set method to "ignored", when not enabled */
+	disabled = svGetValueBoolean(ifcfg, "IPV6_DISABLED", FALSE);
 	nm_clear_g_free (&value);
 	v = svGetValueStr (ifcfg, "IPV6INIT", &value);
 	ipv6init = svGetValueBoolean (ifcfg, "IPV6INIT", FALSE);
@@ -1972,8 +2006,10 @@ make_ip6_setting (shvarFile *ifcfg,
 			ipv6init = svGetValueBoolean (network_ifcfg, "IPV6INIT", FALSE);
 	}
 
-	if (!ipv6init)
-		method = NM_SETTING_IP6_CONFIG_METHOD_IGNORE;  /* IPv6 is disabled */
+	if (disabled)
+		method = NM_SETTING_IP6_CONFIG_METHOD_DISABLED;
+	else if (!ipv6init)
+		method = NM_SETTING_IP6_CONFIG_METHOD_IGNORE;
 	else {
 		ipv6forwarding = svGetValueBoolean (ifcfg, "IPV6FORWARDING", FALSE);
 		nm_clear_g_free (&value);
@@ -2039,7 +2075,8 @@ make_ip6_setting (shvarFile *ifcfg,
 	              NULL);
 
 	/* Don't bother to read IP, DNS and routes when IPv6 is disabled */
-	if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE) == 0)
+	if (NM_IN_STRSET (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+	                          NM_SETTING_IP6_CONFIG_METHOD_DISABLED))
 		return NM_SETTING (g_steal_pointer (&s_ip6));
 
 	nm_clear_g_free (&value);
@@ -3071,12 +3108,17 @@ eap_tls_reader (const char *eap_method,
 	gs_unref_bytes GBytes *privkey = NULL;
 	gs_unref_bytes GBytes *client_cert = NULL;
 	gs_free char *identity_free = NULL;
+	gs_free char *value_to_free = NULL;
+	const char *client_cert_var;
+	const char *client_cert_prop;
+	NMSetting8021xCKFormat format;
 
 	g_object_set (s_8021x,
 	              NM_SETTING_802_1X_IDENTITY,
 	              svGetValueStr (ifcfg, "IEEE_8021X_IDENTITY", &identity_free),
 	              NULL);
 
+	/* CA certificate */
 	if (!_cert_set_from_ifcfg (s_8021x,
 	                           ifcfg,
 	                           phase2 ? "IEEE_8021X_INNER_CA_CERT" : "IEEE_8021X_CA_CERT",
@@ -3090,6 +3132,7 @@ eap_tls_reader (const char *eap_method,
 	                        phase2 ? "IEEE_8021X_INNER_CA_CERT_PASSWORD" : "IEEE_8021X_CA_CERT_PASSWORD",
 	                        phase2 ? NM_SETTING_802_1X_PHASE2_CA_CERT_PASSWORD : NM_SETTING_802_1X_CA_CERT_PASSWORD);
 
+	/* Private key */
 	if (!_cert_set_from_ifcfg (s_8021x,
 	                           ifcfg,
 	                           phase2 ? "IEEE_8021X_INNER_PRIVATE_KEY" : "IEEE_8021X_PRIVATE_KEY",
@@ -3102,35 +3145,39 @@ eap_tls_reader (const char *eap_method,
 	                        keys_ifcfg,
 	                        phase2 ? "IEEE_8021X_INNER_PRIVATE_KEY_PASSWORD" : "IEEE_8021X_PRIVATE_KEY_PASSWORD",
 	                        phase2 ? NM_SETTING_802_1X_PHASE2_PRIVATE_KEY_PASSWORD : NM_SETTING_802_1X_PRIVATE_KEY_PASSWORD);
-	if (!privkey) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-		             "Missing %s for EAP method '%s'.",
-		             phase2 ? "IEEE_8021X_INNER_PRIVATE_KEY" : "IEEE_8021X_PRIVATE_KEY",
-		             eap_method);
-		return FALSE;
-	}
 
+	/* Client certificate */
+	client_cert_var = phase2 ? "IEEE_8021X_INNER_CLIENT_CERT" : "IEEE_8021X_CLIENT_CERT";
+	client_cert_prop = phase2 ? NM_SETTING_802_1X_PHASE2_CLIENT_CERT : NM_SETTING_802_1X_CLIENT_CERT;
 	if (!_cert_set_from_ifcfg (s_8021x,
 	                           ifcfg,
-	                           phase2 ? "IEEE_8021X_INNER_CLIENT_CERT" : "IEEE_8021X_CLIENT_CERT",
-	                           phase2 ? NM_SETTING_802_1X_PHASE2_CLIENT_CERT : NM_SETTING_802_1X_CLIENT_CERT,
+	                           client_cert_var,
+	                           client_cert_prop,
 	                           &client_cert,
 	                           error))
 		return FALSE;
-	/* 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 seldom used feature so that it is not fixed yet. */
 	_secret_set_from_ifcfg (s_8021x,
 	                        ifcfg,
 	                        keys_ifcfg,
 	                        phase2 ? "IEEE_8021X_INNER_CLIENT_CERT_PASSWORD" : "IEEE_8021X_CLIENT_CERT_PASSWORD",
 	                        phase2 ? NM_SETTING_802_1X_PHASE2_CLIENT_CERT_PASSWORD : NM_SETTING_802_1X_CLIENT_CERT_PASSWORD);
-	if (!client_cert) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-		             "Missing certificate for EAP method '%s'.",
-		             eap_method);
-		return FALSE;
+
+	/* In the past when the private key and client certificate
+	 * were the same PKCS #12 file we used to write only the
+	 * private key variable. Still support that even if it means
+	 * that we have to look into the file content, which makes
+	 * the connection not self-contained.
+	 */
+	if (   !client_cert
+	    && privkey
+	    && !svGetValue (ifcfg, client_cert_var, &value_to_free)) {
+		if (phase2)
+			format = nm_setting_802_1x_get_phase2_private_key_format (s_8021x);
+		else
+			format = nm_setting_802_1x_get_private_key_format (s_8021x);
+
+		if (format == NM_SETTING_802_1X_CK_FORMAT_PKCS12)
+			g_object_set (s_8021x, client_cert_prop, privkey, NULL);
 	}
 
 	return TRUE;
@@ -4369,9 +4416,11 @@ parse_ethtool_options (shvarFile *ifcfg, NMConnection *connection)
 	gboolean autoneg = FALSE;
 	guint32 speed = 0;
 	const char *duplex = NULL;
+	gboolean wired_found = FALSE;
 
 	ethtool_opts = svGetValue (ifcfg, "ETHTOOL_OPTS", &ethtool_opts_free);
 	if (ethtool_opts) {
+		wired_found = TRUE;
 		/* WAKE_ON_LAN_IGNORE is inferred from a specified but empty ETHTOOL_OPTS */
 		if (!ethtool_opts[0])
 			wol_flags = NM_SETTING_WIRED_WAKE_ON_LAN_IGNORE;
@@ -4395,7 +4444,9 @@ parse_ethtool_options (shvarFile *ifcfg, NMConnection *connection)
 	}
 
 	/* ETHTOOL_WAKE_ON_LAN = ignore overrides WoL settings in ETHTOOL_OPTS */
-	tmp = svGetValueStr (ifcfg, "ETHTOOL_WAKE_ON_LAN", &wol_value_free);
+	tmp = svGetValue (ifcfg, "ETHTOOL_WAKE_ON_LAN", &wol_value_free);
+	if (tmp)
+		wired_found = TRUE;
 	if (nm_streq0 (tmp, "ignore"))
 		wol_flags = NM_SETTING_WIRED_WAKE_ON_LAN_IGNORE;
 	else if (tmp)
@@ -4408,6 +4459,10 @@ parse_ethtool_options (shvarFile *ifcfg, NMConnection *connection)
 	}
 
 	s_wired = nm_connection_get_setting_wired (connection);
+	if (!s_wired && wired_found) {
+		s_wired = (NMSettingWired *) nm_setting_wired_new ();
+		nm_connection_add_setting (connection, NM_SETTING (s_wired));
+	}
 	if (s_wired) {
 		g_object_set (s_wired,
 		              NM_SETTING_WIRED_WAKE_ON_LAN, wol_flags,
@@ -4433,86 +4488,100 @@ make_wired_setting (shvarFile *ifcfg,
 	gs_unref_object NMSettingWired *s_wired = NULL;
 	const char *cvalue;
 	gs_free char *value = NULL;
-	char *nettype;
+	gboolean found = FALSE;
 
 	s_wired = NM_SETTING_WIRED (nm_setting_wired_new ());
 
-	value = svGetValueStr_cp (ifcfg, "MTU");
-	if (value) {
+	cvalue = svGetValue (ifcfg, "MTU", &value);
+	if (cvalue) {
 		int mtu;
 
-		mtu = _nm_utils_ascii_str_to_int64 (value, 0, 0, 65535, -1);
+		mtu = _nm_utils_ascii_str_to_int64 (cvalue, 0, 0, 65535, -1);
 		if (mtu >= 0)
 			g_object_set (s_wired, NM_SETTING_WIRED_MTU, (guint) mtu, NULL);
 		else
-			PARSE_WARNING ("invalid MTU '%s'", value);
+			PARSE_WARNING ("invalid MTU '%s'", cvalue);
 		nm_clear_g_free (&value);
+		found = TRUE;
 	}
 
-	value = svGetValueStr_cp (ifcfg, "HWADDR");
+	value = svGetValue_cp (ifcfg, "HWADDR");
 	if (value) {
-		value = g_strstrip (value);
-		g_object_set (s_wired, NM_SETTING_WIRED_MAC_ADDRESS, value, NULL);
+		if (value[0] != '\0') {
+			value = g_strstrip (value);
+			g_object_set (s_wired, NM_SETTING_WIRED_MAC_ADDRESS, value, NULL);
+		}
 		nm_clear_g_free (&value);
+		found = TRUE;
 	}
 
-	value = svGetValueStr_cp (ifcfg, "SUBCHANNELS");
-	if (value) {
-		const char *p = value;
-		gboolean success = TRUE;
-
-		/* basic sanity checks */
-		while (*p) {
-			if (!g_ascii_isxdigit (*p) && (*p != ',') && (*p != '.')) {
-				PARSE_WARNING ("invalid SUBCHANNELS '%s'", value);
-				success = FALSE;
-				break;
+	cvalue = svGetValue (ifcfg, "SUBCHANNELS", &value);
+	if (cvalue) {
+		if (cvalue[0] != '\0') {
+			const char *p = cvalue;
+			gboolean success = TRUE;
+
+			/* basic sanity checks */
+			while (*p) {
+				if (!g_ascii_isxdigit (*p) && (*p != ',') && (*p != '.')) {
+					PARSE_WARNING ("invalid SUBCHANNELS '%s'", cvalue);
+					success = FALSE;
+					break;
+				}
+				p++;
 			}
-			p++;
-		}
 
-		if (success) {
-			gs_free const char **chans = NULL;
-			guint32 num_chans;
+			if (success) {
+				gs_free const char **chans = NULL;
+				guint32 num_chans;
 
-			chans = nm_utils_strsplit_set (value, ",");
-			num_chans = NM_PTRARRAY_LEN (chans);
-			if (num_chans < 2 || num_chans > 3) {
-				PARSE_WARNING ("invalid SUBCHANNELS '%s' (%u channels, 2 or 3 expected)",
-				               value, (unsigned) NM_PTRARRAY_LEN (chans));
-			} else
-				g_object_set (s_wired, NM_SETTING_WIRED_S390_SUBCHANNELS, chans, NULL);
+				chans = nm_utils_strsplit_set (cvalue, ",");
+				num_chans = NM_PTRARRAY_LEN (chans);
+				if (num_chans < 2 || num_chans > 3) {
+					PARSE_WARNING ("invalid SUBCHANNELS '%s' (%u channels, 2 or 3 expected)",
+					               cvalue, (unsigned) NM_PTRARRAY_LEN (chans));
+				} else
+					g_object_set (s_wired, NM_SETTING_WIRED_S390_SUBCHANNELS, chans, NULL);
+			}
 		}
 		nm_clear_g_free (&value);
+		found = TRUE;
 	}
 
-	value = svGetValueStr_cp (ifcfg, "PORTNAME");
-	if (value) {
-		nm_setting_wired_add_s390_option (s_wired, "portname", value);
+	cvalue = svGetValue (ifcfg, "PORTNAME", &value);
+	if (cvalue) {
+		if (cvalue[0] != '\0')
+			nm_setting_wired_add_s390_option (s_wired, "portname", cvalue);
+		found = TRUE;
 		nm_clear_g_free (&value);
 	}
 
-	value = svGetValueStr_cp (ifcfg, "CTCPROT");
-	if (value) {
-		nm_setting_wired_add_s390_option (s_wired, "ctcprot", value);
+	cvalue = svGetValue (ifcfg, "CTCPROT", &value);
+	if (cvalue) {
+		if (cvalue[0] != '\0')
+			nm_setting_wired_add_s390_option (s_wired, "ctcprot", cvalue);
 		nm_clear_g_free (&value);
+		found = TRUE;
 	}
 
-	nettype = svGetValueStr_cp (ifcfg, "NETTYPE");
-	if (nettype) {
-		if (!strcmp (nettype, "qeth") || !strcmp (nettype, "lcs") || !strcmp (nettype, "ctc"))
-			g_object_set (s_wired, NM_SETTING_WIRED_S390_NETTYPE, nettype, NULL);
+	cvalue = svGetValue (ifcfg, "NETTYPE", &value);
+	if (cvalue) {
+		if (NM_IN_STRSET (cvalue, "qeth", "lcs", "ctc"))
+			g_object_set (s_wired, NM_SETTING_WIRED_S390_NETTYPE, cvalue, NULL);
 		else
-			PARSE_WARNING ("unknown s390 NETTYPE '%s'", nettype);
-		g_free (nettype);
+			PARSE_WARNING ("unknown s390 NETTYPE '%s'", cvalue);
+		nm_clear_g_free (&value);
+		found = TRUE;
 	}
 
-	value = svGetValueStr_cp (ifcfg, "OPTIONS");
-	if (value) {
+	cvalue = svGetValue (ifcfg, "OPTIONS", &value);
+	if (cvalue)
+		found = TRUE;
+	if (cvalue && cvalue[0]) {
 		gs_free const char **options = NULL;
 		gsize i;
 
-		options = nm_utils_strsplit_set_with_empty (value, " ");
+		options = nm_utils_escaped_tokens_split (cvalue, NM_ASCII_SPACES);
 		for (i = 0; options && options[i]; i++) {
 			const char *line = options[i];
 			const char *equals;
@@ -4526,20 +4595,33 @@ make_wired_setting (shvarFile *ifcfg,
 			if (!valid)
 				PARSE_WARNING ("invalid s390 OPTION '%s'", line);
 		}
-		nm_clear_g_free (&value);
+		found = TRUE;
 	}
-
-	g_object_set (s_wired,
-	              NM_SETTING_WIRED_CLONED_MAC_ADDRESS,
-	              svGetValueStr (ifcfg, "MACADDR", &value),
-	              NULL);
 	nm_clear_g_free (&value);
 
-	g_object_set (s_wired,
-	              NM_SETTING_WIRED_GENERATE_MAC_ADDRESS_MASK,
-	              svGetValueStr (ifcfg, "GENERATE_MAC_ADDRESS_MASK", &value),
-	              NULL);
-	nm_clear_g_free (&value);
+	cvalue = svGetValueStr (ifcfg, "MACADDR", &value);
+	if (cvalue) {
+		if (cvalue[0] != '\0') {
+			g_object_set (s_wired,
+			              NM_SETTING_WIRED_CLONED_MAC_ADDRESS,
+			              cvalue,
+			              NULL);
+		}
+		nm_clear_g_free (&value);
+		found = TRUE;
+	}
+
+	cvalue = svGetValueStr (ifcfg, "GENERATE_MAC_ADDRESS_MASK", &value);
+	if (cvalue) {
+		if (cvalue[0] != '\0') {
+			g_object_set (s_wired,
+			              NM_SETTING_WIRED_GENERATE_MAC_ADDRESS_MASK,
+			              cvalue,
+			              NULL);
+		}
+		nm_clear_g_free (&value);
+		found = TRUE;
+	}
 
 	cvalue = svGetValueStr (ifcfg, "HWADDR_BLACKLIST", &value);
 	if (cvalue) {
@@ -4548,20 +4630,31 @@ make_wired_setting (shvarFile *ifcfg,
 		strv = transform_hwaddr_blacklist (cvalue);
 		g_object_set (s_wired, NM_SETTING_WIRED_MAC_ADDRESS_BLACKLIST, strv, NULL);
 		nm_clear_g_free (&value);
+		found = TRUE;
 	}
 
-	value = svGetValueStr_cp (ifcfg, "KEY_MGMT");
-	if (value) {
-		if (!strcmp (value, "IEEE8021X")) {
-			*s_8021x = fill_8021x (ifcfg, file, value, FALSE, error);
+	cvalue = svGetValue (ifcfg, "KEY_MGMT", &value);
+	if (cvalue)
+		found = TRUE;
+	if (cvalue && cvalue[0] != '\0') {
+		if (!strcmp (cvalue, "IEEE8021X")) {
+			*s_8021x = fill_8021x (ifcfg, file, cvalue, FALSE, error);
 			if (!*s_8021x)
 				return NULL;
 		} else {
 			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			             "Unknown wired KEY_MGMT type '%s'", value);
+			             "Unknown wired KEY_MGMT type '%s'", cvalue);
 			return NULL;
 		}
-		nm_clear_g_free (&value);
+	}
+	nm_clear_g_free (&value);
+
+	if (!found) {
+		g_set_error (error,
+		             NM_UTILS_ERROR,
+		             NM_UTILS_ERROR_SETTING_MISSING,
+		             "The setting is missing.");
+		return NULL;
 	}
 
 	return (NMSetting *) g_steal_pointer (&s_wired);
@@ -4576,6 +4669,7 @@ wired_connection_from_ifcfg (const char *file,
 	NMSetting *con_setting = NULL;
 	NMSetting *wired_setting = NULL;
 	NMSetting8021x *s_8021x = NULL;
+	GError *local = NULL;
 
 	g_return_val_if_fail (file != NULL, NULL);
 	g_return_val_if_fail (ifcfg != NULL, NULL);
@@ -4591,12 +4685,16 @@ wired_connection_from_ifcfg (const char *file,
 	}
 	nm_connection_add_setting (connection, con_setting);
 
-	wired_setting = make_wired_setting (ifcfg, file, &s_8021x, error);
-	if (!wired_setting) {
+	wired_setting = make_wired_setting (ifcfg, file, &s_8021x, &local);
+	if (local && !g_error_matches (local, NM_UTILS_ERROR, NM_UTILS_ERROR_SETTING_MISSING)) {
+		g_propagate_error (error, local);
 		g_object_unref (connection);
 		return NULL;
 	}
-	nm_connection_add_setting (connection, wired_setting);
+	g_clear_error (&local);
+
+	if (wired_setting)
+		nm_connection_add_setting (connection, wired_setting);
 
 	if (s_8021x)
 		nm_connection_add_setting (connection, NM_SETTING (s_8021x));
@@ -4699,8 +4797,8 @@ make_infiniband_setting (shvarFile *ifcfg,
 		g_object_set (s_infiniband, NM_SETTING_INFINIBAND_TRANSPORT_MODE, "datagram", NULL);
 
 	if (svGetValueBoolean (ifcfg, "PKEY", FALSE)) {
+		gs_free char *parent = NULL;
 		int p_key;
-		char *parent;
 
 		if (!parse_infiniband_p_key (ifcfg, &p_key, &parent, error)) {
 			g_object_unref (s_infiniband);
@@ -4827,6 +4925,7 @@ bond_connection_from_ifcfg (const char *file,
 	NMSetting *bond_setting = NULL;
 	NMSetting *wired_setting = NULL;
 	NMSetting8021x *s_8021x = NULL;
+	GError *local = NULL;
 
 	g_return_val_if_fail (file != NULL, NULL);
 	g_return_val_if_fail (ifcfg != NULL, NULL);
@@ -4849,12 +4948,16 @@ bond_connection_from_ifcfg (const char *file,
 	}
 	nm_connection_add_setting (connection, bond_setting);
 
-	wired_setting = make_wired_setting (ifcfg, file, &s_8021x, error);
-	if (!wired_setting) {
+	wired_setting = make_wired_setting (ifcfg, file, &s_8021x, &local);
+	if (local && !g_error_matches (local, NM_UTILS_ERROR, NM_UTILS_ERROR_SETTING_MISSING)) {
+		g_propagate_error (error, local);
 		g_object_unref (connection);
 		return NULL;
 	}
-	nm_connection_add_setting (connection, wired_setting);
+	g_clear_error (&local);
+
+	if (wired_setting)
+		nm_connection_add_setting (connection, wired_setting);
 
 	if (s_8021x)
 		nm_connection_add_setting (connection, NM_SETTING (s_8021x));
@@ -4862,62 +4965,27 @@ bond_connection_from_ifcfg (const char *file,
 	return connection;
 }
 
-/* Check 'error' for errors. Missing config (NULL return value) is a valid case. */
-static char *
-read_team_config (shvarFile *ifcfg, const char *key, GError **error)
-{
-	gs_free_error GError *local_error = NULL;
-	gs_free char *value = NULL;
-	size_t l;
-
-	value = svGetValueStr_cp (ifcfg, key);
-	if (!value)
-		return NULL;
-
-	l = strlen (value);
-	if (l > 1*1024*1024) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-		             "%s too long (size %zd)", key, l);
-		return NULL;
-	}
-
-	if (!nm_utils_is_json_object (value, &local_error)) {
-		PARSE_WARNING ("ignoring invalid team configuration: %s", local_error->message);
-		return NULL;
-	}
-
-	return g_steal_pointer (&value);
-}
-
 static NMSetting *
 make_team_setting (shvarFile *ifcfg,
                    const char *file,
                    GError **error)
 {
-	NMSettingTeam *s_team;
-	char *value;
-	GError *local_err = NULL;
+	NMSetting *s_team;
+	gs_free char *value_device = NULL;
+	gs_free char *value = NULL;
 
-	value = svGetValueStr_cp (ifcfg, "DEVICE");
-	if (!value) {
+	if (!svGetValueStr (ifcfg, "DEVICE", &value_device)) {
 		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 		             "mandatory DEVICE keyword missing");
 		return NULL;
 	}
-	g_free (value);
-
-	value = read_team_config (ifcfg, "TEAM_CONFIG", &local_err);
-	if (local_err) {
-		g_propagate_error (error, local_err);
-		return NULL;
-	}
 
-	s_team = NM_SETTING_TEAM (nm_setting_team_new ());
-
-	g_object_set (s_team, NM_SETTING_TEAM_CONFIG, value, NULL);
-	g_free (value);
-
-	return (NMSetting *) s_team;
+	s_team = nm_setting_team_new ();
+	g_object_set (s_team,
+	              NM_SETTING_TEAM_CONFIG,
+	              svGetValue (ifcfg, "TEAM_CONFIG", &value),
+	              NULL);
+	return s_team;
 }
 
 static NMConnection *
@@ -4930,6 +4998,7 @@ team_connection_from_ifcfg (const char *file,
 	NMSetting *team_setting = NULL;
 	NMSetting *wired_setting = NULL;
 	NMSetting8021x *s_8021x = NULL;
+	GError *local = NULL;
 
 	g_return_val_if_fail (file != NULL, NULL);
 	g_return_val_if_fail (ifcfg != NULL, NULL);
@@ -4952,12 +5021,16 @@ team_connection_from_ifcfg (const char *file,
 	}
 	nm_connection_add_setting (connection, team_setting);
 
-	wired_setting = make_wired_setting (ifcfg, file, &s_8021x, error);
-	if (!wired_setting) {
+	wired_setting = make_wired_setting (ifcfg, file, &s_8021x, &local);
+	if (local && !g_error_matches (local, NM_UTILS_ERROR, NM_UTILS_ERROR_SETTING_MISSING)) {
+		g_propagate_error (error, local);
 		g_object_unref (connection);
 		return NULL;
 	}
-	nm_connection_add_setting (connection, wired_setting);
+	g_clear_error (&local);
+
+	if (wired_setting)
+		nm_connection_add_setting (connection, wired_setting);
 
 	if (s_8021x)
 		nm_connection_add_setting (connection, NM_SETTING (s_8021x));
@@ -5207,6 +5280,7 @@ bridge_connection_from_ifcfg (const char *file,
 	NMSetting *bridge_setting = NULL;
 	NMSetting *wired_setting = NULL;
 	NMSetting8021x *s_8021x = NULL;
+	GError *local = NULL;
 
 	g_return_val_if_fail (file != NULL, NULL);
 	g_return_val_if_fail (ifcfg != NULL, NULL);
@@ -5229,12 +5303,16 @@ bridge_connection_from_ifcfg (const char *file,
 	}
 	nm_connection_add_setting (connection, bridge_setting);
 
-	wired_setting = make_wired_setting (ifcfg, file, &s_8021x, error);
-	if (!wired_setting) {
+	wired_setting = make_wired_setting (ifcfg, file, &s_8021x, &local);
+	if (local && !g_error_matches (local, NM_UTILS_ERROR, NM_UTILS_ERROR_SETTING_MISSING)) {
+		g_propagate_error (error, local);
 		g_object_unref (connection);
 		return NULL;
 	}
-	nm_connection_add_setting (connection, wired_setting);
+	g_clear_error (&local);
+
+	if (wired_setting)
+		nm_connection_add_setting (connection, wired_setting);
 
 	if (s_8021x)
 		nm_connection_add_setting (connection, NM_SETTING (s_8021x));
@@ -5276,20 +5354,18 @@ make_bridge_port_setting (shvarFile *ifcfg)
 static NMSetting *
 make_team_port_setting (shvarFile *ifcfg)
 {
-	NMSetting *s_port = NULL;
-	char *value;
-	GError *error = NULL;
+	NMSetting *s_port;
+	gs_free char *value = NULL;
 
-	value = read_team_config (ifcfg, "TEAM_PORT_CONFIG", &error);
-	if (value) {
-		s_port = nm_setting_team_port_new ();
-		g_object_set (s_port, NM_SETTING_TEAM_PORT_CONFIG, value, NULL);
-		g_free (value);
-	} else if (error) {
-		PARSE_WARNING ("%s", error->message);
-		g_error_free (error);
-	}
+	value = svGetValueStr_cp (ifcfg, "TEAM_PORT_CONFIG");
+	if (!value)
+		return NULL;
 
+	s_port = nm_setting_team_port_new ();
+	g_object_set (s_port,
+	              NM_SETTING_TEAM_PORT_CONFIG,
+	              value,
+	              NULL);
 	return s_port;
 }
 
@@ -5486,6 +5562,7 @@ vlan_connection_from_ifcfg (const char *file,
 	NMSetting *wired_setting = NULL;
 	NMSetting *vlan_setting = NULL;
 	NMSetting8021x *s_8021x = NULL;
+	GError *local = NULL;
 
 	g_return_val_if_fail (file != NULL, NULL);
 	g_return_val_if_fail (ifcfg != NULL, NULL);
@@ -5508,12 +5585,16 @@ vlan_connection_from_ifcfg (const char *file,
 	}
 	nm_connection_add_setting (connection, vlan_setting);
 
-	wired_setting = make_wired_setting (ifcfg, file, &s_8021x, error);
-	if (!wired_setting) {
+	wired_setting = make_wired_setting (ifcfg, file, &s_8021x, &local);
+	if (local && !g_error_matches (local, NM_UTILS_ERROR, NM_UTILS_ERROR_SETTING_MISSING)) {
+		g_propagate_error (error, local);
 		g_object_unref (connection);
 		return NULL;
 	}
-	nm_connection_add_setting (connection, wired_setting);
+	g_clear_error (&local);
+
+	if (wired_setting)
+		nm_connection_add_setting (connection, wired_setting);
 
 	if (s_8021x)
 		nm_connection_add_setting (connection, NM_SETTING (s_8021x));
@@ -5549,21 +5630,21 @@ create_unhandled_connection (const char *filename, shvarFile *ifcfg,
 	if (v) {
 		gs_free char *lower = g_ascii_strdown (v, -1);
 
-		*out_spec = g_strdup_printf ("%s:mac:%s", type, lower);
+		*out_spec = g_strdup_printf ("%s:"NM_MATCH_SPEC_MAC_TAG"%s", type, lower);
 		return connection;
 	}
 
 	nm_clear_g_free (&value);
 	v = svGetValueStr (ifcfg, "SUBCHANNELS", &value);
 	if (v) {
-		*out_spec = g_strdup_printf ("%s:s390-subchannels:%s", type, v);
+		*out_spec = g_strdup_printf ("%s:"NM_MATCH_SPEC_S390_SUBCHANNELS_TAG"%s", type, v);
 		return connection;
 	}
 
 	nm_clear_g_free (&value);
 	v = svGetValueStr (ifcfg, "DEVICE", &value);
 	if (v) {
-		*out_spec = g_strdup_printf ("%s:interface-name:%s", type, v);
+		*out_spec = g_strdup_printf ("%s:"NM_MATCH_SPEC_INTERFACE_NAME_TAG"=%s", type, v);
 		return connection;
 	}
 
@@ -5654,7 +5735,7 @@ connection_from_file_full (const char *filename,
 		return g_steal_pointer (&connection);
 	}
 
-	/* iBFT is handled by the iBFT settings plugin */
+	/* iBFT is handled by nm-initrd-generator during boot. */
 	bootproto = svGetValueStr_cp (main_ifcfg, "BOOTPROTO");
 	if (bootproto && !g_ascii_strcasecmp (bootproto, "ibft")) {
 		NM_SET_OUT (out_ignore_error, TRUE);
@@ -5902,12 +5983,10 @@ connection_from_file_full (const char *filename,
 	if (s_match)
 		nm_connection_add_setting (connection, s_match);
 
-	/* Bridge port? */
 	s_port = make_bridge_port_setting (main_ifcfg);
 	if (s_port)
 		nm_connection_add_setting (connection, s_port);
 
-	/* Team port? */
 	s_port = make_team_port_setting (main_ifcfg);
 	if (s_port)
 		nm_connection_add_setting (connection, s_port);
@@ -5949,20 +6028,3 @@ nmtst_connection_from_file (const char *filename,
 	                                  error,
 	                                  NULL);
 }
-
-guint
-devtimeout_from_file (const char *filename)
-{
-	shvarFile *ifcfg;
-	guint devtimeout;
-
-	g_return_val_if_fail (filename != NULL, 0);
-
-	ifcfg = svOpenFile (filename, NULL);
-	if (!ifcfg)
-		return 0;
-
-	devtimeout = svGetValueInt64 (ifcfg, "DEVTIMEOUT", 10, 0, G_MAXUINT, 0);
-	svCloseFile (ifcfg);
-	return devtimeout;
-}
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.h b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.h
index a8937ac8..8008e052 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.h
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager system settings service
  *
  * This program is free software; you can redistribute it and/or modify
@@ -18,8 +17,8 @@
  * Copyright (C) 2008 Red Hat, Inc.
  */
 
-#ifndef __READER_H__
-#define __READER_H__
+#ifndef __NMS_IFCFG_RH_READER_H__
+#define __NMS_IFCFG_RH_READER_H__
 
 #include "nm-connection.h"
 
@@ -28,12 +27,10 @@ NMConnection *connection_from_file (const char *filename,
                                     GError **error,
                                     gboolean *out_ignore_error);
 
-guint devtimeout_from_file (const char *filename);
-
 NMConnection *nmtst_connection_from_file (const char *filename,
                                           const char *network_file,
                                           const char *test_type,
                                           char **out_unhandled,
                                           GError **error);
 
-#endif  /* __READER_H__ */
+#endif  /* __NMS_IFCFG_RH_READER_H__ */
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.c b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.c
new file mode 100644
index 00000000..2841bedb
--- /dev/null
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.c
@@ -0,0 +1,198 @@
+/* NetworkManager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2018 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nms-ifcfg-rh-storage.h"
+
+#include "nm-utils.h"
+#include "nm-core-internal.h"
+#include "nm-connection.h"
+#include "nms-ifcfg-rh-plugin.h"
+
+/*****************************************************************************/
+
+struct _NMSIfcfgRHStorageClass {
+	NMSettingsStorageClass parent;
+};
+
+G_DEFINE_TYPE (NMSIfcfgRHStorage, nms_ifcfg_rh_storage, NM_TYPE_SETTINGS_STORAGE)
+
+/*****************************************************************************/
+
+gboolean
+nms_ifcfg_rh_storage_equal_type (const NMSIfcfgRHStorage *self_a,
+                                 const NMSIfcfgRHStorage *self_b)
+{
+	return    (self_a == self_b)
+	       || (   self_a
+	           && self_b
+	           && nm_streq0 (nms_ifcfg_rh_storage_get_uuid_opt (self_a),
+	                         nms_ifcfg_rh_storage_get_uuid_opt (self_b))
+	           && nm_streq0 (self_a->unmanaged_spec,
+	                         self_b->unmanaged_spec)
+	           && nm_streq0 (self_a->unrecognized_spec,
+	                         self_b->unrecognized_spec));
+}
+
+void
+nms_ifcfg_rh_storage_copy_content (NMSIfcfgRHStorage *dst,
+                                   const NMSIfcfgRHStorage *src)
+{
+	nm_assert (src != dst);
+	nm_assert (src && dst);
+	nm_assert (nms_ifcfg_rh_storage_equal_type (dst, src));
+	nm_assert (   nms_ifcfg_rh_storage_get_filename (dst)
+	           && nm_streq (nms_ifcfg_rh_storage_get_filename (dst),
+	                        nms_ifcfg_rh_storage_get_filename (src)));
+
+	nm_g_object_ref_set (&dst->connection, src->connection);
+	g_free (dst->unmanaged_spec);
+	g_free (dst->unrecognized_spec);
+	dst->unmanaged_spec    = g_strdup (src->unmanaged_spec);
+	dst->unrecognized_spec = g_strdup (src->unrecognized_spec);
+	dst->stat_mtime        = src->stat_mtime;
+}
+
+NMConnection *
+nms_ifcfg_rh_storage_steal_connection (NMSIfcfgRHStorage *self)
+{
+	nm_assert (NMS_IS_IFCFG_RH_STORAGE (self));
+
+	return g_steal_pointer (&self->connection);
+}
+
+/*****************************************************************************/
+
+static int
+cmp_fcn (const NMSIfcfgRHStorage *a,
+         const NMSIfcfgRHStorage *b)
+{
+	nm_assert (NMS_IS_IFCFG_RH_STORAGE (a));
+	nm_assert (NMS_IS_IFCFG_RH_STORAGE (b));
+	nm_assert (a != b);
+
+	/* newer files are more important. */
+	NM_CMP_FIELD (a, b, stat_mtime.tv_sec);
+	NM_CMP_FIELD (a, b, stat_mtime.tv_nsec);
+
+	NM_CMP_DIRECT_STRCMP (nms_ifcfg_rh_storage_get_filename (a), nms_ifcfg_rh_storage_get_filename (b));
+
+	return 0;
+}
+
+/*****************************************************************************/
+
+static void
+nms_ifcfg_rh_storage_init (NMSIfcfgRHStorage *self)
+{
+}
+
+static NMSIfcfgRHStorage *
+_storage_new (NMSIfcfgRHPlugin *plugin,
+              const char *uuid,
+              const char *filename)
+{
+	nm_assert (NMS_IS_IFCFG_RH_PLUGIN (plugin));
+	nm_assert (!uuid || nm_utils_is_uuid (uuid));
+	nm_assert (filename && filename[0] == '/');
+
+	return g_object_new (NMS_TYPE_IFCFG_RH_STORAGE,
+	                     NM_SETTINGS_STORAGE_PLUGIN, plugin,
+	                     NM_SETTINGS_STORAGE_UUID, uuid,
+	                     NM_SETTINGS_STORAGE_FILENAME, filename,
+	                     NULL);
+}
+
+NMSIfcfgRHStorage *
+nms_ifcfg_rh_storage_new_connection (NMSIfcfgRHPlugin *plugin,
+                                     const char *filename,
+                                     NMConnection *connection_take,
+                                     const struct timespec *mtime)
+{
+	NMSIfcfgRHStorage *self;
+
+	nm_assert (NM_IS_CONNECTION (connection_take));
+	nm_assert (_nm_connection_verify (connection_take, NULL) == NM_SETTING_VERIFY_SUCCESS);
+	nmtst_connection_assert_unchanging (connection_take);
+
+	self = _storage_new (plugin,
+	                     nm_connection_get_uuid (connection_take),
+	                     filename);
+	self->connection = connection_take;
+	if (mtime)
+		self->stat_mtime = *mtime;
+	return self;
+}
+
+NMSIfcfgRHStorage *
+nms_ifcfg_rh_storage_new_unhandled (NMSIfcfgRHPlugin *plugin,
+                                    const char *filename,
+                                    const char *unmanaged_spec,
+                                    const char *unrecognized_spec)
+{
+	NMSIfcfgRHStorage *self;
+
+	nm_assert (unmanaged_spec || unrecognized_spec);
+
+	self = _storage_new (plugin,
+	                     NULL,
+	                     filename);
+	self->unmanaged_spec = g_strdup (unmanaged_spec);
+	self->unrecognized_spec = g_strdup (unrecognized_spec);
+	return self;
+}
+
+static void
+_storage_clear (NMSIfcfgRHStorage *self)
+{
+	c_list_unlink (&self->parent._storage_lst);
+	c_list_unlink (&self->parent._storage_by_uuid_lst);
+	nm_clear_g_free (&self->unmanaged_spec);
+	nm_clear_g_free (&self->unrecognized_spec);
+	g_clear_object (&self->connection);
+}
+
+static void
+dispose (GObject *object)
+{
+	NMSIfcfgRHStorage *self = NMS_IFCFG_RH_STORAGE (object);
+
+	_storage_clear (self);
+
+	G_OBJECT_CLASS (nms_ifcfg_rh_storage_parent_class)->dispose (object);
+}
+
+void
+nms_ifcfg_rh_storage_destroy (NMSIfcfgRHStorage *self)
+{
+	_storage_clear (self);
+	g_object_unref (self);
+}
+
+static void
+nms_ifcfg_rh_storage_class_init (NMSIfcfgRHStorageClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	NMSettingsStorageClass *storage_class = NM_SETTINGS_STORAGE_CLASS (klass);
+
+	object_class->dispose = dispose;
+
+	storage_class->cmp_fcn = (int (*) (NMSettingsStorage *, NMSettingsStorage *)) cmp_fcn;
+}
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.h b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.h
new file mode 100644
index 00000000..e1165f50
--- /dev/null
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.h
@@ -0,0 +1,93 @@
+/* NetworkManager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2019 Red Hat, Inc.
+ */
+
+#ifndef __NMS_IFCFG_RH_STORAGE_H__
+#define __NMS_IFCFG_RH_STORAGE_H__
+
+#include "c-list/src/c-list.h"
+#include "settings/nm-settings-storage.h"
+
+/*****************************************************************************/
+
+#define NMS_TYPE_IFCFG_RH_STORAGE            (nms_ifcfg_rh_storage_get_type ())
+#define NMS_IFCFG_RH_STORAGE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NMS_TYPE_IFCFG_RH_STORAGE, NMSIfcfgRHStorage))
+#define NMS_IFCFG_RH_STORAGE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NMS_TYPE_IFCFG_RH_STORAGE, NMSIfcfgRHStorageClass))
+#define NMS_IS_IFCFG_RH_STORAGE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NMS_TYPE_IFCFG_RH_STORAGE))
+#define NMS_IS_IFCFG_RH_STORAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NMS_TYPE_IFCFG_RH_STORAGE))
+#define NMS_IFCFG_RH_STORAGE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NMS_TYPE_IFCFG_RH_STORAGE, NMSIfcfgRHStorageClass))
+
+typedef struct {
+	NMSettingsStorage parent;
+
+	NMConnection *connection;
+
+	char *unmanaged_spec;
+	char *unrecognized_spec;
+
+	/* The timestamp (stat's mtime) of the file. Newer files have
+	 * higher priority. */
+	struct timespec stat_mtime;
+
+	bool dirty:1;
+
+} NMSIfcfgRHStorage;
+
+typedef struct _NMSIfcfgRHStorageClass NMSIfcfgRHStorageClass;
+
+GType nms_ifcfg_rh_storage_get_type (void);
+
+struct _NMSIfcfgRHPlugin;
+
+NMSIfcfgRHStorage *nms_ifcfg_rh_storage_new_connection (struct _NMSIfcfgRHPlugin *plugin,
+                                                        const char *filename,
+                                                        NMConnection *connection_take,
+                                                        const struct timespec *mtime);
+
+NMSIfcfgRHStorage *nms_ifcfg_rh_storage_new_unhandled (struct _NMSIfcfgRHPlugin *plugin,
+                                                       const char *filename,
+                                                       const char *unmanaged_spec,
+                                                       const char *unrecognized_spec);
+
+void nms_ifcfg_rh_storage_destroy (NMSIfcfgRHStorage *self);
+
+/*****************************************************************************/
+
+gboolean nms_ifcfg_rh_storage_equal_type (const NMSIfcfgRHStorage *self_a,
+                                          const NMSIfcfgRHStorage *self_b);
+
+void nms_ifcfg_rh_storage_copy_content (NMSIfcfgRHStorage *dst,
+                                        const NMSIfcfgRHStorage *src);
+
+NMConnection *nms_ifcfg_rh_storage_steal_connection (NMSIfcfgRHStorage *self);
+
+/*****************************************************************************/
+
+static inline const char *
+nms_ifcfg_rh_storage_get_uuid_opt (const NMSIfcfgRHStorage *self)
+{
+	return nm_settings_storage_get_uuid_opt ((const NMSettingsStorage *) self);
+}
+
+static inline const char *
+nms_ifcfg_rh_storage_get_filename (const NMSIfcfgRHStorage *self)
+{
+	return nm_settings_storage_get_filename ((const NMSettingsStorage *) self);
+}
+
+#endif /* __NMS_IFCFG_RH_STORAGE_H__ */
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c
index 22c9061b..cb1fc23a 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager system settings service
  *
  * This program is free software; you can redistribute it and/or modify
@@ -29,6 +28,32 @@
 
 #include "nms-ifcfg-rh-common.h"
 
+/*****************************************************************************/
+
+gboolean
+nms_ifcfg_rh_util_parse_unhandled_spec (const char *unhandled_spec,
+                                        const char **out_unmanaged_spec,
+                                        const char **out_unrecognized_spec)
+{
+	if (unhandled_spec) {
+		if (NM_STR_HAS_PREFIX (unhandled_spec, "unmanaged:")) {
+			NM_SET_OUT (out_unmanaged_spec, &unhandled_spec[NM_STRLEN ("unmanaged:")]);
+			NM_SET_OUT (out_unrecognized_spec, NULL);
+			return TRUE;
+		}
+		if (NM_STR_HAS_PREFIX (unhandled_spec, "unrecognized:")) {
+			NM_SET_OUT (out_unmanaged_spec, NULL);
+			NM_SET_OUT (out_unrecognized_spec, &unhandled_spec[NM_STRLEN ("unrecognized:")]);
+			return TRUE;
+		}
+	}
+	NM_SET_OUT (out_unmanaged_spec, NULL);
+	NM_SET_OUT (out_unrecognized_spec, NULL);
+	return FALSE;
+}
+
+/*****************************************************************************/
+
 /*
  * Check ';[a-fA-F0-9]{8}' file suffix used for temporary files by rpm when
  * installing packages.
@@ -317,36 +342,41 @@ utils_is_ifcfg_alias_file (const char *alias, const char *ifcfg)
 char *
 utils_detect_ifcfg_path (const char *path, gboolean only_ifcfg)
 {
-	gs_free char *base = NULL;
-	char *ptr, *ifcfg = NULL;
+	const char *base;
 
 	g_return_val_if_fail (path != NULL, NULL);
 
 	if (utils_should_ignore_file (path, only_ifcfg))
 		return NULL;
 
-	base = g_path_get_basename (path);
+	base = strrchr (path, '/');
+	if (!base)
+		base = path;
+	else
+		base += 1;
 
-	if (strncmp (base, IFCFG_TAG, NM_STRLEN (IFCFG_TAG)) == 0) {
+	if (NM_STR_HAS_PREFIX (base, IFCFG_TAG)) {
 		if (base[NM_STRLEN (IFCFG_TAG)] == '\0')
 			return NULL;
 		if (utils_is_ifcfg_alias_file (base, NULL)) {
+			gs_free char *ifcfg = NULL;
+			char *ptr;
+
 			ifcfg = g_strdup (path);
 			ptr = strrchr (ifcfg, ':');
-			if (ptr && ptr > ifcfg) {
+			if (   ptr
+			    && ptr > ifcfg
+			    && !strchr (ptr, '/')) {
 				*ptr = '\0';
 				if (g_file_test (ifcfg, G_FILE_TEST_EXISTS)) {
 					/* the file has a colon, so it is probably an alias.
 					 * To be ~more~ certain that this is an alias file,
 					 * check whether a corresponding base file exists. */
-					if (only_ifcfg) {
-						g_free (ifcfg);
+					if (only_ifcfg)
 						return NULL;
-					}
-					return ifcfg;
+					return g_steal_pointer (&ifcfg);
 				}
 			}
-			g_free (ifcfg);
 		}
 		return g_strdup (path);
 	}
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h
index c7729df5..20d6f72d 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager system settings service
  *
  * This program is free software; you can redistribute it and/or modify
@@ -26,6 +25,10 @@
 
 #include "shvar.h"
 
+gboolean nms_ifcfg_rh_util_parse_unhandled_spec (const char *unhandled_spec,
+                                                 const char **out_unmanaged_spec,
+                                                 const char **out_unrecognized_spec);
+
 #define NM_IFCFG_CONNECTION_LOG_PATH(path)  ((path) ?: "in-memory")
 #define NM_IFCFG_CONNECTION_LOG_FMT         "%s (%s,\"%s\")"
 #define NM_IFCFG_CONNECTION_LOG_ARG(con)    NM_IFCFG_CONNECTION_LOG_PATH (nm_settings_connection_get_filename ((NMSettingsConnection *) (con))), nm_settings_connection_get_uuid ((NMSettingsConnection *) (con)), nm_settings_connection_get_id ((NMSettingsConnection *) (con))
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 80b1bffe..38dc5c8d 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager system settings service - keyfile plugin
  *
  * This program is free software; you can redistribute it and/or modify
@@ -177,30 +176,18 @@ typedef struct {
 } Setting8021xSchemeVtable;
 
 static const Setting8021xSchemeVtable setting_8021x_scheme_vtable[] = {
-	[NM_SETTING_802_1X_SCHEME_TYPE_CA_CERT] = {
-		.vtable                 = &nm_setting_8021x_scheme_vtable[NM_SETTING_802_1X_SCHEME_TYPE_CA_CERT],
-		.ifcfg_rh_key           = "IEEE_8021X_CA_CERT",
-	},
-	[NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_CA_CERT] = {
-		.vtable                 = &nm_setting_8021x_scheme_vtable[NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_CA_CERT],
-		.ifcfg_rh_key           = "IEEE_8021X_INNER_CA_CERT",
-	},
-	[NM_SETTING_802_1X_SCHEME_TYPE_CLIENT_CERT] = {
-		.vtable                 = &nm_setting_8021x_scheme_vtable[NM_SETTING_802_1X_SCHEME_TYPE_CLIENT_CERT],
-		.ifcfg_rh_key           = "IEEE_8021X_CLIENT_CERT",
-	},
-	[NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_CLIENT_CERT] = {
-		.vtable                 = &nm_setting_8021x_scheme_vtable[NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_CLIENT_CERT],
-		.ifcfg_rh_key           = "IEEE_8021X_INNER_CLIENT_CERT",
-	},
-	[NM_SETTING_802_1X_SCHEME_TYPE_PRIVATE_KEY] = {
-		.vtable                 = &nm_setting_8021x_scheme_vtable[NM_SETTING_802_1X_SCHEME_TYPE_PRIVATE_KEY],
-		.ifcfg_rh_key           = "IEEE_8021X_PRIVATE_KEY",
-	},
-	[NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_PRIVATE_KEY] = {
-		.vtable                 = &nm_setting_8021x_scheme_vtable[NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_PRIVATE_KEY],
-		.ifcfg_rh_key           = "IEEE_8021X_INNER_PRIVATE_KEY",
-	},
+#define _D(_scheme_type, _ifcfg_rh_key) \
+	[(_scheme_type)] = { \
+		.vtable       = &nm_setting_8021x_scheme_vtable[(_scheme_type)], \
+		.ifcfg_rh_key = ""_ifcfg_rh_key"", \
+	}
+	_D (NM_SETTING_802_1X_SCHEME_TYPE_CA_CERT,            "IEEE_8021X_CA_CERT"),
+	_D (NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_CA_CERT,     "IEEE_8021X_INNER_CA_CERT"),
+	_D (NM_SETTING_802_1X_SCHEME_TYPE_CLIENT_CERT,        "IEEE_8021X_CLIENT_CERT"),
+	_D (NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_CLIENT_CERT, "IEEE_8021X_INNER_CLIENT_CERT"),
+	_D (NM_SETTING_802_1X_SCHEME_TYPE_PRIVATE_KEY,        "IEEE_8021X_PRIVATE_KEY"),
+	_D (NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_PRIVATE_KEY, "IEEE_8021X_INNER_PRIVATE_KEY"),
+#undef _D
 };
 
 static gboolean
@@ -209,6 +196,7 @@ write_object (NMSetting8021x *s_8021x,
               GHashTable *secrets,
               GHashTable *blobs,
               const Setting8021xSchemeVtable *objtype,
+              gboolean force_write,
               GError **error)
 {
 	NMSetting8021xCKScheme scheme;
@@ -216,7 +204,8 @@ write_object (NMSetting8021x *s_8021x,
 	GBytes *blob = NULL;
 	const char *password = NULL;
 	NMSettingSecretFlags flags = NM_SETTING_SECRET_FLAG_NONE;
-	char *secret_name, *secret_flags;
+	char secret_name[100];
+	char secret_flags[sizeof (secret_name) + NM_STRLEN ("_FLAGS")];
 	const char *extension;
 	char *standard_file;
 
@@ -243,13 +232,11 @@ write_object (NMSetting8021x *s_8021x,
 	}
 
 	/* Set the password for certificate/private key. */
-	secret_name = g_strdup_printf ("%s_PASSWORD", objtype->ifcfg_rh_key);
-	secret_flags = g_strdup_printf ("%s_PASSWORD_FLAGS", objtype->ifcfg_rh_key);
+	nm_sprintf_buf (secret_name, "%s_PASSWORD", objtype->ifcfg_rh_key);
+	nm_sprintf_buf (secret_flags, "%s_PASSWORD_FLAGS", objtype->ifcfg_rh_key);
 	password = (*(objtype->vtable->passwd_func))(s_8021x);
 	flags = (*(objtype->vtable->pwflag_func))(s_8021x);
 	set_secret (ifcfg, secrets, secret_name, password, secret_flags, flags);
-	g_free (secret_name);
-	g_free (secret_flags);
 
 	if (!objtype->vtable->format_func)
 		extension = "der";
@@ -287,7 +274,7 @@ write_object (NMSetting8021x *s_8021x,
 	 */
 	standard_file = utils_cert_path (svFileGetName (ifcfg), objtype->vtable->file_suffix, extension);
 	g_hash_table_replace (blobs, standard_file, NULL);
-	svUnsetValue (ifcfg, objtype->ifcfg_rh_key);
+	svSetValue (ifcfg, objtype->ifcfg_rh_key, force_write ? "" : NULL);
 	return TRUE;
 }
 
@@ -338,43 +325,41 @@ write_8021x_certs (NMSetting8021x *s_8021x,
                    shvarFile *ifcfg,
                    GError **error)
 {
-	const Setting8021xSchemeVtable *otype = NULL;
+	const Setting8021xSchemeVtable *pk_otype = NULL;
+	gs_free char *value_to_free = NULL;
 
 	/* CA certificate */
 	if (!write_object (s_8021x, ifcfg, secrets, blobs,
 	                   phase2
 	                       ? &setting_8021x_scheme_vtable[NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_CA_CERT]
 	                       : &setting_8021x_scheme_vtable[NM_SETTING_802_1X_SCHEME_TYPE_CA_CERT],
+	                   FALSE,
 	                   error))
 		return FALSE;
 
 	/* Private key */
 	if (phase2)
-		otype = &setting_8021x_scheme_vtable[NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_PRIVATE_KEY];
+		pk_otype = &setting_8021x_scheme_vtable[NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_PRIVATE_KEY];
 	else
-		otype = &setting_8021x_scheme_vtable[NM_SETTING_802_1X_SCHEME_TYPE_PRIVATE_KEY];
+		pk_otype = &setting_8021x_scheme_vtable[NM_SETTING_802_1X_SCHEME_TYPE_PRIVATE_KEY];
 
 	/* Save the private key */
-	if (!write_object (s_8021x, ifcfg, secrets, blobs, otype, error))
+	if (!write_object (s_8021x, ifcfg, secrets, blobs, pk_otype, FALSE, error))
 		return FALSE;
 
-	/* Client certificate */
-	if (otype->vtable->format_func (s_8021x) == NM_SETTING_802_1X_CK_FORMAT_PKCS12) {
-		/* Don't need a client certificate with PKCS#12 since the file is both
-		 * the client certificate and the private key in one file.
-		 */
-		svSetValueStr (ifcfg,
-		               phase2 ? "IEEE_8021X_INNER_CLIENT_CERT" : "IEEE_8021X_CLIENT_CERT",
-		               NULL);
-	} else {
-		/* Save the client certificate */
-		if (!write_object (s_8021x, ifcfg, secrets, blobs,
-		                   phase2
-		                       ? &setting_8021x_scheme_vtable[NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_CLIENT_CERT]
-		                       : &setting_8021x_scheme_vtable[NM_SETTING_802_1X_SCHEME_TYPE_CLIENT_CERT],
-		                   error))
-			return FALSE;
-	}
+	/* Save the client certificate.
+	 * If there is a private key, always write a property for the
+	 * client certificate even if it is empty, so that the reader
+	 * doesn't have to read the private key file to determine if it
+	 * is a PKCS #12 one which serves also as client certificate.
+	 */
+	if (!write_object (s_8021x, ifcfg, secrets, blobs,
+	                   phase2
+	                       ? &setting_8021x_scheme_vtable[NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_CLIENT_CERT]
+	                       : &setting_8021x_scheme_vtable[NM_SETTING_802_1X_SCHEME_TYPE_CLIENT_CERT],
+	                   !!svGetValue (ifcfg, pk_otype->ifcfg_rh_key, &value_to_free),
+	                   error))
+		return FALSE;
 
 	return TRUE;
 }
@@ -1118,14 +1103,22 @@ write_wired_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
 			nm_setting_wired_get_s390_option (s_wired, i, &s390_key, &s390_val);
 
 			/* portname is handled separately */
-			if (!strcmp (s390_key, "portname") || !strcmp (s390_key, "ctcprot"))
+			if (NM_IN_STRSET (s390_key, "portname", "ctcprot"))
 				continue;
 
+			if (strchr (s390_key, '=')) {
+				/* this key cannot be expressed. But after all, it's not valid anyway
+				 * and the connection shouldn't even verify. */
+				continue;
+			}
+
 			if (!tmp)
 				tmp = g_string_sized_new (30);
 			else
 				g_string_append_c (tmp, ' ');
-			g_string_append_printf (tmp, "%s=%s", s390_key, s390_val);
+			nm_utils_escaped_tokens_escape_gstr (s390_key, NM_ASCII_SPACES, tmp);
+			g_string_append_c (tmp, '=');
+			nm_utils_escaped_tokens_escape_gstr (s390_val, NM_ASCII_SPACES, tmp);
 		}
 		if (tmp)
 			svSetValueStr (ifcfg, "OPTIONS", tmp->str);
@@ -1302,7 +1295,7 @@ write_wired_for_virtual (NMConnection *connection, shvarFile *ifcfg)
 		has_wired = TRUE;
 
 		device_mac = nm_setting_wired_get_mac_address (s_wired);
-		svSetValueStr (ifcfg, "HWADDR", device_mac);
+		svSetValue (ifcfg, "HWADDR", device_mac ?: "");
 
 		cloned_mac = nm_setting_wired_get_cloned_mac_address (s_wired);
 		svSetValueStr (ifcfg, "MACADDR", cloned_mac);
@@ -1856,6 +1849,7 @@ write_connection_setting (NMSettingConnection *s_con, shvarFile *ifcfg)
 	GString *str;
 	const char *master, *master_iface = NULL, *type;
 	int vint;
+	gint32 vint32;
 	NMSettingConnectionMdns mdns;
 	NMSettingConnectionLlmnr llmnr;
 	guint32 vuint32;
@@ -2022,6 +2016,19 @@ write_connection_setting (NMSettingConnection *s_con, shvarFile *ifcfg)
 	vint = nm_setting_connection_get_auth_retries (s_con);
 	svSetValueInt64_cond (ifcfg, "AUTH_RETRIES", vint >= 0, vint);
 
+	vint32 = nm_setting_connection_get_wait_device_timeout (s_con);
+	if (vint32 == -1)
+		svUnsetValue (ifcfg, "DEVTIMEOUT");
+	else if ((vint32 % 1000) == 0)
+		svSetValueInt64 (ifcfg, "DEVTIMEOUT", vint32 / 1000);
+	else {
+		char b[100];
+
+		svSetValueStr (ifcfg,
+		               "DEVTIMEOUT",
+		               nm_sprintf_buf (b, "%.3f", ((double) vint) / 1000.0));
+	}
+
 	mdns = nm_setting_connection_get_mdns (s_con);
 	if (mdns != NM_SETTING_CONNECTION_MDNS_DEFAULT) {
 		svSetValueEnum (ifcfg, "MDNS", nm_setting_connection_mdns_get_type (),
@@ -2797,10 +2804,17 @@ write_ip6_setting (NMConnection *connection,
 
 	value = nm_setting_ip_config_get_method (s_ip6);
 	g_assert (value);
+	svUnsetValue (ifcfg, "IPV6_DISABLED");
 	if (!strcmp (value, NM_SETTING_IP6_CONFIG_METHOD_IGNORE)) {
 		svSetValueStr (ifcfg, "IPV6INIT", "no");
 		svUnsetValue (ifcfg, "DHCPV6C");
 		return TRUE;
+	} else if (!strcmp (value, NM_SETTING_IP6_CONFIG_METHOD_DISABLED)) {
+		svSetValueStr (ifcfg, "IPV6_DISABLED", "yes");
+		svSetValueStr (ifcfg, "IPV6INIT", "no");
+		svUnsetValue (ifcfg, "DHCPV6C");
+		svUnsetValue (ifcfg, "IPV6_AUTOCONF");
+		return TRUE;
 	} else if (!strcmp (value, NM_SETTING_IP6_CONFIG_METHOD_AUTO)) {
 		svSetValueStr (ifcfg, "IPV6INIT", "yes");
 		svSetValueStr (ifcfg, "IPV6_AUTOCONF", "yes");
@@ -3024,6 +3038,8 @@ static gboolean
 do_write_construct (NMConnection *connection,
                     const char *ifcfg_dir,
                     const char *filename,
+                    NMSIfcfgRHWriterAllowFilenameCb allow_filename_cb,
+                    gpointer allow_filename_user_data,
                     shvarFile **out_ifcfg,
                     GHashTable **out_blobs,
                     GHashTable **out_secrets,
@@ -3067,30 +3083,31 @@ do_write_construct (NMConnection *connection,
 
 		ifcfg_name = g_strdup (filename);
 	} else if (ifcfg_dir) {
-		char *escaped;
+		gs_free char *escaped = NULL;
+		int i_path;
 
 		escaped = escape_id (nm_setting_connection_get_id (s_con));
-		ifcfg_name = g_strdup_printf ("%s/ifcfg-%s", ifcfg_dir, escaped);
 
-		/* If a file with this path already exists then we need another name.
-		 * Multiple connections can have the same ID (ie if two connections with
-		 * the same ID are visible to different users) but of course can't have
-		 * the same path.
-		 */
-		if (g_file_test (ifcfg_name, G_FILE_TEST_EXISTS)) {
-			guint32 idx = 0;
-
-			nm_clear_g_free (&ifcfg_name);
-			while (idx++ < 500) {
-				ifcfg_name = g_strdup_printf ("%s/ifcfg-%s-%u", ifcfg_dir, escaped, idx);
-				if (g_file_test (ifcfg_name, G_FILE_TEST_EXISTS) == FALSE)
-					break;
-				nm_clear_g_free (&ifcfg_name);
-			}
+		for (i_path = 0; i_path < 10000; i_path++) {
+			gs_free char *path_candidate = NULL;
+
+			if (i_path == 0)
+				path_candidate = g_strdup_printf ("%s/ifcfg-%s", ifcfg_dir, escaped);
+			else
+				path_candidate = g_strdup_printf ("%s/ifcfg-%s-%d", ifcfg_dir, escaped, i_path);
+
+			if (   allow_filename_cb
+			    && !allow_filename_cb (path_candidate, allow_filename_user_data))
+				continue;
+
+			if (g_file_test (path_candidate, G_FILE_TEST_EXISTS))
+				continue;
+
+			ifcfg_name = g_steal_pointer (&path_candidate);
+			break;
 		}
-		g_free (escaped);
 
-		if (ifcfg_name == NULL) {
+		if (!ifcfg_name) {
 			g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
 			                     "Failed to find usable ifcfg file name");
 			return FALSE;
@@ -3329,54 +3346,12 @@ do_write_to_disk (NMConnection *connection,
 	return TRUE;
 }
 
-static gboolean
-do_write_reread (NMConnection *connection,
-                 const char *ifcfg_name,
-                 NMConnection **out_reread,
-                 gboolean *out_reread_same,
-                 GError **error)
-{
-	gs_unref_object NMConnection *reread = NULL;
-	gs_free_error GError *local = NULL;
-	gs_free char *unhandled = NULL;
-	gboolean reread_same = FALSE;
-
-	nm_assert (!out_reread || !*out_reread);
-
-	reread = connection_from_file (ifcfg_name, &unhandled, &local, NULL);
-
-	if (!reread) {
-		g_propagate_error (error, local);
-		local = NULL;
-		return FALSE;
-	}
-	if (unhandled) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
-		             "connection is unhandled");
-		return FALSE;
-	}
-	if (out_reread_same) {
-		if (nm_connection_compare (reread, connection, NM_SETTING_COMPARE_FLAG_EXACT))
-			reread_same = TRUE;
-
-		nm_assert (reread_same == nm_connection_compare (connection, reread, NM_SETTING_COMPARE_FLAG_EXACT));
-		nm_assert (reread_same == ({
-		                                gs_unref_hashtable GHashTable *_settings = NULL;
-
-		                                (   nm_connection_diff (reread, connection, NM_SETTING_COMPARE_FLAG_EXACT, &_settings)
-		                                 && !_settings);
-		                           }));
-	}
-
-	NM_SET_OUT (out_reread, g_steal_pointer (&reread));
-	NM_SET_OUT (out_reread_same, reread_same);
-	return TRUE;
-}
-
 gboolean
 nms_ifcfg_rh_writer_write_connection (NMConnection *connection,
                                       const char *ifcfg_dir,
                                       const char *filename,
+                                      NMSIfcfgRHWriterAllowFilenameCb allow_filename_cb,
+                                      gpointer allow_filename_user_data,
                                       char **out_filename,
                                       NMConnection **out_reread,
                                       gboolean *out_reread_same,
@@ -3389,13 +3364,14 @@ nms_ifcfg_rh_writer_write_connection (NMConnection *connection,
 	nm_auto_free_gstring GString *route6_content = NULL;
 	gs_unref_hashtable GHashTable *secrets = NULL;
 	gs_unref_hashtable GHashTable *blobs = NULL;
-	GError *local = NULL;
 
 	nm_assert (!out_reread || !*out_reread);
 
 	if (!do_write_construct (connection,
 	                         ifcfg_dir,
 	                         filename,
+	                         allow_filename_cb,
+	                         allow_filename_user_data,
 	                         &ifcfg,
 	                         &blobs,
 	                         &secrets,
@@ -3424,28 +3400,46 @@ nms_ifcfg_rh_writer_write_connection (NMConnection *connection,
 
 	/* Note that we just wrote the connection to disk, and re-read it from there.
 	 * That is racy if somebody else modifies the connection.
+	 * That race is why we must not tread a failure to re-read the profile
+	 * as an error.
 	 *
-	 * A better solution might be, to re-read the connection only based on the
-	 * in-memory representation of what we collected above. But the reader
+	 * FIXME: a much better solution might be, to re-read the connection only based
+	 * on the in-memory representation of what we collected above. But the reader
 	 * does not yet allow to inject the configuration. */
-	if (out_reread || out_reread_same) {
-		if (!do_write_reread (connection,
-		                      svFileGetName (ifcfg),
-		                      out_reread,
-		                      out_reread_same,
-		                      &local)) {
+	if (   out_reread
+	    || out_reread_same) {
+		gs_unref_object NMConnection *reread = NULL;
+		gboolean reread_same = FALSE;
+		gs_free_error GError *local = NULL;
+		gs_free char *unhandled = NULL;
+
+		reread = connection_from_file (svFileGetName (ifcfg),
+		                               &unhandled,
+		                               &local,
+		                               NULL);
+		nm_assert ((NM_IS_CONNECTION (reread) && !local) || (!reread && local));
+
+		if (!reread) {
 			_LOGW ("write: failure to re-read connection \"%s\": %s",
 			       svFileGetName (ifcfg), local->message);
-			g_clear_error (&local);
+		} else if (unhandled) {
+			g_clear_object (&reread);
+			_LOGW ("write: failure to re-read connection \"%s\": %s",
+			       svFileGetName (ifcfg), "connection is unhandled");
 		} else {
-			if (   out_reread_same
-			    && !*out_reread_same) {
-				_LOGD ("write: connection %s (%s) was modified by persisting it to \"%s\" ",
-				       nm_connection_get_id (connection),
-				       nm_connection_get_uuid (connection),
-				       svFileGetName (ifcfg));
+			if (out_reread_same) {
+				reread_same = nm_connection_compare (reread, connection, NM_SETTING_COMPARE_FLAG_EXACT);
+				if (!reread_same) {
+					_LOGD ("write: connection %s (%s) was modified by persisting it to \"%s\" ",
+					       nm_connection_get_id (connection),
+					       nm_connection_get_uuid (connection),
+					       svFileGetName (ifcfg));
+				}
 			}
 		}
+
+		NM_SET_OUT (out_reread, g_steal_pointer (&reread));
+		NM_SET_OUT (out_reread_same, reread_same);
 	}
 
 	/* Only return the filename if this was a newly written ifcfg */
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.h b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.h
index d7a255a9..0902daee 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.h
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager system settings service - keyfile plugin
  *
  * This program is free software; you can redistribute it and/or modify
@@ -23,12 +22,18 @@
 
 #include "nm-connection.h"
 
+
+typedef gboolean (*NMSIfcfgRHWriterAllowFilenameCb) (const char *check_filename,
+                                                     gpointer allow_filename_user_data);
+
 gboolean nms_ifcfg_rh_writer_can_write_connection (NMConnection *connection,
                                                    GError **error);
 
 gboolean nms_ifcfg_rh_writer_write_connection (NMConnection *connection,
                                                const char *ifcfg_dir,
                                                const char *filename,
+                                               NMSIfcfgRHWriterAllowFilenameCb allow_filename_cb,
+                                               gpointer allow_filename_user_data,
                                                char **out_filename,
                                                NMConnection **out_reread,
                                                gboolean *out_reread_same,
diff --git a/src/settings/plugins/ifcfg-rh/shvar.c b/src/settings/plugins/ifcfg-rh/shvar.c
index b399a17f..94e31aac 100644
--- a/src/settings/plugins/ifcfg-rh/shvar.c
+++ b/src/settings/plugins/ifcfg-rh/shvar.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * shvar.c
  *
diff --git a/src/settings/plugins/ifcfg-rh/shvar.h b/src/settings/plugins/ifcfg-rh/shvar.h
index b38a8557..67fb5404 100644
--- a/src/settings/plugins/ifcfg-rh/shvar.h
+++ b/src/settings/plugins/ifcfg-rh/shvar.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * shvar.h
  *
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bond_Main.cexpected b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bond_Main.cexpected
index 5d81dfef..36df7712 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bond_Main.cexpected
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bond_Main.cexpected
@@ -1,6 +1,7 @@
 BONDING_OPTS="downdelay=5 miimon=100 mode=balance-rr updelay=10"
 TYPE=Bond
 BONDING_MASTER=yes
+HWADDR=
 PROXY_METHOD=none
 BROWSER_ONLY=no
 BOOTPROTO=none
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_VLAN_reorder_hdr.cexpected b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_VLAN_reorder_hdr.cexpected
index 339f8107..9c2a1ff0 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_VLAN_reorder_hdr.cexpected
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_VLAN_reorder_hdr.cexpected
@@ -5,6 +5,7 @@ VLAN_ID=444
 REORDER_HDR=yes
 GVRP=no
 MVRP=no
+HWADDR=
 PROXY_METHOD=none
 BROWSER_ONLY=no
 BOOTPROTO=dhcp
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Vlan_test-vlan-interface.cexpected b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Vlan_test-vlan-interface.cexpected
index 793713ea..44eb777c 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Vlan_test-vlan-interface.cexpected
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Vlan_test-vlan-interface.cexpected
@@ -8,6 +8,7 @@ VLAN_FLAGS=LOOSE_BINDING
 MVRP=no
 VLAN_INGRESS_PRIORITY_MAP=0:1,2:5
 VLAN_EGRESS_PRIORITY_MAP=3:1,12:3,14:7
+HWADDR=
 PROXY_METHOD=none
 BROWSER_ONLY=no
 BOOTPROTO=none
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-team-slave-enp31s0f1-142.cexpected b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-team-slave-enp31s0f1-142.cexpected
index 87980dc4..b01372af 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-team-slave-enp31s0f1-142.cexpected
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-team-slave-enp31s0f1-142.cexpected
@@ -4,6 +4,7 @@ VLAN_ID=142
 REORDER_HDR=yes
 GVRP=no
 MVRP=no
+HWADDR=
 NAME=team-slave-enp31s0f1-142
 UUID=74f435bb-ede4-415a-9d48-f580b60eba04
 DEVICE=enp31s0f1-142
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-ip6-disabled.cexpected b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-ip6-disabled.cexpected
new file mode 100644
index 00000000..cae51b89
--- /dev/null
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-ip6-disabled.cexpected
@@ -0,0 +1,11 @@
+TYPE=Ethernet
+PROXY_METHOD=none
+BROWSER_ONLY=no
+BOOTPROTO=dhcp
+DEFROUTE=yes
+IPV4_FAILURE_FATAL=no
+IPV6_DISABLED=yes
+IPV6INIT=no
+NAME="Test Write Wired Disabled IP6"
+UUID=${UUID}
+ONBOOT=yes
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-p12-no-client-cert b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-p12-no-client-cert
new file mode 100644
index 00000000..24397473
--- /dev/null
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-p12-no-client-cert
@@ -0,0 +1,13 @@
+# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile)
+TYPE=Ethernet
+DEVICE=eth0
+HWADDR=00:11:22:33:44:ee
+BOOTPROTO=dhcp
+ONBOOT=yes
+NM_CONTROLLED=yes
+KEY_MGMT=IEEE8021X
+IEEE_8021X_EAP_METHODS=TLS
+IEEE_8021X_IDENTITY="David Smith"
+IEEE_8021X_CA_CERT=test_ca_cert.pem
+IEEE_8021X_PRIVATE_KEY=test_client.p12
+IEEE_8021X_PRIVATE_KEY_PASSWORD="test1"
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/test_client.p12 b/src/settings/plugins/ifcfg-rh/tests/network-scripts/test_client.p12
new file mode 100644
index 00000000..edc2af75
--- /dev/null
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/test_client.p12
Binary files differdiff --git a/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c b/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
index 49ab04d4..45e90b91 100644
--- a/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
+++ b/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager system settings service - keyfile plugin
  *
  * This program is free software; you can redistribute it and/or modify
@@ -239,7 +238,7 @@ _assert_expected_content (NMConnection *connection, const char *filename, const
 		g_assert (_ifcfg_dir && _ifcfg_dir[0]); \
 		g_assert (_filename && _filename[0]); \
 		\
-		_success = nms_ifcfg_rh_writer_write_connection (_connection, _ifcfg_dir, _filename, NULL, _out_reread, _out_reread_same, &_error); \
+		_success = nms_ifcfg_rh_writer_write_connection (_connection, _ifcfg_dir, _filename, NULL, NULL, NULL, _out_reread, _out_reread_same, &_error); \
 		nmtst_assert_success (_success, _error); \
 		_assert_expected_content (_connection, _filename, _expected); \
 	} G_STMT_END
@@ -310,7 +309,7 @@ _writer_new_connection_reread (NMConnection *connection,
 	char *filename = NULL;
 	gs_unref_object NMConnection *con_verified = NULL;
 	gs_unref_object NMConnection *reread_copy = NULL;
-	NMConnection **reread = out_reread ?: ((nmtst_get_rand_int () % 2) ? &reread_copy : NULL);
+	NMConnection **reread = out_reread ?: ((nmtst_get_rand_uint32 () % 2) ? &reread_copy : NULL);
 
 	g_assert (NM_IS_CONNECTION (connection));
 	g_assert (ifcfg_dir);
@@ -320,6 +319,8 @@ _writer_new_connection_reread (NMConnection *connection,
 	success = nms_ifcfg_rh_writer_write_connection (con_verified,
 	                                                ifcfg_dir,
 	                                                NULL,
+	                                                NULL,
+	                                                NULL,
 	                                                &filename,
 	                                                reread,
 	                                                out_reread_same,
@@ -395,6 +396,8 @@ _writer_new_connection_fail (NMConnection *connection,
 	success = nms_ifcfg_rh_writer_write_connection (connection_normalized,
 	                                                ifcfg_dir,
 	                                                NULL,
+	                                                NULL,
+	                                                NULL,
 	                                                &filename,
 	                                                &reread,
 	                                                NULL,
@@ -718,7 +721,7 @@ test_read_unmanaged_unrecognized (void)
 	connection = _connection_from_file (TEST_IFCFG_DIR"/ifcfg-test-nm-controlled-unrecognized",
 	                                    NULL, NULL,
 	                                    &unhandled_spec);
-	g_assert_cmpstr (unhandled_spec, ==, "unmanaged:interface-name:ipoac0");
+	g_assert_cmpstr (unhandled_spec, ==, "unmanaged:interface-name:=ipoac0");
 
 	/* ===== CONNECTION SETTING ===== */
 	s_con = nm_connection_get_setting_connection (connection);
@@ -1974,6 +1977,27 @@ test_read_802_1x_ttls_eapgtc (void)
 }
 
 static void
+test_read_802_1x_tls_p12_no_client_cert (void)
+{
+	gs_unref_object NMConnection *connection = NULL;
+	NMSetting8021x *s_8021x;
+	const char *path;
+
+	connection = _connection_from_file (TEST_IFCFG_DIR"/ifcfg-test-wired-8021x-tls-p12-no-client-cert",
+	                                    NULL, TYPE_ETHERNET, NULL);
+
+	s_8021x = nm_connection_get_setting_802_1x (connection);
+	g_assert (s_8021x);
+
+	g_assert_cmpint (nm_setting_802_1x_get_private_key_scheme (s_8021x), ==, NM_SETTING_802_1X_CK_SCHEME_PATH);
+	path = nm_setting_802_1x_get_private_key_path (s_8021x);
+	g_assert (path);
+
+	g_assert_cmpint (nm_setting_802_1x_get_client_cert_scheme (s_8021x), ==, NM_SETTING_802_1X_CK_SCHEME_PATH);
+	g_assert_cmpstr (path, ==, nm_setting_802_1x_get_client_cert_path (s_8021x));
+}
+
+static void
 test_read_write_802_1x_password_raw (void)
 {
 	nmtst_auto_unlinkfile char *testfile = NULL;
@@ -4768,6 +4792,48 @@ test_write_wired_static_ip6_only (void)
 	nmtst_assert_connection_equals (connection, TRUE, reread, FALSE);
 }
 
+static void
+test_write_ip6_disabled (void)
+{
+	nmtst_auto_unlinkfile char *testfile = NULL;
+	gs_unref_object NMConnection *connection = NULL;
+	gs_unref_object NMConnection *reread = NULL;
+	NMSettingConnection *s_con;
+	NMSettingWired *s_wired;
+	NMSettingIPConfig *s_ip4;
+	NMSettingIPConfig *s_ip6;
+
+	connection = nmtst_create_minimal_connection ("Test Write Wired Disabled IP6",
+	                                              NULL,
+	                                              NM_SETTING_WIRED_SETTING_NAME,
+	                                              &s_con);
+
+	s_wired = (NMSettingWired *) nm_setting_wired_new ();
+	nm_connection_add_setting (connection, NM_SETTING (s_wired));
+
+	s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new ();
+	nm_connection_add_setting (connection, NM_SETTING (s_ip4));
+	g_object_set (s_ip4,
+	              NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO,
+	              NULL);
+
+	s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new ();
+	nm_connection_add_setting (connection, NM_SETTING (s_ip6));
+	g_object_set (s_ip6,
+	              NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_DISABLED,
+	              NULL);
+
+	nmtst_assert_connection_verifies (connection);
+
+	_writer_new_connec_exp (connection,
+	                        TEST_SCRATCH_DIR_TMP,
+	                        TEST_IFCFG_DIR"/ifcfg-test-ip6-disabled.cexpected",
+	                        &testfile);
+
+	reread = _connection_from_file (testfile, NULL, TYPE_ETHERNET, NULL);
+	nmtst_assert_connection_equals (connection, TRUE, reread, FALSE);
+}
+
 /* Test writing an IPv6 config with varying gateway address.
  * For missing gateway (::), we expect no IPV6_DEFAULTGW to be written
  * to ifcfg-rh.
@@ -8938,25 +9004,16 @@ static void
 test_read_team_master_invalid (gconstpointer user_data)
 {
 	const char *const PATH_NAME = user_data;
-	NMConnection *connection;
-	NMSettingConnection *s_con;
-	NMSettingTeam *s_team;
-
-	NMTST_EXPECT_NM_WARN ("*ignoring invalid team configuration*");
-	connection = _connection_from_file (PATH_NAME, NULL, TYPE_ETHERNET, NULL);
-	g_test_assert_expected_messages ();
-
-	g_assert_cmpstr (nm_connection_get_interface_name (connection), ==, "team0");
-
-	s_con = nm_connection_get_setting_connection (connection);
-	g_assert (s_con);
-	g_assert_cmpstr (nm_setting_connection_get_connection_type (s_con), ==, NM_SETTING_TEAM_SETTING_NAME);
+	gs_free_error GError *error = NULL;
+	gs_unref_object NMConnection *connection = NULL;
 
-	s_team = nm_connection_get_setting_team (connection);
-	g_assert (s_team);
-	g_assert (nm_setting_team_get_config (s_team) == NULL);
+	if (WITH_JSON_VALIDATION) {
+		_connection_from_file_fail (PATH_NAME, NULL, TYPE_ETHERNET, &error);
 
-	g_object_unref (connection);
+		g_assert_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY);
+		g_assert (strstr (error->message, _("invalid json")));
+	} else
+		connection = _connection_from_file (PATH_NAME, NULL, TYPE_ETHERNET, NULL);
 }
 
 static void
@@ -9237,19 +9294,19 @@ test_team_reread_slave (void)
 
 	nmtst_assert_connection_equals (connection_1, FALSE, connection_2, FALSE);
 
-	_writer_new_connection_reread ((nmtst_get_rand_int () % 2) ? connection_1 : connection_2,
+	_writer_new_connection_reread ((nmtst_get_rand_uint32 () % 2) ? connection_1 : connection_2,
 	                               TEST_SCRATCH_DIR,
 	                               &testfile,
 	                               TEST_IFCFG_DIR"/ifcfg-team-slave-enp31s0f1-142.cexpected",
 	                               &reread,
 	                               &reread_same);
-	_assert_reread_same ((nmtst_get_rand_int () % 2) ? connection_1 : connection_2, reread);
+	_assert_reread_same ((nmtst_get_rand_uint32 () % 2) ? connection_1 : connection_2, reread);
 	g_assert (reread_same);
 	g_clear_object (&reread);
 
 	reread = _connection_from_file (testfile, NULL, TYPE_VLAN,
 	                                NULL);
-	nmtst_assert_connection_equals ((nmtst_get_rand_int () % 2) ? connection_1 : connection_2, FALSE,
+	nmtst_assert_connection_equals ((nmtst_get_rand_uint32 () % 2) ? connection_1 : connection_2, FALSE,
 	                                reread, FALSE);
 }
 
@@ -9414,7 +9471,7 @@ do_svUnescape_combine_ansi (GString *str_val, GString *str_exp, const UnescapeTe
 	g_string_append (str_val, "$'");
 	if (idx < 0) {
 		for (i = -idx; i > 0; i--) {
-			j = nmtst_get_rand_int () % data_len;
+			j = nmtst_get_rand_uint32 () % data_len;
 			if (!data_ansi[j].can_concat) {
 				i++;
 				continue;
@@ -9613,7 +9670,7 @@ test_svUnescape (void)
 
 	/* different values can be just concatenated... */
 	for (i = 0; i < 200; i++) {
-		gsize num_concat = (nmtst_get_rand_int () % 5) + 2;
+		gsize num_concat = (nmtst_get_rand_uint32 () % 5) + 2;
 
 		g_string_set_size (str_val, 0);
 		g_string_set_size (str_exp, 0);
@@ -9621,12 +9678,12 @@ test_svUnescape (void)
 		while (num_concat > 0) {
 			gsize idx;
 
-			if ((nmtst_get_rand_int () % 3 == 0)) {
-				do_svUnescape_combine_ansi (str_val2, str_exp2, data_ansi, G_N_ELEMENTS (data_ansi), -((int) ((nmtst_get_rand_int () % 5) + 1)));
+			if ((nmtst_get_rand_uint32 () % 3 == 0)) {
+				do_svUnescape_combine_ansi (str_val2, str_exp2, data_ansi, G_N_ELEMENTS (data_ansi), -((int) ((nmtst_get_rand_uint32 () % 5) + 1)));
 				continue;
 			}
 
-			idx = nmtst_get_rand_int () % G_N_ELEMENTS (data_full);
+			idx = nmtst_get_rand_uint32 () % G_N_ELEMENTS (data_full);
 			if (!data_full[idx].can_concat)
 				continue;
 			g_string_append (str_val, data_full[idx].val);
@@ -9634,7 +9691,7 @@ test_svUnescape (void)
 			num_concat--;
 		}
 
-		switch (nmtst_get_rand_int () % 3) {
+		switch (nmtst_get_rand_uint32 () % 3) {
 		case 0:
 			g_string_append (str_val, " ");
 			break;
@@ -9642,7 +9699,7 @@ test_svUnescape (void)
 			g_string_append (str_val, "    ");
 			break;
 		}
-		switch (nmtst_get_rand_int () % 3) {
+		switch (nmtst_get_rand_uint32 () % 3) {
 		case 0:
 			g_string_append (str_val, " #");
 			break;
@@ -10177,6 +10234,7 @@ int main (int argc, char **argv)
 	g_test_add_data_func (TPATH "static-ip6-only-gw/::", "::", test_write_wired_static_ip6_only_gw);
 	g_test_add_data_func (TPATH "static-ip6-only-gw/2001:db8:8:4::2", "2001:db8:8:4::2", test_write_wired_static_ip6_only_gw);
 	g_test_add_data_func (TPATH "static-ip6-only-gw/::ffff:255.255.255.255", "::ffff:255.255.255.255", test_write_wired_static_ip6_only_gw);
+	g_test_add_func (TPATH "ip6/disabled", test_write_ip6_disabled);
 	g_test_add_func (TPATH "read-dns-options", test_read_dns_options);
 	g_test_add_func (TPATH "clear-master", test_clear_master);
 
@@ -10223,6 +10281,8 @@ int main (int argc, char **argv)
 	g_test_add_func (TPATH "802-1x/subj-matches", test_read_write_802_1X_subj_matches);
 	g_test_add_func (TPATH "802-1x/ttls-eapgtc", test_read_802_1x_ttls_eapgtc);
 	g_test_add_func (TPATH "802-1x/password_raw", test_read_write_802_1x_password_raw);
+	g_test_add_func (TPATH "802-1x/tls-p12-no-client-cert", test_read_802_1x_tls_p12_no_client_cert);
+
 	g_test_add_data_func (TPATH "wired/read/aliases/good/0", GINT_TO_POINTER (0), test_read_wired_aliases_good);
 	g_test_add_data_func (TPATH "wired/read/aliases/good/3", GINT_TO_POINTER (3), test_read_wired_aliases_good);
 	g_test_add_func (TPATH "wired/read/aliases/bad1", test_read_wired_aliases_bad_1);
diff --git a/src/settings/plugins/ifupdown/meson.build b/src/settings/plugins/ifupdown/meson.build
index 42edd438..365ae1a9 100644
--- a/src/settings/plugins/ifupdown/meson.build
+++ b/src/settings/plugins/ifupdown/meson.build
@@ -15,7 +15,6 @@ libnms_ifupdown_core = static_library(
 )
 
 sources = files(
-  'nms-ifupdown-connection.c',
   'nms-ifupdown-plugin.c',
 )
 
diff --git a/src/settings/plugins/ifupdown/nms-ifupdown-connection.c b/src/settings/plugins/ifupdown/nms-ifupdown-connection.c
deleted file mode 100644
index d06078a9..00000000
--- a/src/settings/plugins/ifupdown/nms-ifupdown-connection.c
+++ /dev/null
@@ -1,106 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/* NetworkManager system settings service (ifupdown)
- *
- * Alexander Sack <asac@ubuntu.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * (C) Copyright 2007,2008 Canonical Ltd.
- */
-
-#include "nm-default.h"
-
-#include "nms-ifupdown-connection.h"
-
-#include <glib/gstdio.h>
-
-#include "nm-dbus-interface.h"
-#include "nm-utils.h"
-#include "nm-setting-wireless-security.h"
-#include "settings/nm-settings-connection.h"
-#include "settings/nm-settings-plugin.h"
-
-#include "nms-ifupdown-parser.h"
-
-/*****************************************************************************/
-
-struct _NMIfupdownConnection {
-	NMSettingsConnection parent;
-};
-
-struct _NMIfupdownConnectionClass {
-	NMSettingsConnectionClass parent;
-};
-
-G_DEFINE_TYPE (NMIfupdownConnection, nm_ifupdown_connection, NM_TYPE_SETTINGS_CONNECTION)
-
-/*****************************************************************************/
-
-#define _NMLOG_PREFIX_NAME      "ifupdown"
-#define _NMLOG_DOMAIN           LOGD_SETTINGS
-#define _NMLOG(level, ...) \
-    nm_log ((level), _NMLOG_DOMAIN, NULL, NULL, \
-            "%s" _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
-            _NMLOG_PREFIX_NAME": " \
-            _NM_UTILS_MACRO_REST (__VA_ARGS__))
-
-/*****************************************************************************/
-
-static gboolean
-supports_secrets (NMSettingsConnection *connection, const char *setting_name)
-{
-	_LOGI ("supports_secrets() for setting_name: '%s'", setting_name);
-
-	return (strcmp (setting_name, NM_SETTING_WIRELESS_SECURITY_SETTING_NAME) == 0);
-}
-
-/*****************************************************************************/
-
-static void
-nm_ifupdown_connection_init (NMIfupdownConnection *connection)
-{
-}
-
-NMIfupdownConnection *
-nm_ifupdown_connection_new (if_block *block)
-{
-	NMIfupdownConnection *connection;
-	GError *error = NULL;
-
-	g_return_val_if_fail (block != NULL, NULL);
-
-	connection = g_object_new (NM_TYPE_IFUPDOWN_CONNECTION, NULL);
-
-	/* FIXME(copy-on-write-connection): avoid modifying NMConnection instances and share them via copy-on-write. */
-	if (!ifupdown_update_connection_from_if_block (nm_settings_connection_get_connection (NM_SETTINGS_CONNECTION (connection)),
-	                                               block,
-	                                               &error)) {
-		_LOGW ("invalid connection read from /etc/network/interfaces: %s",
-		       error->message);
-		g_object_unref (connection);
-		return NULL;
-	}
-
-	return connection;
-}
-
-static void
-nm_ifupdown_connection_class_init (NMIfupdownConnectionClass *ifupdown_connection_class)
-{
-	NMSettingsConnectionClass *connection_class = NM_SETTINGS_CONNECTION_CLASS (ifupdown_connection_class);
-
-	connection_class->supports_secrets = supports_secrets;
-}
-
diff --git a/src/settings/plugins/ifupdown/nms-ifupdown-connection.h b/src/settings/plugins/ifupdown/nms-ifupdown-connection.h
deleted file mode 100644
index 7536c23e..00000000
--- a/src/settings/plugins/ifupdown/nms-ifupdown-connection.h
+++ /dev/null
@@ -1,45 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
-/* NetworkManager system settings service (ifupdown)
- *
- * Alexander Sack <asac@ubuntu.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * (C) Copyright 2008 Canonical Ltd.
- */
-
-#ifndef __NETWORKMANAGER_IFUPDOWN_CONNECTION_H__
-#define __NETWORKMANAGER_IFUPDOWN_CONNECTION_H__
-
-#include "settings/nm-settings-connection.h"
-
-#include "nms-ifupdown-interface-parser.h"
-
-#define NM_TYPE_IFUPDOWN_CONNECTION            (nm_ifupdown_connection_get_type ())
-#define NM_IFUPDOWN_CONNECTION(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_IFUPDOWN_CONNECTION, NMIfupdownConnection))
-#define NM_IFUPDOWN_CONNECTION_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_IFUPDOWN_CONNECTION, NMIfupdownConnectionClass))
-#define NM_IS_IFUPDOWN_CONNECTION(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_IFUPDOWN_CONNECTION))
-#define NM_IS_IFUPDOWN_CONNECTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_IFUPDOWN_CONNECTION))
-#define NM_IFUPDOWN_CONNECTION_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_IFUPDOWN_CONNECTION, NMIfupdownConnectionClass))
-
-typedef struct _NMIfupdownConnection NMIfupdownConnection;
-typedef struct _NMIfupdownConnectionClass NMIfupdownConnectionClass;
-
-GType nm_ifupdown_connection_get_type (void);
-
-NMIfupdownConnection *nm_ifupdown_connection_new (if_block *block);
-
-#endif /* __NETWORKMANAGER_IFUPDOWN_CONNECTION_H__ */
diff --git a/src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c b/src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c
index 6587fc84..75f29878 100644
--- a/src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c
+++ b/src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager -- Network link manager
  *
  * Tom Parker <palfrey@tevp.net>
diff --git a/src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.h b/src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.h
index f367f626..308228a4 100644
--- a/src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.h
+++ b/src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager -- Network link manager
  *
  * Tom Parker <palfrey@tevp.net>
diff --git a/src/settings/plugins/ifupdown/nms-ifupdown-parser.c b/src/settings/plugins/ifupdown/nms-ifupdown-parser.c
index 239f6415..41b20850 100644
--- a/src/settings/plugins/ifupdown/nms-ifupdown-parser.c
+++ b/src/settings/plugins/ifupdown/nms-ifupdown-parser.c
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /* NetworkManager system settings service (ifupdown)
  *
  * Alexander Sack <asac@ubuntu.com>
@@ -653,22 +651,21 @@ update_ip6_setting_from_if_block (NMConnection *connection,
 	return TRUE;
 }
 
-gboolean
-ifupdown_update_connection_from_if_block (NMConnection *connection,
-                                          if_block *block,
-                                          GError **error)
+NMConnection *
+ifupdown_new_connection_from_if_block (if_block *block,
+                                       gboolean autoconnect,
+                                       GError **error)
 {
+	gs_unref_object NMConnection *connection = NULL;
 	const char *type;
 	gs_free char *idstr = NULL;
 	gs_free char *uuid = NULL;
 	NMSettingConnection *s_con;
-	gboolean success = FALSE;
 
-	s_con = nm_connection_get_setting_connection (connection);
-	if (!s_con) {
-		s_con = NM_SETTING_CONNECTION (nm_setting_connection_new ());
-		nm_connection_add_setting (connection, NM_SETTING (s_con));
-	}
+	connection = nm_simple_connection_new ();
+
+	s_con = NM_SETTING_CONNECTION (nm_setting_connection_new ());
+	nm_connection_add_setting (connection, NM_SETTING (s_con));
 
 	type = _ifupdownplugin_guess_connection_type (block);
 	idstr = g_strconcat ("Ifupdown (", block->name, ")", NULL);
@@ -679,11 +676,10 @@ ifupdown_update_connection_from_if_block (NMConnection *connection,
 	              NM_SETTING_CONNECTION_INTERFACE_NAME, block->name,
 	              NM_SETTING_CONNECTION_ID, idstr,
 	              NM_SETTING_CONNECTION_UUID, uuid,
-	              NM_SETTING_CONNECTION_READ_ONLY, TRUE,
-	              NM_SETTING_CONNECTION_AUTOCONNECT, FALSE,
+	              NM_SETTING_CONNECTION_AUTOCONNECT, (gboolean) (!!autoconnect),
 	              NULL);
 
-	_LOGI ("update_connection_setting_from_if_block: name:%s, type:%s, id:%s, uuid: %s",
+	_LOGD ("update_connection_setting_from_if_block: name:%s, type:%s, id:%s, uuid: %s",
 	       block->name, type, idstr, nm_setting_connection_get_uuid (s_con));
 
 	if (nm_streq (type, NM_SETTING_WIRED_SETTING_NAME))
@@ -693,13 +689,16 @@ ifupdown_update_connection_from_if_block (NMConnection *connection,
 		update_wireless_security_setting_from_if_block (connection, block);
 	}
 
-	if (ifparser_haskey (block, "inet6"))
-		success = update_ip6_setting_from_if_block (connection, block, error);
-	else
-		success = update_ip4_setting_from_if_block (connection, block, error);
+	if (ifparser_haskey (block, "inet6")) {
+		if (!update_ip6_setting_from_if_block (connection, block, error))
+			return FALSE;
+	} else {
+		if (!update_ip4_setting_from_if_block (connection, block, error))
+			return FALSE;
+	}
 
-	if (success == TRUE)
-		success = nm_connection_verify (connection, error);
+	if (!nm_connection_normalize (connection, NULL, NULL, error))
+		return NULL;
 
-	return success;
+	return g_steal_pointer (&connection);
 }
diff --git a/src/settings/plugins/ifupdown/nms-ifupdown-parser.h b/src/settings/plugins/ifupdown/nms-ifupdown-parser.h
index 6a86bf86..7569648f 100644
--- a/src/settings/plugins/ifupdown/nms-ifupdown-parser.h
+++ b/src/settings/plugins/ifupdown/nms-ifupdown-parser.h
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /* NetworkManager system settings service (ifupdown)
  *
  * Alexander Sack <asac@ubuntu.com>
@@ -21,16 +19,14 @@
  * (C) Copyright 2008 Canonical Ltd.
  */
 
-#ifndef __PARSER_H__
-#define __PARSER_H__
+#ifndef __NMS_IFUPDOWN_PARSER_H__
+#define __NMS_IFUPDOWN_PARSER_H__
 
 #include "nm-connection.h"
-
 #include "nms-ifupdown-interface-parser.h"
 
-gboolean
-ifupdown_update_connection_from_if_block (NMConnection *connection,
-                                          if_block *block,
-                                          GError **error);
+NMConnection *ifupdown_new_connection_from_if_block (if_block *block,
+                                                     gboolean autoconnect,
+                                                     GError **error);
 
-#endif /* __PARSER_H__ */
+#endif /* __NMS_IFUPDOWN_PARSER_H__ */
diff --git a/src/settings/plugins/ifupdown/nms-ifupdown-plugin.c b/src/settings/plugins/ifupdown/nms-ifupdown-plugin.c
index 04281f00..93f1813c 100644
--- a/src/settings/plugins/ifupdown/nms-ifupdown-plugin.c
+++ b/src/settings/plugins/ifupdown/nms-ifupdown-plugin.c
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /* NetworkManager system settings service (ifupdown)
  *
  * Alexander Sack <asac@ubuntu.com>
@@ -26,23 +24,13 @@
 
 #include "nms-ifupdown-plugin.h"
 
-#include <arpa/inet.h>
-#include <gmodule.h>
-
-#include "nm-setting-connection.h"
-#include "nm-dbus-interface.h"
-#include "settings/nm-settings-plugin.h"
-#include "nm-setting-ip4-config.h"
-#include "nm-setting-wireless.h"
-#include "nm-setting-wired.h"
-#include "nm-setting-ppp.h"
-#include "nm-utils.h"
 #include "nm-core-internal.h"
-#include "NetworkManagerUtils.h"
+#include "nm-core-utils.h"
 #include "nm-config.h"
+#include "settings/nm-settings-plugin.h"
+#include "settings/nm-settings-storage.h"
 
 #include "nms-ifupdown-interface-parser.h"
-#include "nms-ifupdown-connection.h"
 #include "nms-ifupdown-parser.h"
 
 #define ENI_INTERFACES_FILE "/etc/network/interfaces"
@@ -52,31 +40,35 @@
 /*****************************************************************************/
 
 typedef struct {
+	NMConnection *connection;
+	NMSettingsStorage *storage;
+} StorageData;
+
+typedef struct {
 	/* Stores an entry for blocks/interfaces read from /e/n/i and (if exists)
-	 * the NMIfupdownConnection associated with the block.
+	 * the StorageData associated with the block.
 	 */
 	GHashTable *eni_ifaces;
 
-	bool ifupdown_managed;
-} SettingsPluginIfupdownPrivate;
+	bool ifupdown_managed:1;
 
-struct _SettingsPluginIfupdown {
+	bool initialized:1;
+
+	bool already_reloaded:1;
+} NMSIfupdownPluginPrivate;
+
+struct _NMSIfupdownPlugin {
 	NMSettingsPlugin parent;
-	SettingsPluginIfupdownPrivate _priv;
+	NMSIfupdownPluginPrivate _priv;
 };
 
-struct _SettingsPluginIfupdownClass {
+struct _NMSIfupdownPluginClass {
 	NMSettingsPluginClass parent;
 };
 
-G_DEFINE_TYPE (SettingsPluginIfupdown, settings_plugin_ifupdown, NM_TYPE_SETTINGS_PLUGIN)
-
-#define SETTINGS_PLUGIN_IFUPDOWN_GET_PRIVATE(self) _NM_GET_PRIVATE (self, SettingsPluginIfupdown, SETTINGS_IS_PLUGIN_IFUPDOWN)
+G_DEFINE_TYPE (NMSIfupdownPlugin, nms_ifupdown_plugin, NM_TYPE_SETTINGS_PLUGIN)
 
-/*****************************************************************************/
-
-static SettingsPluginIfupdown *settings_plugin_ifupdown_get (void);
-NM_DEFINE_SINGLETON_GETTER (SettingsPluginIfupdown, settings_plugin_ifupdown_get, SETTINGS_TYPE_PLUGIN_IFUPDOWN);
+#define NMS_IFUPDOWN_PLUGIN_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMSIfupdownPlugin, NMS_IS_IFUPDOWN_PLUGIN)
 
 /*****************************************************************************/
 
@@ -90,46 +82,146 @@ NM_DEFINE_SINGLETON_GETTER (SettingsPluginIfupdown, settings_plugin_ifupdown_get
 
 /*****************************************************************************/
 
-/* Returns the plugins currently known list of connections.  The returned
- * list is freed by the system settings service.
- */
-static GSList*
-get_connections (NMSettingsPlugin *plugin)
+static GHashTable *load_eni_ifaces (NMSIfupdownPlugin *self);
+
+/*****************************************************************************/
+
+static void
+_storage_data_destroy (StorageData *sd)
+{
+	nm_g_object_unref (sd->connection);
+	nm_g_object_unref (sd->storage);
+	g_slice_free (StorageData, sd);
+}
+
+/*****************************************************************************/
+
+static void
+initialize (NMSIfupdownPlugin *self)
 {
-	SettingsPluginIfupdown *self = SETTINGS_PLUGIN_IFUPDOWN (plugin);
-	SettingsPluginIfupdownPrivate *priv = SETTINGS_PLUGIN_IFUPDOWN_GET_PRIVATE (self);
-	GSList *list = NULL;
+	NMSIfupdownPluginPrivate *priv = NMS_IFUPDOWN_PLUGIN_GET_PRIVATE (self);
+	gboolean ifupdown_managed;
+
+	nm_assert (!priv->initialized);
+
+	priv->initialized = TRUE;
+
+	ifupdown_managed = nm_config_data_get_value_boolean (NM_CONFIG_GET_DATA_ORIG,
+	                                                     NM_CONFIG_KEYFILE_GROUP_IFUPDOWN,
+	                                                     NM_CONFIG_KEYFILE_KEY_IFUPDOWN_MANAGED,
+	                                                     !IFUPDOWN_UNMANAGE_WELL_KNOWN_DEFAULT);
+	_LOGI ("management mode: %s", ifupdown_managed ? "managed" : "unmanaged");
+	priv->ifupdown_managed = ifupdown_managed;
+
+	priv->eni_ifaces = load_eni_ifaces (self);
+}
+
+static void
+reload_connections (NMSettingsPlugin *plugin,
+                    NMSettingsPluginConnectionLoadCallback callback,
+                    gpointer user_data)
+{
+	NMSIfupdownPlugin *self = NMS_IFUPDOWN_PLUGIN (plugin);
+	NMSIfupdownPluginPrivate *priv = NMS_IFUPDOWN_PLUGIN_GET_PRIVATE (self);
+	gs_unref_hashtable GHashTable *eni_ifaces_old = NULL;
 	GHashTableIter iter;
-	void *value;
+	StorageData *sd;
+	StorageData *sd2;
+	const char *block_name;
 
-	if (!priv->ifupdown_managed) {
-		_LOGD ("get_connections: not connections due to managed=false");
-		return NULL;
+	if (!priv->initialized)
+		initialize (self);
+	else if (!priv->already_reloaded) {
+		/* This is the first call to reload, but we are already initialized.
+		 *
+		 * This happens because during start NMSettings first queries unmanaged-specs,
+		 * and then issues a reload call right away.
+		 *
+		 * On future reloads, we really want to load /e/n/i again. */
+		priv->already_reloaded = TRUE;
+	} else {
+		eni_ifaces_old = priv->eni_ifaces;
+		priv->eni_ifaces = load_eni_ifaces (self);
+
+		g_hash_table_iter_init (&iter, eni_ifaces_old);
+		while (g_hash_table_iter_next (&iter, (gpointer *) &block_name, (gpointer *) &sd)) {
+			if (!sd)
+				continue;
+
+			sd2 = g_hash_table_lookup (priv->eni_ifaces, block_name);
+			if (!sd2)
+				continue;
+
+			nm_assert (nm_streq (nm_settings_storage_get_uuid (sd->storage), nm_settings_storage_get_uuid (sd2->storage)));
+			nm_g_object_ref_set (&sd2->storage, sd->storage);
+			g_hash_table_iter_remove (&iter);
+		}
 	}
 
+	if (!priv->ifupdown_managed)
+		_LOGD ("load: no connections due to managed=false");
+
 	g_hash_table_iter_init (&iter, priv->eni_ifaces);
-	while (g_hash_table_iter_next (&iter, NULL, &value)) {
-		if (value)
-			list = g_slist_prepend (list, value);
+	while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &sd)) {
+		gs_unref_object NMConnection *connection = NULL;
+
+		if (!sd)
+			continue;
+
+		connection = g_steal_pointer (&sd->connection);
+
+		if (!priv->ifupdown_managed)
+			continue;
+
+		_LOGD ("load: %s (%s)",
+		        nm_settings_storage_get_uuid (sd->storage),
+		        nm_connection_get_id (connection));
+		callback (plugin,
+		          sd->storage,
+		          connection,
+		          user_data);
 	}
+	if (   eni_ifaces_old
+	    && priv->ifupdown_managed) {
+		g_hash_table_iter_init (&iter, eni_ifaces_old);
+		while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &sd)) {
+			if (!sd)
+				continue;
+			_LOGD ("unload: %s",
+			        nm_settings_storage_get_uuid (sd->storage));
+			callback (plugin,
+			          sd->storage,
+			          NULL,
+			          user_data);
+		}
+	}
+}
+
+/*****************************************************************************/
 
-	_LOGD ("get_connections: %u connections", g_slist_length (list));
-	return list;
+static GSList *
+_unmanaged_specs (GHashTable *eni_ifaces)
+{
+	gs_free const char **keys = NULL;
+	GSList *specs = NULL;
+	guint i, len;
+
+	keys = nm_utils_strdict_get_keys (eni_ifaces, TRUE, &len);
+	for (i = len; i > 0; ) {
+		i--;
+		specs = g_slist_prepend (specs, g_strdup_printf (NM_MATCH_SPEC_INTERFACE_NAME_TAG"=%s", keys[i]));
+	}
+	return specs;
 }
 
-/*
- * Return a list of device specifications which NetworkManager should not
- * manage.  Returned list will be freed by the system settings service, and
- * each element must be allocated using g_malloc() or its variants.
- */
 static GSList*
 get_unmanaged_specs (NMSettingsPlugin *plugin)
 {
-	SettingsPluginIfupdown *self = SETTINGS_PLUGIN_IFUPDOWN (plugin);
-	SettingsPluginIfupdownPrivate *priv = SETTINGS_PLUGIN_IFUPDOWN_GET_PRIVATE (self);
-	GSList *specs = NULL;
-	GHashTableIter iter;
-	const char *iface;
+	NMSIfupdownPlugin *self = NMS_IFUPDOWN_PLUGIN (plugin);
+	NMSIfupdownPluginPrivate *priv = NMS_IFUPDOWN_PLUGIN_GET_PRIVATE (self);
+
+	if (G_UNLIKELY (!priv->initialized))
+		initialize (self);
 
 	if (priv->ifupdown_managed)
 		return NULL;
@@ -137,38 +229,46 @@ get_unmanaged_specs (NMSettingsPlugin *plugin)
 	_LOGD ("unmanaged-specs: unmanaged devices count %u",
 	       g_hash_table_size (priv->eni_ifaces));
 
-	g_hash_table_iter_init (&iter, priv->eni_ifaces);
-	while (g_hash_table_iter_next (&iter, (gpointer) &iface, NULL))
-		specs = g_slist_append (specs, g_strdup_printf ("interface-name:=%s", iface));
-	return specs;
+	return _unmanaged_specs (priv->eni_ifaces);
 }
 
 /*****************************************************************************/
 
-static void
-initialize (NMSettingsPlugin *plugin)
+static GHashTable *
+load_eni_ifaces (NMSIfupdownPlugin *self)
 {
-	SettingsPluginIfupdown *self = SETTINGS_PLUGIN_IFUPDOWN (plugin);
-	SettingsPluginIfupdownPrivate *priv = SETTINGS_PLUGIN_IFUPDOWN_GET_PRIVATE (self);
+	gs_unref_hashtable GHashTable *eni_ifaces = NULL;
 	gs_unref_hashtable GHashTable *auto_ifaces = NULL;
 	nm_auto_ifparser if_parser *parser = NULL;
 	if_block *block;
-	GHashTableIter con_iter;
-	const char *block_name;
-	NMIfupdownConnection *conn;
+	StorageData *sd;
+
+	eni_ifaces = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, (GDestroyNotify) _storage_data_destroy);
 
 	parser = ifparser_parse (ENI_INTERFACES_FILE, 0);
 
 	c_list_for_each_entry (block, &parser->block_lst_head, block_lst) {
-
-		if (NM_IN_STRSET (block->type, "auto", "allow-hotplug")) {
+		if (NM_IN_STRSET (block->type, "auto",
+		                               "allow-hotplug")) {
 			if (!auto_ifaces)
-				auto_ifaces = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, NULL);
-			g_hash_table_add (auto_ifaces, g_strdup (block->name));
-			continue;
+				auto_ifaces = g_hash_table_new (nm_str_hash, g_str_equal);
+			g_hash_table_add (auto_ifaces, (char *) block->name);
 		}
+	}
+
+	c_list_for_each_entry (block, &parser->block_lst_head, block_lst) {
+
+		if (NM_IN_STRSET (block->type, "auto",
+		                               "allow-hotplug"))
+			continue;
 
 		if (nm_streq (block->type, "iface")) {
+			gs_free_error GError *local = NULL;
+			gs_unref_object NMConnection *connection = NULL;
+			gs_unref_object NMSettingsStorage *storage = NULL;
+			const char *uuid = NULL;
+			StorageData *sd_repl;
+
 			/* Bridge configuration */
 			if (g_str_has_prefix (block->name, "br")) {
 				/* Try to find bridge ports */
@@ -201,13 +301,13 @@ initialize (NMSettingsPlugin *plugin)
 						if (nm_streq (token, "none"))
 							continue;
 						if (state == 0) {
-							conn = g_hash_table_lookup (priv->eni_ifaces, block->name);
-							if (!conn) {
+							sd = g_hash_table_lookup (eni_ifaces, block->name);
+							if (!sd) {
 								_LOGD ("parse: adding bridge port \"%s\"", token);
-								g_hash_table_insert (priv->eni_ifaces, g_strdup (token), NULL);
+								g_hash_table_insert (eni_ifaces, g_strdup (token), NULL);
 							} else {
 								_LOGD ("parse: adding bridge port \"%s\" (have connection %s)", token,
-								       nm_settings_connection_get_uuid (NM_SETTINGS_CONNECTION (conn)));
+								       nm_settings_storage_get_uuid (sd->storage));
 							}
 						}
 					}
@@ -219,107 +319,91 @@ initialize (NMSettingsPlugin *plugin)
 			if (nm_streq (block->name, "lo"))
 				continue;
 
-			/* Remove any connection for this block that was previously found */
-			conn = g_hash_table_lookup (priv->eni_ifaces, block->name);
-			if (conn) {
+			sd_repl = g_hash_table_lookup (eni_ifaces, block->name);
+			if (sd_repl) {
+				storage = g_steal_pointer (&sd_repl->storage);
 				_LOGD ("parse: replace connection \"%s\" (%s)",
 				       block->name,
-				       nm_settings_connection_get_uuid (NM_SETTINGS_CONNECTION (conn)));
-				nm_settings_connection_delete (NM_SETTINGS_CONNECTION (conn), NULL);
-				g_hash_table_remove (priv->eni_ifaces, block->name);
+				       nm_settings_storage_get_uuid (sd_repl->storage));
+				g_hash_table_remove (eni_ifaces, block->name);
+			}
+
+			connection = ifupdown_new_connection_from_if_block (block,
+			                                                       auto_ifaces
+			                                                    && g_hash_table_contains (auto_ifaces, block->name),
+			                                                    &local);
+
+			if (!connection) {
+				_LOGD ("parse: adding place holder for \"%s\"%s%s%s",
+				       block->name,
+				       NM_PRINT_FMT_QUOTED (local, " (", local->message, ")", ""));
+				sd = NULL;
+			} else {
+
+				nmtst_connection_assert_unchanging (connection);
+				uuid = nm_connection_get_uuid (connection);
+
+				if (!storage)
+					storage = nm_settings_storage_new (NM_SETTINGS_PLUGIN (self), uuid, NULL);
+
+				sd = g_slice_new (StorageData);
+				*sd = (StorageData) {
+					.connection = g_steal_pointer (&connection),
+					.storage    = g_steal_pointer (&storage),
+				};
+				_LOGD ("parse: adding connection \"%s\" (%s)", block->name, uuid);
 			}
 
-			/* add the new connection */
-			conn = nm_ifupdown_connection_new (block);
-			if (conn) {
-				_LOGD ("parse: adding connection \"%s\" (%s)", block->name,
-				       nm_settings_connection_get_uuid (NM_SETTINGS_CONNECTION (conn)));
-			} else
-				_LOGD ("parse: adding place holder for connection \"%s\"", block->name);
-			g_hash_table_insert (priv->eni_ifaces, g_strdup (block->name), conn);
+			g_hash_table_replace (eni_ifaces, g_strdup (block->name), sd);
 			continue;
 		}
 
 		if (nm_streq (block->type, "mapping")) {
-			conn = g_hash_table_lookup (priv->eni_ifaces, block->name);
-			if (!conn) {
+			sd = g_hash_table_lookup (eni_ifaces, block->name);
+			if (!sd) {
 				_LOGD ("parse: adding mapping \"%s\"", block->name);
-				g_hash_table_insert (priv->eni_ifaces, g_strdup (block->name), NULL);
+				g_hash_table_insert (eni_ifaces, g_strdup (block->name), NULL);
 			} else {
 				_LOGD ("parse: adding mapping \"%s\" (have connection %s)", block->name,
-				       nm_settings_connection_get_uuid (NM_SETTINGS_CONNECTION (conn)));
+				       nm_settings_storage_get_uuid (sd->storage));
 			}
 			continue;
 		}
 	}
 
-	/* Make 'auto' interfaces autoconnect=TRUE */
-	g_hash_table_iter_init (&con_iter, priv->eni_ifaces);
-	while (g_hash_table_iter_next (&con_iter, (gpointer) &block_name, (gpointer) &conn)) {
-		NMSettingConnection *setting;
+	nm_clear_pointer (&auto_ifaces, g_hash_table_destroy);
 
-		if (   !conn
-		    || !auto_ifaces
-		    || !g_hash_table_contains (auto_ifaces, block_name))
-			continue;
-
-		/* FIXME(copy-on-write-connection): avoid modifying NMConnection instances and share them via copy-on-write. */
-		setting = nm_connection_get_setting_connection (nm_settings_connection_get_connection (NM_SETTINGS_CONNECTION (conn)));
-		g_object_set (setting, NM_SETTING_CONNECTION_AUTOCONNECT, TRUE, NULL);
-	}
-
-	/* Check the config file to find out whether to manage interfaces */
-	priv->ifupdown_managed = nm_config_data_get_value_boolean (NM_CONFIG_GET_DATA_ORIG,
-	                                                           NM_CONFIG_KEYFILE_GROUP_IFUPDOWN,
-	                                                           NM_CONFIG_KEYFILE_KEY_IFUPDOWN_MANAGED,
-	                                                           !IFUPDOWN_UNMANAGE_WELL_KNOWN_DEFAULT);
-	_LOGI ("management mode: %s", priv->ifupdown_managed ? "managed" : "unmanaged");
-
-	/* Now if we're running in managed mode, let NM know there are new connections */
-	if (priv->ifupdown_managed) {
-		GHashTableIter iter;
-
-		g_hash_table_iter_init (&iter, priv->eni_ifaces);
-		while (g_hash_table_iter_next (&iter, NULL, (gpointer) &conn)) {
-			if (conn) {
-				_nm_settings_plugin_emit_signal_connection_added (NM_SETTINGS_PLUGIN (self),
-				                                                  NM_SETTINGS_CONNECTION (conn));
-			}
-		}
-	}
+	return g_steal_pointer (&eni_ifaces);
 }
 
 /*****************************************************************************/
 
 static void
-settings_plugin_ifupdown_init (SettingsPluginIfupdown *self)
+nms_ifupdown_plugin_init (NMSIfupdownPlugin *self)
 {
-	SettingsPluginIfupdownPrivate *priv = SETTINGS_PLUGIN_IFUPDOWN_GET_PRIVATE (self);
-
-	priv->eni_ifaces = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, g_object_unref);
 }
 
 static void
 dispose (GObject *object)
 {
-	SettingsPluginIfupdown *plugin = SETTINGS_PLUGIN_IFUPDOWN (object);
-	SettingsPluginIfupdownPrivate *priv = SETTINGS_PLUGIN_IFUPDOWN_GET_PRIVATE (plugin);
+	NMSIfupdownPlugin *plugin = NMS_IFUPDOWN_PLUGIN (object);
+	NMSIfupdownPluginPrivate *priv = NMS_IFUPDOWN_PLUGIN_GET_PRIVATE (plugin);
 
 	g_clear_pointer (&priv->eni_ifaces, g_hash_table_destroy);
 
-	G_OBJECT_CLASS (settings_plugin_ifupdown_parent_class)->dispose (object);
+	G_OBJECT_CLASS (nms_ifupdown_plugin_parent_class)->dispose (object);
 }
 
 static void
-settings_plugin_ifupdown_class_init (SettingsPluginIfupdownClass *klass)
+nms_ifupdown_plugin_class_init (NMSIfupdownPluginClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 	NMSettingsPluginClass *plugin_class = NM_SETTINGS_PLUGIN_CLASS (klass);
 
 	object_class->dispose = dispose;
 
-	plugin_class->initialize          = initialize;
-	plugin_class->get_connections     = get_connections;
+	plugin_class->plugin_name         = "ifupdown";
+	plugin_class->reload_connections  = reload_connections;
 	plugin_class->get_unmanaged_specs = get_unmanaged_specs;
 }
 
@@ -328,5 +412,5 @@ settings_plugin_ifupdown_class_init (SettingsPluginIfupdownClass *klass)
 G_MODULE_EXPORT NMSettingsPlugin *
 nm_settings_plugin_factory (void)
 {
-	return NM_SETTINGS_PLUGIN (g_object_ref (settings_plugin_ifupdown_get ()));
+	return g_object_new (NMS_TYPE_IFUPDOWN_PLUGIN, NULL);
 }
diff --git a/src/settings/plugins/ifupdown/nms-ifupdown-plugin.h b/src/settings/plugins/ifupdown/nms-ifupdown-plugin.h
index c2a3639c..10ea2be4 100644
--- a/src/settings/plugins/ifupdown/nms-ifupdown-plugin.h
+++ b/src/settings/plugins/ifupdown/nms-ifupdown-plugin.h
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /* NetworkManager system settings service (ifupdown)
  *
  * Alexander Sack <asac@ubuntu.com>
@@ -21,21 +19,21 @@
  * (C) Copyright 2008 Canonical Ltd.
  */
 
-#ifndef _PLUGIN_H_
-#define _PLUGIN_H_
+#ifndef __NMS_IFUPDOWN_PLUGIN_H__
+#define __NMS_IFUPDOWN_PLUGIN_H__
 
 #define PLUGIN_NAME "ifupdown"
 
-#define SETTINGS_TYPE_PLUGIN_IFUPDOWN            (settings_plugin_ifupdown_get_type ())
-#define SETTINGS_PLUGIN_IFUPDOWN(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), SETTINGS_TYPE_PLUGIN_IFUPDOWN, SettingsPluginIfupdown))
-#define SETTINGS_PLUGIN_IFUPDOWN_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), SETTINGS_TYPE_PLUGIN_IFUPDOWN, SettingsPluginIfupdownClass))
-#define SETTINGS_IS_PLUGIN_IFUPDOWN(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SETTINGS_TYPE_PLUGIN_IFUPDOWN))
-#define SETTINGS_IS_PLUGIN_IFUPDOWN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SETTINGS_TYPE_PLUGIN_IFUPDOWN))
-#define SETTINGS_PLUGIN_IFUPDOWN_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), SETTINGS_TYPE_PLUGIN_IFUPDOWN, SettingsPluginIfupdownClass))
+#define NMS_TYPE_IFUPDOWN_PLUGIN            (nms_ifupdown_plugin_get_type ())
+#define NMS_IFUPDOWN_PLUGIN(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NMS_TYPE_IFUPDOWN_PLUGIN, NMSIfupdownPlugin))
+#define NMS_IFUPDOWN_PLUGIN_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NMS_TYPE_IFUPDOWN_PLUGIN, NMSIfupdownPluginClass))
+#define NMS_IS_IFUPDOWN_PLUGIN(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NMS_TYPE_IFUPDOWN_PLUGIN))
+#define NMS_IS_IFUPDOWN_PLUGIN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NMS_TYPE_IFUPDOWN_PLUGIN))
+#define NMS_IFUPDOWN_PLUGIN_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NMS_TYPE_IFUPDOWN_PLUGIN, NMSIfupdownPluginClass))
 
-typedef struct _SettingsPluginIfupdown SettingsPluginIfupdown;
-typedef struct _SettingsPluginIfupdownClass SettingsPluginIfupdownClass;
+typedef struct _NMSIfupdownPlugin NMSIfupdownPlugin;
+typedef struct _NMSIfupdownPluginClass NMSIfupdownPluginClass;
 
-GType settings_plugin_ifupdown_get_type (void);
+GType nms_ifupdown_plugin_get_type (void);
 
-#endif /* _PLUGIN_H_ */
+#endif /* __NMS_IFUPDOWN_PLUGIN_H__ */
diff --git a/src/settings/plugins/ifupdown/tests/test-ifupdown.c b/src/settings/plugins/ifupdown/tests/test-ifupdown.c
index 674cb19c..4adcf085 100644
--- a/src/settings/plugins/ifupdown/tests/test-ifupdown.c
+++ b/src/settings/plugins/ifupdown/tests/test-ifupdown.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -31,6 +30,29 @@
 
 /*****************************************************************************/
 
+#define _connection_from_if_block(block) \
+	({ \
+		NMConnection *_con; \
+		if_block *_block = (block); \
+		GError *_local = NULL; \
+		\
+		g_assert (_block); \
+		_con = ifupdown_new_connection_from_if_block (_block, FALSE, &_local); \
+		nmtst_assert_success (NM_IS_CONNECTION (_con), _local); \
+		nmtst_assert_connection_verifies_without_normalization (_con); \
+		_con; \
+	})
+
+#define _connection_first_from_parser(parser) \
+	({ \
+		if_parser *_parser = (parser); \
+		\
+		g_assert (_parser); \
+		_connection_from_if_block (ifparser_getfirst (_parser)); \
+	})
+
+/*****************************************************************************/
+
 typedef struct {
 	char *key;
 	char *data;
@@ -453,26 +475,14 @@ test16_missing_newline (void)
 static void
 test17_read_static_ipv4 (void)
 {
-	NMConnection *connection;
+	gs_unref_object NMConnection *connection = NULL;
 	NMSettingConnection *s_con;
 	NMSettingIPConfig *s_ip4;
 	NMSettingWired *s_wired;
-	GError *error = NULL;
-	gboolean success;
 	NMIPAddress *ip4_addr;
-	if_block *block = NULL;
 	nm_auto_ifparser if_parser *parser = init_ifparser_with_file ("test17-wired-static-verify-ip4");
 
-	block = ifparser_getfirst (parser);
-	connection = nm_simple_connection_new();
-	g_assert (connection);
-
-	ifupdown_update_connection_from_if_block (connection, block, &error);
-	g_assert_no_error (error);
-
-	success = nm_connection_verify (connection, &error);
-	g_assert_no_error (error);
-	g_assert (success);
+	connection = _connection_first_from_parser (parser);
 
 	/* ===== CONNECTION SETTING ===== */
 	s_con = nm_connection_get_setting_connection (connection);
@@ -501,32 +511,19 @@ test17_read_static_ipv4 (void)
 	g_assert_cmpint (nm_setting_ip_config_get_num_dns_searches (s_ip4), ==, 2);
 	g_assert_cmpstr (nm_setting_ip_config_get_dns_search (s_ip4, 0), ==, "example.com");
 	g_assert_cmpstr (nm_setting_ip_config_get_dns_search (s_ip4, 1), ==, "foo.example.com");
-
-	g_object_unref (connection);
 }
 
 static void
 test18_read_static_ipv6 (void)
 {
-	NMConnection *connection;
+	gs_unref_object NMConnection *connection = NULL;
 	NMSettingConnection *s_con;
 	NMSettingIPConfig *s_ip6;
 	NMSettingWired *s_wired;
-	GError *error = NULL;
-	gboolean success;
 	NMIPAddress *ip6_addr;
-	if_block *block = NULL;
 	nm_auto_ifparser if_parser *parser = init_ifparser_with_file ("test18-wired-static-verify-ip6");
 
-	block = ifparser_getfirst (parser);
-	connection = nm_simple_connection_new();
-	g_assert (connection);
-	ifupdown_update_connection_from_if_block (connection, block, &error);
-	g_assert_no_error (error);
-
-	success = nm_connection_verify (connection, &error);
-	g_assert_no_error (error);
-	g_assert (success);
+	connection = _connection_first_from_parser (parser);
 
 	/* ===== CONNECTION SETTING ===== */
 	s_con = nm_connection_get_setting_connection (connection);
@@ -555,30 +552,17 @@ test18_read_static_ipv6 (void)
 	g_assert_cmpint (nm_setting_ip_config_get_num_dns_searches (s_ip6), ==, 2);
 	g_assert_cmpstr (nm_setting_ip_config_get_dns_search (s_ip6, 0), ==, "example.com");
 	g_assert_cmpstr (nm_setting_ip_config_get_dns_search (s_ip6, 1), ==, "foo.example.com");
-
-	g_object_unref (connection);
 }
 
 static void
 test19_read_static_ipv4_plen (void)
 {
-	NMConnection *connection;
+	gs_unref_object NMConnection *connection = NULL;
 	NMSettingIPConfig *s_ip4;
-	GError *error = NULL;
 	NMIPAddress *ip4_addr;
-	if_block *block = NULL;
-	gboolean success;
 	nm_auto_ifparser if_parser *parser = init_ifparser_with_file ("test19-wired-static-verify-ip4-plen");
 
-	block = ifparser_getfirst (parser);
-	connection = nm_simple_connection_new();
-	g_assert (connection);
-	ifupdown_update_connection_from_if_block (connection, block, &error);
-	g_assert_no_error (error);
-
-	success = nm_connection_verify (connection, &error);
-	g_assert_no_error (error);
-	g_assert (success);
+	connection = _connection_first_from_parser (parser);
 
 	/* ===== IPv4 SETTING ===== */
 	s_ip4 = nm_connection_get_setting_ip4_config (connection);
@@ -589,8 +573,6 @@ test19_read_static_ipv4_plen (void)
 	g_assert (ip4_addr != NULL);
 	g_assert_cmpstr (nm_ip_address_get_address (ip4_addr), ==, "10.0.0.3");
 	g_assert_cmpint (nm_ip_address_get_prefix (ip4_addr), ==, 8);
-
-	g_object_unref (connection);
 }
 
 static void
@@ -671,4 +653,3 @@ main (int argc, char **argv)
 
 	return g_test_run ();
 }
-
diff --git a/src/settings/plugins/keyfile/nms-keyfile-connection.c b/src/settings/plugins/keyfile/nms-keyfile-connection.c
deleted file mode 100644
index 3b362978..00000000
--- a/src/settings/plugins/keyfile/nms-keyfile-connection.c
+++ /dev/null
@@ -1,186 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/* NetworkManager system settings service - keyfile plugin
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright (C) 2008 Novell, Inc.
- * Copyright (C) 2008 - 2012 Red Hat, Inc.
- */
-
-#include "nm-default.h"
-
-#include "nms-keyfile-connection.h"
-
-#include <glib/gstdio.h>
-
-#include "nm-dbus-interface.h"
-#include "nm-setting-connection.h"
-#include "nm-utils.h"
-
-#include "settings/nm-settings-plugin.h"
-
-#include "nms-keyfile-reader.h"
-#include "nms-keyfile-writer.h"
-#include "nms-keyfile-utils.h"
-
-/*****************************************************************************/
-
-struct _NMSKeyfileConnection {
-	NMSettingsConnection parent;
-};
-
-struct _NMSKeyfileConnectionClass {
-	NMSettingsConnectionClass parent;
-};
-
-G_DEFINE_TYPE (NMSKeyfileConnection, nms_keyfile_connection, NM_TYPE_SETTINGS_CONNECTION)
-
-/*****************************************************************************/
-
-static gboolean
-commit_changes (NMSettingsConnection *connection,
-                NMConnection *new_connection,
-                NMSettingsConnectionCommitReason commit_reason,
-                NMConnection **out_reread_connection,
-                char **out_logmsg_change,
-                GError **error)
-{
-	gs_free char *path = NULL;
-	gs_unref_object NMConnection *reread = NULL;
-	gboolean reread_same = FALSE;
-
-	nm_assert (out_reread_connection && !*out_reread_connection);
-	nm_assert (!out_logmsg_change || !*out_logmsg_change);
-
-	if (!nms_keyfile_writer_connection (new_connection,
-	                                    TRUE,
-	                                    nm_settings_connection_get_filename (connection),
-	                                    NM_FLAGS_ALL (commit_reason,   NM_SETTINGS_CONNECTION_COMMIT_REASON_USER_ACTION
-	                                                                 | NM_SETTINGS_CONNECTION_COMMIT_REASON_ID_CHANGED),
-	                                    &path,
-	                                    &reread,
-	                                    &reread_same,
-	                                    error))
-		return FALSE;
-
-	if (!nm_streq0 (path, nm_settings_connection_get_filename (connection))) {
-		gs_free char *old_path = g_strdup (nm_settings_connection_get_filename (connection));
-
-		nm_settings_connection_set_filename (connection, path);
-		if (old_path) {
-			NM_SET_OUT (out_logmsg_change,
-			            g_strdup_printf ("keyfile: update "NMS_KEYFILE_CONNECTION_LOG_FMT" and rename from \"%s\"",
-			                             NMS_KEYFILE_CONNECTION_LOG_ARG (connection),
-			                             old_path));
-		} else {
-			NM_SET_OUT (out_logmsg_change,
-			            g_strdup_printf ("keyfile: update "NMS_KEYFILE_CONNECTION_LOG_FMT" and persist connection",
-			                             NMS_KEYFILE_CONNECTION_LOG_ARG (connection)));
-		}
-	} else {
-		NM_SET_OUT (out_logmsg_change,
-		            g_strdup_printf ("keyfile: update "NMS_KEYFILE_CONNECTION_LOG_FMT,
-		                             NMS_KEYFILE_CONNECTION_LOG_ARG (connection)));
-	}
-
-	if (reread && !reread_same)
-		*out_reread_connection = g_steal_pointer (&reread);
-
-	return TRUE;
-}
-
-static gboolean
-delete (NMSettingsConnection *connection,
-        GError **error)
-{
-	const char *path;
-
-	path = nm_settings_connection_get_filename (connection);
-	if (path)
-		g_unlink (path);
-	return TRUE;
-}
-
-/*****************************************************************************/
-
-static void
-nms_keyfile_connection_init (NMSKeyfileConnection *connection)
-{
-}
-
-NMSKeyfileConnection *
-nms_keyfile_connection_new (NMConnection *source,
-                            const char *full_path,
-                            const char *profile_dir,
-                            GError **error)
-{
-	GObject *object;
-	NMConnection *tmp;
-	const char *uuid;
-	gboolean update_unsaved = TRUE;
-
-	nm_assert (source || full_path);
-	nm_assert (!full_path || full_path[0] == '/');
-	nm_assert (!profile_dir || profile_dir[0] == '/');
-
-	/* If we're given a connection already, prefer that instead of re-reading */
-	if (source)
-		tmp = g_object_ref (source);
-	else {
-		tmp = nms_keyfile_reader_from_file (full_path, profile_dir, error);
-		if (!tmp)
-			return NULL;
-
-		uuid = nm_connection_get_uuid (tmp);
-		if (!uuid) {
-			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			             "Connection in file %s had no UUID", full_path);
-			g_object_unref (tmp);
-			return NULL;
-		}
-
-		/* If we just read the connection from disk, it's clearly not Unsaved */
-		update_unsaved = FALSE;
-	}
-
-	object = g_object_new (NMS_TYPE_KEYFILE_CONNECTION,
-	                       NM_SETTINGS_CONNECTION_FILENAME, full_path,
-	                       NULL);
-
-	/* Update our settings with what was read from the file */
-	if (!nm_settings_connection_update (NM_SETTINGS_CONNECTION (object),
-	                                    tmp,
-	                                    update_unsaved
-	                                      ? NM_SETTINGS_CONNECTION_PERSIST_MODE_UNSAVED
-	                                      : NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP_SAVED,
-	                                    NM_SETTINGS_CONNECTION_COMMIT_REASON_NONE,
-	                                    NULL,
-	                                    error)) {
-		g_object_unref (object);
-		object = NULL;
-	}
-
-	g_object_unref (tmp);
-	return (NMSKeyfileConnection *) object;
-}
-
-static void
-nms_keyfile_connection_class_init (NMSKeyfileConnectionClass *keyfile_connection_class)
-{
-	NMSettingsConnectionClass *settings_class = NM_SETTINGS_CONNECTION_CLASS (keyfile_connection_class);
-
-	settings_class->commit_changes = commit_changes;
-	settings_class->delete = delete;
-}
diff --git a/src/settings/plugins/keyfile/nms-keyfile-connection.h b/src/settings/plugins/keyfile/nms-keyfile-connection.h
deleted file mode 100644
index 0773ced0..00000000
--- a/src/settings/plugins/keyfile/nms-keyfile-connection.h
+++ /dev/null
@@ -1,44 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/* NetworkManager system settings service - keyfile plugin
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright (C) 2008 Novell, Inc.
- * Copyright (C) 2008 - 2012 Red Hat, Inc.
- */
-
-#ifndef __NMS_KEYFILE_CONNECTION_H__
-#define __NMS_KEYFILE_CONNECTION_H__
-
-#include "settings/nm-settings-connection.h"
-
-#define NMS_TYPE_KEYFILE_CONNECTION            (nms_keyfile_connection_get_type ())
-#define NMS_KEYFILE_CONNECTION(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NMS_TYPE_KEYFILE_CONNECTION, NMSKeyfileConnection))
-#define NMS_KEYFILE_CONNECTION_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NMS_TYPE_KEYFILE_CONNECTION, NMSKeyfileConnectionClass))
-#define NMS_IS_KEYFILE_CONNECTION(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NMS_TYPE_KEYFILE_CONNECTION))
-#define NMS_IS_KEYFILE_CONNECTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NMS_TYPE_KEYFILE_CONNECTION))
-#define NMS_KEYFILE_CONNECTION_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NMS_TYPE_KEYFILE_CONNECTION, NMSKeyfileConnectionClass))
-
-typedef struct _NMSKeyfileConnection NMSKeyfileConnection;
-typedef struct _NMSKeyfileConnectionClass NMSKeyfileConnectionClass;
-
-GType nms_keyfile_connection_get_type (void);
-
-NMSKeyfileConnection *nms_keyfile_connection_new (NMConnection *source,
-                                                  const char *full_path,
-                                                  const char *profile_dir,
-                                                  GError **error);
-
-#endif /* __NMS_KEYFILE_CONNECTION_H__ */
diff --git a/src/settings/plugins/keyfile/nms-keyfile-plugin.c b/src/settings/plugins/keyfile/nms-keyfile-plugin.c
index c13cc1ff..fbe70ef4 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-plugin.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-plugin.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager system settings service - keyfile plugin
  *
  * This program is free software; you can redistribute it and/or modify
@@ -16,7 +15,7 @@
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
  * Copyright (C) 2008 Novell, Inc.
- * Copyright (C) 2008 - 2013 Red Hat, Inc.
+ * Copyright (C) 2008 - 2018 Red Hat, Inc.
  */
 
 #include "nm-default.h"
@@ -26,7 +25,11 @@
 #include <sys/stat.h>
 #include <unistd.h>
 #include <sys/types.h>
-#include <glib/gstdio.h>
+#include <sys/time.h>
+
+#include "nm-std-aux/c-list-util.h"
+#include "nm-glib-aux/nm-c-list.h"
+#include "nm-glib-aux/nm-io-utils.h"
 
 #include "nm-connection.h"
 #include "nm-setting.h"
@@ -36,22 +39,42 @@
 #include "nm-core-internal.h"
 #include "nm-keyfile-internal.h"
 
+#include "systemd/nm-sd-utils-shared.h"
+
 #include "settings/nm-settings-plugin.h"
+#include "settings/nm-settings-storage.h"
+#include "settings/nm-settings-utils.h"
 
-#include "nms-keyfile-connection.h"
+#include "nms-keyfile-storage.h"
 #include "nms-keyfile-writer.h"
+#include "nms-keyfile-reader.h"
 #include "nms-keyfile-utils.h"
 
 /*****************************************************************************/
 
 typedef struct {
-	GHashTable *connections;  /* uuid::connection */
-
-	gboolean initialized;
-	GFileMonitor *monitor;
-	gulong monitor_id;
 
 	NMConfig *config;
+
+	/* there can/could be multiple read-only directories. For example, one
+	 * could set dirname_libs to
+	 *   - /usr/lib/NetworkManager/profiles/
+	 *   - /etc/NetworkManager/system-connections
+	 * and leave dirname_etc unset. In this case, there would be multiple
+	 * read-only directories.
+	 *
+	 * Directories that come later have higher priority and shadow profiles
+	 * from earlier directories.
+	 *
+	 * Currently, this is only an array with zero or one elements. It could be
+	 * easily extended to support multiple read-only directories.
+	 */
+	char *dirname_libs[2];
+	char *dirname_etc;
+	char *dirname_run;
+
+	NMSettUtilStorages storages;
+
 } NMSKeyfilePluginPrivate;
 
 struct _NMSKeyfilePlugin {
@@ -65,7 +88,7 @@ struct _NMSKeyfilePluginClass {
 
 G_DEFINE_TYPE (NMSKeyfilePlugin, nms_keyfile_plugin, NM_TYPE_SETTINGS_PLUGIN)
 
-#define NMS_KEYFILE_PLUGIN_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMSKeyfilePlugin, NMS_IS_KEYFILE_PLUGIN)
+#define NMS_KEYFILE_PLUGIN_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMSKeyfilePlugin, NMS_IS_KEYFILE_PLUGIN, NMSettingsPlugin)
 
 /*****************************************************************************/
 
@@ -79,494 +102,1112 @@ G_DEFINE_TYPE (NMSKeyfilePlugin, nms_keyfile_plugin, NM_TYPE_SETTINGS_PLUGIN)
 
 /*****************************************************************************/
 
-static void
-connection_removed_cb (NMSettingsConnection *sett_conn, NMSKeyfilePlugin *self)
+static const char *
+_extra_flags_to_string (char *str, gsize str_len, gboolean is_nm_generated, gboolean is_volatile)
 {
-	g_hash_table_remove (NMS_KEYFILE_PLUGIN_GET_PRIVATE (self)->connections,
-	                     nm_settings_connection_get_uuid (sett_conn));
+	const char *str0 = str;
+
+	if (   !is_nm_generated
+	    && !is_volatile)
+		nm_utils_strbuf_append_str (&str, &str_len, "");
+	else {
+		nm_utils_strbuf_append_str (&str, &str_len, " (");
+		if (is_nm_generated) {
+			nm_utils_strbuf_append_str (&str, &str_len, "nm-generated");
+			if (is_volatile)
+				nm_utils_strbuf_append_c (&str, &str_len, ',');
+		}
+		if (is_volatile)
+			nm_utils_strbuf_append_str (&str, &str_len, "volatile");
+		nm_utils_strbuf_append_c (&str, &str_len, ')');
+	}
+
+	return str0;
 }
 
-/* Monitoring */
+static gboolean
+_ignore_filename (NMSKeyfileStorageType storage_type,
+                  const char *filename)
+{
+	/* for backward-compatibility, we don't require an extension for
+	 * files under "/etc/...". */
+	return nm_keyfile_utils_ignore_filename (filename,
+	                                         (storage_type != NMS_KEYFILE_STORAGE_TYPE_ETC));
+}
 
-static void
-remove_connection (NMSKeyfilePlugin *self, NMSKeyfileConnection *connection)
+static const char *
+_get_plugin_dir (NMSKeyfilePluginPrivate *priv)
 {
-	gboolean removed;
+	/* the plugin dir is only needed to generate connection.uuid value via
+	 * nm_keyfile_read_ensure_uuid(). This is either the configured /etc
+	 * directory, of the compile-time default (in case the /etc directory
+	 * is disabled). */
+	return priv->dirname_etc ?: NM_KEYFILE_PATH_NAME_ETC_DEFAULT;
+}
 
-	g_return_if_fail (connection != NULL);
+static gboolean
+_path_detect_storage_type (const char *full_filename,
+                           const char *const*dirname_libs,
+                           const char *dirname_etc,
+                           const char *dirname_run,
+                           NMSKeyfileStorageType *out_storage_type,
+                           const char **out_dirname,
+                           const char **out_filename,
+                           gboolean *out_is_nmmeta_file,
+                           gboolean *out_failed_due_to_invalid_filename)
+{
+	NMSKeyfileStorageType storage_type;
+	const char *filename = NULL;
+	const char *dirname = NULL;
+	guint i;
+	gboolean is_nmmeta_file = FALSE;
+
+	NM_SET_OUT (out_failed_due_to_invalid_filename, FALSE);
+
+	if (full_filename[0] != '/')
+		return FALSE;
+
+	if (   dirname_run
+	    && (filename = nm_utils_file_is_in_path (full_filename, dirname_run))) {
+		storage_type = NMS_KEYFILE_STORAGE_TYPE_RUN;
+		dirname = dirname_run;
+	} else if (   dirname_etc
+	           && (filename = nm_utils_file_is_in_path (full_filename, dirname_etc))) {
+		storage_type = NMS_KEYFILE_STORAGE_TYPE_ETC;
+		dirname = dirname_etc;
+	} else {
+		for (i = 0; dirname_libs && dirname_libs[i]; i++) {
+			if ((filename = nm_utils_file_is_in_path (full_filename, dirname_libs[i]))) {
+				storage_type = NMS_KEYFILE_STORAGE_TYPE_LIB (i);
+				dirname = dirname_libs[i];
+				break;
+			}
+		}
+		if (!dirname)
+			return FALSE;
+	}
+
+	if (_ignore_filename (storage_type, filename)) {
 
-	_LOGI ("removed " NMS_KEYFILE_CONNECTION_LOG_FMT, NMS_KEYFILE_CONNECTION_LOG_ARG (connection));
+		/* we accept nmmeta files, but only in /etc and /run directories. */
 
-	/* Removing from the hash table should drop the last reference */
-	g_object_ref (connection);
-	g_signal_handlers_disconnect_by_func (connection, connection_removed_cb, self);
-	removed = g_hash_table_remove (NMS_KEYFILE_PLUGIN_GET_PRIVATE (self)->connections,
-	                               nm_settings_connection_get_uuid (NM_SETTINGS_CONNECTION (connection)));
-	nm_settings_connection_signal_remove (NM_SETTINGS_CONNECTION (connection));
-	g_object_unref (connection);
+		if (   !NM_IN_SET (storage_type, NMS_KEYFILE_STORAGE_TYPE_RUN,
+		                                 NMS_KEYFILE_STORAGE_TYPE_ETC)
+		    || !nms_keyfile_nmmeta_check_filename (filename, NULL)) {
+			NM_SET_OUT (out_failed_due_to_invalid_filename, TRUE);
+			return FALSE;
+		}
+
+		is_nmmeta_file = TRUE;
+	}
 
-	g_return_if_fail (removed);
+	NM_SET_OUT (out_storage_type, storage_type);
+	NM_SET_OUT (out_dirname, dirname);
+	NM_SET_OUT (out_filename, filename);
+	NM_SET_OUT (out_is_nmmeta_file, is_nmmeta_file);
+	return TRUE;
 }
 
-static NMSKeyfileConnection *
-find_by_path (NMSKeyfilePlugin *self, const char *path)
+/*****************************************************************************/
+
+static NMConnection *
+_read_from_file (const char *full_filename,
+                 const char *plugin_dir,
+                 struct stat *out_stat,
+                 NMTernary *out_is_nm_generated,
+                 NMTernary *out_is_volatile,
+                 char **out_shadowed_storage,
+                 NMTernary *out_shadowed_owned,
+                 GError **error)
 {
-	NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE (self);
-	GHashTableIter iter;
-	NMSettingsConnection *candidate = NULL;
+	NMConnection *connection;
 
-	g_return_val_if_fail (path != NULL, NULL);
+	nm_assert (full_filename && full_filename[0] == '/');
 
-	g_hash_table_iter_init (&iter, priv->connections);
-	while (g_hash_table_iter_next (&iter, NULL, (gpointer) &candidate)) {
-		if (g_strcmp0 (path, nm_settings_connection_get_filename (candidate)) == 0)
-			return NMS_KEYFILE_CONNECTION (candidate);
-	}
-	return NULL;
+	connection = nms_keyfile_reader_from_file (full_filename,
+	                                           plugin_dir,
+	                                           out_stat,
+	                                           out_is_nm_generated,
+	                                           out_is_volatile,
+	                                           out_shadowed_storage,
+	                                           out_shadowed_owned,
+	                                           error);
+
+	nm_assert (!connection || (_nm_connection_verify (connection, NULL) == NM_SETTING_VERIFY_SUCCESS));
+	nm_assert (!connection || nm_utils_is_uuid (nm_connection_get_uuid (connection)));
+
+	return connection;
 }
 
-/* update_connection:
- * @self: the plugin instance
- * @source: if %NULL, this re-reads the connection from @full_path
- *   and updates it. When passing @source, this adds a connection from
- *   memory.
- * @full_path: the filename of the keyfile to be loaded
- * @connection: an existing connection that might be updated.
- *   If given, @connection must be an existing connection that is currently
- *   owned by the plugin.
- * @protect_existing_connection: if %TRUE, and !@connection, we don't allow updating
- *   an existing connection with the same UUID.
- *   If %TRUE and @connection, allow updating only if the reload would modify
- *   @connection (without changing its UUID) or if we would create a new connection.
- *   In other words, if this parameter is %TRUE, we only allow creating a
- *   new connection (with an unseen UUID) or updating the passed in @connection
- *   (whereas the UUID cannot change).
- *   Note, that this allows for @connection to be replaced by a new connection.
- * @protected_connections: (allow-none): if given, we only update an
- *   existing connection if it is not contained in this hash.
- * @error: error in case of failure
- *
- * Loads a connection from file @full_path. This can both be used to
- * load a connection initially or to update an existing connection.
- *
- * If you pass in an existing connection and the reloaded file happens
- * to have a different UUID, the connection is deleted.
- * Beware, that means that after the function, you have a dangling pointer
- * if the returned connection is different from @connection.
- *
- * Returns: the updated connection.
- * */
-static NMSKeyfileConnection *
-update_connection (NMSKeyfilePlugin *self,
-                   NMConnection *source,
-                   const char *full_path,
-                   NMSKeyfileConnection *connection,
-                   gboolean protect_existing_connection,
-                   GHashTable *protected_connections,
-                   GError **error)
+/*****************************************************************************/
+
+static void
+_nm_assert_storage (gpointer plugin  /* NMSKeyfilePlugin  */,
+                    gpointer storage /* NMSKeyfileStorage */,
+                    gboolean tracked)
 {
-	NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE (self);
-	NMSKeyfileConnection *connection_new;
-	NMSKeyfileConnection *connection_by_uuid;
-	GError *local = NULL;
+#if NM_MORE_ASSERTS
+	NMSettUtilStorageByUuidHead *sbuh;
 	const char *uuid;
 
-	g_return_val_if_fail (!source || NM_IS_CONNECTION (source), NULL);
-	g_return_val_if_fail (full_path || source, NULL);
+	nm_assert (!plugin || NMS_IS_KEYFILE_PLUGIN (plugin));
+	nm_assert (NMS_IS_KEYFILE_STORAGE (storage));
+	nm_assert (!plugin || plugin == nm_settings_storage_get_plugin (storage));
 
-	if (full_path)
-		_LOGD ("loading from file \"%s\"...", full_path);
+	nm_assert (({
+	                const char *f = nms_keyfile_storage_get_filename (storage);
+	                f && f[0] == '/';
+	            }));
 
-	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;
-	}
+	uuid = nms_keyfile_storage_get_uuid (storage);
 
-	connection_new = nms_keyfile_connection_new (source, full_path, nms_keyfile_utils_get_path (), &local);
-	if (!connection_new) {
-		/* Error; remove the connection */
-		if (source)
-			_LOGW ("error creating connection %s: %s", nm_connection_get_uuid (source), local->message);
-		else
-			_LOGW ("error loading connection from file %s: %s", full_path, local->message);
-		if (   connection
-		    && !protect_existing_connection
-		    && (!protected_connections || !g_hash_table_contains (protected_connections, connection)))
-			remove_connection (self, connection);
-		g_propagate_error (error, local);
-		return NULL;
+	nm_assert (nm_utils_is_uuid (uuid));
+
+	nm_assert (   ((NMSKeyfileStorage *) storage)->is_meta_data
+	           || !(((NMSKeyfileStorage *) storage)->u.conn_data.connection)
+	           || (   NM_IS_CONNECTION ((((NMSKeyfileStorage *) storage)->u.conn_data.connection))
+	               && nm_streq0 (uuid, nm_connection_get_uuid ((((NMSKeyfileStorage *) storage)->u.conn_data.connection)))));
+
+	nm_assert (   !tracked
+	           || !plugin
+	           || c_list_contains (&NMS_KEYFILE_PLUGIN_GET_PRIVATE (plugin)->storages._storage_lst_head,
+	                               &NMS_KEYFILE_STORAGE (storage)->parent._storage_lst));
+
+	nm_assert (   !tracked
+	           || !plugin
+	           || storage == g_hash_table_lookup (NMS_KEYFILE_PLUGIN_GET_PRIVATE (plugin)->storages.idx_by_filename,
+	                                              nms_keyfile_storage_get_filename (storage)));
+
+	if (   tracked
+	    && plugin) {
+		sbuh = g_hash_table_lookup (NMS_KEYFILE_PLUGIN_GET_PRIVATE (plugin)->storages.idx_by_uuid, &uuid);
+		nm_assert (sbuh);
+		nm_assert (c_list_contains (&sbuh->_storage_by_uuid_lst_head, &((NMSKeyfileStorage *) storage)->parent._storage_by_uuid_lst));
 	}
+#endif
+}
 
-	uuid = nm_settings_connection_get_uuid (NM_SETTINGS_CONNECTION (connection_new));
-	connection_by_uuid = g_hash_table_lookup (priv->connections, uuid);
+/*****************************************************************************/
 
-	if (   connection
-	    && connection != connection_by_uuid) {
+static NMSKeyfileStorage *
+_load_file (NMSKeyfilePlugin *self,
+            const char *dirname,
+            const char *filename,
+            NMSKeyfileStorageType storage_type,
+            GError **error)
+{
+	NMSKeyfilePluginPrivate *priv;
+	gs_unref_object NMConnection *connection = NULL;
+	NMTernary is_volatile_opt;
+	NMTernary is_nm_generated_opt;
+	NMTernary shadowed_owned_opt;
+	gs_free char *shadowed_storage = NULL;
+	gs_free_error GError *local = NULL;
+	gs_free char *full_filename = NULL;
+	struct stat st;
 
-		if (   (protect_existing_connection && connection_by_uuid != NULL)
-		    || (protected_connections && g_hash_table_contains (protected_connections, connection))) {
-			NMSKeyfileConnection *conflicting = (protect_existing_connection && connection_by_uuid != NULL) ? connection_by_uuid : connection;
+	if (_ignore_filename (storage_type, filename)) {
+		gs_free char *nmmeta = NULL;
+		gs_free char *loaded_path = NULL;
+		gs_free char *shadowed_storage_filename = NULL;
 
-			if (source)
-				_LOGW ("cannot update protected "NMS_KEYFILE_CONNECTION_LOG_FMT" connection due to conflicting UUID %s", NMS_KEYFILE_CONNECTION_LOG_ARG (conflicting), uuid);
+		if (!nms_keyfile_nmmeta_check_filename (filename, NULL)) {
+			if (error)
+				nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN, "skip due to invalid filename");
+			else
+				_LOGT ("load: \"%s/%s\": skip file due to invalid filename", dirname, filename);
+			return NULL;
+		}
+		if (!nms_keyfile_nmmeta_read (dirname,
+		                              filename,
+		                              &full_filename,
+		                              &nmmeta,
+		                              &loaded_path,
+		                              &shadowed_storage_filename,
+		                              NULL)) {
+			if (error)
+				nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN, "skip unreadable nmmeta file");
 			else
-				_LOGW ("cannot load %s due to conflicting UUID for "NMS_KEYFILE_CONNECTION_LOG_FMT, full_path, NMS_KEYFILE_CONNECTION_LOG_ARG (conflicting));
-			g_object_unref (connection_new);
-			g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
-			                      "Cannot update protected connection due to conflicting UUID");
+				_LOGT ("load: \"%s/%s\": skip unreadable nmmeta file", dirname, filename);
+			return NULL;
+		}
+		nm_assert (loaded_path);
+		if (!NM_IN_SET (storage_type, NMS_KEYFILE_STORAGE_TYPE_RUN,
+		                              NMS_KEYFILE_STORAGE_TYPE_ETC)) {
+			if (error)
+				nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN, "skip nmmeta file from read-only directory");
+			else
+				_LOGT ("load: \"%s/%s\": skip nmmeta file from read-only directory", dirname, filename);
+			return NULL;
+		}
+		if (!nm_streq (loaded_path, NM_KEYFILE_PATH_NMMETA_SYMLINK_NULL)) {
+			if (error)
+				nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN, "skip nmmeta file not symlinking %s", NM_KEYFILE_PATH_NMMETA_SYMLINK_NULL);
+			else
+				_LOGT ("load: \"%s/%s\": skip nmmeta file not symlinking to %s", dirname, filename, NM_KEYFILE_PATH_NMMETA_SYMLINK_NULL);
 			return NULL;
 		}
 
-		/* The new connection has a different UUID then the original one.
-		 * Remove @connection. */
-		remove_connection (self, connection);
+		return nms_keyfile_storage_new_tombstone (self,
+		                                          nmmeta,
+		                                          full_filename,
+		                                          storage_type,
+		                                          shadowed_storage_filename);
 	}
 
-	if (   connection_by_uuid
-	    && (   (!connection && protect_existing_connection)
-	        || (protected_connections && g_hash_table_contains (protected_connections, connection_by_uuid)))) {
-		if (source)
-			_LOGW ("cannot update connection due to conflicting UUID for "NMS_KEYFILE_CONNECTION_LOG_FMT, NMS_KEYFILE_CONNECTION_LOG_ARG (connection_by_uuid));
+	full_filename = g_build_filename (dirname, filename, NULL);
+
+	priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE (self);
+
+	connection = _read_from_file (full_filename,
+	                              _get_plugin_dir (priv),
+	                              &st,
+	                              &is_nm_generated_opt,
+	                              &is_volatile_opt,
+	                              &shadowed_storage,
+	                              &shadowed_owned_opt,
+	                              &local);
+	if (!connection) {
+		if (error)
+			g_propagate_error (error, local);
 		else
-			_LOGW ("cannot load %s due to conflicting UUID for "NMS_KEYFILE_CONNECTION_LOG_FMT, full_path, NMS_KEYFILE_CONNECTION_LOG_ARG (connection_by_uuid));
-		g_object_unref (connection_new);
-		g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
-		                      "Skip updating protected connection during reload");
+			_LOGW ("load: \"%s\": failed to load connection: %s", full_filename, local->message);
 		return NULL;
 	}
 
-	if (connection_by_uuid) {
-		const char *old_path;
-
-		old_path = nm_settings_connection_get_filename (NM_SETTINGS_CONNECTION (connection_by_uuid));
-
-		if (nm_connection_compare (nm_settings_connection_get_connection (NM_SETTINGS_CONNECTION (connection_by_uuid)),
-		                           nm_settings_connection_get_connection (NM_SETTINGS_CONNECTION (connection_new)),
-		                           NM_SETTING_COMPARE_FLAG_IGNORE_AGENT_OWNED_SECRETS |
-		                           NM_SETTING_COMPARE_FLAG_IGNORE_NOT_SAVED_SECRETS)) {
-			/* Nothing to do... except updating the path. */
-			if (old_path && g_strcmp0 (old_path, full_path) != 0)
-				_LOGI ("rename \"%s\" to "NMS_KEYFILE_CONNECTION_LOG_FMT" without other changes", old_path, NMS_KEYFILE_CONNECTION_LOG_ARG (connection_new));
-		} else {
-			/* An existing connection changed. */
-			if (source)
-				_LOGI ("update "NMS_KEYFILE_CONNECTION_LOG_FMT" from %s", NMS_KEYFILE_CONNECTION_LOG_ARG (connection_new), NMS_KEYFILE_CONNECTION_LOG_PATH (old_path));
-			else if (!g_strcmp0 (old_path, nm_settings_connection_get_filename (NM_SETTINGS_CONNECTION (connection_new))))
-				_LOGI ("update "NMS_KEYFILE_CONNECTION_LOG_FMT, NMS_KEYFILE_CONNECTION_LOG_ARG (connection_new));
-			else if (old_path)
-				_LOGI ("rename \"%s\" to "NMS_KEYFILE_CONNECTION_LOG_FMT, old_path, NMS_KEYFILE_CONNECTION_LOG_ARG (connection_new));
-			else
-				_LOGI ("update and persist "NMS_KEYFILE_CONNECTION_LOG_FMT, NMS_KEYFILE_CONNECTION_LOG_ARG (connection_new));
-
-			if (!nm_settings_connection_update (NM_SETTINGS_CONNECTION (connection_by_uuid),
-			                                    nm_settings_connection_get_connection (NM_SETTINGS_CONNECTION (connection_new)),
-			                                    NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP_SAVED,
-			                                    NM_SETTINGS_CONNECTION_COMMIT_REASON_NONE,
-			                                    "keyfile-update",
-			                                    &local)) {
-				/* Shouldn't ever get here as 'connection_new' was verified by the reader already
-				 * and the UUID did not change. */
-				g_assert_not_reached ();
-			}
-			g_assert_no_error (local);
-		}
-		nm_settings_connection_set_filename (NM_SETTINGS_CONNECTION (connection_by_uuid), full_path);
-		g_object_unref (connection_new);
-		return connection_by_uuid;
-	} else {
-		if (source)
-			_LOGI ("add connection "NMS_KEYFILE_CONNECTION_LOG_FMT, NMS_KEYFILE_CONNECTION_LOG_ARG (connection_new));
-		else
-			_LOGI ("new connection "NMS_KEYFILE_CONNECTION_LOG_FMT, NMS_KEYFILE_CONNECTION_LOG_ARG (connection_new));
-		g_hash_table_insert (priv->connections, g_strdup (uuid), connection_new);
-
-		g_signal_connect (connection_new, NM_SETTINGS_CONNECTION_REMOVED,
-		                  G_CALLBACK (connection_removed_cb),
-		                  self);
-
-		if (!source) {
-			/* Only raise the signal if we were called without source, i.e. if we read the connection from file.
-			 * Otherwise, we were called by add_connection() which does not expect the signal. */
-			_nm_settings_plugin_emit_signal_connection_added (NM_SETTINGS_PLUGIN (self),
-			                                                  NM_SETTINGS_CONNECTION (connection_new));
-		}
+	return nms_keyfile_storage_new_connection (self,
+	                                           g_steal_pointer (&connection),
+	                                           full_filename,
+	                                           storage_type,
+	                                           is_nm_generated_opt,
+	                                           is_volatile_opt,
+	                                           shadowed_storage,
+	                                           shadowed_owned_opt,
+	                                           &st.st_mtim);
+}
 
-		return connection_new;
-	}
+static NMSKeyfileStorage *
+_load_file_from_path (NMSKeyfilePlugin *self,
+                      const char *full_filename,
+                      NMSKeyfileStorageType storage_type,
+                      GError **error)
+{
+	gs_free char *f_dirname_free = NULL;
+	const char *f_filename;
+	const char *f_dirname;
+
+	nm_assert (full_filename && full_filename[0] == '/');
+
+	f_filename = strrchr (full_filename, '/');
+	f_dirname = nm_strndup_a (300, full_filename, f_filename - full_filename, &f_dirname_free);
+	f_filename++;
+	return _load_file (self,
+	                   f_dirname,
+	                   f_filename,
+	                   storage_type,
+	                   error);
 }
 
 static void
-dir_changed (GFileMonitor *monitor,
-             GFile *file,
-             GFile *other_file,
-             GFileMonitorEvent event_type,
-             gpointer user_data)
+_load_dir (NMSKeyfilePlugin *self,
+           NMSKeyfileStorageType storage_type,
+           const char *dirname,
+           NMSettUtilStorages *storages)
 {
-	NMSettingsPlugin *config = NM_SETTINGS_PLUGIN (user_data);
-	NMSKeyfilePlugin *self = NMS_KEYFILE_PLUGIN (config);
-	NMSKeyfileConnection *connection;
-	char *full_path;
-	gboolean exists;
-
-	full_path = g_file_get_path (file);
-	if (nm_keyfile_utils_ignore_filename (full_path, FALSE)) {
-		g_free (full_path);
+	const char *filename;
+	GDir *dir;
+	gs_unref_hashtable GHashTable *dupl_filenames = NULL;
+
+	dir = g_dir_open (dirname, 0, NULL);
+	if (!dir)
 		return;
-	}
-	exists = g_file_test (full_path, G_FILE_TEST_EXISTS);
-
-	_LOGD ("dir_changed(%s) = %d; file %s", full_path, event_type, exists ? "exists" : "does not exist");
-
-	connection = find_by_path (self, full_path);
-
-	switch (event_type) {
-	case G_FILE_MONITOR_EVENT_DELETED:
-		if (!exists && connection)
-			remove_connection (NMS_KEYFILE_PLUGIN (config), connection);
-		break;
-	case G_FILE_MONITOR_EVENT_CREATED:
-	case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
-		if (exists)
-			update_connection (NMS_KEYFILE_PLUGIN (config), NULL, full_path, connection, TRUE, NULL, NULL);
-		break;
-	default:
-		break;
+
+	dupl_filenames = g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, g_free);
+
+	while ((filename = g_dir_read_name (dir))) {
+		gs_unref_object NMSKeyfileStorage *storage = NULL;
+
+		filename = g_strdup (filename);
+		if (!g_hash_table_add (dupl_filenames, (char *) filename))
+			continue;
+
+		storage = _load_file (self,
+		                      dirname,
+		                      filename,
+		                      storage_type,
+		                      NULL);
+		if (!storage)
+			continue;
+
+		nm_sett_util_storages_add_take (storages, g_steal_pointer (&storage));
 	}
 
-	g_free (full_path);
+	g_dir_close (dir);
+
+#if NM_MORE_ASSERTS
+	{
+		NMSKeyfileStorage *storage;
+
+		c_list_for_each_entry (storage, &storages->_storage_lst_head, parent._storage_lst)
+			nm_assert (NMS_IS_KEYFILE_STORAGE (storage));
+	}
+#endif
 }
 
+/*****************************************************************************/
+
 static void
-config_changed_cb (NMConfig *config,
-                   NMConfigData *config_data,
-                   NMConfigChangeFlags changes,
-                   NMConfigData *old_data,
-                   NMSKeyfilePlugin *self)
+_storages_consolidate (NMSKeyfilePlugin *self,
+                       NMSettUtilStorages *storages_new,
+                       gboolean replace_all,
+                       GHashTable *storages_replaced,
+                       NMSettingsPluginConnectionLoadCallback callback,
+                       gpointer user_data)
 {
-	gs_free char *old_value = NULL;
-	gs_free char *new_value = NULL;
+	NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE (self);
+	CList lst_conn_info_deleted = C_LIST_INIT (lst_conn_info_deleted);
+	gs_unref_ptrarray GPtrArray *storages_modified = NULL;
+	CList storages_deleted;
+	NMSKeyfileStorage *storage_safe;
+	NMSKeyfileStorage *storage_new;
+	NMSKeyfileStorage *storage_old;
+	NMSKeyfileStorage *storage;
+	guint i;
 
-	old_value = nm_config_data_get_value (old_data, NM_CONFIG_KEYFILE_GROUP_KEYFILE, NM_CONFIG_KEYFILE_KEY_KEYFILE_UNMANAGED_DEVICES, NM_CONFIG_GET_VALUE_TYPE_SPEC);
-	new_value = nm_config_data_get_value (config_data, NM_CONFIG_KEYFILE_GROUP_KEYFILE, NM_CONFIG_KEYFILE_KEY_KEYFILE_UNMANAGED_DEVICES, NM_CONFIG_GET_VALUE_TYPE_SPEC);
+	storages_modified = g_ptr_array_new_with_free_func (g_object_unref);
+	c_list_init (&storages_deleted);
 
-	if (!nm_streq0 (old_value, new_value))
-		_nm_settings_plugin_emit_signal_unmanaged_specs_changed (NM_SETTINGS_PLUGIN (self));
-}
+	c_list_for_each_entry (storage_old, &priv->storages._storage_lst_head, parent._storage_lst)
+		storage_old->is_dirty = TRUE;
 
-static void
-setup_monitoring (NMSettingsPlugin *config)
-{
-	NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE ((NMSKeyfilePlugin *) config);
-	GFile *file;
-	GFileMonitor *monitor;
-
-	if (nm_config_get_monitor_connection_files (priv->config)) {
-		file = g_file_new_for_path (nms_keyfile_utils_get_path ());
-		monitor = g_file_monitor_directory (file, G_FILE_MONITOR_NONE, NULL, NULL);
-		g_object_unref (file);
-
-		if (monitor) {
-			priv->monitor_id = g_signal_connect (monitor, "changed", G_CALLBACK (dir_changed), config);
-			priv->monitor = monitor;
+	c_list_for_each_entry_safe (storage_new, storage_safe, &storages_new->_storage_lst_head, parent._storage_lst) {
+		storage_old = nm_sett_util_storages_lookup_by_filename (&priv->storages, nms_keyfile_storage_get_filename (storage_new));
+
+		nm_sett_util_storages_steal (storages_new, storage_new);
+
+		if (   !storage_old
+		    || !nm_streq (nms_keyfile_storage_get_uuid (storage_new), nms_keyfile_storage_get_uuid (storage_old))) {
+			if (storage_old) {
+				nm_sett_util_storages_steal (&priv->storages, storage_old);
+				c_list_link_tail (&storages_deleted, &storage_old->parent._storage_by_uuid_lst);
+			}
+			storage_new->is_dirty = FALSE;
+			nm_sett_util_storages_add_take (&priv->storages, storage_new);
+			g_ptr_array_add (storages_modified, g_object_ref (storage_new));
+			continue;
 		}
-	}
 
-	g_signal_connect (G_OBJECT (priv->config),
-	                  NM_CONFIG_SIGNAL_CONFIG_CHANGED,
-	                  G_CALLBACK (config_changed_cb),
-	                  config);
-}
+		storage_old->is_dirty = FALSE;
+		nms_keyfile_storage_copy_content (storage_old, storage_new);
+		nms_keyfile_storage_destroy (storage_new);
+		g_ptr_array_add (storages_modified, g_object_ref (storage_old));
+	}
 
-static GHashTable *
-_paths_from_connections (GHashTable *connections)
-{
-	GHashTableIter iter;
-	NMSKeyfileConnection *connection;
-	GHashTable *paths = g_hash_table_new (nm_str_hash, g_str_equal);
+	c_list_for_each_entry_safe (storage_old, storage_safe, &priv->storages._storage_lst_head, parent._storage_lst) {
+		if (!storage_old->is_dirty)
+			continue;
+		if (   replace_all
+		    || (   storages_replaced
+		        && g_hash_table_contains (storages_replaced, storage_old))) {
+			nm_sett_util_storages_steal (&priv->storages, storage_old);
+			c_list_link_tail (&storages_deleted, &storage_old->parent._storage_by_uuid_lst);
+		}
+	}
 
-	g_hash_table_iter_init (&iter, connections);
-	while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &connection)) {
-		const char *path = nm_settings_connection_get_filename (NM_SETTINGS_CONNECTION (connection));
+	/* raise events. */
 
-		if (path)
-			g_hash_table_add (paths, (void *) path);
+	for (i = 0; i < storages_modified->len; i++) {
+		storage = storages_modified->pdata[i];
+		storage->is_dirty = TRUE;
 	}
-	return paths;
-}
 
-static int
-_sort_paths (const char **f1, const char **f2, GHashTable *paths)
-{
-	struct stat st;
-	gboolean c1, c2;
-	gint64 m1, m2;
+	for (i = 0; i < storages_modified->len; i++) {
+		gs_unref_object NMConnection *connection = NULL;
 
-	c1 = !!g_hash_table_contains (paths, *f1);
-	c2 = !!g_hash_table_contains (paths, *f2);
-	if (c1 != c2)
-		return c1 ? -1 : 1;
+		storage = storages_modified->pdata[i];
 
-	m1 = stat (*f1, &st) == 0 ? (gint64) st.st_mtime : G_MININT64;
-	m2 = stat (*f2, &st) == 0 ? (gint64) st.st_mtime : G_MININT64;
-	if (m1 != m2)
-		return m1 > m2 ? -1 : 1;
+		if (!storage->is_dirty) {
+			/* the entry is no longer is_dirty. In the meantime we already emited
+			 * another signal for it. */
+			continue;
+		}
+		storage->is_dirty = FALSE;
 
-	return strcmp (*f1, *f2);
-}
+		if (c_list_is_empty (&storage->parent._storage_lst)) {
+			/* hm? The profile was deleted in the meantime? That is only possible
+			 * if the signal handler called again into the plugin. In any case, the event
+			 * was already emitted. Skip. */
+			continue;
+		}
 
-static void
-_read_dir (GPtrArray *filenames,
-           const char *path,
-           gboolean require_extension)
-{
-	GDir *dir;
-	const char *item;
-	GError *error = NULL;
+		nm_assert (storage == nm_sett_util_storages_lookup_by_filename (&priv->storages, nms_keyfile_storage_get_filename (storage)));
 
-	dir = g_dir_open (path, 0, &error);
-	if (!dir) {
-		_LOGD ("cannot read directory '%s': %s", path, error->message);
-		g_clear_error (&error);
-		return;
+		connection = nms_keyfile_storage_steal_connection (storage);
+
+		callback (NM_SETTINGS_PLUGIN (self),
+		          NM_SETTINGS_STORAGE (storage),
+		          connection,
+		          user_data);
 	}
 
-	while ((item = g_dir_read_name (dir))) {
-		if (nm_keyfile_utils_ignore_filename (item, require_extension))
-			continue;
-		g_ptr_array_add (filenames, g_build_filename (path, item, NULL));
+	while ((storage = c_list_first_entry (&storages_deleted, NMSKeyfileStorage, parent._storage_by_uuid_lst))) {
+		c_list_unlink (&storage->parent._storage_by_uuid_lst);
+		callback (NM_SETTINGS_PLUGIN (self),
+		          NM_SETTINGS_STORAGE (storage),
+		          NULL,
+		          user_data);
+		nms_keyfile_storage_destroy (storage);
 	}
-	g_dir_close (dir);
 }
 
+static void
+reload_connections (NMSettingsPlugin *plugin,
+                    NMSettingsPluginConnectionLoadCallback callback,
+                    gpointer user_data)
+{
+	NMSKeyfilePlugin *self = NMS_KEYFILE_PLUGIN (plugin);
+	NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE (self);
+	nm_auto_clear_sett_util_storages NMSettUtilStorages storages_new = NM_SETT_UTIL_STORAGES_INIT (storages_new, nms_keyfile_storage_destroy);
+	int i;
+
+	_load_dir (self, NMS_KEYFILE_STORAGE_TYPE_RUN, priv->dirname_run, &storages_new);
+	if (priv->dirname_etc)
+		_load_dir (self, NMS_KEYFILE_STORAGE_TYPE_ETC, priv->dirname_etc, &storages_new);
+	for (i = 0; priv->dirname_libs[i]; i++)
+		_load_dir (self, NMS_KEYFILE_STORAGE_TYPE_LIB (i), priv->dirname_libs[i], &storages_new);
+
+	_storages_consolidate (self,
+	                       &storages_new,
+	                       TRUE,
+	                       NULL,
+	                       callback,
+	                       user_data);
+}
 
 static void
-read_connections (NMSettingsPlugin *config)
+load_connections (NMSettingsPlugin *plugin,
+                  NMSettingsPluginConnectionLoadEntry *entries,
+                  gsize n_entries,
+                  NMSettingsPluginConnectionLoadCallback callback,
+                  gpointer user_data)
 {
-	NMSKeyfilePlugin *self = NMS_KEYFILE_PLUGIN (config);
+	NMSKeyfilePlugin *self = NMS_KEYFILE_PLUGIN (plugin);
 	NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE (self);
-	GHashTable *alive_connections;
-	GHashTableIter iter;
-	NMSKeyfileConnection *connection;
-	GPtrArray *dead_connections = NULL;
-	guint i;
-	GPtrArray *filenames;
-	GHashTable *paths;
+	nm_auto_clear_sett_util_storages NMSettUtilStorages storages_new = NM_SETT_UTIL_STORAGES_INIT (storages_new, nms_keyfile_storage_destroy);
+	gs_unref_hashtable GHashTable *dupl_filenames = NULL;
+	gs_unref_hashtable GHashTable *storages_replaced = NULL;
+	gs_unref_hashtable GHashTable *loaded_uuids = NULL;
+	const char *loaded_uuid;
+	GHashTableIter h_iter;
+	gsize i;
+
+	if (n_entries == 0)
+		return;
 
-	filenames = g_ptr_array_new_with_free_func (g_free);
+	dupl_filenames = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, NULL);
 
-	_read_dir (filenames, NM_KEYFILE_PATH_NAME_RUN, TRUE);
-	_read_dir (filenames, nms_keyfile_utils_get_path (), FALSE);
+	loaded_uuids = g_hash_table_new (nm_str_hash, g_str_equal);
 
-	alive_connections = g_hash_table_new (nm_direct_hash, NULL);
+	storages_replaced = g_hash_table_new_full (nm_direct_hash, NULL, g_object_unref, NULL);
 
-	/* While reloading, we don't replace connections that we already loaded while
-	 * iterating over the files.
-	 *
-	 * To have sensible, reproducible behavior, sort the paths by last modification
-	 * time preferring older files.
-	 */
-	paths = _paths_from_connections (priv->connections);
-	g_ptr_array_sort_with_data (filenames, (GCompareDataFunc) _sort_paths, paths);
-	g_hash_table_destroy (paths);
-
-	for (i = 0; i < filenames->len; i++) {
-		connection = update_connection (self, NULL, filenames->pdata[i], NULL, FALSE, alive_connections, NULL);
-		if (connection)
-			g_hash_table_add (alive_connections, connection);
-	}
-	g_ptr_array_free (filenames, TRUE);
-
-	g_hash_table_iter_init (&iter, priv->connections);
-	while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &connection)) {
-		if (   !g_hash_table_contains (alive_connections, connection)
-		    && nm_settings_connection_get_filename (NM_SETTINGS_CONNECTION (connection))) {
-			if (!dead_connections)
-				dead_connections = g_ptr_array_new ();
-			g_ptr_array_add (dead_connections, connection);
+	for (i = 0; i < n_entries; i++) {
+		NMSettingsPluginConnectionLoadEntry *const entry = &entries[i];
+		NMSKeyfileStorageType storage_type;
+		gs_free_error GError *local = NULL;
+		const char *f_filename;
+		const char *f_dirname;
+		const char *full_filename;
+		gs_free char *full_filename_keep = NULL;
+		gboolean is_nmmeta_file;
+		NMSettingsPluginConnectionLoadEntry *dupl_content_entry;
+		gboolean failed_due_to_invalid_filename;
+		gs_unref_object NMSKeyfileStorage *storage = NULL;
+
+		if (entry->handled)
+			continue;
+
+		if (!_path_detect_storage_type (entry->filename,
+		                                (const char *const*) priv->dirname_libs,
+		                                priv->dirname_etc,
+		                                priv->dirname_run,
+		                                &storage_type,
+		                                &f_dirname,
+		                                &f_filename,
+		                                &is_nmmeta_file,
+		                                &failed_due_to_invalid_filename)) {
+			if (failed_due_to_invalid_filename) {
+				entry->handled = TRUE;
+				nm_utils_error_set (&entry->error, NM_UTILS_ERROR_UNKNOWN, "filename is not valid for a keyfile");
+			}
+			continue;
 		}
-	}
-	g_hash_table_destroy (alive_connections);
 
-	if (dead_connections) {
-		for (i = 0; i < dead_connections->len; i++)
-			remove_connection (self, dead_connections->pdata[i]);
-		g_ptr_array_free (dead_connections, TRUE);
+		full_filename_keep = g_build_filename (f_dirname, f_filename, NULL);
+
+		if ((dupl_content_entry = g_hash_table_lookup (dupl_filenames, full_filename_keep))) {
+			/* we already visited this file. */
+			entry->handled = dupl_content_entry->handled;
+			if (dupl_content_entry->error) {
+				g_set_error_literal (&entry->error,
+				                     dupl_content_entry->error->domain,
+				                     dupl_content_entry->error->code,
+				                     dupl_content_entry->error->message);
+			}
+			continue;
+		}
+
+		entry->handled = TRUE;
+
+		full_filename = full_filename_keep;
+		if (!g_hash_table_insert (dupl_filenames, g_steal_pointer (&full_filename_keep), entry))
+			nm_assert_not_reached ();
+
+		storage = _load_file (self,
+		                      f_dirname,
+		                      f_filename,
+		                      storage_type,
+		                      &local);
+		if (!storage) {
+			if (nm_utils_file_stat (full_filename, NULL) == -ENOENT) {
+				NMSKeyfileStorage *storage2;
+
+				/* the file does not exist. We take that as indication to unload the file
+				 * that was previously loaded... */
+				storage2 = nm_sett_util_storages_lookup_by_filename (&priv->storages, full_filename);
+				if (storage2)
+					g_hash_table_add (storages_replaced, g_object_ref (storage2));
+				continue;
+			}
+			g_propagate_error (&entry->error, g_steal_pointer (&local));
+			continue;
+		}
+
+		g_hash_table_add (loaded_uuids, (char *) nms_keyfile_storage_get_uuid (storage));
+
+		nm_sett_util_storages_add_take (&storages_new, g_steal_pointer (&storage));
 	}
-}
 
-/*****************************************************************************/
+	/* now we visit all UUIDs that are about to change... */
+	g_hash_table_iter_init (&h_iter, loaded_uuids);
+	while (g_hash_table_iter_next (&h_iter, (gpointer *) &loaded_uuid, NULL)) {
+		NMSKeyfileStorage *storage;
+		NMSettUtilStorageByUuidHead *sbuh;
 
-static GSList *
-get_connections (NMSettingsPlugin *config)
-{
-	NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE ((NMSKeyfilePlugin *) config);
+		sbuh = nm_sett_util_storages_lookup_by_uuid (&priv->storages, loaded_uuid);
+		if (!sbuh)
+			continue;
+
+		c_list_for_each_entry (storage, &sbuh->_storage_by_uuid_lst_head, parent._storage_by_uuid_lst) {
+			const char *full_filename = nms_keyfile_storage_get_filename (storage);
+			gs_unref_object NMSKeyfileStorage *storage_new = NULL;
+			gs_free_error GError *local = NULL;
+
+			if (g_hash_table_contains (dupl_filenames, full_filename)) {
+				/* already re-loaded. */
+				continue;
+			}
+
+			/* @storage has a UUID that was just loaded from disk, but we have an entry in cache.
+			 * Reload that file too despite not being told to do so. The reason is to get
+			 * the latest file timestamp so that we get the priorities right. */
+
+			storage_new = _load_file_from_path (self,
+			                                    full_filename,
+			                                    storage->storage_type,
+			                                    &local);
+			if (   storage_new
+			    && !nm_streq (loaded_uuid, nms_keyfile_storage_get_uuid (storage_new))) {
+				/* the file now references a different UUID. We are not told to reload
+				 * that file, so this means the existing storage (with the previous
+				 * filename and UUID tuple) is no longer valid. */
+				g_clear_object (&storage_new);
+			}
 
-	if (!priv->initialized) {
-		setup_monitoring (config);
-		read_connections (config);
-		priv->initialized = TRUE;
+			g_hash_table_add (storages_replaced, g_object_ref (storage));
+			if (storage_new)
+				nm_sett_util_storages_add_take (&storages_new, g_steal_pointer (&storage_new));
+		}
 	}
-	return _nm_utils_hash_values_to_slist (priv->connections);
+
+	nm_clear_pointer (&loaded_uuids, g_hash_table_destroy);
+	nm_clear_pointer (&dupl_filenames, g_hash_table_destroy);
+
+	_storages_consolidate (self,
+	                       &storages_new,
+	                       FALSE,
+	                       storages_replaced,
+	                       callback,
+	                       user_data);
 }
 
-static gboolean
-load_connection (NMSettingsPlugin *config,
-                 const char *filename)
+gboolean
+nms_keyfile_plugin_add_connection (NMSKeyfilePlugin *self,
+                                   NMConnection *connection,
+                                   gboolean in_memory,
+                                   gboolean is_nm_generated,
+                                   gboolean is_volatile,
+                                   const char *shadowed_storage,
+                                   gboolean shadowed_owned,
+                                   NMSettingsStorage **out_storage,
+                                   NMConnection **out_connection,
+                                   GError **error)
 {
-	NMSKeyfilePlugin *self = NMS_KEYFILE_PLUGIN ((NMSKeyfilePlugin *) config);
-	NMSKeyfileConnection *connection;
-	gboolean require_extension;
-
-	if (nm_utils_file_is_in_path (filename, nms_keyfile_utils_get_path ()))
-		require_extension = FALSE;
-	else if (nm_utils_file_is_in_path (filename, NM_KEYFILE_PATH_NAME_RUN))
-		require_extension = TRUE;
-	else
-		return FALSE;
+	NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE (self);
+	gs_unref_object NMConnection *reread = NULL;
+	gs_free char *full_filename = NULL;
+	NMSKeyfileStorageType storage_type;
+	gs_unref_object NMSKeyfileStorage *storage = NULL;
+	GError *local = NULL;
+	const char *uuid;
+	gboolean reread_same;
+	struct timespec mtime;
+	char strbuf[100];
+
+	nm_assert (NM_IS_CONNECTION (connection));
+	nm_assert (out_storage && !*out_storage);
+	nm_assert (out_connection && !*out_connection);
+
+	nm_assert (   in_memory
+	           || (   !is_nm_generated
+	               && !is_volatile
+	               && !shadowed_storage
+	               && !shadowed_owned));
 
-	if (nm_keyfile_utils_ignore_filename (filename, require_extension))
+	uuid = nm_connection_get_uuid (connection);
+
+	/* Note that even if the caller requests persistent storage, we may switch to in-memory, if
+	 * no /etc directory is configured. */
+	storage_type =   !in_memory && priv->dirname_etc
+	               ? NMS_KEYFILE_STORAGE_TYPE_ETC
+	               : NMS_KEYFILE_STORAGE_TYPE_RUN;
+
+	if (!nms_keyfile_writer_connection (connection,
+	                                    is_nm_generated,
+	                                    is_volatile,
+	                                    shadowed_storage,
+	                                    shadowed_owned,
+	                                      storage_type == NMS_KEYFILE_STORAGE_TYPE_ETC
+	                                    ? priv->dirname_etc
+	                                    : priv->dirname_run,
+	                                    _get_plugin_dir (priv),
+	                                    NULL,
+	                                    FALSE,
+	                                    FALSE,
+	                                    nm_sett_util_allow_filename_cb,
+	                                    NM_SETT_UTIL_ALLOW_FILENAME_DATA (&priv->storages, NULL),
+	                                    &full_filename,
+	                                    &reread,
+	                                    &reread_same,
+	                                    &local)) {
+		_LOGT ("commit: %s (%s) failed to add: %s",
+		       nm_connection_get_uuid (connection),
+		       nm_connection_get_id (connection),
+		       local->message);
+		g_propagate_error (error, local);
 		return FALSE;
+	}
 
-	connection = update_connection (self, NULL, filename, find_by_path (self, filename), TRUE, NULL, NULL);
+	if (   !reread
+	    || reread_same)
+		nm_g_object_ref_set (&reread, connection);
 
-	return (connection != NULL);
-}
+	nm_assert (_nm_connection_verify (reread, NULL) == NM_SETTING_VERIFY_SUCCESS);
+	nm_assert (nm_streq0 (nm_connection_get_uuid (connection), nm_connection_get_uuid (reread)));
 
-static void
-reload_connections (NMSettingsPlugin *config)
-{
-	read_connections (config);
+	nm_assert (full_filename && full_filename[0] == '/');
+	nm_assert (!nm_sett_util_storages_lookup_by_filename (&priv->storages, full_filename));
+
+	_LOGT ("commit: %s (%s) added as \"%s\"%s%s%s%s",
+	       uuid,
+	       nm_connection_get_id (connection),
+	       full_filename,
+	       _extra_flags_to_string (strbuf, sizeof (strbuf), is_nm_generated, is_volatile),
+	       NM_PRINT_FMT_QUOTED (shadowed_storage, " (shadows \"", shadowed_storage, shadowed_owned ? "\", owned)" : "\")", ""));
+
+	storage = nms_keyfile_storage_new_connection (self,
+	                                              g_steal_pointer (&reread),
+	                                              full_filename,
+	                                              storage_type,
+	                                              is_nm_generated ? NM_TERNARY_TRUE : NM_TERNARY_FALSE,
+	                                              is_volatile ? NM_TERNARY_TRUE : NM_TERNARY_FALSE,
+	                                              shadowed_storage,
+	                                              shadowed_owned ? NM_TERNARY_TRUE : NM_TERNARY_FALSE,
+	                                              nm_sett_util_stat_mtime (full_filename, FALSE, &mtime));
+
+	nm_sett_util_storages_add_take (&priv->storages, g_object_ref (storage));
+
+	*out_connection = nms_keyfile_storage_steal_connection (storage);
+	*out_storage = NM_SETTINGS_STORAGE (g_steal_pointer (&storage));
+
+	return TRUE;
 }
 
-static NMSettingsConnection *
-add_connection (NMSettingsPlugin *config,
+static gboolean
+add_connection (NMSettingsPlugin *plugin,
                 NMConnection *connection,
-                gboolean save_to_disk,
+                NMSettingsStorage **out_storage,
+                NMConnection **out_connection,
                 GError **error)
 {
-	NMSKeyfilePlugin *self = NMS_KEYFILE_PLUGIN (config);
-	gs_free char *path = NULL;
+	return nms_keyfile_plugin_add_connection (NMS_KEYFILE_PLUGIN (plugin),
+	                                          connection,
+	                                          FALSE,
+	                                          FALSE,
+	                                          FALSE,
+	                                          NULL,
+	                                          FALSE,
+	                                          out_storage,
+	                                          out_connection,
+	                                          error);
+}
+
+gboolean
+nms_keyfile_plugin_update_connection (NMSKeyfilePlugin *self,
+                                      NMSettingsStorage *storage_x,
+                                      NMConnection *connection,
+                                      gboolean is_nm_generated,
+                                      gboolean is_volatile,
+                                      const char *shadowed_storage,
+                                      gboolean shadowed_owned,
+                                      gboolean force_rename,
+                                      NMSettingsStorage **out_storage,
+                                      NMConnection **out_connection,
+                                      GError **error)
+{
+	NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE (self);
+	NMSKeyfileStorage *storage = NMS_KEYFILE_STORAGE (storage_x);
+	gs_unref_object NMConnection *connection_clone = NULL;
 	gs_unref_object NMConnection *reread = NULL;
+	gs_free char *full_filename = NULL;
+	gs_free_error GError *local = NULL;
+	struct timespec mtime;
+	const char *previous_filename;
+	gboolean reread_same;
+	const char *uuid;
+	char strbuf[100];
+
+	_nm_assert_storage (self, storage, TRUE);
+	nm_assert (NM_IS_CONNECTION (connection));
+	nm_assert (_nm_connection_verify (connection, NULL) == NM_SETTING_VERIFY_SUCCESS);
+	nm_assert (nm_streq (nms_keyfile_storage_get_uuid (storage), nm_connection_get_uuid (connection)));
+	nm_assert (!error || !*error);
+	nm_assert (NM_IN_SET (storage->storage_type, NMS_KEYFILE_STORAGE_TYPE_ETC,
+	                                             NMS_KEYFILE_STORAGE_TYPE_RUN));
+	nm_assert (!storage->is_meta_data);
+	nm_assert (   storage->storage_type == NMS_KEYFILE_STORAGE_TYPE_RUN
+	           || (   !is_nm_generated
+	               && !is_volatile
+	               && !shadowed_storage
+	               && !shadowed_owned));
+	nm_assert (!shadowed_owned || shadowed_storage);
+	nm_assert  (   priv->dirname_etc
+	            || storage->storage_type != NMS_KEYFILE_STORAGE_TYPE_ETC);
+
+	previous_filename = nms_keyfile_storage_get_filename (storage);
+	uuid = nms_keyfile_storage_get_uuid (storage);
 
 	if (!nms_keyfile_writer_connection (connection,
-	                                    save_to_disk,
-	                                    NULL,
+	                                    is_nm_generated,
+	                                    is_volatile,
+	                                    shadowed_storage,
+	                                    shadowed_owned,
+	                                      storage->storage_type == NMS_KEYFILE_STORAGE_TYPE_ETC
+	                                    ? priv->dirname_etc
+	                                    : priv->dirname_run,
+	                                    _get_plugin_dir (priv),
+	                                    previous_filename,
+	                                    FALSE,
 	                                    FALSE,
-	                                    &path,
+	                                    nm_sett_util_allow_filename_cb,
+	                                    NM_SETT_UTIL_ALLOW_FILENAME_DATA (&priv->storages, previous_filename),
+	                                    &full_filename,
 	                                    &reread,
-	                                    NULL,
-	                                    error))
-		return NULL;
+	                                    &reread_same,
+	                                    &local)) {
+		_LOGW ("commit: failure to write %s (%s) to \"%s\": %s",
+		       uuid,
+		       nm_connection_get_id (connection_clone),
+		       previous_filename,
+		       local->message);
+		g_propagate_error (error, g_steal_pointer (&local));
+		return FALSE;
+	}
+
+	nm_assert (   full_filename
+	           && nm_streq (full_filename, previous_filename));
+
+	if (   !reread
+	    || reread_same)
+		nm_g_object_ref_set (&reread, connection);
 
-	return NM_SETTINGS_CONNECTION (update_connection (self, reread ?: connection, path, NULL, FALSE, NULL, error));
+	nm_assert (_nm_connection_verify (reread, NULL) == NM_SETTING_VERIFY_SUCCESS);
+	nm_assert (nm_streq (nm_connection_get_uuid (reread), uuid));
+
+	_LOGT ("commit: \"%s\": profile %s (%s) written%s%s%s%s",
+	       full_filename,
+	       uuid,
+	       nm_connection_get_id (connection),
+	       _extra_flags_to_string (strbuf, sizeof (strbuf), is_nm_generated, is_volatile),
+	       NM_PRINT_FMT_QUOTED (shadowed_storage, shadowed_owned ? " (owns \"" : " (shadows \"", shadowed_storage, "\")", ""));
+
+	storage->u.conn_data.is_nm_generated = is_nm_generated;
+	storage->u.conn_data.is_volatile     = is_volatile;
+	storage->u.conn_data.stat_mtime      = *nm_sett_util_stat_mtime (full_filename, FALSE, &mtime);
+	storage->u.conn_data.shadowed_owned  = shadowed_owned;
+
+	*out_storage = g_object_ref (NM_SETTINGS_STORAGE (storage));
+	*out_connection = g_steal_pointer (&reread);
+	return TRUE;
+}
+
+static gboolean
+update_connection (NMSettingsPlugin *plugin,
+                   NMSettingsStorage *storage,
+                   NMConnection *connection,
+                   NMSettingsStorage **out_storage,
+                   NMConnection **out_connection,
+                   GError **error)
+{
+	return nms_keyfile_plugin_update_connection (NMS_KEYFILE_PLUGIN (plugin),
+	                                             storage,
+	                                             connection,
+	                                             FALSE,
+	                                             FALSE,
+	                                             NULL,
+	                                             FALSE,
+	                                             FALSE,
+	                                             out_storage,
+	                                             out_connection,
+	                                             error);
+}
+
+static gboolean
+delete_connection (NMSettingsPlugin *plugin,
+                   NMSettingsStorage *storage_x,
+                   GError **error)
+{
+	NMSKeyfilePlugin *self = NMS_KEYFILE_PLUGIN (plugin);
+	NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE (self);
+	gs_unref_object NMSKeyfileStorage *storage = g_object_ref (NMS_KEYFILE_STORAGE (storage_x));
+	const char *remove_from_disk_errmsg = NULL;
+	const char *operation_message;
+	const char *previous_filename;
+	const char *uuid;
+	gboolean success = TRUE;
+
+	_nm_assert_storage (self, storage, TRUE);
+	nm_assert (!error || !*error);
+
+	previous_filename = nms_keyfile_storage_get_filename (storage);
+	uuid = nms_keyfile_storage_get_uuid (storage);
+
+	if (!NM_IN_SET (storage->storage_type, NMS_KEYFILE_STORAGE_TYPE_ETC,
+	                                       NMS_KEYFILE_STORAGE_TYPE_RUN)) {
+		nm_utils_error_set (error,
+		                    NM_UTILS_ERROR_UNKNOWN,
+		                    "profile in read-only storage cannot be deleted");
+		success = FALSE;
+		operation_message = "dropped readonly file from memory";
+	} else if (unlink (previous_filename) != 0) {
+		int errsv;
+
+		errsv = errno;
+		if (errsv != ENOENT) {
+			remove_from_disk_errmsg = nm_strerror_native (errsv);
+			operation_message = "failed to delete from disk";
+			success = FALSE;
+			nm_utils_error_set_errno (error,
+			                          errsv,
+			                          "failure to delete \"%s\": %s",
+			                          previous_filename);
+		} else
+			operation_message = "does not exist on disk";
+	} else
+		operation_message = "deleted from disk";
+
+	_LOGT ("commit: deleted \"%s\", %s %s (%s%s%s%s)",
+	       previous_filename,
+	       storage->is_meta_data ? "meta-data" : "profile",
+	       uuid,
+	       operation_message,
+	       NM_PRINT_FMT_QUOTED (remove_from_disk_errmsg, ": ", remove_from_disk_errmsg, "", ""));
+
+	if (success) {
+		nm_sett_util_storages_steal (&priv->storages, storage);
+		nms_keyfile_storage_destroy (storage);
+	}
+
+	return success;
+}
+
+/**
+ * nms_keyfile_plugin_set_nmmeta_tombstone:
+ * @self: the #NMSKeyfilePlugin instance
+ * @simulate: if %TRUE, don't do anything on the filename but just pretend
+ *   that the loaded UUID file gets tracked/untracked. In this mode, the function
+ *   cannot fail (except on hard-failure, see below).
+ *   The idea is that you first try without simulate to write to disk.
+ *   If that fails, you might still want to forcefully pretend (in-memory
+ *   only) that this uuid is marked as tombstone (or not), as desired.
+ *   So you repeate the call with @simulate %TRUE.
+ * @uuid: the UUID for which to write/delete the nmmeta file
+ * @in_memory: the storage type, either /etc or /run. Note that if @self
+ *   has no /etc directory configured, this results in a hard failure.
+ * @set: if %TRUE, write the symlink to point to /dev/null. If %FALSE,
+ *   delete the nmmeta file (if it exists).
+ * @shadowed_storage: a tombstone can also shadow an existing storage.
+ *   In combination with @set and @in_memory, this is allowed to store
+ *   the shadowed storage filename.
+ * @out_storage: (transfer full) (allow-none): the storage element that changes, or
+ *   NULL if nothing changed. Note that the file on disk is already as
+ *   we want to write it, then this still counts as a change. No change only
+ *   means if we try to delete a storage (@set %FALSE) that did not
+ *   exist previously.
+ * @out_hard_failure: (allow-none): on failure, indicate that this is a hard failure.
+ *
+ * The function writes or deletes nmmeta files to/from filesystem. In this case,
+ * the nmmeta files can only be symlinks to /dev/null (to indicate tombstones).
+ *
+ * A hard failure can only happen if @self has no /etc directory configured
+ * and @in_memory is FALSE. In such case even @simulate call fails (which
+ * otherwise would always succeed).
+ * Also, if you get a hard-failure (with @simulate %FALSE) there is no point
+ * in retrying with @simulate %TRUE (contrary to all other cases!).
+ *
+ * Returns: %TRUE on success.
+ */
+gboolean
+nms_keyfile_plugin_set_nmmeta_tombstone (NMSKeyfilePlugin *self,
+                                         gboolean simulate,
+                                         const char *uuid,
+                                         gboolean in_memory,
+                                         gboolean set,
+                                         const char *shadowed_storage,
+                                         NMSettingsStorage **out_storage,
+                                         gboolean *out_hard_failure)
+{
+	NMSKeyfilePluginPrivate *priv;
+	gboolean hard_failure = FALSE;
+	NMSKeyfileStorage *storage;
+	gs_unref_object NMSKeyfileStorage *storage_result = NULL;
+	gboolean nmmeta_success = FALSE;
+	gs_free char *nmmeta_filename = NULL;
+	NMSKeyfileStorageType storage_type;
+	const char *loaded_path;
+	const char *dirname;
+
+	nm_assert (NMS_IS_KEYFILE_PLUGIN (self));
+	nm_assert (nm_utils_is_uuid (uuid));
+	nm_assert (!out_storage || !*out_storage);
+	nm_assert (!shadowed_storage || (set && in_memory));
+
+	priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE (self);
+
+	loaded_path =   set
+	              ? NM_KEYFILE_PATH_NMMETA_SYMLINK_NULL
+	              : NULL;
+
+	if (in_memory) {
+		storage_type = NMS_KEYFILE_STORAGE_TYPE_RUN;
+		dirname = priv->dirname_run;
+	} else {
+		if (!priv->dirname_etc) {
+			_LOGT ("commit: cannot %s%s nmmeta file for %s as there is no /etc directory",
+			       simulate ? "simulate " : "",
+			       loaded_path ? "write" : "delete",
+			       uuid);
+			hard_failure = TRUE;
+			goto out;
+		}
+		storage_type = NMS_KEYFILE_STORAGE_TYPE_ETC;
+		dirname = priv->dirname_etc;
+	}
+
+	if (simulate) {
+		nmmeta_success = TRUE;
+		nmmeta_filename = nms_keyfile_nmmeta_filename (dirname, uuid, FALSE);
+	} else {
+		nmmeta_success = nms_keyfile_nmmeta_write (dirname,
+		                                           uuid,
+		                                           loaded_path,
+		                                           FALSE,
+		                                           shadowed_storage,
+		                                           &nmmeta_filename);
+	}
+
+	_LOGT ("commit: %s nmmeta file \"%s\"%s%s%s%s%s%s %s",
+	       loaded_path ? "writing" : "deleting",
+	       nmmeta_filename,
+	       NM_PRINT_FMT_QUOTED (loaded_path, " (pointing to \"", loaded_path, "\")", ""),
+	       NM_PRINT_FMT_QUOTED (shadowed_storage, " (shadows \"", shadowed_storage, "\")", ""),
+	       simulate
+	       ? "simulated"
+	       : (  nmmeta_success
+	          ? "succeeded"
+	          : "failed"));
+
+	if (!nmmeta_success)
+		goto out;
+
+	storage = nm_sett_util_storages_lookup_by_filename (&priv->storages, nmmeta_filename);
+
+	nm_assert (   !storage
+	           || (   storage->is_meta_data
+	               && storage->storage_type == storage_type
+	               && nm_streq (nms_keyfile_storage_get_uuid (storage), uuid)));
+
+	if (loaded_path) {
+
+		if (!storage) {
+			storage = nms_keyfile_storage_new_tombstone (self,
+			                                             uuid,
+			                                             nmmeta_filename,
+			                                             storage_type,
+			                                             shadowed_storage);
+			nm_sett_util_storages_add_take (&priv->storages, storage);
+		} else {
+			g_free (storage->u.meta_data.shadowed_storage);
+			storage->u.meta_data.shadowed_storage = g_strdup (shadowed_storage);
+		}
+
+		storage_result = g_object_ref (storage);
+	} else {
+		if (storage)
+			storage_result = nm_sett_util_storages_steal (&priv->storages, storage);
+	}
+
+out:
+	nm_assert (!nmmeta_success || !hard_failure);
+	nm_assert (nmmeta_success  || !storage_result);
+
+	NM_SET_OUT (out_hard_failure, hard_failure);
+	NM_SET_OUT (out_storage, (NMSettingsStorage *) g_steal_pointer (&storage_result));
+	return nmmeta_success;
+}
+
+/*****************************************************************************/
+
+static void
+config_changed_cb (NMConfig *config,
+                   NMConfigData *config_data,
+                   NMConfigChangeFlags changes,
+                   NMConfigData *old_data,
+                   NMSKeyfilePlugin *self)
+{
+	gs_free char *old_value = NULL;
+	gs_free char *new_value = NULL;
+
+	old_value = nm_config_data_get_value (old_data,    NM_CONFIG_KEYFILE_GROUP_KEYFILE, NM_CONFIG_KEYFILE_KEY_KEYFILE_UNMANAGED_DEVICES, NM_CONFIG_GET_VALUE_TYPE_SPEC);
+	new_value = nm_config_data_get_value (config_data, NM_CONFIG_KEYFILE_GROUP_KEYFILE, NM_CONFIG_KEYFILE_KEY_KEYFILE_UNMANAGED_DEVICES, NM_CONFIG_GET_VALUE_TYPE_SPEC);
+
+	if (!nm_streq0 (old_value, new_value))
+		_nm_settings_plugin_emit_signal_unmanaged_specs_changed (NM_SETTINGS_PLUGIN (self));
 }
 
 static GSList *
 get_unmanaged_specs (NMSettingsPlugin *config)
 {
-	NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE ((NMSKeyfilePlugin *) config);
+	NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE (config);
 	gs_free char *value = NULL;
 
 	value = nm_config_data_get_value (nm_config_get_data (priv->config),
@@ -584,13 +1225,48 @@ nms_keyfile_plugin_init (NMSKeyfilePlugin *plugin)
 	NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE (plugin);
 
 	priv->config = g_object_ref (nm_config_get ());
-	priv->connections = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, g_object_unref);
+
+	priv->storages = (NMSettUtilStorages) NM_SETT_UTIL_STORAGES_INIT (priv->storages, nms_keyfile_storage_destroy);
+
+	/* dirname_libs are a set of read-only directories with lower priority than /etc or /run.
+	 * There is nothing complicated about having multiple of such directories, so dirname_libs
+	 * is a list (which currently only has at most one directory). */
+	priv->dirname_libs[0] = nm_sd_utils_path_simplify (g_strdup (NM_KEYFILE_PATH_NAME_LIB), FALSE);
+	priv->dirname_libs[1] = NULL;
+	priv->dirname_run = nm_sd_utils_path_simplify (g_strdup (NM_KEYFILE_PATH_NAME_RUN), FALSE);
+	priv->dirname_etc = nm_config_data_get_value (NM_CONFIG_GET_DATA_ORIG,
+	                                              NM_CONFIG_KEYFILE_GROUP_KEYFILE,
+	                                              NM_CONFIG_KEYFILE_KEY_KEYFILE_PATH,
+	                                              NM_CONFIG_GET_VALUE_STRIP);
+	if (priv->dirname_etc && priv->dirname_etc[0] == '\0') {
+		/* special case: configure an empty keyfile path so that NM has no writable keyfile
+		 * directory. In this case, NM will only honor dirname_libs and dirname_run, meaning
+		 * it cannot persist profile to non-volatile memory. */
+		nm_clear_g_free (&priv->dirname_etc);
+	} else if (!priv->dirname_etc || priv->dirname_etc[0] != '/') {
+		/* either invalid path or unspecified. Use the default. */
+		g_free (priv->dirname_etc);
+		priv->dirname_etc = nm_sd_utils_path_simplify (g_strdup (NM_KEYFILE_PATH_NAME_ETC_DEFAULT), FALSE);
+	} else
+		nm_sd_utils_path_simplify (priv->dirname_etc, FALSE);
+
+	/* no duplicates */
+	if (NM_IN_STRSET (priv->dirname_libs[0], priv->dirname_etc,
+	                                         priv->dirname_run))
+		nm_clear_g_free (&priv->dirname_libs[0]);
+	if (NM_IN_STRSET (priv->dirname_etc, priv->dirname_run))
+		nm_clear_g_free (&priv->dirname_etc);
+
+	nm_assert (!priv->dirname_libs[0] || priv->dirname_libs[0][0] == '/');
+	nm_assert (!priv->dirname_etc     || priv->dirname_etc[0]     == '/');
+	nm_assert ( priv->dirname_run     && priv->dirname_run[0]     == '/');
 }
 
 static void
 constructed (GObject *object)
 {
-	NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE ((NMSKeyfilePlugin *) object);
+	NMSKeyfilePlugin *self = NMS_KEYFILE_PLUGIN (object);
+	NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE (self);
 
 	G_OBJECT_CLASS (nms_keyfile_plugin_parent_class)->constructed (object);
 
@@ -599,6 +1275,17 @@ constructed (GObject *object)
 	                              NM_CONFIG_KEYFILE_KEY_KEYFILE_HOSTNAME,
 	                              NM_CONFIG_GET_VALUE_RAW))
 		_LOGW ("'hostname' option is deprecated and has no effect");
+
+	if (nm_config_data_has_value (nm_config_get_data_orig (priv->config),
+	                              NM_CONFIG_KEYFILE_GROUP_MAIN,
+	                              NM_CONFIG_KEYFILE_KEY_MAIN_MONITOR_CONNECTION_FILES,
+	                              NM_CONFIG_GET_VALUE_RAW))
+		_LOGW ("'monitor-connection-files' option is deprecated and has no effect");
+
+	g_signal_connect (G_OBJECT (priv->config),
+	                  NM_CONFIG_SIGNAL_CONFIG_CHANGED,
+	                  G_CALLBACK (config_changed_cb),
+	                  self);
 }
 
 NMSKeyfilePlugin *
@@ -610,24 +1297,19 @@ nms_keyfile_plugin_new (void)
 static void
 dispose (GObject *object)
 {
-	NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE ((NMSKeyfilePlugin *) object);
+	NMSKeyfilePlugin *self = NMS_KEYFILE_PLUGIN (object);
+	NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE (self);
 
-	if (priv->monitor) {
-		nm_clear_g_signal_handler (priv->monitor, &priv->monitor_id);
+	if (priv->config)
+		g_signal_handlers_disconnect_by_func (priv->config, config_changed_cb, object);
 
-		g_file_monitor_cancel (priv->monitor);
-		g_clear_object (&priv->monitor);
-	}
+	nm_sett_util_storages_clear (&priv->storages);
 
-	if (priv->connections) {
-		g_hash_table_destroy (priv->connections);
-		priv->connections = NULL;
-	}
+	nm_clear_g_free (&priv->dirname_libs[0]);
+	nm_clear_g_free (&priv->dirname_etc);
+	nm_clear_g_free (&priv->dirname_run);
 
-	if (priv->config) {
-		g_signal_handlers_disconnect_by_func (priv->config, config_changed_cb, object);
-		g_clear_object (&priv->config);
-	}
+	g_clear_object (&priv->config);
 
 	G_OBJECT_CLASS (nms_keyfile_plugin_parent_class)->dispose (object);
 }
@@ -641,9 +1323,11 @@ nms_keyfile_plugin_class_init (NMSKeyfilePluginClass *klass)
 	object_class->constructed = constructed;
 	object_class->dispose     = dispose;
 
-	plugin_class->get_connections     = get_connections;
-	plugin_class->load_connection     = load_connection;
+	plugin_class->plugin_name         = "keyfile";
+	plugin_class->get_unmanaged_specs = get_unmanaged_specs;
 	plugin_class->reload_connections  = reload_connections;
+	plugin_class->load_connections    = load_connections;
 	plugin_class->add_connection      = add_connection;
-	plugin_class->get_unmanaged_specs = get_unmanaged_specs;
+	plugin_class->update_connection   = update_connection;
+	plugin_class->delete_connection   = delete_connection;
 }
diff --git a/src/settings/plugins/keyfile/nms-keyfile-plugin.h b/src/settings/plugins/keyfile/nms-keyfile-plugin.h
index 41f47aac..48440964 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-plugin.h
+++ b/src/settings/plugins/keyfile/nms-keyfile-plugin.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager system settings service - keyfile plugin
  *
  * This program is free software; you can redistribute it and/or modify
@@ -22,6 +21,11 @@
 #ifndef __NMS_KEYFILE_PLUGIN_H__
 #define __NMS_KEYFILE_PLUGIN_H__
 
+#include "settings/nm-settings-plugin.h"
+#include "settings/nm-settings-storage.h"
+
+#include "nms-keyfile-utils.h"
+
 #define NMS_TYPE_KEYFILE_PLUGIN            (nms_keyfile_plugin_get_type ())
 #define NMS_KEYFILE_PLUGIN(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NMS_TYPE_KEYFILE_PLUGIN, NMSKeyfilePlugin))
 #define NMS_KEYFILE_PLUGIN_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NMS_TYPE_KEYFILE_PLUGIN, NMSKeyfilePluginClass))
@@ -36,4 +40,36 @@ GType nms_keyfile_plugin_get_type (void);
 
 NMSKeyfilePlugin *nms_keyfile_plugin_new (void);
 
+gboolean nms_keyfile_plugin_add_connection (NMSKeyfilePlugin *self,
+                                            NMConnection *connection,
+                                            gboolean in_memory,
+                                            gboolean is_nm_generated,
+                                            gboolean is_volatile,
+                                            const char *shadowed_storage,
+                                            gboolean shadowed_owned,
+                                            NMSettingsStorage **out_storage,
+                                            NMConnection **out_connection,
+                                            GError **error);
+
+gboolean nms_keyfile_plugin_update_connection (NMSKeyfilePlugin *self,
+                                               NMSettingsStorage *storage,
+                                               NMConnection *connection,
+                                               gboolean is_nm_generated,
+                                               gboolean is_volatile,
+                                               const char *shadowed_storage,
+                                               gboolean shadowed_owned,
+                                               gboolean force_rename,
+                                               NMSettingsStorage **out_storage,
+                                               NMConnection **out_connection,
+                                               GError **error);
+
+gboolean nms_keyfile_plugin_set_nmmeta_tombstone (NMSKeyfilePlugin *self,
+                                                  gboolean simulate,
+                                                  const char *uuid,
+                                                  gboolean in_memory,
+                                                  gboolean set,
+                                                  const char *shadowed_storage,
+                                                  NMSettingsStorage **out_storage,
+                                                  gboolean *out_hard_failure);
+
 #endif /* __NMS_KEYFILE_PLUGIN_H__ */
diff --git a/src/settings/plugins/keyfile/nms-keyfile-reader.c b/src/settings/plugins/keyfile/nms-keyfile-reader.c
index 5778f13c..8d1f5599 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-reader.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-reader.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager system settings service - keyfile plugin
  *
  * This program is free software; you can redistribute it and/or modify
@@ -162,6 +161,11 @@ nms_keyfile_reader_from_keyfile (GKeyFile *key_file,
 NMConnection *
 nms_keyfile_reader_from_file (const char *full_filename,
                               const char *profile_dir,
+                              struct stat *out_stat,
+                              NMTernary *out_is_nm_generated,
+                              NMTernary *out_is_volatile,
+                              char **out_shadowed_storage,
+                              NMTernary *out_shadowed_owned,
                               GError **error)
 {
 	gs_unref_keyfile GKeyFile *key_file = NULL;
@@ -171,9 +175,12 @@ nms_keyfile_reader_from_file (const char *full_filename,
 	nm_assert (full_filename && full_filename[0] == '/');
 	nm_assert (!profile_dir || profile_dir[0] == '/');
 
+	NM_SET_OUT (out_is_nm_generated, NM_TERNARY_DEFAULT);
+	NM_SET_OUT (out_is_volatile, NM_TERNARY_DEFAULT);
+
 	if (!nms_keyfile_utils_check_file_permissions (NMS_KEYFILE_FILETYPE_KEYFILE,
 	                                               full_filename,
-	                                               NULL,
+	                                               out_stat,
 	                                               error))
 		return NULL;
 
@@ -195,6 +202,26 @@ nms_keyfile_reader_from_file (const char *full_filename,
 		connection = NULL;
 	}
 
+	NM_SET_OUT (out_is_nm_generated, nm_key_file_get_boolean (key_file,
+	                                                          NM_KEYFILE_GROUP_NMMETA,
+	                                                          NM_KEYFILE_KEY_NMMETA_NM_GENERATED,
+	                                                          NM_TERNARY_DEFAULT));
+
+	NM_SET_OUT (out_is_volatile, nm_key_file_get_boolean (key_file,
+	                                                      NM_KEYFILE_GROUP_NMMETA,
+	                                                      NM_KEYFILE_KEY_NMMETA_VOLATILE,
+	                                                      NM_TERNARY_DEFAULT));
+
+	NM_SET_OUT (out_shadowed_storage, g_key_file_get_string (key_file,
+	                                                         NM_KEYFILE_GROUP_NMMETA,
+	                                                         NM_KEYFILE_KEY_NMMETA_SHADOWED_STORAGE,
+	                                                         NULL));
+
+	NM_SET_OUT (out_shadowed_owned, nm_key_file_get_boolean (key_file,
+	                                                         NM_KEYFILE_GROUP_NMMETA,
+	                                                         NM_KEYFILE_KEY_NMMETA_SHADOWED_OWNED,
+	                                                         NM_TERNARY_DEFAULT));
+
 	return connection;
 }
 
diff --git a/src/settings/plugins/keyfile/nms-keyfile-reader.h b/src/settings/plugins/keyfile/nms-keyfile-reader.h
index c0fb06d1..f20e6d93 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-reader.h
+++ b/src/settings/plugins/keyfile/nms-keyfile-reader.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager system settings service - keyfile plugin
  *
  * This program is free software; you can redistribute it and/or modify
@@ -31,8 +30,15 @@ NMConnection *nms_keyfile_reader_from_keyfile (GKeyFile *key_file,
                                                gboolean verbose,
                                                GError **error);
 
+struct stat;
+
 NMConnection *nms_keyfile_reader_from_file (const char *full_filename,
                                             const char *profile_dir,
+                                            struct stat *out_stat,
+                                            NMTernary *out_is_nm_generated,
+                                            NMTernary *out_is_volatile,
+                                            char **out_shadowed_storage,
+                                            NMTernary *out_shadowed_owned,
                                             GError **error);
 
 #endif /* __NMS_KEYFILE_READER_H__ */
diff --git a/src/settings/plugins/keyfile/nms-keyfile-storage.c b/src/settings/plugins/keyfile/nms-keyfile-storage.c
new file mode 100644
index 00000000..d68d60c8
--- /dev/null
+++ b/src/settings/plugins/keyfile/nms-keyfile-storage.c
@@ -0,0 +1,277 @@
+/* NetworkManager system settings service - keyfile plugin
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2018 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nms-keyfile-storage.h"
+
+#include "nm-utils.h"
+#include "nm-core-internal.h"
+#include "nms-keyfile-plugin.h"
+
+/*****************************************************************************/
+
+struct _NMSKeyfileStorageClass {
+	NMSettingsStorageClass parent;
+};
+
+G_DEFINE_TYPE (NMSKeyfileStorage, nms_keyfile_storage, NM_TYPE_SETTINGS_STORAGE)
+
+/*****************************************************************************/
+
+void
+nms_keyfile_storage_copy_content (NMSKeyfileStorage *dst,
+                                  const NMSKeyfileStorage *src)
+{
+	nm_assert (src != dst);
+	nm_assert (nm_streq (nms_keyfile_storage_get_uuid (dst), nms_keyfile_storage_get_uuid (src)));
+	nm_assert (   nms_keyfile_storage_get_filename (dst)
+	           && nm_streq (nms_keyfile_storage_get_filename (dst), nms_keyfile_storage_get_filename (src)));
+	nm_assert (dst->storage_type == src->storage_type);
+	nm_assert (dst->is_meta_data == src->is_meta_data);
+
+	if (dst->is_meta_data) {
+		gs_free char *shadowed_storage_to_free = NULL;
+
+		shadowed_storage_to_free = g_steal_pointer (&dst->u.meta_data.shadowed_storage);
+		dst->u.meta_data = src->u.meta_data;
+		dst->u.meta_data.shadowed_storage = g_strdup (dst->u.meta_data.shadowed_storage);
+	} else {
+		gs_unref_object NMConnection *connection_to_free = NULL;
+		gs_free char *shadowed_storage_to_free = NULL;
+
+		connection_to_free = g_steal_pointer (&dst->u.conn_data.connection);
+		shadowed_storage_to_free = g_steal_pointer (&dst->u.conn_data.shadowed_storage);
+		dst->u.conn_data = src->u.conn_data;
+		nm_g_object_ref (dst->u.conn_data.connection);
+		dst->u.conn_data.shadowed_storage = g_strdup (dst->u.conn_data.shadowed_storage);
+	}
+}
+
+NMConnection *
+nms_keyfile_storage_steal_connection (NMSKeyfileStorage *self)
+{
+	nm_assert (NMS_IS_KEYFILE_STORAGE (self));
+	nm_assert (   self->is_meta_data
+	           || NM_IS_CONNECTION (self->u.conn_data.connection));
+
+	return   self->is_meta_data
+	       ? NULL
+	       : g_steal_pointer (&self->u.conn_data.connection);
+}
+
+/*****************************************************************************/
+
+static int
+cmp_fcn (const NMSKeyfileStorage *a,
+         const NMSKeyfileStorage *b)
+{
+	nm_assert (NMS_IS_KEYFILE_STORAGE (a));
+	nm_assert (NMS_IS_KEYFILE_STORAGE (b));
+	nm_assert (a != b);
+
+	/* sort by storage-type, which also has a numeric value according to their
+	 * (inverse) priority. */
+	NM_CMP_FIELD_UNSAFE (b, a, storage_type);
+
+	/* meta-data is more important. */
+	NM_CMP_FIELD_UNSAFE (a, b, is_meta_data);
+
+	if (a->is_meta_data) {
+		nm_assert (nm_streq (nms_keyfile_storage_get_filename (a), nms_keyfile_storage_get_filename (b)));
+		NM_CMP_FIELD_UNSAFE (a, b, u.meta_data.is_tombstone);
+	} else {
+		/* newer files are more important. */
+		NM_CMP_FIELD (a, b, u.conn_data.stat_mtime.tv_sec);
+		NM_CMP_FIELD (a, b, u.conn_data.stat_mtime.tv_nsec);
+
+		NM_CMP_DIRECT_STRCMP (nms_keyfile_storage_get_filename (a), nms_keyfile_storage_get_filename (b));
+	}
+
+	return 0;
+}
+
+/*****************************************************************************/
+
+static void
+nms_keyfile_storage_init (NMSKeyfileStorage *self)
+{
+}
+
+static NMSKeyfileStorage *
+_storage_new (NMSKeyfilePlugin *plugin,
+              const char *uuid,
+              const char *filename,
+              gboolean is_meta_data,
+              NMSKeyfileStorageType storage_type)
+
+{
+	NMSKeyfileStorage *self;
+
+	nm_assert (NMS_IS_KEYFILE_PLUGIN (plugin));
+	nm_assert (nm_utils_is_uuid (uuid));
+	nm_assert (filename && filename[0] == '/');
+
+	self = g_object_new (NMS_TYPE_KEYFILE_STORAGE,
+	                     NM_SETTINGS_STORAGE_PLUGIN, plugin,
+	                     NM_SETTINGS_STORAGE_UUID, uuid,
+	                     NM_SETTINGS_STORAGE_FILENAME, filename,
+	                     NULL);
+
+	*((bool *) &self->is_meta_data) = is_meta_data;
+	*((NMSKeyfileStorageType *) &self->storage_type) = storage_type;
+
+	return self;
+}
+
+NMSKeyfileStorage *
+nms_keyfile_storage_new_tombstone (NMSKeyfilePlugin *plugin,
+                                   const char *uuid,
+                                   const char *filename,
+                                   NMSKeyfileStorageType storage_type,
+                                   const char *shadowed_storage)
+{
+	NMSKeyfileStorage *self;
+
+	nm_assert (nm_utils_is_uuid (uuid));
+	nm_assert (filename && filename[0] == '/');
+	nm_assert (nms_keyfile_nmmeta_check_filename (filename, NULL));
+	nm_assert (NM_IN_SET (storage_type, NMS_KEYFILE_STORAGE_TYPE_ETC,
+	                                    NMS_KEYFILE_STORAGE_TYPE_RUN));
+
+	self = _storage_new (plugin, uuid, filename, TRUE, storage_type);
+	self->u.meta_data.is_tombstone = TRUE;
+	if (storage_type == NMS_KEYFILE_STORAGE_TYPE_RUN)
+		self->u.meta_data.shadowed_storage = g_strdup (shadowed_storage);
+	return self;
+}
+
+NMSKeyfileStorage *
+nms_keyfile_storage_new_connection (NMSKeyfilePlugin *plugin,
+                                    NMConnection *connection_take /* pass reference */,
+                                    const char *filename,
+                                    NMSKeyfileStorageType storage_type,
+                                    NMTernary is_nm_generated_opt,
+                                    NMTernary is_volatile_opt,
+                                    const char *shadowed_storage,
+                                    NMTernary shadowed_owned_opt,
+                                    const struct timespec *stat_mtime)
+{
+	NMSKeyfileStorage *self;
+
+	nm_assert (NMS_IS_KEYFILE_PLUGIN (plugin));
+	nm_assert (NM_IS_CONNECTION (connection_take));
+	nm_assert (_nm_connection_verify (connection_take, NULL) == NM_SETTING_VERIFY_SUCCESS);
+	nm_assert (filename && filename[0] == '/');
+	nm_assert (   storage_type >= NMS_KEYFILE_STORAGE_TYPE_RUN
+	           && storage_type <= _NMS_KEYFILE_STORAGE_TYPE_LIB_LAST);
+	nmtst_connection_assert_unchanging (connection_take);
+
+	self = _storage_new (plugin, nm_connection_get_uuid (connection_take), filename, FALSE, storage_type);
+
+	self->u.conn_data.connection = connection_take; /* take reference. */
+
+	self->u.conn_data.shadowed_storage = g_strdup (shadowed_storage);
+
+	if (stat_mtime)
+		self->u.conn_data.stat_mtime = *stat_mtime;
+
+	if (storage_type == NMS_KEYFILE_STORAGE_TYPE_RUN) {
+		self->u.conn_data.is_nm_generated = (is_nm_generated_opt == NM_TERNARY_TRUE);
+		self->u.conn_data.is_volatile     = (is_volatile_opt == NM_TERNARY_TRUE);
+		self->u.conn_data.shadowed_owned  =    shadowed_storage
+		                                    && (shadowed_owned_opt == NM_TERNARY_TRUE);
+	}
+
+	return self;
+}
+
+static void
+_storage_clear (NMSKeyfileStorage *self)
+{
+	c_list_unlink (&self->parent._storage_lst);
+	c_list_unlink (&self->parent._storage_by_uuid_lst);
+	if (self->is_meta_data)
+		nm_clear_g_free (&self->u.meta_data.shadowed_storage);
+	else {
+		g_clear_object (&self->u.conn_data.connection);
+		nm_clear_g_free (&self->u.conn_data.shadowed_storage);
+		self->u.conn_data.shadowed_owned = FALSE;
+	}
+}
+
+static void
+dispose (GObject *object)
+{
+	NMSKeyfileStorage *self = NMS_KEYFILE_STORAGE (object);
+
+	_storage_clear (self);
+
+	G_OBJECT_CLASS (nms_keyfile_storage_parent_class)->dispose (object);
+}
+
+void
+nms_keyfile_storage_destroy (NMSKeyfileStorage *self)
+{
+	_storage_clear (self);
+	g_object_unref (self);
+}
+
+static void
+nms_keyfile_storage_class_init (NMSKeyfileStorageClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	NMSettingsStorageClass *storage_class = NM_SETTINGS_STORAGE_CLASS (klass);
+
+	object_class->dispose = dispose;
+
+	storage_class->cmp_fcn = (int (*) (NMSettingsStorage *, NMSettingsStorage *)) cmp_fcn;
+}
+
+/*****************************************************************************/
+
+#include "settings/nm-settings-connection.h"
+
+void
+nm_settings_storage_load_sett_flags (NMSettingsStorage *self,
+                                     NMSettingsConnectionIntFlags *sett_flags,
+                                     NMSettingsConnectionIntFlags *sett_mask)
+{
+	NMSKeyfileStorage *s;
+
+	*sett_flags = NM_SETTINGS_CONNECTION_INT_FLAGS_NONE;
+	*sett_mask =   NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
+	             | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE;
+
+	if (!NMS_IS_KEYFILE_STORAGE (self))
+		return;
+
+	s = NMS_KEYFILE_STORAGE (self);
+
+	if (s->is_meta_data)
+		return;
+	if (s->storage_type != NMS_KEYFILE_STORAGE_TYPE_RUN)
+		return;
+
+	if (s->u.conn_data.is_nm_generated)
+		*sett_flags |= NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED;
+
+	if (s->u.conn_data.is_volatile)
+		*sett_flags |= NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE;
+}
diff --git a/src/settings/plugins/keyfile/nms-keyfile-storage.h b/src/settings/plugins/keyfile/nms-keyfile-storage.h
new file mode 100644
index 00000000..2252b47b
--- /dev/null
+++ b/src/settings/plugins/keyfile/nms-keyfile-storage.h
@@ -0,0 +1,264 @@
+/* NetworkManager system settings service - keyfile plugin
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2018 Red Hat, Inc.
+ */
+
+#ifndef __NMS_KEYFILE_STORAGE_H__
+#define __NMS_KEYFILE_STORAGE_H__
+
+#include "c-list/src/c-list.h"
+#include "settings/nm-settings-storage.h"
+#include "nms-keyfile-utils.h"
+
+/*****************************************************************************/
+
+#define NMS_TYPE_KEYFILE_STORAGE            (nms_keyfile_storage_get_type ())
+#define NMS_KEYFILE_STORAGE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NMS_TYPE_KEYFILE_STORAGE, NMSKeyfileStorage))
+#define NMS_KEYFILE_STORAGE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NMS_TYPE_KEYFILE_STORAGE, NMSKeyfileStorageClass))
+#define NMS_IS_KEYFILE_STORAGE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NMS_TYPE_KEYFILE_STORAGE))
+#define NMS_IS_KEYFILE_STORAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NMS_TYPE_KEYFILE_STORAGE))
+#define NMS_KEYFILE_STORAGE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NMS_TYPE_KEYFILE_STORAGE, NMSKeyfileStorageClass))
+
+typedef struct {
+	/* whether this is a tombstone to hide a UUID (via symlink to /dev/null). */
+	char *shadowed_storage;
+	bool is_tombstone:1;
+} NMSettingsMetaData;
+
+typedef struct {
+	NMSettingsStorage parent;
+
+	/* The connection. Note that there are tombstones (loaded-uuid files to /dev/null)
+	 * that don't have a connection.
+	 *
+	 * Also, we don't actually remember the loaded connection after returning it
+	 * to NMSettings. So, also for regular storages (non-tombstones) this field
+	 * is often cleared. */
+	union {
+		struct {
+			NMConnection *connection;
+
+			/* when we move a profile from permanent storage to unsaved (/run), then
+			 * we may leave the profile on disk (depending on options for Update2()).
+			 *
+			 * Later, when we save the profile again to disk, we want to re-use that filename.
+			 * Likewise, we delete the (now in-memory) profile, we may want to also delete
+			 * the original filename.
+			 *
+			 * This is the original filename, and we store it inside [.nmmeta] in the
+			 * keyfile in /run. Note that we don't store this in the .nmmeta file, because
+			 * the information is tied to the particular keyfile in /run, not to all UUIDs
+			 * in general. */
+			char *shadowed_storage;
+
+			/* the timestamp (stat's mtime) of the keyfile. For meta-data this
+			 * is irrelevant. The purpose is that if the same storage type (directory) has
+			 * multiple files with the same UUID, then the newer file gets preferred. */
+			struct timespec stat_mtime;
+
+			/* these flags are only relevant for storages with %NMS_KEYFILE_STORAGE_TYPE_RUN
+			 * (and non-metadata). This is to persist and reload these settings flags to
+			 * /run.
+			 *
+			 * Note that these flags are not stored in as meta-data. The reason is that meta-data
+			 * is per UUID. But these flags are only relevant for a particular keyfile on disk.
+			 * That is, it must be tied to the actual keyfile, and not to the UUID. */
+			bool is_nm_generated:1;
+			bool is_volatile:1;
+
+			/* if shadowed_storage is set, then this flag indicates whether the file
+			 * is owned. The difference comes into play when deleting the in-memory,
+			 * shadowing profile: a owned profile will also be deleted. */
+			bool shadowed_owned:1;
+
+		} conn_data;
+
+		/* the content from the .nmmeta file. Note that the nmmeta file has the UUID
+		 * in the filename, that means there can be only two variants of this file:
+		 * in /etc and in /run. As such, this is really meta-data about the entire profile
+		 * (the UUID), and not about the individual keyfile. */
+		NMSettingsMetaData meta_data;
+
+	} u;
+
+	/* The storage type. This is directly related to the filename. Since
+	 * the filename cannot change, this value is unchanging. */
+	const NMSKeyfileStorageType storage_type;
+
+	/* whether union "u" has meta_data or conn_data. Since the type of the storage
+	 * depends on the (immutable) filename, this is also const. */
+	const bool is_meta_data;
+
+	/* this flag is only used during reload to mark and prune old entries. */
+	bool is_dirty:1;
+
+} NMSKeyfileStorage;
+
+typedef struct _NMSKeyfileStorageClass NMSKeyfileStorageClass;
+
+GType nms_keyfile_storage_get_type (void);
+
+struct _NMSKeyfilePlugin;
+
+NMSKeyfileStorage *nms_keyfile_storage_new_tombstone (struct _NMSKeyfilePlugin *self,
+                                                      const char *uuid,
+                                                      const char *filename,
+                                                      NMSKeyfileStorageType storage_type,
+                                                      const char *shadowed_storage);
+
+NMSKeyfileStorage *nms_keyfile_storage_new_connection (struct _NMSKeyfilePlugin *self,
+                                                       NMConnection *connection_take /* pass reference */,
+                                                       const char *filename,
+                                                       NMSKeyfileStorageType storage_type,
+                                                       NMTernary is_nm_generated_opt,
+                                                       NMTernary is_volatile_opt,
+                                                       const char *shadowed_storage,
+                                                       NMTernary shadowed_owned_opt,
+                                                       const struct timespec *stat_mtime);
+
+void nms_keyfile_storage_destroy (NMSKeyfileStorage *storage);
+
+/*****************************************************************************/
+
+void nms_keyfile_storage_copy_content (NMSKeyfileStorage *dst,
+                                       const NMSKeyfileStorage *src);
+
+NMConnection *nms_keyfile_storage_steal_connection (NMSKeyfileStorage *storage);
+
+/*****************************************************************************/
+
+static inline const char *
+nms_keyfile_storage_get_uuid (const NMSKeyfileStorage *self)
+{
+	return nm_settings_storage_get_uuid ((const NMSettingsStorage *) self);
+}
+
+static inline const char *
+nms_keyfile_storage_get_filename (const NMSKeyfileStorage *self)
+{
+	return nm_settings_storage_get_filename ((const NMSettingsStorage *) self);
+}
+
+/*****************************************************************************/
+
+static inline gboolean
+nm_settings_storage_is_keyfile_run (const NMSettingsStorage *self)
+{
+	return    NMS_IS_KEYFILE_STORAGE (self)
+	       && (((NMSKeyfileStorage *) self)->storage_type == NMS_KEYFILE_STORAGE_TYPE_RUN);
+}
+
+static inline gboolean
+nm_settings_storage_is_keyfile_lib (const NMSettingsStorage *self)
+{
+	return    NMS_IS_KEYFILE_STORAGE (self)
+	       && (((NMSKeyfileStorage *) self)->storage_type >= NMS_KEYFILE_STORAGE_TYPE_LIB_BASE);
+}
+
+static inline const NMSettingsMetaData *
+nm_settings_storage_is_meta_data (const NMSettingsStorage *storage)
+{
+	const NMSKeyfileStorage *self;
+
+	if (!NMS_IS_KEYFILE_STORAGE (storage))
+		return NULL;
+
+	self = (NMSKeyfileStorage *) storage;
+
+	if (!self->is_meta_data)
+		return NULL;
+
+	return &self->u.meta_data;
+}
+
+static inline const NMSettingsMetaData *
+nm_settings_storage_is_meta_data_alive (const NMSettingsStorage *storage)
+{
+	const NMSettingsMetaData *meta_data;
+
+	meta_data = nm_settings_storage_is_meta_data (storage);
+
+	if (!meta_data)
+		return NULL;
+
+	/* Regular (all other) storages are alive as long as they report a NMConnection, and
+	 * they will be dropped, once they have no more connection.
+	 *
+	 * Meta-data storages are special: they never report a NMConnection.
+	 * So, a meta-data storage is alive as long as it is tracked by the
+	 * settings plugin.
+	 *
+	 * This function is used to ckeck for that. */
+
+	if (c_list_is_empty (&storage->_storage_lst))
+		return NULL;
+
+	return meta_data;
+}
+
+static inline const char *
+nm_settings_storage_get_shadowed_storage (const NMSettingsStorage *storage,
+                                          gboolean *out_shadowed_owned)
+{
+	if (NMS_IS_KEYFILE_STORAGE (storage)) {
+		const NMSKeyfileStorage *self = (const NMSKeyfileStorage *) storage;
+
+		if (self->storage_type == NMS_KEYFILE_STORAGE_TYPE_RUN) {
+			if (!self->is_meta_data) {
+				if (self->u.conn_data.shadowed_storage) {
+					NM_SET_OUT (out_shadowed_owned, self->u.conn_data.shadowed_owned);
+					return self->u.conn_data.shadowed_storage;
+				}
+			} else {
+				NM_SET_OUT (out_shadowed_owned, FALSE);
+				return self->u.meta_data.shadowed_storage;
+			}
+		}
+	}
+
+	NM_SET_OUT (out_shadowed_owned, FALSE);
+	return NULL;
+}
+
+static inline const char *
+nm_settings_storage_get_filename_for_shadowed_storage (const NMSettingsStorage *storage)
+{
+	g_return_val_if_fail (NM_IS_SETTINGS_STORAGE (storage), NULL);
+
+	if (!storage->_filename)
+		return NULL;
+
+	if (NMS_IS_KEYFILE_STORAGE (storage)) {
+		const NMSKeyfileStorage *self = (const NMSKeyfileStorage *) storage;
+
+		if (   self->is_meta_data
+		    || self->storage_type != NMS_KEYFILE_STORAGE_TYPE_ETC)
+			return NULL;
+	}
+
+	return storage->_filename;
+}
+
+/*****************************************************************************/
+
+enum _NMSettingsConnectionIntFlags;
+
+void nm_settings_storage_load_sett_flags (NMSettingsStorage *self,
+                                          enum _NMSettingsConnectionIntFlags *sett_flags,
+                                          enum _NMSettingsConnectionIntFlags *sett_mask);
+
+#endif /* __NMS_KEYFILE_STORAGE_H__ */
diff --git a/src/settings/plugins/keyfile/nms-keyfile-utils.c b/src/settings/plugins/keyfile/nms-keyfile-utils.c
index 3c4b0288..ea03e1b6 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-utils.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-utils.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager system settings service
  *
  * This program is free software; you can redistribute it and/or modify
@@ -15,7 +14,7 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * (C) Copyright 2010 Red Hat, Inc.
+ * (C) Copyright 2010 - 2018 Red Hat, Inc.
  */
 
 #include "nm-default.h"
@@ -25,6 +24,7 @@
 #include <stdlib.h>
 #include <sys/stat.h>
 
+#include "nm-glib-aux/nm-io-utils.h"
 #include "nm-keyfile-internal.h"
 #include "nm-utils.h"
 #include "nm-setting-wired.h"
@@ -34,108 +34,155 @@
 
 /*****************************************************************************/
 
+#define NMMETA_KF_GROUP_NAME_NMMETA                 "nmmeta"
+#define NMMETA_KF_KEY_NAME_NMMETA_UUID              "uuid"
+#define NMMETA_KF_KEY_NAME_NMMETA_LOADED_PATH       "loaded-path"
+#define NMMETA_KF_KEY_NAME_NMMETA_SHADOWED_STORAGE  "shadowed-storage"
+
+/*****************************************************************************/
+
+const char *
+nms_keyfile_nmmeta_check_filename (const char *filename,
+                                   guint *out_uuid_len)
+{
+	const char *uuid;
+	const char *s;
+	gsize len;
+
+	s = strrchr (filename, '/');
+	if (s)
+		filename = &s[1];
+
+	len = strlen (filename);
+	if (   len <= NM_STRLEN (NM_KEYFILE_PATH_SUFFIX_NMMETA)
+	    || memcmp (&filename[len - NM_STRLEN (NM_KEYFILE_PATH_SUFFIX_NMMETA)],
+	               NM_KEYFILE_PATH_SUFFIX_NMMETA,
+	               NM_STRLEN (NM_KEYFILE_PATH_SUFFIX_NMMETA)) != 0) {
+		/* the filename does not have the right suffix. */
+		return NULL;
+	}
+
+	len -= NM_STRLEN (NM_KEYFILE_PATH_SUFFIX_NMMETA);
+
+	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 NULL;
+	}
+
+	uuid = nm_strndup_a (100, filename, len, NULL);
+	if (!nm_utils_is_uuid (uuid))
+		return NULL;
+
+	NM_SET_OUT (out_uuid_len, len);
+	return filename;
+}
+
 char *
-nms_keyfile_loaded_uuid_filename (const char *dirname,
-                                  const char *uuid,
-                                  gboolean temporary)
+nms_keyfile_nmmeta_filename (const char *dirname,
+                             const char *uuid,
+                             gboolean temporary)
 {
 	char filename[250];
+	char *s;
 
 	nm_assert (dirname && dirname[0] == '/');
-	nm_assert (uuid && nm_utils_is_uuid (uuid) && !strchr (uuid, '/'));
+	nm_assert (   nm_utils_is_uuid (uuid)
+	           && !strchr (uuid, '/'));
 
 	if (g_snprintf (filename,
 	                sizeof (filename),
-	                "%s%s%s%s",
-	                NM_KEYFILE_PATH_PREFIX_NMLOADED,
+	                "%s%s%s",
 	                uuid,
-	                NM_KEYFILE_PATH_SUFFIX_NMCONNECTION,
+	                NM_KEYFILE_PATH_SUFFIX_NMMETA,
 	                temporary ? "~" : "") >= sizeof (filename)) {
-		/* valid uuids are limited in length. The buffer should always be large
-		 * enough. */
+		/* valid uuids are limited in length (nm_utils_is_uuid). The buffer should always
+		 * be large enough. */
 		nm_assert_not_reached ();
-		return NULL;
 	}
 
-	return g_build_filename (dirname, filename, NULL);
+	s = g_build_filename (dirname, filename, NULL);
+
+	nm_assert (nm_keyfile_utils_ignore_filename (s, FALSE));
+
+	return s;
 }
 
 gboolean
-nms_keyfile_loaded_uuid_read (const char *dirname,
-                              const char *filename,
-                              char **out_full_filename,
-                              char **out_uuid,
-                              char **out_loaded_path)
+nms_keyfile_nmmeta_read (const char *dirname,
+                         const char *filename,
+                         char **out_full_filename,
+                         char **out_uuid,
+                         char **out_loaded_path,
+                         char **out_shadowed_storage,
+                         struct stat *out_st)
 {
 	const char *uuid;
-	const char *tmp;
-	gsize len;
+	guint uuid_len;
 	gs_free char *full_filename = NULL;
-	gs_free char *ln = NULL;
+	gs_free char *loaded_path = NULL;
+	gs_free char *shadowed_storage = NULL;
+	struct stat st_stack;
+	struct stat *st = out_st ?: &st_stack;
 
 	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(). */
+	uuid = nms_keyfile_nmmeta_check_filename (filename, &uuid_len);
+	if (!uuid)
 		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. */
+	full_filename = g_build_filename (dirname, filename, NULL);
+
+	if (!nms_keyfile_utils_check_file_permissions (NMS_KEYFILE_FILETYPE_NMMETA,
+	                                               full_filename,
+	                                               st,
+	                                               NULL))
 		return FALSE;
-	}
 
-	tmp = &filename[NM_STRLEN (NM_KEYFILE_PATH_PREFIX_NMLOADED)];
-	len -= NM_STRLEN (NM_KEYFILE_PATH_PREFIX_NMLOADED);
+	if (S_ISREG (st->st_mode)) {
+		gs_unref_keyfile GKeyFile *kf = NULL;
+		gs_free char *v_uuid = NULL;
 
-	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);
+		kf = g_key_file_new ();
 
-	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;
-	}
+		if (!g_key_file_load_from_file (kf, full_filename, G_KEY_FILE_NONE, NULL))
+			return FALSE;
 
-	uuid = nm_strndup_a (100, tmp, len, NULL);
-	if (!nm_utils_is_uuid (uuid))
-		return FALSE;
+		v_uuid = g_key_file_get_string (kf, NMMETA_KF_GROUP_NAME_NMMETA, NMMETA_KF_KEY_NAME_NMMETA_UUID, NULL);
+		if (!nm_streq0 (v_uuid, uuid))
+			return FALSE;
 
-	full_filename = g_build_filename (dirname, filename, NULL);
+		loaded_path = g_key_file_get_string (kf, NMMETA_KF_GROUP_NAME_NMMETA, NMMETA_KF_KEY_NAME_NMMETA_LOADED_PATH, NULL);
+		shadowed_storage = g_key_file_get_string (kf, NMMETA_KF_GROUP_NAME_NMMETA, NMMETA_KF_KEY_NAME_NMMETA_SHADOWED_STORAGE, NULL);
 
-	if (!nms_keyfile_utils_check_file_permissions (NMS_KEYFILE_FILETYPE_NMLOADED,
-	                                               full_filename,
-	                                               NULL,
-	                                               NULL))
-		return FALSE;
+		if (   !loaded_path
+		    && !shadowed_storage) {
+			/* if there is no useful information in the file, it is the same as if
+			 * the file is not present. Signal failure. */
+			return FALSE;
+		}
 
-	ln = nm_utils_read_link_absolute (full_filename, NULL);
-	if (!ln)
-		return FALSE;
+	} else {
+		loaded_path = nm_utils_read_link_absolute (full_filename, NULL);
+		if (!loaded_path)
+			return FALSE;
+	}
 
-	NM_SET_OUT (out_uuid, g_strdup (uuid));
+	NM_SET_OUT (out_uuid, g_strndup (uuid, uuid_len));
 	NM_SET_OUT (out_full_filename, g_steal_pointer (&full_filename));
-	NM_SET_OUT (out_loaded_path, g_steal_pointer (&ln));
+	NM_SET_OUT (out_loaded_path, g_steal_pointer (&loaded_path));
+	NM_SET_OUT (out_shadowed_storage, g_steal_pointer (&shadowed_storage));
 	return TRUE;
 }
 
 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)
+nms_keyfile_nmmeta_read_from_file (const char *full_filename,
+                                   char **out_dirname,
+                                   char **out_filename,
+                                   char **out_uuid,
+                                   char **out_loaded_path,
+                                   char **out_shadowed_storage)
 {
 	gs_free char *dirname = NULL;
 	gs_free char *filename = NULL;
@@ -145,11 +192,13 @@ nms_keyfile_loaded_uuid_read_from_file (const char *full_filename,
 	filename = g_path_get_basename (full_filename);
 	dirname = g_path_get_dirname (full_filename);
 
-	if (!nms_keyfile_loaded_uuid_read (dirname,
-	                                   filename,
-	                                   NULL,
-	                                   out_uuid,
-	                                   out_loaded_path))
+	if (!nms_keyfile_nmmeta_read (dirname,
+	                              filename,
+	                              NULL,
+	                              out_uuid,
+	                              out_loaded_path,
+	                              out_shadowed_storage,
+	                              NULL))
 		return FALSE;
 
 	NM_SET_OUT (out_dirname, g_steal_pointer (&dirname));
@@ -158,20 +207,23 @@ nms_keyfile_loaded_uuid_read_from_file (const char *full_filename,
 }
 
 gboolean
-nms_keyfile_loaded_uuid_write (const char *dirname,
-                               const char *uuid,
-                               const char *loaded_path,
-                               gboolean allow_relative,
-                               char **out_full_filename)
+nms_keyfile_nmmeta_write (const char *dirname,
+                          const char *uuid,
+                          const char *loaded_path,
+                          gboolean loaded_path_allow_relative,
+                          const char *shadowed_storage,
+                          char **out_full_filename)
 {
 	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 (   nm_utils_is_uuid (uuid)
+	           && !strchr (uuid, '/'));
 	nm_assert (!loaded_path || loaded_path[0] == '/');
+	nm_assert (!shadowed_storage || loaded_path);
 
-	full_filename_tmp = nms_keyfile_loaded_uuid_filename (dirname, uuid, TRUE);
+	full_filename_tmp = nms_keyfile_nmmeta_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));
@@ -188,7 +240,7 @@ nms_keyfile_loaded_uuid_write (const char *dirname,
 		return success;
 	}
 
-	if (allow_relative) {
+	if (loaded_path_allow_relative) {
 		const char *f;
 
 		f = nm_utils_file_is_in_path (loaded_path, dirname);
@@ -199,18 +251,40 @@ nms_keyfile_loaded_uuid_write (const char *dirname,
 		}
 	}
 
-	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;
-	}
+	full_filename = g_strndup (full_filename_tmp, strlen (full_filename_tmp) - 1);
 
-	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;
+	if (shadowed_storage) {
+		gs_unref_keyfile GKeyFile *kf = NULL;
+		gs_free char *contents = NULL;
+		gsize length;
+
+		kf = g_key_file_new ();
+
+		g_key_file_set_string (kf, NMMETA_KF_GROUP_NAME_NMMETA, NMMETA_KF_KEY_NAME_NMMETA_UUID, uuid);
+		g_key_file_set_string (kf, NMMETA_KF_GROUP_NAME_NMMETA, NMMETA_KF_KEY_NAME_NMMETA_LOADED_PATH, loaded_path);
+		g_key_file_set_string (kf, NMMETA_KF_GROUP_NAME_NMMETA, NMMETA_KF_KEY_NAME_NMMETA_SHADOWED_STORAGE, shadowed_storage);
+
+		contents = g_key_file_to_data (kf, &length, NULL);
+
+		if (!nm_utils_file_set_contents (full_filename, contents, length, 0600, NULL)) {
+			NM_SET_OUT (out_full_filename, g_steal_pointer (&full_filename_tmp));
+			return FALSE;
+		}
+	} else {
+		/* we only have the "loaded_path" to store. That is commonly used for the tombstones to
+		 * link to /dev/null. A symlink is sufficient to store that ammount of information.
+		 * No need to bother with a keyfile. */
+		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;
+		}
+
+		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));
@@ -232,10 +306,11 @@ nms_keyfile_utils_check_file_permissions_stat (NMSKeyfileFiletype filetype,
 			                     "file is not a regular file");
 			return FALSE;
 		}
-	} else if (filetype == NMS_KEYFILE_FILETYPE_NMLOADED) {
-		if (!S_ISLNK (st->st_mode)) {
+	} else if (filetype == NMS_KEYFILE_FILETYPE_NMMETA) {
+		if (   !S_ISLNK (st->st_mode)
+		    && !S_ISREG (st->st_mode)) {
 			g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			                     "file is not a slink");
+			                     "file is neither a symlink nor a regular file");
 			return FALSE;
 		}
 	} else
@@ -249,7 +324,7 @@ nms_keyfile_utils_check_file_permissions_stat (NMSKeyfileFiletype filetype,
 			return FALSE;
 		}
 
-		if (   filetype == NMS_KEYFILE_FILETYPE_KEYFILE
+		if (   S_ISREG (st->st_mode)
 		    && (st->st_mode & 0077)) {
 			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 			             "File permissions (%03o) are insecure",
@@ -279,7 +354,7 @@ nms_keyfile_utils_check_file_permissions (NMSKeyfileFiletype filetype,
 			             "cannot access file: %s", nm_strerror_native (errsv));
 			return FALSE;
 		}
-	} else if (filetype == NMS_KEYFILE_FILETYPE_NMLOADED) {
+	} else if (filetype == NMS_KEYFILE_FILETYPE_NMMETA) {
 		if (lstat (filename, &st) != 0) {
 			errsv = errno;
 			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
@@ -295,22 +370,3 @@ nms_keyfile_utils_check_file_permissions (NMSKeyfileFiletype filetype,
 	NM_SET_OUT (out_st, st);
 	return TRUE;
 }
-
-/*****************************************************************************/
-
-const char *
-nms_keyfile_utils_get_path (void)
-{
-	static char *path = NULL;
-
-	if (G_UNLIKELY (!path)) {
-		path = nm_config_data_get_value (NM_CONFIG_GET_DATA_ORIG,
-		                                 NM_CONFIG_KEYFILE_GROUP_KEYFILE,
-		                                 NM_CONFIG_KEYFILE_KEY_KEYFILE_PATH,
-		                                 NM_CONFIG_GET_VALUE_STRIP | NM_CONFIG_GET_VALUE_NO_EMPTY);
-		if (!path)
-			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 bc601dad..723c4436 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-utils.h
+++ b/src/settings/plugins/keyfile/nms-keyfile-utils.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager system settings service
  *
  * This program is free software; you can redistribute it and/or modify
@@ -15,7 +14,7 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * (C) Copyright 2010-2016 Red Hat, Inc.
+ * (C) Copyright 2010 - 2018 Red Hat, Inc.
  */
 
 #ifndef __NMS_KEYFILE_UTILS_H__
@@ -23,42 +22,56 @@
 
 #include "NetworkManagerUtils.h"
 
-#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)
-
 typedef enum {
 	NMS_KEYFILE_FILETYPE_KEYFILE,
-	NMS_KEYFILE_FILETYPE_NMLOADED,
+	NMS_KEYFILE_FILETYPE_NMMETA,
 } NMSKeyfileFiletype;
 
-const char *nms_keyfile_utils_get_path (void);
+typedef enum {
+	NMS_KEYFILE_STORAGE_TYPE_RUN       = 1, /* read-write, runtime only, e.g. /run */
+	NMS_KEYFILE_STORAGE_TYPE_ETC       = 2, /* read-write, persistent,   e.g. /etc     */
+	NMS_KEYFILE_STORAGE_TYPE_LIB_BASE  = 3, /* read-only,                e.g. /usr/lib */
+
+	_NMS_KEYFILE_STORAGE_TYPE_LIB_LAST = 1000,
+} NMSKeyfileStorageType;
+
+static inline NMSKeyfileStorageType
+NMS_KEYFILE_STORAGE_TYPE_LIB (guint run_idx)
+{
+	nm_assert (run_idx <= (_NMS_KEYFILE_STORAGE_TYPE_LIB_LAST - NMS_KEYFILE_STORAGE_TYPE_LIB_BASE));
+	return NMS_KEYFILE_STORAGE_TYPE_LIB_BASE + run_idx;
+}
 
 /*****************************************************************************/
 
-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);
+const char *nms_keyfile_nmmeta_check_filename (const char *filename,
+                                               guint *out_uuid_len);
+
+char *nms_keyfile_nmmeta_filename (const char *dirname,
+                                   const char *uuid,
+                                   gboolean temporary);
+
+gboolean nms_keyfile_nmmeta_read (const char *dirname,
+                                  const char *filename,
+                                  char **out_full_filename,
+                                  char **out_uuid,
+                                  char **out_loaded_path,
+                                  char **out_shadowed_storage,
+                                  struct stat *out_st);
+
+gboolean nms_keyfile_nmmeta_read_from_file (const char *full_filename,
+                                            char **out_dirname,
+                                            char **out_filename,
+                                            char **out_uuid,
+                                            char **out_loaded_path,
+                                            char **out_shadowed_storage);
+
+gboolean nms_keyfile_nmmeta_write (const char *dirname,
+                                   const char *uuid,
+                                   const char *loaded_path,
+                                   gboolean loaded_path_allow_relative,
+                                   const char *shadowed_storage,
+                                   char **out_full_filename);
 
 /*****************************************************************************/
 
diff --git a/src/settings/plugins/keyfile/nms-keyfile-writer.c b/src/settings/plugins/keyfile/nms-keyfile-writer.c
index 2e06aeb9..5fbbb7a1 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-writer.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-writer.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager system settings service - keyfile plugin
  *
  * This program is free software; you can redistribute it and/or modify
@@ -169,6 +168,10 @@ _handler_write (NMConnection *connection,
 
 static gboolean
 _internal_write_connection (NMConnection *connection,
+                            gboolean is_nm_generated,
+                            gboolean is_volatile,
+                            const char *shadowed_storage,
+                            gboolean shadowed_owned,
                             const char *keyfile_dir,
                             const char *profile_dir,
                             gboolean with_extension,
@@ -177,6 +180,8 @@ _internal_write_connection (NMConnection *connection,
                             const char *existing_path,
                             gboolean existing_path_read_only,
                             gboolean force_rename,
+                            NMSKeyfileWriterAllowFilenameCb allow_filename_cb,
+                            gpointer allow_filename_user_data,
                             char **out_path,
                             NMConnection **out_reread,
                             gboolean *out_reread_same,
@@ -191,33 +196,57 @@ _internal_write_connection (NMConnection *connection,
 	GError *local_err = NULL;
 	int errsv;
 	gboolean rename;
+	int i_path;
 
 	g_return_val_if_fail (!out_path || !*out_path, FALSE);
 	g_return_val_if_fail (keyfile_dir && keyfile_dir[0] == '/', FALSE);
 
+	nm_assert (_nm_connection_verify (connection, NULL) == NM_SETTING_VERIFY_SUCCESS);
+
+	nm_assert (!shadowed_owned || shadowed_storage);
+
 	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:
-		nm_assert_not_reached ();
-		/* fall-through */
-	case NM_SETTING_VERIFY_SUCCESS:
-		break;
-	default:
-		g_return_val_if_reached (FALSE);
-	}
-
 	id = nm_connection_get_id (connection);
-	g_assert (id && *id);
+	nm_assert (id && *id);
 
 	info.keyfile_dir = keyfile_dir;
 
 	kf_file = nm_keyfile_write (connection, _handler_write, &info, error);
 	if (!kf_file)
 		return FALSE;
+
+	if (is_nm_generated) {
+		g_key_file_set_boolean (kf_file,
+		                        NM_KEYFILE_GROUP_NMMETA,
+		                        NM_KEYFILE_KEY_NMMETA_NM_GENERATED,
+		                        TRUE);
+	}
+
+	if (is_volatile) {
+		g_key_file_set_boolean (kf_file,
+		                        NM_KEYFILE_GROUP_NMMETA,
+		                        NM_KEYFILE_KEY_NMMETA_VOLATILE,
+		                        TRUE);
+	}
+
+	if (shadowed_storage) {
+		g_key_file_set_string (kf_file,
+		                       NM_KEYFILE_GROUP_NMMETA,
+		                       NM_KEYFILE_KEY_NMMETA_SHADOWED_STORAGE,
+		                       shadowed_storage);
+	}
+
+	if (shadowed_owned) {
+		g_key_file_set_boolean (kf_file,
+		                        NM_KEYFILE_GROUP_NMMETA,
+		                        NM_KEYFILE_KEY_NMMETA_SHADOWED_OWNED,
+		                        TRUE);
+	}
+
 	kf_content_buf = g_key_file_to_data (kf_file, &kf_content_len, error);
 	if (!kf_content_buf)
 		return FALSE;
@@ -225,64 +254,59 @@ _internal_write_connection (NMConnection *connection,
 	if (!g_file_test (keyfile_dir, G_FILE_TEST_IS_DIR))
 		(void) g_mkdir_with_parents (keyfile_dir, 0755);
 
-	/* If we have existing file path, use it. Else generate one from
-	 * connection's ID.
-	 */
-	if (   existing_path
-	    && !rename)
-		path = g_strdup (existing_path);
-	else {
-		gs_free char *filename_escaped = NULL;
+	for (i_path = -2; i_path < 10000; i_path++) {
+		gs_free char *path_candidate = NULL;
+		gboolean is_existing_path;
 
-		filename_escaped = nm_keyfile_utils_create_filename (id, with_extension);
-		path = g_build_filename (keyfile_dir, filename_escaped, NULL);
-	}
-
-	/* If a file with this path already exists (but isn't the existing path
-	 * of the connection) then we need another name.  Multiple connections
-	 * can have the same ID (ie if two connections with the same ID are visible
-	 * to different users) but of course can't have the same path.  Yeah,
-	 * 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 (   !nm_streq0 (path, existing_path)
-	    && g_file_test (path, G_FILE_TEST_EXISTS)) {
-		guint i;
-		gboolean name_found = FALSE;
+		if (i_path == -2) {
+			if (   !existing_path
+			    || rename)
+				continue;
+			path_candidate = g_strdup (existing_path);
+		} else if (i_path == -1) {
+			gs_free char *filename_escaped = NULL;
 
-		/* A keyfile with this connection's ID already exists. Pick another name. */
-		for (i = 0; i < 100; i++) {
+			filename_escaped = nm_keyfile_utils_create_filename (id, with_extension);
+			path_candidate = g_build_filename (keyfile_dir, filename_escaped, NULL);
+		} else {
 			gs_free char *filename_escaped = NULL;
 			gs_free char *filename = NULL;
 
-			if (i == 0)
+			if (i_path == 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 = g_strdup_printf ("%s-%s-%d", id, nm_connection_get_uuid (connection), i_path);
 
 			filename_escaped = nm_keyfile_utils_create_filename (filename, with_extension);
 
-			g_free (path);
-			path = g_strdup_printf ("%s/%s", keyfile_dir, filename_escaped);
-
-			if (   nm_streq0 (path, existing_path)
-			    || !g_file_test (path, G_FILE_TEST_EXISTS)) {
-				name_found = TRUE;
-				break;
-			}
+			path_candidate = g_strdup_printf ("%s/%s", keyfile_dir, filename_escaped);
 		}
-		if (!name_found) {
-			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);
-				return FALSE;
-			}
-			/* Both our preferred path based on connection id and id-uuid are taken.
-			 * Fallback to @existing_path */
-			g_free (path);
-			path = g_strdup (existing_path);
+
+		is_existing_path =    existing_path
+		                   && nm_streq (existing_path, path_candidate);
+
+		if (   is_existing_path
+		    && rename)
+			continue;
+
+		if (   allow_filename_cb
+		    && !allow_filename_cb (path_candidate, allow_filename_user_data))
+			continue;
+
+		if (!is_existing_path) {
+			if (g_file_test (path_candidate, G_FILE_TEST_EXISTS))
+				continue;
 		}
+
+		path = g_steal_pointer (&path_candidate);
+		break;
+	}
+
+	if (!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);
+		return FALSE;
 	}
 
 	nm_utils_file_set_contents (path, kf_content_buf, kf_content_len, 0600, &local_err);
@@ -348,30 +372,37 @@ _internal_write_connection (NMConnection *connection,
 
 gboolean
 nms_keyfile_writer_connection (NMConnection *connection,
-                               gboolean save_to_disk,
+                               gboolean is_nm_generated,
+                               gboolean is_volatile,
+                               const char *shadowed_storage,
+                               gboolean shadowed_owned,
+                               const char *keyfile_dir,
+                               const char *profile_dir,
                                const char *existing_path,
+                               gboolean existing_path_read_only,
                                gboolean force_rename,
+                               NMSKeyfileWriterAllowFilenameCb allow_filename_cb,
+                               gpointer allow_filename_user_data,
                                char **out_path,
                                NMConnection **out_reread,
                                gboolean *out_reread_same,
                                GError **error)
 {
-	const char *keyfile_dir;
-
-	if (save_to_disk)
-		keyfile_dir = nms_keyfile_utils_get_path ();
-	else
-		keyfile_dir = NM_KEYFILE_PATH_NAME_RUN;
-
 	return _internal_write_connection (connection,
+	                                   is_nm_generated,
+	                                   is_volatile,
+	                                   shadowed_storage,
+	                                   shadowed_owned,
 	                                   keyfile_dir,
-	                                   nms_keyfile_utils_get_path (),
+	                                   profile_dir,
 	                                   TRUE,
 	                                   0,
 	                                   0,
 	                                   existing_path,
-	                                   FALSE,
+	                                   existing_path_read_only,
 	                                   force_rename,
+	                                   allow_filename_cb,
+	                                   allow_filename_user_data,
 	                                   out_path,
 	                                   out_reread,
 	                                   out_reread_same,
@@ -389,6 +420,10 @@ nms_keyfile_writer_test_connection (NMConnection *connection,
                                     GError **error)
 {
 	return _internal_write_connection (connection,
+	                                   FALSE,
+	                                   FALSE,
+	                                   NULL,
+	                                   FALSE,
 	                                   keyfile_dir,
 	                                   keyfile_dir,
 	                                   FALSE,
@@ -397,6 +432,8 @@ nms_keyfile_writer_test_connection (NMConnection *connection,
 	                                   NULL,
 	                                   FALSE,
 	                                   FALSE,
+	                                   NULL,
+	                                   NULL,
 	                                   out_path,
 	                                   out_reread,
 	                                   out_reread_same,
diff --git a/src/settings/plugins/keyfile/nms-keyfile-writer.h b/src/settings/plugins/keyfile/nms-keyfile-writer.h
index 030fb7cc..99e86025 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-writer.h
+++ b/src/settings/plugins/keyfile/nms-keyfile-writer.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager system settings service - keyfile plugin
  *
  * This program is free software; you can redistribute it and/or modify
@@ -24,10 +23,21 @@
 
 #include "nm-connection.h"
 
+typedef gboolean (*NMSKeyfileWriterAllowFilenameCb) (const char *check_filename,
+                                                     gpointer allow_filename_user_data);
+
 gboolean nms_keyfile_writer_connection (NMConnection *connection,
-                                        gboolean save_to_disk,
+                                        gboolean is_nm_generated,
+                                        gboolean is_volatile,
+                                        const char *shadowed_storage,
+                                        gboolean shadowed_owned,
+                                        const char *keyfile_dir,
+                                        const char *profile_dir,
                                         const char *existing_path,
+                                        gboolean existing_path_read_only,
                                         gboolean force_rename,
+                                        NMSKeyfileWriterAllowFilenameCb allow_filename_cb,
+                                        gpointer allow_filename_user_data,
                                         char **out_path,
                                         NMConnection **out_reread,
                                         gboolean *out_reread_same,
diff --git a/src/settings/plugins/keyfile/tests/meson.build b/src/settings/plugins/keyfile/tests/meson.build
index 752b6d7b..f1e96bdf 100644
--- a/src/settings/plugins/keyfile/tests/meson.build
+++ b/src/settings/plugins/keyfile/tests/meson.build
@@ -1,6 +1,4 @@
-test_unit = 'test-keyfile'
-
-test_keyfiles_dir = join_paths(meson.current_source_dir(), 'keyfiles')
+test_unit = 'test-keyfile-settings'
 
 exe = executable(
   test_unit,
@@ -9,7 +7,7 @@ exe = executable(
 )
 
 test(
-  'keyfile/' + test_unit,
+  test_unit,
   test_script,
   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-settings.c
index baecac13..f96111a2 100644
--- a/src/settings/plugins/keyfile/tests/test-keyfile.c
+++ b/src/settings/plugins/keyfile/tests/test-keyfile-settings.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager system settings service - keyfile plugin
  *
  * This program is free software; you can redistribute it and/or modify
@@ -73,7 +72,12 @@ check_ip_route (NMSettingIPConfig *config, int idx, const char *destination, int
 	\
 	_connection = nms_keyfile_reader_from_file (full_filename, \
 	                                            NULL, \
-	                                            (nmtst_get_rand_int () % 2) ? &_error : NULL); \
+	                                            NULL, \
+	                                            NULL, \
+	                                            NULL, \
+	                                            NULL, \
+	                                            NULL, \
+	                                            (nmtst_get_rand_uint32 () % 2) ? &_error : NULL); \
 	nmtst_assert_success (_connection, _error); \
 	nmtst_assert_connection_verifies_without_normalization (_connection); \
 	\
@@ -128,7 +132,7 @@ write_test_connection_reread (NMConnection *connection,
 	gid_t owner_grp;
 	gboolean success;
 	GError *error = NULL;
-	GError **p_error = (nmtst_get_rand_int () % 2) ? &error : NULL;
+	GError **p_error = (nmtst_get_rand_uint32 () % 2) ? &error : NULL;
 	gs_unref_object NMConnection *connection_normalized = NULL;
 
 	g_assert (NM_IS_CONNECTION (connection));
@@ -2508,14 +2512,14 @@ test_nm_keyfile_plugin_utils_escape_filename (void)
 /*****************************************************************************/
 
 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)
+_assert_keyfile_nmmeta (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;
@@ -2534,12 +2538,12 @@ _assert_keyfile_loaded_uuid (const char *dirname,
 
 	filename = g_path_get_basename (exp_full_filename);
 
-	full_filename = nms_keyfile_loaded_uuid_filename (dirname, uuid, FALSE);
+	full_filename = nms_keyfile_nmmeta_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 (nms_keyfile_nmmeta_write (dirname, uuid, loaded_path, allow_relative, NULL, &full_filename));
 	g_assert_cmpstr (full_filename, ==, exp_full_filename);
 	nm_clear_g_free (&full_filename);
 
@@ -2551,7 +2555,7 @@ _assert_keyfile_loaded_uuid (const char *dirname,
 	g_assert_cmpstr (symlink_target, ==, exp_symlink_target);
 
 
-	success = nms_keyfile_loaded_uuid_read (dirname, filename, &full_filename, &uuid2, &loaded_path2);
+	success = nms_keyfile_nmmeta_read (dirname, filename, &full_filename, &uuid2, &loaded_path2, NULL, NULL);
 	g_assert_cmpint (!!exp_uuid, ==, success);
 	if (success)
 		g_assert_cmpstr (full_filename, ==, exp_full_filename);
@@ -2562,7 +2566,7 @@ _assert_keyfile_loaded_uuid (const char *dirname,
 	g_assert_cmpstr (loaded_path2, ==, exp_loaded_path);
 
 
-	success = nms_keyfile_loaded_uuid_read_from_file (exp_full_filename, &dirname3, &filename3, &uuid3, &loaded_path3);
+	success = nms_keyfile_nmmeta_read_from_file (exp_full_filename, &dirname3, &filename3, &uuid3, &loaded_path3, NULL);
 	g_assert_cmpint (!!exp_uuid, ==, success);
 	if (success) {
 		g_assert_cmpstr (dirname3, ==, dirname);
@@ -2576,34 +2580,33 @@ _assert_keyfile_loaded_uuid (const char *dirname,
 }
 
 static void
-test_loaded_uuid (void)
+test_nmmeta (void)
 {
 	const char *uuid = "3c03fd17-ddc3-4100-a954-88b6fafff959";
-	gs_free char *filename = g_strdup_printf ("%s%s%s",
-	                                          NM_KEYFILE_PATH_PREFIX_NMLOADED,
+	gs_free char *filename = g_strdup_printf ("%s%s",
 	                                          uuid,
-	                                          NM_KEYFILE_PATH_SUFFIX_NMCONNECTION);
+	                                          NM_KEYFILE_PATH_SUFFIX_NMMETA);
 	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_path0 = NM_KEYFILE_PATH_NMMETA_SYMLINK_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_nmmeta (TEST_SCRATCH_DIR, uuid, NULL,         FALSE, full_filename, NULL, NULL,         NULL);
+	_assert_keyfile_nmmeta (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_nmmeta (TEST_SCRATCH_DIR, uuid, loaded_path0, FALSE, full_filename, uuid, loaded_path0, loaded_path0);
+	_assert_keyfile_nmmeta (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_nmmeta (TEST_SCRATCH_DIR, uuid, loaded_path1, FALSE, full_filename, uuid, loaded_path1, loaded_path1);
+	_assert_keyfile_nmmeta (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);
+	_assert_keyfile_nmmeta (TEST_SCRATCH_DIR, uuid, loaded_path2, FALSE, full_filename, uuid, loaded_path2, loaded_path2);
+	_assert_keyfile_nmmeta (TEST_SCRATCH_DIR, uuid, loaded_path2, TRUE,  full_filename, uuid, filename2,    loaded_path2);
 
 	(void) unlink (full_filename);
 }
@@ -2697,7 +2700,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);
+	g_test_add_func ("/keyfile/test_nmmeta", test_nmmeta);
 
 	return g_test_run ();
 }
diff --git a/src/settings/plugins/meson.build b/src/settings/plugins/meson.build
index a1aa7823..83981aab 100644
--- a/src/settings/plugins/meson.build
+++ b/src/settings/plugins/meson.build
@@ -1,7 +1,3 @@
-if enable_ibft
-  subdir('ibft')
-endif
-
 if enable_ifcfg_rh
   subdir('ifcfg-rh')
 endif