summary refs log tree commit diff
path: root/src/nm-config.c
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2015-07-14 19:38:58 +0200
committerMichael Biebl <biebl@debian.org>2015-07-14 19:38:58 +0200
commit50a58f0fabd8a34c1b6108a107e08abe3c1ccd24 (patch)
tree6790165f39daee79e2b6c6617483320613493367 /src/nm-config.c
parentf408e27bccfacf347605a8d98649975a68f38a17 (diff)
Imported Upstream version 1.0.4 upstream/1.0.4
Diffstat (limited to 'src/nm-config.c')
-rw-r--r--src/nm-config.c1191
1 files changed, 865 insertions, 326 deletions
diff --git a/src/nm-config.c b/src/nm-config.c
index 4626ae26..86b3d929 100644
--- a/src/nm-config.c
+++ b/src/nm-config.c
@@ -29,100 +29,213 @@
 #include "nm-utils.h"
 #include "nm-glib-compat.h"
 #include "nm-device.h"
+#include "NetworkManagerUtils.h"
+#include "gsystem-local-alloc.h"
+#include "nm-enum-types.h"
+#include "nm-core-internal.h"
 
 #include <gio/gio.h>
 #include <glib/gi18n.h>
 
-#define NM_DEFAULT_SYSTEM_CONF_FILE    NMCONFDIR "/NetworkManager.conf"
-#define NM_DEFAULT_SYSTEM_CONF_DIR     NMCONFDIR "/conf.d"
-#define NM_OLD_SYSTEM_CONF_FILE        NMCONFDIR "/nm-system-settings.conf"
-#define NM_NO_AUTO_DEFAULT_STATE_FILE  NMSTATEDIR "/no-auto-default.state"
+#define DEFAULT_CONFIG_MAIN_FILE        NMCONFDIR "/NetworkManager.conf"
+#define DEFAULT_CONFIG_DIR              NMCONFDIR "/conf.d"
+#define DEFAULT_CONFIG_MAIN_FILE_OLD    NMCONFDIR "/nm-system-settings.conf"
+#define DEFAULT_NO_AUTO_DEFAULT_FILE    NMSTATEDIR "/no-auto-default.state"
+
+struct NMConfigCmdLineOptions {
+	char *config_main_file;
+	char *config_dir;
+	char *no_auto_default_file;
+	char *plugins;
+	gboolean configure_and_quit;
+	char *connectivity_uri;
+
+	/* We store interval as signed internally to track whether it's
+	 * set or not via GOptionEntry
+	 */
+	int connectivity_interval;
+	char *connectivity_response;
+};
 
 typedef struct {
-	char *nm_conf_path;
+	NMConfigCmdLineOptions cli;
+
+	NMConfigData *config_data;
+	NMConfigData *config_data_orig;
+
 	char *config_dir;
-	char *config_description;
 	char *no_auto_default_file;
-	GKeyFile *keyfile;
 
 	char **plugins;
 	gboolean monitor_connection_files;
 	gboolean auth_polkit;
 	char *dhcp_client;
-	char *dns_mode;
 
 	char *log_level;
 	char *log_domains;
 
 	char *debug;
 
-	char *connectivity_uri;
-	gint connectivity_interval;
-	char *connectivity_response;
-
-	char **no_auto_default;
-	char **ignore_carrier;
-
 	gboolean configure_and_quit;
 } NMConfigPrivate;
 
-static NMConfig *singleton = NULL;
+enum {
+	PROP_0,
+	PROP_CMD_LINE_OPTIONS,
+	LAST_PROP,
+};
+
+enum {
+	SIGNAL_CONFIG_CHANGED,
+
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+static void nm_config_initable_iface_init (GInitableIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (NMConfig, nm_config, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, nm_config_initable_iface_init);
+                         )
 
-G_DEFINE_TYPE (NMConfig, nm_config, G_TYPE_OBJECT)
 
 #define NM_CONFIG_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_CONFIG, NMConfigPrivate))
 
 /************************************************************************/
 
-static gboolean
-_get_bool_value (GKeyFile *keyfile,
-                 const char *section,
-                 const char *key,
-                 gboolean default_value)
+static void _set_config_data (NMConfig *self, NMConfigData *new_data, int signal);
+
+/************************************************************************/
+
+gint
+nm_config_parse_boolean (const char *str,
+                         gint default_value)
 {
-	gboolean value = default_value;
-	char *str;
+	gsize len;
+	char *s = NULL;
+
+	if (!str)
+		return default_value;
+
+	while (str[0] && g_ascii_isspace (str[0]))
+		str++;
+
+	if (!str[0])
+		return default_value;
+
+	len = strlen (str);
+	if (g_ascii_isspace (str[len - 1])) {
+		s = g_strdup (str);
+		g_strchomp (s);
+		str = s;
+	}
+
+	if (!g_ascii_strcasecmp (str, "true") || !g_ascii_strcasecmp (str, "yes") || !g_ascii_strcasecmp (str, "on") || !g_ascii_strcasecmp (str, "1"))
+		default_value = TRUE;
+	else if (!g_ascii_strcasecmp (str, "false") || !g_ascii_strcasecmp (str, "no") || !g_ascii_strcasecmp (str, "off") || !g_ascii_strcasecmp (str, "0"))
+		default_value = FALSE;
+	if (s)
+		g_free (s);
+	return default_value;
+}
+
+gint
+nm_config_keyfile_get_boolean (GKeyFile *keyfile,
+                               const char *section,
+                               const char *key,
+                               gint default_value)
+{
+	gs_free char *str = NULL;
 
 	g_return_val_if_fail (keyfile != NULL, default_value);
 	g_return_val_if_fail (section != NULL, default_value);
 	g_return_val_if_fail (key != NULL, default_value);
 
 	str = g_key_file_get_value (keyfile, section, key, NULL);
-	if (!str)
-		return default_value;
+	return nm_config_parse_boolean (str, default_value);
+}
 
-	g_strstrip (str);
-	if (str[0]) {
-		if (!g_ascii_strcasecmp (str, "true") || !g_ascii_strcasecmp (str, "yes") || !g_ascii_strcasecmp (str, "on") || !g_ascii_strcasecmp (str, "1"))
-			value = TRUE;
-		else if (!g_ascii_strcasecmp (str, "false") || !g_ascii_strcasecmp (str, "no") || !g_ascii_strcasecmp (str, "off") || !g_ascii_strcasecmp (str, "0"))
-			value = FALSE;
-		else {
-			nm_log_warn (LOGD_CORE, "Unrecognized value for %s.%s: '%s'. Assuming '%s'",
-			             section, key, str, default_value ? "true" : "false");
-		}
+char *
+nm_config_keyfile_get_value (GKeyFile *keyfile,
+                             const char *section,
+                             const char *key,
+                             NMConfigGetValueFlags flags)
+{
+	char *value;
+
+	if (NM_FLAGS_HAS (flags, NM_CONFIG_GET_VALUE_RAW))
+		value = g_key_file_get_value (keyfile, section, key, NULL);
+	else
+		value = g_key_file_get_string (keyfile, section, key, NULL);
+
+	if (!value)
+		return NULL;
+
+	if (NM_FLAGS_HAS (flags, NM_CONFIG_GET_VALUE_STRIP))
+		g_strstrip (value);
+
+	if (   NM_FLAGS_HAS (flags, NM_CONFIG_GET_VALUE_NO_EMPTY)
+	    && !*value) {
+		g_free (value);
+		return NULL;
 	}
 
-	g_free (str);
 	return value;
 }
 
+void
+nm_config_keyfile_set_string_list (GKeyFile *keyfile,
+                                   const char *group,
+                                   const char *key,
+                                   const char *const* strv,
+                                   gssize len)
+{
+	gsize l;
+	char *new_value;
+
+	if (len < 0)
+		len = strv ? g_strv_length ((char **) strv) : 0;
+
+	g_key_file_set_string_list (keyfile, group, key, strv, len);
+
+	/* g_key_file_set_string_list() appends a trailing separator to the value.
+	 * We don't like that, get rid of it. */
+
+	new_value = g_key_file_get_value (keyfile, group, key, NULL);
+	if (!new_value)
+		return;
+
+	l = strlen (new_value);
+	if (l > 0 && new_value[l - 1] == NM_CONFIG_KEYFILE_LIST_SEPARATOR) {
+		/* Maybe we should check that value doesn't end with "\\,", i.e.
+		 * with an escaped separator. But the way g_key_file_set_string_list()
+		 * is implemented (currently), it always adds a trailing separator. */
+		new_value[l - 1] = '\0';
+		g_key_file_set_value (keyfile, group, key, new_value);
+	}
+	g_free (new_value);
+}
+
 /************************************************************************/
 
-const char *
-nm_config_get_path (NMConfig *config)
+NMConfigData *
+nm_config_get_data (NMConfig *config)
 {
 	g_return_val_if_fail (config != NULL, NULL);
 
-	return NM_CONFIG_GET_PRIVATE (config)->nm_conf_path;
+	return NM_CONFIG_GET_PRIVATE (config)->config_data;
 }
 
-const char *
-nm_config_get_description (NMConfig *config)
+/* The NMConfigData instance is reloadable and will be swapped on reload.
+ * nm_config_get_data_orig() returns the original configuration, when the NMConfig
+ * instance was created. */
+NMConfigData *
+nm_config_get_data_orig (NMConfig *config)
 {
 	g_return_val_if_fail (config != NULL, NULL);
 
-	return NM_CONFIG_GET_PRIVATE (config)->config_description;
+	return NM_CONFIG_GET_PRIVATE (config)->config_data_orig;
 }
 
 const char **
@@ -158,14 +271,6 @@ nm_config_get_dhcp_client (NMConfig *config)
 }
 
 const char *
-nm_config_get_dns_mode (NMConfig *config)
-{
-	g_return_val_if_fail (config != NULL, NULL);
-
-	return NM_CONFIG_GET_PRIVATE (config)->dns_mode;
-}
-
-const char *
 nm_config_get_log_level (NMConfig *config)
 {
 	g_return_val_if_fail (config != NULL, NULL);
@@ -189,102 +294,32 @@ nm_config_get_debug (NMConfig *config)
 	return NM_CONFIG_GET_PRIVATE (config)->debug;
 }
 
-const char *
-nm_config_get_connectivity_uri (NMConfig *config)
-{
-	g_return_val_if_fail (config != NULL, NULL);
-
-	return NM_CONFIG_GET_PRIVATE (config)->connectivity_uri;
-}
-
-guint
-nm_config_get_connectivity_interval (NMConfig *config)
-{
-	g_return_val_if_fail (config != NULL, 0);
-
-	/* We store interval as signed internally to track whether it's
-	 * set or not, but report as unsigned to callers.
-	 */
-	return MAX (NM_CONFIG_GET_PRIVATE (config)->connectivity_interval, 0);
-}
-
-const char *
-nm_config_get_connectivity_response (NMConfig *config)
-{
-	g_return_val_if_fail (config != NULL, NULL);
-
-	return NM_CONFIG_GET_PRIVATE (config)->connectivity_response;
-}
-
 gboolean
 nm_config_get_configure_and_quit (NMConfig *config)
 {
 	return NM_CONFIG_GET_PRIVATE (config)->configure_and_quit;
 }
 
-char *
-nm_config_get_value (NMConfig *config, const char *group, const char *key, GError **error)
-{
-	NMConfigPrivate *priv = NM_CONFIG_GET_PRIVATE (config);
-
-	return g_key_file_get_string (priv->keyfile, group, key, error);
-}
-
-gboolean
-nm_config_get_ignore_carrier (NMConfig *config, NMDevice *device)
-{
-	NMConfigPrivate *priv = NM_CONFIG_GET_PRIVATE (config);
-	GSList *specs = NULL;
-	int i;
-	gboolean match;
-
-	if (!priv->ignore_carrier)
-		return FALSE;
-
-	for (i = 0; priv->ignore_carrier[i]; i++)
-		specs = g_slist_prepend (specs, priv->ignore_carrier[i]);
-
-	match = nm_device_spec_match_list (device, specs);
-
-	g_slist_free (specs);
-	return match;
-}
-
 /************************************************************************/
 
-static void
-merge_no_auto_default_state (NMConfig *config)
+static char **
+no_auto_default_from_file (const char *no_auto_default_file)
 {
-	NMConfigPrivate *priv = NM_CONFIG_GET_PRIVATE (config);
-	GPtrArray *updated;
+	GPtrArray *no_auto_default_new;
 	char **list;
-	int i, j;
+	guint i;
 	char *data;
 
-	/* If the config already matches everything, we don't need to do anything else. */
-	if (priv->no_auto_default && !g_strcmp0 (priv->no_auto_default[0], "*"))
-		return;
+	no_auto_default_new = g_ptr_array_new ();
 
-	updated = g_ptr_array_new ();
-	if (priv->no_auto_default) {
-		for (i = 0; priv->no_auto_default[i]; i++)
-			g_ptr_array_add (updated, priv->no_auto_default[i]);
-		g_free (priv->no_auto_default);
-	}
-
-	if (g_file_get_contents (priv->no_auto_default_file, &data, NULL, NULL)) {
+	if (   no_auto_default_file
+	    && g_file_get_contents (no_auto_default_file, &data, NULL, NULL)) {
 		list = g_strsplit (data, "\n", -1);
 		for (i = 0; list[i]; i++) {
-			if (!*list[i]) {
-				g_free (list[i]);
-				continue;
-			}
-			for (j = 0; j < updated->len; j++) {
-				if (!strcmp (list[i], updated->pdata[j]))
-					break;
-			}
-			if (j == updated->len)
-				g_ptr_array_add (updated, list[i]);
+			if (   *list[i]
+			    && nm_utils_hwaddr_valid (list[i], -1)
+			    && _nm_utils_strv_find_first (list, i, list[i]) < 0)
+				g_ptr_array_add (no_auto_default_new, list[i]);
 			else
 				g_free (list[i]);
 		}
@@ -292,99 +327,240 @@ merge_no_auto_default_state (NMConfig *config)
 		g_free (data);
 	}
 
-	g_ptr_array_add (updated, NULL);
-	priv->no_auto_default = (char **) g_ptr_array_free (updated, FALSE);
+	g_ptr_array_add (no_auto_default_new, NULL);
+	return (char **) g_ptr_array_free (no_auto_default_new, FALSE);
 }
 
-gboolean
-nm_config_get_ethernet_can_auto_default (NMConfig *config, NMDevice *device)
+static gboolean
+no_auto_default_to_file (const char *no_auto_default_file, const char *const*no_auto_default, GError **error)
 {
-	NMConfigPrivate *priv = NM_CONFIG_GET_PRIVATE (config);
-	GSList *specs = NULL;
-	int i;
-	gboolean match;
-
-	for (i = 0; priv->no_auto_default[i]; i++)
-		specs = g_slist_prepend (specs, priv->no_auto_default[i]);
+	GString *data;
+	gboolean success;
+	guint i;
+
+	data = g_string_new ("");
+	for (i = 0; no_auto_default && no_auto_default[i]; i++) {
+		g_string_append (data, no_auto_default[i]);
+		g_string_append_c (data, '\n');
+	}
+	success = g_file_set_contents (no_auto_default_file, data->str, data->len, error);
+	g_string_free (data, TRUE);
+	return success;
+}
 
-	match = nm_device_spec_match_list (device, specs);
+gboolean
+nm_config_get_no_auto_default_for_device (NMConfig *self, NMDevice *device)
+{
+	g_return_val_if_fail (NM_IS_CONFIG (self), FALSE);
 
-	g_slist_free (specs);
-	return !match;
+	return nm_config_data_get_no_auto_default_for_device (NM_CONFIG_GET_PRIVATE (self)->config_data, device);
 }
 
 void
-nm_config_set_ethernet_no_auto_default (NMConfig *config, NMDevice *device)
+nm_config_set_no_auto_default_for_device (NMConfig *self, NMDevice *device)
 {
-	NMConfigPrivate *priv = NM_CONFIG_GET_PRIVATE (config);
-	char *current;
-	GString *updated;
+	NMConfigPrivate *priv = NM_CONFIG_GET_PRIVATE (self);
 	GError *error = NULL;
+	NMConfigData *new_data = NULL;
+	const char *hw_address;
+	const char *const*no_auto_default_current;
+	GPtrArray *no_auto_default_new = NULL;
+	guint i;
 
-	if (!nm_config_get_ethernet_can_auto_default (config, device))
-		return;
+	g_return_if_fail (NM_IS_CONFIG (self));
+	g_return_if_fail (NM_IS_DEVICE (device));
+
+	hw_address = nm_device_get_hw_address (device);
 
-	updated = g_string_new (NULL);
-	if (g_file_get_contents (priv->no_auto_default_file, &current, NULL, NULL)) {
-		g_string_append (updated, current);
-		g_free (current);
-		if (updated->str[updated->len - 1] != '\n')
-			g_string_append_c (updated, '\n');
+	no_auto_default_current = nm_config_data_get_no_auto_default (priv->config_data);
+
+	if (_nm_utils_strv_find_first ((char **) no_auto_default_current, -1, hw_address) >= 0) {
+		/* @hw_address is already blocked. We don't have to update our in-memory representation.
+		 * Maybe we should write to no_auto_default_file anew, but let's save that too. */
+		return;
 	}
 
-	g_string_append (updated, nm_device_get_hw_address (device));
-	g_string_append_c (updated, '\n');
+	no_auto_default_new = g_ptr_array_new ();
+	for (i = 0; no_auto_default_current && no_auto_default_current[i]; i++)
+		g_ptr_array_add (no_auto_default_new, (char *) no_auto_default_current[i]);
+	g_ptr_array_add (no_auto_default_new, (char *) hw_address);
+	g_ptr_array_add (no_auto_default_new, NULL);
 
-	if (!g_file_set_contents (priv->no_auto_default_file, updated->str, updated->len, &error)) {
+	if (!no_auto_default_to_file (priv->no_auto_default_file, (const char *const*) no_auto_default_new->pdata, &error)) {
 		nm_log_warn (LOGD_SETTINGS, "Could not update no-auto-default.state file: %s",
 		             error->message);
 		g_error_free (error);
 	}
 
-	g_string_free (updated, TRUE);
+	new_data = nm_config_data_new_update_no_auto_default (priv->config_data, (const char *const*) no_auto_default_new->pdata);
 
-	merge_no_auto_default_state (config);
+	/* unref no_auto_default_set here. Note that _set_config_data() probably invalidates the content of the array. */
+	g_ptr_array_unref (no_auto_default_new);
+
+	_set_config_data (self, new_data, 0);
 }
 
 /************************************************************************/
 
-static char *cli_config_path;
-static char *cli_config_dir;
-static char *cli_no_auto_default_file;
-static char *cli_plugins;
-static char *cli_connectivity_uri;
-static int cli_connectivity_interval = -1;
-static char *cli_connectivity_response;
-
-static GOptionEntry config_options[] = {
-	{ "config", 0, 0, G_OPTION_ARG_FILENAME, &cli_config_path, N_("Config file location"), N_("/path/to/config.file") },
-	{ "config-dir", 0, 0, G_OPTION_ARG_FILENAME, &cli_config_dir, N_("Config directory location"), N_("/path/to/config/dir") },
-	{ "no-auto-default", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_FILENAME, &cli_no_auto_default_file, "no-auto-default.state location", NULL },
-	{ "plugins", 0, 0, G_OPTION_ARG_STRING, &cli_plugins, N_("List of plugins separated by ','"), N_("plugin1,plugin2") },
-
-	/* These three are hidden for now, and should eventually just go away. */
-	{ "connectivity-uri", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &cli_connectivity_uri, N_("An http(s) address for checking internet connectivity"), "http://example.com" },
-	{ "connectivity-interval", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_INT, &cli_connectivity_interval, N_("The interval between connectivity checks (in seconds)"), "60" },
-	{ "connectivity-response", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &cli_connectivity_response, N_("The expected start of the response"), N_("Bingo!") },
-	{NULL}
-};
-GOptionEntry *
-nm_config_get_options (void)
+static void
+_nm_config_cmd_line_options_clear (NMConfigCmdLineOptions *cli)
+{
+	g_clear_pointer (&cli->config_main_file, g_free);
+	g_clear_pointer (&cli->config_dir, g_free);
+	g_clear_pointer (&cli->no_auto_default_file, g_free);
+	g_clear_pointer (&cli->plugins, g_free);
+	cli->configure_and_quit = FALSE;
+	g_clear_pointer (&cli->connectivity_uri, g_free);
+	g_clear_pointer (&cli->connectivity_response, g_free);
+	cli->connectivity_interval = -1;
+}
+
+static void
+_nm_config_cmd_line_options_copy (const NMConfigCmdLineOptions *cli, NMConfigCmdLineOptions *dst)
+{
+	g_return_if_fail (cli);
+	g_return_if_fail (dst);
+	g_return_if_fail (cli != dst);
+
+	_nm_config_cmd_line_options_clear (dst);
+	dst->config_dir = g_strdup (cli->config_dir);
+	dst->config_main_file = g_strdup (cli->config_main_file);
+	dst->no_auto_default_file = g_strdup (cli->no_auto_default_file);
+	dst->plugins = g_strdup (cli->plugins);
+	dst->configure_and_quit = cli->configure_and_quit;
+	dst->connectivity_uri = g_strdup (cli->connectivity_uri);
+	dst->connectivity_response = g_strdup (cli->connectivity_response);
+	dst->connectivity_interval = cli->connectivity_interval;
+}
+
+NMConfigCmdLineOptions *
+nm_config_cmd_line_options_new ()
+{
+	NMConfigCmdLineOptions *cli = g_new0 (NMConfigCmdLineOptions, 1);
+
+	_nm_config_cmd_line_options_clear (cli);
+	return cli;
+}
+
+void
+nm_config_cmd_line_options_free (NMConfigCmdLineOptions *cli)
+{
+	g_return_if_fail (cli);
+
+	_nm_config_cmd_line_options_clear (cli);
+	g_free (cli);
+}
+
+void
+nm_config_cmd_line_options_add_to_entries (NMConfigCmdLineOptions *cli,
+                                           GOptionContext *opt_ctx)
 {
-	return config_options;
+	g_return_if_fail (opt_ctx);
+	g_return_if_fail (cli);
+
+	{
+		GOptionEntry config_options[] = {
+			{ "config", 0, 0, G_OPTION_ARG_FILENAME, &cli->config_main_file, N_("Config file location"), N_(DEFAULT_CONFIG_MAIN_FILE) },
+			{ "config-dir", 0, 0, G_OPTION_ARG_FILENAME, &cli->config_dir, N_("Config directory location"), N_(DEFAULT_CONFIG_DIR) },
+			{ "no-auto-default", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_FILENAME, &cli->no_auto_default_file, N_("State file for no-auto-default devices"), N_(DEFAULT_NO_AUTO_DEFAULT_FILE) },
+			{ "plugins", 0, 0, G_OPTION_ARG_STRING, &cli->plugins, N_("List of plugins separated by ','"), N_(CONFIG_PLUGINS_DEFAULT) },
+			{ "configure-and-quit", 0, 0, G_OPTION_ARG_NONE, &cli->configure_and_quit, N_("Quit after initial configuration"), NULL },
+
+				/* These three are hidden for now, and should eventually just go away. */
+			{ "connectivity-uri", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &cli->connectivity_uri, N_("An http(s) address for checking internet connectivity"), "http://example.com" },
+			{ "connectivity-interval", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_INT, &cli->connectivity_interval, N_("The interval between connectivity checks (in seconds)"), G_STRINGIFY (NM_CONFIG_DEFAULT_CONNECTIVITY_INTERVAL) },
+			{ "connectivity-response", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &cli->connectivity_response, N_("The expected start of the response"), N_(NM_CONFIG_DEFAULT_CONNECTIVITY_RESPONSE) },
+			{ 0 },
+		};
+
+		g_option_context_add_main_entries (opt_ctx, config_options, NULL);
+	}
 }
 
 /************************************************************************/
 
+GKeyFile *
+nm_config_create_keyfile ()
+{
+	GKeyFile *keyfile;
+
+	keyfile = g_key_file_new ();
+	g_key_file_set_list_separator (keyfile, NM_CONFIG_KEYFILE_LIST_SEPARATOR);
+	return keyfile;
+}
+
+static int
+_sort_groups_cmp (const char **pa, const char **pb, gpointer dummy)
+{
+	const char *a, *b;
+	gboolean a_is_connection, b_is_connection;
+
+	/* basic NULL checking... */
+	if (pa == pb)
+		return 0;
+	if (!pa)
+		return -1;
+	if (!pb)
+		return 1;
+
+	a = *pa;
+	b = *pb;
+
+	a_is_connection = g_str_has_prefix (a, NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION);
+	b_is_connection = g_str_has_prefix (b, NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION);
+
+	if (a_is_connection != b_is_connection) {
+		/* one is a [connection*] entry, the other not. We sort [connection*] entires
+		 * after.  */
+		if (a_is_connection)
+			return 1;
+		return -1;
+	}
+	if (!a_is_connection) {
+		/* both are non-connection entries. Don't reorder. */
+		return 0;
+	}
+
+	/* both are [connection.\+] entires. Reverse their order.
+	 * One of the sections might be literally [connection]. That section
+	 * is special and it's order will be fixed later. It doesn't actually
+	 * matter here how it compares with [connection.\+] sections. */
+	return pa > pb ? -1 : 1;
+}
+
 static gboolean
-read_config (NMConfig *config, const char *path, GError **error)
+_setting_is_device_spec (const char *group, const char *key)
+{
+#define _IS(group_v, key_v) (strcmp (group, (""group_v)) == 0 && strcmp (key, (""key_v)) == 0)
+	return    _IS (NM_CONFIG_KEYFILE_GROUP_MAIN, "no-auto-default")
+	       || _IS (NM_CONFIG_KEYFILE_GROUP_MAIN, "ignore-carrier")
+	       || _IS (NM_CONFIG_KEYFILE_GROUP_MAIN, "assume-ipv6ll-only")
+	       || _IS (NM_CONFIG_KEYFILE_GROUP_KEYFILE, "unmanaged-devices")
+	       || (g_str_has_prefix (group, NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION) && !strcmp (key, "match-device"));
+}
+
+static gboolean
+_setting_is_string_list (const char *group, const char *key)
+{
+	return    _IS (NM_CONFIG_KEYFILE_GROUP_MAIN, "plugins")
+	       || _IS (NM_CONFIG_KEYFILE_GROUP_MAIN, "debug")
+	       || _IS (NM_CONFIG_KEYFILE_GROUP_LOGGING, "domains")
+	       || g_str_has_prefix (group, NM_CONFIG_KEYFILE_GROUPPREFIX_TEST_APPEND_STRINGLIST);
+#undef _IS
+}
+
+static gboolean
+read_config (GKeyFile *keyfile, const char *path, GError **error)
 {
-	NMConfigPrivate *priv = NM_CONFIG_GET_PRIVATE (config);
 	GKeyFile *kf;
 	char **groups, **keys;
 	gsize ngroups, nkeys;
 	int g, k;
 
+	g_return_val_if_fail (keyfile, FALSE);
+	g_return_val_if_fail (path, FALSE);
+	g_return_val_if_fail (!error || !*error, FALSE);
+
 	if (g_file_test (path, G_FILE_TEST_EXISTS) == FALSE) {
 		g_set_error (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_NOT_FOUND, "file %s not found", path);
 		return FALSE;
@@ -392,8 +568,7 @@ read_config (NMConfig *config, const char *path, GError **error)
 
 	nm_log_dbg (LOGD_SETTINGS, "Reading config file '%s'", path);
 
-	kf = g_key_file_new ();
-	g_key_file_set_list_separator (kf, ',');
+	kf = nm_config_create_keyfile ();
 	if (!g_key_file_load_from_file (kf, path, G_KEY_FILE_NONE, error)) {
 		g_key_file_free (kf);
 		return FALSE;
@@ -401,36 +576,116 @@ read_config (NMConfig *config, const char *path, GError **error)
 
 	/* Override the current settings with the new ones */
 	groups = g_key_file_get_groups (kf, &ngroups);
-	for (g = 0; groups[g]; g++) {
-		keys = g_key_file_get_keys (kf, groups[g], &nkeys, NULL);
+	if (!groups)
+		ngroups = 0;
+
+	/* Within one file we reverse the order of the '[connection.\+] sections.
+	 * Here we merge the current file (@kf) into @keyfile. As we merge multiple
+	 * files, earlier sections (with lower priority) will be added first.
+	 * But within one file, we want a top-to-bottom order. This means we
+	 * must reverse the order within each file.
+	 * At the very end, we will revert the order of all sections again and
+	 * get thus the right behavior. This final reversing is done in
+	 * NMConfigData:_get_connection_infos().  */
+	if (ngroups > 1) {
+		g_qsort_with_data (groups,
+		                   ngroups,
+		                   sizeof (char *),
+		                   (GCompareDataFunc) _sort_groups_cmp,
+		                   NULL);
+	}
+
+	for (g = 0; groups && groups[g]; g++) {
+		const char *group = groups[g];
+
+		keys = g_key_file_get_keys (kf, group, &nkeys, NULL);
 		if (!keys)
 			continue;
 		for (k = 0; keys[k]; k++) {
-			int len = strlen (keys[k]);
-			char *v;
-
-			if (keys[k][len - 1] == '+') {
-				char *base_key = g_strndup (keys[k], len - 1);
-				char *old_val = g_key_file_get_value (priv->keyfile, groups[g], base_key, NULL);
-				char *new_val = g_key_file_get_value (kf, groups[g], keys[k], NULL);
-
-				if (old_val && *old_val) {
-					char *combined = g_strconcat (old_val, ",", new_val, NULL);
-
-					g_key_file_set_value (priv->keyfile, groups[g], base_key, combined);
-					g_free (combined);
-				} else
-					g_key_file_set_value (priv->keyfile, groups[g], base_key, new_val);
-
-				g_free (base_key);
-				g_free (old_val);
-				g_free (new_val);
+			const char *key;
+			char *new_value;
+			char last_char;
+			gsize key_len;
+
+			key = keys[k];
+			g_assert (key && *key);
+			key_len = strlen (key);
+			last_char = key[key_len - 1];
+			if (   key_len > 1
+			    && (last_char == '+' || last_char == '-')) {
+				gs_free char *base_key = g_strndup (key, key_len - 1);
+				gboolean is_string_list;
+
+				is_string_list = _setting_is_string_list (group, base_key);
+
+				if (   is_string_list
+				    || _setting_is_device_spec (group, base_key)) {
+					gs_unref_ptrarray GPtrArray *new = g_ptr_array_new_with_free_func (g_free);
+					char **iter_val;
+					gs_strfreev  char **old_val = NULL;
+					gs_free char **new_val = NULL;
+
+					if (is_string_list) {
+						old_val = g_key_file_get_string_list (keyfile, group, base_key, NULL, NULL);
+						new_val = g_key_file_get_string_list (kf, group, key, NULL, NULL);
+					} else {
+						gs_free char *old_sval = nm_config_keyfile_get_value (keyfile, group, base_key, NM_CONFIG_GET_VALUE_TYPE_SPEC);
+						gs_free char *new_sval = nm_config_keyfile_get_value (kf, group, key, NM_CONFIG_GET_VALUE_TYPE_SPEC);
+						gs_free_slist GSList *old_specs = nm_match_spec_split (old_sval);
+						gs_free_slist GSList *new_specs = nm_match_spec_split (new_sval);
+
+						/* the key is a device spec. This is a special kind of string-list, that
+						 * we must split differently. */
+						old_val = _nm_utils_slist_to_strv (old_specs, FALSE);
+						new_val = _nm_utils_slist_to_strv (new_specs, FALSE);
+					}
+
+					/* merge the string lists, by omiting duplicates. */
+
+					for (iter_val = old_val; iter_val && *iter_val; iter_val++) {
+						if (   last_char != '-'
+						    || _nm_utils_strv_find_first (new_val, -1, *iter_val) < 0)
+							g_ptr_array_add (new, g_strdup (*iter_val));
+					}
+					for (iter_val = new_val; iter_val && *iter_val; iter_val++) {
+						/* don't add duplicates. That means an "option=a,b"; "option+=a,c" results in "option=a,b,c" */
+						if (   last_char == '+'
+						    && _nm_utils_strv_find_first (old_val, -1, *iter_val) < 0)
+							g_ptr_array_add (new, *iter_val);
+						else
+							g_free (*iter_val);
+					}
+
+					if (new->len > 0) {
+						if (is_string_list)
+							nm_config_keyfile_set_string_list (keyfile, group, base_key, (const char *const*) new->pdata, new->len);
+						else {
+							gs_free_slist GSList *specs = NULL;
+							gs_free char *specs_joined = NULL;
+
+							g_ptr_array_add (new, NULL);
+							specs = _nm_utils_strv_to_slist ((char **) new->pdata, FALSE);
+
+							specs_joined = nm_match_spec_join (specs);
+
+							g_key_file_set_value (keyfile, group, base_key, specs_joined);
+						}
+					} else {
+						if (is_string_list)
+							g_key_file_remove_key (keyfile, group, base_key, NULL);
+						else
+							g_key_file_set_value (keyfile, group, base_key, "");
+					}
+				} else {
+					/* For any other settings we don't support extending the option with +/-.
+					 * Just drop the key. */
+				}
 				continue;
 			}
 
-			g_key_file_set_value (priv->keyfile, groups[g], keys[k],
-			                      v = g_key_file_get_value (kf, groups[g], keys[k], NULL));
-			g_free (v);
+			new_value = g_key_file_get_value (kf, group, key, NULL);
+			g_key_file_set_value (keyfile, group, key, new_value);
+			g_free (new_value);
 		}
 		g_strfreev (keys);
 	}
@@ -441,16 +696,22 @@ read_config (NMConfig *config, const char *path, GError **error)
 }
 
 static gboolean
-find_base_config (NMConfig *config, GError **error)
+read_base_config (GKeyFile *keyfile,
+                  const char *cli_config_main_file,
+                  char **out_config_main_file,
+                  GError **error)
 {
-	NMConfigPrivate *priv = NM_CONFIG_GET_PRIVATE (config);
 	GError *my_error = NULL;
 
+	g_return_val_if_fail (keyfile, FALSE);
+	g_return_val_if_fail (out_config_main_file && !*out_config_main_file, FALSE);
+	g_return_val_if_fail (!error || !*error, FALSE);
+
 	/* Try a user-specified config file first */
-	if (cli_config_path) {
+	if (cli_config_main_file) {
 		/* Bad user-specific config file path is a hard error */
-		if (read_config (config, cli_config_path, error)) {
-			priv->nm_conf_path = g_strdup (cli_config_path);
+		if (read_config (keyfile, cli_config_main_file, error)) {
+			*out_config_main_file = g_strdup (cli_config_main_file);
 			return TRUE;
 		} else
 			return FALSE;
@@ -464,27 +725,27 @@ find_base_config (NMConfig *config, GError **error)
 	 */
 
 	/* Try deprecated nm-system-settings.conf first */
-	if (read_config (config, NM_OLD_SYSTEM_CONF_FILE, &my_error)) {
-		priv->nm_conf_path = g_strdup (NM_OLD_SYSTEM_CONF_FILE);
+	if (read_config (keyfile, DEFAULT_CONFIG_MAIN_FILE_OLD, &my_error)) {
+		*out_config_main_file = g_strdup (DEFAULT_CONFIG_MAIN_FILE_OLD);
 		return TRUE;
 	}
 
 	if (!g_error_matches (my_error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_NOT_FOUND)) {
 		nm_log_warn (LOGD_CORE, "Old default config file %s invalid: %s\n",
-		             NM_OLD_SYSTEM_CONF_FILE,
+		             DEFAULT_CONFIG_MAIN_FILE_OLD,
 		             my_error->message);
 	}
 	g_clear_error (&my_error);
 
 	/* Try the standard config file location next */
-	if (read_config (config, NM_DEFAULT_SYSTEM_CONF_FILE, &my_error)) {
-		priv->nm_conf_path = g_strdup (NM_DEFAULT_SYSTEM_CONF_FILE);
+	if (read_config (keyfile, DEFAULT_CONFIG_MAIN_FILE, &my_error)) {
+		*out_config_main_file = g_strdup (DEFAULT_CONFIG_MAIN_FILE);
 		return TRUE;
 	}
 
 	if (!g_error_matches (my_error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_NOT_FOUND)) {
 		nm_log_warn (LOGD_CORE, "Default config file %s invalid: %s\n",
-		             NM_DEFAULT_SYSTEM_CONF_FILE,
+		             DEFAULT_CONFIG_MAIN_FILE,
 		             my_error->message);
 		g_propagate_error (error, my_error);
 		return FALSE;
@@ -494,21 +755,12 @@ find_base_config (NMConfig *config, GError **error)
 	/* If for some reason no config file exists, use the default
 	 * config file path.
 	 */
-	priv->nm_conf_path = g_strdup (NM_DEFAULT_SYSTEM_CONF_FILE);
+	*out_config_main_file = g_strdup (DEFAULT_CONFIG_MAIN_FILE);
 	nm_log_info (LOGD_CORE, "No config file found or given; using %s\n",
-	             NM_DEFAULT_SYSTEM_CONF_FILE);
+	             DEFAULT_CONFIG_MAIN_FILE);
 	return TRUE;
 }
 
-/************************************************************************/
-
-NMConfig *
-nm_config_get (void)
-{
-	g_assert (singleton);
-	return singleton;
-}
-
 static int
 sort_asciibetically (gconstpointer a, gconstpointer b)
 {
@@ -518,114 +770,374 @@ sort_asciibetically (gconstpointer a, gconstpointer b)
 	return strcmp (s1, s2);
 }
 
-/* call this function only once! */
-NMConfig *
-nm_config_new (GError **error)
+static GPtrArray *
+_get_config_dir_files (const char *config_main_file,
+                       const char *config_dir,
+                       char **out_config_description)
 {
-	NMConfigPrivate *priv = NULL;
 	GFile *dir;
 	GFileEnumerator *direnum;
 	GFileInfo *info;
 	GPtrArray *confs;
-	const char *name;
-	int i;
 	GString *config_description;
+	const char *name;
+	guint i;
 
-	g_assert (!singleton);
-	singleton = NM_CONFIG (g_object_new (NM_TYPE_CONFIG, NULL));
-	priv = NM_CONFIG_GET_PRIVATE (singleton);
-
-	/* First read the base config file */
-	if (!find_base_config (singleton, error)) {
-		g_object_unref (singleton);
-		singleton = NULL;
-		return NULL;
-	}
-
-	/* Now read the overrides in the config dir */
-	if (cli_config_dir)
-		priv->config_dir = g_strdup (cli_config_dir);
-	else
-		priv->config_dir = g_strdup (NM_DEFAULT_SYSTEM_CONF_DIR);
+	g_return_val_if_fail (config_main_file, NULL);
+	g_return_val_if_fail (config_dir, NULL);
+	g_return_val_if_fail (out_config_description && !*out_config_description, NULL);
 
 	confs = g_ptr_array_new_with_free_func (g_free);
-	config_description = g_string_new (priv->nm_conf_path);
-	dir = g_file_new_for_path (priv->config_dir);
+	config_description = g_string_new (config_main_file);
+	dir = g_file_new_for_path (config_dir);
 	direnum = g_file_enumerate_children (dir, G_FILE_ATTRIBUTE_STANDARD_NAME, 0, NULL, NULL);
 	if (direnum) {
 		while ((info = g_file_enumerator_next_file (direnum, NULL, NULL))) {
 			name = g_file_info_get_name (info);
-			if (g_str_has_suffix (name, ".conf")) {
-				g_ptr_array_add (confs, g_build_filename (priv->config_dir, name, NULL));
-				if (confs->len == 1)
-					g_string_append (config_description, " and conf.d: ");
-				else
-					g_string_append (config_description, ", ");
-				g_string_append (config_description, name);
-			}
+			if (g_str_has_suffix (name, ".conf"))
+				g_ptr_array_add (confs, g_strdup (name));
 			g_object_unref (info);
 		}
 		g_object_unref (direnum);
 	}
 	g_object_unref (dir);
 
-	g_ptr_array_sort (confs, sort_asciibetically);
-	priv->config_description = g_string_free (config_description, FALSE);
+	if (confs->len > 0) {
+		g_ptr_array_sort (confs, sort_asciibetically);
+		g_string_append (config_description, " and conf.d: ");
+		for (i = 0; i < confs->len; i++) {
+			char *n = confs->pdata[i];
+
+			if (i > 0)
+				g_string_append (config_description, ", ");
+			g_string_append (config_description, n);
+			confs->pdata[i] = g_build_filename (config_dir, n, NULL);
+			g_free (n);
+		}
+	}
+
+	*out_config_description = g_string_free (config_description, FALSE);
+	return confs;
+}
+
+static GKeyFile *
+read_entire_config (const NMConfigCmdLineOptions *cli,
+                    const char *config_dir,
+                    char **out_config_main_file,
+                    char **out_config_description,
+                    GError **error)
+{
+	GKeyFile *keyfile = nm_config_create_keyfile ();
+	GPtrArray *confs;
+	guint i;
+	char *o_config_main_file = NULL;
+	char *o_config_description = NULL;
+	char **plugins_tmp;
+
+	g_return_val_if_fail (config_dir, NULL);
+	g_return_val_if_fail (out_config_main_file && !*out_config_main_file, FALSE);
+	g_return_val_if_fail (out_config_description && !*out_config_description, NULL);
+	g_return_val_if_fail (!error || !*error, FALSE);
+
+	/* First read the base config file */
+	if (!read_base_config (keyfile, cli ? cli->config_main_file : NULL, &o_config_main_file, error)) {
+		g_key_file_free (keyfile);
+		return NULL;
+	}
+
+	g_assert (o_config_main_file);
+
+	confs = _get_config_dir_files (o_config_main_file, config_dir, &o_config_description);
 	for (i = 0; i < confs->len; i++) {
-		if (!read_config (singleton, confs->pdata[i], error)) {
-			g_object_unref (singleton);
-			singleton = NULL;
-			break;
+		if (!read_config (keyfile, confs->pdata[i], error)) {
+			g_key_file_free (keyfile);
+			g_free (o_config_main_file);
+			g_free (o_config_description);
+			g_ptr_array_unref (confs);
+			return NULL;
 		}
 	}
 	g_ptr_array_unref (confs);
-	if (!singleton)
+
+	/* Merge settings from command line. They overwrite everything read from
+	 * config files. */
+
+	if (cli && cli->plugins && cli->plugins[0])
+		g_key_file_set_value (keyfile, NM_CONFIG_KEYFILE_GROUP_MAIN, "plugins", cli->plugins);
+	plugins_tmp = g_key_file_get_string_list (keyfile, NM_CONFIG_KEYFILE_GROUP_MAIN, "plugins", NULL, NULL);
+	if (!plugins_tmp) {
+		if (STRLEN (CONFIG_PLUGINS_DEFAULT) > 0)
+			g_key_file_set_value (keyfile, NM_CONFIG_KEYFILE_GROUP_MAIN, "plugins", CONFIG_PLUGINS_DEFAULT);
+	} else
+		g_strfreev (plugins_tmp);
+
+	if (cli && cli->configure_and_quit)
+		g_key_file_set_boolean (keyfile, NM_CONFIG_KEYFILE_GROUP_MAIN, "configure-and-quit", TRUE);
+
+	if (cli && cli->connectivity_uri && cli->connectivity_uri[0])
+		g_key_file_set_string (keyfile, NM_CONFIG_KEYFILE_GROUP_CONNECTIVITY, "uri", cli->connectivity_uri);
+	if (cli && cli->connectivity_interval >= 0)
+		g_key_file_set_integer (keyfile, NM_CONFIG_KEYFILE_GROUP_CONNECTIVITY, "interval", cli->connectivity_interval);
+	if (cli && cli->connectivity_response && cli->connectivity_response[0])
+		g_key_file_set_string (keyfile, NM_CONFIG_KEYFILE_GROUP_CONNECTIVITY, "response", cli->connectivity_response);
+
+	*out_config_main_file = o_config_main_file;
+	*out_config_description = o_config_description;
+	return keyfile;
+}
+
+GSList *
+nm_config_get_device_match_spec (const GKeyFile *keyfile, const char *group, const char *key, gboolean *out_has_key)
+{
+	gs_free char *value = NULL;
+
+	/* nm_match_spec_split() already supports full escaping and is basically
+	 * a modified version of g_key_file_parse_value_as_string(). So we first read
+	 * the raw value (g_key_file_get_value()), and do the parsing ourselves. */
+	value = g_key_file_get_value ((GKeyFile *) keyfile, group, key, NULL);
+	if (out_has_key)
+		*out_has_key = !!value;
+	return nm_match_spec_split (value);
+}
+
+/************************************************************************/
+
+void
+nm_config_reload (NMConfig *self, int signal)
+{
+	NMConfigPrivate *priv;
+	GError *error = NULL;
+	GKeyFile *keyfile;
+	NMConfigData *new_data = NULL;
+	char *config_main_file = NULL;
+	char *config_description = NULL;
+	gs_strfreev char **no_auto_default = NULL;
+
+	g_return_if_fail (NM_IS_CONFIG (self));
+
+	priv = NM_CONFIG_GET_PRIVATE (self);
+
+	if (signal != SIGHUP) {
+		_set_config_data (self, NULL, signal);
+		return;
+	}
+
+	/* pass on the original command line options. This means, that
+	 * options specified at command line cannot ever be reloaded from
+	 * file. That seems desirable.
+	 */
+	keyfile = read_entire_config (&priv->cli,
+	                              priv->config_dir,
+	                              &config_main_file,
+	                              &config_description,
+	                              &error);
+	if (!keyfile) {
+		nm_log_err (LOGD_CORE, "Failed to reload the configuration: %s", error->message);
+		g_clear_error (&error);
+		_set_config_data (self, NULL, signal);
+		return;
+	}
+	no_auto_default = no_auto_default_from_file (priv->no_auto_default_file);
+
+	new_data = nm_config_data_new (config_main_file, config_description, (const char *const*) no_auto_default, keyfile);
+	g_free (config_main_file);
+	g_free (config_description);
+	g_key_file_unref (keyfile);
+
+	_set_config_data (self, new_data, signal);
+}
+
+static const char *
+_change_flags_one_to_string (NMConfigChangeFlags flag)
+{
+	switch (flag) {
+	case NM_CONFIG_CHANGE_SIGHUP:
+		return "SIGHUP";
+	case NM_CONFIG_CHANGE_SIGUSR1:
+		return "SIGUSR1";
+	case NM_CONFIG_CHANGE_SIGUSR2:
+		return "SIGUSR2";
+	case NM_CONFIG_CHANGE_CONFIG_FILES:
+		return "config-files";
+	case NM_CONFIG_CHANGE_VALUES:
+		return "values";
+	case NM_CONFIG_CHANGE_CONNECTIVITY:
+		return "connectivity";
+	case NM_CONFIG_CHANGE_NO_AUTO_DEFAULT:
+		return "no-auto-default";
+	case NM_CONFIG_CHANGE_DNS_MODE:
+		return "dns-mode";
+	default:
+		g_return_val_if_reached ("unknown");
+	}
+}
+
+char *
+nm_config_change_flags_to_string (NMConfigChangeFlags flags)
+{
+	GString *str = g_string_new ("");
+	NMConfigChangeFlags s = 0x01;
+
+	while (flags) {
+		if (NM_FLAGS_HAS (flags, s)) {
+			if (str->len)
+				g_string_append_c (str, ',');
+			g_string_append (str, _change_flags_one_to_string (s));
+		}
+		flags = flags & ~s;
+		s <<= 1;
+	}
+	return g_string_free (str, FALSE);
+}
+
+static void
+_set_config_data (NMConfig *self, NMConfigData *new_data, int signal)
+{
+	NMConfigPrivate *priv = NM_CONFIG_GET_PRIVATE (self);
+	NMConfigData *old_data = priv->config_data;
+	NMConfigChangeFlags changes, changes_diff;
+	gs_free char *log_str = NULL;
+	gboolean had_new_data = !!new_data;
+
+	switch (signal) {
+	case SIGHUP:
+		changes = NM_CONFIG_CHANGE_SIGHUP;
+		break;
+	case SIGUSR1:
+		changes = NM_CONFIG_CHANGE_SIGUSR1;
+		break;
+	case SIGUSR2:
+		changes = NM_CONFIG_CHANGE_SIGUSR2;
+		break;
+	default:
+		changes = NM_CONFIG_CHANGE_NONE;
+		break;
+	}
+
+	if (new_data) {
+		changes_diff = nm_config_data_diff (old_data, new_data);
+		if (changes_diff == NM_CONFIG_CHANGE_NONE)
+			g_clear_object (&new_data);
+		else
+			changes |= changes_diff;
+	}
+
+	if (changes == NM_CONFIG_CHANGE_NONE)
+		return;
+
+	if (new_data) {
+		nm_log_info (LOGD_CORE, "config: update %s (%s)", nm_config_data_get_config_description (new_data),
+		             (log_str = nm_config_change_flags_to_string (changes)));
+		nm_config_data_log (new_data, "CONFIG: ");
+		priv->config_data = new_data;
+	} else if (had_new_data)
+		nm_log_info (LOGD_CORE, "config: signal %s (no changes from disk)", (log_str = nm_config_change_flags_to_string (changes)));
+	else
+		nm_log_info (LOGD_CORE, "config: signal %s", (log_str = nm_config_change_flags_to_string (changes)));
+	g_signal_emit (self, signals[SIGNAL_CONFIG_CHANGED], 0,
+	               new_data ? new_data : old_data,
+	               changes, old_data);
+	if (new_data)
+		g_object_unref (old_data);
+}
+
+NM_DEFINE_SINGLETON_DESTRUCTOR (NMConfig);
+NM_DEFINE_SINGLETON_WEAK_REF (NMConfig);
+
+NMConfig *
+nm_config_get (void)
+{
+	g_assert (singleton_instance);
+	return singleton_instance;
+}
+
+NMConfig *
+nm_config_setup (const NMConfigCmdLineOptions *cli, GError **error)
+{
+	g_assert (!singleton_instance);
+
+	singleton_instance = nm_config_new (cli, error);
+	if (singleton_instance)
+		nm_singleton_instance_weak_ref_register ();
+	return singleton_instance;
+}
+
+static gboolean
+init_sync (GInitable *initable, GCancellable *cancellable, GError **error)
+{
+	NMConfig *self = NM_CONFIG (initable);
+	NMConfigPrivate *priv = NM_CONFIG_GET_PRIVATE (self);
+	GKeyFile *keyfile;
+	char *config_main_file = NULL;
+	char *config_description = NULL;
+	gs_strfreev char **no_auto_default = NULL;
+
+	if (priv->config_dir) {
+		/* Object is already initialized. */
+		if (priv->config_data)
+			return TRUE;
+		g_set_error (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_NOT_FOUND, "unspecified error");
+		return FALSE;
+	}
+
+	if (priv->cli.config_dir)
+		priv->config_dir = g_strdup (priv->cli.config_dir);
+	else
+		priv->config_dir = g_strdup (DEFAULT_CONFIG_DIR);
+
+	keyfile = read_entire_config (&priv->cli,
+	                              priv->config_dir,
+	                              &config_main_file,
+	                              &config_description,
+	                              error);
+	if (!keyfile)
 		return FALSE;
 
-	/* Handle no-auto-default key and state file */
-	priv->no_auto_default = g_key_file_get_string_list (priv->keyfile, "main", "no-auto-default", NULL, NULL);
-	if (cli_no_auto_default_file)
-		priv->no_auto_default_file = g_strdup (cli_no_auto_default_file);
+	/* Initialize read only private members */
+
+	if (priv->cli.no_auto_default_file)
+		priv->no_auto_default_file = g_strdup (priv->cli.no_auto_default_file);
 	else
-		priv->no_auto_default_file = g_strdup (NM_NO_AUTO_DEFAULT_STATE_FILE);
-	merge_no_auto_default_state (singleton);
+		priv->no_auto_default_file = g_strdup (DEFAULT_NO_AUTO_DEFAULT_FILE);
 
-	/* Now let command-line options override the config files, and fill in priv. */
-	if (cli_plugins && cli_plugins[0])
-		g_key_file_set_value (priv->keyfile, "main", "plugins", cli_plugins);
-	priv->plugins = g_key_file_get_string_list (priv->keyfile, "main", "plugins", NULL, NULL);
-	if (!priv->plugins && STRLEN (CONFIG_PLUGINS_DEFAULT) > 0)
-		priv->plugins = g_strsplit (CONFIG_PLUGINS_DEFAULT, ",", -1);
+	priv->plugins = _nm_utils_strv_cleanup (g_key_file_get_string_list (keyfile, NM_CONFIG_KEYFILE_GROUP_MAIN, "plugins", NULL, NULL),
+	                                        TRUE, TRUE, TRUE);
+	if (!priv->plugins)
+		priv->plugins = g_new0 (char *, 1);
 
-	priv->monitor_connection_files = _get_bool_value (priv->keyfile, "main", "monitor-connection-files", FALSE);
+	priv->monitor_connection_files = nm_config_keyfile_get_boolean (keyfile, NM_CONFIG_KEYFILE_GROUP_MAIN, "monitor-connection-files", FALSE);
 
-	priv->auth_polkit = _get_bool_value (priv->keyfile, "main", "auth-polkit", NM_CONFIG_DEFAULT_AUTH_POLKIT);
+	priv->auth_polkit = nm_config_keyfile_get_boolean (keyfile, NM_CONFIG_KEYFILE_GROUP_MAIN, "auth-polkit", NM_CONFIG_DEFAULT_AUTH_POLKIT);
 
-	priv->dhcp_client = g_key_file_get_value (priv->keyfile, "main", "dhcp", NULL);
-	priv->dns_mode = g_key_file_get_value (priv->keyfile, "main", "dns", NULL);
+	priv->dhcp_client = nm_strstrip (g_key_file_get_string (keyfile, NM_CONFIG_KEYFILE_GROUP_MAIN, "dhcp", NULL));
 
-	priv->log_level = g_key_file_get_value (priv->keyfile, "logging", "level", NULL);
-	priv->log_domains = g_key_file_get_value (priv->keyfile, "logging", "domains", NULL);
+	priv->log_level = nm_strstrip (g_key_file_get_string (keyfile, NM_CONFIG_KEYFILE_GROUP_LOGGING, "level", NULL));
+	priv->log_domains = nm_strstrip (g_key_file_get_string (keyfile, NM_CONFIG_KEYFILE_GROUP_LOGGING, "domains", NULL));
 
-	priv->debug = g_key_file_get_value (priv->keyfile, "main", "debug", NULL);
+	priv->debug = g_key_file_get_string (keyfile, NM_CONFIG_KEYFILE_GROUP_MAIN, "debug", NULL);
 
-	if (cli_connectivity_uri && cli_connectivity_uri[0])
-		g_key_file_set_value (priv->keyfile, "connectivity", "uri", cli_connectivity_uri);
-	priv->connectivity_uri = g_key_file_get_value (priv->keyfile, "connectivity", "uri", NULL);
+	priv->configure_and_quit = nm_config_keyfile_get_boolean (keyfile, NM_CONFIG_KEYFILE_GROUP_MAIN, "configure-and-quit", FALSE);
 
-	if (cli_connectivity_interval >= 0)
-		g_key_file_set_integer (priv->keyfile, "connectivity", "interval", cli_connectivity_interval);
-	priv->connectivity_interval = g_key_file_get_integer (priv->keyfile, "connectivity", "interval", NULL);
+	no_auto_default = no_auto_default_from_file (priv->no_auto_default_file);
 
-	if (cli_connectivity_response && cli_connectivity_response[0])
-		g_key_file_set_value (priv->keyfile, "connectivity", "response", cli_connectivity_response);
-	priv->connectivity_response = g_key_file_get_value (priv->keyfile, "connectivity", "response", NULL);
+	priv->config_data_orig = nm_config_data_new (config_main_file, config_description, (const char *const*) no_auto_default, keyfile);
 
-	priv->ignore_carrier = g_key_file_get_string_list (priv->keyfile, "main", "ignore-carrier", NULL, NULL);
+	priv->config_data = g_object_ref (priv->config_data_orig);
 
-	priv->configure_and_quit = _get_bool_value (priv->keyfile, "main", "configure-and-quit", FALSE);
+	g_free (config_main_file);
+	g_free (config_description);
+	g_key_file_unref (keyfile);
+	return TRUE;
+}
 
-	return singleton;
+NMConfig *
+nm_config_new (const NMConfigCmdLineOptions *cli, GError **error)
+{
+	return NM_CONFIG (g_initable_new (NM_TYPE_CONFIG,
+	                                  NULL,
+	                                  error,
+	                                  NM_CONFIG_CMD_LINE_OPTIONS, cli,
+	                                  NULL));
 }
 
 static void
@@ -634,11 +1146,6 @@ nm_config_init (NMConfig *config)
 	NMConfigPrivate *priv = NM_CONFIG_GET_PRIVATE (config);
 
 	priv->auth_polkit = NM_CONFIG_DEFAULT_AUTH_POLKIT;
-
-	priv->keyfile = g_key_file_new ();
-	g_key_file_set_list_separator (priv->keyfile, ',');
-
-	priv->connectivity_interval = -1;
 }
 
 static void
@@ -646,34 +1153,44 @@ finalize (GObject *gobject)
 {
 	NMConfigPrivate *priv = NM_CONFIG_GET_PRIVATE (gobject);
 
-	g_free (priv->nm_conf_path);
 	g_free (priv->config_dir);
-	g_free (priv->config_description);
 	g_free (priv->no_auto_default_file);
-	g_clear_pointer (&priv->keyfile, g_key_file_unref);
 	g_strfreev (priv->plugins);
 	g_free (priv->dhcp_client);
-	g_free (priv->dns_mode);
 	g_free (priv->log_level);
 	g_free (priv->log_domains);
 	g_free (priv->debug);
-	g_free (priv->connectivity_uri);
-	g_free (priv->connectivity_response);
-	g_strfreev (priv->no_auto_default);
-	g_strfreev (priv->ignore_carrier);
 
-	singleton = NULL;
+	_nm_config_cmd_line_options_clear (&priv->cli);
 
-	g_clear_pointer (&cli_config_path, g_free);
-	g_clear_pointer (&cli_config_dir, g_free);
-	g_clear_pointer (&cli_no_auto_default_file, g_free);
-	g_clear_pointer (&cli_plugins, g_free);
-	g_clear_pointer (&cli_connectivity_uri, g_free);
-	g_clear_pointer (&cli_connectivity_response, g_free);
+	g_clear_object (&priv->config_data);
+	g_clear_object (&priv->config_data_orig);
 
 	G_OBJECT_CLASS (nm_config_parent_class)->finalize (gobject);
 }
 
+static void
+set_property (GObject *object, guint prop_id,
+              const GValue *value, GParamSpec *pspec)
+{
+	NMConfig *self = NM_CONFIG (object);
+	NMConfigPrivate *priv = NM_CONFIG_GET_PRIVATE (self);
+	NMConfigCmdLineOptions *cli;
+
+	switch (prop_id) {
+	case PROP_CMD_LINE_OPTIONS:
+		/* construct only */
+		cli = g_value_get_pointer (value);
+		if (!cli)
+			_nm_config_cmd_line_options_clear (&priv->cli);
+		else
+			_nm_config_cmd_line_options_copy (cli, &priv->cli);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
 
 static void
 nm_config_class_init (NMConfigClass *config_class)
@@ -682,5 +1199,27 @@ nm_config_class_init (NMConfigClass *config_class)
 
 	g_type_class_add_private (config_class, sizeof (NMConfigPrivate));
 	object_class->finalize = finalize;
+	object_class->set_property = set_property;
+
+	g_object_class_install_property
+	    (object_class, PROP_CMD_LINE_OPTIONS,
+	     g_param_spec_pointer (NM_CONFIG_CMD_LINE_OPTIONS, "", "",
+	                           G_PARAM_WRITABLE |
+	                           G_PARAM_CONSTRUCT_ONLY |
+	                           G_PARAM_STATIC_STRINGS));
+
+	signals[SIGNAL_CONFIG_CHANGED] =
+	    g_signal_new (NM_CONFIG_SIGNAL_CONFIG_CHANGED,
+	                  G_OBJECT_CLASS_TYPE (object_class),
+	                  G_SIGNAL_RUN_FIRST,
+	                  G_STRUCT_OFFSET (NMConfigClass, config_changed),
+	                  NULL, NULL, NULL,
+	                  G_TYPE_NONE, 3, NM_TYPE_CONFIG_DATA, NM_TYPE_CONFIG_CHANGE_FLAGS, NM_TYPE_CONFIG_DATA);
+}
+
+static void
+nm_config_initable_iface_init (GInitableIface *iface)
+{
+	iface->init = init_sync;
 }