summary refs log tree commit diff
path: root/libnm/nm-dbus-helpers.c
diff options
context:
space:
mode:
Diffstat (limited to 'libnm/nm-dbus-helpers.c')
-rw-r--r--libnm/nm-dbus-helpers.c405
1 files changed, 405 insertions, 0 deletions
diff --git a/libnm/nm-dbus-helpers.c b/libnm/nm-dbus-helpers.c
new file mode 100644
index 00000000..7a99e70d
--- /dev/null
+++ b/libnm/nm-dbus-helpers.c
@@ -0,0 +1,405 @@
+/* -*- 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
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright 2013 Red Hat, Inc.
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <gio/gio.h>
+#include "nm-dbus-helpers.h"
+#include "nm-dbus-interface.h"
+
+static GBusType nm_bus = G_BUS_TYPE_SYSTEM;
+
+GBusType
+_nm_dbus_bus_type (void)
+{
+	static gsize init_value = 0;
+
+	if (g_once_init_enter (&init_value)) {
+		if (g_getenv ("LIBNM_USE_SESSION_BUS"))
+			nm_bus = G_BUS_TYPE_SESSION;
+
+		g_once_init_leave (&init_value, 1);
+	}
+
+	return nm_bus;
+}
+
+static struct {
+	GMutex mutex;
+	GWeakRef weak_ref;
+} private_connection;
+
+static void
+_private_dbus_connection_closed_cb (GDBusConnection *connection,
+                                    gboolean         remote_peer_vanished,
+                                    GError          *error,
+                                    gpointer         user_data)
+{
+	GDBusConnection *p;
+
+	g_mutex_lock (&private_connection.mutex);
+	p = g_weak_ref_get (&private_connection.weak_ref);
+	if (connection == p) {
+		g_signal_handlers_disconnect_by_func (G_OBJECT (connection), G_CALLBACK (_private_dbus_connection_closed_cb), NULL);
+		g_weak_ref_set (&private_connection.weak_ref, NULL);
+	}
+	if (p)
+		g_object_unref (p);
+	g_mutex_unlock (&private_connection.mutex);
+}
+
+static GDBusConnection *
+_private_dbus_connection_internalize (GDBusConnection *connection)
+{
+	GDBusConnection *p;
+
+	g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL);
+	g_return_val_if_fail (!g_dbus_connection_is_closed (connection), NULL);
+
+	g_mutex_lock (&private_connection.mutex);
+	p = g_weak_ref_get (&private_connection.weak_ref);
+	if (p) {
+		g_object_unref (connection);
+		connection = p;
+	} else {
+		g_weak_ref_set (&private_connection.weak_ref, connection);
+		g_signal_connect (connection, "closed", G_CALLBACK (_private_dbus_connection_closed_cb), NULL);
+	}
+	g_mutex_unlock (&private_connection.mutex);
+	return connection;
+}
+
+GDBusConnection *
+_nm_dbus_new_connection (GCancellable *cancellable, GError **error)
+{
+	GDBusConnection *connection = NULL;
+
+	/* If running as root try the private bus first */
+	if (0 == geteuid ()) {
+		GError *local = NULL;
+		GDBusConnection *p;
+
+		p = g_weak_ref_get (&private_connection.weak_ref);
+		if (p)
+			return p;
+
+		connection = g_dbus_connection_new_for_address_sync ("unix:path=" NMRUNDIR "/private",
+		                                                     G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT,
+		                                                     NULL, cancellable, &local);
+		if (connection)
+			return _private_dbus_connection_internalize (connection);
+
+		if (g_error_matches (local, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+			g_propagate_error (error, local);
+			return NULL;
+		}
+		g_error_free (local);
+	}
+
+	return g_bus_get_sync (_nm_dbus_bus_type (), cancellable, error);
+}
+
+static void
+new_connection_async_got_system (GObject *source, GAsyncResult *result, gpointer user_data)
+{
+	GSimpleAsyncResult *simple = user_data;
+	GDBusConnection *connection;
+	GError *error = NULL;
+
+	connection = g_bus_get_finish (result, &error);
+	if (connection)
+		g_simple_async_result_set_op_res_gpointer (simple, connection, g_object_unref);
+	else
+		g_simple_async_result_take_error (simple, error);
+
+	g_simple_async_result_complete (simple);
+	g_object_unref (simple);
+}
+
+static void
+new_connection_async_got_private (GObject *source, GAsyncResult *result, gpointer user_data)
+{
+	GSimpleAsyncResult *simple = user_data;
+	GDBusConnection *connection;
+	GError *error = NULL;
+
+	connection = g_dbus_connection_new_for_address_finish (result, &error);
+	if (connection) {
+		connection = _private_dbus_connection_internalize (connection);
+		g_simple_async_result_set_op_res_gpointer (simple, connection, g_object_unref);
+		g_simple_async_result_complete (simple);
+		g_object_unref (simple);
+		return;
+	}
+
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+		g_simple_async_result_take_error (simple, error);
+		g_simple_async_result_complete (simple);
+		g_object_unref (simple);
+		return;
+	}
+
+	g_clear_error (&error);
+	g_bus_get (_nm_dbus_bus_type (),
+	           g_object_get_data (G_OBJECT (simple), "cancellable"),
+	           new_connection_async_got_system, simple);
+}
+
+static void
+_nm_dbus_new_connection_async_do (GSimpleAsyncResult *simple, GCancellable *cancellable)
+{
+	g_dbus_connection_new_for_address ("unix:path=" NMRUNDIR "/private",
+	                                   G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT,
+	                                   NULL,
+	                                   cancellable,
+	                                   new_connection_async_got_private, simple);
+}
+
+static gboolean
+_nm_dbus_new_connection_async_get_private (gpointer user_data)
+{
+	GSimpleAsyncResult *simple = user_data;
+	GDBusConnection *p;
+
+	p = g_weak_ref_get (&private_connection.weak_ref);
+	if (!p) {
+		/* The connection is gone. Create a new one async... */
+		_nm_dbus_new_connection_async_do (simple,
+		                                  g_object_get_data (G_OBJECT (simple), "cancellable"));
+	} else {
+		g_simple_async_result_set_op_res_gpointer (simple, p, g_object_unref);
+		g_simple_async_result_complete (simple);
+		g_object_unref (simple);
+	}
+
+	return G_SOURCE_REMOVE;
+}
+
+void
+_nm_dbus_new_connection_async (GCancellable *cancellable,
+                               GAsyncReadyCallback callback,
+                               gpointer user_data)
+{
+	GSimpleAsyncResult *simple;
+
+	simple = g_simple_async_result_new (NULL, callback, user_data, _nm_dbus_new_connection_async);
+
+	/* If running as root try the private bus first */
+	if (0 == geteuid ()) {
+		GDBusConnection *p;
+
+		if (cancellable) {
+			g_object_set_data_full (G_OBJECT (simple), "cancellable",
+			                        g_object_ref (cancellable), g_object_unref);
+		}
+		p = g_weak_ref_get (&private_connection.weak_ref);
+		if (p) {
+			g_object_unref (p);
+			g_idle_add (_nm_dbus_new_connection_async_get_private, simple);
+		} else
+			_nm_dbus_new_connection_async_do (simple, cancellable);
+	} else {
+		g_bus_get (_nm_dbus_bus_type (),
+		           cancellable,
+		           new_connection_async_got_system, simple);
+	}
+}
+
+GDBusConnection *
+_nm_dbus_new_connection_finish (GAsyncResult *result,
+                                GError **error)
+{
+	GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result);
+
+	if (g_simple_async_result_propagate_error (simple, error))
+		return NULL;
+
+	return g_object_ref (g_simple_async_result_get_op_res_gpointer (simple));
+}
+
+gboolean
+_nm_dbus_is_connection_private (GDBusConnection *connection)
+{
+	return g_dbus_connection_get_unique_name (connection) == NULL;
+}
+
+static GHashTable *proxy_types;
+
+#undef _nm_dbus_register_proxy_type
+void
+_nm_dbus_register_proxy_type (const char *interface,
+                              GType       proxy_type)
+{
+	if (!proxy_types)
+		proxy_types = g_hash_table_new (g_str_hash, g_str_equal);
+
+	g_assert (g_hash_table_lookup (proxy_types, interface) == NULL);
+	g_hash_table_insert (proxy_types, (char *) interface, GSIZE_TO_POINTER (proxy_type));
+}
+
+/* We don't (currently) use GDBus's property-handling code */
+#define NM_DBUS_PROXY_FLAGS (G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | \
+                             G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START)
+
+GDBusProxy *
+_nm_dbus_new_proxy_for_connection (GDBusConnection *connection,
+                                   const char *path,
+                                   const char *interface,
+                                   GCancellable *cancellable,
+                                   GError **error)
+{
+	GType proxy_type;
+	const char *name;
+
+	proxy_type = GPOINTER_TO_SIZE (g_hash_table_lookup (proxy_types, interface));
+	if (!proxy_type)
+		proxy_type = G_TYPE_DBUS_PROXY;
+
+	if (_nm_dbus_is_connection_private (connection))
+		name = NULL;
+	else
+		name = NM_DBUS_SERVICE;
+
+	return g_initable_new (proxy_type, cancellable, error,
+	                       "g-connection", connection,
+	                       "g-flags", NM_DBUS_PROXY_FLAGS,
+	                       "g-name", name,
+	                       "g-object-path", path,
+	                       "g-interface-name", interface,
+	                       NULL);
+}
+
+void
+_nm_dbus_new_proxy_for_connection_async (GDBusConnection *connection,
+                                         const char *path,
+                                         const char *interface,
+                                         GCancellable *cancellable,
+                                         GAsyncReadyCallback callback,
+                                         gpointer user_data)
+{
+	GType proxy_type;
+	const char *name;
+
+	proxy_type = GPOINTER_TO_SIZE (g_hash_table_lookup (proxy_types, interface));
+	if (!proxy_type)
+		proxy_type = G_TYPE_DBUS_PROXY;
+
+	if (_nm_dbus_is_connection_private (connection))
+		name = NULL;
+	else
+		name = NM_DBUS_SERVICE;
+
+	g_async_initable_new_async (proxy_type, G_PRIORITY_DEFAULT,
+	                            cancellable, callback, user_data,
+	                            "g-connection", connection,
+	                            "g-flags", NM_DBUS_PROXY_FLAGS,
+	                            "g-name", name,
+	                            "g-object-path", path,
+	                            "g-interface-name", interface,
+	                            NULL);
+}
+
+GDBusProxy *
+_nm_dbus_new_proxy_for_connection_finish (GAsyncResult *result,
+                                          GError **error)
+{
+	GObject *source, *proxy;
+
+	source = g_async_result_get_source_object (result);
+	proxy = g_async_initable_new_finish (G_ASYNC_INITABLE (source), result, error);
+	g_object_unref (source);
+
+	return G_DBUS_PROXY (proxy);
+}
+
+/* Binds the properties on a generated server-side GDBus object to the
+ * corresponding properties on the public object.
+ */
+void
+_nm_dbus_bind_properties (gpointer object, gpointer skeleton)
+{
+	GParamSpec **properties;
+	guint n_properties;
+	int i;
+
+	properties = g_object_class_list_properties (G_OBJECT_GET_CLASS (skeleton), &n_properties);
+	for (i = 0; i < n_properties; i++) {
+		if (g_str_has_prefix (properties[i]->name, "g-"))
+			continue;
+
+		g_object_bind_property (object, properties[i]->name,
+		                        skeleton, properties[i]->name,
+		                        G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+	}
+}
+
+static char *
+signal_name_from_method_name (const char *method_name)
+{
+	GString *signal_name;
+	const char *p;
+
+	signal_name = g_string_new ("handle");
+	for (p = method_name; *p; p++) {
+		if (g_ascii_isupper (*p))
+			g_string_append_c (signal_name, '-');
+		g_string_append_c (signal_name, g_ascii_tolower (*p));
+	}
+
+	return g_string_free (signal_name, FALSE);
+}
+
+static void
+_nm_dbus_method_meta_marshal (GClosure *closure, GValue *return_value,
+                              guint n_param_values, const GValue *param_values,
+                              gpointer invocation_hint, gpointer marshal_data)
+{
+	closure->marshal (closure, return_value, n_param_values,
+	                  param_values, invocation_hint,
+	                  ((GCClosure *)closure)->callback);
+
+	g_value_set_boolean (return_value, TRUE);
+}
+
+/* Takes (method_name, handler_func) pairs and connects the handlers to the
+ * signals on skeleton, with object as the user_data, but swapped so it comes
+ * first in the argument list, and handling the return value automatically.
+ */
+void
+_nm_dbus_bind_methods (gpointer object, gpointer skeleton, ...)
+{
+	va_list ap;
+	const char *method_name;
+	char *signal_name;
+	GCallback handler;
+	GClosure *closure;
+
+	va_start (ap, skeleton);
+	while (   (method_name = va_arg (ap, const char *))
+	       && (handler = va_arg (ap, GCallback))) {
+		signal_name = signal_name_from_method_name (method_name);
+		closure = g_cclosure_new_swap (handler, object, NULL);
+		g_closure_set_meta_marshal (closure, NULL, _nm_dbus_method_meta_marshal);
+		g_signal_connect_closure (skeleton, signal_name, closure, FALSE);
+		g_free (signal_name);
+	}
+	va_end (ap);
+}