about summary refs log tree commit diff
path: root/libnm-core/nm-connection.c
diff options
context:
space:
mode:
Diffstat (limited to 'libnm-core/nm-connection.c')
-rw-r--r--libnm-core/nm-connection.c401
1 files changed, 313 insertions, 88 deletions
diff --git a/libnm-core/nm-connection.c b/libnm-core/nm-connection.c
index 3182e346..2532b64a 100644
--- a/libnm-core/nm-connection.c
+++ b/libnm-core/nm-connection.c
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -581,7 +579,8 @@ nm_connection_compare (NMConnection *a,
 	while (g_hash_table_iter_next (&iter, NULL, (gpointer) &src)) {
 		NMSetting *cmp = nm_connection_get_setting (b, G_OBJECT_TYPE (src));
 
-		if (!cmp || !nm_setting_compare (src, cmp, flags))
+		if (   !cmp
+		    || !_nm_setting_compare (a, src, b, cmp, flags))
 			return FALSE;
 	}
 
@@ -614,7 +613,7 @@ diff_one_connection (NMConnection *a,
 		if (results)
 			new_results = FALSE;
 
-		if (!nm_setting_diff (a_setting, b_setting, flags, invert_results, &results))
+		if (!_nm_setting_diff (a, a_setting, b, b_setting, flags, invert_results, &results))
 			diff_found = TRUE;
 
 		if (new_results && results)
@@ -1024,8 +1023,9 @@ _normalize_ip_config (NMConnection *self, GHashTable *parameters)
 				changed = TRUE;
 			}
 
-			if (   nm_streq0 (nm_setting_ip_config_get_method (s_ip6),
-			                  NM_SETTING_IP6_CONFIG_METHOD_IGNORE)
+			if (   NM_IN_STRSET (nm_setting_ip_config_get_method (s_ip6),
+			                     NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+			                     NM_SETTING_IP6_CONFIG_METHOD_DISABLED)
 			    && !nm_setting_ip_config_get_may_fail (s_ip6)) {
 				g_object_set (s_ip6, NM_SETTING_IP_CONFIG_MAY_FAIL, TRUE, NULL);
 				changed = TRUE;
@@ -1585,63 +1585,29 @@ nm_connection_verify_secrets (NMConnection *connection, GError **error)
 	return TRUE;
 }
 
-/**
- * nm_connection_normalize:
- * @connection: the #NMConnection to normalize
- * @parameters: (allow-none) (element-type utf8 gpointer): a #GHashTable with
- * normalization parameters to allow customization of the normalization by providing
- * specific arguments. Unknown arguments will be ignored and the default will be
- * used. The keys must be strings compared with g_str_equal() function.
- * The values are opaque and depend on the parameter name.
- * @modified: (out) (allow-none): outputs whether any settings were modified.
- * @error: location to store error, or %NULL. Contains the reason,
- * why the connection is invalid, if the function returns an error.
- *
- * Does some basic normalization and fixup of well known inconsistencies
- * and deprecated fields. If the connection was modified in any way,
- * the output parameter @modified is set %TRUE.
- *
- * Finally the connection will be verified and %TRUE returns if the connection
- * is valid. As this function only performs some specific normalization steps
- * it cannot repair all connections. If the connection has errors that
- * cannot be normalized, the connection will not be modified.
- *
- * Returns: %TRUE if the connection is valid, %FALSE if it is not
- **/
-gboolean
-nm_connection_normalize (NMConnection *connection,
-                         GHashTable *parameters,
-                         gboolean *modified,
-                         GError **error)
+static gboolean
+_connection_normalize (NMConnection *connection,
+                       GHashTable *parameters,
+                       gboolean *modified,
+                       GError **error)
 {
 	NMSettingVerifyResult success;
-	gboolean was_modified = FALSE;
-	GError *normalizable_error = NULL;
-
-	success = _nm_connection_verify (connection, &normalizable_error);
-
-	if (success == NM_SETTING_VERIFY_ERROR ||
-	    success == NM_SETTING_VERIFY_SUCCESS) {
-		if (normalizable_error)
-			g_propagate_error (error, normalizable_error);
-		if (modified)
-			*modified = FALSE;
-		if (success == NM_SETTING_VERIFY_ERROR && error && !*error) {
-			g_set_error_literal (error,
-			                     NM_CONNECTION_ERROR,
-			                     NM_CONNECTION_ERROR_FAILED,
-			                     _("Unexpected failure to verify the connection"));
-			g_return_val_if_reached (FALSE);
-		}
-		return success == NM_SETTING_VERIFY_SUCCESS;
-	}
-	g_assert (success == NM_SETTING_VERIFY_NORMALIZABLE || success == NM_SETTING_VERIFY_NORMALIZABLE_ERROR);
-	g_clear_error (&normalizable_error);
+	gboolean was_modified;
+
+#if NM_MORE_ASSERTS > 10
+	/* only call this _nm_connection_verify() confirms that the connection
+	 * requires normalization and is normalizable. */
+	nm_assert (NM_IN_SET (_nm_connection_verify (connection, NULL),
+	                      NM_SETTING_VERIFY_NORMALIZABLE,
+	                      NM_SETTING_VERIFY_NORMALIZABLE_ERROR));
+#endif
 
 	/* Try to perform all kind of normalizations on the settings to fix it.
 	 * We only do this, after verifying that the connection contains no un-normalizable
 	 * errors, because in that case we rather fail without touching the settings. */
 
+	was_modified = FALSE;
+
 	was_modified |= _normalize_connection_uuid (connection);
 	was_modified |= _normalize_connection_type (connection);
 	was_modified |= _normalize_connection_slave_type (connection);
@@ -1663,11 +1629,12 @@ nm_connection_normalize (NMConnection *connection,
 	was_modified |= _normalize_bridge_vlan_order (connection, parameters);
 	was_modified |= _normalize_bridge_port_vlan_order (connection, parameters);
 
-	/* Verify anew. */
+	was_modified = !!was_modified;
+
+	/* Verify anew */
 	success = _nm_connection_verify (connection, error);
 
-	if (modified)
-		*modified = was_modified;
+	NM_SET_OUT (modified, was_modified);
 
 	if (success != NM_SETTING_VERIFY_SUCCESS) {
 		/* we would expect, that after normalization, the connection can be verified.
@@ -1690,6 +1657,194 @@ nm_connection_normalize (NMConnection *connection,
 }
 
 /**
+ * nm_connection_normalize:
+ * @connection: the #NMConnection to normalize
+ * @parameters: (allow-none) (element-type utf8 gpointer): a #GHashTable with
+ * normalization parameters to allow customization of the normalization by providing
+ * specific arguments. Unknown arguments will be ignored and the default will be
+ * used. The keys must be strings compared with g_str_equal() function.
+ * The values are opaque and depend on the parameter name.
+ * @modified: (out) (allow-none): outputs whether any settings were modified.
+ * @error: location to store error, or %NULL. Contains the reason,
+ * why the connection is invalid, if the function returns an error.
+ *
+ * Does some basic normalization and fixup of well known inconsistencies
+ * and deprecated fields. If the connection was modified in any way,
+ * the output parameter @modified is set %TRUE.
+ *
+ * Finally the connection will be verified and %TRUE returns if the connection
+ * is valid. As this function only performs some specific normalization steps
+ * it cannot repair all connections. If the connection has errors that
+ * cannot be normalized, the connection will not be modified.
+ *
+ * Returns: %TRUE if the connection is valid, %FALSE if it is not
+ **/
+gboolean
+nm_connection_normalize (NMConnection *connection,
+                         GHashTable *parameters,
+                         gboolean *modified,
+                         GError **error)
+{
+	NMSettingVerifyResult success;
+	gs_free_error GError *normalizable_error = NULL;
+
+	success = _nm_connection_verify (connection, &normalizable_error);
+
+	if (!NM_IN_SET (success,
+	                NM_SETTING_VERIFY_NORMALIZABLE,
+	                NM_SETTING_VERIFY_NORMALIZABLE_ERROR)) {
+		if (normalizable_error) {
+			nm_assert (success == NM_SETTING_VERIFY_ERROR);
+			g_propagate_error (error, g_steal_pointer (&normalizable_error));
+		} else
+			nm_assert (success == NM_SETTING_VERIFY_SUCCESS);
+
+		NM_SET_OUT (modified, FALSE);
+
+		if (success != NM_SETTING_VERIFY_SUCCESS) {
+			if (   error
+			    && !*error) {
+				g_set_error_literal (error,
+				                     NM_CONNECTION_ERROR,
+				                     NM_CONNECTION_ERROR_FAILED,
+				                     _("Unexpected failure to verify the connection"));
+				return FALSE;
+			}
+			return FALSE;
+		}
+
+		if (error && *error)
+			return FALSE;
+		return TRUE;
+	}
+
+	return _connection_normalize (connection, parameters, modified, error);
+}
+
+gboolean
+_nm_connection_ensure_normalized (NMConnection *connection,
+                                  gboolean allow_modify,
+                                  const char *expected_uuid,
+                                  gboolean coerce_uuid,
+                                  NMConnection **out_connection_clone,
+                                  GError **error)
+{
+	gs_unref_object NMConnection *connection_clone = NULL;
+	gs_free_error GError *local = NULL;
+	NMSettingVerifyResult vresult;
+
+	nm_assert (NM_IS_CONNECTION (connection));
+	nm_assert (!out_connection_clone || !*out_connection_clone);
+	nm_assert (!expected_uuid || nm_utils_is_uuid (expected_uuid));
+
+	if (expected_uuid) {
+		if (nm_streq0 (expected_uuid, nm_connection_get_uuid (connection)))
+			expected_uuid = NULL;
+		else if (   !coerce_uuid
+		         || (!allow_modify && !out_connection_clone)) {
+			g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
+			             _("unexpected uuid %s instead of %s"),
+			             nm_connection_get_uuid (connection),
+			             expected_uuid);
+			return FALSE;
+		}
+	}
+
+	vresult = _nm_connection_verify (connection, &local);
+	if (vresult != NM_SETTING_VERIFY_SUCCESS) {
+		if (!NM_IN_SET (vresult, NM_SETTING_VERIFY_NORMALIZABLE,
+		                         NM_SETTING_VERIFY_NORMALIZABLE_ERROR)) {
+			g_propagate_error (error, g_steal_pointer (&local));
+			return FALSE;
+		}
+		if (!allow_modify) {
+			if (!out_connection_clone) {
+				/* even NM_SETTING_VERIFY_NORMALIZABLE is treated as an error. We could normalize,
+				 * but are not allowed to (and no out argument is provided for cloning).  */
+				g_propagate_error (error, g_steal_pointer (&local));
+				return FALSE;
+			}
+			connection_clone = nm_simple_connection_new_clone (connection);
+			connection = connection_clone;
+		}
+		if (!_connection_normalize (connection, NULL, NULL, error))
+			g_return_val_if_reached (FALSE);
+	}
+
+	if (expected_uuid) {
+		NMSettingConnection *s_con;
+
+		if (   !allow_modify
+		    && !connection_clone) {
+			nm_assert (out_connection_clone);
+			connection_clone = nm_simple_connection_new_clone (connection);
+			connection = connection_clone;
+		}
+		s_con = nm_connection_get_setting_connection (connection);
+		g_object_set (s_con,
+		              NM_SETTING_CONNECTION_UUID,
+		              expected_uuid,
+		              NULL);
+	}
+
+	NM_SET_OUT (out_connection_clone, g_steal_pointer (&connection_clone));
+	return TRUE;
+}
+
+/*****************************************************************************/
+
+#if NM_MORE_ASSERTS
+static void
+_nmtst_connection_unchanging_changed_cb (NMConnection *connection, gpointer user_data)
+{
+	nm_assert_not_reached ();
+}
+
+static void
+_nmtst_connection_unchanging_secrets_updated_cb (NMConnection *connection, const char *setting_name, gpointer user_data)
+{
+	nm_assert_not_reached ();
+}
+
+const char _nmtst_connection_unchanging_user_data = 0;
+
+void
+nmtst_connection_assert_unchanging (NMConnection *connection)
+{
+	if (!connection)
+		return;
+
+	nm_assert (NM_IS_CONNECTION (connection));
+
+	if (g_signal_handler_find (connection,
+	                           G_SIGNAL_MATCH_DATA,
+	                           0,
+	                           0,
+	                           NULL,
+	                           NULL,
+	                           (gpointer) &_nmtst_connection_unchanging_user_data) != 0) {
+		/* avoid connecting the assertion handler multiple times. */
+		return;
+	}
+
+	g_signal_connect (connection,
+	                  NM_CONNECTION_CHANGED,
+	                  G_CALLBACK (_nmtst_connection_unchanging_changed_cb),
+	                  (gpointer) &_nmtst_connection_unchanging_user_data);
+	g_signal_connect (connection,
+	                  NM_CONNECTION_SECRETS_CLEARED,
+	                  G_CALLBACK (_nmtst_connection_unchanging_changed_cb),
+	                  (gpointer) &_nmtst_connection_unchanging_user_data);
+	g_signal_connect (connection,
+	                  NM_CONNECTION_SECRETS_UPDATED,
+	                  G_CALLBACK (_nmtst_connection_unchanging_secrets_updated_cb),
+	                  (gpointer) &_nmtst_connection_unchanging_user_data);
+}
+#endif
+
+/*****************************************************************************/
+
+/**
  * nm_connection_update_secrets:
  * @connection: the #NMConnection
  * @setting_name: the setting object name to which the secrets apply
@@ -1714,7 +1869,8 @@ nm_connection_update_secrets (NMConnection *connection,
                               GError **error)
 {
 	NMSetting *setting;
-	gboolean success = TRUE, updated = FALSE;
+	gboolean success = TRUE;
+	gboolean updated = FALSE;
 	GVariant *setting_dict = NULL;
 	GVariantIter iter;
 	const char *key;
@@ -1722,13 +1878,13 @@ nm_connection_update_secrets (NMConnection *connection,
 	int success_detail;
 
 	g_return_val_if_fail (NM_IS_CONNECTION (connection), FALSE);
-	g_return_val_if_fail (   g_variant_is_of_type (secrets, NM_VARIANT_TYPE_SETTING)
-	                      || g_variant_is_of_type (secrets, NM_VARIANT_TYPE_CONNECTION), FALSE);
-	if (error)
-		g_return_val_if_fail (*error == NULL, FALSE);
 
 	full_connection = g_variant_is_of_type (secrets, NM_VARIANT_TYPE_CONNECTION);
-	g_return_val_if_fail (setting_name != NULL || full_connection, FALSE);
+
+	g_return_val_if_fail (   full_connection
+	                      || g_variant_is_of_type (secrets, NM_VARIANT_TYPE_SETTING), FALSE);
+	g_return_val_if_fail (!error || !*error, FALSE);
+	g_return_val_if_fail (setting_name || full_connection, FALSE);
 
 	/* Empty @secrets means success */
 	if (g_variant_n_children (secrets) == 0)
@@ -1763,8 +1919,10 @@ nm_connection_update_secrets (NMConnection *connection,
 
 		g_clear_pointer (&setting_dict, g_variant_unref);
 
-		if (success_detail == NM_SETTING_UPDATE_SECRET_ERROR)
+		if (success_detail == NM_SETTING_UPDATE_SECRET_ERROR) {
+			nm_assert (!error || *error);
 			return FALSE;
+		}
 		if (success_detail == NM_SETTING_UPDATE_SECRET_SUCCESS_MODIFIED)
 			updated = TRUE;
 	} else {
@@ -1784,17 +1942,27 @@ nm_connection_update_secrets (NMConnection *connection,
 		/* Update each setting with any secrets from the connection dictionary */
 		g_variant_iter_init (&iter, secrets);
 		while (g_variant_iter_next (&iter, "{&s@a{sv}}", &key, &setting_dict)) {
+			gs_free_error GError *local = NULL;
+
 			/* Update the secrets for this setting */
 			setting = nm_connection_get_setting_by_name (connection, key);
 
 			g_signal_handlers_block_by_func (setting, (GCallback) setting_changed_cb, connection);
-			success_detail = _nm_setting_update_secrets (setting, setting_dict, error);
+			success_detail = _nm_setting_update_secrets (setting, setting_dict, error ? &local : NULL);
 			g_signal_handlers_unblock_by_func (setting, (GCallback) setting_changed_cb, connection);
 
 			g_variant_unref (setting_dict);
 
 			if (success_detail == NM_SETTING_UPDATE_SECRET_ERROR) {
-				success = FALSE;
+				if (success) {
+					if (error) {
+						nm_assert (local);
+						g_propagate_error (error, g_steal_pointer (&local));
+						error = NULL;
+					} else
+						nm_assert (!local);
+					success = FALSE;
+				}
 				break;
 			}
 			if (success_detail == NM_SETTING_UPDATE_SECRET_SUCCESS_MODIFIED)
@@ -1911,6 +2079,52 @@ nm_connection_clear_secrets_with_flags (NMConnection *connection,
 	g_signal_emit (connection, signals[SECRETS_CLEARED], 0);
 }
 
+static gboolean
+_clear_secrets_by_secret_flags_cb (NMSetting *setting,
+                                   const char *secret,
+                                   NMSettingSecretFlags flags,
+                                   gpointer user_data)
+{
+	NMSettingSecretFlags filter_flags = GPOINTER_TO_UINT (user_data);
+	gboolean remove_secret;
+
+	if (filter_flags == NM_SETTING_SECRET_FLAG_NONE) {
+		/* Can't use bitops with SECRET_FLAG_NONE so handle that specifically */
+		remove_secret = (flags != NM_SETTING_SECRET_FLAG_NONE);
+	} else {
+		/* Otherwise if the secret has at least one of the desired flags keep it */
+		remove_secret = !NM_FLAGS_ANY (flags, filter_flags);
+	}
+
+	return remove_secret;
+}
+
+/**
+ * _nm_connection_clear_secrets_by_secret_flags:
+ * @self: the #NMConnection to filter (will be modified)
+ * @filter_flags: the secret flags to control whether to drop/remove
+ *   a secret or to keep it. The meaning of the filter flags is to
+ *   preseve the secrets. The secrets that have matching (see below)
+ *   flags are kept, the others are dropped.
+ *
+ * Removes/drops secrets from @self according to @filter_flags.
+ * If @filter_flags is %NM_SETTING_SECRET_NONE, then only secrets that
+ * have %NM_SETTING_SECRET_NONE flags are kept.
+ * Otherwise, only secrets with secret flags are kept that have at least
+ * one of the filter flags.
+ */
+void
+_nm_connection_clear_secrets_by_secret_flags (NMConnection *self,
+                                              NMSettingSecretFlags filter_flags)
+{
+	nm_connection_clear_secrets_with_flags (self,
+	                                        _clear_secrets_by_secret_flags_cb,
+	                                        GUINT_TO_POINTER (filter_flags));
+}
+
+/*****************************************************************************/
+
+
 /*****************************************************************************/
 
 /* Returns always a non-NULL, floating variant that must
@@ -2029,6 +2243,14 @@ GVariant *
 nm_connection_to_dbus (NMConnection *connection,
                        NMConnectionSerializationFlags flags)
 {
+	return nm_connection_to_dbus_full (connection, flags, NULL);
+}
+
+GVariant *
+nm_connection_to_dbus_full (NMConnection *connection,
+                            NMConnectionSerializationFlags flags,
+                            const NMConnectionSerializationOptions *options)
+{
 	NMConnectionPrivate *priv;
 	GVariantBuilder builder;
 	GHashTableIter iter;
@@ -2041,11 +2263,14 @@ nm_connection_to_dbus (NMConnection *connection,
 	g_variant_builder_init (&builder, NM_VARIANT_TYPE_CONNECTION);
 
 	/* Add each setting's hash to the main hash */
+
+	/* FIXME: the order of serialized settings must be stable. */
+
 	g_hash_table_iter_init (&iter, priv->settings);
 	while (g_hash_table_iter_next (&iter, NULL, &data)) {
 		NMSetting *setting = NM_SETTING (data);
 
-		setting_dict = _nm_setting_to_dbus (setting, connection, flags);
+		setting_dict = _nm_setting_to_dbus (setting, connection, flags, options);
 		if (setting_dict)
 			g_variant_builder_add (&builder, "{s@a{sv}}", nm_setting_get_name (setting), setting_dict);
 	}
@@ -3158,13 +3383,13 @@ nm_connection_default_init (NMConnectionInterface *iface)
 	 */
 	signals[SECRETS_UPDATED] =
 	    g_signal_new (NM_CONNECTION_SECRETS_UPDATED,
-	                 NM_TYPE_CONNECTION,
-	                 G_SIGNAL_RUN_FIRST,
-	                 G_STRUCT_OFFSET (NMConnectionInterface, secrets_updated),
-	                 NULL, NULL,
-	                 g_cclosure_marshal_VOID__STRING,
-	                 G_TYPE_NONE, 1,
-	                 G_TYPE_STRING);
+	                  NM_TYPE_CONNECTION,
+	                  G_SIGNAL_RUN_FIRST,
+	                  G_STRUCT_OFFSET (NMConnectionInterface, secrets_updated),
+	                  NULL, NULL,
+	                  g_cclosure_marshal_VOID__STRING,
+	                  G_TYPE_NONE, 1,
+	                  G_TYPE_STRING);
 
 	/**
 	 * NMConnection::secrets-cleared:
@@ -3175,12 +3400,12 @@ nm_connection_default_init (NMConnectionInterface *iface)
 	 */
 	signals[SECRETS_CLEARED] =
 	    g_signal_new (NM_CONNECTION_SECRETS_CLEARED,
-	                 NM_TYPE_CONNECTION,
-	                 G_SIGNAL_RUN_FIRST,
-	                 G_STRUCT_OFFSET (NMConnectionInterface, secrets_cleared),
-	                 NULL, NULL,
-	                 g_cclosure_marshal_VOID__VOID,
-	                 G_TYPE_NONE, 0);
+	                  NM_TYPE_CONNECTION,
+	                  G_SIGNAL_RUN_FIRST,
+	                  G_STRUCT_OFFSET (NMConnectionInterface, secrets_cleared),
+	                  NULL, NULL,
+	                  g_cclosure_marshal_VOID__VOID,
+	                  G_TYPE_NONE, 0);
 
 	/**
 	 * NMConnection::changed:
@@ -3192,10 +3417,10 @@ nm_connection_default_init (NMConnectionInterface *iface)
 	 */
 	signals[CHANGED] =
 	    g_signal_new (NM_CONNECTION_CHANGED,
-	                 NM_TYPE_CONNECTION,
-	                 G_SIGNAL_RUN_FIRST,
-	                 G_STRUCT_OFFSET (NMConnectionInterface, changed),
-	                 NULL, NULL,
-	                 g_cclosure_marshal_VOID__VOID,
-	                 G_TYPE_NONE, 0);
+	                  NM_TYPE_CONNECTION,
+	                  G_SIGNAL_RUN_FIRST,
+	                  G_STRUCT_OFFSET (NMConnectionInterface, changed),
+	                  NULL, NULL,
+	                  g_cclosure_marshal_VOID__VOID,
+	                  G_TYPE_NONE, 0);
 }