summary refs log tree commit diff
path: root/libnm/nm-client.c
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2020-04-11 21:28:04 +0200
committerMichael Biebl <biebl@debian.org>2020-04-11 21:28:04 +0200
commit1e5977b62f896e844b548c3007ace9e1dfa7f9ed (patch)
tree7a7416ed410e72b6200f3d860fd315ec11cc106b /libnm/nm-client.c
parentb012fa6e1d808e0736c009799c62d835cbfcc1dd (diff)
New upstream version 1.23.90 upstream/1.23.90
Diffstat (limited to 'libnm/nm-client.c')
-rw-r--r--libnm/nm-client.c795
1 files changed, 583 insertions, 212 deletions
diff --git a/libnm/nm-client.c b/libnm/nm-client.c
index 0c4daab2..68a1836d 100644
--- a/libnm/nm-client.c
+++ b/libnm/nm-client.c
@@ -60,7 +60,7 @@
 
 /*****************************************************************************/
 
-static NM_CACHED_QUARK_FCN ("nm-client-context-busy-watcher", nm_client_context_busy_watcher_quark)
+NM_CACHED_QUARK_FCN ("nm-context-busy-watcher", nm_context_busy_watcher_quark)
 
 static void
 _context_busy_watcher_attach_integration_source_cb (gpointer data,
@@ -69,10 +69,21 @@ _context_busy_watcher_attach_integration_source_cb (gpointer data,
 	nm_g_source_destroy_and_unref (data);
 }
 
-static void
-_context_busy_watcher_attach_integration_source (GObject *context_busy_watcher,
-                                                 GSource *source_take)
+void
+nm_context_busy_watcher_integrate_source (GMainContext *outer_context,
+                                          GMainContext *inner_context,
+                                          GObject *context_busy_watcher)
 {
+	GSource *source;
+
+	nm_assert (outer_context);
+	nm_assert (inner_context);
+	nm_assert (outer_context != inner_context);
+	nm_assert (G_IS_OBJECT (context_busy_watcher));
+
+	source = nm_utils_g_main_context_create_integrate_source (inner_context);
+	g_source_attach (source, outer_context);
+
 	/* The problem is...
 	 *
 	 * NMClient is associated with a GMainContext, just like its underlying GDBusConnection
@@ -114,7 +125,7 @@ _context_busy_watcher_attach_integration_source (GObject *context_busy_watcher,
 
 	g_object_weak_ref (context_busy_watcher,
 	                   _context_busy_watcher_attach_integration_source_cb,
-	                   source_take);
+	                   source);
 }
 
 /*****************************************************************************/
@@ -175,26 +186,11 @@ typedef struct {
 
 /*****************************************************************************/
 
-typedef struct {
-	GCancellable *cancellable;
-	GSource *cancel_on_idle_source;
-	gulong cancelled_id;
-	union {
-		struct {
-			GTask *task;
-		} async;
-		struct {
-			GMainLoop *main_loop;
-			GError **error_location;
-		} sync;
-	} data;
-	bool is_sync:1;
-} InitData;
-
 NM_GOBJECT_PROPERTIES_DEFINE (NMClient,
 	PROP_DBUS_CONNECTION,
 	PROP_DBUS_NAME_OWNER,
 	PROP_VERSION,
+	PROP_INSTANCE_FLAGS,
 	PROP_STATE,
 	PROP_STARTUP,
 	PROP_NM_RUNNING,
@@ -223,6 +219,7 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMClient,
 	PROP_DNS_CONFIGURATION,
 	PROP_CHECKPOINTS,
 	PROP_CAPABILITIES,
+	PROP_PERMISSIONS_STATE,
 );
 
 enum {
@@ -261,7 +258,7 @@ typedef struct {
 	GMainContext *dbus_context;
 	GObject *context_busy_watcher;
 	GDBusConnection *dbus_connection;
-	InitData *init_data;
+	NMLInitData *init_data;
 	GHashTable *dbus_objects;
 	CList obj_changed_lst_head;
 	GCancellable *name_owner_get_cancellable;
@@ -279,7 +276,7 @@ typedef struct {
 	NMLDBusObject *dbobj_settings;
 	NMLDBusObject *dbobj_dns_manager;
 
-	GHashTable *permissions;
+	guint8 *permissions;
 	GCancellable *permissions_cancellable;
 
 	char *name_owner;
@@ -291,6 +288,12 @@ typedef struct {
 	guint dbsid_nm_vpn_connection_state_changed;
 	guint dbsid_nm_check_permissions;
 
+	NMClientInstanceFlags instance_flags:3;
+
+	NMTernary permissions_state:3;
+
+	bool instance_flags_constructed:1;
+
 	bool udev_inited:1;
 	bool notify_event_lst_changed:1;
 	bool check_dbobj_visible_all:1;
@@ -379,7 +382,8 @@ static NMRefString *_dbus_path_dns_manager = NULL;
 
 /*****************************************************************************/
 
-NM_UTILS_LOOKUP_STR_DEFINE_STATIC (nml_dbus_obj_state_to_string, NMLDBusObjState,
+static
+NM_UTILS_LOOKUP_STR_DEFINE (nml_dbus_obj_state_to_string, NMLDBusObjState,
 	NM_UTILS_LOOKUP_DEFAULT_WARN ("???"),
 	NM_UTILS_LOOKUP_ITEM (NML_DBUS_OBJ_STATE_UNLINKED,             "unlinked"),
 	NM_UTILS_LOOKUP_ITEM (NML_DBUS_OBJ_STATE_WATCHED_ONLY,         "watched-only"),
@@ -401,15 +405,15 @@ NM_CACHED_QUARK_FCN ("nm-client-error-quark", nm_client_error_quark)
 
 /*****************************************************************************/
 
-static InitData *
-_init_data_new_sync (GCancellable *cancellable,
-                     GMainLoop *main_loop,
-                     GError **error_location)
+NMLInitData *
+nml_init_data_new_sync (GCancellable *cancellable,
+                        GMainLoop *main_loop,
+                        GError **error_location)
 {
-	InitData *init_data;
+	NMLInitData *init_data;
 
-	init_data = g_slice_new (InitData);
-	*init_data = (InitData) {
+	init_data = g_slice_new (NMLInitData);
+	*init_data = (NMLInitData) {
 		.cancellable = nm_g_object_ref (cancellable),
 		.is_sync     = TRUE,
 		.data.sync   = {
@@ -420,14 +424,14 @@ _init_data_new_sync (GCancellable *cancellable,
 	return init_data;
 }
 
-static InitData *
-_init_data_new_async (GCancellable *cancellable,
-                      GTask *task_take)
+NMLInitData *
+nml_init_data_new_async (GCancellable *cancellable,
+                         GTask *task_take)
 {
-	InitData *init_data;
+	NMLInitData *init_data;
 
-	init_data = g_slice_new (InitData);
-	*init_data = (InitData) {
+	init_data = g_slice_new (NMLInitData);
+	*init_data = (NMLInitData) {
 		.cancellable = nm_g_object_ref (cancellable),
 		.is_sync     = FALSE,
 		.data.async  = {
@@ -437,6 +441,30 @@ _init_data_new_async (GCancellable *cancellable,
 	return init_data;
 }
 
+void
+nml_init_data_return (NMLInitData *init_data,
+                      GError *error_take)
+{
+	nm_assert (init_data);
+
+	nm_clear_pointer (&init_data->cancel_on_idle_source, nm_g_source_destroy_and_unref);
+	nm_clear_g_signal_handler (init_data->cancellable, &init_data->cancelled_id);
+
+	if (init_data->is_sync) {
+		if (error_take)
+			g_propagate_error (init_data->data.sync.error_location, error_take);
+		g_main_loop_quit (init_data->data.sync.main_loop);
+	} else {
+		if (error_take)
+			g_task_return_error (init_data->data.async.task, error_take);
+		else
+			g_task_return_boolean (init_data->data.async.task, TRUE);
+		g_object_unref (init_data->data.async.task);
+	}
+	nm_g_object_unref (init_data->cancellable);
+	nm_g_slice_free (init_data);
+}
+
 /*****************************************************************************/
 
 GError *
@@ -1000,7 +1028,7 @@ nm_client_get_context_busy_watcher (NMClient *self)
 	g_return_val_if_fail (NM_IS_CLIENT (self), NULL);
 
 	w = NM_CLIENT_GET_PRIVATE (self)->context_busy_watcher;
-	return    g_object_get_qdata (w, nm_client_context_busy_watcher_quark ())
+	return    g_object_get_qdata (w, nm_context_busy_watcher_quark ())
 	       ?: w;
 }
 
@@ -3115,7 +3143,7 @@ _dbus_get_managed_objects_cb (GObject *source,
 	nm_assert ((!!ret) != (!!error));
 
 	if (   !ret
-	    && nm_utils_error_is_cancelled (error, FALSE))
+	    && nm_utils_error_is_cancelled (error))
 		return;
 
 	priv = NM_CLIENT_GET_PRIVATE (self);
@@ -3166,7 +3194,7 @@ _nm_client_get_settings_call_cb (GObject *source, GAsyncResult *result, gpointer
 
 	ret = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), result, &error);
 	if (   !ret
-	    && nm_utils_error_is_cancelled (error, FALSE))
+	    && nm_utils_error_is_cancelled (error))
 		return;
 
 	remote_connection = user_data;
@@ -3356,27 +3384,35 @@ _dbus_nm_vpn_connection_state_changed_cb (GDBusConnection *connection,
 
 static void
 _emit_permissions_changed (NMClient *self,
-                           GHashTable *permissions,
-                           gboolean force_unknown)
+                           const guint8 *old_permissions,
+                           const guint8 *permissions)
 {
-	GHashTableIter iter;
-	gpointer key;
-	gpointer value;
+	int i;
 
-	if (!permissions)
-		return;
 	if (self->obj_base.is_disposing)
 		return;
 
-	g_hash_table_iter_init (&iter, permissions);
-	while (g_hash_table_iter_next (&iter, &key, &value)) {
+	if (old_permissions == permissions)
+		return;
+
+	for (i = 0; i < (int) G_N_ELEMENTS (nm_auth_permission_sorted); i++) {
+		NMClientPermission perm = nm_auth_permission_sorted[i];
+		NMClientPermissionResult perm_result = NM_CLIENT_PERMISSION_RESULT_UNKNOWN;
+		NMClientPermissionResult perm_result_old = NM_CLIENT_PERMISSION_RESULT_UNKNOWN;
+
+		if (permissions)
+			perm_result = permissions[perm - 1];
+		if (old_permissions)
+			perm_result_old = old_permissions[perm - 1];
+
+		if (perm_result == perm_result_old)
+			continue;
+
 		g_signal_emit (self,
 		               signals[PERMISSION_CHANGED],
 		               0,
-		               GPOINTER_TO_UINT (key),
-		                 force_unknown
-		               ? (guint) NM_CLIENT_PERMISSION_NONE
-		               : GPOINTER_TO_UINT (value));
+		               (guint) perm,
+		               (guint) perm_result);
 	}
 }
 
@@ -3389,14 +3425,15 @@ _dbus_check_permissions_start_cb (GObject *source, GAsyncResult *result, gpointe
 	NMClientPrivate *priv;
 	gs_unref_variant GVariant *ret = NULL;
 	nm_auto_free_variant_iter GVariantIter *v_permissions = NULL;
-	gs_unref_hashtable GHashTable *old_permissions = NULL;
+	gs_free guint8 *old_permissions = NULL;
 	gs_free_error GError *error = NULL;
 	const char *pkey;
 	const char *pvalue;
+	int i;
 
 	ret = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), result, &error);
 	if (   !ret
-	    && nm_utils_error_is_cancelled (error, FALSE))
+	    && nm_utils_error_is_cancelled (error))
 		return;
 
 	self = user_data;
@@ -3404,63 +3441,73 @@ _dbus_check_permissions_start_cb (GObject *source, GAsyncResult *result, gpointe
 
 	g_clear_object (&priv->permissions_cancellable);
 
+	old_permissions = g_steal_pointer (&priv->permissions);
+
 	if (!ret) {
+		/* when the call completes, we always pretend success. Even a failure means
+		 * that we fetched the permissions, however they are all unknown. */
 		NML_NMCLIENT_LOG_T (self, "GetPermissions call failed: %s", error->message);
-		return;
+		goto out;
 	}
 
 	NML_NMCLIENT_LOG_T (self, "GetPermissions call finished with success");
 
-	/* get list of old permissions for change notification */
-	old_permissions = g_steal_pointer (&priv->permissions);
-	priv->permissions = g_hash_table_new (nm_direct_hash, NULL);
-
 	g_variant_get (ret, "(a{ss})", &v_permissions);
 	while (g_variant_iter_next (v_permissions, "{&s&s}", &pkey, &pvalue)) {
 		NMClientPermission perm;
 		NMClientPermissionResult perm_result;
 
-		perm = nm_permission_to_client (pkey);
+		perm = nm_auth_permission_from_string (pkey);
 		if (perm == NM_CLIENT_PERMISSION_NONE)
 			continue;
 
-		perm_result = nm_permission_result_to_client (pvalue);
+		perm_result = nm_client_permission_result_from_string (pvalue);
 
-		g_hash_table_insert (priv->permissions,
-		                     GUINT_TO_POINTER (perm),
-		                     GUINT_TO_POINTER (perm_result));
-		if (old_permissions) {
-			g_hash_table_remove (old_permissions,
-			                     GUINT_TO_POINTER (perm));
+		if (!priv->permissions) {
+			if (perm_result == NM_CLIENT_PERMISSION_RESULT_UNKNOWN)
+				continue;
+			priv->permissions = g_new (guint8, G_N_ELEMENTS (nm_auth_permission_sorted));
+			for (i = 0; i < (int) G_N_ELEMENTS (nm_auth_permission_sorted); i++)
+				priv->permissions[i] = NM_CLIENT_PERMISSION_RESULT_UNKNOWN;
 		}
+		priv->permissions[perm - 1] = perm_result;
 	}
 
+out:
+	priv->permissions_state = NM_TERNARY_TRUE;
+
 	dbus_context = nm_g_main_context_push_thread_default_if_necessary (priv->main_context);
-	_emit_permissions_changed (self, priv->permissions, FALSE);
-	_emit_permissions_changed (self, old_permissions, TRUE);
+	_emit_permissions_changed (self, old_permissions, priv->permissions);
+	_notify (self, PROP_PERMISSIONS_STATE);
 }
 
 static void
 _dbus_check_permissions_start (NMClient *self)
 {
 	NMClientPrivate *priv = NM_CLIENT_GET_PRIVATE (self);
+	gboolean fetch;
+
+	fetch = !NM_FLAGS_HAS ((NMClientInstanceFlags) priv->instance_flags,
+	                       NM_CLIENT_INSTANCE_FLAGS_NO_AUTO_FETCH_PERMISSIONS);
 
 	nm_clear_g_cancellable (&priv->permissions_cancellable);
-	priv->permissions_cancellable = g_cancellable_new ();
 
-	NML_NMCLIENT_LOG_T (self, "GetPermissions() call started...");
+	if (fetch) {
+		NML_NMCLIENT_LOG_T (self, "GetPermissions() call started...");
 
-	_nm_client_dbus_call_simple (self,
-	                             priv->permissions_cancellable,
-	                             NM_DBUS_PATH,
-	                             NM_DBUS_INTERFACE,
-	                             "GetPermissions",
-	                             g_variant_new ("()"),
-	                             G_VARIANT_TYPE ("(a{ss})"),
-	                             G_DBUS_CALL_FLAGS_NONE,
-	                             NM_DBUS_DEFAULT_TIMEOUT_MSEC,
-	                             _dbus_check_permissions_start_cb,
-	                             self);
+		priv->permissions_cancellable = g_cancellable_new ();
+		_nm_client_dbus_call_simple (self,
+		                             priv->permissions_cancellable,
+		                             NM_DBUS_PATH,
+		                             NM_DBUS_INTERFACE,
+		                             "GetPermissions",
+		                             g_variant_new ("()"),
+		                             G_VARIANT_TYPE ("(a{ss})"),
+		                             G_DBUS_CALL_FLAGS_NONE,
+		                             NM_DBUS_DEFAULT_TIMEOUT_MSEC,
+		                             _dbus_check_permissions_start_cb,
+		                             self);
+	}
 }
 
 static void
@@ -3473,6 +3520,7 @@ _dbus_nm_check_permissions_cb (GDBusConnection *connection,
                                gpointer user_data)
 {
 	NMClient *self = user_data;
+	NMClientPrivate *priv = NM_CLIENT_GET_PRIVATE (self);
 
 	if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("()"))) {
 		NML_NMCLIENT_LOG_E (self, "ignore CheckPermissions signal with unexpected signature %s",
@@ -3481,6 +3529,10 @@ _dbus_nm_check_permissions_cb (GDBusConnection *connection,
 	}
 
 	_dbus_check_permissions_start (self);
+
+	if (priv->permissions_state == NM_TERNARY_TRUE)
+		priv->permissions_state = NM_TERNARY_FALSE;
+	_notify (self, PROP_PERMISSIONS_STATE);
 }
 
 /*****************************************************************************/
@@ -3794,6 +3846,22 @@ _request_wait_finish (NMClient *client,
 /*****************************************************************************/
 
 /**
+ * nm_client_get_instance_flags:
+ * @self: the #NMClient instance.
+ *
+ * Returns: the #NMClientInstanceFlags flags.
+ *
+ * Since: 1.24
+ */
+NMClientInstanceFlags
+nm_client_get_instance_flags (NMClient *self)
+{
+	g_return_val_if_fail (NM_IS_CLIENT (self), NM_CLIENT_INSTANCE_FLAGS_NONE);
+
+	return NM_CLIENT_GET_PRIVATE (self)->instance_flags;
+}
+
+/**
  * nm_client_get_dbus_connection:
  * @client: a #NMClient
  *
@@ -3908,6 +3976,26 @@ nm_client_get_nm_running (NMClient *client)
 }
 
 /**
+ * nm_client_get_object_by_path:
+ * @client: the #NMClient instance
+ * @dbus_path: the D-Bus path of the object to look up
+ *
+ * Returns: (transfer none): the #NMObject instance that is
+ *   cached under @dbus_path, or %NULL if no such object exists.
+ *
+ * Since: 1.24
+ */
+NMObject *
+nm_client_get_object_by_path (NMClient *client,
+                              const char *dbus_path)
+{
+	g_return_val_if_fail (NM_IS_CLIENT (client), NULL);
+	g_return_val_if_fail (dbus_path, NULL);
+
+	return _dbobjs_get_nmobj_unpack_visible (client, dbus_path, G_TYPE_NONE);
+}
+
+/**
  * nm_client_get_metered:
  * @client: a #NMClient
  *
@@ -3951,15 +4039,14 @@ nm_client_networking_get_enabled (NMClient *client)
  *
  * Returns: %TRUE on success, %FALSE otherwise
  *
- * Deprecated: 1.22, use nm_client_networking_set_enabled_async() or GDBusConnection
+ * Deprecated: 1.22: Use the async command nm_client_dbus_call() on %NM_DBUS_PATH,
+ * %NM_DBUS_INTERFACE to call "Enable" with "(b)" arguments and no return value.
  **/
 gboolean
 nm_client_networking_set_enabled (NMClient *client, gboolean enable, GError **error)
 {
 	g_return_val_if_fail (NM_IS_CLIENT (client), FALSE);
 
-	/* FIXME(libnm-async-api): add nm_client_networking_set_enabled_async(). */
-
 	return _nm_client_dbus_call_sync_void (client,
 	                                       NULL,
 	                                       NM_DBUS_PATH,
@@ -3995,15 +4082,14 @@ nm_client_wireless_get_enabled (NMClient *client)
  *
  * Enables or disables wireless devices.
  *
- * Deprecated: 1.22, use nm_client_wireless_set_enabled_async() or GDBusConnection
+ * Deprecated: 1.22: Use the async command nm_client_dbus_set_property() on %NM_DBUS_PATH,
+ * %NM_DBUS_INTERFACE to set "WirelessEnabled" property to a "(b)" value.
  */
 void
 nm_client_wireless_set_enabled (NMClient *client, gboolean enabled)
 {
 	g_return_if_fail (NM_IS_CLIENT (client));
 
-	/* FIXME(libnm-async-api): add nm_client_wireless_set_enabled_async(). */
-
 	_nm_client_set_property_sync_legacy (client,
 	                                     NM_DBUS_PATH,
 	                                     NM_DBUS_INTERFACE,
@@ -4050,14 +4136,15 @@ nm_client_wwan_get_enabled (NMClient *client)
  * @enabled: %TRUE to enable WWAN
  *
  * Enables or disables WWAN devices.
+ *
+ * Deprecated: 1.22: Use the async command nm_client_dbus_set_property() on %NM_DBUS_PATH,
+ * %NM_DBUS_INTERFACE to set "WwanEnabled" property to a "(b)" value.
  **/
 void
 nm_client_wwan_set_enabled (NMClient *client, gboolean enabled)
 {
 	g_return_if_fail (NM_IS_CLIENT (client));
 
-	/* FIXME(libnm-async-api): add nm_client_wwan_set_enabled_async(). */
-
 	_nm_client_set_property_sync_legacy (client,
 	                                     NM_DBUS_PATH,
 	                                     NM_DBUS_INTERFACE,
@@ -4090,7 +4177,7 @@ nm_client_wwan_hardware_get_enabled (NMClient *client)
  *
  * Returns: %TRUE if WiMAX is enabled
  *
- * Deprecated: 1.22 This function always returns FALSE because WiMax is no longer supported
+ * Deprecated: 1.22: This function always returns FALSE because WiMax is no longer supported.
  **/
 gboolean
 nm_client_wimax_get_enabled (NMClient *client)
@@ -4107,7 +4194,7 @@ nm_client_wimax_get_enabled (NMClient *client)
  *
  * Enables or disables WiMAX devices.
  *
- * Deprecated: 1.22 This function does nothing because WiMax is no longer supported
+ * Deprecated: 1.22: This function does nothing because WiMax is no longer supported.
  **/
 void
 nm_client_wimax_set_enabled (NMClient *client, gboolean enabled)
@@ -4123,7 +4210,7 @@ nm_client_wimax_set_enabled (NMClient *client, gboolean enabled)
  *
  * Returns: %TRUE if the WiMAX hardware is enabled
  *
- * Deprecated: 1.22 This function always returns FALSE because WiMax is no longer supported
+ * Deprecated: 1.22: This function always returns FALSE because WiMax is no longer supported.
  **/
 gboolean
 nm_client_wimax_hardware_get_enabled (NMClient *client)
@@ -4181,14 +4268,15 @@ nm_client_connectivity_check_get_enabled (NMClient *client)
  * have any effect.
  *
  * Since: 1.10
+ *
+ * Deprecated: 1.22: Use the async command nm_client_dbus_set_property() on %NM_DBUS_PATH,
+ * %NM_DBUS_INTERFACE to set "ConnectivityCheckEnabled" property to a "(b)" value.
  */
 void
 nm_client_connectivity_check_set_enabled (NMClient *client, gboolean enabled)
 {
 	g_return_if_fail (NM_IS_CLIENT (client));
 
-	/* FIXME(libnm-async-api): add nm_client_wireless_set_enabled_async(). */
-
 	_nm_client_set_property_sync_legacy (client,
 	                                     NM_DBUS_PATH,
 	                                     NM_DBUS_INTERFACE,
@@ -4228,7 +4316,9 @@ nm_client_connectivity_check_get_uri (NMClient *client)
  *
  * Returns: %TRUE on success, %FALSE otherwise
  *
- * Deprecated: 1.22, use nm_client_get_logging_async() or GDBusConnection
+ * Deprecated: 1.22: Use the async command nm_client_dbus_call() on %NM_DBUS_PATH,
+ * %NM_DBUS_INTERFACE to call "GetLogging" with no arguments to get "(ss)" for level
+ * and domains.
  **/
 gboolean
 nm_client_get_logging (NMClient *client,
@@ -4243,8 +4333,6 @@ nm_client_get_logging (NMClient *client,
 	g_return_val_if_fail (domains == NULL || *domains == NULL, FALSE);
 	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
 
-	/* FIXME(libnm-async-api): add nm_client_get_logging_async(). */
-
 	ret = _nm_client_dbus_call_sync (client,
 	                                 NULL,
 	                                 NM_DBUS_PATH,
@@ -4278,7 +4366,8 @@ nm_client_get_logging (NMClient *client,
  *
  * Returns: %TRUE on success, %FALSE otherwise
  *
- * Deprecated: 1.22, use nm_client_set_logging_async() or GDBusConnection
+ * Deprecated: 1.22: Use the async command nm_client_dbus_call() on %NM_DBUS_PATH,
+ * %NM_DBUS_INTERFACE to call "SetLogging" with "(ss)" arguments for level and domains.
  **/
 gboolean
 nm_client_set_logging (NMClient *client, const char *level, const char *domains, GError **error)
@@ -4286,8 +4375,6 @@ nm_client_set_logging (NMClient *client, const char *level, const char *domains,
 	g_return_val_if_fail (NM_IS_CLIENT (client), FALSE);
 	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
 
-	/* FIXME(libnm-async-api): add nm_client_set_logging_async(). */
-
 	return _nm_client_dbus_call_sync_void (client,
 	                                       NULL,
 	                                       NM_DBUS_PATH,
@@ -4316,18 +4403,39 @@ NMClientPermissionResult
 nm_client_get_permission_result (NMClient *client, NMClientPermission permission)
 {
 	NMClientPrivate *priv;
-	gpointer result;
+	NMClientPermissionResult result = NM_CLIENT_PERMISSION_RESULT_UNKNOWN;
 
 	g_return_val_if_fail (NM_IS_CLIENT (client), NM_CLIENT_PERMISSION_RESULT_UNKNOWN);
 
-	priv = NM_CLIENT_GET_PRIVATE (client);
-	if (   !priv->permissions
-	    || !g_hash_table_lookup_extended (priv->permissions,
-	                                      GUINT_TO_POINTER (permission),
-	                                      NULL,
-	                                      &result))
-		return NM_CLIENT_PERMISSION_RESULT_UNKNOWN;
-	return GPOINTER_TO_UINT (result);
+	if (   permission > NM_CLIENT_PERMISSION_NONE
+	    && permission <= NM_CLIENT_PERMISSION_LAST) {
+		priv = NM_CLIENT_GET_PRIVATE (client);
+		if (priv->permissions)
+			result = priv->permissions[permission - 1];
+	}
+
+	return result;
+}
+
+/**
+ * nm_client_get_permissions_state:
+ * @self: the #NMClient instance
+ *
+ * Returns: the state of the cached permissions. %NM_TERNARY_DEFAULT
+ *   means that no permissions result was yet received. All permissions
+ *   are unknown. %NM_TERNARY_TRUE means that the permissions got received
+ *   and are cached. %%NM_TERNARY_FALSE means that permissions are cached,
+ *   but they are invalided as as "CheckPermissions" signal was received
+ *   in the meantime.
+ *
+ * Since: 1.24
+ */
+NMTernary
+nm_client_get_permissions_state (NMClient *self)
+{
+	g_return_val_if_fail (NM_IS_CLIENT (self), NM_TERNARY_DEFAULT);
+
+	return NM_CLIENT_GET_PRIVATE (self)->permissions_state;
 }
 
 /**
@@ -4364,7 +4472,7 @@ nm_client_get_connectivity (NMClient *client)
  *
  * Returns: the (new) current connectivity state
  *
- * Deprecated: 1.22, use nm_client_check_connectivity_async() or GDBusConnection
+ * Deprecated: 1.22: Use nm_client_check_connectivity_async() or GDBusConnection.
  */
 NMConnectivityState
 nm_client_check_connectivity (NMClient *client,
@@ -4493,7 +4601,7 @@ nm_client_check_connectivity_finish (NMClient *client,
  *
  * Returns: %TRUE if the request was successful, %FALSE if it failed
  *
- * Deprecated: 1.22, use nm_client_save_hostname_async() or GDBusConnection
+ * Deprecated: 1.22: Use nm_client_save_hostname_async() or GDBusConnection.
  **/
 gboolean
 nm_client_save_hostname (NMClient *client,
@@ -4757,7 +4865,7 @@ activate_connection_cb (GObject *object,
 
 	ret = g_dbus_connection_call_finish (G_DBUS_CONNECTION (object), result, &error);
 	if (!ret) {
-		if (!nm_utils_error_is_cancelled (error, FALSE))
+		if (!nm_utils_error_is_cancelled (error))
 			g_dbus_error_strip_remote_error (error);
 		g_task_return_error (task, error);
 		return;
@@ -4895,7 +5003,7 @@ _add_and_activate_connection_done (GObject *object,
 
 	ret = g_dbus_connection_call_finish (G_DBUS_CONNECTION (object), result, &error);
 	if (!ret) {
-		if (!nm_utils_error_is_cancelled (error, FALSE))
+		if (!nm_utils_error_is_cancelled (error))
 			g_dbus_error_strip_remote_error (error);
 		g_task_return_error (task, error);
 		return;
@@ -5203,7 +5311,7 @@ nm_client_add_and_activate_connection2_finish (NMClient *client,
  *
  * Returns: success or failure
  *
- * Deprecated: 1.22, use nm_client_deactivate_connection_async() or GDBusConnection
+ * Deprecated: 1.22: Use nm_client_deactivate_connection_async() or GDBusConnection.
  **/
 gboolean
 nm_client_deactivate_connection (NMClient *client,
@@ -5418,7 +5526,7 @@ _add_connection_cb (GObject *source,
 
 	ret = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), result, &error);
 	if (!ret) {
-		if (!nm_utils_error_is_cancelled (error, FALSE))
+		if (!nm_utils_error_is_cancelled (error))
 			g_dbus_error_strip_remote_error (error);
 		g_task_return_error (task, error);
 		return;
@@ -5708,7 +5816,7 @@ nm_client_add_connection2_finish (NMClient *client,
  *   set. Note that even in the success case, you might have individual @failures.
  *   With 1.22, the return value is consistent with nm_client_load_connections_finish().
  *
- * Deprecated: 1.22, use nm_client_load_connections_async() or GDBusConnection
+ * Deprecated: 1.22: Use nm_client_load_connections_async() or GDBusConnection.
  **/
 gboolean
 nm_client_load_connections (NMClient *client,
@@ -5839,7 +5947,7 @@ nm_client_load_connections_finish (NMClient *client,
  *
  * Return value: %TRUE on success, %FALSE on failure
  *
- * Deprecated: 1.22, use nm_client_reload_connections_async() or GDBusConnection
+ * Deprecated: 1.22: Use nm_client_reload_connections_async() or GDBusConnection.
  **/
 gboolean
 nm_client_reload_connections (NMClient *client,
@@ -6080,7 +6188,7 @@ _notify_update_prop_dns_manager_configuration (NMClient *self,
  *   The array is terminated by a numeric zero sentinel
  *   at position @length.
  *
- * Since: 1.24, 1.22.2
+ * Since: 1.24
  */
 const guint32 *
 nm_client_get_capabilities (NMClient *client,
@@ -6160,7 +6268,7 @@ checkpoint_create_cb (GObject *object,
 
 	ret = g_dbus_connection_call_finish (G_DBUS_CONNECTION (object), result, &error);
 	if (!ret) {
-		if (!nm_utils_error_is_cancelled (error, FALSE))
+		if (!nm_utils_error_is_cancelled (error))
 			g_dbus_error_strip_remote_error (error);
 		g_task_return_error (task, error);
 		return;
@@ -6540,6 +6648,174 @@ nm_client_reload_finish (NMClient *client,
 
 /*****************************************************************************/
 
+/**
+ * nm_client_dbus_call:
+ * @client: the #NMClient
+ * @object_path: path of remote object
+ * @interface_name: D-Bus interface to invoke method on
+ * @method_name: the name of the method to invoke
+ * @parameters: (nullable): a #GVariant tuple with parameters for the method
+ *     or %NULL if not passing parameters
+ * @reply_type: (nullable): the expected type of the reply (which will be a
+ *     tuple), or %NULL
+ * @timeout_msec: the timeout in milliseconds, -1 to use the default
+ *     timeout or %G_MAXINT for no timeout
+ * @cancellable: (nullable): a #GCancellable or %NULL
+ * @callback: (nullable): a #GAsyncReadyCallback to call when the request
+ *     is satisfied or %NULL if you don't care about the result of the
+ *     method invocation
+ * @user_data: the data to pass to @callback
+ *
+ * Call g_dbus_connection_call() on the current name owner with the specified
+ * arguments. Most importantly, this invokes g_dbus_connection_call() with the
+ * client's #GMainContext, so that the response is always in order with other
+ * events D-Bus events. Of course, the call uses #GTask and will invoke the
+ * callback on the current g_main_context_get_thread_default().
+ *
+ * This API is merely a convenient wrapper for g_dbus_connection_call(). You can
+ * also use g_dbus_connection_call() directly, with the same effect.
+ *
+ * Since: 1.24
+ **/
+void
+nm_client_dbus_call (NMClient *client,
+                     const char *object_path,
+                     const char *interface_name,
+                     const char *method_name,
+                     GVariant *parameters,
+                     const GVariantType *reply_type,
+                     int timeout_msec,
+                     GCancellable *cancellable,
+                     GAsyncReadyCallback callback,
+                     gpointer user_data)
+{
+	g_return_if_fail (NM_IS_CLIENT (client));
+
+	_nm_client_dbus_call (client,
+	                      client,
+	                      nm_client_dbus_call,
+	                      cancellable,
+	                      callback,
+	                      user_data,
+	                      object_path,
+	                      interface_name,
+	                      method_name,
+	                      parameters,
+	                      reply_type,
+	                      G_DBUS_CALL_FLAGS_NONE,
+	                        timeout_msec == -1
+	                      ? NM_DBUS_DEFAULT_TIMEOUT_MSEC
+	                      : timeout_msec,
+	                      nm_dbus_connection_call_finish_variant_cb);
+}
+
+/**
+ * nm_client_dbus_call_finish:
+ * @client: the #NMClient instance
+ * @result: the result passed to the #GAsyncReadyCallback
+ * @error: location for a #GError, or %NULL
+ *
+ * Gets the result of a call to nm_client_dbus_call().
+ *
+ * Returns: (transfer full): the result #GVariant or %NULL on error.
+ *
+ * Since: 1.24
+ **/
+GVariant *
+nm_client_dbus_call_finish (NMClient *client,
+                            GAsyncResult *result,
+                            GError **error)
+{
+	g_return_val_if_fail (NM_IS_CLIENT (client), FALSE);
+	g_return_val_if_fail (nm_g_task_is_valid (result, client, nm_client_dbus_call), FALSE);
+
+	return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+/*****************************************************************************/
+
+/**
+ * nm_client_dbus_set_property:
+ * @client: the #NMClient
+ * @object_path: path of remote object
+ * @interface_name: D-Bus interface to invoke method on
+ * @property_name: the name of the property to set
+ * @value: a #GVariant tuple with the value to set
+ * @timeout_msec: the timeout in milliseconds, -1 to use the default
+ *     timeout or %G_MAXINT for no timeout
+ * @cancellable: (nullable): a #GCancellable or %NULL
+ * @callback: (nullable): a #GAsyncReadyCallback to call when the request
+ *     is satisfied or %NULL if you don't care about the result of the
+ *     method invocation
+ * @user_data: the data to pass to @callback
+ *
+ * Like nm_client_dbus_call() but calls "Set" on the standard "org.freedesktop.DBus.Properties"
+ * D-Bus interface.
+ *
+ * Since: 1.24
+ **/
+void
+nm_client_dbus_set_property (NMClient *client,
+                             const char *object_path,
+                             const char *interface_name,
+                             const char *property_name,
+                             GVariant *value,
+                             int timeout_msec,
+                             GCancellable *cancellable,
+                             GAsyncReadyCallback callback,
+                             gpointer user_data)
+{
+	g_return_if_fail (NM_IS_CLIENT (client));
+	g_return_if_fail (interface_name);
+	g_return_if_fail (property_name);
+	g_return_if_fail (value);
+
+	_nm_client_dbus_call (client,
+	                      client,
+	                      nm_client_dbus_set_property,
+	                      cancellable,
+	                      callback,
+	                      user_data,
+	                      object_path,
+	                      DBUS_INTERFACE_PROPERTIES,
+	                      "Set",
+	                      g_variant_new ("(ssv)",
+	                                     interface_name,
+	                                     property_name,
+	                                     value),
+	                      G_VARIANT_TYPE ("()"),
+	                      G_DBUS_CALL_FLAGS_NONE,
+	                        timeout_msec == -1
+	                      ? NM_DBUS_DEFAULT_TIMEOUT_MSEC
+	                      : timeout_msec,
+	                      nm_dbus_connection_call_finish_void_cb);
+}
+
+/**
+ * nm_client_dbus_set_property_finish:
+ * @client: the #NMClient instance
+ * @result: the result passed to the #GAsyncReadyCallback
+ * @error: location for a #GError, or %NULL
+ *
+ * Gets the result of a call to nm_client_dbus_set_property().
+ *
+ * Returns: %TRUE on success or %FALSE on failure.
+ *
+ * Since: 1.24
+ **/
+gboolean
+nm_client_dbus_set_property_finish (NMClient *client,
+                                    GAsyncResult *result,
+                                    GError **error)
+{
+	g_return_val_if_fail (NM_IS_CLIENT (client), FALSE);
+	g_return_val_if_fail (nm_g_task_is_valid (result, client, nm_client_dbus_set_property), FALSE);
+
+	return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+/*****************************************************************************/
+
 static void
 _init_fetch_all (NMClient *self)
 {
@@ -6554,13 +6830,13 @@ _init_fetch_all (NMClient *self)
 
 	priv->get_managed_objects_cancellable = g_cancellable_new ();
 
-	priv->dbsid_nm_object_manager = nm_dbus_connection_signal_subscribe_object_manager_plain (priv->dbus_connection,
-	                                                                                          priv->name_owner,
-	                                                                                          "/org/freedesktop",
-	                                                                                          NULL,
-	                                                                                          _dbus_managed_objects_changed_cb,
-	                                                                                          self,
-	                                                                                          NULL);
+	priv->dbsid_nm_object_manager = nm_dbus_connection_signal_subscribe_object_manager (priv->dbus_connection,
+	                                                                                    priv->name_owner,
+	                                                                                    "/org/freedesktop",
+	                                                                                    NULL,
+	                                                                                    _dbus_managed_objects_changed_cb,
+	                                                                                    self,
+	                                                                                    NULL);
 
 	priv->dbsid_dbus_properties_properties_changed = nm_dbus_connection_signal_subscribe_properties_changed (priv->dbus_connection,
 	                                                                                                         priv->name_owner,
@@ -6637,6 +6913,7 @@ _init_release_all (NMClient *self)
 	CList **dbus_objects_lst_heads;
 	NMLDBusObject *dbobj;
 	int i;
+	gboolean permissions_state_changed = FALSE;
 
 	NML_NMCLIENT_LOG_D (self, "release all");
 
@@ -6656,12 +6933,20 @@ _init_release_all (NMClient *self)
 	nm_clear_g_dbus_connection_signal (priv->dbus_connection,
 	                                   &priv->dbsid_nm_check_permissions);
 
+	if (priv->permissions_state != NM_TERNARY_DEFAULT) {
+		priv->permissions_state = NM_TERNARY_DEFAULT;
+		permissions_state_changed = TRUE;
+	}
+
 	if (priv->permissions) {
-		gs_unref_hashtable GHashTable *old_permissions = g_steal_pointer (&priv->permissions);
+		gs_free guint8 *old_permissions = g_steal_pointer (&priv->permissions);
 
-		_emit_permissions_changed (self, old_permissions, TRUE);
+		_emit_permissions_changed (self, old_permissions, NULL);
 	}
 
+	if (permissions_state_changed)
+		_notify (self, PROP_PERMISSIONS_STATE);
+
 	nm_assert (c_list_is_empty (&priv->obj_changed_lst_head));
 
 	dbus_objects_lst_heads = ((CList *[]) {
@@ -6726,7 +7011,7 @@ name_owner_changed (NMClient *self,
 
 		old_context_busy_watcher = g_steal_pointer (&priv->context_busy_watcher);
 		priv->context_busy_watcher = g_object_ref (g_object_get_qdata (old_context_busy_watcher,
-		                                                               nm_client_context_busy_watcher_quark ()));
+		                                                               nm_context_busy_watcher_quark ()));
 
 		g_main_context_ref (priv->main_context);
 		g_main_context_unref (priv->dbus_context);
@@ -6825,7 +7110,7 @@ name_owner_get_cb (GObject *source,
 	ret = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), result, &error);
 
 	if (   !ret
-	    && nm_utils_error_is_cancelled (error, FALSE))
+	    && nm_utils_error_is_cancelled (error))
 		return;
 
 	priv = NM_CLIENT_GET_PRIVATE (self);
@@ -6862,35 +7147,80 @@ name_owner_get_call (NMClient *self)
 
 /*****************************************************************************/
 
+static inline gboolean
+_nml_cleanup_context_busy_watcher_on_idle_cb (gpointer user_data)
+{
+	nm_auto_unref_gmaincontext GMainContext *context = NULL;
+	gs_unref_object GObject *context_busy_watcher = NULL;
+
+	nm_utils_user_data_unpack (user_data, &context, &context_busy_watcher);
+
+	nm_assert (context);
+	nm_assert (G_IS_OBJECT (context_busy_watcher));
+	return G_SOURCE_REMOVE;
+}
+
+void
+nml_cleanup_context_busy_watcher_on_idle (GObject *context_busy_watcher_take,
+                                          GMainContext *context)
+{
+	gs_unref_object GObject *context_busy_watcher = g_steal_pointer (&context_busy_watcher_take);
+	GSource *cleanup_source;
+
+	nm_assert (G_IS_OBJECT (context_busy_watcher));
+	nm_assert (context);
+
+	/* Technically, we cancelled all pending actions (and these actions
+	 * (GTask) keep the context_busy_watcher object alive). Also, we passed
+	 * no destroy notify to g_dbus_connection_signal_subscribe().
+	 * That means, there should be no other unaccounted GSource'es left.
+	 *
+	 * However, we really need to be sure that the context_busy_watcher's
+	 * lifetime matches the time that the context is busy. That is especially
+	 * important with synchronous initialization, where the context-busy-watcher
+	 * keeps the inner GMainContext integrated in the caller's.
+	 * We must not g_source_destroy() that integration too early.
+	 *
+	 * So to be really sure all this is given, always schedule one last
+	 * cleanup idle action with low priority. This should be the last
+	 * thing related to this instance that keeps the context busy.
+	 *
+	 * Note that we could also *not* take a reference on @context
+	 * and unref @context_busy_watcher via the GDestroyNotify. That would
+	 * allow for the context to be wrapped up early, and when the last user
+	 * gives up the reference to the context, the destroy notify could complete
+	 * without even invoke the idle handler. However, that destroy notify may
+	 * not be called in the right thread. So, we want to be sure that we unref
+	 * the context-busy-watcher in the right context. Hence, we always take an
+	 * additional reference and always cleanup in the idle handler. This means:
+	 * the user *MUST* always keep iterating the context after NMClient got destroyed.
+	 * But that is not a severe limitation, because the user anyway must be prepared
+	 * to do that. That is because in many cases it is necessary anyway (and the user
+	 * wouldn't know a priory when not). This way, it is just always necessary. */
+
+	cleanup_source = nm_g_idle_source_new (G_PRIORITY_LOW + 10,
+	                                       _nml_cleanup_context_busy_watcher_on_idle_cb,
+	                                       nm_utils_user_data_pack (g_main_context_ref (context),
+	                                                                g_steal_pointer (&context_busy_watcher)),
+	                                       NULL);
+	g_source_attach (cleanup_source, context);
+	g_source_unref (cleanup_source);
+}
+
+/*****************************************************************************/
+
 static void
 _init_start_complete (NMClient *self,
                       GError *error_take)
 {
 	NMClientPrivate *priv = NM_CLIENT_GET_PRIVATE (self);
-	InitData *init_data;
-
-	init_data = g_steal_pointer (&priv->init_data);
 
 	NML_NMCLIENT_LOG_D (self, "%s init complete with %s%s%s",
-	                   init_data->is_sync ? "sync" : "async",
+	                   priv->init_data->is_sync ? "sync" : "async",
 	                   NM_PRINT_FMT_QUOTED (error_take, "error: ", error_take->message, "", "success"));
 
-	nm_clear_pointer (&init_data->cancel_on_idle_source, nm_g_source_destroy_and_unref);
-	nm_clear_g_signal_handler (init_data->cancellable, &init_data->cancelled_id);
-
-	if (init_data->is_sync) {
-		if (error_take)
-			g_propagate_error (init_data->data.sync.error_location, error_take);
-		g_main_loop_quit (init_data->data.sync.main_loop);
-	} else {
-		if (error_take)
-			g_task_return_error (init_data->data.async.task, error_take);
-		else
-			g_task_return_boolean (init_data->data.async.task, TRUE);
-		g_object_unref (init_data->data.async.task);
-	}
-	nm_g_object_unref (init_data->cancellable);
-	nm_g_slice_free (init_data);
+	nml_init_data_return (g_steal_pointer (&priv->init_data),
+	                      error_take);
 }
 
 static void
@@ -7033,6 +7363,9 @@ get_property (GObject *object, guint prop_id,
 	NMClientPrivate *priv = NM_CLIENT_GET_PRIVATE (object);
 
 	switch (prop_id) {
+	case PROP_INSTANCE_FLAGS:
+		g_value_set_uint (value, priv->instance_flags);
+		break;
 	case PROP_DBUS_CONNECTION:
 		g_value_set_object (value, priv->dbus_connection);
 		break;
@@ -7121,6 +7454,9 @@ get_property (GObject *object, guint prop_id,
 			g_value_take_boxed (value, out);
 		}
 		break;
+	case PROP_PERMISSIONS_STATE:
+		g_value_set_enum (value, priv->permissions_state);
+		break;
 
 	/* Settings properties. */
 	case PROP_CONNECTIONS:
@@ -7159,8 +7495,38 @@ set_property (GObject *object, guint prop_id,
 	NMClient *self = NM_CLIENT (object);
 	NMClientPrivate *priv = NM_CLIENT_GET_PRIVATE (self);
 	gboolean b;
+	guint v_uint;
 
 	switch (prop_id) {
+
+	case PROP_INSTANCE_FLAGS:
+		/* construct */
+
+		v_uint = g_value_get_uint (value);
+		g_return_if_fail (!NM_FLAGS_ANY (v_uint, ~((guint) NM_CLIENT_INSTANCE_FLAGS_ALL)));
+		v_uint &= ((guint) NM_CLIENT_INSTANCE_FLAGS_ALL);
+
+		if (!priv->instance_flags_constructed) {
+			priv->instance_flags_constructed = TRUE;
+			priv->instance_flags = v_uint;
+			nm_assert ((guint) priv->instance_flags == v_uint);
+		} else {
+			NMClientInstanceFlags flags = v_uint;
+
+			/* After object construction, we only allow to toggle certain flags and
+			 * ignore all other flags. */
+
+			if ((priv->instance_flags ^ flags) & NM_CLIENT_INSTANCE_FLAGS_NO_AUTO_FETCH_PERMISSIONS) {
+				if (NM_FLAGS_HAS (flags, NM_CLIENT_INSTANCE_FLAGS_NO_AUTO_FETCH_PERMISSIONS))
+					priv->instance_flags |= NM_CLIENT_INSTANCE_FLAGS_NO_AUTO_FETCH_PERMISSIONS;
+				else
+					priv->instance_flags &= ~NM_CLIENT_INSTANCE_FLAGS_NO_AUTO_FETCH_PERMISSIONS;
+				if (priv->dbsid_nm_check_permissions != 0)
+					_dbus_check_permissions_start (self);
+			}
+		}
+		break;
+
 	case PROP_DBUS_CONNECTION:
 		/* construct-only */
 		priv->dbus_connection = g_value_dup_object (value);
@@ -7253,12 +7619,12 @@ init_sync (GInitable *initable, GCancellable *cancellable, GError **error)
 	 * to resync and drop the inner context. That means, requests made against the inner
 	 * context have a different lifetime. Hence, we create a separate tracking
 	 * object. This "wraps" the outer context-busy-watcher and references it, so
-	 * that the work together. Grep for nm_client_context_busy_watcher_quark() to
+	 * that the work together. Grep for nm_context_busy_watcher_quark() to
 	 * see how this works. */
 	parent_context_busy_watcher = g_steal_pointer (&priv->context_busy_watcher);
 	priv->context_busy_watcher = g_object_new (G_TYPE_OBJECT, NULL);
 	g_object_set_qdata_full (priv->context_busy_watcher,
-	                         nm_client_context_busy_watcher_quark (),
+	                         nm_context_busy_watcher_quark (),
 	                         parent_context_busy_watcher,
 	                         g_object_unref);
 
@@ -7266,7 +7632,7 @@ init_sync (GInitable *initable, GCancellable *cancellable, GError **error)
 
 	main_loop = g_main_loop_new (dbus_context, FALSE);
 
-	priv->init_data = _init_data_new_sync (cancellable, main_loop, &local_error);
+	priv->init_data = nml_init_data_new_sync (cancellable, main_loop, &local_error);
 
 	_init_start (self);
 
@@ -7277,12 +7643,9 @@ init_sync (GInitable *initable, GCancellable *cancellable, GError **error)
 	g_main_context_pop_thread_default (dbus_context);
 
 	if (priv->main_context != priv->dbus_context) {
-		GSource *source;
-
-		source = nm_utils_g_main_context_create_integrate_source (priv->dbus_context);
-		g_source_attach (source, priv->main_context);
-		_context_busy_watcher_attach_integration_source (priv->context_busy_watcher,
-		                                                 g_steal_pointer (&source));
+		nm_context_busy_watcher_integrate_source (priv->main_context,
+		                                          priv->dbus_context,
+		                                          priv->context_busy_watcher);
 	}
 
 	g_main_context_unref (dbus_context);
@@ -7322,7 +7685,7 @@ init_async (GAsyncInitable *initable,
 	task = nm_g_task_new (self, cancellable, init_async, callback, user_data);
 	g_task_set_priority (task, io_priority);
 
-	priv->init_data = _init_data_new_async (cancellable, g_steal_pointer (&task));
+	priv->init_data = nml_init_data_new_async (cancellable, g_steal_pointer (&task));
 
 	_init_start (self);
 }
@@ -7343,6 +7706,8 @@ nm_client_init (NMClient *self)
 {
 	NMClientPrivate *priv = NM_CLIENT_GET_PRIVATE (self);
 
+	priv->permissions_state = NM_TERNARY_DEFAULT;
+
 	priv->context_busy_watcher = g_object_new (G_TYPE_OBJECT, NULL);
 
 	c_list_init (&self->obj_base.queue_notify_lst);
@@ -7441,18 +7806,6 @@ constructed (GObject *object)
 	NML_NMCLIENT_LOG_D (self, "new NMClient instance");
 }
 
-static inline gboolean
-_dispose_cleanup_context_busy_watcher_cb (gpointer user_data)
-{
-	nm_auto_unref_gmaincontext GMainContext *context = NULL;
-	gs_unref_object GObject *context_busy_watcher = NULL;
-
-	nm_utils_user_data_unpack (user_data, &context, &context_busy_watcher);
-
-	nm_assert (G_IS_OBJECT (context_busy_watcher));
-	return G_SOURCE_REMOVE;
-}
-
 static void
 dispose (GObject *object)
 {
@@ -7503,48 +7856,14 @@ dispose (GObject *object)
 
 	if (   priv->context_busy_watcher
 	    && priv->dbus_context) {
-		GSource *cleanup_source;
-
-		/* Technically, we cancelled all pending actions (and these actions
-		 * (GTask) keep the context_busy_watcher object alive). Also, we passed
-		 * no destroy notify to g_dbus_connection_signal_subscribe().
-		 * That means, there should be no other unaccounted GSource'es left.
-		 *
-		 * However, we really need to be sure that the context_busy_watcher's
-		 * lifetime matches the time that the context is busy. That is especially
-		 * important with synchronous initialization, where the context-busy-watcher
-		 * keeps the inner GMainContext integrated in the caller's.
-		 * We must not g_source_destroy() that integration too early.
-		 *
-		 * So to be really sure all this is given, always schedule one last
-		 * cleanup idle action with low priority. This should be the last
-		 * thing related to this instance that keeps the context busy.
-		 *
-		 * Note that we could also *not* take a reference on priv->dbus_context
-		 * and unref priv->context_busy_watcher via the GDestroyNotify. That would
-		 * allow for the context to be wrapped up early, and when the last user
-		 * gives up the reference to the context, the destroy notify could complete
-		 * without even invoke the idle handler. However, that destroy notify may
-		 * not be called in the right thread. So, we want to be sure that we unref
-		 * the context-busy-watcher in the right context. Hence, we always take an
-		 * additional reference and always cleanup in the idle handler. This means:
-		 * the user *MUST* always keep iterating the context after NMClient got destroyed.
-		 * But that is not a severe limitation, because the user anyway must be prepared
-		 * to do that. That is because in many cases it is necessary anyway (and the user
-		 * wouldn't know a priory when not). This way, it is just always necessary. */
-		cleanup_source = nm_g_idle_source_new (G_PRIORITY_LOW + 10,
-		                                       _dispose_cleanup_context_busy_watcher_cb,
-		                                       nm_utils_user_data_pack (g_main_context_ref (priv->dbus_context),
-		                                                                g_steal_pointer (&priv->context_busy_watcher)),
-		                                       NULL);
-		g_source_attach (cleanup_source, priv->dbus_context);
-		g_source_unref (cleanup_source);
+		nml_cleanup_context_busy_watcher_on_idle (g_steal_pointer (&priv->context_busy_watcher),
+		                                          priv->dbus_context);
 	}
 
 	nm_clear_pointer (&priv->dbus_context, g_main_context_unref);
 	nm_clear_pointer (&priv->main_context, g_main_context_unref);
 
-	nm_clear_pointer (&priv->permissions, g_hash_table_unref);
+	nm_clear_g_free (&priv->permissions);
 
 	g_clear_object (&priv->dbus_connection);
 
@@ -7554,6 +7873,8 @@ dispose (GObject *object)
 
 	priv->nm.capabilities_len = 0;
 	nm_clear_g_free (&priv->nm.capabilities_arr);
+
+	NML_NMCLIENT_LOG_D (self, "disposed");
 }
 
 const NMLDBusMetaIface _nml_dbus_meta_iface_nm_agentmanager = NML_DBUS_META_IFACE_INIT (
@@ -7649,6 +7970,30 @@ nm_client_class_init (NMClientClass *client_class)
 	                         G_PARAM_STATIC_STRINGS);
 
 	/**
+	 * NMClient:instance-flags:
+	 *
+	 * #NMClientInstanceFlags for the instance. These affect behavior of #NMClient.
+	 * This is a construct property and you may only set most flags only during
+	 * construction.
+	 *
+	 * The flag %NM_CLIENT_INSTANCE_FLAGS_NO_AUTO_FETCH_PERMISSIONS can be toggled any time,
+	 * even after constructing the instance. Note that you may want to watch NMClient:permissions-state
+	 * property to know whether permissions are ready. Note that permissions are only fetched
+	 * when NMClient has a D-Bus name owner.
+	 *
+	 * Since: 1.24
+	 */
+	obj_properties[PROP_INSTANCE_FLAGS] =
+	    g_param_spec_uint (NM_CLIENT_INSTANCE_FLAGS, "", "",
+	                       0,
+	                       G_MAXUINT32,
+	                       0,
+	                       G_PARAM_READABLE |
+	                       G_PARAM_WRITABLE |
+	                       G_PARAM_CONSTRUCT |
+	                       G_PARAM_STATIC_STRINGS);
+
+	/**
 	 * NMClient:dbus-name-owner:
 	 *
 	 * The name owner of the NetworkManager D-Bus service.
@@ -8014,7 +8359,7 @@ nm_client_class_init (NMClientClass *client_class)
 	 * there are no capabitilies. The numeric value correspond
 	 * to %NMCapability enum.
 	 *
-	 * Since: 1.24, 1.22.2
+	 * Since: 1.24
 	 */
 	obj_properties[PROP_CAPABILITIES] =
 	    g_param_spec_boxed (NM_CLIENT_CAPABILITIES, "", "",
@@ -8022,6 +8367,32 @@ nm_client_class_init (NMClientClass *client_class)
 	                        G_PARAM_READABLE |
 	                        G_PARAM_STATIC_STRINGS);
 
+	/**
+	 * NMClient:permissions-state:
+	 *
+	 * The state of the cached permissions. The value %NM_TERNARY_DEFAULT
+	 * means that no permissions are yet received (or not yet requested).
+	 * %NM_TERNARY_TRUE means that permissions are received, cached and up
+	 * to date. %NM_TERNARY_FALSE means that permissions were received and are
+	 * cached, but in the meantime a "CheckPermissions" signal was received
+	 * that invalidated the cached permissions.
+	 * Note that NMClient will always emit a notify::permissions-state signal
+	 * when a "CheckPermissions" signal got received or after new permissions
+	 * got received (that is regardless whether the value of the permission state
+	 * actually changed). With this you can watch the permissions-state property
+	 * to know whether the permissions are ready. Note that while NMClient has
+	 * no D-Bus name owner, no permissions are fetched (and this property won't
+	 * change).
+	 *
+	 * Since: 1.24
+	 */
+	obj_properties[PROP_PERMISSIONS_STATE] =
+	    g_param_spec_enum (NM_CLIENT_PERMISSIONS_STATE, "", "",
+	                       NM_TYPE_TERNARY,
+	                       NM_TERNARY_DEFAULT,
+	                       G_PARAM_READABLE |
+	                       G_PARAM_STATIC_STRINGS);
+
 	_nml_dbus_meta_class_init_with_properties (object_class, &_nml_dbus_meta_iface_nm,
 	                                                         &_nml_dbus_meta_iface_nm_settings,
 	                                                         &_nml_dbus_meta_iface_nm_dnsmanager);