summary refs log tree commit diff
path: root/src/settings/nm-settings.c
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2019-07-31 10:51:42 +0200
committerMichael Biebl <biebl@debian.org>2019-07-31 10:51:42 +0200
commit2e5fa45ddfbb5cffa1e78221f1cea706e2f298af (patch)
tree86f69d36c56de3074280456eddc854a780b8e04b /src/settings/nm-settings.c
parent85563b7fc7ec2cd21e38debb9b28db342e2e8e7c (diff)
New upstream version 1.19.90 upstream/1.19.90
Diffstat (limited to 'src/settings/nm-settings.c')
-rw-r--r--src/settings/nm-settings.c3806
1 files changed, 2905 insertions, 901 deletions
diff --git a/src/settings/nm-settings.c b/src/settings/nm-settings.c
index 8e18a33e..430d2776 100644
--- a/src/settings/nm-settings.c
+++ b/src/settings/nm-settings.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager system settings service
  *
  * Søren Sandmann <sandmann@daimi.au.dk>
@@ -37,6 +36,8 @@
 #endif
 
 #include "nm-libnm-core-intern/nm-common-macros.h"
+#include "nm-glib-aux/nm-keyfile-aux.h"
+#include "nm-keyfile-internal.h"
 #include "nm-dbus-interface.h"
 #include "nm-connection.h"
 #include "nm-setting-8021x.h"
@@ -60,6 +61,7 @@
 #include "nm-utils.h"
 #include "nm-core-internal.h"
 
+#include "nm-std-aux/c-list-util.h"
 #include "nm-glib-aux/nm-c-list.h"
 #include "nm-dbus-object.h"
 #include "devices/nm-device-ethernet.h"
@@ -70,6 +72,7 @@
 #include "nm-auth-subject.h"
 #include "nm-session-monitor.h"
 #include "plugins/keyfile/nms-keyfile-plugin.h"
+#include "plugins/keyfile/nms-keyfile-storage.h"
 #include "nm-agent-manager.h"
 #include "nm-config.h"
 #include "nm-audit-manager.h"
@@ -79,16 +82,255 @@
 
 /*****************************************************************************/
 
-#define EXPORT(sym) void * __export_##sym = &sym;
+static NM_CACHED_QUARK_FCN ("default-wired-connection", _default_wired_connection_quark)
+
+/*****************************************************************************/
+
+typedef struct _StorageData {
+	CList sd_lst;
+	NMSettingsStorage *storage;
+	NMConnection *connection;
+	bool prioritize:1;
+} StorageData;
+
+static StorageData *
+_storage_data_new_stale (NMSettingsStorage *storage,
+                         NMConnection *connection)
+{
+	StorageData *sd;
+
+	sd = g_slice_new (StorageData);
+	sd->storage    = g_object_ref (storage);
+	sd->connection = nm_g_object_ref (connection);
+	sd->prioritize = FALSE;
+	return sd;
+}
+
+static void
+_storage_data_destroy (StorageData *sd)
+{
+	c_list_unlink_stale (&sd->sd_lst);
+	g_object_unref (sd->storage);
+	nm_g_object_unref (sd->connection);
+	g_slice_free (StorageData, sd);
+}
+
+static StorageData *
+_storage_data_find_in_lst (CList *head,
+                           NMSettingsStorage *storage)
+{
+	StorageData *sd;
+
+	nm_assert (head);
+	nm_assert (NM_IS_SETTINGS_STORAGE (storage));
+
+	c_list_for_each_entry (sd, head, sd_lst) {
+		if (sd->storage == storage)
+			return sd;
+	}
+	return NULL;
+}
+
+static void
+nm_assert_storage_data_lst (CList *head)
+{
+#if NM_MORE_ASSERTS > 5
+	const char *uuid = NULL;
+	StorageData *sd;
+	CList *iter;
 
-EXPORT(nm_settings_connection_get_type)
-EXPORT(nm_settings_connection_update)
+	nm_assert (head);
+
+	if (c_list_is_empty (head))
+		return;
+
+	c_list_for_each_entry (sd, head, sd_lst) {
+		const char *u;
+
+		nm_assert (NM_IS_SETTINGS_STORAGE (sd->storage));
+		nm_assert (!sd->connection || NM_IS_CONNECTION (sd->connection));
+		u = nm_settings_storage_get_uuid (sd->storage);
+		if (!uuid) {
+			uuid = u;
+			nm_assert (nm_utils_is_uuid (uuid));
+		} else
+			nm_assert (nm_streq0 (uuid, u));
+	}
+
+	/* assert that all storages are unique. */
+	c_list_for_each_entry (sd, head, sd_lst) {
+		for (iter = sd->sd_lst.next; iter != head; iter = iter->next)
+			nm_assert (c_list_entry (iter, StorageData, sd_lst)->storage != sd->storage);
+	}
+#endif
+}
+
+static gboolean
+_storage_data_is_alive (StorageData *sd)
+{
+	/* If the storage tracks a connection, it is considered alive.
+	 *
+	 * Meta-data storages are special: they never track a connection.
+	 * We need to check them specially to know when to drop them. */
+	return    sd->connection
+	       || nm_settings_storage_is_meta_data_alive (sd->storage);
+}
 
 /*****************************************************************************/
 
-static NM_CACHED_QUARK_FCN ("plugin-module-path", plugin_module_path_quark)
-static NM_CACHED_QUARK_FCN ("default-wired-connection", _default_wired_connection_quark)
-static NM_CACHED_QUARK_FCN ("default-wired-device", _default_wired_device_quark)
+typedef struct {
+	const char *uuid;
+	NMSettingsConnection *sett_conn;
+	NMSettingsStorage *storage;
+	CList sd_lst_head;
+	CList dirty_sd_lst_head;
+
+	CList sce_dirty_lst;
+
+	char _uuid_data[];
+} SettConnEntry;
+
+static SettConnEntry *
+_sett_conn_entry_new (const char *uuid)
+{
+	SettConnEntry *sett_conn_entry;
+	gsize l_p_1;
+
+	nm_assert (nm_utils_is_uuid (uuid));
+
+	l_p_1 = strlen (uuid) + 1;
+
+	sett_conn_entry = g_malloc (sizeof (SettConnEntry) + l_p_1);
+	sett_conn_entry->uuid = sett_conn_entry->_uuid_data;
+	sett_conn_entry->sett_conn = NULL;
+	sett_conn_entry->storage = NULL;
+	c_list_init (&sett_conn_entry->sd_lst_head);
+	c_list_init (&sett_conn_entry->dirty_sd_lst_head);
+	c_list_init (&sett_conn_entry->sce_dirty_lst);
+	memcpy (sett_conn_entry->_uuid_data, uuid, l_p_1);
+	return sett_conn_entry;
+}
+
+static void
+_sett_conn_entry_free (SettConnEntry *sett_conn_entry)
+{
+	c_list_unlink_stale (&sett_conn_entry->sce_dirty_lst);
+	nm_c_list_free_all (&sett_conn_entry->sd_lst_head,       StorageData, sd_lst, _storage_data_destroy);
+	nm_c_list_free_all (&sett_conn_entry->dirty_sd_lst_head, StorageData, sd_lst, _storage_data_destroy);
+	nm_g_object_unref (sett_conn_entry->sett_conn);
+	nm_g_object_unref (sett_conn_entry->storage);
+	g_free (sett_conn_entry);
+}
+
+static NMSettingsConnection *
+_sett_conn_entry_get_conn (SettConnEntry *sett_conn_entry)
+{
+	return sett_conn_entry ? sett_conn_entry->sett_conn : NULL;
+}
+
+/**
+ * _sett_conn_entry_storage_find_conflicting_storage:
+ * @sett_conn_entry: the list of settings-storages for the given UUID.
+ * @target_plugin: the settings plugin to check
+ * @storage_check_including: (allow-none): optionally compare against this storage.
+ * @plugins: the list of plugins sorted in descending priority. This determines
+ *   the priority and whether a storage conflicts.
+ *
+ * If we were to add the a storage to @target_plugin, then this function checks
+ * whether there are already other storages that would hide the storage after we
+ * add it. Those conflicting/hiding storages are a problem, because they have higher
+ * priority, so we cannot add the storage.
+ *
+ * @storage_check_including is optional, and if given then it checks whether updating
+ * the profile in this storage would result in confict. This is the check before
+ * update-connection. If this parameter is omitted, then it's about what happens
+ * when adding a new profile (add-connection).
+ *
+ * Returns: the conflicting storage or %NULL if there is none.
+ */
+static NMSettingsStorage *
+_sett_conn_entry_storage_find_conflicting_storage (SettConnEntry *sett_conn_entry,
+                                                   NMSettingsPlugin *target_plugin,
+                                                   NMSettingsStorage *storage_check_including,
+                                                   const GSList *plugins)
+{
+	StorageData *sd;
+
+	if (!sett_conn_entry)
+		return NULL;
+
+	if (   storage_check_including
+	    && nm_settings_storage_is_keyfile_run (storage_check_including)) {
+		/* the storage we check against is in-memory. It always has highest
+		 * priority, so there can be no other conflicting storages. */
+		return NULL;
+	}
+
+	/* Finds the first (highest priority) storage that has a connection.
+	 * Note that due to tombstones (that have a high priority), the connection
+	 * may not actually be exposed. This is to find hidden/shadowed storages
+	 * that provide a connection. */
+	c_list_for_each_entry (sd, &sett_conn_entry->sd_lst_head, sd_lst) {
+		nm_assert (NM_IS_SETTINGS_STORAGE (sd->storage));
+
+		if (!sd->connection) {
+			/* We only consider storages with connection. In particular,
+			 * tombstones are not relevant, because we can delete them to
+			 * resolve the conflict. */
+			continue;
+		}
+
+		if (sd->storage == storage_check_including) {
+			/* ok, the storage is the one we are about to check. All other
+			 * storages are lower priority, so there is no storage that hides
+			 * our storage_check_including. */
+			return NULL;
+		}
+
+		if (nm_settings_plugin_cmp_by_priority (nm_settings_storage_get_plugin (sd->storage),
+		                                        target_plugin,
+		                                        plugins) <= 0) {
+			/* the plugin of the existing storage is less important than @target_plugin.
+			 * We have no conflicting/hiding storage. */
+			return NULL;
+		}
+
+		/* Found. If we would add the profile to @target_plugin, then it would be hidden
+		 * by existing_storage. */
+		return sd->storage;
+	}
+
+	return NULL;
+}
+
+static NMSettingsStorage *
+_sett_conn_entry_find_shadowed_storage (SettConnEntry *sett_conn_entry,
+                                        const char *shadowed_storage_filename,
+                                        NMSettingsStorage *blacklisted_storage)
+{
+	StorageData *sd;
+
+	if (!shadowed_storage_filename)
+		return NULL;
+
+	c_list_for_each_entry (sd, &sett_conn_entry->sd_lst_head, sd_lst) {
+
+		nm_assert (NM_IS_SETTINGS_STORAGE (sd->storage));
+
+		if (!sd->connection)
+			continue;
+
+		if (blacklisted_storage == sd->storage)
+			continue;
+
+		if (!nm_streq0 (nm_settings_storage_get_filename_for_shadowed_storage (sd->storage), shadowed_storage_filename))
+			continue;
+
+		return sd->storage;
+	}
+
+	return NULL;
+}
 
 /*****************************************************************************/
 
@@ -115,25 +357,45 @@ typedef struct {
 
 	NMConfig *config;
 
-	GSList *auths;
+	NMPlatform *platform;
+
+	NMHostnameManager *hostname_manager;
+
+	NMSessionMonitor *session_monitor;
+
+	CList auth_lst_head;
+
+	NMSKeyfilePlugin *keyfile_plugin;
 
 	GSList *plugins;
 
+	NMKeyFileDB *kf_db_timestamps;
+	NMKeyFileDB *kf_db_seen_bssids;
+
+	GHashTable *sce_idx;
+
+	CList sce_dirty_lst_head;
+
 	CList connections_lst_head;
 
 	NMSettingsConnection **connections_cached_list;
+
 	GSList *unmanaged_specs;
 	GSList *unrecognized_specs;
 
-	NMHostnameManager *hostname_manager;
-
+	GHashTable *startup_complete_idx;
 	NMSettingsConnection *startup_complete_blocked_by;
+	gulong startup_complete_platform_change_id;
+	guint startup_complete_timeout_id;
 
 	guint connections_len;
 
+	guint connections_generation;
+
+	guint kf_db_flush_idle_id_timestamps;
+	guint kf_db_flush_idle_id_seen_bssids;
+
 	bool started:1;
-	bool startup_complete:1;
-	bool connections_loaded:1;
 
 } NMSettingsPrivate;
 
@@ -152,6 +414,9 @@ G_DEFINE_TYPE (NMSettings, nm_settings, NM_TYPE_DBUS_OBJECT);
 
 /*****************************************************************************/
 
+/* FIXME: a lot of logging lines are directly connected to a profile. Set the @con_uuid
+ *   argument for structured logging. */
+
 #define _NMLOG_DOMAIN         LOGD_SETTINGS
 #define _NMLOG(level, ...) __NMLOG_DEFAULT (level, _NMLOG_DOMAIN, "settings", __VA_ARGS__)
 
@@ -161,367 +426,286 @@ static const NMDBusInterfaceInfoExtended interface_info_settings;
 static const GDBusSignalInfo signal_info_new_connection;
 static const GDBusSignalInfo signal_info_connection_removed;
 
-static void claim_connection (NMSettings *self,
-                              NMSettingsConnection *connection);
-
-static void unmanaged_specs_changed (NMSettingsPlugin *config, gpointer user_data);
-static void unrecognized_specs_changed (NMSettingsPlugin *config, gpointer user_data);
-
-static void connection_ready_changed (NMSettingsConnection *conn,
-                                      GParamSpec *pspec,
-                                      gpointer user_data);
-
 static void default_wired_clear_tag (NMSettings *self,
                                      NMDevice *device,
-                                     NMSettingsConnection *connection,
+                                     NMSettingsConnection *sett_conn,
                                      gboolean add_to_no_auto_default);
 
+static void _clear_connections_cached_list (NMSettingsPrivate *priv);
+
+static void _startup_complete_check (NMSettings *self,
+                                     gint64 now_us);
+
 /*****************************************************************************/
 
 static void
-check_startup_complete (NMSettings *self)
+_emit_connection_added (NMSettings *self,
+                        NMSettingsConnection *sett_conn)
 {
-	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
-	NMSettingsConnection *sett_conn;
-
-	if (priv->startup_complete)
-		return;
-
-	c_list_for_each_entry (sett_conn, &priv->connections_lst_head, _connections_lst) {
-		if (!nm_settings_connection_get_ready (sett_conn)) {
-			nm_g_object_ref_set (&priv->startup_complete_blocked_by, sett_conn);
-			return;
-		}
-	}
-
-	g_clear_object (&priv->startup_complete_blocked_by);
-
-	/* the connection_ready_changed signal handler is no longer needed. */
-	c_list_for_each_entry (sett_conn, &priv->connections_lst_head, _connections_lst)
-		g_signal_handlers_disconnect_by_func (sett_conn, G_CALLBACK (connection_ready_changed), self);
-
-	priv->startup_complete = TRUE;
-	_notify (self, PROP_STARTUP_COMPLETE);
+	g_signal_emit (self, signals[CONNECTION_ADDED], 0, sett_conn);
 }
 
 static void
-connection_ready_changed (NMSettingsConnection *conn,
-                          GParamSpec *pspec,
-                          gpointer user_data)
+_emit_connection_updated (NMSettings *self,
+                          NMSettingsConnection *sett_conn,
+                          NMSettingsConnectionUpdateReason update_reason)
 {
-	NMSettings *self = NM_SETTINGS (user_data);
-
-	if (nm_settings_connection_get_ready (conn))
-		check_startup_complete (self);
+	_nm_settings_connection_emit_signal_updated_internal (sett_conn, update_reason);
+	g_signal_emit (self, signals[CONNECTION_UPDATED], 0, sett_conn, (guint) update_reason);
 }
 
 static void
-plugin_connection_added (NMSettingsPlugin *config,
-                         NMSettingsConnection *connection,
-                         NMSettings *self)
+_emit_connection_removed (NMSettings *self,
+                          NMSettingsConnection *sett_conn)
 {
-	claim_connection (self, connection);
+	g_signal_emit (self, signals[CONNECTION_REMOVED], 0, sett_conn);
 }
 
 static void
-load_connections (NMSettings *self)
+_emit_connection_flags_changed (NMSettings *self,
+                                NMSettingsConnection *sett_conn)
 {
-	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
-	GSList *iter;
+	g_signal_emit (self, signals[CONNECTION_FLAGS_CHANGED], 0, sett_conn);
+}
 
-	for (iter = priv->plugins; iter; iter = g_slist_next (iter)) {
-		NMSettingsPlugin *plugin = NM_SETTINGS_PLUGIN (iter->data);
-		GSList *plugin_connections;
-		GSList *elt;
+/*****************************************************************************/
 
-		plugin_connections = nm_settings_plugin_get_connections (plugin);
+typedef struct {
+	NMSettingsConnection *sett_conn;
+	gint64 start_at;
+	gint64 timeout;
+} StartupCompleteData;
 
-		// FIXME: ensure connections from plugins loaded with a lower priority
-		// get rejected when they conflict with connections from a higher
-		// priority plugin.
+static void
+_startup_complete_data_destroy (StartupCompleteData *scd)
+{
+	g_object_unref (scd->sett_conn);
+	g_slice_free (StartupCompleteData, scd);
+}
 
-		for (elt = plugin_connections; elt; elt = g_slist_next (elt))
-			claim_connection (self, elt->data);
+static gboolean
+_startup_complete_check_is_ready (NMPlatform *platform,
+                                  NMSettingsConnection *sett_conn)
+{
+	const NMPlatformLink *plink;
+	const char *ifname;
 
-		g_slist_free (plugin_connections);
+	/* FIXME: instead of just looking for the interface name, it would be better
+	 *        to wait for a device that is compatible with the profile. */
 
-		g_signal_connect (plugin, NM_SETTINGS_PLUGIN_CONNECTION_ADDED,
-		                  G_CALLBACK (plugin_connection_added), self);
-		g_signal_connect (plugin, NM_SETTINGS_PLUGIN_UNMANAGED_SPECS_CHANGED,
-		                  G_CALLBACK (unmanaged_specs_changed), self);
-		g_signal_connect (plugin, NM_SETTINGS_PLUGIN_UNRECOGNIZED_SPECS_CHANGED,
-		                  G_CALLBACK (unrecognized_specs_changed), self);
-	}
+	ifname = nm_connection_get_interface_name (nm_settings_connection_get_connection (sett_conn));
 
-	priv->connections_loaded = TRUE;
-	_notify (self, PROP_CONNECTIONS);
+	if (!ifname)
+		return TRUE;
 
-	unmanaged_specs_changed (NULL, self);
-	unrecognized_specs_changed (NULL, self);
+	plink = nm_platform_link_get_by_ifname (platform, ifname);
+	return plink && plink->initialized;
 }
 
-static void
-impl_settings_list_connections (NMDBusObject *obj,
-                                const NMDBusInterfaceInfoExtended *interface_info,
-                                const NMDBusMethodInfoExtended *method_info,
-                                GDBusConnection *dbus_connection,
-                                const char *sender,
-                                GDBusMethodInvocation *invocation,
-                                GVariant *parameters)
+static gboolean
+_startup_complete_timeout_cb (gpointer user_data)
 {
-	NMSettings *self = NM_SETTINGS (obj);
+	NMSettings *self = user_data;
 	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
-	gs_free const char **strv = NULL;
 
-	strv = nm_dbus_utils_get_paths_for_clist (&priv->connections_lst_head,
-	                                          priv->connections_len,
-	                                          G_STRUCT_OFFSET (NMSettingsConnection, _connections_lst),
-	                                          TRUE);
-	g_dbus_method_invocation_return_value (invocation,
-	                                       g_variant_new ("(^ao)", strv));
-}
-
-NMSettingsConnection *
-nm_settings_get_connection_by_uuid (NMSettings *self, const char *uuid)
-{
-	NMSettingsPrivate *priv;
-	NMSettingsConnection *candidate;
-
-	g_return_val_if_fail (NM_IS_SETTINGS (self), NULL);
-	g_return_val_if_fail (uuid != NULL, NULL);
-
-	priv = NM_SETTINGS_GET_PRIVATE (self);
-
-	c_list_for_each_entry (candidate, &priv->connections_lst_head, _connections_lst) {
-		if (nm_streq (uuid, nm_settings_connection_get_uuid (candidate)))
-			return candidate;
-	}
-
-	return NULL;
+	priv->startup_complete_timeout_id = 0;
+	_startup_complete_check (self, 0);
+	return G_SOURCE_REMOVE;
 }
 
 static void
-impl_settings_get_connection_by_uuid (NMDBusObject *obj,
-                                      const NMDBusInterfaceInfoExtended *interface_info,
-                                      const NMDBusMethodInfoExtended *method_info,
-                                      GDBusConnection *dbus_connection,
-                                      const char *sender,
-                                      GDBusMethodInvocation *invocation,
-                                      GVariant *parameters)
+_startup_complete_platform_change_cb (NMPlatform *platform,
+                                      int obj_type_i,
+                                      int ifindex,
+                                      const NMPlatformLink *link,
+                                      int change_type_i,
+                                      NMSettings *self)
 {
-	NMSettings *self = NM_SETTINGS (obj);
-	NMSettingsConnection *sett_conn;
-	gs_unref_object NMAuthSubject *subject = NULL;
-	GError *error = NULL;
-	const char *uuid;
+	const NMPlatformSignalChangeType change_type = change_type_i;
+	NMSettingsPrivate *priv;
+	const char *ifname;
 
-	g_variant_get (parameters, "(&s)", &uuid);
+	if (change_type == NM_PLATFORM_SIGNAL_REMOVED)
+		return;
 
-	sett_conn = nm_settings_get_connection_by_uuid (self, uuid);
-	if (!sett_conn) {
-		error = g_error_new_literal (NM_SETTINGS_ERROR,
-		                             NM_SETTINGS_ERROR_INVALID_CONNECTION,
-		                             "No connection with the UUID was found.");
-		goto error;
-	}
+	if (!link->initialized)
+		return;
 
-	subject = nm_auth_subject_new_unix_process_from_context (invocation);
-	if (!subject) {
-		error = g_error_new_literal (NM_SETTINGS_ERROR,
-		                             NM_SETTINGS_ERROR_PERMISSION_DENIED,
-		                             "Unable to determine UID of request.");
-		goto error;
-	}
+	priv = NM_SETTINGS_GET_PRIVATE (self);
 
-	if (!nm_auth_is_subject_in_acl_set_error (nm_settings_connection_get_connection (sett_conn),
-	                                          subject,
-	                                          NM_SETTINGS_ERROR,
-	                                          NM_SETTINGS_ERROR_PERMISSION_DENIED,
-	                                          &error))
-		goto error;
+	ifname = nm_connection_get_interface_name (nm_settings_connection_get_connection (priv->startup_complete_blocked_by));
+	if (   ifname
+	    && !nm_streq (ifname, link->name))
+		return;
 
-	g_dbus_method_invocation_return_value (invocation,
-	                                       g_variant_new ("(o)",
-	                                                      nm_dbus_object_get_path (NM_DBUS_OBJECT (sett_conn))));
-	return;
+	nm_assert (priv->startup_complete_timeout_id > 0);
 
-error:
-	g_dbus_method_invocation_take_error (invocation, error);
+	nm_clear_g_source (&priv->startup_complete_timeout_id);
+	priv->startup_complete_timeout_id = g_idle_add (_startup_complete_timeout_cb, self);
 }
 
 static void
-_clear_connections_cached_list (NMSettingsPrivate *priv)
+_startup_complete_check (NMSettings *self,
+                         gint64 now_us)
 {
-	if (!priv->connections_cached_list)
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	gint64 next_expiry;
+	StartupCompleteData *scd;
+	NMSettingsConnection *next_sett_conn = NULL;
+	GHashTableIter iter;
+
+	if (!priv->started) {
+		/* before we are started, we don't setup the timers... */
 		return;
+	}
 
-	nm_assert (priv->connections_len == NM_PTRARRAY_LEN (priv->connections_cached_list));
+	if (!priv->startup_complete_idx)
+		goto ready;
 
-#if NM_MORE_ASSERTS
-	/* set the pointer to a bogus value. This makes it more apparent
-	 * if somebody has a reference to the cached list and still uses
-	 * it. That is a bug, this code just tries to make it blow up
-	 * more eagerly. */
-	memset (priv->connections_cached_list,
-	        0xdeaddead,
-	        sizeof (NMSettingsConnection *) * (priv->connections_len + 1));
-#endif
+	if (!now_us)
+		now_us = nm_utils_get_monotonic_timestamp_us ();
 
-	nm_clear_g_free (&priv->connections_cached_list);
-}
+	next_expiry = 0;
 
-/**
- * nm_settings_get_connections:
- * @self: the #NMSettings
- * @out_len: (out) (allow-none): returns the number of returned
- *   connections.
- *
- * Returns: (transfer none): a list of NMSettingsConnections. The list is
- * unsorted and NULL terminated. The result is never %NULL, in case of no
- * connections, it returns an empty list.
- * The returned list is cached internally, only valid until the next
- * NMSettings operation.
- */
-NMSettingsConnection *const*
-nm_settings_get_connections (NMSettings *self, guint *out_len)
-{
-	NMSettingsPrivate *priv;
-	NMSettingsConnection **v;
-	NMSettingsConnection *con;
-	guint i;
+	g_hash_table_iter_init (&iter, priv->startup_complete_idx);
+	while (g_hash_table_iter_next (&iter, (gpointer *) &scd, NULL)) {
+		gint64 expiry;
 
-	g_return_val_if_fail (NM_IS_SETTINGS (self), NULL);
+		if (scd->start_at == 0) {
+			/* once ready, the decision is remembered and there is nothing
+			 * left to check. */
+			continue;
+		}
 
-	priv = NM_SETTINGS_GET_PRIVATE (self);
+		expiry = scd->start_at + scd->timeout;
+		if (expiry <= now_us) {
+			scd->start_at = 0;
+			continue;
+		}
 
-	nm_assert (priv->connections_len == c_list_length (&priv->connections_lst_head));
+		if (_startup_complete_check_is_ready (priv->platform, scd->sett_conn)) {
+			scd->start_at = 0;
+			continue;
+		}
 
-	if (G_UNLIKELY (!priv->connections_cached_list)) {
-		v = g_new (NMSettingsConnection *, priv->connections_len + 1);
+		next_expiry = expiry;
+		next_sett_conn = scd->sett_conn;
+		/* we found one timeout for which to wait. that's good enough. */
+		break;
+	}
 
-		i = 0;
-		c_list_for_each_entry (con, &priv->connections_lst_head, _connections_lst) {
-			nm_assert (i < priv->connections_len);
-			v[i++] = con;
+	nm_clear_g_source (&priv->startup_complete_timeout_id);
+	nm_g_object_ref_set (&priv->startup_complete_blocked_by, next_sett_conn);
+	if (next_expiry > 0) {
+		nm_assert (priv->startup_complete_blocked_by);
+		if (priv->startup_complete_platform_change_id == 0) {
+			priv->startup_complete_platform_change_id = g_signal_connect (priv->platform,
+			                                                              NM_PLATFORM_SIGNAL_LINK_CHANGED,
+			                                                              G_CALLBACK (_startup_complete_platform_change_cb),
+			                                                              self);
 		}
-		nm_assert (i == priv->connections_len);
-		v[i] = NULL;
-
-		priv->connections_cached_list = v;
+		priv->startup_complete_timeout_id = g_timeout_add (NM_MIN (3600u*1000u, (next_expiry - now_us) / 1000u),
+		                                                   _startup_complete_timeout_cb,
+		                                                   self);
+		_LOGT ("startup-complete: wait for device \"%s\" due to connection %s (%s)",
+		       nm_connection_get_interface_name (nm_settings_connection_get_connection (priv->startup_complete_blocked_by)),
+		       nm_settings_connection_get_uuid (priv->startup_complete_blocked_by),
+		       nm_settings_connection_get_id (priv->startup_complete_blocked_by));
+		return;
 	}
 
-	NM_SET_OUT (out_len, priv->connections_len);
-	return priv->connections_cached_list;
+	nm_clear_pointer (&priv->startup_complete_idx, g_hash_table_destroy);
+	nm_clear_g_signal_handler (priv->platform, &priv->startup_complete_platform_change_id);
+
+ready:
+	_LOGT ("startup-complete: ready, no profiles to wait for");
+	nm_assert (priv->started);
+	nm_assert (!priv->startup_complete_blocked_by);
+	nm_assert (!priv->startup_complete_idx);
+	nm_assert (priv->startup_complete_timeout_id == 0);
+	nm_assert (priv->startup_complete_platform_change_id == 0);
+	_notify (self, PROP_STARTUP_COMPLETE);
 }
 
-/**
- * nm_settings_get_connections_clone:
- * @self: the #NMSetting
- * @out_len: (allow-none): optional output argument
- * @func: caller-supplied function for filtering connections
- * @func_data: caller-supplied data passed to @func
- * @sort_compare_func: (allow-none): optional function pointer for
- *   sorting the returned list.
- * @sort_data: user data for @sort_compare_func.
- *
- * Returns: (transfer container) (element-type NMSettingsConnection):
- *   an NULL terminated array of #NMSettingsConnection objects that were
- *   filtered by @func (or all connections if no filter was specified).
- *   The order is arbitrary.
- *   Caller is responsible for freeing the returned array with free(),
- *   the contained values do not need to be unrefed.
- */
-NMSettingsConnection **
-nm_settings_get_connections_clone (NMSettings *self,
-                                   guint *out_len,
-                                   NMSettingsConnectionFilterFunc func,
-                                   gpointer func_data,
-                                   GCompareDataFunc sort_compare_func,
-                                   gpointer sort_data)
+static void
+_startup_complete_notify_connection (NMSettings *self,
+                                     NMSettingsConnection *sett_conn,
+                                     gboolean forget)
 {
-	NMSettingsConnection *const*list_cached;
-	NMSettingsConnection **list;
-	guint len, i, j;
-
-	g_return_val_if_fail (NM_IS_SETTINGS (self), NULL);
-
-	list_cached = nm_settings_get_connections (self, &len);
-
-#if NM_MORE_ASSERTS
-	nm_assert (list_cached);
-	for (i = 0; i < len; i++)
-		nm_assert (NM_IS_SETTINGS_CONNECTION (list_cached[i]));
-	nm_assert (!list_cached[i]);
-#endif
-
-	list = g_new (NMSettingsConnection *, ((gsize) len + 1));
-	if (func) {
-		for (i = 0, j = 0; i < len; i++) {
-			if (func (self, list_cached[i], func_data))
-				list[j++] = list_cached[i];
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	gint64 timeout;
+	gint64 now_us = 0;
+
+	nm_assert (   !priv->started
+	           || priv->startup_complete_idx);
+
+	timeout = 0;
+	if (!forget) {
+		NMSettingConnection *s_con;
+		gint32 v;
+
+		s_con = nm_connection_get_setting_connection (nm_settings_connection_get_connection (sett_conn));
+		v = nm_setting_connection_get_wait_device_timeout (s_con);
+		if (v > 0) {
+			nm_assert (nm_setting_connection_get_interface_name (s_con));
+			timeout = ((gint64) v) * 1000;
 		}
-		list[j] = NULL;
-		len = j;
-	} else
-		memcpy (list, list_cached, sizeof (list[0]) * ((gsize) len + 1));
-
-	if (   len > 1
-	    && sort_compare_func) {
-		g_qsort_with_data (list, len, sizeof (NMSettingsConnection *),
-		                   sort_compare_func, sort_data);
 	}
-	NM_SET_OUT (out_len, len);
-	return list;
-}
-
-NMSettingsConnection *
-nm_settings_get_connection_by_path (NMSettings *self, const char *path)
-{
-	NMSettingsPrivate *priv;
-	NMSettingsConnection *connection;
-
-	g_return_val_if_fail (NM_IS_SETTINGS (self), NULL);
-	g_return_val_if_fail (path, NULL);
-
-	priv = NM_SETTINGS_GET_PRIVATE (self);
 
-	connection = nm_dbus_manager_lookup_object (nm_dbus_object_get_manager (NM_DBUS_OBJECT (self)),
-	                                            path);
-	if (   !connection
-	    || !NM_IS_SETTINGS_CONNECTION (connection))
-		return NULL;
+	if (timeout == 0) {
+		if (   !priv->startup_complete_idx
+		    || !g_hash_table_remove (priv->startup_complete_idx, &sett_conn))
+			return;
+	} else {
+		StartupCompleteData *scd;
+
+		if (!priv->startup_complete_idx) {
+			nm_assert (!priv->started);
+			priv->startup_complete_idx = g_hash_table_new_full (nm_pdirect_hash,
+			                                                    nm_pdirect_equal,
+			                                                    NULL,
+			                                                    (GDestroyNotify) _startup_complete_data_destroy);
+			scd = NULL;
+		} else
+			scd = g_hash_table_lookup (priv->startup_complete_idx, &sett_conn);
+		if (!scd) {
+			now_us = nm_utils_get_monotonic_timestamp_us ();
+			scd = g_slice_new (StartupCompleteData);
+			*scd = (StartupCompleteData) {
+				.sett_conn = g_object_ref (sett_conn),
+				.start_at  = now_us,
+				.timeout   = timeout,
+			};
+			g_hash_table_add (priv->startup_complete_idx, scd);
+		} else {
+			if (scd->start_at == 0) {
+				/* the entry already is ready and no longer relevant. Ignore it. */
+				return;
+			}
+			scd->timeout = timeout;
+		}
+	}
 
-	nm_assert (c_list_contains (&priv->connections_lst_head, &connection->_connections_lst));
-	return connection;
+	_startup_complete_check (self, now_us);
 }
 
-gboolean
-nm_settings_has_connection (NMSettings *self, NMSettingsConnection *connection)
+const char *
+nm_settings_get_startup_complete_blocked_reason (NMSettings *self)
 {
-	gboolean has;
-
-	g_return_val_if_fail (NM_IS_SETTINGS (self), FALSE);
-	g_return_val_if_fail (NM_IS_SETTINGS_CONNECTION (connection), FALSE);
-
-	has = !c_list_is_empty (&connection->_connections_lst);
-
-	nm_assert (has == nm_c_list_contains_entry (&NM_SETTINGS_GET_PRIVATE (self)->connections_lst_head,
-                                                connection,
-                                                _connections_lst));
-	nm_assert (({
-		NMSettingsConnection *candidate = NULL;
-		const char *path;
-
-		path = nm_dbus_object_get_path (NM_DBUS_OBJECT (connection));
-		if (path)
-			candidate = nm_settings_get_connection_by_path (self, path);
-
-		(has == (connection == candidate));
-	}));
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	const char *uuid = NULL;
 
-	return has;
+	if (priv->started) {
+		if (!priv->startup_complete_idx)
+			return NULL;
+		if (priv->startup_complete_blocked_by)
+			uuid = nm_settings_connection_get_uuid (priv->startup_complete_blocked_by);
+	}
+	return uuid ?: "unknown";
 }
 
+/*****************************************************************************/
+
 const GSList *
 nm_settings_get_unmanaged_specs (NMSettings *self)
 {
@@ -531,57 +715,57 @@ nm_settings_get_unmanaged_specs (NMSettings *self)
 }
 
 static gboolean
-find_spec (GSList *spec_list, const char *spec)
-{
-	GSList *iter;
-
-	for (iter = spec_list; iter; iter = g_slist_next (iter)) {
-		if (!strcmp ((const char *) iter->data, spec))
-			return TRUE;
-	}
-	return FALSE;
-}
-
-static void
 update_specs (NMSettings *self, GSList **specs_ptr,
               GSList * (*get_specs_func) (NMSettingsPlugin *))
 {
 	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	GSList *new = NULL;
 	GSList *iter;
 
-	g_slist_free_full (*specs_ptr, g_free);
-	*specs_ptr = NULL;
-
 	for (iter = priv->plugins; iter; iter = g_slist_next (iter)) {
-		GSList *specs, *specs_iter;
+		GSList *specs;
 
-		specs = get_specs_func (NM_SETTINGS_PLUGIN (iter->data));
-		for (specs_iter = specs; specs_iter; specs_iter = specs_iter->next) {
-			if (!find_spec (*specs_ptr, (const char *) specs_iter->data)) {
-				*specs_ptr = g_slist_prepend (*specs_ptr, specs_iter->data);
-			} else
-				g_free (specs_iter->data);
+		specs = get_specs_func (iter->data);
+		while (specs) {
+			GSList *s = specs;
+
+			specs = g_slist_remove_link (specs, s);
+			if (nm_utils_g_slist_find_str (new, s->data)) {
+				g_free (s->data);
+				g_slist_free_1 (s);
+				continue;
+			}
+			s->next = new;
+			new = s;
 		}
+	}
 
-		g_slist_free (specs);
+	if (nm_utils_g_slist_strlist_cmp (new, *specs_ptr) == 0) {
+		g_slist_free_full (new, g_free);
+		return FALSE;
 	}
+
+	g_slist_free_full (*specs_ptr, g_free);
+	*specs_ptr = new;
+	return TRUE;
+
 }
 
 static void
-unmanaged_specs_changed (NMSettingsPlugin *config,
-                         gpointer user_data)
+_plugin_unmanaged_specs_changed (NMSettingsPlugin *config,
+                                 gpointer user_data)
 {
 	NMSettings *self = NM_SETTINGS (user_data);
 	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
 
-	update_specs (self, &priv->unmanaged_specs,
-	              nm_settings_plugin_get_unmanaged_specs);
-	_notify (self, PROP_UNMANAGED_SPECS);
+	if (update_specs (self, &priv->unmanaged_specs,
+	                  nm_settings_plugin_get_unmanaged_specs))
+		_notify (self, PROP_UNMANAGED_SPECS);
 }
 
 static void
-unrecognized_specs_changed (NMSettingsPlugin *config,
-                               gpointer user_data)
+_plugin_unrecognized_specs_changed (NMSettingsPlugin *config,
+                                    gpointer user_data)
 {
 	NMSettings *self = NM_SETTINGS (user_data);
 	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
@@ -590,521 +774,1605 @@ unrecognized_specs_changed (NMSettingsPlugin *config,
 	              nm_settings_plugin_get_unrecognized_specs);
 }
 
+/*****************************************************************************/
+
 static void
-add_plugin (NMSettings *self, NMSettingsPlugin *plugin, const char *path)
+connection_flags_changed (NMSettingsConnection *sett_conn,
+                          gpointer user_data)
 {
-	NMSettingsPrivate *priv;
+	_emit_connection_flags_changed (NM_SETTINGS (user_data), sett_conn);
+}
 
-	nm_assert (NM_IS_SETTINGS (self));
-	nm_assert (NM_IS_SETTINGS_PLUGIN (plugin));
+/*****************************************************************************/
 
-	priv = NM_SETTINGS_GET_PRIVATE (self);
+static SettConnEntry *
+_sett_conn_entries_get (NMSettings *self,
+                        const char *uuid)
+{
+	nm_assert (uuid);
+	return g_hash_table_lookup (NM_SETTINGS_GET_PRIVATE (self)->sce_idx, &uuid);
+}
 
-	nm_assert (!g_slist_find (priv->plugins, plugin));
+static SettConnEntry *
+_sett_conn_entries_create_and_add (NMSettings *self,
+                                   const char *uuid)
+{
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	SettConnEntry *sett_conn_entry;
 
-	priv->plugins = g_slist_append (priv->plugins, g_object_ref (plugin));
+	sett_conn_entry = _sett_conn_entry_new (uuid);
 
-	nm_settings_plugin_initialize (plugin);
+	if (!g_hash_table_add (priv->sce_idx, sett_conn_entry))
+		nm_assert_not_reached ();
+	else if (g_hash_table_size (priv->sce_idx) == 1)
+		g_object_ref (self);
 
-	_LOGI ("Loaded settings plugin: %s (%s%s%s)",
-	       G_OBJECT_TYPE_NAME (plugin),
-	       NM_PRINT_FMT_QUOTED (path, "\"", path, "\"", "internal"));
+	return sett_conn_entry;
 }
 
-static gboolean
-add_plugin_load_file (NMSettings *self, const char *pname, GError **error)
+static void
+_sett_conn_entries_remove_and_destroy (NMSettings *self,
+                                       SettConnEntry *sett_conn_entry)
 {
 	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
-	gs_free char *full_name = NULL;
-	gs_free char *path = NULL;
-	gs_unref_object NMSettingsPlugin *plugin = NULL;
-	GModule *module;
-	NMSettingsPluginFactoryFunc factory_func;
-	GSList *iter;
-	struct stat st;
-	int errsv;
-
-	full_name = g_strdup_printf ("nm-settings-plugin-%s", pname);
-	path = g_module_build_path (NMPLUGINDIR, full_name);
 
-	for (iter = priv->plugins; iter; iter = iter->next) {
-		if (nm_streq0 (path,
-		               g_object_get_qdata (iter->data,
-		                                   plugin_module_path_quark ())))
-			return TRUE;
-	}
-
-	if (stat (path, &st) != 0) {
-		errsv = errno;
-		_LOGW ("could not load plugin '%s' from file '%s': %s", pname, path, nm_strerror_native (errsv));
-		return TRUE;
-	}
-	if (!S_ISREG (st.st_mode)) {
-		_LOGW ("could not load plugin '%s' from file '%s': not a file", pname, path);
-		return TRUE;
-	}
-	if (st.st_uid != 0) {
-		_LOGW ("could not load plugin '%s' from file '%s': file must be owned by root", pname, path);
-		return TRUE;
-	}
-	if (st.st_mode & (S_IWGRP | S_IWOTH | S_ISUID)) {
-		_LOGW ("could not load plugin '%s' from file '%s': invalid file permissions", pname, path);
-		return TRUE;
-	}
+	if (!g_hash_table_remove (priv->sce_idx, sett_conn_entry))
+		nm_assert_not_reached ();
+	else if (g_hash_table_size (priv->sce_idx) == 0)
+		g_object_unref (self);
+}
 
-	module = g_module_open (path, G_MODULE_BIND_LOCAL);
-	if (!module) {
-		_LOGW ("could not load plugin '%s' from file '%s': %s",
-		     pname, path, g_module_error ());
-		return TRUE;
-	}
+/*****************************************************************************/
 
-	/* errors after this point are fatal, because we loaded the shared library already. */
+static int
+_sett_conn_entry_sds_update_cmp_ascending (const StorageData *sd_a,
+                                           const StorageData *sd_b,
+                                           const GSList *plugins)
+{
+	const NMSettingsMetaData *meta_data_a;
+	const NMSettingsMetaData *meta_data_b;
+	bool is_keyfile_run_a;
+	bool is_keyfile_run_b;
+
+	/* Sort storages by priority. More important storages are sorted
+	 * higher (ascending sort). For example, if "sd_a" is more important than
+	 * "sd_b" (sd_a>sd_b), a positive integer is returned. */
+
+	meta_data_a = nm_settings_storage_is_meta_data (sd_a->storage);
+	meta_data_b = nm_settings_storage_is_meta_data (sd_b->storage);
+
+	/* runtime storages (both connections and meta-data) are always more
+	 * important. */
+	is_keyfile_run_a = nm_settings_storage_is_keyfile_run (sd_a->storage);
+	is_keyfile_run_b = nm_settings_storage_is_keyfile_run (sd_b->storage);
+	if (is_keyfile_run_a != is_keyfile_run_b) {
+
+		if (   !meta_data_a
+		    && !meta_data_b) {
+			/* Ok, both are non-meta-data providing actual profiles. But one is in /run and one is in
+			 * another storage. In this case we first honor whether one of the storages is explicitly
+			 * prioritized. The prioritize flag is an in-memory hack to overwrite relative priorities
+			 * contrary to what exists on-disk.
+			 *
+			 * This is done because when we use explicit D-Bus API (like update-connection)
+			 * to update a profile, then we really want to prioritize the candidate
+			 * despite having multiple other profiles.
+			 *
+			 * The example is if you have the same UUID twice in /run (one of them shadowed).
+			 * If you move it to disk, then one of the profiles gets deleted and re-created
+			 * on disk, but that on-disk profile must win against the remainging profile in
+			 * /run. At least until the next reload/restart. */
+			NM_CMP_FIELD_UNSAFE (sd_a, sd_b, prioritize);
+		}
 
-	if (!g_module_symbol (module, "nm_settings_plugin_factory", (gpointer) (&factory_func))) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
-		             "Could not find plugin '%s' factory function.",
-		             pname);
-		g_module_close (module);
-		return FALSE;
+		/* in-memory has higher priority. That is regardless of whether any of
+		 * them is meta-data/tombstone or a profile.
+		 *
+		 * That works, because if any of them are tombstones/metadata, then we are in full
+		 * control. There can by only one meta-data file, which is fully owned (and accordingly
+		 * created/deleted) by NetworkManager.
+		 *
+		 * The only case where this might not be right is if we have profiles
+		 * in /run that are shadowed. When we move such a profile to disk, then
+		 * a conflict might arise. That is handled by "prioritize" above! */
+		NM_CMP_DIRECT (is_keyfile_run_a, is_keyfile_run_b);
 	}
 
-	/* after accessing the plugin we cannot unload it anymore, because the glib
-	 * types cannot be properly unregistered. */
-	g_module_make_resident (module);
+	/* After we determined that both profiles are either in /run or not,
+	 * tombstones are always more important than non-tombstones. */
+	NM_CMP_DIRECT (meta_data_a && meta_data_a->is_tombstone,
+	               meta_data_b && meta_data_b->is_tombstone);
 
-	plugin = (*factory_func) ();
-	if (!NM_IS_SETTINGS_PLUGIN (plugin)) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
-		             "plugin '%s' returned invalid settings plugin",
-		             pname);
-		return FALSE;
-	}
+	/* Again, prioritized entries are sorted first (higher priority). */
+	NM_CMP_FIELD_UNSAFE (sd_a, sd_b, prioritize);
 
-	add_plugin (self, NM_SETTINGS_PLUGIN (plugin), path);
-	g_object_set_qdata_full (G_OBJECT (plugin),
-	                         plugin_module_path_quark (),
-	                         g_steal_pointer (&path),
-	                         g_free);
-	return TRUE;
+	/* finally, compare the storages. This basically honors the timestamp
+	 * of the profile and the relative order of the source plugin (via the
+	 * @plugins list). */
+	return nm_settings_storage_cmp (sd_a->storage, sd_b->storage, plugins);
 }
 
-static void
-add_plugin_keyfile (NMSettings *self)
+static int
+_sett_conn_entry_sds_update_cmp (const CList *ls_a,
+                                 const CList *ls_b,
+                                 gconstpointer user_data)
 {
-	gs_unref_object NMSKeyfilePlugin *keyfile_plugin = NULL;
-
-	keyfile_plugin = nms_keyfile_plugin_new ();
-	add_plugin (self, NM_SETTINGS_PLUGIN (keyfile_plugin), NULL);
+	/* we sort highest priority storages first (descending). Hence, the order is swapped. */
+	return _sett_conn_entry_sds_update_cmp_ascending (c_list_entry (ls_b, StorageData, sd_lst),
+	                                                  c_list_entry (ls_a, StorageData, sd_lst),
+	                                                  user_data);
 }
 
-static gboolean
-load_plugins (NMSettings *self, const char **plugins, GError **error)
+static void
+_sett_conn_entry_sds_update (NMSettings *self,
+                             SettConnEntry *sett_conn_entry)
 {
-	const char **iter;
-	gboolean keyfile_added = FALSE;
-	gboolean success = TRUE;
-	gboolean add_ibft = FALSE;
-	gboolean has_no_ibft;
-	gssize idx_no_ibft, idx_ibft;
-
-	idx_ibft    = nm_utils_strv_find_first ((char **) plugins, -1, "ibft");
-	idx_no_ibft = nm_utils_strv_find_first ((char **) plugins, -1, "no-ibft");
-	has_no_ibft = idx_no_ibft >= 0 && idx_no_ibft > idx_ibft;
-#if WITH_SETTINGS_PLUGIN_IBFT
-	add_ibft = idx_no_ibft < 0 && idx_ibft < 0;
-#endif
+	StorageData *sd;
+	StorageData *sd_safe;
+	StorageData *sd_dirty;
+	gboolean reprioritize;
+
+	nm_assert_storage_data_lst (&sett_conn_entry->sd_lst_head);
+	nm_assert_storage_data_lst (&sett_conn_entry->dirty_sd_lst_head);
+
+	/* we merge the dirty list with the previous list.
+	 *
+	 * The idea is:
+	 *
+	 *  - _connection_changed_track() appends events for the same UUID. Meaning:
+	 *    if the storage is new, it get appended (having lower priority).
+	 *    If it already exist and is an update for an event that we already
+	 *    track it, it keeps the list position in @dirty_sd_lst_head unchanged.
+	 *
+	 *  - during merge, we want to preserve the previous order (with higher
+	 *    priority first in the list).
+	 */
 
-	for (iter = plugins; iter && *iter; iter++) {
-		const char *pname = *iter;
+	/* first go through all storages that we track and check whether they
+	 * got an update...*/
 
-		if (!*pname || strchr (pname, '/')) {
-			_LOGW ("ignore invalid plugin \"%s\"", pname);
-			continue;
+	reprioritize = FALSE;
+	c_list_for_each_entry (sd, &sett_conn_entry->dirty_sd_lst_head, sd_lst) {
+		if (sd->prioritize) {
+			reprioritize = TRUE;
+			break;
 		}
+	}
 
-		if (NM_IN_STRSET (pname, "ifcfg-suse", "ifnet")) {
-			_LOGW ("skipping deprecated plugin %s", pname);
-			continue;
-		}
+	nm_assert_storage_data_lst (&sett_conn_entry->sd_lst_head);
 
-		if (nm_streq (pname, "no-ibft"))
-			continue;
-		if (has_no_ibft && nm_streq (pname, "ibft"))
-			continue;
+	c_list_for_each_entry_safe (sd, sd_safe, &sett_conn_entry->sd_lst_head, sd_lst) {
 
-		/* keyfile plugin is built-in now */
-		if (nm_streq (pname, "keyfile")) {
-			if (!keyfile_added) {
-				add_plugin_keyfile (self);
-				keyfile_added = TRUE;
-			}
+		sd_dirty = _storage_data_find_in_lst (&sett_conn_entry->dirty_sd_lst_head, sd->storage);
+		if (!sd_dirty) {
+			/* there is no update for this storage (except maybe reprioritize). */
+			if (reprioritize)
+				sd->prioritize = FALSE;
 			continue;
 		}
 
-		if (nm_utils_strv_find_first ((char **) plugins,
-		                              iter - plugins,
-		                              pname) >= 0) {
-			/* the plugin is already mentioned in the list previously.
-			 * Don't load a duplicate. */
-			continue;
-		}
+		nm_g_object_ref_set (&sd->connection, sd_dirty->connection);
+		sd->prioritize = sd_dirty->prioritize;
 
-		success = add_plugin_load_file (self, pname, error);
-		if (!success)
-			break;
+		_storage_data_destroy (sd_dirty);
+	}
 
-		if (add_ibft && nm_streq (pname, "ifcfg-rh")) {
-			/* The plugin ibft is not explicitly mentioned but we just enabled "ifcfg-rh".
-			 * Enable "ibft" by default after "ifcfg-rh". */
-			pname = "ibft";
-			add_ibft = FALSE;
+	nm_assert_storage_data_lst (&sett_conn_entry->sd_lst_head);
 
-			success = add_plugin_load_file (self, "ibft", error);
-			if (!success)
-				break;
-		}
+	/* all remaining (so far unseen) dirty entries are appended to the merged list.
+	 * (append means lower priority). */
+
+	c_list_splice (&sett_conn_entry->sd_lst_head, &sett_conn_entry->dirty_sd_lst_head);
+
+	nm_assert_storage_data_lst (&sett_conn_entry->sd_lst_head);
+
+	/* we drop the entries that are no longer "alive" (meaning, they no longer
+	 * indicate a connection and are not a tombstone). */
+	c_list_for_each_entry_safe (sd, sd_safe, &sett_conn_entry->sd_lst_head, sd_lst) {
+		if (!_storage_data_is_alive (sd))
+			_storage_data_destroy (sd);
 	}
 
-	/* If keyfile plugin was not among configured plugins, add it as the last one */
-	if (!keyfile_added && success)
-		add_plugin_keyfile (self);
+	nm_assert_storage_data_lst (&sett_conn_entry->sd_lst_head);
+	nm_assert (c_list_is_empty (&sett_conn_entry->dirty_sd_lst_head));
 
-	return success;
+	/* as last, we sort the entries. Note that this is a stable-sort... */
+	c_list_sort (&sett_conn_entry->sd_lst_head,
+	             _sett_conn_entry_sds_update_cmp,
+	             NM_SETTINGS_GET_PRIVATE (self)->plugins);
+
+	nm_assert_storage_data_lst (&sett_conn_entry->sd_lst_head);
+	nm_assert (c_list_is_empty (&sett_conn_entry->dirty_sd_lst_head));
 }
 
-static void
-connection_updated (NMSettingsConnection *connection, gboolean by_user, gpointer user_data)
+/*****************************************************************************/
+
+static NMConnection *
+_connection_changed_normalize_connection (NMSettingsStorage *storage,
+                                          NMConnection *connection,
+                                          GVariant *secrets_to_merge,
+                                          NMConnection **out_connection_cloned)
 {
-	g_signal_emit (NM_SETTINGS (user_data),
-	               signals[CONNECTION_UPDATED],
-	               0,
-	               connection,
-	               by_user);
+	gs_unref_object NMConnection *connection_cloned = NULL;
+	gs_free_error GError *error = NULL;
+	const char *uuid;
+
+	nm_assert (NM_IS_SETTINGS_STORAGE (storage));
+	nm_assert (out_connection_cloned && !*out_connection_cloned);
+
+	if (!connection)
+		return NULL;
+
+	nm_assert (NM_IS_CONNECTION (connection));
+
+	uuid = nm_settings_storage_get_uuid (storage);
+
+	if (secrets_to_merge) {
+		connection_cloned = nm_simple_connection_new_clone (connection);
+		connection = connection_cloned;
+		nm_connection_update_secrets (connection,
+		                              NULL,
+		                              secrets_to_merge,
+		                              NULL);
+	}
+
+	if (!_nm_connection_ensure_normalized (connection,
+	                                       !!connection_cloned,
+	                                       uuid,
+	                                       FALSE,
+	                                       connection_cloned ? NULL : &connection_cloned,
+	                                       &error)) {
+		/* this is most likely a bug in the plugin. It provided a connection that no longer verifies.
+		 * Well, I guess it could also happen when we merge @secrets_to_merge above. In any case
+		 * somewhere is a bug. */
+		_LOGT ("storage[%s,"NM_SETTINGS_STORAGE_PRINT_FMT"]: plugin provided an invalid connection: %s",
+		       uuid,
+		       NM_SETTINGS_STORAGE_PRINT_ARG (storage),
+		       error->message);
+		return NULL;
+	}
+	if (connection_cloned)
+		connection = connection_cloned;
+
+	*out_connection_cloned = g_steal_pointer (&connection_cloned);
+	return connection;
 }
 
+/*****************************************************************************/
+
 static void
-connection_flags_changed (NMSettingsConnection *connection,
-                          gpointer user_data)
+_connection_changed_update (NMSettings *self,
+                            SettConnEntry *sett_conn_entry,
+                            NMConnection *connection,
+                            NMSettingsConnectionIntFlags sett_flags,
+                            NMSettingsConnectionIntFlags sett_mask,
+                            NMSettingsConnectionUpdateReason update_reason)
 {
-	g_signal_emit (NM_SETTINGS (user_data),
-	               signals[CONNECTION_FLAGS_CHANGED],
-	               0,
-	               connection);
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	gs_unref_object NMConnection *connection_old = NULL;
+	NMSettingsStorage *storage = sett_conn_entry->storage;
+	gs_unref_object NMSettingsConnection *sett_conn = g_object_ref (sett_conn_entry->sett_conn);
+	const char *path;
+	gboolean is_new;
+
+	nm_assert (!NM_FLAGS_ANY (sett_mask, ~_NM_SETTINGS_CONNECTION_INT_FLAGS_PERSISTENT_MASK));
+	nm_assert (!NM_FLAGS_ANY (sett_flags, ~sett_mask));
+
+	is_new = c_list_is_empty (&sett_conn->_connections_lst);
+
+	_LOGT ("update[%s]: %s connection \"%s\" ("NM_SETTINGS_STORAGE_PRINT_FMT")",
+	       nm_settings_storage_get_uuid (storage),
+	       is_new ? "adding" : "updating",
+	       nm_connection_get_id (connection),
+	       NM_SETTINGS_STORAGE_PRINT_ARG (storage));
+
+	_nm_settings_connection_set_storage (sett_conn, storage);
+
+	_nm_settings_connection_set_connection (sett_conn, connection, &connection_old, update_reason);
+
+
+	if (is_new) {
+		_nm_settings_connection_register_kf_dbs (sett_conn,
+		                                         priv->kf_db_timestamps,
+		                                         priv->kf_db_seen_bssids);
+
+		_clear_connections_cached_list (priv);
+		c_list_link_tail (&priv->connections_lst_head, &sett_conn->_connections_lst);
+		priv->connections_len++;
+		priv->connections_generation++;
+
+		g_signal_connect (sett_conn, NM_SETTINGS_CONNECTION_FLAGS_CHANGED, G_CALLBACK (connection_flags_changed), self);
+	}
+
+	if (NM_FLAGS_HAS (update_reason, NM_SETTINGS_CONNECTION_UPDATE_REASON_BLOCK_AUTOCONNECT)) {
+		nm_settings_connection_autoconnect_blocked_reason_set (sett_conn,
+		                                                       NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST,
+		                                                       TRUE);
+	}
+
+	sett_mask |= NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE;
+	if (nm_settings_connection_check_visibility (sett_conn, priv->session_monitor))
+		sett_flags |= NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE;
+	else
+		nm_assert (!NM_FLAGS_HAS (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE));
+
+	sett_mask |= NM_SETTINGS_CONNECTION_INT_FLAGS_UNSAVED;
+	if (nm_settings_storage_is_keyfile_run (storage))
+		sett_flags |= NM_SETTINGS_CONNECTION_INT_FLAGS_UNSAVED;
+	else {
+		nm_assert (!NM_FLAGS_HAS (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_UNSAVED));
+
+		/* Profiles that don't reside in /run, are never nm-generated
+		 * and never volatile. */
+		sett_mask |= (  NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
+		              | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE);
+		sett_flags &= ~(  NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
+		                | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE);
+	}
+
+	nm_settings_connection_set_flags_full (sett_conn,
+	                                       sett_mask,
+	                                       sett_flags);
+
+	if (is_new) {
+		/* FIXME(shutdown): The NMSettings instance can't be disposed
+		 * while there is any exported connection. Ideally we should
+		 * unexport all connections on NMSettings' disposal, but for now
+		 * leak @self on termination when there are connections alive. */
+		path = nm_dbus_object_export (NM_DBUS_OBJECT (sett_conn));
+	} else
+		path = nm_dbus_object_get_path (NM_DBUS_OBJECT (sett_conn));
+
+	if (   is_new
+	    || connection_old) {
+		nm_utils_log_connection_diff (nm_settings_connection_get_connection (sett_conn),
+		                              connection_old,
+		                              LOGL_DEBUG,
+		                              LOGD_CORE,
+		                              is_new ? "new connection" : "update connection",
+		                              "++ ",
+		                              path);
+	}
+
+	if (is_new) {
+		nm_dbus_object_emit_signal (NM_DBUS_OBJECT (self),
+		                            &interface_info_settings,
+		                            &signal_info_new_connection,
+		                            "(o)",
+		                            path);
+		_notify (self, PROP_CONNECTIONS);
+		_emit_connection_added (self, sett_conn);
+	} else {
+		_nm_settings_connection_emit_dbus_signal_updated (sett_conn);
+		_emit_connection_updated (self, sett_conn, update_reason);
+	}
+
+	if (   !priv->started
+	    || priv->startup_complete_idx) {
+		if (nm_settings_has_connection (self, sett_conn))
+			_startup_complete_notify_connection (self, sett_conn, FALSE);
+	}
 }
 
 static void
-connection_removed (NMSettingsConnection *connection, gpointer user_data)
+_connection_changed_delete (NMSettings *self,
+                            NMSettingsStorage *storage,
+                            NMSettingsConnection *sett_conn,
+                            gboolean allow_add_to_no_auto_default)
 {
-	NMSettings *self = NM_SETTINGS (user_data);
 	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	gs_unref_object NMConnection *connection_for_agents = NULL;
 	NMDevice *device;
+	const char *uuid;
 
-	g_return_if_fail (NM_IS_SETTINGS_CONNECTION (connection));
-	g_return_if_fail (!c_list_is_empty (&connection->_connections_lst));
-	nm_assert (c_list_contains (&priv->connections_lst_head, &connection->_connections_lst));
+	nm_assert (NM_IS_SETTINGS_CONNECTION (sett_conn));
+	nm_assert (c_list_contains (&priv->connections_lst_head, &sett_conn->_connections_lst));
+	nm_assert (nm_dbus_object_is_exported (NM_DBUS_OBJECT (sett_conn)));
+
+	uuid = nm_settings_storage_get_uuid (storage);
+
+	_LOGT ("update[%s]: delete connection \"%s\" ("NM_SETTINGS_STORAGE_PRINT_FMT")",
+	       uuid,
+	       nm_settings_connection_get_id (sett_conn),
+	       NM_SETTINGS_STORAGE_PRINT_ARG (storage));
 
-	/* When the default wired connection is removed (either deleted or saved to
-	 * a new persistent connection by a plugin), write the MAC address of the
+	/* When the default wired sett_conn is removed (either deleted or saved to
+	 * a new persistent sett_conn by a plugin), write the MAC address of the
 	 * wired device to the config file and don't create a new default wired
-	 * connection for that device again.
+	 * sett_conn for that device again.
 	 */
-	device = g_object_get_qdata (G_OBJECT (connection), _default_wired_device_quark ());
+	device = nm_settings_connection_default_wired_get_device (sett_conn);
 	if (device)
-		default_wired_clear_tag (self, device, connection, TRUE);
+		default_wired_clear_tag (self, device, sett_conn, allow_add_to_no_auto_default);
 
-	/* Disconnect signal handlers, as plugins might still keep references
-	 * to the connection (and thus the signal handlers would still be live)
-	 * even after NMSettings has dropped all its references.
-	 */
-
-	g_signal_handlers_disconnect_by_func (connection, G_CALLBACK (connection_removed), self);
-	g_signal_handlers_disconnect_by_func (connection, G_CALLBACK (connection_updated), self);
-	g_signal_handlers_disconnect_by_func (connection, G_CALLBACK (connection_flags_changed), self);
-	if (!priv->startup_complete)
-		g_signal_handlers_disconnect_by_func (connection, G_CALLBACK (connection_ready_changed), self);
+	g_signal_handlers_disconnect_by_func (sett_conn, G_CALLBACK (connection_flags_changed), self);
 
-	/* Forget about the connection internally */
 	_clear_connections_cached_list (priv);
+	c_list_unlink (&sett_conn->_connections_lst);
 	priv->connections_len--;
-	c_list_unlink (&connection->_connections_lst);
+	priv->connections_generation++;
 
-	if (priv->connections_loaded) {
-		_notify (self, PROP_CONNECTIONS);
+	/* Tell agents to remove secrets for this connection */
+	connection_for_agents = nm_simple_connection_new_clone (nm_settings_connection_get_connection (sett_conn));
+	nm_connection_clear_secrets (connection_for_agents);
+	nm_agent_manager_delete_secrets (priv->agent_mgr,
+	                                 nm_dbus_object_get_path (NM_DBUS_OBJECT (self)),
+	                                 connection_for_agents);
 
-		nm_dbus_object_emit_signal (NM_DBUS_OBJECT (self),
-		                            &interface_info_settings,
-		                            &signal_info_connection_removed,
-		                            "(o)",
-		                            nm_dbus_object_get_path (NM_DBUS_OBJECT (connection)));
-	}
+	_notify (self, PROP_CONNECTIONS);
+	_nm_settings_connection_emit_dbus_signal_removed (sett_conn);
+	nm_dbus_object_emit_signal (NM_DBUS_OBJECT (self),
+	                            &interface_info_settings,
+	                            &signal_info_connection_removed,
+	                            "(o)",
+	                            nm_dbus_object_get_path (NM_DBUS_OBJECT (sett_conn)));
 
-	nm_dbus_object_unexport (NM_DBUS_OBJECT (connection));
+	nm_dbus_object_unexport (NM_DBUS_OBJECT (sett_conn));
 
-	if (priv->connections_loaded)
-		g_signal_emit (self, signals[CONNECTION_REMOVED], 0, connection);
+	nm_settings_connection_set_flags (sett_conn,
+	                                    NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE
+	                                  | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE,
+	                                  FALSE);
 
-	check_startup_complete (self);
+	_emit_connection_removed (self, sett_conn);
 
-	g_object_unref (connection);
+	_nm_settings_connection_cleanup_after_remove (sett_conn);
 
-	g_object_unref (self);       /* Balanced by a ref in claim_connection() */
-}
+	nm_key_file_db_remove_key (priv->kf_db_timestamps, uuid);
+	nm_key_file_db_remove_key (priv->kf_db_seen_bssids, uuid);
 
-#define NM_DBUS_SERVICE_OPENCONNECT    "org.freedesktop.NetworkManager.openconnect"
-#define NM_OPENCONNECT_KEY_GATEWAY "gateway"
-#define NM_OPENCONNECT_KEY_COOKIE "cookie"
-#define NM_OPENCONNECT_KEY_GWCERT "gwcert"
-#define NM_OPENCONNECT_KEY_XMLCONFIG "xmlconfig"
-#define NM_OPENCONNECT_KEY_LASTHOST "lasthost"
-#define NM_OPENCONNECT_KEY_AUTOCONNECT "autoconnect"
-#define NM_OPENCONNECT_KEY_CERTSIGS "certsigs"
+	if (   !priv->started
+	    || priv->startup_complete_idx)
+		_startup_complete_notify_connection (self, sett_conn, TRUE);
+}
 
 static void
-openconnect_migrate_hack (NMConnection *connection)
+_connection_changed_process_one (NMSettings *self,
+                                 SettConnEntry *sett_conn_entry,
+                                 gboolean allow_add_to_no_auto_default,
+                                 NMSettingsConnectionIntFlags sett_flags,
+                                 NMSettingsConnectionIntFlags sett_mask,
+                                 gboolean override_sett_flags,
+                                 NMSettingsConnectionUpdateReason update_reason)
 {
-	NMSettingVpn *s_vpn;
-	NMSettingSecretFlags flags = NM_SETTING_SECRET_FLAG_NOT_SAVED;
+	StorageData *sd_best;
 
-	/* Huge hack.  There were some openconnect changes that needed to happen
-	 * pretty late, too late to get into distros.  Migration has already
-	 * happened for many people, and their secret flags are wrong.  But we
-	 * don't want to requrie re-migration, so we have to fix it up here. Ugh.
-	 */
+	c_list_unlink (&sett_conn_entry->sce_dirty_lst);
+
+	_sett_conn_entry_sds_update (self, sett_conn_entry);
+
+	sd_best = c_list_first_entry (&sett_conn_entry->sd_lst_head, StorageData, sd_lst);;
+
+	if (   !sd_best
+	    || !sd_best->connection) {
+		gs_unref_object NMSettingsConnection *sett_conn = NULL;
+		gs_unref_object NMSettingsStorage *storage = NULL;
+
+		if (!sett_conn_entry->sett_conn) {
+
+			if (!sd_best) {
+				_sett_conn_entries_remove_and_destroy (self, sett_conn_entry);
+				return;
+			}
 
-	s_vpn = nm_connection_get_setting_vpn (connection);
-	if (s_vpn == NULL)
+			if (sett_conn_entry->storage != sd_best->storage) {
+				_LOGT ("update[%s]: shadow UUID ("NM_SETTINGS_STORAGE_PRINT_FMT")",
+				       sett_conn_entry->uuid,
+				       NM_SETTINGS_STORAGE_PRINT_ARG (sd_best->storage));
+			}
+
+			nm_g_object_ref_set (&sett_conn_entry->storage, sd_best->storage);
+			return;
+		}
+
+		sett_conn = g_steal_pointer (&sett_conn_entry->sett_conn);
+		if (sd_best) {
+			storage = g_object_ref (sd_best->storage);
+			nm_g_object_ref_set (&sett_conn_entry->storage, storage);
+			nm_assert_valid_settings_storage (NULL, storage);
+		} else {
+			storage = g_object_ref (sett_conn_entry->storage);
+			_sett_conn_entries_remove_and_destroy (self, sett_conn_entry);
+		}
+
+		_connection_changed_delete (self, storage, sett_conn, allow_add_to_no_auto_default);
 		return;
+	}
 
-	if (g_strcmp0 (nm_setting_vpn_get_service_type (s_vpn), NM_DBUS_SERVICE_OPENCONNECT) == 0) {
-		/* These are different for every login session, and should not be stored */
-		nm_setting_set_secret_flags (NM_SETTING (s_vpn), NM_OPENCONNECT_KEY_GATEWAY, flags, NULL);
-		nm_setting_set_secret_flags (NM_SETTING (s_vpn), NM_OPENCONNECT_KEY_COOKIE, flags, NULL);
-		nm_setting_set_secret_flags (NM_SETTING (s_vpn), NM_OPENCONNECT_KEY_GWCERT, flags, NULL);
+	if (override_sett_flags) {
+		NMSettingsConnectionIntFlags s_f, s_m;
 
-		/* These are purely internal data for the auth-dialog, and should be stored */
-		flags = 0;
-		nm_setting_set_secret_flags (NM_SETTING (s_vpn), NM_OPENCONNECT_KEY_XMLCONFIG, flags, NULL);
-		nm_setting_set_secret_flags (NM_SETTING (s_vpn), NM_OPENCONNECT_KEY_LASTHOST, flags, NULL);
-		nm_setting_set_secret_flags (NM_SETTING (s_vpn), NM_OPENCONNECT_KEY_AUTOCONNECT, flags, NULL);
-		nm_setting_set_secret_flags (NM_SETTING (s_vpn), NM_OPENCONNECT_KEY_CERTSIGS, flags, NULL);
+		nm_settings_storage_load_sett_flags (sd_best->storage, &s_f, &s_m);
+
+		nm_assert (!NM_FLAGS_ANY (s_f, ~s_m));
+
+		sett_mask |= s_m;
+		sett_flags = (sett_flags & ~s_m) | (s_f & s_m);
 	}
+
+	nm_g_object_ref_set (&sett_conn_entry->storage, sd_best->storage);
+
+	if (!sett_conn_entry->sett_conn)
+		sett_conn_entry->sett_conn = nm_settings_connection_new ();
+
+	_connection_changed_update (self,
+	                            sett_conn_entry,
+	                            sd_best->connection,
+	                            sett_flags,
+	                            sett_mask,
+	                            update_reason);
 }
 
 static void
-claim_connection (NMSettings *self, NMSettingsConnection *sett_conn)
+_connection_changed_process_all_dirty (NMSettings *self,
+                                       gboolean allow_add_to_no_auto_default,
+                                       NMSettingsConnectionIntFlags sett_flags,
+                                       NMSettingsConnectionIntFlags sett_mask,
+                                       gboolean override_sett_flags,
+                                       NMSettingsConnectionUpdateReason update_reason)
 {
-	NMSettingsPrivate *priv;
-	GError *error = NULL;
-	const char *path;
-	NMSettingsConnection *existing;
-
-	g_return_if_fail (NM_IS_SETTINGS (self));
-	g_return_if_fail (NM_IS_SETTINGS_CONNECTION (sett_conn));
-	g_return_if_fail (!nm_dbus_object_is_exported (NM_DBUS_OBJECT (sett_conn)));
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	SettConnEntry *sett_conn_entry;
+
+	while ((sett_conn_entry = c_list_first_entry (&priv->sce_dirty_lst_head, SettConnEntry, sce_dirty_lst))) {
+		_connection_changed_process_one (self,
+		                                 sett_conn_entry,
+		                                 allow_add_to_no_auto_default,
+		                                 sett_flags,
+		                                 sett_mask,
+		                                 override_sett_flags,
+		                                 update_reason);
+	}
+}
 
-	priv = NM_SETTINGS_GET_PRIVATE (self);
+static SettConnEntry *
+_connection_changed_track (NMSettings *self,
+                           NMSettingsStorage *storage,
+                           NMConnection *connection,
+                           gboolean prioritize)
+{
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	SettConnEntry *sett_conn_entry;
+	StorageData *sd;
+	const char *uuid;
 
-	/* prevent duplicates */
-	if (!c_list_is_empty (&sett_conn->_connections_lst)) {
-		nm_assert (c_list_contains (&priv->connections_lst_head, &sett_conn->_connections_lst));
-		return;
+	nm_assert_valid_settings_storage (NULL, storage);
+
+	uuid = nm_settings_storage_get_uuid (storage);
+
+	nm_assert (!connection || NM_IS_CONNECTION (connection));
+	nm_assert (!connection || (_nm_connection_verify (connection, NULL) == NM_SETTING_VERIFY_SUCCESS));
+	nm_assert (!connection || nm_streq0 (uuid, nm_connection_get_uuid (connection)));
+
+	nmtst_connection_assert_unchanging (connection);
+
+	sett_conn_entry =    _sett_conn_entries_get (self, uuid)
+	                  ?: _sett_conn_entries_create_and_add (self, uuid);
+
+	if (_LOGT_ENABLED ()) {
+		const char *filename;
+		const NMSettingsMetaData *meta_data;
+		const char *shadowed_storage;
+		gboolean shadowed_owned;
+
+		filename = nm_settings_storage_get_filename (storage);
+		if (connection) {
+			shadowed_storage = nm_settings_storage_get_shadowed_storage (storage, &shadowed_owned);
+			_LOGT ("storage[%s,"NM_SETTINGS_STORAGE_PRINT_FMT"]: change event with connection \"%s\"%s%s%s%s%s%s",
+			       sett_conn_entry->uuid,
+			       NM_SETTINGS_STORAGE_PRINT_ARG (storage),
+			       nm_connection_get_id (connection),
+			       NM_PRINT_FMT_QUOTED (filename, " (file \"", filename, "\")", ""),
+			       NM_PRINT_FMT_QUOTED (shadowed_storage, shadowed_owned ? " (owns \"" : " (shadows \"", shadowed_storage, "\")", ""));
+		} else if ((meta_data = nm_settings_storage_is_meta_data (storage))) {
+			nm_assert (meta_data->is_tombstone);
+			shadowed_storage = nm_settings_storage_get_shadowed_storage (storage, &shadowed_owned);
+			_LOGT ("storage[%s,"NM_SETTINGS_STORAGE_PRINT_FMT"]: change event for %shiding profile%s%s%s%s%s%s",
+			       sett_conn_entry->uuid,
+			       NM_SETTINGS_STORAGE_PRINT_ARG (storage),
+			       nm_settings_storage_is_meta_data_alive  (storage) ? "" : "dropping ",
+			       NM_PRINT_FMT_QUOTED (filename, " (file \"", filename, "\")", ""),
+			       NM_PRINT_FMT_QUOTED (shadowed_storage, shadowed_owned ? " (owns \"" : " (shadows \"", shadowed_storage, "\")", ""));
+		} else {
+			_LOGT ("storage[%s,"NM_SETTINGS_STORAGE_PRINT_FMT"]: change event for dropping profile%s%s%s",
+			       sett_conn_entry->uuid,
+			       NM_SETTINGS_STORAGE_PRINT_ARG (storage),
+			       NM_PRINT_FMT_QUOTED (filename, " (file \"", filename, "\")", ""));
+		}
 	}
 
-	/* FIXME(copy-on-write-connection): avoid modifying NMConnection instances and share them via copy-on-write. */
-	if (!nm_connection_normalize (nm_settings_connection_get_connection (sett_conn), NULL, NULL, &error)) {
-		_LOGW ("plugin provided invalid connection: %s", error->message);
-		g_error_free (error);
-		return;
+	/* see _sett_conn_entry_sds_update() for why we append the new events
+	 * and leave existing ones at their position. */
+	sd = _storage_data_find_in_lst (&sett_conn_entry->dirty_sd_lst_head, storage);
+	if (sd)
+		nm_g_object_ref_set (&sd->connection, connection);
+	else {
+		sd = _storage_data_new_stale (storage, connection);
+		c_list_link_tail (&sett_conn_entry->dirty_sd_lst_head, &sd->sd_lst);
 	}
 
-	existing = nm_settings_get_connection_by_uuid (self, nm_settings_connection_get_uuid (sett_conn));
-	if (existing) {
-		/* Cannot add duplicate connections per UUID. Just return without action and
-		 * log a warning.
-		 *
-		 * This means, that plugins must not provide duplicate connections (UUID).
-		 * In fact, none of the plugins currently would do that.
-		 *
-		 * But globaly, over different setting plugins, there could be duplicates
-		 * without the individual plugins being aware. Don't handle that at all, just
-		 * error out. That should not happen unless the admin misconfigured the system
-		 * to create conflicting connections. */
-		_LOGW ("plugin provided duplicate connection with UUID %s",
-		       nm_settings_connection_get_uuid (sett_conn));
-		return;
+	if (prioritize) {
+		StorageData *sd2;
+
+		/* only one entry can be prioritized. */
+		c_list_for_each_entry (sd2, &sett_conn_entry->dirty_sd_lst_head, sd_lst)
+			sd2->prioritize = FALSE;
+		sd->prioritize = TRUE;
 	}
 
-	/* Read timestamp from look-aside file and put it into the connection's data */
-	nm_settings_connection_read_and_fill_timestamp (sett_conn);
+	nm_c_list_move_tail (&priv->sce_dirty_lst_head, &sett_conn_entry->sce_dirty_lst);
 
-	/* Read seen-bssids from look-aside file and put it into the connection's data */
-	nm_settings_connection_read_and_fill_seen_bssids (sett_conn);
+	return sett_conn_entry;
+}
 
-	/* Ensure its initial visibility is up-to-date */
-	nm_settings_connection_recheck_visibility (sett_conn);
+/*****************************************************************************/
 
-	/* Evil openconnect migration hack */
-	/* FIXME(copy-on-write-connection): avoid modifying NMConnection instances and share them via copy-on-write. */
-	openconnect_migrate_hack (nm_settings_connection_get_connection (sett_conn));
+static void
+_plugin_connections_reload_cb (NMSettingsPlugin *plugin,
+                               NMSettingsStorage *storage,
+                               NMConnection *connection,
+                               gpointer user_data)
+{
+	_connection_changed_track (user_data, storage, connection, FALSE);
+}
 
-	/* This one unexports the connection, it needs to run late to give the active
-	 * connection a chance to deal with its reference to this settings connection. */
-	g_signal_connect_after (sett_conn, NM_SETTINGS_CONNECTION_REMOVED,
-	                        G_CALLBACK (connection_removed), self);
-	g_signal_connect (sett_conn, NM_SETTINGS_CONNECTION_UPDATED_INTERNAL,
-	                  G_CALLBACK (connection_updated), self);
-	g_signal_connect (sett_conn, NM_SETTINGS_CONNECTION_FLAGS_CHANGED,
-	                  G_CALLBACK (connection_flags_changed),
-	                  self);
-	if (!priv->startup_complete) {
-		g_signal_connect (sett_conn, "notify::" NM_SETTINGS_CONNECTION_READY,
-		                  G_CALLBACK (connection_ready_changed),
-		                  self);
+static void
+_plugin_connections_reload (NMSettings *self)
+{
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	GSList *iter;
+
+	for (iter = priv->plugins; iter; iter = iter->next) {
+		nm_settings_plugin_reload_connections (iter->data,
+		                                       _plugin_connections_reload_cb,
+		                                       self);
 	}
 
-	_clear_connections_cached_list (priv);
+	_connection_changed_process_all_dirty (self,
+	                                       FALSE,
+	                                       NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
+	                                       NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
+	                                       TRUE,
+	                                         NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_SYSTEM_SECRETS
+	                                       | NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_AGENT_SECRETS);
 
-	g_object_ref (sett_conn);
-	/* FIXME(shutdown): The NMSettings instance can't be disposed
-	 * while there is any exported connection. Ideally we should
-	 * unexport all connections on NMSettings' disposal, but for now
-	 * leak @self on termination when there are connections alive. */
-	g_object_ref (self);
-	priv->connections_len++;
-	c_list_link_tail (&priv->connections_lst_head, &sett_conn->_connections_lst);
-
-	path = nm_dbus_object_export (NM_DBUS_OBJECT (sett_conn));
-
-	nm_utils_log_connection_diff (nm_settings_connection_get_connection (sett_conn),
-	                              NULL,
-	                              LOGL_DEBUG,
-	                              LOGD_CORE,
-	                              "new connection", "++ ",
-	                              path);
-
-	/* Only emit the individual connection-added signal after connections
-	 * have been initially loaded.
-	 */
-	if (priv->connections_loaded) {
-		nm_dbus_object_emit_signal (NM_DBUS_OBJECT (self),
-		                            &interface_info_settings,
-		                            &signal_info_new_connection,
-		                            "(o)",
-		                            nm_dbus_object_get_path (NM_DBUS_OBJECT (sett_conn)));
+	for (iter = priv->plugins; iter; iter = iter->next)
+		nm_settings_plugin_load_connections_done (iter->data);
+}
 
-		g_signal_emit (self, signals[CONNECTION_ADDED], 0, sett_conn);
-		_notify (self, PROP_CONNECTIONS);
+/*****************************************************************************/
+
+static gboolean
+_add_connection_to_first_plugin (NMSettings *self,
+                                 SettConnEntry *sett_conn_entry,
+                                 NMConnection *new_connection,
+                                 gboolean in_memory,
+                                 NMSettingsConnectionIntFlags sett_flags,
+                                 const char *shadowed_storage,
+                                 gboolean shadowed_owned,
+                                 NMSettingsStorage **out_new_storage,
+                                 NMConnection **out_new_connection,
+                                 GError **error)
+{
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	GError *first_error = NULL;
+	GSList *iter;
+	const char *uuid;
+
+	uuid = nm_connection_get_uuid (new_connection);
+
+	nm_assert (nm_utils_is_uuid (uuid));
+
+	for (iter = priv->plugins; iter; iter = iter->next) {
+		NMSettingsPlugin *plugin = NM_SETTINGS_PLUGIN (iter->data);
+		gs_unref_object NMSettingsStorage *storage = NULL;
+		gs_unref_object NMConnection *connection_to_add = NULL;
+		gs_unref_object NMConnection *connection_to_add_cloned = NULL;
+		NMConnection *connection_to_add_real = NULL;
+		gs_unref_variant GVariant *agent_owned_secrets = NULL;
+		gs_free_error GError *add_error = NULL;
+		gboolean success;
+		const char *filename;
+
+		if (!in_memory) {
+			NMSettingsStorage *conflicting_storage;
+
+			conflicting_storage = _sett_conn_entry_storage_find_conflicting_storage (sett_conn_entry, plugin, NULL, priv->plugins);
+			if (conflicting_storage) {
+				/* we have a connection provided by a plugin with higher priority than the one
+				 * we would want to add the connection. We cannot do that, because doing so
+				 * would result in adding a connection that gets hidden by the existing profile.
+				 * Also, since we test the plugins in order of priority, all following plugins
+				 * are unsuitable.
+				 *
+				 * Multiple connection plugins are so cumbersome, especially if they are unable
+				 * to add the connection. I suggest to disable all plugins except keyfile. */
+				_LOGT ("add-connection: failed to add %s/'%s': there is an existing storage "NM_SETTINGS_STORAGE_PRINT_FMT" with higher priority",
+				       nm_connection_get_uuid (new_connection),
+				       nm_connection_get_id (new_connection),
+				       NM_SETTINGS_STORAGE_PRINT_ARG (conflicting_storage));
+				nm_assert (first_error);
+				break;
+			}
+		}
+
+		if (plugin == (NMSettingsPlugin *) priv->keyfile_plugin) {
+			success = nms_keyfile_plugin_add_connection (priv->keyfile_plugin,
+			                                             new_connection,
+			                                             in_memory,
+			                                             NM_FLAGS_HAS (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED),
+			                                             NM_FLAGS_HAS (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE),
+			                                             shadowed_storage,
+			                                             shadowed_owned,
+			                                             &storage,
+			                                             &connection_to_add,
+			                                             &add_error);
+		} else {
+			if (in_memory)
+				continue;
+			nm_assert (!NM_FLAGS_HAS (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED));
+			nm_assert (!NM_FLAGS_HAS (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE));
+			success = nm_settings_plugin_add_connection (plugin,
+			                                             new_connection,
+			                                             &storage,
+			                                             &connection_to_add,
+			                                             &add_error);
+		}
+
+		if (!success) {
+			_LOGT ("add-connection: failed to add %s/'%s': %s",
+			       nm_connection_get_uuid (new_connection),
+			       nm_connection_get_id (new_connection),
+			       add_error->message);
+			if (!first_error)
+				first_error = g_steal_pointer (&add_error);
+			continue;
+		}
+
+		if (!nm_streq0 (nm_settings_storage_get_uuid (storage), uuid)) {
+			nm_assert_not_reached ();
+			continue;
+		}
+
+		agent_owned_secrets = nm_connection_to_dbus (new_connection,
+		                                               NM_CONNECTION_SERIALIZE_ONLY_SECRETS
+		                                             | NM_CONNECTION_SERIALIZE_WITH_SECRETS_AGENT_OWNED);
+		connection_to_add_real = _connection_changed_normalize_connection (storage,
+		                                                                   connection_to_add,
+		                                                                   agent_owned_secrets,
+		                                                                   &connection_to_add_cloned);
+		if (!connection_to_add_real) {
+			nm_assert_not_reached ();
+			continue;
+		}
+
+		filename = nm_settings_storage_get_filename (storage);
+		_LOGT ("add-connection: successfully added connection %s,'%s' ("NM_SETTINGS_STORAGE_PRINT_FMT"%s%s%s",
+		       nm_settings_storage_get_uuid (storage),
+		       nm_connection_get_id (new_connection),
+		       NM_SETTINGS_STORAGE_PRINT_ARG (storage),
+		       NM_PRINT_FMT_QUOTED (filename, ", \"", filename, "\")", ")"));
+
+		*out_new_storage = g_steal_pointer (&storage);
+		*out_new_connection =    g_steal_pointer (&connection_to_add_cloned)
+		                      ?: g_steal_pointer (&connection_to_add);
+		nm_assert (NM_IS_CONNECTION (*out_new_connection));
+		return TRUE;
 	}
 
-	nm_settings_connection_added (sett_conn);
+	nm_assert (first_error);
+	g_propagate_error (error, first_error);
+	return FALSE;
 }
 
 static gboolean
-secrets_filter_cb (NMSetting *setting,
-                   const char *secret,
-                   NMSettingSecretFlags flags,
-                   gpointer user_data)
+_update_connection_to_plugin (NMSettings *self,
+                              NMSettingsStorage *storage,
+                              NMConnection *connection,
+                              NMSettingsConnectionIntFlags sett_flags,
+                              gboolean force_rename,
+                              const char *shadowed_storage,
+                              gboolean shadowed_owned,
+                              NMSettingsStorage **out_new_storage,
+                              NMConnection **out_new_connection,
+                              GError **error)
 {
-	NMSettingSecretFlags filter_flags = GPOINTER_TO_UINT (user_data);
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	NMSettingsPlugin *plugin;
+	gboolean success;
+
+	plugin = nm_settings_storage_get_plugin (storage);
+
+	if (plugin == (NMSettingsPlugin *) priv->keyfile_plugin) {
+		success = nms_keyfile_plugin_update_connection (priv->keyfile_plugin,
+		                                                storage,
+		                                                connection,
+		                                                NM_FLAGS_HAS (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED),
+		                                                NM_FLAGS_HAS (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE),
+		                                                shadowed_storage,
+		                                                shadowed_owned,
+		                                                force_rename,
+		                                                out_new_storage,
+		                                                out_new_connection,
+		                                                error);
+	} else {
+		nm_assert (!shadowed_storage);
+		nm_assert (!shadowed_owned);
+		success = nm_settings_plugin_update_connection (plugin,
+		                                                storage,
+		                                                connection,
+		                                                out_new_storage,
+		                                                out_new_connection,
+		                                                error);
+	}
 
-	/* Returns TRUE to remove the secret */
+	return success;
+}
 
-	/* Can't use bitops with SECRET_FLAG_NONE so handle that specifically */
-	if (   (flags == NM_SETTING_SECRET_FLAG_NONE)
-	    && (filter_flags == NM_SETTING_SECRET_FLAG_NONE))
-		return FALSE;
+static void
+_set_nmmeta_tombstone (NMSettings *self,
+                       const char *uuid,
+                       gboolean tombstone_on_disk,
+                       gboolean tombstone_in_memory,
+                       const char *shadowed_storage)
+{
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	gs_unref_object NMSettingsStorage *tombstone_1_storage = NULL;
+	gs_unref_object NMSettingsStorage *tombstone_2_storage = NULL;
+
+	if (tombstone_on_disk) {
+		if (!nms_keyfile_plugin_set_nmmeta_tombstone (priv->keyfile_plugin,
+		                                              FALSE,
+		                                              uuid,
+		                                              FALSE,
+		                                              TRUE,
+		                                              NULL,
+		                                              &tombstone_1_storage,
+		                                              NULL))
+			tombstone_in_memory = TRUE;
+		if (tombstone_1_storage)
+			_connection_changed_track (self, tombstone_1_storage, NULL, FALSE);
+	}
 
-	/* Otherwise if the secret has at least one of the desired flags keep it */
-	return (flags & filter_flags) ? FALSE : TRUE;
+	if (tombstone_in_memory) {
+		if (!nms_keyfile_plugin_set_nmmeta_tombstone (priv->keyfile_plugin,
+		                                              FALSE,
+		                                              uuid,
+		                                              TRUE,
+		                                              TRUE,
+		                                              shadowed_storage,
+		                                              &tombstone_2_storage,
+		                                              NULL)) {
+			nms_keyfile_plugin_set_nmmeta_tombstone (priv->keyfile_plugin,
+			                                         TRUE,
+			                                         uuid,
+			                                         TRUE,
+			                                         TRUE,
+			                                         shadowed_storage,
+			                                         &tombstone_2_storage,
+			                                         NULL);
+		}
+		_connection_changed_track (self, tombstone_2_storage, NULL, FALSE);
+	}
 }
 
 /**
  * nm_settings_add_connection:
  * @self: the #NMSettings object
  * @connection: the source connection to create a new #NMSettingsConnection from
- * @save_to_disk: %TRUE to save the connection to disk immediately, %FALSE to
- * not save to disk
+ * @persist_mode: the persist-mode for this profile.
+ * @add_reason: the add-reason flags.
+ * @sett_flags: the settings flags to set.
+ * @out_sett_conn: (allow-none) (transfer none): the added settings connection on success.
  * @error: on return, a location to store any errors that may occur
  *
  * Creates a new #NMSettingsConnection for the given source @connection.
  * The returned object is owned by @self and the caller must reference
  * the object to continue using it.
  *
- * Returns: the new #NMSettingsConnection or %NULL
+ * Returns: TRUE on success.
  */
-NMSettingsConnection *
+gboolean
 nm_settings_add_connection (NMSettings *self,
                             NMConnection *connection,
-                            gboolean save_to_disk,
+                            NMSettingsConnectionPersistMode persist_mode,
+                            NMSettingsConnectionAddReason add_reason,
+                            NMSettingsConnectionIntFlags sett_flags,
+                            NMSettingsConnection **out_sett_conn,
                             GError **error)
 {
-	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
-	GSList *iter;
-	NMSettingsConnection *added = NULL;
-	NMSettingsConnection *candidate = NULL;
+	NMSettingsPrivate *priv;
+	gs_unref_object NMConnection *connection_cloned_1 = NULL;
+	gs_unref_object NMConnection *new_connection = NULL;
+	gs_unref_object NMSettingsStorage *new_storage = NULL;
+	gs_unref_object NMSettingsStorage *shadowed_storage = NULL;
+	NMSettingsStorage *update_storage = NULL;
+	gs_free_error GError *local = NULL;
+	SettConnEntry *sett_conn_entry;
 	const char *uuid;
+	StorageData *sd;
+	gboolean new_in_memory;
+	gboolean success;
+	const char *shadowed_storage_filename = NULL;
+
+	priv = NM_SETTINGS_GET_PRIVATE (self);
+
+	nm_assert (NM_IN_SET (persist_mode, NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK,
+	                                    NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY));
+
+	new_in_memory = (persist_mode != NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK);
+
+	nm_assert (!NM_FLAGS_ANY (sett_flags, ~_NM_SETTINGS_CONNECTION_INT_FLAGS_PERSISTENT_MASK));
+
+	if (NM_FLAGS_ANY (sett_flags,   NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
+	                              | NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED)) {
+		nm_assert (new_in_memory);
+		new_in_memory = TRUE;
+	}
+
+	nm_assert (!NM_FLAGS_ANY (add_reason, ~NM_SETTINGS_CONNECTION_ADD_REASON_BLOCK_AUTOCONNECT));
+
+	NM_SET_OUT (out_sett_conn, NULL);
 
 	uuid = nm_connection_get_uuid (connection);
 
-	/* Make sure a connection with this UUID doesn't already exist */
-	c_list_for_each_entry (candidate, &priv->connections_lst_head, _connections_lst) {
-		if (nm_streq0 (uuid, nm_settings_connection_get_uuid (candidate))) {
-			g_set_error_literal (error,
-			                     NM_SETTINGS_ERROR,
-			                     NM_SETTINGS_ERROR_UUID_EXISTS,
-			                     "A connection with this UUID already exists.");
-			return NULL;
+	sett_conn_entry = _sett_conn_entries_get (self, uuid);
+	if (_sett_conn_entry_get_conn (sett_conn_entry)) {
+		g_set_error_literal (error,
+		                     NM_SETTINGS_ERROR,
+		                     NM_SETTINGS_ERROR_UUID_EXISTS,
+		                     "a connection with this UUID already exists");
+		return FALSE;
+	}
+
+	if (!_nm_connection_ensure_normalized (connection,
+	                                       FALSE,
+	                                       NULL,
+	                                       FALSE,
+	                                       &connection_cloned_1,
+	                                       &local)) {
+		g_set_error (error,
+		             NM_SETTINGS_ERROR,
+		             NM_SETTINGS_ERROR_INVALID_CONNECTION,
+		             "connection is invalid: %s",
+		             local->message);
+		return FALSE;
+	}
+	if (connection_cloned_1)
+		connection = connection_cloned_1;
+
+	if (sett_conn_entry) {
+		c_list_for_each_entry (sd, &sett_conn_entry->sd_lst_head, sd_lst) {
+			if (!nm_settings_storage_is_meta_data (sd->storage))
+				continue;
+			shadowed_storage = nm_g_object_ref (_sett_conn_entry_find_shadowed_storage (sett_conn_entry,
+			                                                                            nm_settings_storage_get_shadowed_storage (sd->storage, NULL),
+			                                                                            NULL));
+			if (shadowed_storage) {
+				/* We have a nmmeta tombstone that indicates that a storage is shadowed.
+				 *
+				 * This happens when deleting a in-memory profile that was decoupled from
+				 * the persitant storage with NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED.
+				 * We need to take over this storage again... */
+				break;
+			}
 		}
 	}
 
-	/* 1) plugin writes the NMConnection to disk
-	 * 2) plugin creates a new NMSettingsConnection subclass with the settings
-	 *     from the NMConnection and returns it to the settings service
-	 * 3) settings service exports the new NMSettingsConnection subclass
-	 * 4) plugin notices that something on the filesystem has changed
-	 * 5) plugin reads the changes and ignores them because they will
-	 *     contain the same data as the connection it already knows about
-	 */
-	for (iter = priv->plugins; iter; iter = g_slist_next (iter)) {
-		NMSettingsPlugin *plugin = NM_SETTINGS_PLUGIN (iter->data);
-		GError *add_error = NULL;
-		gs_unref_object NMConnection *simple = NULL;
-		gs_unref_variant GVariant *secrets = NULL;
-
-		/* Make a copy of agent-owned secrets because they won't be present in
-		 * the connection returned by plugins, as plugins return only what was
-		 * reread from the file. */
-		simple = nm_simple_connection_new_clone (connection);
-		nm_connection_clear_secrets_with_flags (simple,
-		                                        secrets_filter_cb,
-		                                        GUINT_TO_POINTER (NM_SETTING_SECRET_FLAG_AGENT_OWNED));
-		secrets = nm_connection_to_dbus (simple, NM_CONNECTION_SERIALIZE_ONLY_SECRETS);
-
-		added = nm_settings_plugin_add_connection (plugin, connection, save_to_disk, &add_error);
-		if (added) {
-			if (secrets) {
-				/* FIXME(copy-on-write-connection): avoid modifying NMConnection instances and share them via copy-on-write. */
-				nm_connection_update_secrets (nm_settings_connection_get_connection (added),
-				                              NULL,
-				                              secrets,
-				                              NULL);
+	if (   shadowed_storage
+	    && !new_in_memory) {
+		NMSettingsStorage *conflicting_storage;
+
+		conflicting_storage = _sett_conn_entry_storage_find_conflicting_storage (sett_conn_entry,
+		                                                                         nm_settings_storage_get_plugin (shadowed_storage),
+		                                                                         shadowed_storage,
+		                                                                         priv->plugins);
+		if (conflicting_storage) {
+			/* We cannot add the profile as @shadowed_storage, because there is another, existing storage
+			 * that would hide it. Just add it as new storage. In general, this leads to duplication of profiles,
+			 * but the circumstances where this happens are very exotic (you need at least one additional settings
+			 * plugin, then going through the paths of making shadowed_storage in-memory-detached and delete it,
+			 * and finally adding the conflicting storage outside of NM and restart/reload). */
+			_LOGT ("ignore shadowed storage "NM_SETTINGS_STORAGE_PRINT_FMT" due to conflicting storage "NM_SETTINGS_STORAGE_PRINT_FMT,
+			       NM_SETTINGS_STORAGE_PRINT_ARG (shadowed_storage),
+			       NM_SETTINGS_STORAGE_PRINT_ARG (conflicting_storage));
+		} else
+			update_storage = shadowed_storage;
+	}
+
+	shadowed_storage_filename =   (   shadowed_storage
+	                               && !update_storage)
+	                            ? nm_settings_storage_get_filename_for_shadowed_storage (shadowed_storage)
+	                            : NULL;
+
+again_add_connection:
+
+	if (!update_storage) {
+		success = _add_connection_to_first_plugin (self,
+		                                           sett_conn_entry,
+		                                           connection,
+		                                           new_in_memory,
+		                                           sett_flags,
+		                                           shadowed_storage_filename,
+		                                           FALSE,
+		                                           &new_storage,
+		                                           &new_connection,
+		                                           &local);
+	} else {
+		success = _update_connection_to_plugin (self,
+		                                        update_storage,
+		                                        connection,
+		                                        sett_flags,
+		                                        FALSE,
+		                                        shadowed_storage_filename,
+		                                        FALSE,
+		                                        &new_storage,
+		                                        &new_connection,
+		                                        &local);
+		if (!success) {
+			if (!NMS_IS_KEYFILE_STORAGE (update_storage)) {
+				/* hm, the intended storage is not keyfile (it's ifcfg-rh). This settings
+				 * plugin may not support the new connection. So step back and retry adding
+				 * the profile anew. */
+				_LOGT ("failure to add profile as existing storage \"%s\": %s",
+				       nm_settings_storage_get_filename (update_storage),
+				       local->message);
+				update_storage = NULL;
+				g_clear_object (&shadowed_storage);
+				shadowed_storage_filename = NULL;
+				g_clear_error (&local);
+				goto again_add_connection;
 			}
-			claim_connection (self, added);
-			return added;
 		}
-		_LOGD ("Failed to add %s/'%s': %s",
-		       nm_connection_get_uuid (connection),
-		       nm_connection_get_id (connection),
-		       add_error->message);
-		g_clear_error (&add_error);
 	}
 
-	g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
-	                     "No plugin supported adding this connection");
-	return NULL;
+	if (!success) {
+		if (!update_storage) {
+			g_set_error (error,
+			             NM_SETTINGS_ERROR,
+			             NM_SETTINGS_ERROR_FAILED,
+			             "failure adding connection: %s",
+			             local->message);
+		} else {
+			g_set_error (error,
+			             NM_SETTINGS_ERROR,
+			             NM_SETTINGS_ERROR_FAILED,
+			             "failure writing connection to existing storage \"%s\": %s",
+			             nm_settings_storage_get_filename (update_storage),
+			             local->message);
+		}
+		return FALSE;
+	}
+
+	sett_conn_entry = _connection_changed_track (self, new_storage, new_connection, TRUE);
+
+	c_list_for_each_entry (sd, &sett_conn_entry->sd_lst_head, sd_lst) {
+		const NMSettingsMetaData *meta_data;
+		gs_unref_object NMSettingsStorage *new_tombstone_storage = NULL;
+		gboolean in_memory;
+		gboolean simulate;
+
+		meta_data = nm_settings_storage_is_meta_data_alive (sd->storage);
+		if (   !meta_data
+		    || !meta_data->is_tombstone)
+			continue;
+
+		if (nm_settings_storage_is_keyfile_run (sd->storage))
+			in_memory = TRUE;
+		else {
+			if (nm_settings_storage_is_keyfile_run (new_storage)) {
+				/* Don't remove the file from /etc if we just wrote an in-memory connection */
+				continue;
+			}
+			in_memory = FALSE;
+		}
+
+		simulate = FALSE;
+again_delete_tombstone:
+		if (!nms_keyfile_plugin_set_nmmeta_tombstone (priv->keyfile_plugin,
+		                                              simulate,
+		                                              uuid,
+		                                              in_memory,
+		                                              FALSE,
+		                                              NULL,
+		                                              &new_tombstone_storage,
+		                                              NULL)) {
+			/* Ups, something went wrong. We really need to get rid of the tombstone. At least
+			 * forget about it in-memory. Upong next restart/reload, this might be reverted
+			 * however :( .*/
+			if (!simulate) {
+				simulate = TRUE;
+				goto again_delete_tombstone;
+			}
+		}
+		if (new_tombstone_storage)
+			_connection_changed_track (self, new_tombstone_storage, NULL, FALSE);
+	}
+
+	_connection_changed_process_all_dirty (self,
+	                                       FALSE,
+	                                       sett_flags,
+	                                       _NM_SETTINGS_CONNECTION_INT_FLAGS_PERSISTENT_MASK,
+	                                       FALSE,
+	                                         NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_SYSTEM_SECRETS
+	                                       | NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_AGENT_SECRETS
+	                                       | (  NM_FLAGS_HAS (add_reason, NM_SETTINGS_CONNECTION_ADD_REASON_BLOCK_AUTOCONNECT)
+	                                          ? NM_SETTINGS_CONNECTION_UPDATE_REASON_BLOCK_AUTOCONNECT
+	                                          : NM_SETTINGS_CONNECTION_UPDATE_REASON_NONE));
+
+	nm_assert (sett_conn_entry == _sett_conn_entries_get (self, sett_conn_entry->uuid));
+	nm_assert (NM_IS_SETTINGS_CONNECTION (sett_conn_entry->sett_conn));
+
+	NM_SET_OUT (out_sett_conn, _sett_conn_entry_get_conn (sett_conn_entry));
+	return TRUE;
+}
+
+/*****************************************************************************/
+
+gboolean
+nm_settings_update_connection (NMSettings *self,
+                               NMSettingsConnection *sett_conn,
+                               NMConnection *connection,
+                               NMSettingsConnectionPersistMode persist_mode,
+                               NMSettingsConnectionIntFlags sett_flags,
+                               NMSettingsConnectionIntFlags sett_mask,
+                               NMSettingsConnectionUpdateReason update_reason,
+                               const char *log_context_name,
+                               GError **error)
+{
+	gs_unref_object NMConnection *connection_cloned_1 = NULL;
+	gs_unref_object NMConnection *new_connection_cloned = NULL;
+	gs_unref_object NMConnection *new_connection = NULL;
+	NMConnection *new_connection_real;
+	gs_unref_object NMSettingsStorage *cur_storage = NULL;
+	gs_unref_object NMSettingsStorage *new_storage = NULL;
+	NMSettingsStorage *drop_storage = NULL;
+	SettConnEntry *sett_conn_entry;
+	gboolean cur_in_memory;
+	gboolean new_in_memory;
+	const char *uuid;
+	gboolean tombstone_in_memory = FALSE;
+	gboolean tombstone_on_disk = FALSE;
+
+	g_return_val_if_fail (NM_IS_SETTINGS (self), FALSE);
+	g_return_val_if_fail (NM_IS_SETTINGS_CONNECTION (sett_conn), FALSE);
+	g_return_val_if_fail (!connection || NM_IS_CONNECTION (connection), FALSE);
+
+	nm_assert (!NM_FLAGS_ANY (sett_mask, ~_NM_SETTINGS_CONNECTION_INT_FLAGS_PERSISTENT_MASK));
+	nm_assert (!NM_FLAGS_ANY (sett_flags, ~sett_mask));
+	nm_assert (NM_IN_SET (persist_mode, NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP,
+	                                    NM_SETTINGS_CONNECTION_PERSIST_MODE_NO_PERSIST,
+	                                    NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK,
+	                                    NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY,
+	                                    NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED,
+	                                    NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY));
+
+	cur_storage = g_object_ref (nm_settings_connection_get_storage (sett_conn));
+
+	uuid = nm_settings_storage_get_uuid (cur_storage);
+
+	nm_assert (NM_IS_SETTINGS_STORAGE (cur_storage));
+
+	sett_conn_entry = _sett_conn_entries_get (self, uuid);
+
+	nm_assert (_sett_conn_entry_get_conn (sett_conn_entry) == sett_conn);
+
+	if (connection) {
+		gs_free_error GError *local = NULL;
+
+		if (!_nm_connection_ensure_normalized (connection,
+		                                       FALSE,
+		                                       uuid,
+		                                       TRUE,
+		                                       &connection_cloned_1,
+		                                       &local)) {
+			_LOGT ("update[%s]: %s: failed because profile is invalid: %s",
+			       nm_settings_storage_get_uuid (cur_storage),
+			       log_context_name,
+			       local->message);
+			g_set_error (error,
+			             NM_SETTINGS_ERROR,
+			             NM_SETTINGS_ERROR_INVALID_CONNECTION,
+			             "connection is invalid: %s",
+			             local->message);
+			return FALSE;
+		}
+		if (connection_cloned_1)
+			connection = connection_cloned_1;
+	} else
+		connection = nm_settings_connection_get_connection (sett_conn);
+
+	cur_in_memory = nm_settings_storage_is_keyfile_run (cur_storage);
+
+	if (persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP) {
+		persist_mode =   cur_in_memory
+		               ? NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY
+		               : NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK;
+	}
+
+	if (   NM_FLAGS_HAS (sett_mask, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED)
+	    && !NM_FLAGS_HAS (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED)) {
+		NMDevice *device;
+
+		/* The connection has been changed by the user, it should no longer be
+		 * considered a default wired connection, and should no longer affect
+		 * the no-auto-default configuration option.
+		 */
+		device = nm_settings_connection_default_wired_get_device (sett_conn);
+		if (device) {
+			nm_assert (cur_in_memory);
+			nm_assert (!NM_FLAGS_ANY (nm_settings_connection_get_flags (sett_conn),
+			                            NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
+			                          | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE));
+
+			default_wired_clear_tag (self, device, sett_conn, FALSE);
+
+			if (NM_IN_SET (persist_mode, NM_SETTINGS_CONNECTION_PERSIST_MODE_NO_PERSIST)) {
+				/* making a default-wired-connection a regular connection implies persisting
+				 * it to disk (unless specified differently).
+				 *
+				 * Actually, this line is probably unreached, because we should not use
+				 * NM_SETTINGS_CONNECTION_PERSIST_MODE_NO_PERSIST to toggle the nm-generated
+				 * flag. */
+				nm_assert_not_reached ();
+				persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK;
+			}
+		}
+	}
+
+	if (   persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_NO_PERSIST
+	    && NM_FLAGS_ANY (sett_mask,   NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
+	                                | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE)
+	    && NM_FLAGS_ANY ((sett_flags ^ nm_settings_connection_get_flags (sett_conn)) & sett_mask,
+	                       NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
+	                     | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE)) {
+		/* we update the nm-generated/volatile setting of a profile (which is inherrently
+		 * in-memory. The caller did not request to persist this to disk, however we need
+		 * to store the flags in run. */
+		nm_assert (cur_in_memory);
+		persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY;
+	}
+
+	if (persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK)
+		new_in_memory = FALSE;
+	else if (NM_IN_SET (persist_mode, NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY,
+	                                  NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED,
+	                                  NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY))
+		new_in_memory = TRUE;
+	else {
+		nm_assert (persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_NO_PERSIST);
+		new_in_memory = cur_in_memory;
+	}
+
+	if (!new_in_memory) {
+		/* Persistent connections cannot be volatile nor nm-generated.
+		 *
+		 * That is obviously true for volatile, as it is enforced by Update2() API.
+		 *
+		 * For nm-generated profiles also, because the nm-generated flag is only stored
+		 * for in-memory profiles. If we would persist the profile to /etc it would loose
+		 * the nm-generated flag after restart/reload, and that cannot be right. If a profile
+		 * ends up on disk, the information who created it gets lost. */
+		nm_assert (!NM_FLAGS_ANY (sett_flags,   NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
+		                                      | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE));
+		sett_mask |=   NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
+		             | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE;
+		sett_flags &= ~(  NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
+		                | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE);
+	}
+
+	if (persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_NO_PERSIST) {
+		new_storage = g_object_ref (cur_storage);
+		new_connection_real = connection;
+		_LOGT ("update[%s]: %s: update profile \"%s\" (not persisted)",
+		       nm_settings_storage_get_uuid (cur_storage),
+		       log_context_name,
+		       nm_connection_get_id (connection));
+	} else {
+		NMSettingsStorage *shadowed_storage;
+		const char *cur_shadowed_storage_filename;
+		const char *new_shadowed_storage_filename = NULL;
+		gboolean cur_shadowed_owned;
+		gboolean new_shadowed_owned = FALSE;
+		NMSettingsStorage *update_storage = NULL;
+		gs_free_error GError *local = NULL;
+		gboolean success;
+
+		cur_shadowed_storage_filename = nm_settings_storage_get_shadowed_storage (cur_storage, &cur_shadowed_owned);
+
+		shadowed_storage = _sett_conn_entry_find_shadowed_storage (sett_conn_entry, cur_shadowed_storage_filename, cur_storage);
+		if (!shadowed_storage) {
+			cur_shadowed_storage_filename = NULL;
+			cur_shadowed_owned = FALSE;
+		}
+
+		if (   new_in_memory
+		    && persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY) {
+			if (cur_in_memory) {
+				drop_storage = shadowed_storage;
+				update_storage = cur_storage;
+			} else
+				drop_storage = cur_storage;
+		} else if (   !new_in_memory
+		           && cur_in_memory
+		           && shadowed_storage) {
+			drop_storage = cur_storage;
+			update_storage = shadowed_storage;
+		} else if (new_in_memory != cur_in_memory) {
+			if (!new_in_memory)
+				drop_storage = cur_storage;
+			else if (persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY)
+				drop_storage = cur_storage;
+			else {
+				nm_assert (NM_IN_SET (persist_mode, NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY,
+				                                    NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED));
+			}
+		} else if (nm_settings_storage_is_keyfile_lib (cur_storage)) {
+			/* the profile is a keyfile in /usr/lib. It cannot be overwritten, we must migrate it
+			 * from /usr/lib to /etc. */
+		} else
+			update_storage = cur_storage;
+
+		if (new_in_memory) {
+			if (persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY) {
+				/* pass */
+			} else if (!cur_in_memory) {
+				new_shadowed_storage_filename = nm_settings_storage_get_filename_for_shadowed_storage (cur_storage);
+				if (   new_shadowed_storage_filename
+				    && persist_mode != NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED)
+					new_shadowed_owned = TRUE;
+			} else {
+				new_shadowed_storage_filename = cur_shadowed_storage_filename;
+				if (   new_shadowed_storage_filename
+				    && persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY)
+					new_shadowed_owned = TRUE;
+			}
+		}
+
+		if (!update_storage) {
+			success = _add_connection_to_first_plugin (self,
+			                                           sett_conn_entry,
+			                                           connection,
+			                                           new_in_memory,
+			                                           sett_flags,
+			                                           new_shadowed_storage_filename,
+			                                           new_shadowed_owned,
+			                                           &new_storage,
+			                                           &new_connection,
+			                                           &local);
+		} else {
+			success = _update_connection_to_plugin (self,
+			                                        update_storage,
+			                                        connection,
+			                                        sett_flags,
+			                                        update_reason,
+			                                        new_shadowed_storage_filename,
+			                                        new_shadowed_owned,
+			                                        &new_storage,
+			                                        &new_connection,
+			                                        &local);
+		}
+		if (!success) {
+			gboolean ignore_failure;
+
+			ignore_failure = NM_FLAGS_ANY (update_reason, NM_SETTINGS_CONNECTION_UPDATE_REASON_IGNORE_PERSIST_FAILURE);
+
+			_LOGT ("update[%s]: %s: %sfailure to %s connection \"%s\" on storage: %s",
+			       nm_settings_storage_get_uuid (cur_storage),
+			       log_context_name,
+			       ignore_failure ? "ignore " : "",
+			       update_storage ? "update" : "write",
+			       nm_connection_get_id (connection),
+			       local->message);
+			if (!ignore_failure) {
+				g_set_error (error,
+				             NM_SETTINGS_ERROR,
+				             NM_SETTINGS_ERROR_INVALID_CONNECTION,
+				             "failed to %s connection: %s",
+				             update_storage ? "update" : "write",
+				             local->message);
+				return FALSE;
+			}
+
+			new_storage = g_object_ref (cur_storage);
+			new_connection_real = connection;
+		} else {
+			gs_unref_variant GVariant *agent_owned_secrets = NULL;
+
+			_LOGT ("update[%s]: %s: %s profile \"%s\"",
+			       nm_settings_storage_get_uuid (cur_storage),
+			       log_context_name,
+			       update_storage ? "update" : "write",
+			       nm_connection_get_id (connection));
+
+			nm_assert_valid_settings_storage (NULL, new_storage);
+			nm_assert (NM_IS_CONNECTION (new_connection));
+			nm_assert (nm_streq (uuid, nm_settings_storage_get_uuid (new_storage)));
+
+
+			agent_owned_secrets = nm_connection_to_dbus (connection,
+			                                               NM_CONNECTION_SERIALIZE_ONLY_SECRETS
+			                                             | NM_CONNECTION_SERIALIZE_WITH_SECRETS_AGENT_OWNED);
+			new_connection_real = _connection_changed_normalize_connection (new_storage,
+			                                                                new_connection,
+			                                                                agent_owned_secrets,
+			                                                                &new_connection_cloned);
+			if (!new_connection_real) {
+				nm_assert_not_reached ();
+				new_connection_real = new_connection;
+			}
+		}
+	}
+
+	nm_assert (NM_IS_SETTINGS_STORAGE (new_storage));
+	nm_assert (NM_IS_CONNECTION (new_connection_real));
+
+	_connection_changed_track (self, new_storage, new_connection_real, TRUE);
+
+	if (   drop_storage
+	    && drop_storage != new_storage) {
+		gs_free_error GError *local = NULL;
+
+		if (!nm_settings_plugin_delete_connection (nm_settings_storage_get_plugin (drop_storage),
+		                                           drop_storage,
+		                                           &local)) {
+			const char *filename;
+
+			filename = nm_settings_storage_get_filename (drop_storage);
+			_LOGT ("update[%s]: failed to delete moved storage "NM_SETTINGS_STORAGE_PRINT_FMT"%s%s%s: %s",
+			       nm_settings_storage_get_uuid (drop_storage),
+			       NM_SETTINGS_STORAGE_PRINT_ARG (drop_storage),
+			       NM_PRINT_FMT_QUOTED (filename, " (file \"", filename, "\")", ""),
+			       local->message);
+			/* there is no aborting back form this. We must get rid of the connection and
+			 * cannot do better than log a message. Proceed, but remember to write tombstones. */
+			if (nm_settings_storage_is_keyfile_run (cur_storage))
+				tombstone_in_memory = TRUE;
+			else
+				tombstone_on_disk = TRUE;
+		} else
+			_connection_changed_track (self, drop_storage, NULL, FALSE);
+	}
+
+	_set_nmmeta_tombstone (self,
+	                       uuid,
+	                       tombstone_on_disk,
+	                       tombstone_in_memory,
+	                       NULL);
+
+	_connection_changed_process_all_dirty (self,
+	                                       FALSE,
+	                                       sett_flags,
+	                                       sett_mask,
+	                                       FALSE,
+	                                       update_reason);
+
+	return TRUE;
 }
 
+void
+nm_settings_delete_connection (NMSettings *self,
+                               NMSettingsConnection *sett_conn,
+                               gboolean allow_add_to_no_auto_default)
+{
+	NMSettingsStorage *cur_storage;
+	NMSettingsStorage *shadowed_storage;
+	NMSettingsStorage *shadowed_storage_unowned = NULL;
+	NMSettingsStorage *drop_storages[2] = { };
+	gs_free_error GError *local = NULL;
+	SettConnEntry *sett_conn_entry;
+	const char *cur_shadowed_storage_filename;
+	const char *new_shadowed_storage_filename = NULL;
+	gboolean cur_shadowed_owned;
+	const char *uuid;
+	gboolean tombstone_in_memory = FALSE;
+	gboolean tombstone_on_disk = FALSE;
+	int i;
+
+	g_return_if_fail (NM_IS_SETTINGS (self));
+	g_return_if_fail (NM_IS_SETTINGS_CONNECTION (sett_conn));
+	g_return_if_fail (nm_settings_has_connection (self, sett_conn));
+
+	cur_storage = nm_settings_connection_get_storage (sett_conn);
+
+	nm_assert (NM_IS_SETTINGS_STORAGE (cur_storage));
+
+	uuid = nm_settings_storage_get_uuid (cur_storage);
+	nm_assert (nm_utils_is_uuid (uuid));
+
+	sett_conn_entry = _sett_conn_entries_get (self, uuid);
+
+	g_return_if_fail (sett_conn_entry);
+	nm_assert (sett_conn_entry->sett_conn == sett_conn);
+	g_return_if_fail (sett_conn_entry->storage == cur_storage);
+
+	if (NMS_IS_KEYFILE_STORAGE (cur_storage)) {
+		NMSKeyfileStorage *s = NMS_KEYFILE_STORAGE (cur_storage);
+
+		if (NM_IN_SET (s->storage_type, NMS_KEYFILE_STORAGE_TYPE_RUN,
+		                                NMS_KEYFILE_STORAGE_TYPE_ETC))
+			drop_storages[0] = cur_storage;
+		else
+			tombstone_on_disk = TRUE;
+	} else
+		drop_storages[0] = cur_storage;
+
+	cur_shadowed_storage_filename = nm_settings_storage_get_shadowed_storage (cur_storage, &cur_shadowed_owned);
+
+	shadowed_storage = _sett_conn_entry_find_shadowed_storage (sett_conn_entry, cur_shadowed_storage_filename, cur_storage);
+	if (shadowed_storage) {
+		if (!cur_shadowed_owned)
+			shadowed_storage_unowned = g_steal_pointer (&shadowed_storage);
+	}
+	drop_storages[1] = shadowed_storage;
+
+	for (i = 0; i < (int) G_N_ELEMENTS (drop_storages); i++) {
+		NMSettingsStorage *storage;
+		StorageData *sd;
+
+		storage = drop_storages[i];
+		if (!storage)
+			continue;
+
+		if (!nm_settings_plugin_delete_connection (nm_settings_storage_get_plugin (storage),
+		                                           storage,
+		                                           &local)) {
+			_LOGT ("delete-connection: failed to delete storage "NM_SETTINGS_STORAGE_PRINT_FMT": %s",
+			       NM_SETTINGS_STORAGE_PRINT_ARG (storage),
+			       local->message);
+			g_clear_error (&local);
+			/* there is no aborting back form this. We must get rid of the connection and
+			 * cannot do better than log a message. Proceed, but remember to write tombstones. */
+			if (nm_settings_storage_is_keyfile_run (cur_storage))
+				tombstone_in_memory = TRUE;
+			else
+				tombstone_on_disk = TRUE;
+			sett_conn_entry = _sett_conn_entries_get (self, uuid);
+		} else
+			sett_conn_entry = _connection_changed_track (self, storage, NULL, FALSE);
+
+		c_list_for_each_entry (sd, &sett_conn_entry->sd_lst_head, sd_lst) {
+			if (NM_IN_SET (sd->storage, drop_storages[0],
+			                            drop_storages[1]))
+				continue;
+			if (!_storage_data_is_alive (sd))
+				continue;
+			if (nm_settings_storage_is_meta_data (sd->storage))
+				continue;
+
+			if (sd->storage == shadowed_storage_unowned) {
+				/* this only happens if we leak a profile on disk after NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED.
+				 * We need to write a tombstone and remember the shadowed-storage. */
+				tombstone_in_memory = TRUE;
+				new_shadowed_storage_filename = nm_settings_storage_get_filename (shadowed_storage_unowned);
+				continue;
+			}
+
+			/* we have still conflicting storages. We need to hide them with tombstones. */
+			if (nm_settings_storage_is_keyfile_run (sd->storage)) {
+				tombstone_in_memory = TRUE;
+				continue;
+			}
+			tombstone_on_disk = TRUE;
+		}
+	}
+
+	_set_nmmeta_tombstone (self,
+	                       uuid,
+	                       tombstone_on_disk,
+	                       tombstone_in_memory,
+	                       new_shadowed_storage_filename);
+
+	_connection_changed_process_all_dirty (self,
+	                                       allow_add_to_no_auto_default,
+	                                       NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
+	                                       NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
+	                                       FALSE,
+	                                       NM_SETTINGS_CONNECTION_UPDATE_REASON_NONE);
+}
+
+/*****************************************************************************/
+
 static void
 send_agent_owned_secrets (NMSettings *self,
                           NMSettingsConnection *sett_conn,
@@ -1118,9 +2386,8 @@ send_agent_owned_secrets (NMSettings *self,
 	 * Only send secrets to agents of the same UID that called update too.
 	 */
 	for_agent = nm_simple_connection_new_clone (nm_settings_connection_get_connection (sett_conn));
-	nm_connection_clear_secrets_with_flags (for_agent,
-	                                        secrets_filter_cb,
-	                                        GUINT_TO_POINTER (NM_SETTING_SECRET_FLAG_AGENT_OWNED));
+	_nm_connection_clear_secrets_by_secret_flags (for_agent,
+	                                              NM_SETTING_SECRET_FLAG_AGENT_OWNED);
 	nm_agent_manager_save_secrets (priv->agent_mgr,
 	                               nm_dbus_object_get_path (NM_DBUS_OBJECT (sett_conn)),
 	                               for_agent,
@@ -1129,52 +2396,50 @@ send_agent_owned_secrets (NMSettings *self,
 
 static void
 pk_add_cb (NMAuthChain *chain,
-           GError *chain_error,
            GDBusMethodInvocation *context,
            gpointer user_data)
 {
 	NMSettings *self = NM_SETTINGS (user_data);
-	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
 	NMAuthCallResult result;
-	GError *error = NULL;
+	gs_free_error GError *error = NULL;
 	NMConnection *connection = NULL;
 	gs_unref_object NMSettingsConnection *added = NULL;
 	NMSettingsAddCallback callback;
 	gpointer callback_data;
 	NMAuthSubject *subject;
 	const char *perm;
-	gboolean save_to_disk;
 
-	g_assert (context);
+	nm_assert (G_IS_DBUS_METHOD_INVOCATION (context));
 
-	priv->auths = g_slist_remove (priv->auths, chain);
+	c_list_unlink (nm_auth_chain_parent_lst_list (chain));
 
 	perm = nm_auth_chain_get_data (chain, "perm");
-	g_assert (perm);
+	nm_assert (perm);
+
 	result = nm_auth_chain_get_result (chain, perm);
 
-	if (chain_error) {
-		error = g_error_new (NM_SETTINGS_ERROR,
-		                     NM_SETTINGS_ERROR_FAILED,
-		                     "Error checking authorization: %s",
-		                     chain_error->message);
-	} else if (result != NM_AUTH_CALL_RESULT_YES) {
+	if (result != NM_AUTH_CALL_RESULT_YES) {
 		error = g_error_new_literal (NM_SETTINGS_ERROR,
 		                             NM_SETTINGS_ERROR_PERMISSION_DENIED,
 		                             "Insufficient privileges.");
 	} else {
 		/* Authorized */
 		connection = nm_auth_chain_get_data (chain, "connection");
-		nm_assert (connection);
+		nm_assert (NM_IS_CONNECTION (connection));
 
-		save_to_disk = GPOINTER_TO_UINT (nm_auth_chain_get_data (chain, "save-to-disk"));
-		added = nm_settings_add_connection (self, connection, save_to_disk, &error);
+		nm_settings_add_connection (self,
+		                            connection,
+		                            GPOINTER_TO_UINT (nm_auth_chain_get_data (chain, "persist-mode")),
+		                            GPOINTER_TO_UINT (nm_auth_chain_get_data (chain, "add-reason")),
+		                            GPOINTER_TO_UINT (nm_auth_chain_get_data (chain, "sett-flags")),
+		                            &added,
+		                            &error);
 
 		/* The callback may remove the connection from the settings manager (e.g.
 		 * because it's found to be incompatible with the device on AddAndActivate).
 		 * But we need to keep it alive for a bit longer, precisely to check wehther
 		 * it's still known to the setting manager. */
-		g_object_ref (added);
+		nm_g_object_ref (added);
 	}
 
 	callback = nm_auth_chain_get_data (chain, "callback");
@@ -1184,49 +2449,17 @@ pk_add_cb (NMAuthChain *chain,
 	callback (self, added, error, context, subject, callback_data);
 
 	/* Send agent-owned secrets to the agents */
-	if (!error && added && nm_settings_has_connection (self, added))
+	if (   added
+	    && nm_settings_has_connection (self, added))
 		send_agent_owned_secrets (self, added, subject);
-
-	g_clear_error (&error);
-	nm_auth_chain_destroy (chain);
-}
-
-/* FIXME: remove if/when kernel supports adhoc wpa */
-static gboolean
-is_adhoc_wpa (NMConnection *connection)
-{
-	NMSettingWireless *s_wifi;
-	NMSettingWirelessSecurity *s_wsec;
-	const char *mode, *key_mgmt;
-
-	/* The kernel doesn't support Ad-Hoc WPA connections well at this time,
-	 * and turns them into open networks.  It's been this way since at least
-	 * 2.6.30 or so; until that's fixed, disable WPA-protected Ad-Hoc networks.
-	 */
-
-	s_wifi = nm_connection_get_setting_wireless (connection);
-	if (!s_wifi)
-		return FALSE;
-
-	mode = nm_setting_wireless_get_mode (s_wifi);
-	if (g_strcmp0 (mode, NM_SETTING_WIRELESS_MODE_ADHOC) != 0)
-		return FALSE;
-
-	s_wsec = nm_connection_get_setting_wireless_security (connection);
-	if (!s_wsec)
-		return FALSE;
-
-	key_mgmt = nm_setting_wireless_security_get_key_mgmt (s_wsec);
-	if (g_strcmp0 (key_mgmt, "wpa-none") != 0)
-		return FALSE;
-
-	return TRUE;
 }
 
 void
 nm_settings_add_connection_dbus (NMSettings *self,
                                  NMConnection *connection,
-                                 gboolean save_to_disk,
+                                 NMSettingsConnectionPersistMode persist_mode,
+                                 NMSettingsConnectionAddReason add_reason,
+                                 NMSettingsConnectionIntFlags sett_flags,
                                  NMAuthSubject *subject,
                                  GDBusMethodInvocation *context,
                                  NMSettingsAddCallback callback,
@@ -1242,8 +2475,10 @@ nm_settings_add_connection_dbus (NMSettings *self,
 	g_return_if_fail (NM_IS_AUTH_SUBJECT (subject));
 	g_return_if_fail (G_IS_DBUS_METHOD_INVOCATION (context));
 
+	nm_assert (!NM_FLAGS_ANY (sett_flags, ~_NM_SETTINGS_CONNECTION_INT_FLAGS_PERSISTENT_MASK));
+
 	/* Connection must be valid, of course */
-	if (!nm_connection_verify (connection, &tmp_error)) {
+	if (_nm_connection_verify (connection, &tmp_error) != NM_SETTING_VERIFY_SUCCESS) {
 		error = g_error_new (NM_SETTINGS_ERROR,
 		                     NM_SETTINGS_ERROR_INVALID_CONNECTION,
 		                     "The connection was invalid: %s",
@@ -1252,11 +2487,11 @@ nm_settings_add_connection_dbus (NMSettings *self,
 		goto done;
 	}
 
-	/* The kernel doesn't support Ad-Hoc WPA connections well at this time,
+	/* FIXME: The kernel doesn't support Ad-Hoc WPA connections well at this time,
 	 * and turns them into open networks.  It's been this way since at least
 	 * 2.6.30 or so; until that's fixed, disable WPA-protected Ad-Hoc networks.
 	 */
-	if (is_adhoc_wpa (connection)) {
+	if (nm_utils_connection_is_adhoc_wpa (connection)) {
 		error = g_error_new_literal (NM_SETTINGS_ERROR,
 		                             NM_SETTINGS_ERROR_INVALID_CONNECTION,
 		                             "WPA Ad-Hoc disabled due to kernel bugs");
@@ -1275,7 +2510,7 @@ nm_settings_add_connection_dbus (NMSettings *self,
 	 * request affects more than just the caller, require 'modify.system'.
 	 */
 	s_con = nm_connection_get_setting_connection (connection);
-	g_assert (s_con);
+	nm_assert (s_con);
 	if (nm_setting_connection_get_num_permissions (s_con) == 1)
 		perm = NM_AUTH_PERMISSION_SETTINGS_MODIFY_OWN;
 	else
@@ -1290,14 +2525,17 @@ nm_settings_add_connection_dbus (NMSettings *self,
 		goto done;
 	}
 
-	priv->auths = g_slist_append (priv->auths, chain);
+	c_list_link_tail (&priv->auth_lst_head, nm_auth_chain_parent_lst_list (chain));
+
 	nm_auth_chain_set_data (chain, "perm", (gpointer) perm, NULL);
 	nm_auth_chain_set_data (chain, "connection", g_object_ref (connection), g_object_unref);
 	nm_auth_chain_set_data (chain, "callback", callback, NULL);
 	nm_auth_chain_set_data (chain, "callback-data", user_data, NULL);
 	nm_auth_chain_set_data (chain, "subject", g_object_ref (subject), g_object_unref);
-	nm_auth_chain_set_data (chain, "save-to-disk", GUINT_TO_POINTER (save_to_disk), NULL);
-	nm_auth_chain_add_call (chain, perm, TRUE);
+	nm_auth_chain_set_data (chain, "persist-mode", GUINT_TO_POINTER (persist_mode), NULL);
+	nm_auth_chain_set_data (chain, "add-reason", GUINT_TO_POINTER (add_reason), NULL);
+	nm_auth_chain_set_data (chain, "sett-flags", GUINT_TO_POINTER (sett_flags), NULL);
+	nm_auth_chain_add_call_unsafe (chain, perm, TRUE);
 	return;
 
 done:
@@ -1314,27 +2552,42 @@ settings_add_connection_add_cb (NMSettings *self,
                                 NMAuthSubject *subject,
                                 gpointer user_data)
 {
+	gboolean is_add_connection_2 = GPOINTER_TO_INT (user_data);
+
 	if (error) {
 		g_dbus_method_invocation_return_gerror (context, error);
 		nm_audit_log_connection_op (NM_AUDIT_OP_CONN_ADD, NULL, FALSE, NULL, subject, error->message);
+		return;
+	}
+
+	if (is_add_connection_2) {
+		GVariantBuilder builder;
+
+		g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
+		g_dbus_method_invocation_return_value (context,
+		                                       g_variant_new ("(oa{sv})",
+		                                                      nm_dbus_object_get_path (NM_DBUS_OBJECT (connection)),
+		                                                      &builder));
 	} else {
 		g_dbus_method_invocation_return_value (context,
 		                                       g_variant_new ("(o)",
 		                                                      nm_dbus_object_get_path (NM_DBUS_OBJECT (connection))));
-		nm_audit_log_connection_op (NM_AUDIT_OP_CONN_ADD, connection, TRUE, NULL,
-		                            subject, NULL);
 	}
+	nm_audit_log_connection_op (NM_AUDIT_OP_CONN_ADD, connection, TRUE, NULL,
+	                            subject, NULL);
 }
 
 static void
 settings_add_connection_helper (NMSettings *self,
                                 GDBusMethodInvocation *context,
+                                gboolean is_add_connection_2,
                                 GVariant *settings,
-                                gboolean save_to_disk)
+                                NMSettingsAddConnection2Flags flags)
 {
 	gs_unref_object NMConnection *connection = NULL;
 	GError *error = NULL;
 	gs_unref_object NMAuthSubject *subject = NULL;
+	NMSettingsConnectionPersistMode persist_mode;
 
 	connection = _nm_simple_connection_new_from_dbus (settings,
 	                                                    NM_SETTING_PARSE_FLAGS_STRICT
@@ -1356,13 +2609,24 @@ settings_add_connection_helper (NMSettings *self,
 		return;
 	}
 
+	if (NM_FLAGS_HAS (flags, NM_SETTINGS_ADD_CONNECTION2_FLAG_TO_DISK))
+		persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK;
+	else {
+		nm_assert (NM_FLAGS_HAS (flags, NM_SETTINGS_ADD_CONNECTION2_FLAG_IN_MEMORY));
+		persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY;
+	}
+
 	nm_settings_add_connection_dbus (self,
 	                                 connection,
-	                                 save_to_disk,
+	                                 persist_mode,
+	                                   NM_FLAGS_HAS (flags, NM_SETTINGS_ADD_CONNECTION2_FLAG_BLOCK_AUTOCONNECT)
+	                                 ? NM_SETTINGS_CONNECTION_ADD_REASON_BLOCK_AUTOCONNECT
+	                                 : NM_SETTINGS_CONNECTION_ADD_REASON_NONE,
+	                                 NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
 	                                 subject,
 	                                 context,
 	                                 settings_add_connection_add_cb,
-	                                 NULL);
+	                                 GINT_TO_POINTER (!!is_add_connection_2));
 }
 
 static void
@@ -1378,7 +2642,7 @@ impl_settings_add_connection (NMDBusObject *obj,
 	gs_unref_variant GVariant *settings = NULL;
 
 	g_variant_get (parameters, "(@a{sa{sv}})", &settings);
-	settings_add_connection_helper (self, invocation, settings, TRUE);
+	settings_add_connection_helper (self, invocation, FALSE, settings, NM_SETTINGS_ADD_CONNECTION2_FLAG_TO_DISK);
 }
 
 static void
@@ -1394,14 +2658,79 @@ impl_settings_add_connection_unsaved (NMDBusObject *obj,
 	gs_unref_variant GVariant *settings = NULL;
 
 	g_variant_get (parameters, "(@a{sa{sv}})", &settings);
-	settings_add_connection_helper (self, invocation, settings, FALSE);
+	settings_add_connection_helper (self, invocation, FALSE, settings, NM_SETTINGS_ADD_CONNECTION2_FLAG_IN_MEMORY);
+}
+
+static void
+impl_settings_add_connection2 (NMDBusObject *obj,
+                               const NMDBusInterfaceInfoExtended *interface_info,
+                               const NMDBusMethodInfoExtended *method_info,
+                               GDBusConnection *connection,
+                               const char *sender,
+                               GDBusMethodInvocation *invocation,
+                               GVariant *parameters)
+{
+	NMSettings *self = NM_SETTINGS (obj);
+	gs_unref_variant GVariant *settings = NULL;
+	gs_unref_variant GVariant *args = NULL;
+	NMSettingsAddConnection2Flags flags;
+	const char *args_name;
+	GVariantIter iter;
+	guint32 flags_u;
+
+	g_variant_get (parameters, "(@a{sa{sv}}u@a{sv})", &settings, &flags_u, &args);
+
+	if (NM_FLAGS_ANY (flags_u, ~((guint32) (  NM_SETTINGS_ADD_CONNECTION2_FLAG_TO_DISK
+	                                        | NM_SETTINGS_ADD_CONNECTION2_FLAG_IN_MEMORY
+	                                        | NM_SETTINGS_ADD_CONNECTION2_FLAG_BLOCK_AUTOCONNECT)))) {
+		g_dbus_method_invocation_take_error (invocation,
+		                                     g_error_new_literal (NM_SETTINGS_ERROR,
+		                                                          NM_SETTINGS_ERROR_INVALID_ARGUMENTS,
+		                                                          "Unknown flags"));
+		return;
+	}
+
+	flags = flags_u;
+
+	if (!NM_FLAGS_ANY (flags,   NM_SETTINGS_ADD_CONNECTION2_FLAG_TO_DISK
+	                          | NM_SETTINGS_ADD_CONNECTION2_FLAG_IN_MEMORY)) {
+		g_dbus_method_invocation_take_error (invocation,
+		                                     g_error_new_literal (NM_SETTINGS_ERROR,
+		                                                          NM_SETTINGS_ERROR_INVALID_ARGUMENTS,
+		                                                          "Requires either to-disk (0x1) or in-memory (0x2) flags"));
+		return;
+	}
+
+	if (NM_FLAGS_ALL (flags,   NM_SETTINGS_ADD_CONNECTION2_FLAG_TO_DISK
+	                         | NM_SETTINGS_ADD_CONNECTION2_FLAG_IN_MEMORY)) {
+		g_dbus_method_invocation_take_error (invocation,
+		                                     g_error_new_literal (NM_SETTINGS_ERROR,
+		                                                          NM_SETTINGS_ERROR_INVALID_ARGUMENTS,
+		                                                          "Cannot set to-disk (0x1) and in-memory (0x2) flags together"));
+		return;
+	}
+
+	nm_assert (g_variant_is_of_type (args, G_VARIANT_TYPE ("a{sv}")));
+
+	g_variant_iter_init (&iter, args);
+	while (g_variant_iter_next (&iter, "{&sv}", &args_name, NULL)) {
+		g_dbus_method_invocation_take_error (invocation,
+		                                     g_error_new (NM_SETTINGS_ERROR,
+		                                                  NM_SETTINGS_ERROR_INVALID_ARGUMENTS,
+		                                                  "Unsupported argument '%s'", args_name));
+		return;
+	}
+
+	settings_add_connection_helper (self, invocation, TRUE, settings, flags);
 }
 
+/*****************************************************************************/
+
 static void
 impl_settings_load_connections (NMDBusObject *obj,
                                 const NMDBusInterfaceInfoExtended *interface_info,
                                 const NMDBusMethodInfoExtended *method_info,
-                                GDBusConnection *connection,
+                                GDBusConnection *dbus_connection,
                                 const char *sender,
                                 GDBusMethodInvocation *invocation,
                                 GVariant *parameters)
@@ -1409,9 +2738,8 @@ impl_settings_load_connections (NMDBusObject *obj,
 	NMSettings *self = NM_SETTINGS (obj);
 	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
 	gs_unref_ptrarray GPtrArray *failures = NULL;
-	GSList *iter;
-	guint i;
 	gs_free const char **filenames = NULL;
+	gs_free char *op_result_str = NULL;
 
 	g_variant_get (parameters, "(^a&s)", &filenames);
 
@@ -1426,31 +2754,68 @@ impl_settings_load_connections (NMDBusObject *obj,
 	                                 NM_SETTINGS_ERROR_PERMISSION_DENIED))
 		return;
 
-	if (filenames) {
-		for (i = 0; filenames[i]; i++) {
-			for (iter = priv->plugins; iter; iter = g_slist_next (iter)) {
-				NMSettingsPlugin *plugin = NM_SETTINGS_PLUGIN (iter->data);
+	if (   filenames
+	    && filenames[0]) {
+		NMSettingsPluginConnectionLoadEntry *entries;
+		gsize n_entries;
+		gsize i;
+		GSList *iter;
 
-				if (nm_settings_plugin_load_connection (plugin, filenames[i]))
-					break;
-			}
+		entries = nm_settings_plugin_create_connection_load_entries (filenames, &n_entries);
 
-			if (!iter) {
-				if (!g_path_is_absolute (filenames[i]))
-					_LOGW ("connection filename '%s' is not an absolute path", filenames[i]);
-				if (!failures)
-					failures = g_ptr_array_new ();
-				g_ptr_array_add (failures, (char *) filenames[i]);
-			}
+		for (iter = priv->plugins; iter; iter = iter->next) {
+			NMSettingsPlugin *plugin = iter->data;
+
+			nm_settings_plugin_load_connections (plugin,
+			                                     entries,
+			                                     n_entries,
+			                                     _plugin_connections_reload_cb,
+			                                     self);
+		}
+
+		for (i = 0; i < n_entries; i++) {
+			NMSettingsPluginConnectionLoadEntry *entry = &entries[i];
+
+			if (!entry->handled)
+				_LOGW ("load: no settings plugin could load \"%s\"", entry->filename);
+			else if (entry->error) {
+				_LOGW ("load: failure to load \"%s\": %s", entry->filename, entry->error->message);
+				g_clear_error (&entry->error);
+			} else
+				continue;
+
+			if (!failures)
+				failures = g_ptr_array_new ();
+			g_ptr_array_add (failures, (char *) entry->filename);
 		}
+
+		nm_clear_g_free (&entries);
+
+		_connection_changed_process_all_dirty (self,
+		                                       TRUE,
+		                                       NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
+		                                       NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
+		                                       TRUE,
+		                                         NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_SYSTEM_SECRETS
+		                                       | NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_AGENT_SECRETS);
+
+		for (iter = priv->plugins; iter; iter = iter->next)
+			nm_settings_plugin_load_connections_done (iter->data);
 	}
 
 	if (failures)
 		g_ptr_array_add (failures, NULL);
 
+	nm_audit_log_connection_op (NM_AUDIT_OP_CONNS_LOAD,
+	                            NULL,
+	                            !failures,
+	                            (op_result_str = g_strjoinv (",", (char **) filenames)),
+	                            invocation,
+	                            NULL);
+
 	g_dbus_method_invocation_return_value (invocation,
 	                                       g_variant_new ("(b^as)",
-	                                                      (gboolean) (!!failures),
+	                                                      (gboolean) (!failures),
 	                                                      failures
 	                                                        ? (const char **) failures->pdata
 	                                                        : NM_PTRARRAY_EMPTY (const char *)));
@@ -1466,8 +2831,6 @@ impl_settings_reload_connections (NMDBusObject *obj,
                                   GVariant *parameters)
 {
 	NMSettings *self = NM_SETTINGS (obj);
-	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
-	GSList *iter;
 
 	/* The permission is already enforced by the D-Bus daemon, but we ensure
 	 * that the caller is still alive so that clients are forced to wait and
@@ -1480,11 +2843,9 @@ impl_settings_reload_connections (NMDBusObject *obj,
 	                                 NM_SETTINGS_ERROR_PERMISSION_DENIED))
 		return;
 
-	for (iter = priv->plugins; iter; iter = g_slist_next (iter)) {
-		NMSettingsPlugin *plugin = NM_SETTINGS_PLUGIN (iter->data);
+	_plugin_connections_reload (self);
 
-		nm_settings_plugin_reload_connections (plugin);
-	}
+	nm_audit_log_connection_op (NM_AUDIT_OP_CONNS_RELOAD, NULL, TRUE, NULL, invocation, NULL);
 
 	g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", TRUE));
 }
@@ -1492,8 +2853,427 @@ impl_settings_reload_connections (NMDBusObject *obj,
 /*****************************************************************************/
 
 static void
+_clear_connections_cached_list (NMSettingsPrivate *priv)
+{
+	if (!priv->connections_cached_list)
+		return;
+
+	nm_assert (priv->connections_len == NM_PTRARRAY_LEN (priv->connections_cached_list));
+
+#if NM_MORE_ASSERTS
+	/* set the pointer to a bogus value. This makes it more apparent
+	 * if somebody has a reference to the cached list and still uses
+	 * it. That is a bug, this code just tries to make it blow up
+	 * more eagerly. */
+	memset (priv->connections_cached_list,
+	        0xdeaddead,
+	        sizeof (NMSettingsConnection *) * (priv->connections_len + 1));
+#endif
+
+	nm_clear_g_free (&priv->connections_cached_list);
+}
+
+static void
+impl_settings_list_connections (NMDBusObject *obj,
+                                const NMDBusInterfaceInfoExtended *interface_info,
+                                const NMDBusMethodInfoExtended *method_info,
+                                GDBusConnection *dbus_connection,
+                                const char *sender,
+                                GDBusMethodInvocation *invocation,
+                                GVariant *parameters)
+{
+	NMSettings *self = NM_SETTINGS (obj);
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	gs_free const char **strv = NULL;
+
+	strv = nm_dbus_utils_get_paths_for_clist (&priv->connections_lst_head,
+	                                          priv->connections_len,
+	                                          G_STRUCT_OFFSET (NMSettingsConnection, _connections_lst),
+	                                          TRUE);
+	g_dbus_method_invocation_return_value (invocation,
+	                                       g_variant_new ("(^ao)", strv));
+}
+
+NMSettingsConnection *
+nm_settings_get_connection_by_uuid (NMSettings *self, const char *uuid)
+{
+	g_return_val_if_fail (NM_IS_SETTINGS (self), NULL);
+	g_return_val_if_fail (uuid != NULL, NULL);
+
+	return _sett_conn_entry_get_conn (_sett_conn_entries_get (self, uuid));
+}
+
+const char *
+nm_settings_get_dbus_path_for_uuid (NMSettings *self,
+                                    const char *uuid)
+{
+	NMSettingsConnection *sett_conn;
+
+	sett_conn = nm_settings_get_connection_by_uuid (self, uuid);
+
+	if (!sett_conn)
+		return NULL;
+
+	return nm_dbus_object_get_path (NM_DBUS_OBJECT (sett_conn));
+}
+
+static void
+impl_settings_get_connection_by_uuid (NMDBusObject *obj,
+                                      const NMDBusInterfaceInfoExtended *interface_info,
+                                      const NMDBusMethodInfoExtended *method_info,
+                                      GDBusConnection *dbus_connection,
+                                      const char *sender,
+                                      GDBusMethodInvocation *invocation,
+                                      GVariant *parameters)
+{
+	NMSettings *self = NM_SETTINGS (obj);
+	NMSettingsConnection *sett_conn;
+	gs_unref_object NMAuthSubject *subject = NULL;
+	GError *error = NULL;
+	const char *uuid;
+
+	g_variant_get (parameters, "(&s)", &uuid);
+
+	sett_conn = nm_settings_get_connection_by_uuid (self, uuid);
+	if (!sett_conn) {
+		error = g_error_new_literal (NM_SETTINGS_ERROR,
+		                             NM_SETTINGS_ERROR_INVALID_CONNECTION,
+		                             "No connection with the UUID was found.");
+		goto error;
+	}
+
+	subject = nm_auth_subject_new_unix_process_from_context (invocation);
+	if (!subject) {
+		error = g_error_new_literal (NM_SETTINGS_ERROR,
+		                             NM_SETTINGS_ERROR_PERMISSION_DENIED,
+		                             "Unable to determine UID of request.");
+		goto error;
+	}
+
+	if (!nm_auth_is_subject_in_acl_set_error (nm_settings_connection_get_connection (sett_conn),
+	                                          subject,
+	                                          NM_SETTINGS_ERROR,
+	                                          NM_SETTINGS_ERROR_PERMISSION_DENIED,
+	                                          &error))
+		goto error;
+
+	g_dbus_method_invocation_return_value (invocation,
+	                                       g_variant_new ("(o)",
+	                                                      nm_dbus_object_get_path (NM_DBUS_OBJECT (sett_conn))));
+	return;
+
+error:
+	g_dbus_method_invocation_take_error (invocation, error);
+}
+
+/**
+ * nm_settings_get_connections:
+ * @self: the #NMSettings
+ * @out_len: (out) (allow-none): returns the number of returned
+ *   connections.
+ *
+ * Returns: (transfer none): a list of NMSettingsConnections. The list is
+ * unsorted and NULL terminated. The result is never %NULL, in case of no
+ * connections, it returns an empty list.
+ * The returned list is cached internally, only valid until the next
+ * NMSettings operation.
+ */
+NMSettingsConnection *const*
+nm_settings_get_connections (NMSettings *self, guint *out_len)
+{
+	NMSettingsPrivate *priv;
+	NMSettingsConnection **v;
+	NMSettingsConnection *con;
+	guint i;
+
+	g_return_val_if_fail (NM_IS_SETTINGS (self), NULL);
+
+	priv = NM_SETTINGS_GET_PRIVATE (self);
+
+	nm_assert (priv->connections_len == c_list_length (&priv->connections_lst_head));
+
+	if (G_UNLIKELY (!priv->connections_cached_list)) {
+		v = g_new (NMSettingsConnection *, priv->connections_len + 1);
+
+		i = 0;
+		c_list_for_each_entry (con, &priv->connections_lst_head, _connections_lst) {
+			nm_assert (i < priv->connections_len);
+			v[i++] = con;
+		}
+		nm_assert (i == priv->connections_len);
+		v[i] = NULL;
+
+		priv->connections_cached_list = v;
+	}
+
+	NM_SET_OUT (out_len, priv->connections_len);
+	return priv->connections_cached_list;
+}
+
+/**
+ * nm_settings_get_connections_clone:
+ * @self: the #NMSetting
+ * @out_len: (allow-none): optional output argument
+ * @func: caller-supplied function for filtering connections
+ * @func_data: caller-supplied data passed to @func
+ * @sort_compare_func: (allow-none): optional function pointer for
+ *   sorting the returned list.
+ * @sort_data: user data for @sort_compare_func.
+ *
+ * Returns: (transfer container) (element-type NMSettingsConnection):
+ *   an NULL terminated array of #NMSettingsConnection objects that were
+ *   filtered by @func (or all connections if no filter was specified).
+ *   The order is arbitrary.
+ *   Caller is responsible for freeing the returned array with free(),
+ *   the contained values do not need to be unrefed.
+ */
+NMSettingsConnection **
+nm_settings_get_connections_clone (NMSettings *self,
+                                   guint *out_len,
+                                   NMSettingsConnectionFilterFunc func,
+                                   gpointer func_data,
+                                   GCompareDataFunc sort_compare_func,
+                                   gpointer sort_data)
+{
+	NMSettingsConnection *const*list_cached;
+	NMSettingsConnection **list;
+	guint len, i, j;
+
+	g_return_val_if_fail (NM_IS_SETTINGS (self), NULL);
+
+	list_cached = nm_settings_get_connections (self, &len);
+
+#if NM_MORE_ASSERTS
+	nm_assert (list_cached);
+	for (i = 0; i < len; i++)
+		nm_assert (NM_IS_SETTINGS_CONNECTION (list_cached[i]));
+	nm_assert (!list_cached[i]);
+#endif
+
+	list = g_new (NMSettingsConnection *, ((gsize) len + 1));
+	if (func) {
+		for (i = 0, j = 0; i < len; i++) {
+			if (func (self, list_cached[i], func_data))
+				list[j++] = list_cached[i];
+		}
+		list[j] = NULL;
+		len = j;
+	} else
+		memcpy (list, list_cached, sizeof (list[0]) * ((gsize) len + 1));
+
+	if (   len > 1
+	    && sort_compare_func) {
+		g_qsort_with_data (list, len, sizeof (NMSettingsConnection *),
+		                   sort_compare_func, sort_data);
+	}
+	NM_SET_OUT (out_len, len);
+	return list;
+}
+
+NMSettingsConnection *
+nm_settings_get_connection_by_path (NMSettings *self, const char *path)
+{
+	NMSettingsPrivate *priv;
+	NMSettingsConnection *connection;
+
+	g_return_val_if_fail (NM_IS_SETTINGS (self), NULL);
+	g_return_val_if_fail (path, NULL);
+
+	priv = NM_SETTINGS_GET_PRIVATE (self);
+
+	connection = nm_dbus_manager_lookup_object (nm_dbus_object_get_manager (NM_DBUS_OBJECT (self)),
+	                                            path);
+	if (   !connection
+	    || !NM_IS_SETTINGS_CONNECTION (connection))
+		return NULL;
+
+	nm_assert (c_list_contains (&priv->connections_lst_head, &connection->_connections_lst));
+	return connection;
+}
+
+gboolean
+nm_settings_has_connection (NMSettings *self, NMSettingsConnection *connection)
+{
+	gboolean has;
+
+	g_return_val_if_fail (NM_IS_SETTINGS (self), FALSE);
+	g_return_val_if_fail (NM_IS_SETTINGS_CONNECTION (connection), FALSE);
+
+	has = !c_list_is_empty (&connection->_connections_lst);
+
+	nm_assert (has == nm_c_list_contains_entry (&NM_SETTINGS_GET_PRIVATE (self)->connections_lst_head,
+                                                connection,
+                                                _connections_lst));
+	nm_assert (({
+		NMSettingsConnection *candidate = NULL;
+		const char *path;
+
+		path = nm_dbus_object_get_path (NM_DBUS_OBJECT (connection));
+		if (path)
+			candidate = nm_settings_get_connection_by_path (self, path);
+
+		(has == (connection == candidate));
+	}));
+
+	return has;
+}
+
+/*****************************************************************************/
+
+static void
+add_plugin (NMSettings *self,
+            NMSettingsPlugin *plugin,
+            const char *pname,
+            const char *path)
+{
+	NMSettingsPrivate *priv;
+
+	nm_assert (NM_IS_SETTINGS (self));
+	nm_assert (NM_IS_SETTINGS_PLUGIN (plugin));
+
+	nm_assert (pname);
+	nm_assert (nm_streq0 (pname, nm_settings_plugin_get_plugin_name (plugin)));
+
+	priv = NM_SETTINGS_GET_PRIVATE (self);
+
+	nm_assert (!g_slist_find (priv->plugins, plugin));
+
+	priv->plugins = g_slist_append (priv->plugins, g_object_ref (plugin));
+
+	nm_shutdown_wait_obj_register_full (G_OBJECT (plugin),
+	                                    g_strdup_printf ("%s-settings-plugin", pname),
+	                                    TRUE);
+
+	_LOGI ("Loaded settings plugin: %s (%s%s%s)",
+	       pname,
+	       NM_PRINT_FMT_QUOTED (path, "\"", path, "\"", "internal"));
+}
+
+static gboolean
+add_plugin_load_file (NMSettings *self, const char *pname, GError **error)
+{
+	gs_free char *full_name = NULL;
+	gs_free char *path = NULL;
+	gs_unref_object NMSettingsPlugin *plugin = NULL;
+	GModule *module;
+	NMSettingsPluginFactoryFunc factory_func;
+	struct stat st;
+	int errsv;
+
+	full_name = g_strdup_printf ("nm-settings-plugin-%s", pname);
+	path = g_module_build_path (NMPLUGINDIR, full_name);
+
+	if (stat (path, &st) != 0) {
+		errsv = errno;
+		_LOGW ("could not load plugin '%s' from file '%s': %s", pname, path, nm_strerror_native (errsv));
+		return TRUE;
+	}
+	if (!S_ISREG (st.st_mode)) {
+		_LOGW ("could not load plugin '%s' from file '%s': not a file", pname, path);
+		return TRUE;
+	}
+	if (st.st_uid != 0) {
+		_LOGW ("could not load plugin '%s' from file '%s': file must be owned by root", pname, path);
+		return TRUE;
+	}
+	if (st.st_mode & (S_IWGRP | S_IWOTH | S_ISUID)) {
+		_LOGW ("could not load plugin '%s' from file '%s': invalid file permissions", pname, path);
+		return TRUE;
+	}
+
+	module = g_module_open (path, G_MODULE_BIND_LOCAL);
+	if (!module) {
+		_LOGW ("could not load plugin '%s' from file '%s': %s",
+		     pname, path, g_module_error ());
+		return TRUE;
+	}
+
+	/* errors after this point are fatal, because we loaded the shared library already. */
+
+	if (!g_module_symbol (module, "nm_settings_plugin_factory", (gpointer) (&factory_func))) {
+		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
+		             "Could not find plugin '%s' factory function.",
+		             pname);
+		g_module_close (module);
+		return FALSE;
+	}
+
+	/* after accessing the plugin we cannot unload it anymore, because the glib
+	 * types cannot be properly unregistered. */
+	g_module_make_resident (module);
+
+	plugin = (*factory_func) ();
+	if (!NM_IS_SETTINGS_PLUGIN (plugin)) {
+		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
+		             "plugin '%s' returned invalid settings plugin",
+		             pname);
+		return FALSE;
+	}
+
+	add_plugin (self, NM_SETTINGS_PLUGIN (plugin), pname, path);
+	return TRUE;
+}
+
+static void
+add_plugin_keyfile (NMSettings *self)
+{
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+
+	if (priv->keyfile_plugin)
+		return;
+	priv->keyfile_plugin = nms_keyfile_plugin_new ();
+	add_plugin (self, NM_SETTINGS_PLUGIN (priv->keyfile_plugin), "keyfile", NULL);
+}
+
+static gboolean
+load_plugins (NMSettings *self, const char *const*plugins, GError **error)
+{
+	const char *const*iter;
+	gboolean success = TRUE;
+
+	for (iter = plugins; iter && *iter; iter++) {
+		const char *pname = *iter;
+
+		if (!*pname || strchr (pname, '/')) {
+			_LOGW ("ignore invalid plugin \"%s\"", pname);
+			continue;
+		}
+
+		if (NM_IN_STRSET (pname, "ifcfg-suse", "ifnet", "ibft", "no-ibft")) {
+			_LOGW ("skipping deprecated plugin %s", pname);
+			continue;
+		}
+
+		/* keyfile plugin is built-in now */
+		if (nm_streq (pname, "keyfile")) {
+			add_plugin_keyfile (self);
+			continue;
+		}
+
+		if (nm_utils_strv_find_first ((char **) plugins,
+		                              iter - plugins,
+		                              pname) >= 0) {
+			/* the plugin is already mentioned in the list previously.
+			 * Don't load a duplicate. */
+			continue;
+		}
+
+		success = add_plugin_load_file (self, pname, error);
+		if (!success)
+			break;
+	}
+
+	/* If keyfile plugin was not among configured plugins, add it as the last one */
+	if (success)
+		add_plugin_keyfile (self);
+
+	return success;
+}
+
+/*****************************************************************************/
+
+static void
 pk_hostname_cb (NMAuthChain *chain,
-                GError *chain_error,
                 GDBusMethodInvocation *context,
                 gpointer user_data)
 {
@@ -1503,19 +3283,14 @@ pk_hostname_cb (NMAuthChain *chain,
 	GError *error = NULL;
 	const char *hostname;
 
-	g_assert (context);
+	nm_assert (G_IS_DBUS_METHOD_INVOCATION (context));
 
-	priv->auths = g_slist_remove (priv->auths, chain);
+	c_list_unlink (nm_auth_chain_parent_lst_list (chain));
 
 	result = nm_auth_chain_get_result (chain, NM_AUTH_PERMISSION_SETTINGS_MODIFY_HOSTNAME);
 
 	/* If our NMSettingsConnection is already gone, do nothing */
-	if (chain_error) {
-		error = g_error_new (NM_SETTINGS_ERROR,
-		                     NM_SETTINGS_ERROR_FAILED,
-		                     "Error checking authorization: %s",
-		                     chain_error->message);
-	} else if (result != NM_AUTH_CALL_RESULT_YES) {
+	if (result != NM_AUTH_CALL_RESULT_YES) {
 		error = g_error_new_literal (NM_SETTINGS_ERROR,
 		                             NM_SETTINGS_ERROR_PERMISSION_DENIED,
 		                             "Insufficient privileges.");
@@ -1533,8 +3308,6 @@ pk_hostname_cb (NMAuthChain *chain,
 		g_dbus_method_invocation_take_error (context, error);
 	else
 		g_dbus_method_invocation_return_value (context, NULL);
-
-	nm_auth_chain_destroy (chain);
 }
 
 static void
@@ -1571,18 +3344,27 @@ impl_settings_save_hostname (NMDBusObject *obj,
 		return;
 	}
 
-	priv->auths = g_slist_append (priv->auths, chain);
+	c_list_link_tail (&priv->auth_lst_head, nm_auth_chain_parent_lst_list (chain));
 	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_SETTINGS_MODIFY_HOSTNAME, TRUE);
 	nm_auth_chain_set_data (chain, "hostname", g_strdup (hostname), g_free);
 }
 
 /*****************************************************************************/
 
+static void
+_hostname_changed_cb (NMHostnameManager *hostname_manager,
+                      GParamSpec *pspec,
+                      gpointer user_data)
+{
+	_notify (user_data, PROP_HOSTNAME);
+}
+
+/*****************************************************************************/
+
 static gboolean
 have_connection_for_device (NMSettings *self, NMDevice *device)
 {
 	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
-	NMSettingConnection *s_con;
 	NMSettingWired *s_wired;
 	const char *setting_hwaddr;
 	const char *perm_hw_addr;
@@ -1595,32 +3377,36 @@ have_connection_for_device (NMSettings *self, NMDevice *device)
 	/* Find a wired connection locked to the given MAC address, if any */
 	c_list_for_each_entry (sett_conn, &priv->connections_lst_head, _connections_lst) {
 		NMConnection *connection = nm_settings_connection_get_connection (sett_conn);
-		const char *ctype, *iface;
+		NMSettingConnection *s_con = nm_connection_get_setting_connection (connection);
+		const char *ctype;
+		const char *iface;
+
+		ctype = nm_setting_connection_get_connection_type (s_con);
+		if (!NM_IN_STRSET (ctype, NM_SETTING_WIRED_SETTING_NAME,
+		                          NM_SETTING_PPPOE_SETTING_NAME))
+			continue;
 
 		if (!nm_device_check_connection_compatible (device, connection, NULL))
 			continue;
 
-		s_con = nm_connection_get_setting_connection (connection);
+		if (nm_settings_connection_default_wired_get_device (sett_conn))
+			continue;
 
-		iface = nm_setting_connection_get_interface_name (s_con);
-		if (iface && strcmp (iface, nm_device_get_iface (device)) != 0)
+		if (NM_FLAGS_ANY (nm_settings_connection_get_flags (sett_conn),
+		                  NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE))
 			continue;
 
-		ctype = nm_setting_connection_get_connection_type (s_con);
-		if (   strcmp (ctype, NM_SETTING_WIRED_SETTING_NAME)
-		    && strcmp (ctype, NM_SETTING_PPPOE_SETTING_NAME))
+		iface = nm_setting_connection_get_interface_name (s_con);
+		if (!nm_streq0 (iface, nm_device_get_iface (device)))
 			continue;
 
 		s_wired = nm_connection_get_setting_wired (connection);
-
 		if (   !s_wired
 		    && nm_streq (ctype, NM_SETTING_PPPOE_SETTING_NAME)) {
 			/* No wired setting; therefore the PPPoE connection applies to any device */
 			return TRUE;
 		}
 
-		nm_assert (s_wired);
-
 		setting_hwaddr = nm_setting_wired_get_mac_address (s_wired);
 		if (setting_hwaddr) {
 			/* A connection mac-locked to this device */
@@ -1641,38 +3427,26 @@ have_connection_for_device (NMSettings *self, NMDevice *device)
 }
 
 static void
-default_wired_connection_updated_by_user_cb (NMSettingsConnection *connection, gboolean by_user, NMSettings *self)
-{
-	NMDevice *device;
-
-	if (!by_user)
-		return;
-
-	/* The connection has been changed by the user, it should no longer be
-	 * considered a default wired connection, and should no longer affect
-	 * the no-auto-default configuration option.
-	 */
-	device = g_object_get_qdata (G_OBJECT (connection), _default_wired_device_quark ());
-	if (device)
-		default_wired_clear_tag (self, device, connection, FALSE);
-}
-
-static void
 default_wired_clear_tag (NMSettings *self,
                          NMDevice *device,
-                         NMSettingsConnection *connection,
+                         NMSettingsConnection *sett_conn,
                          gboolean add_to_no_auto_default)
 {
 	nm_assert (NM_IS_SETTINGS (self));
 	nm_assert (NM_IS_DEVICE (device));
-	nm_assert (NM_IS_SETTINGS_CONNECTION (connection));
-	nm_assert (device == g_object_get_qdata (G_OBJECT (connection), _default_wired_device_quark ()));
-	nm_assert (connection == g_object_get_qdata (G_OBJECT (device), _default_wired_connection_quark ()));
+	nm_assert (NM_IS_SETTINGS_CONNECTION (sett_conn));
+	nm_assert (device == nm_settings_connection_default_wired_get_device (sett_conn));
+	nm_assert (sett_conn == g_object_get_qdata (G_OBJECT (device), _default_wired_connection_quark ()));
 
-	g_object_set_qdata (G_OBJECT (connection), _default_wired_device_quark (), NULL);
-	g_object_set_qdata (G_OBJECT (device), _default_wired_connection_quark (), NULL);
+	_LOGT ("auto-default: forget association between %s (%s) and device %s (%s)",
+	       nm_settings_connection_get_uuid (sett_conn),
+	       nm_settings_connection_get_id (sett_conn),
+	       nm_device_get_iface (device),
+	       add_to_no_auto_default ? "persisted" : "temporary");
+
+	nm_settings_connection_default_wired_set_device (sett_conn, NULL);
 
-	g_signal_handlers_disconnect_by_func (connection, G_CALLBACK (default_wired_connection_updated_by_user_cb), self);
+	g_object_set_qdata (G_OBJECT (device), _default_wired_connection_quark (), NULL);
 
 	if (add_to_no_auto_default)
 		nm_config_set_no_auto_default_for_device (NM_SETTINGS_GET_PRIVATE (self)->config, device);
@@ -1681,7 +3455,8 @@ default_wired_clear_tag (NMSettings *self,
 static void
 device_realized (NMDevice *device, GParamSpec *pspec, NMSettings *self)
 {
-	NMConnection *connection;
+	gs_unref_object NMConnection *connection = NULL;
+	NMSettingsPrivate *priv;
 	NMSettingsConnection *added;
 	GError *error = NULL;
 
@@ -1692,22 +3467,33 @@ device_realized (NMDevice *device, GParamSpec *pspec, NMSettings *self)
 	                                      G_CALLBACK (device_realized),
 	                                      self);
 
+	priv = NM_SETTINGS_GET_PRIVATE (self);
+
 	/* If the device isn't managed or it already has a default wired connection,
 	 * ignore it.
 	 */
 	if (   !nm_device_get_managed (device, FALSE)
 	    || g_object_get_qdata (G_OBJECT (device), _default_wired_connection_quark ())
-	    || have_connection_for_device (self, device))
+	    || have_connection_for_device (self, device)
+	    || nm_config_get_no_auto_default_for_device (priv->config, device))
 		return;
 
 	connection = nm_device_new_default_connection (device);
 	if (!connection)
 		return;
 
-	/* Add the connection */
-	added = nm_settings_add_connection (self, connection, FALSE, &error);
-	g_object_unref (connection);
-
+	_LOGT ("auto-default: creating in-memory connection %s (%s) for device %s",
+	       nm_connection_get_uuid (connection),
+	       nm_connection_get_id (connection),
+	       nm_device_get_iface (device));
+
+	nm_settings_add_connection (self,
+	                            connection,
+	                            NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY,
+	                            NM_SETTINGS_CONNECTION_ADD_REASON_NONE,
+	                            NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED,
+	                            &added,
+	                            &error);
 	if (!added) {
 		if (!g_error_matches (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_UUID_EXISTS)) {
 			_LOGW ("(%s) couldn't create default wired connection: %s",
@@ -1718,11 +3504,9 @@ device_realized (NMDevice *device, GParamSpec *pspec, NMSettings *self)
 		return;
 	}
 
-	g_object_set_qdata (G_OBJECT (added), _default_wired_device_quark (), device);
-	g_object_set_qdata (G_OBJECT (device), _default_wired_connection_quark (), added);
+	nm_settings_connection_default_wired_set_device (added, device);
 
-	g_signal_connect (added, NM_SETTINGS_CONNECTION_UPDATED_INTERNAL,
-	                  G_CALLBACK (default_wired_connection_updated_by_user_cb), self);
+	g_object_set_qdata (G_OBJECT (device), _default_wired_connection_quark (), added);
 
 	_LOGI ("(%s): created default wired connection '%s'",
 	       nm_device_get_iface (device),
@@ -1735,6 +3519,8 @@ nm_settings_device_added (NMSettings *self, NMDevice *device)
 	if (nm_device_is_real (device))
 		device_realized (device, NULL, self);
 	else {
+		/* FIXME(shutdown): we need to disconnect this signal handler during
+		 *   shutdown. */
 		g_signal_connect_after (device, "notify::" NM_DEVICE_REAL,
 		                        G_CALLBACK (device_realized),
 		                        self);
@@ -1758,33 +3544,166 @@ nm_settings_device_removed (NMSettings *self, NMDevice *device, gboolean quittin
 		 * remains up and can be assumed if NM starts again.
 		 */
 		if (quitting == FALSE)
-			nm_settings_connection_delete (connection, NULL);
+			nm_settings_connection_delete (connection, TRUE);
 	}
 }
 
 /*****************************************************************************/
 
-const char *
-nm_settings_get_startup_complete_blocked_reason (NMSettings *self)
+static void
+session_monitor_changed_cb (NMSessionMonitor *session_monitor,
+                            NMSettings *self)
 {
 	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
-	const char *uuid = NULL;
-
-	if (priv->startup_complete)
-		return NULL;
-	if (priv->startup_complete_blocked_by)
-		uuid = nm_settings_connection_get_uuid (priv->startup_complete_blocked_by);
-	return uuid ?: "unknown";
+	NMSettingsConnection *const*list;
+	guint i, len;
+	guint generation;
+
+again:
+	list = nm_settings_get_connections (self, &len);
+	generation = priv->connections_generation;
+	for (i = 0; i < len; i++) {
+		gboolean is_visible;
+
+		is_visible = nm_settings_connection_check_visibility (list[i],
+		                                                      session_monitor);
+		nm_settings_connection_set_flags (list[i],
+		                                  NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE,
+		                                  is_visible);
+		if (generation != priv->connections_generation) {
+			/* the cached list was invalidated. Start again.
+			 *
+			 * Note that nm_settings_connection_recheck_visibility() will do nothing
+			 * if the visibility didn't change (including emitting no signals,
+			 * and not invalidating the list).
+			 *
+			 * Hence, for this to be an endless loop, the settings would have
+			 * to constantly change the visibility flag and also invalidate the list. */
+			goto again;
+		}
+	}
 }
 
 /*****************************************************************************/
 
+G_GNUC_PRINTF (4, 5)
 static void
-_hostname_changed_cb (NMHostnameManager *hostname_manager,
-                      GParamSpec *pspec,
+_kf_db_log_fcn (NMKeyFileDB *kf_db,
+                int syslog_level,
+                gpointer user_data,
+                const char *fmt,
+                ...)
+{
+	NMSettings *self = user_data;
+	NMLogLevel level = nm_log_level_from_syslog (syslog_level);
+
+	if (_NMLOG_ENABLED (level)) {
+		NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+		gs_free char *msg = NULL;
+		va_list ap;
+		const char *prefix;
+
+		va_start (ap, fmt);
+		msg = g_strdup_vprintf (fmt, ap);
+		va_end (ap);
+
+		if (priv->kf_db_timestamps == kf_db)
+			prefix = "timestamps";
+		else if (priv->kf_db_seen_bssids == kf_db)
+			prefix = "seen-bssids";
+		else {
+			nm_assert_not_reached ();
+			prefix = "???";
+		}
+
+		_NMLOG (level, "[%s-keyfile]: %s", prefix, msg);
+	}
+}
+
+static gboolean
+_kf_db_got_dirty_flush (NMSettings *self,
+                        gboolean is_timestamps)
+{
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	const char *prefix;
+	NMKeyFileDB *kf_db;
+
+	if (is_timestamps) {
+		prefix = "timestamps";
+		kf_db = priv->kf_db_timestamps;
+		priv->kf_db_flush_idle_id_timestamps = 0;
+	} else {
+		prefix = "seen-bssids";
+		kf_db = priv->kf_db_seen_bssids;
+		priv->kf_db_flush_idle_id_seen_bssids = 0;
+	}
+
+	if (nm_key_file_db_is_dirty (kf_db))
+		nm_key_file_db_to_file (kf_db, FALSE);
+	else {
+		_LOGT ("[%s-keyfile]: skip saving changes to \"%s\"",
+		       prefix,
+		       nm_key_file_db_get_filename (kf_db));
+	}
+
+	return G_SOURCE_REMOVE;
+}
+
+static gboolean
+_kf_db_got_dirty_flush_timestamps_cb (gpointer user_data)
+{
+	return _kf_db_got_dirty_flush (user_data,
+	                               TRUE);
+}
+
+static gboolean
+_kf_db_got_dirty_flush_seen_bssids_cb (gpointer user_data)
+{
+	return _kf_db_got_dirty_flush (user_data,
+	                               FALSE);
+}
+
+static void
+_kf_db_got_dirty_fcn (NMKeyFileDB *kf_db,
                       gpointer user_data)
 {
-	_notify (user_data, PROP_HOSTNAME);
+	NMSettings *self = user_data;
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	GSourceFunc idle_func;
+	guint *p_id;
+	const char *prefix;
+
+	if (priv->kf_db_timestamps == kf_db) {
+		prefix = "timestamps";
+		p_id = &priv->kf_db_flush_idle_id_timestamps;
+		idle_func = _kf_db_got_dirty_flush_timestamps_cb;
+	} else if (priv->kf_db_seen_bssids == kf_db) {
+		prefix = "seen-bssids";
+		p_id = &priv->kf_db_flush_idle_id_seen_bssids;
+		idle_func = _kf_db_got_dirty_flush_seen_bssids_cb;
+	} else {
+		nm_assert_not_reached ();
+		return;
+	}
+
+	if (*p_id != 0)
+		return;
+	_LOGT ("[%s-keyfile]: schedule flushing changes to disk", prefix);
+	*p_id = g_idle_add_full (G_PRIORITY_LOW, idle_func, self, NULL);
+}
+
+void
+nm_settings_kf_db_write (NMSettings *self)
+{
+	NMSettingsPrivate *priv;
+
+	g_return_if_fail (NM_IS_SETTINGS (self));
+
+	priv = NM_SETTINGS_GET_PRIVATE (self);
+	if (priv->kf_db_timestamps)
+		nm_key_file_db_to_file (priv->kf_db_timestamps, TRUE);
+	if (priv->kf_db_seen_bssids)
+		nm_key_file_db_to_file (priv->kf_db_seen_bssids, TRUE);
 }
 
 /*****************************************************************************/
@@ -1794,19 +3713,47 @@ nm_settings_start (NMSettings *self, GError **error)
 {
 	NMSettingsPrivate *priv;
 	gs_strfreev char **plugins = NULL;
+	GSList *iter;
 
 	priv = NM_SETTINGS_GET_PRIVATE (self);
 
+	nm_assert (!priv->started);
+
+	priv->hostname_manager = g_object_ref (nm_hostname_manager_get ());
+
+	priv->kf_db_timestamps = nm_key_file_db_new (NMSTATEDIR "/timestamps",
+	                                             "timestamps",
+	                                             _kf_db_log_fcn,
+	                                             _kf_db_got_dirty_fcn,
+	                                             self);
+	priv->kf_db_seen_bssids = nm_key_file_db_new (NMSTATEDIR "/seen-bssids",
+	                                              "seen-bssids",
+	                                              _kf_db_log_fcn,
+	                                              _kf_db_got_dirty_fcn,
+	                                              self);
+	nm_key_file_db_start (priv->kf_db_timestamps);
+	nm_key_file_db_start (priv->kf_db_seen_bssids);
+
 	/* Load the plugins; fail if a plugin is not found. */
 	plugins = nm_config_data_get_plugins (nm_config_get_data_orig (priv->config), TRUE);
 
-	if (!load_plugins (self, (const char **) plugins, error))
+	if (!load_plugins (self, (const char *const*) plugins, error))
 		return FALSE;
 
-	load_connections (self);
-	check_startup_complete (self);
+	for (iter = priv->plugins; iter; iter = iter->next) {
+		NMSettingsPlugin *plugin = NM_SETTINGS_PLUGIN (iter->data);
+
+		g_signal_connect (plugin, NM_SETTINGS_PLUGIN_UNMANAGED_SPECS_CHANGED,
+		                  G_CALLBACK (_plugin_unmanaged_specs_changed), self);
+		g_signal_connect (plugin, NM_SETTINGS_PLUGIN_UNRECOGNIZED_SPECS_CHANGED,
+		                  G_CALLBACK (_plugin_unrecognized_specs_changed), self);
+	}
+
+	_plugin_unmanaged_specs_changed (NULL, self);
+	_plugin_unrecognized_specs_changed (NULL, self);
+
+	_plugin_connections_reload (self);
 
-	priv->hostname_manager = g_object_ref (nm_hostname_manager_get ());
 	g_signal_connect (priv->hostname_manager,
 	                  "notify::"NM_HOSTNAME_MANAGER_HOSTNAME,
 	                  G_CALLBACK (_hostname_changed_cb),
@@ -1814,6 +3761,14 @@ nm_settings_start (NMSettings *self, GError **error)
 	if (nm_hostname_manager_get_hostname (priv->hostname_manager))
 		_notify (self, PROP_HOSTNAME);
 
+	priv->started = TRUE;
+	_startup_complete_check (self, 0);
+
+	/* FIXME(shutdown): we also need a nm_settings_stop() during shutdown.
+	 *
+	 * In particular, we need to remove all in-memory keyfiles from /run that are nm-generated.
+	 * alternatively, the nm-generated flag must also be persisted and loaded to /run. */
+
 	return TRUE;
 }
 
@@ -1825,39 +3780,29 @@ get_property (GObject *object, guint prop_id,
 {
 	NMSettings *self = NM_SETTINGS (object);
 	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
-	const GSList *specs, *iter;
-	guint i;
-	char **strvs;
 	const char **strv;
 
 	switch (prop_id) {
 	case PROP_UNMANAGED_SPECS:
-		specs = nm_settings_get_unmanaged_specs (self);
-		strvs = g_new (char *, g_slist_length ((GSList *) specs) + 1);
-		i = 0;
-		for (iter = specs; iter; iter = iter->next)
-			strvs[i++] = g_strdup (iter->data);
-		strvs[i] = NULL;
-		g_value_take_boxed (value, strvs);
+		g_value_take_boxed (value,
+		                    _nm_utils_slist_to_strv (nm_settings_get_unmanaged_specs (self),
+		                                             TRUE));
 		break;
 	case PROP_HOSTNAME:
 		g_value_set_string (value,
-		                    priv->hostname_manager
-		                      ? nm_hostname_manager_get_hostname (priv->hostname_manager)
-		                      : NULL);
+		                      priv->hostname_manager
+		                    ? nm_hostname_manager_get_hostname (priv->hostname_manager)
+		                    : NULL);
 		break;
 	case PROP_CAN_MODIFY:
 		g_value_set_boolean (value, TRUE);
 		break;
 	case PROP_CONNECTIONS:
-		if (priv->connections_loaded) {
-			strv = nm_dbus_utils_get_paths_for_clist (&priv->connections_lst_head,
-			                                          priv->connections_len,
-			                                          G_STRUCT_OFFSET (NMSettingsConnection, _connections_lst),
-			                                          TRUE);
-			g_value_take_boxed (value, nm_utils_strv_make_deep_copied (strv));
-		} else
-			g_value_set_boxed (value, NULL);
+		strv = nm_dbus_utils_get_paths_for_clist (&priv->connections_lst_head,
+		                                          priv->connections_len,
+		                                          G_STRUCT_OFFSET (NMSettingsConnection, _connections_lst),
+		                                          TRUE);
+		g_value_take_boxed (value, nm_utils_strv_make_deep_copied (strv));
 		break;
 	case PROP_STARTUP_COMPLETE:
 		g_value_set_boolean (value, !nm_settings_get_startup_complete_blocked_reason (self));
@@ -1875,10 +3820,24 @@ nm_settings_init (NMSettings *self)
 {
 	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
 
+	c_list_init (&priv->auth_lst_head);
 	c_list_init (&priv->connections_lst_head);
 
-	priv->agent_mgr = g_object_ref (nm_agent_manager_get ());
+	c_list_init (&priv->sce_dirty_lst_head);
+	priv->sce_idx = g_hash_table_new_full (nm_pstr_hash, nm_pstr_equal,
+	                                       NULL, (GDestroyNotify) _sett_conn_entry_free);
+
 	priv->config = g_object_ref (nm_config_get ());
+
+	priv->agent_mgr = g_object_ref (nm_agent_manager_get ());
+
+	priv->platform = g_object_ref (NM_PLATFORM_GET);
+
+	priv->session_monitor = g_object_ref (nm_session_monitor_get ());
+	g_signal_connect (priv->session_monitor,
+	                  NM_SESSION_MONITOR_CHANGED,
+	                  G_CALLBACK (session_monitor_changed_cb),
+	                  self);
 }
 
 NMSettings *
@@ -1892,11 +3851,18 @@ dispose (GObject *object)
 {
 	NMSettings *self = NM_SETTINGS (object);
 	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	CList *iter;
 
+	nm_assert (c_list_is_empty (&priv->sce_dirty_lst_head));
+	nm_assert (g_hash_table_size (priv->sce_idx) == 0);
+
+	nm_clear_g_source (&priv->startup_complete_timeout_id);
+	nm_clear_g_signal_handler (priv->platform, &priv->startup_complete_platform_change_id);
+	nm_clear_pointer (&priv->startup_complete_idx, g_hash_table_destroy);
 	g_clear_object (&priv->startup_complete_blocked_by);
 
-	g_slist_free_full (priv->auths, (GDestroyNotify) nm_auth_chain_destroy);
-	priv->auths = NULL;
+	while ((iter = c_list_first (&priv->auth_lst_head)))
+		nm_auth_chain_destroy (nm_auth_chain_parent_lst_entry (iter));
 
 	if (priv->hostname_manager) {
 		g_signal_handlers_disconnect_by_func (priv->hostname_manager,
@@ -1905,6 +3871,13 @@ dispose (GObject *object)
 		g_clear_object (&priv->hostname_manager);
 	}
 
+	if (priv->session_monitor) {
+		g_signal_handlers_disconnect_by_func (priv->session_monitor,
+		                                      G_CALLBACK (session_monitor_changed_cb),
+		                                      self);
+		g_clear_object (&priv->session_monitor);
+	}
+
 	G_OBJECT_CLASS (nm_settings_parent_class)->dispose (object);
 }
 
@@ -1919,6 +3892,11 @@ finalize (GObject *object)
 
 	nm_assert (c_list_is_empty (&priv->connections_lst_head));
 
+	nm_assert (c_list_is_empty (&priv->sce_dirty_lst_head));
+	nm_assert (g_hash_table_size (priv->sce_idx) == 0);
+
+	nm_clear_pointer (&priv->sce_idx, g_hash_table_destroy);
+
 	g_slist_free_full (priv->unmanaged_specs, g_free);
 	g_slist_free_full (priv->unrecognized_specs, g_free);
 
@@ -1929,11 +3907,22 @@ finalize (GObject *object)
 		g_signal_handlers_disconnect_by_data (plugin, self);
 	}
 
+	g_clear_object (&priv->keyfile_plugin);
+
 	g_clear_object (&priv->agent_mgr);
 
-	g_clear_object (&priv->config);
+	nm_clear_g_source (&priv->kf_db_flush_idle_id_timestamps);
+	nm_clear_g_source (&priv->kf_db_flush_idle_id_seen_bssids);
+	nm_key_file_db_to_file (priv->kf_db_timestamps, FALSE);
+	nm_key_file_db_to_file (priv->kf_db_seen_bssids, FALSE);
+	nm_key_file_db_destroy (priv->kf_db_timestamps);
+	nm_key_file_db_destroy (priv->kf_db_seen_bssids);
 
 	G_OBJECT_CLASS (nm_settings_parent_class)->finalize (object);
+
+	g_clear_object (&priv->config);
+
+	g_clear_object (&priv->platform);
 }
 
 static const GDBusSignalInfo signal_info_new_connection = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT (
@@ -2001,6 +3990,21 @@ static const NMDBusInterfaceInfoExtended interface_info_settings = {
 			),
 			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
 				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"AddConnection2",
+					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("settings", "a{sa{sv}}"),
+						NM_DEFINE_GDBUS_ARG_INFO ("flags",    "u"),
+						NM_DEFINE_GDBUS_ARG_INFO ("args",     "a{sv}"),
+					),
+					.out_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("path", "o"),
+						NM_DEFINE_GDBUS_ARG_INFO ("result", "a{sv}"),
+					),
+				),
+				.handle = impl_settings_add_connection2,
+			),
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
 					"LoadConnections",
 					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
 						NM_DEFINE_GDBUS_ARG_INFO ("filenames", "as"),
@@ -2104,7 +4108,7 @@ nm_settings_class_init (NMSettingsClass *class)
 	                  G_SIGNAL_RUN_FIRST,
 	                  0, NULL, NULL,
 	                  NULL,
-	                  G_TYPE_NONE, 2, NM_TYPE_SETTINGS_CONNECTION, G_TYPE_BOOLEAN);
+	                  G_TYPE_NONE, 2, NM_TYPE_SETTINGS_CONNECTION, G_TYPE_UINT);
 
 	signals[CONNECTION_REMOVED] =
 	    g_signal_new (NM_SETTINGS_SIGNAL_CONNECTION_REMOVED,