summary refs log tree commit diff
path: root/src/nm-config.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nm-config.c')
-rw-r--r--src/nm-config.c283
1 files changed, 254 insertions, 29 deletions
diff --git a/src/nm-config.c b/src/nm-config.c
index 247b9a89..4bc4f483 100644
--- a/src/nm-config.c
+++ b/src/nm-config.c
@@ -39,12 +39,27 @@
 #define DEFAULT_SYSTEM_CONFIG_DIR       NMLIBDIR  "/conf.d"
 #define DEFAULT_NO_AUTO_DEFAULT_FILE    NMSTATEDIR "/no-auto-default.state"
 #define DEFAULT_INTERN_CONFIG_FILE      NMSTATEDIR "/NetworkManager-intern.conf"
+#define DEFAULT_STATE_FILE              NMSTATEDIR "/NetworkManager.state"
+
+/*****************************************************************************/
+
+#define _NMLOG_PREFIX_NAME                "config"
+#define _NMLOG_DOMAIN                     LOGD_CORE
+
+#define _NMLOG(level, ...) \
+	nm_log (level, _NMLOG_DOMAIN, \
+	        "%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
+	        _NMLOG_PREFIX_NAME \
+	        _NM_UTILS_MACRO_REST(__VA_ARGS__))
+
+/*****************************************************************************/
 
 struct NMConfigCmdLineOptions {
 	char *config_main_file;
 	char *intern_config_file;
 	char *config_dir;
 	char *system_config_dir;
+	char *state_file;
 	char *no_auto_default_file;
 	char *plugins;
 	gboolean configure_and_quit;
@@ -59,6 +74,10 @@ struct NMConfigCmdLineOptions {
 };
 
 typedef struct {
+	NMConfigState p;
+} State;
+
+typedef struct {
 	NMConfigCmdLineOptions cli;
 
 	NMConfigData *config_data;
@@ -82,6 +101,19 @@ typedef struct {
 	gboolean configure_and_quit;
 
 	char **atomic_section_prefixes;
+
+	/* The state. This is actually a mutable data member and it makes sense:
+	 * The regular config is immutable (NMConfigData) and can old be swapped
+	 * as a whole (via nm_config_set_values() or during reload). Thus, it can
+	 * be changed, but it is still immutable and is swapped atomically as a
+	 * whole. Also, we emit a config-changed signal on that occasion.
+	 *
+	 * For state, there are no events. You can query it and set it.
+	 * It only gets read *once* at startup, and later is cached and only
+	 * written out to disk. Hence, no need for the immutable dance here
+	 * because the state changes only on explicit actions from the daemon
+	 * itself. */
+	State *state;
 } NMConfigPrivate;
 
 enum {
@@ -367,7 +399,9 @@ nm_config_set_no_auto_default_for_device (NMConfig *self, NMDevice *device)
 
 	priv = NM_CONFIG_GET_PRIVATE (self);
 
-	hw_address = nm_device_get_hw_address (device);
+	hw_address = nm_device_get_permanent_hw_address (device, FALSE);
+	if (!hw_address)
+		return;
 
 	no_auto_default_current = nm_config_data_get_no_auto_default (priv->config_data);
 
@@ -407,6 +441,7 @@ _nm_config_cmd_line_options_clear (NMConfigCmdLineOptions *cli)
 	g_clear_pointer (&cli->system_config_dir, g_free);
 	g_clear_pointer (&cli->no_auto_default_file, g_free);
 	g_clear_pointer (&cli->intern_config_file, g_free);
+	g_clear_pointer (&cli->state_file, g_free);
 	g_clear_pointer (&cli->plugins, g_free);
 	cli->configure_and_quit = FALSE;
 	cli->is_debug = FALSE;
@@ -428,6 +463,7 @@ _nm_config_cmd_line_options_copy (const NMConfigCmdLineOptions *cli, NMConfigCmd
 	dst->config_main_file = g_strdup (cli->config_main_file);
 	dst->no_auto_default_file = g_strdup (cli->no_auto_default_file);
 	dst->intern_config_file = g_strdup (cli->intern_config_file);
+	dst->state_file = g_strdup (cli->state_file);
 	dst->plugins = g_strdup (cli->plugins);
 	dst->configure_and_quit = cli->configure_and_quit;
 	dst->is_debug = cli->is_debug;
@@ -467,6 +503,7 @@ nm_config_cmd_line_options_add_to_entries (NMConfigCmdLineOptions *cli,
 			{ "config-dir", 0, 0, G_OPTION_ARG_FILENAME, &cli->config_dir, N_("Config directory location"), N_(DEFAULT_CONFIG_DIR) },
 			{ "system-config-dir", 0, 0, G_OPTION_ARG_FILENAME, &cli->system_config_dir, N_("System config directory location"), N_(DEFAULT_SYSTEM_CONFIG_DIR) },
 			{ "intern-config", 0, 0, G_OPTION_ARG_FILENAME, &cli->intern_config_file, N_("Internal config file location"), N_(DEFAULT_INTERN_CONFIG_FILE) },
+			{ "state-file", 0, 0, G_OPTION_ARG_FILENAME, &cli->state_file, N_("State file location"), N_(DEFAULT_STATE_FILE) },
 			{ "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 },
@@ -541,14 +578,7 @@ _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;
+	gboolean a_is_device, b_is_device;
 
 	a = *pa;
 	b = *pb;
@@ -563,16 +593,34 @@ _sort_groups_cmp (const char **pa, const char **pb, gpointer dummy)
 			return 1;
 		return -1;
 	}
-	if (!a_is_connection) {
-		/* both are non-connection entries. Don't reorder. */
-		return 0;
+	if (a_is_connection) {
+		/* 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;
 	}
 
-	/* 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;
+	a_is_device = g_str_has_prefix (a, NM_CONFIG_KEYFILE_GROUPPREFIX_DEVICE);
+	b_is_device = g_str_has_prefix (b, NM_CONFIG_KEYFILE_GROUPPREFIX_DEVICE);
+
+	if (a_is_device != b_is_device) {
+		/* one is a [device*] entry, the other not. We sort [device*] entires
+		 * after.  */
+		if (a_is_device)
+			return 1;
+		return -1;
+	}
+	if (a_is_device) {
+		/* both are [device.\+] entires. Reverse their order.
+		 * One of the sections might be literally [device]. That section
+		 * is special and it's order will be fixed later. It doesn't actually
+		 * matter here how it compares with [device.\+] sections. */
+		return pa > pb ? -1 : 1;
+	}
+
+	/* don't reorder the rest. */
+	return 0;
 }
 
 void
@@ -595,7 +643,8 @@ _setting_is_device_spec (const char *group, const char *key)
 	       || _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"));
+	       || (g_str_has_prefix (group, NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION) && !strcmp (key, "match-device"))
+	       || (g_str_has_prefix (group, NM_CONFIG_KEYFILE_GROUPPREFIX_DEVICE    ) && !strcmp (key, "match-device"));
 }
 
 static gboolean
@@ -845,15 +894,6 @@ read_base_config (GKeyFile *keyfile,
 	return TRUE;
 }
 
-static int
-sort_asciibetically (gconstpointer a, gconstpointer b)
-{
-	const char *s1 = *(const char **)a;
-	const char *s2 = *(const char **)b;
-
-	return strcmp (s1, s2);
-}
-
 static GPtrArray *
 _get_config_dir_files (const char *config_dir)
 {
@@ -882,7 +922,7 @@ _get_config_dir_files (const char *config_dir)
 	}
 	g_object_unref (dir);
 
-	g_ptr_array_sort (confs, sort_asciibetically);
+	g_ptr_array_sort (confs, nm_strcmp_p);
 	return confs;
 }
 
@@ -1645,7 +1685,190 @@ nm_config_set_values (NMConfig *self,
 	g_key_file_unref (keyfile_new);
 }
 
-/************************************************************************/
+/******************************************************************************
+ * State
+ ******************************************************************************/
+
+static const char *
+state_get_filename (const NMConfigCmdLineOptions *cli)
+{
+	/* For an empty filename, we assume the user wants to disable
+	 * state. NMConfig will not try to read it nor write it out. */
+	if (!cli->state_file)
+		return DEFAULT_STATE_FILE;
+	return cli->state_file[0] ? cli->state_file : NULL;
+}
+
+static State *
+state_new (void)
+{
+	State *state;
+
+	state = g_slice_new0 (State);
+	state->p.net_enabled = TRUE;
+	state->p.wifi_enabled = TRUE;
+	state->p.wwan_enabled = TRUE;
+
+	return state;
+}
+
+static void
+state_free (State *state)
+{
+	if (!state)
+		return;
+	g_slice_free (State, state);
+}
+
+static State *
+state_new_from_file (const char *filename)
+{
+	GKeyFile *keyfile;
+	gs_free_error GError *error = NULL;
+	State *state;
+
+	state = state_new ();
+
+	if (!filename)
+		return state;
+
+	keyfile = g_key_file_new ();
+	g_key_file_set_list_separator (keyfile, ',');
+	if (!g_key_file_load_from_file (keyfile, filename, G_KEY_FILE_NONE, &error)) {
+		if (g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT))
+			_LOGD ("state: missing state file \"%s\": %s", filename, error->message);
+		else
+			_LOGW ("state: error reading state file \"%s\": %s", filename, error->message);
+		goto out;
+	}
+
+	_LOGD ("state: successfully read state file \"%s\"", filename);
+
+	state->p.net_enabled  = nm_config_keyfile_get_boolean (keyfile, "main", "NetworkingEnabled", state->p.net_enabled);
+	state->p.wifi_enabled = nm_config_keyfile_get_boolean (keyfile, "main", "WirelessEnabled", state->p.wifi_enabled);
+	state->p.wwan_enabled = nm_config_keyfile_get_boolean (keyfile, "main", "WWANEnabled", state->p.wwan_enabled);
+
+out:
+	g_key_file_unref (keyfile);
+	return state;
+}
+
+const NMConfigState *
+nm_config_state_get (NMConfig *self)
+{
+	NMConfigPrivate *priv;
+
+	g_return_val_if_fail (NM_IS_CONFIG (self), NULL);
+
+	priv = NM_CONFIG_GET_PRIVATE (self);
+
+	if (G_UNLIKELY (!priv->state)) {
+		/* read the state from file lazy on first access. The reason is that
+		 * we want to log a failure to read the file via nm-logging.
+		 *
+		 * So we cannot read the state during construction of NMConfig,
+		 * because at that time nm-logging is not yet configured.
+		 */
+		priv->state = state_new_from_file (state_get_filename (&priv->cli));
+	}
+
+	return &priv->state->p;
+}
+
+static void
+state_write (NMConfig *self)
+{
+	NMConfigPrivate *priv = NM_CONFIG_GET_PRIVATE (self);
+	const char *filename;
+	GString *str;
+	GError *error = NULL;
+
+	filename = state_get_filename (&priv->cli);
+
+	if (!filename) {
+		priv->state->p.dirty = FALSE;
+		return;
+	}
+
+	str = g_string_sized_new (256);
+
+	/* Let's construct the keyfile data by hand. */
+
+	g_string_append (str, "[main]\n");
+	g_string_append_printf (str, "NetworkingEnabled=%s\n", priv->state->p.net_enabled ? "true" : "false");
+	g_string_append_printf (str, "WirelessEnabled=%s\n", priv->state->p.wifi_enabled ? "true" : "false");
+	g_string_append_printf (str, "WWANEnabled=%s\n", priv->state->p.wwan_enabled ? "true" : "false");
+
+	if (!g_file_set_contents (filename,
+	                          str->str, str->len,
+	                          &error)) {
+		_LOGD ("state: error writing state file \"%s\": %s", filename, error->message);
+		g_clear_error (&error);
+		/* we leave the state dirty. That potentally means, that we try to
+		 * write the file over and over again, although it isn't possible. */
+		priv->state->p.dirty = TRUE;
+	} else
+		priv->state->p.dirty = FALSE;
+
+	_LOGT ("state: success writing state file \"%s\"", filename);
+
+	g_string_free (str, TRUE);
+}
+
+void
+_nm_config_state_set (NMConfig *self,
+                      gboolean allow_persist,
+                      gboolean force_persist,
+                      ...)
+{
+	NMConfigPrivate *priv;
+	va_list ap;
+	NMConfigRunStatePropertyType property_type;
+
+	g_return_if_fail (NM_IS_CONFIG (self));
+
+	priv = NM_CONFIG_GET_PRIVATE (self);
+
+	va_start (ap, force_persist);
+
+	/* We expect that the NMConfigRunStatePropertyType is an integer type <= sizeof (int).
+	 * Smaller would be fine, since the variadic arguments get promoted to int.
+	 * Larger would be a problem, also, because we want that "0" is a valid sentinel. */
+	G_STATIC_ASSERT_EXPR (sizeof (NMConfigRunStatePropertyType) <= sizeof (int));
+
+	while ((property_type = va_arg (ap, int)) != NM_CONFIG_STATE_PROPERTY_NONE) {
+		bool *p_bool, v_bool;
+
+		switch (property_type) {
+		case NM_CONFIG_STATE_PROPERTY_NETWORKING_ENABLED:
+			p_bool = &priv->state->p.net_enabled;
+			break;
+		case NM_CONFIG_STATE_PROPERTY_WIFI_ENABLED:
+			p_bool = &priv->state->p.wifi_enabled;
+			break;
+		case NM_CONFIG_STATE_PROPERTY_WWAN_ENABLED:
+			p_bool = &priv->state->p.wwan_enabled;
+			break;
+		default:
+			va_end (ap);
+			g_return_if_reached ();
+		}
+
+		v_bool = va_arg (ap, gboolean);
+		if (*p_bool == v_bool)
+			continue;
+		*p_bool = v_bool;
+		priv->state->p.dirty = TRUE;
+	}
+
+	va_end (ap);
+
+	if (   allow_persist
+	    && (force_persist || priv->state->p.dirty))
+		state_write (self);
+}
+
+/*****************************************************************************/
 
 void
 nm_config_reload (NMConfig *self, NMConfigChangeFlags reload_flags)
@@ -1929,6 +2152,8 @@ finalize (GObject *gobject)
 {
 	NMConfigPrivate *priv = NM_CONFIG_GET_PRIVATE (gobject);
 
+	state_free (priv->state);
+
 	g_free (priv->config_dir);
 	g_free (priv->system_config_dir);
 	g_free (priv->no_auto_default_file);