| /* GDBus - GLib D-Bus Library |
| * |
| * Copyright (C) 2008-2010 Red Hat, Inc. |
| * |
| * SPDX-License-Identifier: LGPL-2.1-or-later |
| * |
| * 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.1 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, see <http://www.gnu.org/licenses/>. |
| * |
| * Author: David Zeuthen <davidz@redhat.com> |
| */ |
| |
| /* |
| * TODO for GDBus: |
| * |
| * - would be nice to expose GDBusAuthMechanism and an extension point |
| * |
| * - Need to rewrite GDBusAuth and rework GDBusAuthMechanism. In particular |
| * the mechanism VFuncs need to be able to set an error. |
| * |
| * - Need to document other mechanisms/sources for determining the D-Bus |
| * address of a well-known bus. |
| * |
| * - e.g. on Win32 we need code like from here |
| * |
| * http://cgit.freedesktop.org/~david/gdbus-standalone/tree/gdbus/gdbusaddress.c#n900 |
| * |
| * that was never copied over here because it originally was copy-paste |
| * from the GPLv2 / AFL 2.1 libdbus sources. |
| * |
| * - on OS X we need to look in launchd for the address |
| * |
| * https://bugs.freedesktop.org/show_bug.cgi?id=14259 |
| * |
| * - on X11 we need to look in a X11 property on the X server |
| * - (we can also just use dbus-launch(1) from the D-Bus |
| * distribution) |
| * |
| * - (ideally) this requires D-Bus spec work because none of |
| * this has never really been specced out properly (except |
| * the X11 bits) |
| * |
| * - Related to the above, we also need to be able to launch a message bus |
| * instance.... Since we don't want to write our own bus daemon we should |
| * launch dbus-daemon(1) (thus: Win32 and OS X need to bundle it) |
| * |
| * - probably want a G_DBUS_NONCE_TCP_TMPDIR environment variable |
| * to specify where the nonce is stored. This will allow people to use |
| * G_DBUS_NONCE_TCP_TMPDIR=/mnt/secure.company.server/dbus-nonce-dir |
| * to easily achieve secure RPC via nonce-tcp. |
| * |
| * - need to expose an extension point for resolving D-Bus address and |
| * turning them into GIOStream objects. This will allow us to implement |
| * e.g. X11 D-Bus transports without dlopen()'ing or linking against |
| * libX11 from libgio. |
| * - see g_dbus_address_connect() in gdbusaddress.c |
| * |
| * - would be cute to use kernel-specific APIs to resolve fds for |
| * debug output when using G_DBUS_DEBUG=message, e.g. in addition to |
| * |
| * fd 21: dev=8:1,mode=0100644,ino=1171231,uid=0,gid=0,rdev=0:0,size=234,atime=1273070640,mtime=1267126160,ctime=1267126160 |
| * |
| * maybe we can show more information about what fd 21 really is. |
| * Ryan suggests looking in /proc/self/fd for clues / symlinks! |
| * Initial experiments on Linux 2.6 suggests that the symlink looks |
| * like this: |
| * |
| * 3 -> /proc/18068/fd |
| * |
| * e.g. not of much use. |
| * |
| * - GDBus High-Level docs |
| * - Proxy: properties, signals... |
| * - Connection: IOStream based, ::close, connection setup steps |
| * mainloop integration, threading |
| * - Differences from libdbus (extend "Migrating from") |
| * - the message handling thread |
| * - Using GVariant instead of GValue |
| * - Explain why the high-level API is a good thing and what |
| * kind of pitfalls it avoids |
| * - Export objects before claiming names |
| * - Talk about auto-starting services (cf. GBusNameWatcherFlags) |
| */ |
| |
| #include "config.h" |
| |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include "gdbusauth.h" |
| #include "gdbusutils.h" |
| #include "gdbusaddress.h" |
| #include "gdbusmessage.h" |
| #include "gdbusconnection.h" |
| #include "gdbuserror.h" |
| #include "gioenumtypes.h" |
| #include "gdbusintrospection.h" |
| #include "gdbusmethodinvocation.h" |
| #include "gdbusprivate.h" |
| #include "gdbusauthobserver.h" |
| #include "ginitable.h" |
| #include "gasyncinitable.h" |
| #include "giostream.h" |
| #include "gasyncresult.h" |
| #include "gtask.h" |
| #include "gmarshal-internal.h" |
| |
| #ifdef G_OS_UNIX |
| #include "gunixconnection.h" |
| #include "gunixfdmessage.h" |
| #endif |
| |
| #include "glibintl.h" |
| |
| #define G_DBUS_CONNECTION_FLAGS_ALL \ |
| (G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT | \ |
| G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER | \ |
| G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS | \ |
| G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION | \ |
| G_DBUS_CONNECTION_FLAGS_DELAY_MESSAGE_PROCESSING | \ |
| G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_REQUIRE_SAME_USER) |
| |
| /** |
| * GDBusConnection: |
| * |
| * The `GDBusConnection` type is used for D-Bus connections to remote |
| * peers such as a message buses. |
| * |
| * It is a low-level API that offers a lot of flexibility. For instance, |
| * it lets you establish a connection over any transport that can by represented |
| * as a [class@Gio.IOStream]. |
| * |
| * This class is rarely used directly in D-Bus clients. If you are writing |
| * a D-Bus client, it is often easier to use the [func@Gio.bus_own_name], |
| * [func@Gio.bus_watch_name] or [func@Gio.DBusProxy.new_for_bus] APIs. |
| * |
| * As an exception to the usual GLib rule that a particular object must not |
| * be used by two threads at the same time, `GDBusConnection`s methods may be |
| * called from any thread. This is so that [func@Gio.bus_get] and |
| * [func@Gio.bus_get_sync] can safely return the same `GDBusConnection` when |
| * called from any thread. |
| * |
| * Most of the ways to obtain a `GDBusConnection` automatically initialize it |
| * (i.e. connect to D-Bus): for instance, [func@Gio.DBusConnection.new] and |
| * [func@Gio.bus_get], and the synchronous versions of those methods, give you |
| * an initialized connection. Language bindings for GIO should use |
| * [func@Gio.Initable.new] or [func@Gio.AsyncInitable.new_async], which also |
| * initialize the connection. |
| * |
| * If you construct an uninitialized `GDBusConnection`, such as via |
| * [ctor@GObject.Object.new], you must initialize it via [method@Gio.Initable.init] or |
| * [method@Gio.AsyncInitable.init_async] before using its methods or properties. |
| * Calling methods or accessing properties on a `GDBusConnection` that has not |
| * completed initialization successfully is considered to be invalid, and leads |
| * to undefined behaviour. In particular, if initialization fails with a |
| * `GError`, the only valid thing you can do with that `GDBusConnection` is to |
| * free it with [method@GObject.Object.unref]. |
| * |
| * ## An example D-Bus server |
| * |
| * Here is an example for a D-Bus server: |
| * [gdbus-example-server.c](https://gitlab.gnome.org/GNOME/glib/-/blob/HEAD/gio/tests/gdbus-example-server.c) |
| * |
| * ## An example for exporting a subtree |
| * |
| * Here is an example for exporting a subtree: |
| * [gdbus-example-subtree.c](https://gitlab.gnome.org/GNOME/glib/-/blob/HEAD/gio/tests/gdbus-example-subtree.c) |
| * |
| * ## An example for file descriptor passing |
| * |
| * Here is an example for passing UNIX file descriptors: |
| * [gdbus-unix-fd-client.c](https://gitlab.gnome.org/GNOME/glib/-/blob/HEAD/gio/tests/gdbus-example-unix-fd-client.c) |
| * |
| * ## An example for exporting a GObject |
| * |
| * Here is an example for exporting a #GObject: |
| * [gdbus-example-export.c](https://gitlab.gnome.org/GNOME/glib/-/blob/HEAD/gio/tests/gdbus-example-export.c) |
| * |
| * Since: 2.26 |
| */ |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| typedef struct _GDBusConnectionClass GDBusConnectionClass; |
| |
| /** |
| * GDBusConnectionClass: |
| * @closed: Signal class handler for the #GDBusConnection::closed signal. |
| * |
| * Class structure for #GDBusConnection. |
| * |
| * Since: 2.26 |
| */ |
| struct _GDBusConnectionClass |
| { |
| /*< private >*/ |
| GObjectClass parent_class; |
| |
| /*< public >*/ |
| /* Signals */ |
| void (*closed) (GDBusConnection *connection, |
| gboolean remote_peer_vanished, |
| GError *error); |
| }; |
| |
| G_LOCK_DEFINE_STATIC (message_bus_lock); |
| |
| static GWeakRef the_session_bus; |
| static GWeakRef the_system_bus; |
| |
| /* Extra pseudo-member of GDBusSendMessageFlags. |
| * Set by initable_init() to indicate that despite not being initialized yet, |
| * enough of the only-valid-after-init members are set that we can send a |
| * message, and we're being called from its thread, so no memory barrier is |
| * required before accessing them. |
| */ |
| #define SEND_MESSAGE_FLAGS_INITIALIZING (1u << 31) |
| |
| /* Same as SEND_MESSAGE_FLAGS_INITIALIZING, but in GDBusCallFlags */ |
| #define CALL_FLAGS_INITIALIZING (1u << 31) |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| typedef struct |
| { |
| GDestroyNotify callback; |
| gpointer user_data; |
| } CallDestroyNotifyData; |
| |
| static gboolean |
| call_destroy_notify_data_in_idle (gpointer user_data) |
| { |
| CallDestroyNotifyData *data = user_data; |
| data->callback (data->user_data); |
| return FALSE; |
| } |
| |
| static void |
| call_destroy_notify_data_free (CallDestroyNotifyData *data) |
| { |
| g_free (data); |
| } |
| |
| /* |
| * call_destroy_notify: <internal> |
| * @context: (nullable): A #GMainContext or %NULL. |
| * @callback: (nullable): A #GDestroyNotify or %NULL. |
| * @user_data: Data to pass to @callback. |
| * |
| * Schedules @callback to run in @context. |
| */ |
| static void |
| call_destroy_notify (GMainContext *context, |
| GDestroyNotify callback, |
| gpointer user_data) |
| { |
| GSource *idle_source; |
| CallDestroyNotifyData *data; |
| |
| if (callback == NULL) |
| return; |
| |
| data = g_new0 (CallDestroyNotifyData, 1); |
| data->callback = callback; |
| data->user_data = user_data; |
| |
| idle_source = g_idle_source_new (); |
| g_source_set_priority (idle_source, G_PRIORITY_DEFAULT); |
| g_source_set_callback (idle_source, |
| call_destroy_notify_data_in_idle, |
| data, |
| (GDestroyNotify) call_destroy_notify_data_free); |
| g_source_set_static_name (idle_source, "[gio] call_destroy_notify_data_in_idle"); |
| g_source_attach (idle_source, context); |
| g_source_unref (idle_source); |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| #ifdef G_OS_WIN32 |
| #define CONNECTION_ENSURE_LOCK(obj) do { ; } while (FALSE) |
| #else |
| // TODO: for some reason this doesn't work on Windows |
| #define CONNECTION_ENSURE_LOCK(obj) do { \ |
| if (G_UNLIKELY (g_mutex_trylock(&(obj)->lock))) \ |
| { \ |
| g_assertion_message (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ |
| "CONNECTION_ENSURE_LOCK: GDBusConnection object lock is not locked"); \ |
| } \ |
| } while (FALSE) |
| #endif |
| |
| #define CONNECTION_LOCK(obj) do { \ |
| g_mutex_lock (&(obj)->lock); \ |
| } while (FALSE) |
| |
| #define CONNECTION_UNLOCK(obj) do { \ |
| g_mutex_unlock (&(obj)->lock); \ |
| } while (FALSE) |
| |
| /* Flags in connection->atomic_flags */ |
| enum { |
| FLAG_INITIALIZED = 1 << 0, |
| FLAG_EXIT_ON_CLOSE = 1 << 1, |
| FLAG_CLOSED = 1 << 2 |
| }; |
| |
| struct _GDBusConnection |
| { |
| /*< private >*/ |
| GObject parent_instance; |
| |
| /* ------------------------------------------------------------------------ */ |
| /* -- General object state ------------------------------------------------ */ |
| /* ------------------------------------------------------------------------ */ |
| |
| /* General-purpose lock for most fields */ |
| GMutex lock; |
| |
| /* A lock used in the init() method of the GInitable interface - see comments |
| * in initable_init() for why a separate lock is needed. |
| * |
| * If you need both @lock and @init_lock, you must take @init_lock first. |
| */ |
| GMutex init_lock; |
| |
| /* Set (by loading the contents of /var/lib/dbus/machine-id) the first time |
| * someone calls org.freedesktop.DBus.Peer.GetMachineId(). Protected by @lock. |
| */ |
| gchar *machine_id; |
| |
| /* The underlying stream used for communication |
| * Read-only after initable_init(), so it may be read if you either |
| * hold @init_lock or check for initialization first. |
| */ |
| GIOStream *stream; |
| |
| /* The object used for authentication (if any). |
| * Read-only after initable_init(), so it may be read if you either |
| * hold @init_lock or check for initialization first. |
| */ |
| GDBusAuth *auth; |
| |
| /* Last serial used. Protected by @lock. */ |
| guint32 last_serial; |
| |
| /* The object used to send/receive messages. |
| * Read-only after initable_init(), so it may be read if you either |
| * hold @init_lock or check for initialization first. |
| */ |
| GDBusWorker *worker; |
| |
| /* If connected to a message bus, this contains the unique name assigned to |
| * us by the bus (e.g. ":1.42"). |
| * Read-only after initable_init(), so it may be read if you either |
| * hold @init_lock or check for initialization first. |
| */ |
| gchar *bus_unique_name; |
| |
| /* The GUID returned by the other side if we authenticated as a client or |
| * the GUID to use if authenticating as a server. |
| * Read-only after initable_init(), so it may be read if you either |
| * hold @init_lock or check for initialization first. |
| */ |
| gchar *guid; |
| |
| /* FLAG_INITIALIZED is set exactly when initable_init() has finished running. |
| * Inspect @initialization_error to see whether it succeeded or failed. |
| * |
| * FLAG_EXIT_ON_CLOSE is the exit-on-close property. |
| * |
| * FLAG_CLOSED is the closed property. It may be read at any time, but |
| * may only be written while holding @lock. |
| */ |
| gint atomic_flags; /* (atomic) */ |
| |
| /* If the connection could not be established during initable_init(), |
| * this GError will be set. |
| * Read-only after initable_init(), so it may be read if you either |
| * hold @init_lock or check for initialization first. |
| */ |
| GError *initialization_error; |
| |
| /* The result of g_main_context_ref_thread_default() when the object |
| * was created (the GObject _init() function) - this is used for delivery |
| * of the :closed GObject signal. |
| * |
| * Only set in the GObject init function, so no locks are needed. |
| */ |
| GMainContext *main_context_at_construction; |
| |
| /* Read-only construct properties, no locks needed */ |
| gchar *address; |
| GDBusConnectionFlags flags; |
| |
| /* Map used for managing method replies, protected by @lock */ |
| GHashTable *map_method_serial_to_task; /* guint32 -> owned GTask* */ |
| |
| /* Maps used for managing signal subscription, protected by @lock */ |
| GHashTable *map_rule_to_signal_data; /* match rule (gchar*) -> SignalData */ |
| GHashTable *map_id_to_signal_data; /* id (guint) -> SignalData */ |
| GHashTable *map_sender_unique_name_to_signal_data_array; /* unique sender (gchar*) -> GPtrArray* of SignalData */ |
| |
| /* Maps used for managing exported objects and subtrees, |
| * protected by @lock |
| */ |
| GHashTable *map_object_path_to_eo; /* gchar* -> ExportedObject* */ |
| GHashTable *map_id_to_ei; /* guint -> ExportedInterface* */ |
| GHashTable *map_object_path_to_es; /* gchar* -> ExportedSubtree* */ |
| GHashTable *map_id_to_es; /* guint -> ExportedSubtree* */ |
| |
| /* Map used for storing last used serials for each thread, protected by @lock */ |
| GHashTable *map_thread_to_last_serial; |
| |
| /* Structure used for message filters, protected by @lock */ |
| GPtrArray *filters; |
| |
| /* Capabilities negotiated during authentication |
| * Read-only after initable_init(), so it may be read without holding a |
| * lock, if you check for initialization first. |
| */ |
| GDBusCapabilityFlags capabilities; |
| |
| /* Protected by @init_lock */ |
| GDBusAuthObserver *authentication_observer; |
| |
| /* Read-only after initable_init(), so it may be read if you either |
| * hold @init_lock or check for initialization first. |
| */ |
| GCredentials *credentials; |
| |
| /* set to TRUE when finalizing */ |
| gboolean finalizing; |
| }; |
| |
| typedef struct ExportedObject ExportedObject; |
| static void exported_object_free (ExportedObject *eo); |
| |
| typedef struct ExportedSubtree ExportedSubtree; |
| static ExportedSubtree *exported_subtree_ref (ExportedSubtree *es); |
| static void exported_subtree_unref (ExportedSubtree *es); |
| |
| enum |
| { |
| CLOSED_SIGNAL, |
| LAST_SIGNAL, |
| }; |
| |
| enum |
| { |
| PROP_0, |
| PROP_STREAM, |
| PROP_ADDRESS, |
| PROP_FLAGS, |
| PROP_GUID, |
| PROP_UNIQUE_NAME, |
| PROP_CLOSED, |
| PROP_EXIT_ON_CLOSE, |
| PROP_CAPABILITY_FLAGS, |
| PROP_AUTHENTICATION_OBSERVER, |
| }; |
| |
| static void distribute_signals (GDBusConnection *connection, |
| GDBusMessage *message); |
| |
| static void distribute_method_call (GDBusConnection *connection, |
| GDBusMessage *message); |
| |
| static gboolean handle_generic_unlocked (GDBusConnection *connection, |
| GDBusMessage *message); |
| |
| |
| static void purge_all_signal_subscriptions (GDBusConnection *connection); |
| static void purge_all_filters (GDBusConnection *connection); |
| |
| static void schedule_method_call (GDBusConnection *connection, |
| GDBusMessage *message, |
| guint registration_id, |
| guint subtree_registration_id, |
| const GDBusInterfaceInfo *interface_info, |
| const GDBusMethodInfo *method_info, |
| const GDBusPropertyInfo *property_info, |
| GVariant *parameters, |
| const GDBusInterfaceVTable *vtable, |
| GMainContext *main_context, |
| gpointer user_data); |
| |
| #define _G_ENSURE_LOCK(name) do { \ |
| if (G_UNLIKELY (G_TRYLOCK(name))) \ |
| { \ |
| g_assertion_message (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ |
| "_G_ENSURE_LOCK: Lock '" #name "' is not locked"); \ |
| } \ |
| } while (FALSE) \ |
| |
| static guint signals[LAST_SIGNAL] = { 0 }; |
| |
| static void initable_iface_init (GInitableIface *initable_iface); |
| static void async_initable_iface_init (GAsyncInitableIface *async_initable_iface); |
| |
| G_DEFINE_TYPE_WITH_CODE (GDBusConnection, g_dbus_connection, G_TYPE_OBJECT, |
| G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init) |
| G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, async_initable_iface_init) |
| ); |
| |
| /* |
| * Check that all members of @connection that can only be accessed after |
| * the connection is initialized can safely be accessed. If not, |
| * log a critical warning. This function is a memory barrier. |
| * |
| * Returns: %TRUE if initialized |
| */ |
| static gboolean |
| check_initialized (GDBusConnection *connection) |
| { |
| /* The access to @atomic_flags isn't conditional, so that this function |
| * provides a memory barrier for thread-safety even if checks are disabled. |
| * (If you don't want this stricter guarantee, you can call |
| * g_return_if_fail (check_initialized (c)).) |
| * |
| * This isn't strictly necessary now that we've decided use of an |
| * uninitialized GDBusConnection is undefined behaviour, but it seems |
| * better to be as deterministic as is feasible. |
| * |
| * (Anything that could suffer a crash from seeing undefined values |
| * must have a race condition - thread A initializes the connection while |
| * thread B calls a method without initialization, hoping that thread A will |
| * win the race - so its behaviour is undefined anyway.) |
| */ |
| gint flags = g_atomic_int_get (&connection->atomic_flags); |
| |
| g_return_val_if_fail (flags & FLAG_INITIALIZED, FALSE); |
| |
| /* We can safely access this, due to the memory barrier above */ |
| g_return_val_if_fail (connection->initialization_error == NULL, FALSE); |
| |
| return TRUE; |
| } |
| |
| typedef enum { |
| MAY_BE_UNINITIALIZED = (1<<1) |
| } CheckUnclosedFlags; |
| |
| /* |
| * Check the same thing as check_initialized(), and also that the |
| * connection is not closed. If the connection is uninitialized, |
| * raise a critical warning (it's programmer error); if it's closed, |
| * raise a recoverable GError (it's a runtime error). |
| * |
| * This function is a memory barrier. |
| * |
| * Returns: %TRUE if initialized and not closed |
| */ |
| static gboolean |
| check_unclosed (GDBusConnection *connection, |
| CheckUnclosedFlags check, |
| GError **error) |
| { |
| /* check_initialized() is effectively inlined, so we don't waste time |
| * doing two memory barriers |
| */ |
| gint flags = g_atomic_int_get (&connection->atomic_flags); |
| |
| if (!(check & MAY_BE_UNINITIALIZED)) |
| { |
| g_return_val_if_fail (flags & FLAG_INITIALIZED, FALSE); |
| g_return_val_if_fail (connection->initialization_error == NULL, FALSE); |
| } |
| |
| if (flags & FLAG_CLOSED) |
| { |
| g_set_error_literal (error, |
| G_IO_ERROR, |
| G_IO_ERROR_CLOSED, |
| _("The connection is closed")); |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| static GHashTable *alive_connections = NULL; |
| |
| static void |
| g_dbus_connection_dispose (GObject *object) |
| { |
| GDBusConnection *connection = G_DBUS_CONNECTION (object); |
| |
| G_LOCK (message_bus_lock); |
| CONNECTION_LOCK (connection); |
| if (connection->worker != NULL) |
| { |
| _g_dbus_worker_stop (connection->worker); |
| connection->worker = NULL; |
| if (alive_connections != NULL) |
| g_warn_if_fail (g_hash_table_remove (alive_connections, connection)); |
| } |
| else |
| { |
| if (alive_connections != NULL) |
| g_warn_if_fail (!g_hash_table_contains (alive_connections, connection)); |
| } |
| CONNECTION_UNLOCK (connection); |
| G_UNLOCK (message_bus_lock); |
| |
| if (G_OBJECT_CLASS (g_dbus_connection_parent_class)->dispose != NULL) |
| G_OBJECT_CLASS (g_dbus_connection_parent_class)->dispose (object); |
| } |
| |
| static void |
| g_dbus_connection_finalize (GObject *object) |
| { |
| GDBusConnection *connection = G_DBUS_CONNECTION (object); |
| |
| connection->finalizing = TRUE; |
| |
| purge_all_signal_subscriptions (connection); |
| |
| purge_all_filters (connection); |
| g_ptr_array_unref (connection->filters); |
| |
| if (connection->authentication_observer != NULL) |
| g_object_unref (connection->authentication_observer); |
| |
| if (connection->auth != NULL) |
| g_object_unref (connection->auth); |
| |
| if (connection->credentials) |
| g_object_unref (connection->credentials); |
| |
| if (connection->stream != NULL) |
| { |
| g_object_unref (connection->stream); |
| connection->stream = NULL; |
| } |
| |
| g_free (connection->address); |
| |
| g_free (connection->guid); |
| g_free (connection->bus_unique_name); |
| |
| if (connection->initialization_error != NULL) |
| g_error_free (connection->initialization_error); |
| |
| g_hash_table_unref (connection->map_method_serial_to_task); |
| |
| g_hash_table_unref (connection->map_rule_to_signal_data); |
| g_hash_table_unref (connection->map_id_to_signal_data); |
| g_hash_table_unref (connection->map_sender_unique_name_to_signal_data_array); |
| |
| g_hash_table_unref (connection->map_id_to_ei); |
| g_hash_table_unref (connection->map_object_path_to_eo); |
| g_hash_table_unref (connection->map_id_to_es); |
| g_hash_table_unref (connection->map_object_path_to_es); |
| |
| g_hash_table_unref (connection->map_thread_to_last_serial); |
| |
| g_main_context_unref (connection->main_context_at_construction); |
| |
| g_free (connection->machine_id); |
| |
| g_mutex_clear (&connection->init_lock); |
| g_mutex_clear (&connection->lock); |
| |
| G_OBJECT_CLASS (g_dbus_connection_parent_class)->finalize (object); |
| } |
| |
| /* called in any user thread, with the connection's lock not held */ |
| static void |
| g_dbus_connection_get_property (GObject *object, |
| guint prop_id, |
| GValue *value, |
| GParamSpec *pspec) |
| { |
| GDBusConnection *connection = G_DBUS_CONNECTION (object); |
| |
| switch (prop_id) |
| { |
| case PROP_STREAM: |
| g_value_set_object (value, g_dbus_connection_get_stream (connection)); |
| break; |
| |
| case PROP_GUID: |
| g_value_set_string (value, g_dbus_connection_get_guid (connection)); |
| break; |
| |
| case PROP_UNIQUE_NAME: |
| g_value_set_string (value, g_dbus_connection_get_unique_name (connection)); |
| break; |
| |
| case PROP_CLOSED: |
| g_value_set_boolean (value, g_dbus_connection_is_closed (connection)); |
| break; |
| |
| case PROP_EXIT_ON_CLOSE: |
| g_value_set_boolean (value, g_dbus_connection_get_exit_on_close (connection)); |
| break; |
| |
| case PROP_CAPABILITY_FLAGS: |
| g_value_set_flags (value, g_dbus_connection_get_capabilities (connection)); |
| break; |
| |
| case PROP_FLAGS: |
| g_value_set_flags (value, g_dbus_connection_get_flags (connection)); |
| break; |
| |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| } |
| |
| /* called in any user thread, with the connection's lock not held */ |
| static void |
| g_dbus_connection_set_property (GObject *object, |
| guint prop_id, |
| const GValue *value, |
| GParamSpec *pspec) |
| { |
| GDBusConnection *connection = G_DBUS_CONNECTION (object); |
| |
| switch (prop_id) |
| { |
| case PROP_STREAM: |
| connection->stream = g_value_dup_object (value); |
| break; |
| |
| case PROP_GUID: |
| connection->guid = g_value_dup_string (value); |
| break; |
| |
| case PROP_ADDRESS: |
| connection->address = g_value_dup_string (value); |
| break; |
| |
| case PROP_FLAGS: |
| connection->flags = g_value_get_flags (value); |
| break; |
| |
| case PROP_EXIT_ON_CLOSE: |
| g_dbus_connection_set_exit_on_close (connection, g_value_get_boolean (value)); |
| break; |
| |
| case PROP_AUTHENTICATION_OBSERVER: |
| connection->authentication_observer = g_value_dup_object (value); |
| break; |
| |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| } |
| |
| /* Base-class implementation of GDBusConnection::closed. |
| * |
| * Called in a user thread, by the main context that was thread-default when |
| * the object was constructed. |
| */ |
| static void |
| g_dbus_connection_real_closed (GDBusConnection *connection, |
| gboolean remote_peer_vanished, |
| GError *error) |
| { |
| gint flags = g_atomic_int_get (&connection->atomic_flags); |
| |
| /* Because atomic int access is a memory barrier, we can safely read |
| * initialization_error without a lock, as long as we do it afterwards. |
| */ |
| if (remote_peer_vanished && |
| (flags & FLAG_EXIT_ON_CLOSE) != 0 && |
| (flags & FLAG_INITIALIZED) != 0 && |
| connection->initialization_error == NULL) |
| { |
| raise (SIGTERM); |
| } |
| } |
| |
| static void |
| g_dbus_connection_class_init (GDBusConnectionClass *klass) |
| { |
| GObjectClass *gobject_class; |
| |
| gobject_class = G_OBJECT_CLASS (klass); |
| |
| gobject_class->finalize = g_dbus_connection_finalize; |
| gobject_class->dispose = g_dbus_connection_dispose; |
| gobject_class->set_property = g_dbus_connection_set_property; |
| gobject_class->get_property = g_dbus_connection_get_property; |
| |
| klass->closed = g_dbus_connection_real_closed; |
| |
| /** |
| * GDBusConnection:stream: |
| * |
| * The underlying #GIOStream used for I/O. |
| * |
| * If this is passed on construction and is a #GSocketConnection, |
| * then the corresponding #GSocket will be put into non-blocking mode. |
| * |
| * While the #GDBusConnection is active, it will interact with this |
| * stream from a worker thread, so it is not safe to interact with |
| * the stream directly. |
| * |
| * Since: 2.26 |
| */ |
| g_object_class_install_property (gobject_class, |
| PROP_STREAM, |
| g_param_spec_object ("stream", NULL, NULL, |
| G_TYPE_IO_STREAM, |
| G_PARAM_READABLE | |
| G_PARAM_WRITABLE | |
| G_PARAM_CONSTRUCT_ONLY | |
| G_PARAM_STATIC_NAME | |
| G_PARAM_STATIC_BLURB | |
| G_PARAM_STATIC_NICK)); |
| |
| /** |
| * GDBusConnection:address: |
| * |
| * A D-Bus address specifying potential endpoints that can be used |
| * when establishing the connection. |
| * |
| * Since: 2.26 |
| */ |
| g_object_class_install_property (gobject_class, |
| PROP_ADDRESS, |
| g_param_spec_string ("address", NULL, NULL, |
| NULL, |
| G_PARAM_WRITABLE | |
| G_PARAM_CONSTRUCT_ONLY | |
| G_PARAM_STATIC_NAME | |
| G_PARAM_STATIC_BLURB | |
| G_PARAM_STATIC_NICK)); |
| |
| /** |
| * GDBusConnection:flags: |
| * |
| * Flags from the #GDBusConnectionFlags enumeration. |
| * |
| * Since: 2.26 |
| */ |
| g_object_class_install_property (gobject_class, |
| PROP_FLAGS, |
| g_param_spec_flags ("flags", NULL, NULL, |
| G_TYPE_DBUS_CONNECTION_FLAGS, |
| G_DBUS_CONNECTION_FLAGS_NONE, |
| G_PARAM_READABLE | |
| G_PARAM_WRITABLE | |
| G_PARAM_CONSTRUCT_ONLY | |
| G_PARAM_STATIC_NAME | |
| G_PARAM_STATIC_BLURB | |
| G_PARAM_STATIC_NICK)); |
| |
| /** |
| * GDBusConnection:guid: |
| * |
| * The GUID of the peer performing the role of server when |
| * authenticating. |
| * |
| * If you are constructing a #GDBusConnection and pass |
| * %G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER in the |
| * #GDBusConnection:flags property then you **must** also set this |
| * property to a valid guid. |
| * |
| * If you are constructing a #GDBusConnection and pass |
| * %G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT in the |
| * #GDBusConnection:flags property you will be able to read the GUID |
| * of the other peer here after the connection has been successfully |
| * initialized. |
| * |
| * Note that the |
| * [D-Bus specification](https://dbus.freedesktop.org/doc/dbus-specification.html#addresses) |
| * uses the term ‘UUID’ to refer to this, whereas GLib consistently uses the |
| * term ‘GUID’ for historical reasons. |
| * |
| * Despite its name, the format of #GDBusConnection:guid does not follow |
| * [RFC 4122](https://datatracker.ietf.org/doc/html/rfc4122) or the Microsoft |
| * GUID format. |
| * |
| * Since: 2.26 |
| */ |
| g_object_class_install_property (gobject_class, |
| PROP_GUID, |
| g_param_spec_string ("guid", NULL, NULL, |
| NULL, |
| G_PARAM_READABLE | |
| G_PARAM_WRITABLE | |
| G_PARAM_CONSTRUCT_ONLY | |
| G_PARAM_STATIC_NAME | |
| G_PARAM_STATIC_BLURB | |
| G_PARAM_STATIC_NICK)); |
| |
| /** |
| * GDBusConnection:unique-name: |
| * |
| * The unique name as assigned by the message bus or %NULL if the |
| * connection is not open or not a message bus connection. |
| * |
| * Since: 2.26 |
| */ |
| g_object_class_install_property (gobject_class, |
| PROP_UNIQUE_NAME, |
| g_param_spec_string ("unique-name", NULL, NULL, |
| NULL, |
| G_PARAM_READABLE | |
| G_PARAM_STATIC_NAME | |
| G_PARAM_STATIC_BLURB | |
| G_PARAM_STATIC_NICK)); |
| |
| /** |
| * GDBusConnection:closed: |
| * |
| * A boolean specifying whether the connection has been closed. |
| * |
| * Since: 2.26 |
| */ |
| g_object_class_install_property (gobject_class, |
| PROP_CLOSED, |
| g_param_spec_boolean ("closed", NULL, NULL, |
| FALSE, |
| G_PARAM_READABLE | |
| G_PARAM_STATIC_NAME | |
| G_PARAM_STATIC_BLURB | |
| G_PARAM_STATIC_NICK)); |
| |
| /** |
| * GDBusConnection:exit-on-close: |
| * |
| * A boolean specifying whether the process will be terminated (by |
| * calling `raise(SIGTERM)`) if the connection is closed by the |
| * remote peer. |
| * |
| * Note that #GDBusConnection objects returned by g_bus_get_finish() |
| * and g_bus_get_sync() will (usually) have this property set to %TRUE. |
| * |
| * Since: 2.26 |
| */ |
| g_object_class_install_property (gobject_class, |
| PROP_EXIT_ON_CLOSE, |
| g_param_spec_boolean ("exit-on-close", NULL, NULL, |
| FALSE, |
| G_PARAM_READABLE | |
| G_PARAM_WRITABLE | |
| G_PARAM_STATIC_NAME | |
| G_PARAM_STATIC_BLURB | |
| G_PARAM_STATIC_NICK)); |
| |
| /** |
| * GDBusConnection:capabilities: |
| * |
| * Flags from the #GDBusCapabilityFlags enumeration |
| * representing connection features negotiated with the other peer. |
| * |
| * Since: 2.26 |
| */ |
| g_object_class_install_property (gobject_class, |
| PROP_CAPABILITY_FLAGS, |
| g_param_spec_flags ("capabilities", NULL, NULL, |
| G_TYPE_DBUS_CAPABILITY_FLAGS, |
| G_DBUS_CAPABILITY_FLAGS_NONE, |
| G_PARAM_READABLE | |
| G_PARAM_STATIC_NAME | |
| G_PARAM_STATIC_BLURB | |
| G_PARAM_STATIC_NICK)); |
| |
| /** |
| * GDBusConnection:authentication-observer: |
| * |
| * A #GDBusAuthObserver object to assist in the authentication process or %NULL. |
| * |
| * Since: 2.26 |
| */ |
| g_object_class_install_property (gobject_class, |
| PROP_AUTHENTICATION_OBSERVER, |
| g_param_spec_object ("authentication-observer", NULL, NULL, |
| G_TYPE_DBUS_AUTH_OBSERVER, |
| G_PARAM_WRITABLE | |
| G_PARAM_CONSTRUCT_ONLY | |
| G_PARAM_STATIC_NAME | |
| G_PARAM_STATIC_BLURB | |
| G_PARAM_STATIC_NICK)); |
| |
| /** |
| * GDBusConnection::closed: |
| * @connection: the #GDBusConnection emitting the signal |
| * @remote_peer_vanished: %TRUE if @connection is closed because the |
| * remote peer closed its end of the connection |
| * @error: (nullable): a #GError with more details about the event or %NULL |
| * |
| * Emitted when the connection is closed. |
| * |
| * The cause of this event can be |
| * |
| * - If g_dbus_connection_close() is called. In this case |
| * @remote_peer_vanished is set to %FALSE and @error is %NULL. |
| * |
| * - If the remote peer closes the connection. In this case |
| * @remote_peer_vanished is set to %TRUE and @error is set. |
| * |
| * - If the remote peer sends invalid or malformed data. In this |
| * case @remote_peer_vanished is set to %FALSE and @error is set. |
| * |
| * Upon receiving this signal, you should give up your reference to |
| * @connection. You are guaranteed that this signal is emitted only |
| * once. |
| * |
| * Since: 2.26 |
| */ |
| signals[CLOSED_SIGNAL] = g_signal_new (I_("closed"), |
| G_TYPE_DBUS_CONNECTION, |
| G_SIGNAL_RUN_LAST, |
| G_STRUCT_OFFSET (GDBusConnectionClass, closed), |
| NULL, |
| NULL, |
| _g_cclosure_marshal_VOID__BOOLEAN_BOXED, |
| G_TYPE_NONE, |
| 2, |
| G_TYPE_BOOLEAN, |
| G_TYPE_ERROR); |
| g_signal_set_va_marshaller (signals[CLOSED_SIGNAL], |
| G_TYPE_FROM_CLASS (klass), |
| _g_cclosure_marshal_VOID__BOOLEAN_BOXEDv); |
| } |
| |
| static void |
| g_dbus_connection_init (GDBusConnection *connection) |
| { |
| g_mutex_init (&connection->lock); |
| g_mutex_init (&connection->init_lock); |
| |
| connection->map_method_serial_to_task = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_object_unref); |
| |
| connection->map_rule_to_signal_data = g_hash_table_new (g_str_hash, |
| g_str_equal); |
| connection->map_id_to_signal_data = g_hash_table_new (g_direct_hash, |
| g_direct_equal); |
| connection->map_sender_unique_name_to_signal_data_array = g_hash_table_new_full (g_str_hash, |
| g_str_equal, |
| g_free, |
| (GDestroyNotify) g_ptr_array_unref); |
| |
| connection->map_object_path_to_eo = g_hash_table_new_full (g_str_hash, |
| g_str_equal, |
| NULL, |
| (GDestroyNotify) exported_object_free); |
| |
| connection->map_id_to_ei = g_hash_table_new (g_direct_hash, |
| g_direct_equal); |
| |
| connection->map_object_path_to_es = g_hash_table_new_full (g_str_hash, |
| g_str_equal, |
| NULL, |
| (GDestroyNotify) exported_subtree_unref); |
| |
| connection->map_id_to_es = g_hash_table_new (g_direct_hash, |
| g_direct_equal); |
| |
| connection->map_thread_to_last_serial = g_hash_table_new (g_direct_hash, |
| g_direct_equal); |
| |
| connection->main_context_at_construction = g_main_context_ref_thread_default (); |
| |
| connection->filters = g_ptr_array_new (); |
| } |
| |
| /** |
| * g_dbus_connection_get_stream: |
| * @connection: a #GDBusConnection |
| * |
| * Gets the underlying stream used for IO. |
| * |
| * While the #GDBusConnection is active, it will interact with this |
| * stream from a worker thread, so it is not safe to interact with |
| * the stream directly. |
| * |
| * Returns: (transfer none) (not nullable): the stream used for IO |
| * |
| * Since: 2.26 |
| */ |
| GIOStream * |
| g_dbus_connection_get_stream (GDBusConnection *connection) |
| { |
| g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL); |
| |
| /* do not use g_return_val_if_fail(), we want the memory barrier */ |
| if (!check_initialized (connection)) |
| return NULL; |
| |
| return connection->stream; |
| } |
| |
| /** |
| * g_dbus_connection_start_message_processing: |
| * @connection: a #GDBusConnection |
| * |
| * If @connection was created with |
| * %G_DBUS_CONNECTION_FLAGS_DELAY_MESSAGE_PROCESSING, this method |
| * starts processing messages. Does nothing on if @connection wasn't |
| * created with this flag or if the method has already been called. |
| * |
| * Since: 2.26 |
| */ |
| void |
| g_dbus_connection_start_message_processing (GDBusConnection *connection) |
| { |
| g_return_if_fail (G_IS_DBUS_CONNECTION (connection)); |
| |
| /* do not use g_return_val_if_fail(), we want the memory barrier */ |
| if (!check_initialized (connection)) |
| return; |
| |
| g_assert (connection->worker != NULL); |
| _g_dbus_worker_unfreeze (connection->worker); |
| } |
| |
| /** |
| * g_dbus_connection_is_closed: |
| * @connection: a #GDBusConnection |
| * |
| * Gets whether @connection is closed. |
| * |
| * Returns: %TRUE if the connection is closed, %FALSE otherwise |
| * |
| * Since: 2.26 |
| */ |
| gboolean |
| g_dbus_connection_is_closed (GDBusConnection *connection) |
| { |
| gint flags; |
| |
| g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE); |
| |
| flags = g_atomic_int_get (&connection->atomic_flags); |
| |
| return (flags & FLAG_CLOSED) ? TRUE : FALSE; |
| } |
| |
| /** |
| * g_dbus_connection_get_capabilities: |
| * @connection: a #GDBusConnection |
| * |
| * Gets the capabilities negotiated with the remote peer |
| * |
| * Returns: zero or more flags from the #GDBusCapabilityFlags enumeration |
| * |
| * Since: 2.26 |
| */ |
| GDBusCapabilityFlags |
| g_dbus_connection_get_capabilities (GDBusConnection *connection) |
| { |
| g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), G_DBUS_CAPABILITY_FLAGS_NONE); |
| |
| /* do not use g_return_val_if_fail(), we want the memory barrier */ |
| if (!check_initialized (connection)) |
| return G_DBUS_CAPABILITY_FLAGS_NONE; |
| |
| return connection->capabilities; |
| } |
| |
| /** |
| * g_dbus_connection_get_flags: |
| * @connection: a #GDBusConnection |
| * |
| * Gets the flags used to construct this connection |
| * |
| * Returns: zero or more flags from the #GDBusConnectionFlags enumeration |
| * |
| * Since: 2.60 |
| */ |
| GDBusConnectionFlags |
| g_dbus_connection_get_flags (GDBusConnection *connection) |
| { |
| g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), G_DBUS_CONNECTION_FLAGS_NONE); |
| |
| /* do not use g_return_val_if_fail(), we want the memory barrier */ |
| if (!check_initialized (connection)) |
| return G_DBUS_CONNECTION_FLAGS_NONE; |
| |
| return connection->flags; |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| /* Called in a temporary thread without holding locks. */ |
| static void |
| flush_in_thread_func (GTask *task, |
| gpointer source_object, |
| gpointer task_data, |
| GCancellable *cancellable) |
| { |
| GError *error = NULL; |
| |
| if (g_dbus_connection_flush_sync (source_object, |
| cancellable, |
| &error)) |
| g_task_return_boolean (task, TRUE); |
| else |
| g_task_return_error (task, error); |
| } |
| |
| /** |
| * g_dbus_connection_flush: |
| * @connection: a #GDBusConnection |
| * @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 |
| * @user_data: The data to pass to @callback |
| * |
| * Asynchronously flushes @connection, that is, writes all queued |
| * outgoing message to the transport and then flushes the transport |
| * (using g_output_stream_flush_async()). This is useful in programs |
| * that wants to emit a D-Bus signal and then exit immediately. Without |
| * flushing the connection, there is no guaranteed that the message has |
| * been sent to the networking buffers in the OS kernel. |
| * |
| * This is an asynchronous method. When the operation is finished, |
| * @callback will be invoked in the |
| * [thread-default main context][g-main-context-push-thread-default] |
| * of the thread you are calling this method from. You can |
| * then call g_dbus_connection_flush_finish() to get the result of the |
| * operation. See g_dbus_connection_flush_sync() for the synchronous |
| * version. |
| * |
| * Since: 2.26 |
| */ |
| void |
| g_dbus_connection_flush (GDBusConnection *connection, |
| GCancellable *cancellable, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GTask *task; |
| |
| g_return_if_fail (G_IS_DBUS_CONNECTION (connection)); |
| |
| task = g_task_new (connection, cancellable, callback, user_data); |
| g_task_set_source_tag (task, g_dbus_connection_flush); |
| g_task_run_in_thread (task, flush_in_thread_func); |
| g_object_unref (task); |
| } |
| |
| /** |
| * g_dbus_connection_flush_finish: |
| * @connection: a #GDBusConnection |
| * @res: a #GAsyncResult obtained from the #GAsyncReadyCallback passed |
| * to g_dbus_connection_flush() |
| * @error: return location for error or %NULL |
| * |
| * Finishes an operation started with g_dbus_connection_flush(). |
| * |
| * Returns: %TRUE if the operation succeeded, %FALSE if @error is set |
| * |
| * Since: 2.26 |
| */ |
| gboolean |
| g_dbus_connection_flush_finish (GDBusConnection *connection, |
| GAsyncResult *res, |
| GError **error) |
| { |
| g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE); |
| g_return_val_if_fail (g_task_is_valid (res, connection), FALSE); |
| g_return_val_if_fail (error == NULL || *error == NULL, FALSE); |
| |
| return g_task_propagate_boolean (G_TASK (res), error); |
| } |
| |
| /** |
| * g_dbus_connection_flush_sync: |
| * @connection: a #GDBusConnection |
| * @cancellable: (nullable): a #GCancellable or %NULL |
| * @error: return location for error or %NULL |
| * |
| * Synchronously flushes @connection. The calling thread is blocked |
| * until this is done. See g_dbus_connection_flush() for the |
| * asynchronous version of this method and more details about what it |
| * does. |
| * |
| * Returns: %TRUE if the operation succeeded, %FALSE if @error is set |
| * |
| * Since: 2.26 |
| */ |
| gboolean |
| g_dbus_connection_flush_sync (GDBusConnection *connection, |
| GCancellable *cancellable, |
| GError **error) |
| { |
| gboolean ret; |
| |
| g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE); |
| g_return_val_if_fail (error == NULL || *error == NULL, FALSE); |
| |
| ret = FALSE; |
| |
| /* This is only a best-effort attempt to see whether the connection is |
| * closed, so it doesn't need the lock. If the connection closes just |
| * after this check, but before scheduling the flush operation, the |
| * result will be more or less the same as if the connection closed while |
| * the flush operation was pending - it'll fail with either CLOSED or |
| * CANCELLED. |
| */ |
| if (!check_unclosed (connection, 0, error)) |
| goto out; |
| |
| g_assert (connection->worker != NULL); |
| |
| ret = _g_dbus_worker_flush_sync (connection->worker, |
| cancellable, |
| error); |
| |
| out: |
| return ret; |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| typedef struct |
| { |
| GDBusConnection *connection; |
| GError *error; |
| gboolean remote_peer_vanished; |
| } EmitClosedData; |
| |
| static void |
| emit_closed_data_free (EmitClosedData *data) |
| { |
| g_object_unref (data->connection); |
| if (data->error != NULL) |
| g_error_free (data->error); |
| g_free (data); |
| } |
| |
| /* Called in a user thread that has acquired the main context that was |
| * thread-default when the object was constructed |
| */ |
| static gboolean |
| emit_closed_in_idle (gpointer user_data) |
| { |
| EmitClosedData *data = user_data; |
| gboolean result; |
| |
| g_object_notify (G_OBJECT (data->connection), "closed"); |
| g_signal_emit (data->connection, |
| signals[CLOSED_SIGNAL], |
| 0, |
| data->remote_peer_vanished, |
| data->error, |
| &result); |
| return FALSE; |
| } |
| |
| /* Can be called from any thread, must hold lock. |
| * FLAG_CLOSED must already have been set. |
| */ |
| static void |
| schedule_closed_unlocked (GDBusConnection *connection, |
| gboolean remote_peer_vanished, |
| GError *error) |
| { |
| GSource *idle_source; |
| EmitClosedData *data; |
| |
| CONNECTION_ENSURE_LOCK (connection); |
| |
| data = g_new0 (EmitClosedData, 1); |
| data->connection = g_object_ref (connection); |
| data->remote_peer_vanished = remote_peer_vanished; |
| data->error = error != NULL ? g_error_copy (error) : NULL; |
| |
| idle_source = g_idle_source_new (); |
| g_source_set_priority (idle_source, G_PRIORITY_DEFAULT); |
| g_source_set_callback (idle_source, |
| emit_closed_in_idle, |
| data, |
| (GDestroyNotify) emit_closed_data_free); |
| g_source_set_static_name (idle_source, "[gio] emit_closed_in_idle"); |
| g_source_attach (idle_source, connection->main_context_at_construction); |
| g_source_unref (idle_source); |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| /** |
| * g_dbus_connection_close: |
| * @connection: a #GDBusConnection |
| * @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 |
| * @user_data: The data to pass to @callback |
| * |
| * Closes @connection. Note that this never causes the process to |
| * exit (this might only happen if the other end of a shared message |
| * bus connection disconnects, see #GDBusConnection:exit-on-close). |
| * |
| * Once the connection is closed, operations such as sending a message |
| * will return with the error %G_IO_ERROR_CLOSED. Closing a connection |
| * will not automatically flush the connection so queued messages may |
| * be lost. Use g_dbus_connection_flush() if you need such guarantees. |
| * |
| * If @connection is already closed, this method fails with |
| * %G_IO_ERROR_CLOSED. |
| * |
| * When @connection has been closed, the #GDBusConnection::closed |
| * signal is emitted in the |
| * [thread-default main context][g-main-context-push-thread-default] |
| * of the thread that @connection was constructed in. |
| * |
| * This is an asynchronous method. When the operation is finished, |
| * @callback will be invoked in the |
| * [thread-default main context][g-main-context-push-thread-default] |
| * of the thread you are calling this method from. You can |
| * then call g_dbus_connection_close_finish() to get the result of the |
| * operation. See g_dbus_connection_close_sync() for the synchronous |
| * version. |
| * |
| * Since: 2.26 |
| */ |
| void |
| g_dbus_connection_close (GDBusConnection *connection, |
| GCancellable *cancellable, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GTask *task; |
| |
| g_return_if_fail (G_IS_DBUS_CONNECTION (connection)); |
| |
| /* do not use g_return_val_if_fail(), we want the memory barrier */ |
| if (!check_initialized (connection)) |
| return; |
| |
| g_assert (connection->worker != NULL); |
| |
| task = g_task_new (connection, cancellable, callback, user_data); |
| g_task_set_source_tag (task, g_dbus_connection_close); |
| _g_dbus_worker_close (connection->worker, task); |
| g_object_unref (task); |
| } |
| |
| /** |
| * g_dbus_connection_close_finish: |
| * @connection: a #GDBusConnection |
| * @res: a #GAsyncResult obtained from the #GAsyncReadyCallback passed |
| * to g_dbus_connection_close() |
| * @error: return location for error or %NULL |
| * |
| * Finishes an operation started with g_dbus_connection_close(). |
| * |
| * Returns: %TRUE if the operation succeeded, %FALSE if @error is set |
| * |
| * Since: 2.26 |
| */ |
| gboolean |
| g_dbus_connection_close_finish (GDBusConnection *connection, |
| GAsyncResult *res, |
| GError **error) |
| { |
| g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE); |
| g_return_val_if_fail (g_task_is_valid (res, connection), FALSE); |
| g_return_val_if_fail (error == NULL || *error == NULL, FALSE); |
| |
| return g_task_propagate_boolean (G_TASK (res), error); |
| } |
| |
| typedef struct { |
| GMainLoop *loop; |
| GAsyncResult *result; |
| } SyncCloseData; |
| |
| /* Can be called by any thread, without the connection lock */ |
| static void |
| sync_close_cb (GObject *source_object, |
| GAsyncResult *res, |
| gpointer user_data) |
| { |
| SyncCloseData *data = user_data; |
| |
| data->result = g_object_ref (res); |
| g_main_loop_quit (data->loop); |
| } |
| |
| /** |
| * g_dbus_connection_close_sync: |
| * @connection: a #GDBusConnection |
| * @cancellable: (nullable): a #GCancellable or %NULL |
| * @error: return location for error or %NULL |
| * |
| * Synchronously closes @connection. The calling thread is blocked |
| * until this is done. See g_dbus_connection_close() for the |
| * asynchronous version of this method and more details about what it |
| * does. |
| * |
| * Returns: %TRUE if the operation succeeded, %FALSE if @error is set |
| * |
| * Since: 2.26 |
| */ |
| gboolean |
| g_dbus_connection_close_sync (GDBusConnection *connection, |
| GCancellable *cancellable, |
| GError **error) |
| { |
| gboolean ret; |
| |
| g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE); |
| g_return_val_if_fail (error == NULL || *error == NULL, FALSE); |
| |
| ret = FALSE; |
| |
| if (check_unclosed (connection, 0, error)) |
| { |
| GMainContext *context; |
| SyncCloseData data; |
| |
| context = g_main_context_new (); |
| g_main_context_push_thread_default (context); |
| data.loop = g_main_loop_new (context, TRUE); |
| data.result = NULL; |
| |
| g_dbus_connection_close (connection, cancellable, sync_close_cb, &data); |
| g_main_loop_run (data.loop); |
| ret = g_dbus_connection_close_finish (connection, data.result, error); |
| |
| g_object_unref (data.result); |
| g_main_loop_unref (data.loop); |
| g_main_context_pop_thread_default (context); |
| g_main_context_unref (context); |
| } |
| |
| return ret; |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| /** |
| * g_dbus_connection_get_last_serial: |
| * @connection: a #GDBusConnection |
| * |
| * Retrieves the last serial number assigned to a #GDBusMessage on |
| * the current thread. This includes messages sent via both low-level |
| * API such as g_dbus_connection_send_message() as well as |
| * high-level API such as g_dbus_connection_emit_signal(), |
| * g_dbus_connection_call() or g_dbus_proxy_call(). |
| * |
| * Returns: the last used serial or zero when no message has been sent |
| * within the current thread |
| * |
| * Since: 2.34 |
| */ |
| guint32 |
| g_dbus_connection_get_last_serial (GDBusConnection *connection) |
| { |
| guint32 ret; |
| |
| g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), 0); |
| |
| CONNECTION_LOCK (connection); |
| ret = GPOINTER_TO_UINT (g_hash_table_lookup (connection->map_thread_to_last_serial, |
| g_thread_self ())); |
| CONNECTION_UNLOCK (connection); |
| |
| return ret; |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| /* Can be called by any thread, with the connection lock held */ |
| static gboolean |
| g_dbus_connection_send_message_unlocked (GDBusConnection *connection, |
| GDBusMessage *message, |
| GDBusSendMessageFlags flags, |
| guint32 *out_serial, |
| GError **error) |
| { |
| guchar *blob; |
| gsize blob_size; |
| guint32 serial_to_use; |
| |
| CONNECTION_ENSURE_LOCK (connection); |
| |
| g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE); |
| g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), FALSE); |
| |
| /* TODO: check all necessary headers are present */ |
| |
| if (out_serial != NULL) |
| *out_serial = 0; |
| |
| /* If we're in initable_init(), don't check for being initialized, to avoid |
| * chicken-and-egg problems. initable_init() is responsible for setting up |
| * our prerequisites (mainly connection->worker), and only calling us |
| * from its own thread (so no memory barrier is needed). |
| */ |
| if (!check_unclosed (connection, |
| (flags & SEND_MESSAGE_FLAGS_INITIALIZING) ? MAY_BE_UNINITIALIZED : 0, |
| error)) |
| return FALSE; |
| |
| blob = g_dbus_message_to_blob (message, |
| &blob_size, |
| connection->capabilities, |
| error); |
| if (blob == NULL) |
| return FALSE; |
| |
| if (flags & G_DBUS_SEND_MESSAGE_FLAGS_PRESERVE_SERIAL) |
| serial_to_use = g_dbus_message_get_serial (message); |
| else |
| serial_to_use = ++connection->last_serial; /* TODO: handle overflow */ |
| |
| switch (blob[0]) |
| { |
| case 'l': |
| ((guint32 *) blob)[2] = GUINT32_TO_LE (serial_to_use); |
| break; |
| case 'B': |
| ((guint32 *) blob)[2] = GUINT32_TO_BE (serial_to_use); |
| break; |
| default: |
| g_assert_not_reached (); |
| break; |
| } |
| |
| #if 0 |
| g_printerr ("Writing message of %" G_GSIZE_FORMAT " bytes (serial %d) on %p:\n", |
| blob_size, serial_to_use, connection); |
| g_printerr ("----\n"); |
| hexdump (blob, blob_size); |
| g_printerr ("----\n"); |
| #endif |
| |
| /* TODO: use connection->auth to encode the blob */ |
| |
| if (out_serial != NULL) |
| *out_serial = serial_to_use; |
| |
| /* store used serial for the current thread */ |
| /* TODO: watch the thread disposal and remove associated record |
| * from hashtable |
| * - see https://bugzilla.gnome.org/show_bug.cgi?id=676825#c7 |
| */ |
| g_hash_table_replace (connection->map_thread_to_last_serial, |
| g_thread_self (), |
| GUINT_TO_POINTER (serial_to_use)); |
| |
| if (!(flags & G_DBUS_SEND_MESSAGE_FLAGS_PRESERVE_SERIAL)) |
| g_dbus_message_set_serial (message, serial_to_use); |
| |
| g_dbus_message_lock (message); |
| |
| _g_dbus_worker_send_message (connection->worker, |
| message, |
| (gchar*) blob, /* transfer ownership */ |
| blob_size); |
| |
| return TRUE; |
| } |
| |
| /** |
| * g_dbus_connection_send_message: |
| * @connection: a #GDBusConnection |
| * @message: a #GDBusMessage |
| * @flags: flags affecting how the message is sent |
| * @out_serial: (out) (optional): return location for serial number assigned |
| * to @message when sending it or %NULL |
| * @error: Return location for error or %NULL |
| * |
| * Asynchronously sends @message to the peer represented by @connection. |
| * |
| * Unless @flags contain the |
| * %G_DBUS_SEND_MESSAGE_FLAGS_PRESERVE_SERIAL flag, the serial number |
| * will be assigned by @connection and set on @message via |
| * g_dbus_message_set_serial(). If @out_serial is not %NULL, then the |
| * serial number used will be written to this location prior to |
| * submitting the message to the underlying transport. While it has a `volatile` |
| * qualifier, this is a historical artifact and the argument passed to it should |
| * not be `volatile`. |
| * |
| * If @connection is closed then the operation will fail with |
| * %G_IO_ERROR_CLOSED. If @message is not well-formed, |
| * the operation fails with %G_IO_ERROR_INVALID_ARGUMENT. |
| * |
| * See this [server][gdbus-server] and [client][gdbus-unix-fd-client] |
| * for an example of how to use this low-level API to send and receive |
| * UNIX file descriptors. |
| * |
| * Note that @message must be unlocked, unless @flags contain the |
| * %G_DBUS_SEND_MESSAGE_FLAGS_PRESERVE_SERIAL flag. |
| * |
| * Returns: %TRUE if the message was well-formed and queued for |
| * transmission, %FALSE if @error is set |
| * |
| * Since: 2.26 |
| */ |
| gboolean |
| g_dbus_connection_send_message (GDBusConnection *connection, |
| GDBusMessage *message, |
| GDBusSendMessageFlags flags, |
| volatile guint32 *out_serial, |
| GError **error) |
| { |
| gboolean ret; |
| |
| g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE); |
| g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), FALSE); |
| g_return_val_if_fail ((flags & G_DBUS_SEND_MESSAGE_FLAGS_PRESERVE_SERIAL) || !g_dbus_message_get_locked (message), FALSE); |
| g_return_val_if_fail (error == NULL || *error == NULL, FALSE); |
| |
| CONNECTION_LOCK (connection); |
| ret = g_dbus_connection_send_message_unlocked (connection, message, flags, (guint32 *) out_serial, error); |
| CONNECTION_UNLOCK (connection); |
| return ret; |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| typedef struct |
| { |
| guint32 serial; |
| |
| gulong cancellable_handler_id; |
| GSource *cancelled_idle_source; /* (owned) (nullable) */ |
| |
| GSource *timeout_source; /* (owned) (nullable) */ |
| |
| gboolean delivered; |
| } SendMessageData; |
| |
| /* Can be called from any thread with or without lock held */ |
| static void |
| send_message_data_free (SendMessageData *data) |
| { |
| /* These should already have been cleared by send_message_with_reply_cleanup(). */ |
| g_assert (data->timeout_source == NULL); |
| g_assert (data->cancellable_handler_id == 0); |
| |
| g_slice_free (SendMessageData, data); |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| /* can be called from any thread with lock held; @task is (transfer none) */ |
| static void |
| send_message_with_reply_cleanup (GTask *task, gboolean remove) |
| { |
| GDBusConnection *connection = g_task_get_source_object (task); |
| SendMessageData *data = g_task_get_task_data (task); |
| |
| CONNECTION_ENSURE_LOCK (connection); |
| |
| g_assert (!data->delivered); |
| |
| data->delivered = TRUE; |
| |
| if (data->timeout_source != NULL) |
| { |
| g_source_destroy (data->timeout_source); |
| g_clear_pointer (&data->timeout_source, g_source_unref); |
| } |
| if (data->cancellable_handler_id > 0) |
| { |
| g_cancellable_disconnect (g_task_get_cancellable (task), data->cancellable_handler_id); |
| data->cancellable_handler_id = 0; |
| } |
| if (data->cancelled_idle_source != NULL) |
| { |
| g_source_destroy (data->cancelled_idle_source); |
| g_clear_pointer (&data->cancelled_idle_source, g_source_unref); |
| } |
| |
| if (remove) |
| { |
| gboolean removed = g_hash_table_remove (connection->map_method_serial_to_task, |
| GUINT_TO_POINTER (data->serial)); |
| g_warn_if_fail (removed); |
| } |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| /* Called from GDBus worker thread with lock held; @task is (transfer none). */ |
| static void |
| send_message_data_deliver_reply_unlocked (GTask *task, |
| GDBusMessage *reply) |
| { |
| SendMessageData *data = g_task_get_task_data (task); |
| |
| if (data->delivered) |
| goto out; |
| |
| g_task_return_pointer (task, g_object_ref (reply), g_object_unref); |
| |
| send_message_with_reply_cleanup (task, TRUE); |
| |
| out: |
| ; |
| } |
| |
| /* Called from a user thread, lock is not held; @task is (transfer none) */ |
| static void |
| send_message_data_deliver_error (GTask *task, |
| GQuark domain, |
| gint code, |
| const char *message) |
| { |
| GDBusConnection *connection = g_task_get_source_object (task); |
| SendMessageData *data = g_task_get_task_data (task); |
| |
| CONNECTION_LOCK (connection); |
| if (data->delivered) |
| { |
| CONNECTION_UNLOCK (connection); |
| return; |
| } |
| |
| /* Hold a ref on @task as send_message_with_reply_cleanup() will remove it |
| * from the task map and could end up dropping the last reference */ |
| g_object_ref (task); |
| |
| send_message_with_reply_cleanup (task, TRUE); |
| CONNECTION_UNLOCK (connection); |
| |
| g_task_return_new_error_literal (task, domain, code, message); |
| g_object_unref (task); |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| /* Called from a user thread, lock is not held; @task is (transfer none) */ |
| static gboolean |
| send_message_with_reply_cancelled_idle_cb (gpointer user_data) |
| { |
| GTask *task = user_data; |
| |
| send_message_data_deliver_error (task, G_IO_ERROR, G_IO_ERROR_CANCELLED, |
| _("Operation was cancelled")); |
| return G_SOURCE_REMOVE; |
| } |
| |
| /* Can be called from any thread with or without lock held */ |
| static void |
| send_message_with_reply_cancelled_cb (GCancellable *cancellable, |
| gpointer user_data) |
| { |
| GTask *task = user_data; |
| SendMessageData *data = g_task_get_task_data (task); |
| |
| /* postpone cancellation to idle handler since we may be called directly |
| * via g_cancellable_connect() (e.g. holding lock) |
| */ |
| if (data->cancelled_idle_source != NULL) |
| return; |
| |
| data->cancelled_idle_source = g_idle_source_new (); |
| g_source_set_static_name (data->cancelled_idle_source, "[gio] send_message_with_reply_cancelled_idle_cb"); |
| g_task_attach_source (task, data->cancelled_idle_source, send_message_with_reply_cancelled_idle_cb); |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| /* Called from a user thread, lock is not held; @task is (transfer none) */ |
| static gboolean |
| send_message_with_reply_timeout_cb (gpointer user_data) |
| { |
| GTask *task = user_data; |
| |
| send_message_data_deliver_error (task, G_IO_ERROR, G_IO_ERROR_TIMED_OUT, |
| _("Timeout was reached")); |
| return G_SOURCE_REMOVE; |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| /* Called from a user thread, connection's lock is held */ |
| static void |
| g_dbus_connection_send_message_with_reply_unlocked (GDBusConnection *connection, |
| GDBusMessage *message, |
| GDBusSendMessageFlags flags, |
| gint timeout_msec, |
| guint32 *out_serial, |
| GCancellable *cancellable, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GTask *task; |
| SendMessageData *data; |
| GError *error = NULL; |
| guint32 serial; |
| |
| if (out_serial == NULL) |
| out_serial = &serial; |
| |
| if (timeout_msec == -1) |
| timeout_msec = 25 * 1000; |
| |
| data = g_slice_new0 (SendMessageData); |
| task = g_task_new (connection, cancellable, callback, user_data); |
| g_task_set_source_tag (task, |
| g_dbus_connection_send_message_with_reply_unlocked); |
| g_task_set_task_data (task, data, (GDestroyNotify) send_message_data_free); |
| |
| if (g_task_return_error_if_cancelled (task)) |
| { |
| g_object_unref (task); |
| return; |
| } |
| |
| if (!g_dbus_connection_send_message_unlocked (connection, message, flags, out_serial, &error)) |
| { |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| data->serial = *out_serial; |
| |
| if (cancellable != NULL) |
| { |
| data->cancellable_handler_id = g_cancellable_connect (cancellable, |
| G_CALLBACK (send_message_with_reply_cancelled_cb), |
| g_object_ref (task), |
| g_object_unref); |
| } |
| |
| if (timeout_msec != G_MAXINT) |
| { |
| data->timeout_source = g_timeout_source_new (timeout_msec); |
| g_source_set_static_name (data->timeout_source, "[gio] send_message_with_reply_unlocked"); |
| g_task_attach_source (task, data->timeout_source, |
| (GSourceFunc) send_message_with_reply_timeout_cb); |
| } |
| |
| g_hash_table_insert (connection->map_method_serial_to_task, |
| GUINT_TO_POINTER (*out_serial), |
| g_steal_pointer (&task)); |
| } |
| |
| /** |
| * g_dbus_connection_send_message_with_reply: |
| * @connection: a #GDBusConnection |
| * @message: a #GDBusMessage |
| * @flags: flags affecting how the message is sent |
| * @timeout_msec: the timeout in milliseconds, -1 to use the default |
| * timeout or %G_MAXINT for no timeout |
| * @out_serial: (out) (optional): return location for serial number assigned |
| * to @message when sending it or %NULL |
| * @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 |
| * @user_data: The data to pass to @callback |
| * |
| * Asynchronously sends @message to the peer represented by @connection. |
| * |
| * Unless @flags contain the |
| * %G_DBUS_SEND_MESSAGE_FLAGS_PRESERVE_SERIAL flag, the serial number |
| * will be assigned by @connection and set on @message via |
| * g_dbus_message_set_serial(). If @out_serial is not %NULL, then the |
| * serial number used will be written to this location prior to |
| * submitting the message to the underlying transport. While it has a `volatile` |
| * qualifier, this is a historical artifact and the argument passed to it should |
| * not be `volatile`. |
| * |
| * If @connection is closed then the operation will fail with |
| * %G_IO_ERROR_CLOSED. If @cancellable is canceled, the operation will |
| * fail with %G_IO_ERROR_CANCELLED. If @message is not well-formed, |
| * the operation fails with %G_IO_ERROR_INVALID_ARGUMENT. |
| * |
| * This is an asynchronous method. When the operation is finished, @callback |
| * will be invoked in the |
| * [thread-default main context][g-main-context-push-thread-default] |
| * of the thread you are calling this method from. You can then call |
| * g_dbus_connection_send_message_with_reply_finish() to get the result of the operation. |
| * See g_dbus_connection_send_message_with_reply_sync() for the synchronous version. |
| * |
| * Note that @message must be unlocked, unless @flags contain the |
| * %G_DBUS_SEND_MESSAGE_FLAGS_PRESERVE_SERIAL flag. |
| * |
| * See this [server][gdbus-server] and [client][gdbus-unix-fd-client] |
| * for an example of how to use this low-level API to send and receive |
| * UNIX file descriptors. |
| * |
| * Since: 2.26 |
| */ |
| void |
| g_dbus_connection_send_message_with_reply (GDBusConnection *connection, |
| GDBusMessage *message, |
| GDBusSendMessageFlags flags, |
| gint timeout_msec, |
| volatile guint32 *out_serial, |
| GCancellable *cancellable, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| g_return_if_fail (G_IS_DBUS_CONNECTION (connection)); |
| g_return_if_fail (G_IS_DBUS_MESSAGE (message)); |
| g_return_if_fail ((flags & G_DBUS_SEND_MESSAGE_FLAGS_PRESERVE_SERIAL) || !g_dbus_message_get_locked (message)); |
| g_return_if_fail (timeout_msec >= 0 || timeout_msec == -1); |
| |
| CONNECTION_LOCK (connection); |
| g_dbus_connection_send_message_with_reply_unlocked (connection, |
| message, |
| flags, |
| timeout_msec, |
| (guint32 *) out_serial, |
| cancellable, |
| callback, |
| user_data); |
| CONNECTION_UNLOCK (connection); |
| } |
| |
| /** |
| * g_dbus_connection_send_message_with_reply_finish: |
| * @connection: a #GDBusConnection |
| * @res: a #GAsyncResult obtained from the #GAsyncReadyCallback passed to |
| * g_dbus_connection_send_message_with_reply() |
| * @error: teturn location for error or %NULL |
| * |
| * Finishes an operation started with g_dbus_connection_send_message_with_reply(). |
| * |
| * Note that @error is only set if a local in-process error |
| * occurred. That is to say that the returned #GDBusMessage object may |
| * be of type %G_DBUS_MESSAGE_TYPE_ERROR. Use |
| * g_dbus_message_to_gerror() to transcode this to a #GError. |
| * |
| * See this [server][gdbus-server] and [client][gdbus-unix-fd-client] |
| * for an example of how to use this low-level API to send and receive |
| * UNIX file descriptors. |
| * |
| * Returns: (transfer full): a locked #GDBusMessage or %NULL if @error is set |
| * |
| * Since: 2.26 |
| */ |
| GDBusMessage * |
| g_dbus_connection_send_message_with_reply_finish (GDBusConnection *connection, |
| GAsyncResult *res, |
| GError **error) |
| { |
| g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL); |
| g_return_val_if_fail (g_task_is_valid (res, connection), NULL); |
| g_return_val_if_fail (error == NULL || *error == NULL, NULL); |
| |
| return g_task_propagate_pointer (G_TASK (res), error); |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| typedef struct |
| { |
| GAsyncResult *res; |
| GMainContext *context; |
| GMainLoop *loop; |
| } SendMessageSyncData; |
| |
| /* Called from a user thread, lock is not held */ |
| static void |
| send_message_with_reply_sync_cb (GDBusConnection *connection, |
| GAsyncResult *res, |
| gpointer user_data) |
| { |
| SendMessageSyncData *data = user_data; |
| data->res = g_object_ref (res); |
| g_main_loop_quit (data->loop); |
| } |
| |
| /** |
| * g_dbus_connection_send_message_with_reply_sync: |
| * @connection: a #GDBusConnection |
| * @message: a #GDBusMessage |
| * @flags: flags affecting how the message is sent. |
| * @timeout_msec: the timeout in milliseconds, -1 to use the default |
| * timeout or %G_MAXINT for no timeout |
| * @out_serial: (out) (optional): return location for serial number |
| * assigned to @message when sending it or %NULL |
| * @cancellable: (nullable): a #GCancellable or %NULL |
| * @error: return location for error or %NULL |
| * |
| * Synchronously sends @message to the peer represented by @connection |
| * and blocks the calling thread until a reply is received or the |
| * timeout is reached. See g_dbus_connection_send_message_with_reply() |
| * for the asynchronous version of this method. |
| * |
| * Unless @flags contain the |
| * %G_DBUS_SEND_MESSAGE_FLAGS_PRESERVE_SERIAL flag, the serial number |
| * will be assigned by @connection and set on @message via |
| * g_dbus_message_set_serial(). If @out_serial is not %NULL, then the |
| * serial number used will be written to this location prior to |
| * submitting the message to the underlying transport. While it has a `volatile` |
| * qualifier, this is a historical artifact and the argument passed to it should |
| * not be `volatile`. |
| * |
| * If @connection is closed then the operation will fail with |
| * %G_IO_ERROR_CLOSED. If @cancellable is canceled, the operation will |
| * fail with %G_IO_ERROR_CANCELLED. If @message is not well-formed, |
| * the operation fails with %G_IO_ERROR_INVALID_ARGUMENT. |
| * |
| * Note that @error is only set if a local in-process error |
| * occurred. That is to say that the returned #GDBusMessage object may |
| * be of type %G_DBUS_MESSAGE_TYPE_ERROR. Use |
| * g_dbus_message_to_gerror() to transcode this to a #GError. |
| * |
| * See this [server][gdbus-server] and [client][gdbus-unix-fd-client] |
| * for an example of how to use this low-level API to send and receive |
| * UNIX file descriptors. |
| * |
| * Note that @message must be unlocked, unless @flags contain the |
| * %G_DBUS_SEND_MESSAGE_FLAGS_PRESERVE_SERIAL flag. |
| * |
| * Returns: (transfer full): a locked #GDBusMessage that is the reply |
| * to @message or %NULL if @error is set |
| * |
| * Since: 2.26 |
| */ |
| GDBusMessage * |
| g_dbus_connection_send_message_with_reply_sync (GDBusConnection *connection, |
| GDBusMessage *message, |
| GDBusSendMessageFlags flags, |
| gint timeout_msec, |
| volatile guint32 *out_serial, |
| GCancellable *cancellable, |
| GError **error) |
| { |
| SendMessageSyncData data; |
| GDBusMessage *reply; |
| |
| g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL); |
| g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL); |
| g_return_val_if_fail ((flags & G_DBUS_SEND_MESSAGE_FLAGS_PRESERVE_SERIAL) || !g_dbus_message_get_locked (message), NULL); |
| g_return_val_if_fail (timeout_msec >= 0 || timeout_msec == -1, NULL); |
| g_return_val_if_fail (error == NULL || *error == NULL, NULL); |
| |
| data.res = NULL; |
| data.context = g_main_context_new (); |
| data.loop = g_main_loop_new (data.context, FALSE); |
| |
| g_main_context_push_thread_default (data.context); |
| |
| g_dbus_connection_send_message_with_reply (connection, |
| message, |
| flags, |
| timeout_msec, |
| out_serial, |
| cancellable, |
| (GAsyncReadyCallback) send_message_with_reply_sync_cb, |
| &data); |
| g_main_loop_run (data.loop); |
| reply = g_dbus_connection_send_message_with_reply_finish (connection, |
| data.res, |
| error); |
| |
| g_main_context_pop_thread_default (data.context); |
| |
| g_main_context_unref (data.context); |
| g_main_loop_unref (data.loop); |
| if (data.res) |
| g_object_unref (data.res); |
| |
| return reply; |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| typedef struct |
| { |
| guint id; |
| guint ref_count; |
| GDBusMessageFilterFunction filter_function; |
| gpointer user_data; |
| GDestroyNotify user_data_free_func; |
| GMainContext *context; |
| } FilterData; |
| |
| static void |
| filter_data_destroy (FilterData *filter, gboolean notify_sync) |
| { |
| if (notify_sync) |
| { |
| if (filter->user_data_free_func != NULL) |
| filter->user_data_free_func (filter->user_data); |
| } |
| else |
| { |
| call_destroy_notify (filter->context, |
| filter->user_data_free_func, |
| filter->user_data); |
| } |
| g_main_context_unref (filter->context); |
| g_free (filter); |
| } |
| |
| /* requires CONNECTION_LOCK */ |
| static FilterData ** |
| copy_filter_list (GPtrArray *filters) |
| { |
| FilterData **copy; |
| guint n; |
| |
| copy = g_new (FilterData *, filters->len + 1); |
| for (n = 0; n < filters->len; n++) |
| { |
| copy[n] = filters->pdata[n]; |
| copy[n]->ref_count++; |
| } |
| copy[n] = NULL; |
| |
| return copy; |
| } |
| |
| /* requires CONNECTION_LOCK */ |
| static void |
| free_filter_list (FilterData **filters) |
| { |
| guint n; |
| |
| for (n = 0; filters[n]; n++) |
| { |
| filters[n]->ref_count--; |
| if (filters[n]->ref_count == 0) |
| filter_data_destroy (filters[n], FALSE); |
| } |
| g_free (filters); |
| } |
| |
| /* Called in GDBusWorker's thread - we must not block - with no lock held */ |
| static void |
| on_worker_message_received (GDBusWorker *worker, |
| GDBusMessage *message, |
| gpointer user_data) |
| { |
| GDBusConnection *connection; |
| FilterData **filters; |
| guint n; |
| gboolean alive; |
| |
| G_LOCK (message_bus_lock); |
| alive = g_hash_table_contains (alive_connections, user_data); |
| if (!alive) |
| { |
| G_UNLOCK (message_bus_lock); |
| return; |
| } |
| connection = G_DBUS_CONNECTION (user_data); |
| g_object_ref (connection); |
| G_UNLOCK (message_bus_lock); |
| |
| //g_debug ("in on_worker_message_received"); |
| |
| g_object_ref (message); |
| g_dbus_message_lock (message); |
| |
| //g_debug ("boo ref_count = %d %p %p", G_OBJECT (connection)->ref_count, connection, connection->worker); |
| |
| /* First collect the set of callback functions */ |
| CONNECTION_LOCK (connection); |
| filters = copy_filter_list (connection->filters); |
| CONNECTION_UNLOCK (connection); |
| |
| /* then call the filters in order (without holding the lock) */ |
| for (n = 0; filters[n]; n++) |
| { |
| message = filters[n]->filter_function (connection, |
| message, |
| TRUE, |
| filters[n]->user_data); |
| if (message == NULL) |
| break; |
| g_dbus_message_lock (message); |
| } |
| |
| CONNECTION_LOCK (connection); |
| free_filter_list (filters); |
| CONNECTION_UNLOCK (connection); |
| |
| /* Standard dispatch unless the filter ate the message - no need to |
| * do anything if the message was altered |
| */ |
| if (message != NULL) |
| { |
| GDBusMessageType message_type; |
| |
| message_type = g_dbus_message_get_message_type (message); |
| if (message_type == G_DBUS_MESSAGE_TYPE_METHOD_RETURN || message_type == G_DBUS_MESSAGE_TYPE_ERROR) |
| { |
| guint32 reply_serial; |
| GTask *task; |
| |
| reply_serial = g_dbus_message_get_reply_serial (message); |
| CONNECTION_LOCK (connection); |
| task = g_hash_table_lookup (connection->map_method_serial_to_task, |
| GUINT_TO_POINTER (reply_serial)); |
| if (task != NULL) |
| { |
| /* This removes @task from @map_method_serial_to_task. */ |
| //g_debug ("delivering reply/error for serial %d for %p", reply_serial, connection); |
| send_message_data_deliver_reply_unlocked (task, message); |
| } |
| else |
| { |
| //g_debug ("message reply/error for serial %d but no SendMessageData found for %p", reply_serial, connection); |
| } |
| CONNECTION_UNLOCK (connection); |
| } |
| else if (message_type == G_DBUS_MESSAGE_TYPE_SIGNAL) |
| { |
| CONNECTION_LOCK (connection); |
| distribute_signals (connection, message); |
| CONNECTION_UNLOCK (connection); |
| } |
| else if (message_type == G_DBUS_MESSAGE_TYPE_METHOD_CALL) |
| { |
| CONNECTION_LOCK (connection); |
| distribute_method_call (connection, message); |
| CONNECTION_UNLOCK (connection); |
| } |
| } |
| |
| if (message != NULL) |
| g_object_unref (message); |
| g_object_unref (connection); |
| } |
| |
| /* Called in GDBusWorker's thread, lock is not held */ |
| static GDBusMessage * |
| on_worker_message_about_to_be_sent (GDBusWorker *worker, |
| GDBusMessage *message, |
| gpointer user_data) |
| { |
| GDBusConnection *connection; |
| FilterData **filters; |
| guint n; |
| gboolean alive; |
| |
| G_LOCK (message_bus_lock); |
| alive = g_hash_table_contains (alive_connections, user_data); |
| if (!alive) |
| { |
| G_UNLOCK (message_bus_lock); |
| return message; |
| } |
| connection = G_DBUS_CONNECTION (user_data); |
| g_object_ref (connection); |
| G_UNLOCK (message_bus_lock); |
| |
| //g_debug ("in on_worker_message_about_to_be_sent"); |
| |
| /* First collect the set of callback functions */ |
| CONNECTION_LOCK (connection); |
| filters = copy_filter_list (connection->filters); |
| CONNECTION_UNLOCK (connection); |
| |
| /* then call the filters in order (without holding the lock) */ |
| for (n = 0; filters[n]; n++) |
| { |
| g_dbus_message_lock (message); |
| message = filters[n]->filter_function (connection, |
| message, |
| FALSE, |
| filters[n]->user_data); |
| if (message == NULL) |
| break; |
| } |
| |
| CONNECTION_LOCK (connection); |
| free_filter_list (filters); |
| CONNECTION_UNLOCK (connection); |
| |
| g_object_unref (connection); |
| |
| return message; |
| } |
| |
| /* called with connection lock held, in GDBusWorker thread |
| * @key, @value and @user_data are (transfer none) */ |
| static gboolean |
| cancel_method_on_close (gpointer key, gpointer value, gpointer user_data) |
| { |
| GTask *task = value; |
| SendMessageData *data = g_task_get_task_data (task); |
| |
| if (data->delivered) |
| return FALSE; |
| |
| g_task_return_new_error_literal (task, |
| G_IO_ERROR, |
| G_IO_ERROR_CLOSED, |
| _("The connection is closed")); |
| |
| /* Ask send_message_with_reply_cleanup not to remove the element from the |
| * hash table - we're in the middle of a foreach; that would be unsafe. |
| * Instead, return TRUE from this function so that it gets removed safely. |
| */ |
| send_message_with_reply_cleanup (task, FALSE); |
| return TRUE; |
| } |
| |
| /* Called in GDBusWorker's thread - we must not block - without lock held */ |
| static void |
| on_worker_closed (GDBusWorker *worker, |
| gboolean remote_peer_vanished, |
| GError *error, |
| gpointer user_data) |
| { |
| GDBusConnection *connection; |
| gboolean alive; |
| guint old_atomic_flags; |
| |
| G_LOCK (message_bus_lock); |
| alive = g_hash_table_contains (alive_connections, user_data); |
| if (!alive) |
| { |
| G_UNLOCK (message_bus_lock); |
| return; |
| } |
| connection = G_DBUS_CONNECTION (user_data); |
| g_object_ref (connection); |
| G_UNLOCK (message_bus_lock); |
| |
| //g_debug ("in on_worker_closed: %s", error->message); |
| |
| CONNECTION_LOCK (connection); |
| /* Even though this is atomic, we do it inside the lock to avoid breaking |
| * assumptions in remove_match_rule(). We'd need the lock in a moment |
| * anyway, so, no loss. |
| */ |
| old_atomic_flags = g_atomic_int_or (&connection->atomic_flags, FLAG_CLOSED); |
| |
| if (!(old_atomic_flags & FLAG_CLOSED)) |
| { |
| g_hash_table_foreach_remove (connection->map_method_serial_to_task, cancel_method_on_close, NULL); |
| schedule_closed_unlocked (connection, remote_peer_vanished, error); |
| } |
| CONNECTION_UNLOCK (connection); |
| |
| g_object_unref (connection); |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| /* Determines the biggest set of capabilities we can support on this |
| * connection. |
| * |
| * Called with the init_lock held. |
| */ |
| static GDBusCapabilityFlags |
| get_offered_capabilities_max (GDBusConnection *connection) |
| { |
| GDBusCapabilityFlags ret; |
| ret = G_DBUS_CAPABILITY_FLAGS_NONE; |
| #ifdef G_OS_UNIX |
| if (G_IS_UNIX_CONNECTION (connection->stream)) |
| ret |= G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING; |
| #endif |
| return ret; |
| } |
| |
| /* Called in a user thread, lock is not held */ |
| static gboolean |
| initable_init (GInitable *initable, |
| GCancellable *cancellable, |
| GError **error) |
| { |
| GDBusConnection *connection = G_DBUS_CONNECTION (initable); |
| gboolean ret; |
| |
| /* This method needs to be idempotent to work with the singleton |
| * pattern. See the docs for g_initable_init(). We implement this by |
| * locking. |
| * |
| * Unfortunately we can't use the main lock since the on_worker_*() |
| * callbacks above needs the lock during initialization (for message |
| * bus connections we do a synchronous Hello() call on the bus). |
| */ |
| g_mutex_lock (&connection->init_lock); |
| |
| ret = FALSE; |
| |
| /* Make this a no-op if we're already initialized (successfully or |
| * unsuccessfully) |
| */ |
| if ((g_atomic_int_get (&connection->atomic_flags) & FLAG_INITIALIZED)) |
| { |
| ret = (connection->initialization_error == NULL); |
| goto out; |
| } |
| |
| /* Because of init_lock, we can't get here twice in different threads */ |
| g_assert (connection->initialization_error == NULL); |
| |
| /* The user can pass multiple (but mutally exclusive) construct |
| * properties: |
| * |
| * - stream (of type GIOStream) |
| * - address (of type gchar*) |
| * |
| * At the end of the day we end up with a non-NULL GIOStream |
| * object in connection->stream. |
| */ |
| if (connection->address != NULL) |
| { |
| g_assert (connection->stream == NULL); |
| |
| if ((connection->flags & G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER) || |
| (connection->flags & G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS) || |
| (connection->flags & G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_REQUIRE_SAME_USER)) |
| { |
| g_set_error_literal (&connection->initialization_error, |
| G_IO_ERROR, |
| G_IO_ERROR_INVALID_ARGUMENT, |
| _("Unsupported flags encountered when constructing a client-side connection")); |
| goto out; |
| } |
| |
| connection->stream = g_dbus_address_get_stream_sync (connection->address, |
| NULL, /* TODO: out_guid */ |
| cancellable, |
| &connection->initialization_error); |
| if (connection->stream == NULL) |
| goto out; |
| } |
| else if (connection->stream != NULL) |
| { |
| /* nothing to do */ |
| } |
| else |
| { |
| g_assert_not_reached (); |
| } |
| |
| /* Authenticate the connection */ |
| if (connection->flags & G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER) |
| { |
| g_assert (!(connection->flags & G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT)); |
| g_assert (connection->guid != NULL); |
| connection->auth = _g_dbus_auth_new (connection->stream); |
| if (!_g_dbus_auth_run_server (connection->auth, |
| connection->authentication_observer, |
| connection->guid, |
| (connection->flags & G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS), |
| (connection->flags & G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_REQUIRE_SAME_USER), |
| get_offered_capabilities_max (connection), |
| &connection->capabilities, |
| &connection->credentials, |
| cancellable, |
| &connection->initialization_error)) |
| goto out; |
| } |
| else if (connection->flags & G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT) |
| { |
| g_assert (!(connection->flags & G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER)); |
| g_assert (connection->guid == NULL); |
| connection->auth = _g_dbus_auth_new (connection->stream); |
| connection->guid = _g_dbus_auth_run_client (connection->auth, |
| connection->authentication_observer, |
| connection->flags, |
| get_offered_capabilities_max (connection), |
| &connection->capabilities, |
| cancellable, |
| &connection->initialization_error); |
| if (connection->guid == NULL) |
| goto out; |
| } |
| |
| if (connection->authentication_observer != NULL) |
| { |
| g_object_unref (connection->authentication_observer); |
| connection->authentication_observer = NULL; |
| } |
| |
| //g_output_stream_flush (G_SOCKET_CONNECTION (connection->stream) |
| |
| //g_debug ("haz unix fd passing powers: %d", connection->capabilities & G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING); |
| |
| #ifdef G_OS_UNIX |
| /* We want all IO operations to be non-blocking since they happen in |
| * the worker thread which is shared by _all_ connections. |
| */ |
| if (G_IS_SOCKET_CONNECTION (connection->stream)) |
| { |
| g_socket_set_blocking (g_socket_connection_get_socket (G_SOCKET_CONNECTION (connection->stream)), FALSE); |
| } |
| #endif |
| |
| G_LOCK (message_bus_lock); |
| if (alive_connections == NULL) |
| alive_connections = g_hash_table_new (g_direct_hash, g_direct_equal); |
| g_hash_table_add (alive_connections, connection); |
| G_UNLOCK (message_bus_lock); |
| |
| connection->worker = _g_dbus_worker_new (connection->stream, |
| connection->capabilities, |
| ((connection->flags & G_DBUS_CONNECTION_FLAGS_DELAY_MESSAGE_PROCESSING) != 0), |
| on_worker_message_received, |
| on_worker_message_about_to_be_sent, |
| on_worker_closed, |
| connection); |
| |
| /* if a bus connection, call org.freedesktop.DBus.Hello - this is how we're getting a name */ |
| if (connection->flags & G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION) |
| { |
| GVariant *hello_result; |
| |
| /* we could lift this restriction by adding code in gdbusprivate.c */ |
| if (connection->flags & G_DBUS_CONNECTION_FLAGS_DELAY_MESSAGE_PROCESSING) |
| { |
| g_set_error_literal (&connection->initialization_error, |
| G_IO_ERROR, |
| G_IO_ERROR_FAILED, |
| "Cannot use DELAY_MESSAGE_PROCESSING with MESSAGE_BUS_CONNECTION"); |
| goto out; |
| } |
| |
| hello_result = g_dbus_connection_call_sync (connection, |
| "org.freedesktop.DBus", /* name */ |
| "/org/freedesktop/DBus", /* path */ |
| "org.freedesktop.DBus", /* interface */ |
| "Hello", |
| NULL, /* parameters */ |
| G_VARIANT_TYPE ("(s)"), |
| CALL_FLAGS_INITIALIZING, |
| -1, |
| NULL, /* TODO: cancellable */ |
| &connection->initialization_error); |
| if (hello_result == NULL) |
| goto out; |
| |
| g_variant_get (hello_result, "(s)", &connection->bus_unique_name); |
| g_variant_unref (hello_result); |
| //g_debug ("unique name is '%s'", connection->bus_unique_name); |
| } |
| |
| ret = TRUE; |
| out: |
| if (!ret) |
| { |
| g_assert (connection->initialization_error != NULL); |
| g_propagate_error (error, g_error_copy (connection->initialization_error)); |
| } |
| |
| /* Don't cache canceled errors. Otherwise other concurrent users of the same connection |
| * object will be canceled as well. */ |
| if (g_error_matches (connection->initialization_error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) |
| { |
| if (connection->worker != NULL) |
| { |
| _g_dbus_worker_stop (connection->worker); |
| connection->worker = NULL; |
| if (alive_connections != NULL) |
| g_warn_if_fail (g_hash_table_remove (alive_connections, connection)); |
| } |
| g_clear_error (&connection->initialization_error); |
| g_clear_object (&connection->stream); |
| g_clear_object (&connection->auth); |
| g_clear_object (&connection->credentials); |
| g_clear_pointer (&connection->guid, g_free); |
| connection->capabilities = 0; |
| } |
| else |
| g_atomic_int_or (&connection->atomic_flags, FLAG_INITIALIZED); |
| g_mutex_unlock (&connection->init_lock); |
| |
| return ret; |
| } |
| |
| static void |
| initable_iface_init (GInitableIface *initable_iface) |
| { |
| initable_iface->init = initable_init; |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| static void |
| async_initable_iface_init (GAsyncInitableIface *async_initable_iface) |
| { |
| /* Use default */ |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| /** |
| * g_dbus_connection_new: |
| * @stream: a #GIOStream |
| * @guid: (nullable): the GUID to use if authenticating as a server or %NULL |
| * @flags: flags describing how to make the connection |
| * @observer: (nullable): a #GDBusAuthObserver or %NULL |
| * @cancellable: (nullable): a #GCancellable or %NULL |
| * @callback: a #GAsyncReadyCallback to call when the request is satisfied |
| * @user_data: the data to pass to @callback |
| * |
| * Asynchronously sets up a D-Bus connection for exchanging D-Bus messages |
| * with the end represented by @stream. |
| * |
| * If @stream is a #GSocketConnection, then the corresponding #GSocket |
| * will be put into non-blocking mode. |
| * |
| * The D-Bus connection will interact with @stream from a worker thread. |
| * As a result, the caller should not interact with @stream after this |
| * method has been called, except by calling g_object_unref() on it. |
| * |
| * If @observer is not %NULL it may be used to control the |
| * authentication process. |
| * |
| * When the operation is finished, @callback will be invoked. You can |
| * then call g_dbus_connection_new_finish() to get the result of the |
| * operation. |
| * |
| * This is an asynchronous failable constructor. See |
| * g_dbus_connection_new_sync() for the synchronous |
| * version. |
| * |
| * Since: 2.26 |
| */ |
| void |
| g_dbus_connection_new (GIOStream *stream, |
| const gchar *guid, |
| GDBusConnectionFlags flags, |
| GDBusAuthObserver *observer, |
| GCancellable *cancellable, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| _g_dbus_initialize (); |
| |
| g_return_if_fail (G_IS_IO_STREAM (stream)); |
| g_return_if_fail ((flags & ~G_DBUS_CONNECTION_FLAGS_ALL) == 0); |
| |
| g_async_initable_new_async (G_TYPE_DBUS_CONNECTION, |
| G_PRIORITY_DEFAULT, |
| cancellable, |
| callback, |
| user_data, |
| "stream", stream, |
| "guid", guid, |
| "flags", flags, |
| "authentication-observer", observer, |
| NULL); |
| } |
| |
| /** |
| * g_dbus_connection_new_finish: |
| * @res: a #GAsyncResult obtained from the #GAsyncReadyCallback |
| * passed to g_dbus_connection_new(). |
| * @error: return location for error or %NULL |
| * |
| * Finishes an operation started with g_dbus_connection_new(). |
| * |
| * Returns: (transfer full): a #GDBusConnection or %NULL if @error is set. Free |
| * with g_object_unref(). |
| * |
| * Since: 2.26 |
| */ |
| GDBusConnection * |
| g_dbus_connection_new_finish (GAsyncResult *res, |
| GError **error) |
| { |
| GObject *object; |
| GObject *source_object; |
| |
| g_return_val_if_fail (G_IS_ASYNC_RESULT (res), NULL); |
| g_return_val_if_fail (error == NULL || *error == NULL, NULL); |
| |
| source_object = g_async_result_get_source_object (res); |
| g_assert (source_object != NULL); |
| object = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object), |
| res, |
| error); |
| g_object_unref (source_object); |
| if (object != NULL) |
| return G_DBUS_CONNECTION (object); |
| else |
| return NULL; |
| } |
| |
| /** |
| * g_dbus_connection_new_sync: |
| * @stream: a #GIOStream |
| * @guid: (nullable): the GUID to use if authenticating as a server or %NULL |
| * @flags: flags describing how to make the connection |
| * @observer: (nullable): a #GDBusAuthObserver or %NULL |
| * @cancellable: (nullable): a #GCancellable or %NULL |
| * @error: return location for error or %NULL |
| * |
| * Synchronously sets up a D-Bus connection for exchanging D-Bus messages |
| * with the end represented by @stream. |
| * |
| * If @stream is a #GSocketConnection, then the corresponding #GSocket |
| * will be put into non-blocking mode. |
| * |
| * The D-Bus connection will interact with @stream from a worker thread. |
| * As a result, the caller should not interact with @stream after this |
| * method has been called, except by calling g_object_unref() on it. |
| * |
| * If @observer is not %NULL it may be used to control the |
| * authentication process. |
| * |
| * This is a synchronous failable constructor. See |
| * g_dbus_connection_new() for the asynchronous version. |
| * |
| * Returns: (transfer full): a #GDBusConnection or %NULL if @error is set. |
| * Free with g_object_unref(). |
| * |
| * Since: 2.26 |
| */ |
| GDBusConnection * |
| g_dbus_connection_new_sync (GIOStream *stream, |
| const gchar *guid, |
| GDBusConnectionFlags flags, |
| GDBusAuthObserver *observer, |
| GCancellable *cancellable, |
| GError **error) |
| { |
| _g_dbus_initialize (); |
| g_return_val_if_fail (G_IS_IO_STREAM (stream), NULL); |
| g_return_val_if_fail ((flags & ~G_DBUS_CONNECTION_FLAGS_ALL) == 0, NULL); |
| g_return_val_if_fail (error == NULL || *error == NULL, NULL); |
| return g_initable_new (G_TYPE_DBUS_CONNECTION, |
| cancellable, |
| error, |
| "stream", stream, |
| "guid", guid, |
| "flags", flags, |
| "authentication-observer", observer, |
| NULL); |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| /** |
| * g_dbus_connection_new_for_address: |
| * @address: a D-Bus address |
| * @flags: flags describing how to make the connection |
| * @observer: (nullable): a #GDBusAuthObserver or %NULL |
| * @cancellable: (nullable): a #GCancellable or %NULL |
| * @callback: a #GAsyncReadyCallback to call when the request is satisfied |
| * @user_data: the data to pass to @callback |
| * |
| * Asynchronously connects and sets up a D-Bus client connection for |
| * exchanging D-Bus messages with an endpoint specified by @address |
| * which must be in the |
| * [D-Bus address format](https://dbus.freedesktop.org/doc/dbus-specification.html#addresses). |
| * |
| * This constructor can only be used to initiate client-side |
| * connections - use g_dbus_connection_new() if you need to act as the |
| * server. In particular, @flags cannot contain the |
| * %G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER, |
| * %G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS or |
| * %G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_REQUIRE_SAME_USER flags. |
| * |
| * When the operation is finished, @callback will be invoked. You can |
| * then call g_dbus_connection_new_for_address_finish() to get the result of |
| * the operation. |
| * |
| * If @observer is not %NULL it may be used to control the |
| * authentication process. |
| * |
| * This is an asynchronous failable constructor. See |
| * g_dbus_connection_new_for_address_sync() for the synchronous |
| * version. |
| * |
| * Since: 2.26 |
| */ |
| void |
| g_dbus_connection_new_for_address (const gchar *address, |
| GDBusConnectionFlags flags, |
| GDBusAuthObserver *observer, |
| GCancellable *cancellable, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| _g_dbus_initialize (); |
| |
| g_return_if_fail (address != NULL); |
| g_return_if_fail ((flags & ~G_DBUS_CONNECTION_FLAGS_ALL) == 0); |
| |
| g_async_initable_new_async (G_TYPE_DBUS_CONNECTION, |
| G_PRIORITY_DEFAULT, |
| cancellable, |
| callback, |
| user_data, |
| "address", address, |
| "flags", flags, |
| "authentication-observer", observer, |
| NULL); |
| } |
| |
| /** |
| * g_dbus_connection_new_for_address_finish: |
| * @res: a #GAsyncResult obtained from the #GAsyncReadyCallback passed |
| * to g_dbus_connection_new() |
| * @error: return location for error or %NULL |
| * |
| * Finishes an operation started with g_dbus_connection_new_for_address(). |
| * |
| * Returns: (transfer full): a #GDBusConnection or %NULL if @error is set. |
| * Free with g_object_unref(). |
| * |
| * Since: 2.26 |
| */ |
| GDBusConnection * |
| g_dbus_connection_new_for_address_finish (GAsyncResult *res, |
| GError **error) |
| { |
| GObject *object; |
| GObject *source_object; |
| |
| g_return_val_if_fail (G_IS_ASYNC_RESULT (res), NULL); |
| g_return_val_if_fail (error == NULL || *error == NULL, NULL); |
| |
| source_object = g_async_result_get_source_object (res); |
| g_assert (source_object != NULL); |
| object = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object), |
| res, |
| error); |
| g_object_unref (source_object); |
| if (object != NULL) |
| return G_DBUS_CONNECTION (object); |
| else |
| return NULL; |
| } |
| |
| /** |
| * g_dbus_connection_new_for_address_sync: |
| * @address: a D-Bus address |
| * @flags: flags describing how to make the connection |
| * @observer: (nullable): a #GDBusAuthObserver or %NULL |
| * @cancellable: (nullable): a #GCancellable or %NULL |
| * @error: return location for error or %NULL |
| * |
| * Synchronously connects and sets up a D-Bus client connection for |
| * exchanging D-Bus messages with an endpoint specified by @address |
| * which must be in the |
| * [D-Bus address format](https://dbus.freedesktop.org/doc/dbus-specification.html#addresses). |
| * |
| * This constructor can only be used to initiate client-side |
| * connections - use g_dbus_connection_new_sync() if you need to act |
| * as the server. In particular, @flags cannot contain the |
| * %G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER, |
| * %G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS or |
| * %G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_REQUIRE_SAME_USER flags. |
| * |
| * This is a synchronous failable constructor. See |
| * g_dbus_connection_new_for_address() for the asynchronous version. |
| * |
| * If @observer is not %NULL it may be used to control the |
| * authentication process. |
| * |
| * Returns: (transfer full): a #GDBusConnection or %NULL if @error is set. |
| * Free with g_object_unref(). |
| * |
| * Since: 2.26 |
| */ |
| GDBusConnection * |
| g_dbus_connection_new_for_address_sync (const gchar *address, |
| GDBusConnectionFlags flags, |
| GDBusAuthObserver *observer, |
| GCancellable *cancellable, |
| GError **error) |
| { |
| _g_dbus_initialize (); |
| |
| g_return_val_if_fail (address != NULL, NULL); |
| g_return_val_if_fail ((flags & ~G_DBUS_CONNECTION_FLAGS_ALL) == 0, NULL); |
| g_return_val_if_fail (error == NULL || *error == NULL, NULL); |
| return g_initable_new (G_TYPE_DBUS_CONNECTION, |
| cancellable, |
| error, |
| "address", address, |
| "flags", flags, |
| "authentication-observer", observer, |
| NULL); |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| /** |
| * g_dbus_connection_set_exit_on_close: |
| * @connection: a #GDBusConnection |
| * @exit_on_close: whether the process should be terminated |
| * when @connection is closed by the remote peer |
| * |
| * Sets whether the process should be terminated when @connection is |
| * closed by the remote peer. See #GDBusConnection:exit-on-close for |
| * more details. |
| * |
| * Note that this function should be used with care. Most modern UNIX |
| * desktops tie the notion of a user session with the session bus, and expect |
| * all of a user's applications to quit when their bus connection goes away. |
| * If you are setting @exit_on_close to %FALSE for the shared session |
| * bus connection, you should make sure that your application exits |
| * when the user session ends. |
| * |
| * Since: 2.26 |
| */ |
| void |
| g_dbus_connection_set_exit_on_close (GDBusConnection *connection, |
| gboolean exit_on_close) |
| { |
| g_return_if_fail (G_IS_DBUS_CONNECTION (connection)); |
| |
| if (exit_on_close) |
| g_atomic_int_or (&connection->atomic_flags, FLAG_EXIT_ON_CLOSE); |
| else |
| g_atomic_int_and (&connection->atomic_flags, ~FLAG_EXIT_ON_CLOSE); |
| |
| } |
| |
| /** |
| * g_dbus_connection_get_exit_on_close: |
| * @connection: a #GDBusConnection |
| * |
| * Gets whether the process is terminated when @connection is |
| * closed by the remote peer. See |
| * #GDBusConnection:exit-on-close for more details. |
| * |
| * Returns: whether the process is terminated when @connection is |
| * closed by the remote peer |
| * |
| * Since: 2.26 |
| */ |
| gboolean |
| g_dbus_connection_get_exit_on_close (GDBusConnection *connection) |
| { |
| g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE); |
| |
| if (g_atomic_int_get (&connection->atomic_flags) & FLAG_EXIT_ON_CLOSE) |
| return TRUE; |
| else |
| return FALSE; |
| } |
| |
| /** |
| * g_dbus_connection_get_guid: |
| * @connection: a #GDBusConnection |
| * |
| * The GUID of the peer performing the role of server when |
| * authenticating. See #GDBusConnection:guid for more details. |
| * |
| * Returns: (not nullable): The GUID. Do not free this string, it is owned by |
| * @connection. |
| * |
| * Since: 2.26 |
| */ |
| const gchar * |
| g_dbus_connection_get_guid (GDBusConnection *connection) |
| { |
| g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL); |
| return connection->guid; |
| } |
| |
| /** |
| * g_dbus_connection_get_unique_name: |
| * @connection: a #GDBusConnection |
| * |
| * Gets the unique name of @connection as assigned by the message |
| * bus. This can also be used to figure out if @connection is a |
| * message bus connection. |
| * |
| * Returns: (nullable): the unique name or %NULL if @connection is not a message |
| * bus connection. Do not free this string, it is owned by |
| * @connection. |
| * |
| * Since: 2.26 |
| */ |
| const gchar * |
| g_dbus_connection_get_unique_name (GDBusConnection *connection) |
| { |
| g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL); |
| |
| /* do not use g_return_val_if_fail(), we want the memory barrier */ |
| if (!check_initialized (connection)) |
| return NULL; |
| |
| return connection->bus_unique_name; |
| } |
| |
| /** |
| * g_dbus_connection_get_peer_credentials: |
| * @connection: a #GDBusConnection |
| * |
| * Gets the credentials of the authenticated peer. This will always |
| * return %NULL unless @connection acted as a server |
| * (e.g. %G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER was passed) |
| * when set up and the client passed credentials as part of the |
| * authentication process. |
| * |
| * In a message bus setup, the message bus is always the server and |
| * each application is a client. So this method will always return |
| * %NULL for message bus clients. |
| * |
| * Returns: (transfer none) (nullable): a #GCredentials or %NULL if not |
| * available. Do not free this object, it is owned by @connection. |
| * |
| * Since: 2.26 |
| */ |
| GCredentials * |
| g_dbus_connection_get_peer_credentials (GDBusConnection *connection) |
| { |
| g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL); |
| |
| /* do not use g_return_val_if_fail(), we want the memory barrier */ |
| if (!check_initialized (connection)) |
| return NULL; |
| |
| return connection->credentials; |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| static guint _global_filter_id = 1; /* (atomic) */ |
| |
| /** |
| * g_dbus_connection_add_filter: |
| * @connection: a #GDBusConnection |
| * @filter_function: a filter function |
| * @user_data: user data to pass to @filter_function |
| * @user_data_free_func: function to free @user_data with when filter |
| * is removed or %NULL |
| * |
| * Adds a message filter. Filters are handlers that are run on all |
| * incoming and outgoing messages, prior to standard dispatch. Filters |
| * are run in the order that they were added. The same handler can be |
| * added as a filter more than once, in which case it will be run more |
| * than once. Filters added during a filter callback won't be run on |
| * the message being processed. Filter functions are allowed to modify |
| * and even drop messages. |
| * |
| * Note that filters are run in a dedicated message handling thread so |
| * they can't block and, generally, can't do anything but signal a |
| * worker thread. Also note that filters are rarely needed - use API |
| * such as g_dbus_connection_send_message_with_reply(), |
| * g_dbus_connection_signal_subscribe() or g_dbus_connection_call() instead. |
| * |
| * If a filter consumes an incoming message the message is not |
| * dispatched anywhere else - not even the standard dispatch machinery |
| * (that API such as g_dbus_connection_signal_subscribe() and |
| * g_dbus_connection_send_message_with_reply() relies on) will see the |
| * message. Similarly, if a filter consumes an outgoing message, the |
| * message will not be sent to the other peer. |
| * |
| * If @user_data_free_func is non-%NULL, it will be called (in the |
| * thread-default main context of the thread you are calling this |
| * method from) at some point after @user_data is no longer |
| * needed. (It is not guaranteed to be called synchronously when the |
| * filter is removed, and may be called after @connection has been |
| * destroyed.) |
| * |
| * Returns: a filter identifier that can be used with |
| * g_dbus_connection_remove_filter() |
| * |
| * Since: 2.26 |
| */ |
| guint |
| g_dbus_connection_add_filter (GDBusConnection *connection, |
| GDBusMessageFilterFunction filter_function, |
| gpointer user_data, |
| GDestroyNotify user_data_free_func) |
| { |
| FilterData *data; |
| |
| g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), 0); |
| g_return_val_if_fail (filter_function != NULL, 0); |
| g_return_val_if_fail (check_initialized (connection), 0); |
| |
| CONNECTION_LOCK (connection); |
| data = g_new0 (FilterData, 1); |
| data->id = (guint) g_atomic_int_add (&_global_filter_id, 1); /* TODO: overflow etc. */ |
| data->ref_count = 1; |
| data->filter_function = filter_function; |
| data->user_data = user_data; |
| data->user_data_free_func = user_data_free_func; |
| data->context = g_main_context_ref_thread_default (); |
| g_ptr_array_add (connection->filters, data); |
| CONNECTION_UNLOCK (connection); |
| |
| return data->id; |
| } |
| |
| /* only called from finalize(), removes all filters */ |
| static void |
| purge_all_filters (GDBusConnection *connection) |
| { |
| guint n; |
| |
| for (n = 0; n < connection->filters->len; n++) |
| filter_data_destroy (connection->filters->pdata[n], FALSE); |
| } |
| |
| /** |
| * g_dbus_connection_remove_filter: |
| * @connection: a #GDBusConnection |
| * @filter_id: an identifier obtained from g_dbus_connection_add_filter() |
| * |
| * Removes a filter. |
| * |
| * Note that since filters run in a different thread, there is a race |
| * condition where it is possible that the filter will be running even |
| * after calling g_dbus_connection_remove_filter(), so you cannot just |
| * free data that the filter might be using. Instead, you should pass |
| * a #GDestroyNotify to g_dbus_connection_add_filter(), which will be |
| * called when it is guaranteed that the data is no longer needed. |
| * |
| * Since: 2.26 |
| */ |
| void |
| g_dbus_connection_remove_filter (GDBusConnection *connection, |
| guint filter_id) |
| { |
| guint n; |
| gboolean found; |
| FilterData *to_destroy; |
| |
| g_return_if_fail (G_IS_DBUS_CONNECTION (connection)); |
| g_return_if_fail (check_initialized (connection)); |
| |
| CONNECTION_LOCK (connection); |
| found = FALSE; |
| to_destroy = NULL; |
| for (n = 0; n < connection->filters->len; n++) |
| { |
| FilterData *data = connection->filters->pdata[n]; |
| if (data->id == filter_id) |
| { |
| found = TRUE; |
| g_ptr_array_remove_index (connection->filters, n); |
| data->ref_count--; |
| if (data->ref_count == 0) |
| to_destroy = data; |
| break; |
| } |
| } |
| CONNECTION_UNLOCK (connection); |
| |
| /* do free without holding lock */ |
| if (to_destroy != NULL) |
| filter_data_destroy (to_destroy, TRUE); |
| else if (!found) |
| { |
| g_warning ("g_dbus_connection_remove_filter: No filter found for filter_id %d", filter_id); |
| } |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| typedef struct |
| { |
| gchar *rule; |
| gchar *sender; |
| gchar *sender_unique_name; /* if sender is unique or org.freedesktop.DBus, then that name... otherwise blank */ |
| gchar *interface_name; |
| gchar *member; |
| gchar *object_path; |
| gchar *arg0; |
| GDBusSignalFlags flags; |
| GPtrArray *subscribers; /* (owned) (element-type SignalSubscriber) */ |
| } SignalData; |
| |
| static void |
| signal_data_free (SignalData *signal_data) |
| { |
| g_free (signal_data->rule); |
| g_free (signal_data->sender); |
| g_free (signal_data->sender_unique_name); |
| g_free (signal_data->interface_name); |
| g_free (signal_data->member); |
| g_free (signal_data->object_path); |
| g_free (signal_data->arg0); |
| g_ptr_array_unref (signal_data->subscribers); |
| g_free (signal_data); |
| } |
| |
| typedef struct |
| { |
| /* All fields are immutable after construction. */ |
| gatomicrefcount ref_count; |
| GDBusSignalCallback callback; |
| gpointer user_data; |
| GDestroyNotify user_data_free_func; |
| guint id; |
| GMainContext *context; |
| } SignalSubscriber; |
| |
| static SignalSubscriber * |
| signal_subscriber_ref (SignalSubscriber *subscriber) |
| { |
| g_atomic_ref_count_inc (&subscriber->ref_count); |
| return subscriber; |
| } |
| |
| static void |
| signal_subscriber_unref (SignalSubscriber *subscriber) |
| { |
| if (g_atomic_ref_count_dec (&subscriber->ref_count)) |
| { |
| /* Destroy the user data. It doesn’t matter which thread |
| * signal_subscriber_unref() is called in (or whether it’s called with a |
| * lock held), as call_destroy_notify() always defers to the next |
| * #GMainContext iteration. */ |
| call_destroy_notify (subscriber->context, |
| subscriber->user_data_free_func, |
| subscriber->user_data); |
| |
| g_main_context_unref (subscriber->context); |
| g_free (subscriber); |
| } |
| } |
| |
| static gchar * |
| args_to_rule (const gchar *sender, |
| const gchar *interface_name, |
| const gchar *member, |
| const gchar *object_path, |
| const gchar *arg0, |
| GDBusSignalFlags flags) |
| { |
| GString *rule; |
| |
| rule = g_string_new ("type='signal'"); |
| if (flags & G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE) |
| g_string_prepend_c (rule, '-'); |
| if (sender != NULL) |
| g_string_append_printf (rule, ",sender='%s'", sender); |
| if (interface_name != NULL) |
| g_string_append_printf (rule, ",interface='%s'", interface_name); |
| if (member != NULL) |
| g_string_append_printf (rule, ",member='%s'", member); |
| if (object_path != NULL) |
| g_string_append_printf (rule, ",path='%s'", object_path); |
| |
| if (arg0 != NULL) |
| { |
| if (flags & G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_PATH) |
| g_string_append_printf (rule, ",arg0path='%s'", arg0); |
| else if (flags & G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_NAMESPACE) |
| g_string_append_printf (rule, ",arg0namespace='%s'", arg0); |
| else |
| g_string_append_printf (rule, ",arg0='%s'", arg0); |
| } |
| |
| return g_string_free (rule, FALSE); |
| } |
| |
| static guint _global_subscriber_id = 1; /* (atomic) */ |
| static guint _global_registration_id = 1; /* (atomic) */ |
| static guint _global_subtree_registration_id = 1; /* (atomic) */ |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| /* Called in a user thread, lock is held */ |
| static void |
| add_match_rule (GDBusConnection *connection, |
| const gchar *match_rule) |
| { |
| GError *error; |
| GDBusMessage *message; |
| |
| if (match_rule[0] == '-') |
| return; |
| |
| message = g_dbus_message_new_method_call ("org.freedesktop.DBus", /* name */ |
| "/org/freedesktop/DBus", /* path */ |
| "org.freedesktop.DBus", /* interface */ |
| "AddMatch"); |
| g_dbus_message_set_body (message, g_variant_new ("(s)", match_rule)); |
| error = NULL; |
| if (!g_dbus_connection_send_message_unlocked (connection, |
| message, |
| G_DBUS_SEND_MESSAGE_FLAGS_NONE, |
| NULL, |
| &error)) |
| { |
| g_critical ("Error while sending AddMatch() message: %s", error->message); |
| g_error_free (error); |
| } |
| g_object_unref (message); |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| /* Called in a user thread, lock is held */ |
| static void |
| remove_match_rule (GDBusConnection *connection, |
| const gchar *match_rule) |
| { |
| GError *error; |
| GDBusMessage *message; |
| |
| if (match_rule[0] == '-') |
| return; |
| |
| message = g_dbus_message_new_method_call ("org.freedesktop.DBus", /* name */ |
| "/org/freedesktop/DBus", /* path */ |
| "org.freedesktop.DBus", /* interface */ |
| "RemoveMatch"); |
| g_dbus_message_set_body (message, g_variant_new ("(s)", match_rule)); |
| |
| error = NULL; |
| if (!g_dbus_connection_send_message_unlocked (connection, |
| message, |
| G_DBUS_SEND_MESSAGE_FLAGS_NONE, |
| NULL, |
| &error)) |
| { |
| /* If we could get G_IO_ERROR_CLOSED here, it wouldn't be reasonable to |
| * critical; but we're holding the lock, and our caller checked whether |
| * we were already closed, so we can't get that error. |
| */ |
| g_critical ("Error while sending RemoveMatch() message: %s", error->message); |
| g_error_free (error); |
| } |
| g_object_unref (message); |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| static gboolean |
| is_signal_data_for_name_lost_or_acquired (SignalData *signal_data) |
| { |
| return g_strcmp0 (signal_data->sender_unique_name, "org.freedesktop.DBus") == 0 && |
| g_strcmp0 (signal_data->interface_name, "org.freedesktop.DBus") == 0 && |
| g_strcmp0 (signal_data->object_path, "/org/freedesktop/DBus") == 0 && |
| (g_strcmp0 (signal_data->member, "NameLost") == 0 || |
| g_strcmp0 (signal_data->member, "NameAcquired") == 0); |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| /** |
| * g_dbus_connection_signal_subscribe: |
| * @connection: a #GDBusConnection |
| * @sender: (nullable): sender name to match on (unique or well-known name) |
| * or %NULL to listen from all senders |
| * @interface_name: (nullable): D-Bus interface name to match on or %NULL to |
| * match on all interfaces |
| * @member: (nullable): D-Bus signal name to match on or %NULL to match on |
| * all signals |
| * @object_path: (nullable): object path to match on or %NULL to match on |
| * all object paths |
| * @arg0: (nullable): contents of first string argument to match on or %NULL |
| * to match on all kinds of arguments |
| * @flags: #GDBusSignalFlags describing how arg0 is used in subscribing to the |
| * signal |
| * @callback: callback to invoke when there is a signal matching the requested data |
| * @user_data: user data to pass to @callback |
| * @user_data_free_func: (nullable): function to free @user_data with when |
| * subscription is removed or %NULL |
| * |
| * Subscribes to signals on @connection and invokes @callback whenever |
| * the signal is received. Note that @callback will be invoked in the |
| * [thread-default main context][g-main-context-push-thread-default] |
| * of the thread you are calling this method from. |
| * |
| * If @connection is not a message bus connection, @sender must be |
| * %NULL. |
| * |
| * If @sender is a well-known name note that @callback is invoked with |
| * the unique name for the owner of @sender, not the well-known name |
| * as one would expect. This is because the message bus rewrites the |
| * name. As such, to avoid certain race conditions, users should be |
| * tracking the name owner of the well-known name and use that when |
| * processing the received signal. |
| * |
| * If one of %G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_NAMESPACE or |
| * %G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_PATH are given, @arg0 is |
| * interpreted as part of a namespace or path. The first argument |
| * of a signal is matched against that part as specified by D-Bus. |
| * |
| * If @user_data_free_func is non-%NULL, it will be called (in the |
| * thread-default main context of the thread you are calling this |
| * method from) at some point after @user_data is no longer |
| * needed. (It is not guaranteed to be called synchronously when the |
| * signal is unsubscribed from, and may be called after @connection |
| * has been destroyed.) |
| * |
| * As @callback is potentially invoked in a different thread from where it’s |
| * emitted, it’s possible for this to happen after |
| * g_dbus_connection_signal_unsubscribe() has been called in another thread. |
| * Due to this, @user_data should have a strong reference which is freed with |
| * @user_data_free_func, rather than pointing to data whose lifecycle is tied |
| * to the signal subscription. For example, if a #GObject is used to store the |
| * subscription ID from g_dbus_connection_signal_subscribe(), a strong reference |
| * to that #GObject must be passed to @user_data, and g_object_unref() passed to |
| * @user_data_free_func. You are responsible for breaking the resulting |
| * reference count cycle by explicitly unsubscribing from the signal when |
| * dropping the last external reference to the #GObject. Alternatively, a weak |
| * reference may be used. |
| * |
| * It is guaranteed that if you unsubscribe from a signal using |
| * g_dbus_connection_signal_unsubscribe() from the same thread which made the |
| * corresponding g_dbus_connection_signal_subscribe() call, @callback will not |
| * be invoked after g_dbus_connection_signal_unsubscribe() returns. |
| * |
| * The returned subscription identifier is an opaque value which is guaranteed |
| * to never be zero. |
| * |
| * This function can never fail. |
| * |
| * Returns: a subscription identifier that can be used with g_dbus_connection_signal_unsubscribe() |
| * |
| * Since: 2.26 |
| */ |
| guint |
| g_dbus_connection_signal_subscribe (GDBusConnection *connection, |
| const gchar *sender, |
| const gchar *interface_name, |
| const gchar *member, |
| const gchar *object_path, |
| const gchar *arg0, |
| GDBusSignalFlags flags, |
| GDBusSignalCallback callback, |
| gpointer user_data, |
| GDestroyNotify user_data_free_func) |
| { |
| gchar *rule; |
| SignalData *signal_data; |
| SignalSubscriber *subscriber; |
| GPtrArray *signal_data_array; |
| const gchar *sender_unique_name; |
| |
| /* Right now we abort if AddMatch() fails since it can only fail with the bus being in |
| * an OOM condition. We might want to change that but that would involve making |
| * g_dbus_connection_signal_subscribe() asynchronous and having the call sites |
| * handle that. And there's really no sensible way of handling this short of retrying |
| * to add the match rule... and then there's the little thing that, hey, maybe there's |
| * a reason the bus in an OOM condition. |
| * |
| * Doable, but not really sure it's worth it... |
| */ |
| |
| g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), 0); |
| g_return_val_if_fail (sender == NULL || (g_dbus_is_name (sender) && (connection->flags & G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION)), 0); |
| g_return_val_if_fail (interface_name == NULL || g_dbus_is_interface_name (interface_name), 0); |
| g_return_val_if_fail (member == NULL || g_dbus_is_member_name (member), 0); |
| g_return_val_if_fail (object_path == NULL || g_variant_is_object_path (object_path), 0); |
| g_return_val_if_fail (callback != NULL, 0); |
| g_return_val_if_fail (check_initialized (connection), 0); |
| g_return_val_if_fail (!((flags & G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_PATH) && (flags & G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_NAMESPACE)), 0); |
| g_return_val_if_fail (!(arg0 == NULL && (flags & (G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_PATH | G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_NAMESPACE))), 0); |
| |
| CONNECTION_LOCK (connection); |
| |
| /* If G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE was specified, we will end up |
| * with a '-' character to prefix the rule (which will otherwise be |
| * normal). |
| * |
| * This allows us to hash the rule and do our lifecycle tracking in |
| * the usual way, but the '-' prevents the match rule from ever |
| * actually being send to the bus (either for add or remove). |
| */ |
| rule = args_to_rule (sender, interface_name, member, object_path, arg0, flags); |
| |
| if (sender != NULL && (g_dbus_is_unique_name (sender) || g_strcmp0 (sender, "org.freedesktop.DBus") == 0)) |
| sender_unique_name = sender; |
| else |
| sender_unique_name = ""; |
| |
| subscriber = g_new0 (SignalSubscriber, 1); |
| subscriber->ref_count = 1; |
| subscriber->callback = callback; |
| subscriber->user_data = user_data; |
| subscriber->user_data_free_func = user_data_free_func; |
| subscriber->id = (guint) g_atomic_int_add (&_global_subscriber_id, 1); /* TODO: overflow etc. */ |
| subscriber->context = g_main_context_ref_thread_default (); |
| |
| /* see if we've already have this rule */ |
| signal_data = g_hash_table_lookup (connection->map_rule_to_signal_data, rule); |
| if (signal_data != NULL) |
| { |
| g_ptr_array_add (signal_data->subscribers, subscriber); |
| g_free (rule); |
| goto out; |
| } |
| |
| signal_data = g_new0 (SignalData, 1); |
| signal_data->rule = rule; |
| signal_data->sender = g_strdup (sender); |
| signal_data->sender_unique_name = g_strdup (sender_unique_name); |
| signal_data->interface_name = g_strdup (interface_name); |
| signal_data->member = g_strdup (member); |
| signal_data->object_path = g_strdup (object_path); |
| signal_data->arg0 = g_strdup (arg0); |
| signal_data->flags = flags; |
| signal_data->subscribers = g_ptr_array_new_with_free_func ((GDestroyNotify) signal_subscriber_unref); |
| g_ptr_array_add (signal_data->subscribers, subscriber); |
| |
| g_hash_table_insert (connection->map_rule_to_signal_data, |
| signal_data->rule, |
| signal_data); |
| |
| /* Add the match rule to the bus... |
| * |
| * Avoid adding match rules for NameLost and NameAcquired messages - the bus will |
| * always send such messages to us. |
| */ |
| if (connection->flags & G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION) |
| { |
| if (!is_signal_data_for_name_lost_or_acquired (signal_data)) |
| add_match_rule (connection, signal_data->rule); |
| } |
| |
| signal_data_array = g_hash_table_lookup (connection->map_sender_unique_name_to_signal_data_array, |
| signal_data->sender_unique_name); |
| if (signal_data_array == NULL) |
| { |
| signal_data_array = g_ptr_array_new (); |
| g_hash_table_insert (connection->map_sender_unique_name_to_signal_data_array, |
| g_strdup (signal_data->sender_unique_name), |
| signal_data_array); |
| } |
| g_ptr_array_add (signal_data_array, signal_data); |
| |
| out: |
| g_hash_table_insert (connection->map_id_to_signal_data, |
| GUINT_TO_POINTER (subscriber->id), |
| signal_data); |
| |
| CONNECTION_UNLOCK (connection); |
| |
| return subscriber->id; |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| /* called in any thread */ |
| /* must hold lock when calling this (except if connection->finalizing is TRUE) |
| * returns the number of removed subscribers */ |
| static guint |
| unsubscribe_id_internal (GDBusConnection *connection, |
| guint subscription_id) |
| { |
| SignalData *signal_data; |
| GPtrArray *signal_data_array; |
| guint n; |
| guint n_removed = 0; |
| |
| signal_data = g_hash_table_lookup (connection->map_id_to_signal_data, |
| GUINT_TO_POINTER (subscription_id)); |
| if (signal_data == NULL) |
| { |
| /* Don't warn here, we may have thrown all subscriptions out when the connection was closed */ |
| goto out; |
| } |
| |
| for (n = 0; n < signal_data->subscribers->len; n++) |
| { |
| SignalSubscriber *subscriber = signal_data->subscribers->pdata[n]; |
| |
| if (subscriber->id != subscription_id) |
| continue; |
| |
| /* It’s OK to rearrange the array order using the ‘fast’ #GPtrArray |
| * removal functions, since we’re going to exit the loop below anyway — we |
| * never move on to the next element. Secondly, subscription IDs are |
| * guaranteed to be unique. */ |
| g_warn_if_fail (g_hash_table_remove (connection->map_id_to_signal_data, |
| GUINT_TO_POINTER (subscription_id))); |
| n_removed++; |
| g_ptr_array_remove_index_fast (signal_data->subscribers, n); |
| |
| if (signal_data->subscribers->len == 0) |
| { |
| g_warn_if_fail (g_hash_table_remove (connection->map_rule_to_signal_data, signal_data->rule)); |
| |
| signal_data_array = g_hash_table_lookup (connection->map_sender_unique_name_to_signal_data_array, |
| signal_data->sender_unique_name); |
| g_warn_if_fail (signal_data_array != NULL); |
| g_warn_if_fail (g_ptr_array_remove (signal_data_array, signal_data)); |
| |
| if (signal_data_array->len == 0) |
| { |
| g_warn_if_fail (g_hash_table_remove (connection->map_sender_unique_name_to_signal_data_array, |
| signal_data->sender_unique_name)); |
| } |
| |
| /* remove the match rule from the bus unless NameLost or NameAcquired (see subscribe()) */ |
| if ((connection->flags & G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION) && |
| !is_signal_data_for_name_lost_or_acquired (signal_data) && |
| !g_dbus_connection_is_closed (connection) && |
| !connection->finalizing) |
| { |
| /* The check for g_dbus_connection_is_closed() means that |
| * sending the RemoveMatch message can't fail with |
| * G_IO_ERROR_CLOSED, because we're holding the lock, |
| * so on_worker_closed() can't happen between the check we just |
| * did, and releasing the lock later. |
| */ |
| remove_match_rule (connection, signal_data->rule); |
| } |
| |
| signal_data_free (signal_data); |
| } |
| |
| goto out; |
| } |
| |
| g_assert_not_reached (); |
| |
| out: |
| return n_removed; |
| } |
| |
| /** |
| * g_dbus_connection_signal_unsubscribe: |
| * @connection: a #GDBusConnection |
| * @subscription_id: a subscription id obtained from |
| * g_dbus_connection_signal_subscribe() |
| * |
| * Unsubscribes from signals. |
| * |
| * Note that there may still be D-Bus traffic to process (relating to this |
| * signal subscription) in the current thread-default #GMainContext after this |
| * function has returned. You should continue to iterate the #GMainContext |
| * until the #GDestroyNotify function passed to |
| * g_dbus_connection_signal_subscribe() is called, in order to avoid memory |
| * leaks through callbacks queued on the #GMainContext after it’s stopped being |
| * iterated. |
| * Alternatively, any idle source with a priority lower than %G_PRIORITY_DEFAULT |
| * that was scheduled after unsubscription, also indicates that all resources |
| * of this subscription are released. |
| * |
| * Since: 2.26 |
| */ |
| void |
| g_dbus_connection_signal_unsubscribe (GDBusConnection *connection, |
| guint subscription_id) |
| { |
| guint n_subscribers_removed G_GNUC_UNUSED /* when compiling with G_DISABLE_ASSERT */; |
| |
| g_return_if_fail (G_IS_DBUS_CONNECTION (connection)); |
| g_return_if_fail (check_initialized (connection)); |
| |
| CONNECTION_LOCK (connection); |
| n_subscribers_removed = unsubscribe_id_internal (connection, subscription_id); |
| CONNECTION_UNLOCK (connection); |
| |
| /* invariant */ |
| g_assert (n_subscribers_removed == 0 || n_subscribers_removed == 1); |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| typedef struct |
| { |
| SignalSubscriber *subscriber; /* (owned) */ |
| GDBusMessage *message; /* (owned) */ |
| GDBusConnection *connection; |
| const gchar *sender; /* (nullable) for peer-to-peer connections */ |
| const gchar *path; |
| const gchar *interface; |
| const gchar *member; |
| } SignalInstance; |
| |
| /* called on delivery thread (e.g. where g_dbus_connection_signal_subscribe() was called) with |
| * no locks held |
| */ |
| static gboolean |
| emit_signal_instance_in_idle_cb (gpointer data) |
| { |
| SignalInstance *signal_instance = data; |
| GVariant *parameters; |
| gboolean has_subscription; |
| |
| parameters = g_dbus_message_get_body (signal_instance->message); |
| if (parameters == NULL) |
| { |
| parameters = g_variant_new ("()"); |
| g_variant_ref_sink (parameters); |
| } |
| else |
| { |
| g_variant_ref_sink (parameters); |
| } |
| |
| #if 0 |
| g_print ("in emit_signal_instance_in_idle_cb (id=%d sender=%s path=%s interface=%s member=%s params=%s)\n", |
| signal_instance->subscriber->id, |
| signal_instance->sender, |
| signal_instance->path, |
| signal_instance->interface, |
| signal_instance->member, |
| g_variant_print (parameters, TRUE)); |
| #endif |
| |
| /* Careful here, don't do the callback if we no longer has the subscription */ |
| CONNECTION_LOCK (signal_instance->connection); |
| has_subscription = FALSE; |
| if (g_hash_table_lookup (signal_instance->connection->map_id_to_signal_data, |
| GUINT_TO_POINTER (signal_instance->subscriber->id)) != NULL) |
| has_subscription = TRUE; |
| CONNECTION_UNLOCK (signal_instance->connection); |
| |
| if (has_subscription) |
| signal_instance->subscriber->callback (signal_instance->connection, |
| signal_instance->sender, |
| signal_instance->path, |
| signal_instance->interface, |
| signal_instance->member, |
| parameters, |
| signal_instance->subscriber->user_data); |
| |
| g_variant_unref (parameters); |
| |
| return FALSE; |
| } |
| |
| static void |
| signal_instance_free (SignalInstance *signal_instance) |
| { |
| g_clear_object (&signal_instance->message); |
| g_object_unref (signal_instance->connection); |
| signal_subscriber_unref (signal_instance->subscriber); |
| g_free (signal_instance); |
| } |
| |
| static gboolean |
| namespace_rule_matches (const gchar *namespace, |
| const gchar *name) |
| { |
| gint len_namespace; |
| gint len_name; |
| |
| len_namespace = strlen (namespace); |
| len_name = strlen (name); |
| |
| if (len_name < len_namespace) |
| return FALSE; |
| |
| if (memcmp (namespace, name, len_namespace) != 0) |
| return FALSE; |
| |
| return len_namespace == len_name || name[len_namespace] == '.'; |
| } |
| |
| static gboolean |
| path_rule_matches (const gchar *path_a, |
| const gchar *path_b) |
| { |
| gint len_a, len_b; |
| |
| len_a = strlen (path_a); |
| len_b = strlen (path_b); |
| |
| if (len_a < len_b && (len_a == 0 || path_a[len_a - 1] != '/')) |
| return FALSE; |
| |
| if (len_b < len_a && (len_b == 0 || path_b[len_b - 1] != '/')) |
| return FALSE; |
| |
| return memcmp (path_a, path_b, MIN (len_a, len_b)) == 0; |
| } |
| |
| /* called in GDBusWorker thread WITH lock held |
| * |
| * @sender is (nullable) for peer-to-peer connections */ |
| static void |
| schedule_callbacks (GDBusConnection *connection, |
| GPtrArray *signal_data_array, |
| GDBusMessage *message, |
| const gchar *sender) |
| { |
| guint n, m; |
| const gchar *interface; |
| const gchar *member; |
| const gchar *path; |
| const gchar *arg0; |
| const gchar *arg0_path; |
| |
| interface = NULL; |
| member = NULL; |
| path = NULL; |
| arg0 = NULL; |
| arg0_path = NULL; |
| |
| interface = g_dbus_message_get_interface (message); |
| member = g_dbus_message_get_member (message); |
| path = g_dbus_message_get_path (message); |
| arg0 = g_dbus_message_get_arg0 (message); |
| arg0_path = g_dbus_message_get_arg0_path (message); |
| |
| /* These two are mutually exclusive through the type system. */ |
| g_assert (arg0 == NULL || arg0_path == NULL); |
| |
| #if 0 |
| g_print ("In schedule_callbacks:\n" |
| " sender = '%s'\n" |
| " interface = '%s'\n" |
| " member = '%s'\n" |
| " path = '%s'\n" |
| " arg0 = '%s'\n", |
| sender, |
| interface, |
| member, |
| path, |
| arg0); |
| #endif |
| |
| /* TODO: if this is slow, then we can change signal_data_array into |
| * map_object_path_to_signal_data_array or something. |
| */ |
| for (n = 0; n < signal_data_array->len; n++) |
| { |
| SignalData *signal_data = signal_data_array->pdata[n]; |
| |
| if (signal_data->interface_name != NULL && g_strcmp0 (signal_data->interface_name, interface) != 0) |
| continue; |
| |
| if (signal_data->member != NULL && g_strcmp0 (signal_data->member, member) != 0) |
| continue; |
| |
| if (signal_data->object_path != NULL && g_strcmp0 (signal_data->object_path, path) != 0) |
| continue; |
| |
| if (signal_data->arg0 != NULL) |
| { |
| if (signal_data->flags & G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_NAMESPACE) |
| { |
| if (arg0 == NULL || !namespace_rule_matches (signal_data->arg0, arg0)) |
| continue; |
| } |
| else if (signal_data->flags & G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_PATH) |
| { |
| if ((arg0 == NULL || !path_rule_matches (signal_data->arg0, arg0)) && |
| (arg0_path == NULL || !path_rule_matches (signal_data->arg0, arg0_path))) |
| continue; |
| } |
| else if (!g_str_equal (signal_data->arg0, arg0)) |
| continue; |
| } |
| |
| for (m = 0; m < signal_data->subscribers->len; m++) |
| { |
| SignalSubscriber *subscriber = signal_data->subscribers->pdata[m]; |
| GSource *idle_source; |
| SignalInstance *signal_instance; |
| |
| signal_instance = g_new0 (SignalInstance, 1); |
| signal_instance->subscriber = signal_subscriber_ref (subscriber); |
| signal_instance->message = g_object_ref (message); |
| signal_instance->connection = g_object_ref (connection); |
| signal_instance->sender = sender; |
| signal_instance->path = path; |
| signal_instance->interface = interface; |
| signal_instance->member = member; |
| |
| idle_source = g_idle_source_new (); |
| g_source_set_priority (idle_source, G_PRIORITY_DEFAULT); |
| g_source_set_callback (idle_source, |
| emit_signal_instance_in_idle_cb, |
| signal_instance, |
| (GDestroyNotify) signal_instance_free); |
| g_source_set_static_name (idle_source, "[gio] emit_signal_instance_in_idle_cb"); |
| g_source_attach (idle_source, subscriber->context); |
| g_source_unref (idle_source); |
| } |
| } |
| } |
| |
| /* called in GDBusWorker thread with lock held */ |
| static void |
| distribute_signals (GDBusConnection *connection, |
| GDBusMessage *message) |
| { |
| GPtrArray *signal_data_array; |
| const gchar *sender, *interface, *member, *path; |
| |
| g_assert (g_dbus_message_get_message_type (message) == G_DBUS_MESSAGE_TYPE_SIGNAL); |
| |
| sender = g_dbus_message_get_sender (message); |
| |
| /* all three of these are required, but should have been validated already |
| * by validate_headers() in gdbusmessage.c */ |
| interface = g_dbus_message_get_interface (message); |
| member = g_dbus_message_get_member (message); |
| path = g_dbus_message_get_path (message); |
| |
| g_assert (interface != NULL); |
| g_assert (member != NULL); |
| g_assert (path != NULL); |
| |
| if (G_UNLIKELY (_g_dbus_debug_signal ())) |
| { |
| _g_dbus_debug_print_lock (); |
| g_print ("========================================================================\n" |
| "GDBus-debug:Signal:\n" |
| " <<<< RECEIVED SIGNAL %s.%s\n" |
| " on object %s\n" |
| " sent by name %s\n", |
| interface, member, path, |
| sender != NULL ? sender : "(none)"); |
| _g_dbus_debug_print_unlock (); |
| } |
| |
| /* collect subscribers that match on sender */ |
| if (sender != NULL) |
| { |
| signal_data_array = g_hash_table_lookup (connection->map_sender_unique_name_to_signal_data_array, sender); |
| if (signal_data_array != NULL) |
| schedule_callbacks (connection, signal_data_array, message, sender); |
| } |
| |
| /* collect subscribers not matching on sender */ |
| signal_data_array = g_hash_table_lookup (connection->map_sender_unique_name_to_signal_data_array, ""); |
| if (signal_data_array != NULL) |
| schedule_callbacks (connection, signal_data_array, message, sender); |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| /* only called from finalize(), removes all subscriptions */ |
| static void |
| purge_all_signal_subscriptions (GDBusConnection *connection) |
| { |
| GHashTableIter iter; |
| gpointer key; |
| GArray *ids; |
| guint n; |
| |
| ids = g_array_new (FALSE, FALSE, sizeof (guint)); |
| g_hash_table_iter_init (&iter, connection->map_id_to_signal_data); |
| while (g_hash_table_iter_next (&iter, &key, NULL)) |
| { |
| guint subscription_id = GPOINTER_TO_UINT (key); |
| g_array_append_val (ids, subscription_id); |
| } |
| |
| for (n = 0; n < ids->len; n++) |
| { |
| guint subscription_id = g_array_index (ids, guint, n); |
| unsubscribe_id_internal (connection, subscription_id); |
| } |
| g_array_free (ids, TRUE); |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| static GDBusInterfaceVTable * |
| _g_dbus_interface_vtable_copy (const GDBusInterfaceVTable *vtable) |
| { |
| /* Don't waste memory by copying padding - remember to update this |
| * when changing struct _GDBusInterfaceVTable in gdbusconnection.h |
| */ |
| return g_memdup2 ((gconstpointer) vtable, 3 * sizeof (gpointer)); |
| } |
| |
| static void |
| _g_dbus_interface_vtable_free (GDBusInterfaceVTable *vtable) |
| { |
| g_free (vtable); |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| static GDBusSubtreeVTable * |
| _g_dbus_subtree_vtable_copy (const GDBusSubtreeVTable *vtable) |
| { |
| /* Don't waste memory by copying padding - remember to update this |
| * when changing struct _GDBusSubtreeVTable in gdbusconnection.h |
| */ |
| return g_memdup2 ((gconstpointer) vtable, 3 * sizeof (gpointer)); |
| } |
| |
| static void |
| _g_dbus_subtree_vtable_free (GDBusSubtreeVTable *vtable) |
| { |
| g_free (vtable); |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| struct ExportedObject |
| { |
| gchar *object_path; |
| GDBusConnection *connection; |
| |
| /* maps gchar* -> ExportedInterface* */ |
| GHashTable *map_if_name_to_ei; |
| }; |
| |
| /* only called with lock held */ |
| static void |
| exported_object_free (ExportedObject *eo) |
| { |
| g_free (eo->object_path); |
| g_hash_table_unref (eo->map_if_name_to_ei); |
| g_free (eo); |
| } |
| |
| typedef struct |
| { |
| ExportedObject *eo; |
| |
| gint refcount; /* (atomic) */ |
| |
| guint id; |
| gchar *interface_name; /* (owned) */ |
| GDBusInterfaceVTable *vtable; /* (owned) */ |
| GDBusInterfaceInfo *interface_info; /* (owned) */ |
| |
| GMainContext *context; /* (owned) */ |
| gpointer user_data; |
| GDestroyNotify user_data_free_func; |
| } ExportedInterface; |
| |
| static ExportedInterface * |
| exported_interface_ref (ExportedInterface *ei) |
| { |
| g_atomic_int_inc (&ei->refcount); |
| |
| return ei; |
| } |
| |
| /* May be called with lock held */ |
| static void |
| exported_interface_unref (ExportedInterface *ei) |
| { |
| if (!g_atomic_int_dec_and_test (&ei->refcount)) |
| return; |
| |
| g_dbus_interface_info_cache_release (ei->interface_info); |
| g_dbus_interface_info_unref ((GDBusInterfaceInfo *) ei->interface_info); |
| |
| /* All uses of ei->vtable from callbacks scheduled in idle functions must |
| * have completed by this call_destroy_notify() call, as language bindings |
| * may destroy function closures in this callback. */ |
| call_destroy_notify (ei->context, |
| ei->user_data_free_func, |
| ei->user_data); |
| |
| g_main_context_unref (ei->context); |
| |
| g_free (ei->interface_name); |
| _g_dbus_interface_vtable_free (ei->vtable); |
| g_free (ei); |
| } |
| |
| struct ExportedSubtree |
| { |
| gint refcount; /* (atomic) */ |
| |
| guint id; |
| gchar *object_path; /* (owned) */ |
| GDBusConnection *connection; /* (unowned) */ |
| GDBusSubtreeVTable *vtable; /* (owned) */ |
| GDBusSubtreeFlags flags; |
| |
| GMainContext *context; /* (owned) */ |
| gpointer user_data; |
| GDestroyNotify user_data_free_func; |
| }; |
| |
| static ExportedSubtree * |
| exported_subtree_ref (ExportedSubtree *es) |
| { |
| g_atomic_int_inc (&es->refcount); |
| |
| return es; |
| } |
| |
| /* May be called with lock held */ |
| static void |
| exported_subtree_unref (ExportedSubtree *es) |
| { |
| if (!g_atomic_int_dec_and_test (&es->refcount)) |
| return; |
| |
| /* All uses of es->vtable from callbacks scheduled in idle functions must |
| * have completed by this call_destroy_notify() call, as language bindings |
| * may destroy function closures in this callback. */ |
| call_destroy_notify (es->context, |
| es->user_data_free_func, |
| es->user_data); |
| |
| g_main_context_unref (es->context); |
| |
| _g_dbus_subtree_vtable_free (es->vtable); |
| g_free (es->object_path); |
| g_free (es); |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| /* Convenience function to check if @registration_id (if not zero) or |
| * @subtree_registration_id (if not zero) has been unregistered. If |
| * so, returns %TRUE. |
| * |
| * If not, sets @out_ei and/or @out_es to a strong reference to the relevant |
| * #ExportedInterface/#ExportedSubtree and returns %FALSE. |
| * |
| * May be called by any thread. Caller must *not* hold lock. |
| */ |
| static gboolean |
| has_object_been_unregistered (GDBusConnection *connection, |
| guint registration_id, |
| ExportedInterface **out_ei, |
| guint subtree_registration_id, |
| ExportedSubtree **out_es) |
| { |
| gboolean ret; |
| ExportedInterface *ei = NULL; |
| gpointer es = NULL; |
| |
| g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE); |
| |
| ret = FALSE; |
| |
| CONNECTION_LOCK (connection); |
| |
| if (registration_id != 0) |
| { |
| ei = g_hash_table_lookup (connection->map_id_to_ei, GUINT_TO_POINTER (registration_id)); |
| if (ei == NULL) |
| ret = TRUE; |
| else if (out_ei != NULL) |
| *out_ei = exported_interface_ref (ei); |
| } |
| if (subtree_registration_id != 0) |
| { |
| es = g_hash_table_lookup (connection->map_id_to_es, GUINT_TO_POINTER (subtree_registration_id)); |
| if (es == NULL) |
| ret = TRUE; |
| else if (out_es != NULL) |
| *out_es = exported_subtree_ref (es); |
| } |
| |
| CONNECTION_UNLOCK (connection); |
| |
| return ret; |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| typedef struct |
| { |
| GDBusConnection *connection; |
| GDBusMessage *message; /* (owned) */ |
| gpointer user_data; |
| const gchar *property_name; |
| const GDBusInterfaceVTable *vtable; |
| GDBusInterfaceInfo *interface_info; |
| const GDBusPropertyInfo *property_info; |
| guint registration_id; |
| guint subtree_registration_id; |
| } PropertyData; |
| |
| static void |
| property_data_free (PropertyData *data) |
| { |
| g_object_unref (data->connection); |
| g_clear_object (&data->message); |
| g_free (data); |
| } |
| |
| /* called in thread where object was registered - no locks held */ |
| static gboolean |
| invoke_get_property_in_idle_cb (gpointer _data) |
| { |
| PropertyData *data = _data; |
| GVariant *value; |
| GError *error; |
| GDBusMessage *reply; |
| ExportedInterface *ei = NULL; |
| ExportedSubtree *es = NULL; |
| |
| if (has_object_been_unregistered (data->connection, |
| data->registration_id, |
| &ei, |
| data->subtree_registration_id, |
| &es)) |
| { |
| reply = g_dbus_message_new_method_error (data->message, |
| "org.freedesktop.DBus.Error.UnknownMethod", |
| _("No such interface “org.freedesktop.DBus.Properties” on object at path %s"), |
| g_dbus_message_get_path (data->message)); |
| g_dbus_connection_send_message (data->connection, reply, G_DBUS_SEND_MESSAGE_FLAGS_NONE, NULL, NULL); |
| g_object_unref (reply); |
| goto out; |
| } |
| |
| error = NULL; |
| value = data->vtable->get_property (data->connection, |
| g_dbus_message_get_sender (data->message), |
| g_dbus_message_get_path (data->message), |
| data->interface_info->name, |
| data->property_name, |
| &error, |
| data->user_data); |
| |
| |
| if (value != NULL) |
| { |
| g_assert_no_error (error); |
| |
| g_variant_take_ref (value); |
| reply = g_dbus_message_new_method_reply (data->message); |
| g_dbus_message_set_body (reply, g_variant_new ("(v)", value)); |
| g_dbus_connection_send_message (data->connection, reply, G_DBUS_SEND_MESSAGE_FLAGS_NONE, NULL, NULL); |
| g_variant_unref (value); |
| g_object_unref (reply); |
| } |
| else |
| { |
| gchar *dbus_error_name; |
| g_assert (error != NULL); |
| dbus_error_name = g_dbus_error_encode_gerror (error); |
| reply = g_dbus_message_new_method_error_literal (data->message, |
| dbus_error_name, |
| error->message); |
| g_dbus_connection_send_message (data->connection, reply, G_DBUS_SEND_MESSAGE_FLAGS_NONE, NULL, NULL); |
| g_free (dbus_error_name); |
| g_error_free (error); |
| g_object_unref (reply); |
| } |
| |
| out: |
| g_clear_pointer (&ei, exported_interface_unref); |
| g_clear_pointer (&es, exported_subtree_unref); |
| |
| return FALSE; |
| } |
| |
| /* called in thread where object was registered - no locks held */ |
| static gboolean |
| invoke_set_property_in_idle_cb (gpointer _data) |
| { |
| PropertyData *data = _data; |
| GError *error; |
| GDBusMessage *reply; |
| GVariant *value; |
| |
| error = NULL; |
| value = NULL; |
| |
| g_variant_get (g_dbus_message_get_body (data->message), |
| "(ssv)", |
| NULL, |
| NULL, |
| &value); |
| |
| if (!data->vtable->set_property (data->connection, |
| g_dbus_message_get_sender (data->message), |
| g_dbus_message_get_path (data->message), |
| data->interface_info->name, |
| data->property_name, |
| value, |
| &error, |
| data->user_data)) |
| { |
| gchar *dbus_error_name; |
| g_assert (error != NULL); |
| dbus_error_name = g_dbus_error_encode_gerror (error); |
| reply = g_dbus_message_new_method_error_literal (data->message, |
| dbus_error_name, |
| error->message); |
| g_free (dbus_error_name); |
| g_error_free (error); |
| } |
| else |
| { |
| reply = g_dbus_message_new_method_reply (data->message); |
| } |
| |
| g_assert (reply != NULL); |
| g_dbus_connection_send_message (data->connection, reply, G_DBUS_SEND_MESSAGE_FLAGS_NONE, NULL, NULL); |
| g_object_unref (reply); |
| g_variant_unref (value); |
| |
| return FALSE; |
| } |
| |
| /* called in any thread with connection's lock held */ |
| static gboolean |
| validate_and_maybe_schedule_property_getset (GDBusConnection *connection, |
| GDBusMessage *message, |
| guint registration_id, |
| guint subtree_registration_id, |
| gboolean is_get, |
| GDBusInterfaceInfo *interface_info, |
| const GDBusInterfaceVTable *vtable, |
| GMainContext *main_context, |
| gpointer user_data) |
| { |
| gboolean handled; |
| const char *interface_name; |
| const char *property_name; |
| const GDBusPropertyInfo *property_info; |
| GSource *idle_source; |
| PropertyData *property_data; |
| GDBusMessage *reply; |
| |
| handled = FALSE; |
| |
| if (is_get) |
| g_variant_get (g_dbus_message_get_body (message), |
| "(&s&s)", |
| &interface_name, |
| &property_name); |
| else |
| g_variant_get (g_dbus_message_get_body (message), |
| "(&s&sv)", |
| &interface_name, |
| &property_name, |
| NULL); |
| |
| if (vtable == NULL) |
| goto out; |
| |
| /* Check that the property exists - if not fail with org.freedesktop.DBus.Error.InvalidArgs |
| */ |
| property_info = NULL; |
| |
| /* TODO: the cost of this is O(n) - it might be worth caching the result */ |
| property_info = g_dbus_interface_info_lookup_property (interface_info, property_name); |
| if (property_info == NULL) |
| { |
| reply = g_dbus_message_new_method_error (message, |
| "org.freedesktop.DBus.Error.InvalidArgs", |
| _("No such property “%s”"), |
| property_name); |
| g_dbus_connection_send_message_unlocked (connection, reply, G_DBUS_SEND_MESSAGE_FLAGS_NONE, NULL, NULL); |
| g_object_unref (reply); |
| handled = TRUE; |
| goto out; |
| } |
| |
| if (is_get && !(property_info->flags & G_DBUS_PROPERTY_INFO_FLAGS_READABLE)) |
| { |
| reply = g_dbus_message_new_method_error (message, |
| "org.freedesktop.DBus.Error.InvalidArgs", |
| _("Property “%s” is not readable"), |
| property_name); |
| g_dbus_connection_send_message_unlocked (connection, reply, G_DBUS_SEND_MESSAGE_FLAGS_NONE, NULL, NULL); |
| g_object_unref (reply); |
| handled = TRUE; |
| goto out; |
| } |
| else if (!is_get && !(property_info->flags & G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE)) |
| { |
| reply = g_dbus_message_new_method_error (message, |
| "org.freedesktop.DBus.Error.InvalidArgs", |
| _("Property “%s” is not writable"), |
| property_name); |
| g_dbus_connection_send_message_unlocked (connection, reply, G_DBUS_SEND_MESSAGE_FLAGS_NONE, NULL, NULL); |
| g_object_unref (reply); |
| handled = TRUE; |
| goto out; |
| } |
| |
| if (!is_get) |
| { |
| GVariant *value; |
| |
| /* Fail with org.freedesktop.DBus.Error.InvalidArgs if the type |
| * of the given value is wrong |
| */ |
| g_variant_get_child (g_dbus_message_get_body (message), 2, "v", &value); |
| if (g_strcmp0 (g_variant_get_type_string (value), property_info->signature) != 0) |
| { |
| reply = g_dbus_message_new_method_error (message, |
| "org.freedesktop.DBus.Error.InvalidArgs", |
| _("Error setting property “%s”: Expected type “%s” but got “%s”"), |
| property_name, property_info->signature, |
| g_variant_get_type_string (value)); |
| g_dbus_connection_send_message_unlocked (connection, reply, G_DBUS_SEND_MESSAGE_FLAGS_NONE, NULL, NULL); |
| g_variant_unref (value); |
| g_object_unref (reply); |
| handled = TRUE; |
| goto out; |
| } |
| |
| g_variant_unref (value); |
| } |
| |
| /* If the vtable pointer for get_property() resp. set_property() is |
| * NULL then dispatch the call via the method_call() handler. |
| */ |
| if (is_get) |
| { |
| if (vtable->get_property == NULL) |
| { |
| schedule_method_call (connection, message, registration_id, subtree_registration_id, |
| interface_info, NULL, property_info, g_dbus_message_get_body (message), |
| vtable, main_context, user_data); |
| handled = TRUE; |
| goto out; |
| } |
| } |
| else |
| { |
| if (vtable->set_property == NULL) |
| { |
| schedule_method_call (connection, message, registration_id, subtree_registration_id, |
| interface_info, NULL, property_info, g_dbus_message_get_body (message), |
| vtable, main_context, user_data); |
| handled = TRUE; |
| goto out; |
| } |
| } |
| |
| /* ok, got the property info - call user code in an idle handler */ |
| property_data = g_new0 (PropertyData, 1); |
| property_data->connection = g_object_ref (connection); |
| property_data->message = g_object_ref (message); |
| property_data->user_data = user_data; |
| property_data->property_name = property_name; |
| property_data->vtable = vtable; |
| property_data->interface_info = interface_info; |
| property_data->property_info = property_info; |
| property_data->registration_id = registration_id; |
| property_data->subtree_registration_id = subtree_registration_id; |
| |
| idle_source = g_idle_source_new (); |
| g_source_set_priority (idle_source, G_PRIORITY_DEFAULT); |
| g_source_set_callback (idle_source, |
| is_get ? invoke_get_property_in_idle_cb : invoke_set_property_in_idle_cb, |
| property_data, |
| (GDestroyNotify) property_data_free); |
| if (is_get) |
| g_source_set_static_name (idle_source, "[gio] invoke_get_property_in_idle_cb"); |
| else |
| g_source_set_static_name (idle_source, "[gio] invoke_set_property_in_idle_cb"); |
| g_source_attach (idle_source, main_context); |
| g_source_unref (idle_source); |
| |
| handled = TRUE; |
| |
| out: |
| return handled; |
| } |
| |
| /* called in GDBusWorker thread with connection's lock held */ |
| static gboolean |
| handle_getset_property (GDBusConnection *connection, |
| ExportedObject *eo, |
| GDBusMessage *message, |
| gboolean is_get) |
| { |
| ExportedInterface *ei; |
| gboolean handled; |
| const char *interface_name; |
| const char *property_name; |
| |
| handled = FALSE; |
| |
| if (is_get) |
| g_variant_get (g_dbus_message_get_body (message), |
| "(&s&s)", |
| &interface_name, |
| &property_name); |
| else |
| g_variant_get (g_dbus_message_get_body (message), |
| "(&s&sv)", |
| &interface_name, |
| &property_name, |
| NULL); |
| |
| /* Fail with org.freedesktop.DBus.Error.InvalidArgs if there is |
| * no such interface registered |
| */ |
| ei = g_hash_table_lookup (eo->map_if_name_to_ei, interface_name); |
| if (ei == NULL) |
| { |
| GDBusMessage *reply; |
| reply = g_dbus_message_new_method_error (message, |
| "org.freedesktop.DBus.Error.InvalidArgs", |
| _("No such interface “%s”"), |
| interface_name); |
| g_dbus_connection_send_message_unlocked (eo->connection, reply, G_DBUS_SEND_MESSAGE_FLAGS_NONE, NULL, NULL); |
| g_object_unref (reply); |
| handled = TRUE; |
| goto out; |
| } |
| |
| handled = validate_and_maybe_schedule_property_getset (eo->connection, |
| message, |
| ei->id, |
| 0, |
| is_get, |
| ei->interface_info, |
| ei->vtable, |
| ei->context, |
| ei->user_data); |
| out: |
| return handled; |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| typedef struct |
| { |
| GDBusConnection *connection; |
| GDBusMessage *message; /* (owned) */ |
| gpointer user_data; |
| const GDBusInterfaceVTable *vtable; |
| GDBusInterfaceInfo *interface_info; |
| guint registration_id; |
| guint subtree_registration_id; |
| } PropertyGetAllData; |
| |
| static void |
| property_get_all_data_free (PropertyGetAllData *data) |
| { |
| g_object_unref (data->connection); |
| g_clear_object (&data->message); |
| g_free (data); |
| } |
| |
| /* called in thread where object was registered - no locks held */ |
| static gboolean |
| invoke_get_all_properties_in_idle_cb (gpointer _data) |
| { |
| PropertyGetAllData *data = _data; |
| GVariantBuilder builder; |
| GDBusMessage *reply; |
| guint n; |
| ExportedInterface *ei = NULL; |
| ExportedSubtree *es = NULL; |
| |
| if (has_object_been_unregistered (data->connection, |
| data->registration_id, |
| &ei, |
| data->subtree_registration_id, |
| &es)) |
| { |
| reply = g_dbus_message_new_method_error (data->message, |
| "org.freedesktop.DBus.Error.UnknownMethod", |
| _("No such interface “org.freedesktop.DBus.Properties” on object at path %s"), |
| g_dbus_message_get_path (data->message)); |
| g_dbus_connection_send_message (data->connection, reply, G_DBUS_SEND_MESSAGE_FLAGS_NONE, NULL, NULL); |
| g_object_unref (reply); |
| goto out; |
| } |
| |
| /* TODO: Right now we never fail this call - we just omit values if |
| * a get_property() call is failing. |
| * |
| * We could fail the whole call if just a single get_property() call |
| * returns an error. We need clarification in the D-Bus spec about this. |
| */ |
| g_variant_builder_init (&builder, G_VARIANT_TYPE ("(a{sv})")); |
| g_variant_builder_open (&builder, G_VARIANT_TYPE ("a{sv}")); |
| for (n = 0; data->interface_info->properties != NULL && data->interface_info->properties[n] != NULL; n++) |
| { |
| const GDBusPropertyInfo *property_info = data->interface_info->properties[n]; |
| GVariant *value; |
| |
| if (!(property_info->flags & G_DBUS_PROPERTY_INFO_FLAGS_READABLE)) |
| continue; |
| |
| value = data->vtable->get_property (data->connection, |
| g_dbus_message_get_sender (data->message), |
| g_dbus_message_get_path (data->message), |
| data->interface_info->name, |
| property_info->name, |
| NULL, |
| data->user_data); |
| |
| if (value == NULL) |
| continue; |
| |
| g_variant_take_ref (value); |
| g_variant_builder_add (&builder, |
| "{sv}", |
| property_info->name, |
| value); |
| g_variant_unref (value); |
| } |
| g_variant_builder_close (&builder); |
| |
| reply = g_dbus_message_new_method_reply (data->message); |
| g_dbus_message_set_body (reply, g_variant_builder_end (&builder)); |
| g_dbus_connection_send_message (data->connection, reply, G_DBUS_SEND_MESSAGE_FLAGS_NONE, NULL, NULL); |
| g_object_unref (reply); |
| |
| out: |
| g_clear_pointer (&ei, exported_interface_unref); |
| g_clear_pointer (&es, exported_subtree_unref); |
| |
| return FALSE; |
| } |
| |
| static gboolean |
| interface_has_readable_properties (GDBusInterfaceInfo *interface_info) |
| { |
| gint i; |
| |
| if (!interface_info->properties) |
| return FALSE; |
| |
| for (i = 0; interface_info->properties[i]; i++) |
| if (interface_info->properties[i]->flags & G_DBUS_PROPERTY_INFO_FLAGS_READABLE) |
| return TRUE; |
| |
| return FALSE; |
| } |
| |
| /* called in any thread with connection's lock held */ |
| static gboolean |
| validate_and_maybe_schedule_property_get_all (GDBusConnection *connection, |
| GDBusMessage *message, |
| guint registration_id, |
| guint subtree_registration_id, |
| GDBusInterfaceInfo *interface_info, |
| const GDBusInterfaceVTable *vtable, |
| GMainContext *main_context, |
| gpointer user_data) |
| { |
| gboolean handled; |
| GSource *idle_source; |
| PropertyGetAllData *property_get_all_data; |
| |
| handled = FALSE; |
| |
| if (vtable == NULL) |
| goto out; |
| |
| /* If the vtable pointer for get_property() is NULL but we have a |
| * non-zero number of readable properties, then dispatch the call via |
| * the method_call() handler. |
| */ |
| if (vtable->get_property == NULL && interface_has_readable_properties (interface_info)) |
| { |
| schedule_method_call (connection, message, registration_id, subtree_registration_id, |
| interface_info, NULL, NULL, g_dbus_message_get_body (message), |
| vtable, main_context, user_data); |
| handled = TRUE; |
| goto out; |
| } |
| |
| /* ok, got the property info - call user in an idle handler */ |
| property_get_all_data = g_new0 (PropertyGetAllData, 1); |
| property_get_all_data->connection = g_object_ref (connection); |
| property_get_all_data->message = g_object_ref (message); |
| property_get_all_data->user_data = user_data; |
| property_get_all_data->vtable = vtable; |
| property_get_all_data->interface_info = interface_info; |
| property_get_all_data->registration_id = registration_id; |
| property_get_all_data->subtree_registration_id = subtree_registration_id; |
| |
| idle_source = g_idle_source_new (); |
| g_source_set_priority (idle_source, G_PRIORITY_DEFAULT); |
| g_source_set_callback (idle_source, |
| invoke_get_all_properties_in_idle_cb, |
| property_get_all_data, |
| (GDestroyNotify) property_get_all_data_free); |
| g_source_set_static_name (idle_source, "[gio] invoke_get_all_properties_in_idle_cb"); |
| g_source_attach (idle_source, main_context); |
| g_source_unref (idle_source); |
| |
| handled = TRUE; |
| |
| out: |
| return handled; |
| } |
| |
| /* called in GDBusWorker thread with connection's lock held */ |
| static gboolean |
| handle_get_all_properties (GDBusConnection *connection, |
| ExportedObject *eo, |
| GDBusMessage *message) |
| { |
| ExportedInterface *ei; |
| gboolean handled; |
| const char *interface_name; |
| |
| handled = FALSE; |
| |
| g_variant_get (g_dbus_message_get_body (message), |
| "(&s)", |
| &interface_name); |
| |
| /* Fail with org.freedesktop.DBus.Error.InvalidArgs if there is |
| * no such interface registered |
| */ |
| ei = g_hash_table_lookup (eo->map_if_name_to_ei, interface_name); |
| if (ei == NULL) |
| { |
| GDBusMessage *reply; |
| reply = g_dbus_message_new_method_error (message, |
| "org.freedesktop.DBus.Error.InvalidArgs", |
| _("No such interface “%s”"), |
| interface_name); |
| g_dbus_connection_send_message_unlocked (eo->connection, reply, G_DBUS_SEND_MESSAGE_FLAGS_NONE, NULL, NULL); |
| g_object_unref (reply); |
| handled = TRUE; |
| goto out; |
| } |
| |
| handled = validate_and_maybe_schedule_property_get_all (eo->connection, |
| message, |
| ei->id, |
| 0, |
| ei->interface_info, |
| ei->vtable, |
| ei->context, |
| ei->user_data); |
| out: |
| return handled; |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| static const gchar introspect_header[] = |
| "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\"\n" |
| " \"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n" |
| "<!-- GDBus " PACKAGE_VERSION " -->\n" |
| "<node>\n"; |
| |
| static const gchar introspect_tail[] = |
| "</node>\n"; |
| |
| static const gchar introspect_properties_interface[] = |
| " <interface name=\"org.freedesktop.DBus.Properties\">\n" |
| " <method name=\"Get\">\n" |
| " <arg type=\"s\" name=\"interface_name\" direction=\"in\"/>\n" |
| " <arg type=\"s\" name=\"property_name\" direction=\"in\"/>\n" |
| " <arg type=\"v\" name=\"value\" direction=\"out\"/>\n" |
| " </method>\n" |
| " <method name=\"GetAll\">\n" |
| " <arg type=\"s\" name=\"interface_name\" direction=\"in\"/>\n" |
| " <arg type=\"a{sv}\" name=\"properties\" direction=\"out\"/>\n" |
| " </method>\n" |
| " <method name=\"Set\">\n" |
| " <arg type=\"s\" name=\"interface_name\" direction=\"in\"/>\n" |
| " <arg type=\"s\" name=\"property_name\" direction=\"in\"/>\n" |
| " <arg type=\"v\" name=\"value\" direction=\"in\"/>\n" |
| " </method>\n" |
| " <signal name=\"PropertiesChanged\">\n" |
| " <arg type=\"s\" name=\"interface_name\"/>\n" |
| " <arg type=\"a{sv}\" name=\"changed_properties\"/>\n" |
| " <arg type=\"as\" name=\"invalidated_properties\"/>\n" |
| " </signal>\n" |
| " </interface>\n"; |
| |
| static const gchar introspect_introspectable_interface[] = |
| " <interface name=\"org.freedesktop.DBus.Introspectable\">\n" |
| " <method name=\"Introspect\">\n" |
| " <arg type=\"s\" name=\"xml_data\" direction=\"out\"/>\n" |
| " </method>\n" |
| " </interface>\n" |
| " <interface name=\"org.freedesktop.DBus.Peer\">\n" |
| " <method name=\"Ping\"/>\n" |
| " <method name=\"GetMachineId\">\n" |
| " <arg type=\"s\" name=\"machine_uuid\" direction=\"out\"/>\n" |
| " </method>\n" |
| " </interface>\n"; |
| |
| static void |
| introspect_append_header (GString *s) |
| { |
| g_string_append (s, introspect_header); |
| } |
| |
| static void |
| maybe_add_path (const gchar *path, gsize path_len, const gchar *object_path, GHashTable *set) |
| { |
| if (g_str_has_prefix (object_path, path) && strlen (object_path) > path_len && object_path[path_len-1] == '/') |
| { |
| const gchar *begin; |
| const gchar *end; |
| gchar *s; |
| |
| begin = object_path + path_len; |
| end = strchr (begin, '/'); |
| if (end != NULL) |
| s = g_strndup (begin, end - begin); |
| else |
| s = g_strdup (begin); |
| |
| if (!g_hash_table_contains (set, s)) |
| g_hash_table_add (set, s); |
| else |
| g_free (s); |
| } |
| } |
| |
| /* TODO: we want a nicer public interface for this */ |
| /* called in any thread with connection's lock held */ |
| static gchar ** |
| g_dbus_connection_list_registered_unlocked (GDBusConnection *connection, |
| const gchar *path) |
| { |
| GPtrArray *p; |
| gchar **ret; |
| GHashTableIter hash_iter; |
| const gchar *object_path; |
| gsize path_len; |
| GHashTable *set; |
| |
| CONNECTION_ENSURE_LOCK (connection); |
| |
| path_len = strlen (path); |
| if (path_len > 1) |
| path_len++; |
| |
| set = g_hash_table_new (g_str_hash, g_str_equal); |
| |
| g_hash_table_iter_init (&hash_iter, connection->map_object_path_to_eo); |
| while (g_hash_table_iter_next (&hash_iter, (gpointer) &object_path, NULL)) |
| maybe_add_path (path, path_len, object_path, set); |
| |
| g_hash_table_iter_init (&hash_iter, connection->map_object_path_to_es); |
| while (g_hash_table_iter_next (&hash_iter, (gpointer) &object_path, NULL)) |
| maybe_add_path (path, path_len, object_path, set); |
| |
| p = g_hash_table_steal_all_keys (set); |
| g_hash_table_unref (set); |
| |
| g_ptr_array_add (p, NULL); |
| ret = (gchar **) g_ptr_array_free (p, FALSE); |
| return ret; |
| } |
| |
| /* called in any thread with connection's lock not held */ |
| static gchar ** |
| g_dbus_connection_list_registered (GDBusConnection *connection, |
| const gchar *path) |
| { |
| gchar **ret; |
| CONNECTION_LOCK (connection); |
| ret = g_dbus_connection_list_registered_unlocked (connection, path); |
| CONNECTION_UNLOCK (connection); |
| return ret; |
| } |
| |
| /* called in GDBusWorker thread with connection's lock held */ |
| static gboolean |
| handle_introspect (GDBusConnection *connection, |
| ExportedObject *eo, |
| GDBusMessage *message) |
| { |
| guint n; |
| GString *s; |
| GDBusMessage *reply; |
| GHashTableIter hash_iter; |
| ExportedInterface *ei; |
| gchar **registered; |
| |
| /* first the header with the standard interfaces */ |
| s = g_string_sized_new (sizeof (introspect_header) + |
| sizeof (introspect_properties_interface) + |
| sizeof (introspect_introspectable_interface) + |
| sizeof (introspect_tail)); |
| introspect_append_header (s); |
| if (!g_hash_table_lookup (eo->map_if_name_to_ei, |
| "org.freedesktop.DBus.Properties")) |
| g_string_append (s, introspect_properties_interface); |
| |
| if (!g_hash_table_lookup (eo->map_if_name_to_ei, |
| "org.freedesktop.DBus.Introspectable")) |
| g_string_append (s, introspect_introspectable_interface); |
| |
| /* then include the registered interfaces */ |
| g_hash_table_iter_init (&hash_iter, eo->map_if_name_to_ei); |
| while (g_hash_table_iter_next (&hash_iter, NULL, (gpointer) &ei)) |
| g_dbus_interface_info_generate_xml (ei->interface_info, 2, s); |
| |
| /* finally include nodes registered below us */ |
| registered = g_dbus_connection_list_registered_unlocked (connection, eo->object_path); |
| for (n = 0; registered != NULL && registered[n] != NULL; n++) |
| g_string_append_printf (s, " <node name=\"%s\"/>\n", registered[n]); |
| g_strfreev (registered); |
| g_string_append (s, introspect_tail); |
| |
| reply = g_dbus_message_new_method_reply (message); |
| g_dbus_message_set_body (reply, g_variant_new ("(s)", s->str)); |
| g_dbus_connection_send_message_unlocked (connection, reply, G_DBUS_SEND_MESSAGE_FLAGS_NONE, NULL, NULL); |
| g_object_unref (reply); |
| g_string_free (s, TRUE); |
| |
| return TRUE; |
| } |
| |
| /* called in thread where object was registered - no locks held */ |
| static gboolean |
| call_in_idle_cb (gpointer user_data) |
| { |
| GDBusMethodInvocation *invocation = G_DBUS_METHOD_INVOCATION (user_data); |
| GDBusInterfaceVTable *vtable; |
| guint registration_id; |
| guint subtree_registration_id; |
| ExportedInterface *ei = NULL; |
| ExportedSubtree *es = NULL; |
| |
| registration_id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (invocation), "g-dbus-registration-id")); |
| subtree_registration_id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (invocation), "g-dbus-subtree-registration-id")); |
| |
| if (has_object_been_unregistered (g_dbus_method_invocation_get_connection (invocation), |
| registration_id, |
| &ei, |
| subtree_registration_id, |
| &es)) |
| { |
| GDBusMessage *reply; |
| reply = g_dbus_message_new_method_error (g_dbus_method_invocation_get_message (invocation), |
| "org.freedesktop.DBus.Error.UnknownMethod", |
| _("No such interface “%s” on object at path %s"), |
| g_dbus_method_invocation_get_interface_name (invocation), |
| g_dbus_method_invocation_get_object_path (invocation)); |
| g_dbus_connection_send_message (g_dbus_method_invocation_get_connection (invocation), reply, G_DBUS_SEND_MESSAGE_FLAGS_NONE, NULL, NULL); |
| g_object_unref (reply); |
| goto out; |
| } |
| |
| vtable = g_object_get_data (G_OBJECT (invocation), "g-dbus-interface-vtable"); |
| g_assert (vtable != NULL && vtable->method_call != NULL); |
| |
| vtable->method_call (g_dbus_method_invocation_get_connection (invocation), |
| g_dbus_method_invocation_get_sender (invocation), |
| g_dbus_method_invocation_get_object_path (invocation), |
| g_dbus_method_invocation_get_interface_name (invocation), |
| g_dbus_method_invocation_get_method_name (invocation), |
| g_dbus_method_invocation_get_parameters (invocation), |
| g_object_ref (invocation), |
| g_dbus_method_invocation_get_user_data (invocation)); |
| |
| out: |
| g_clear_pointer (&ei, exported_interface_unref); |
| g_clear_pointer (&es, exported_subtree_unref); |
| |
| return FALSE; |
| } |
| |
| /* called in GDBusWorker thread with connection's lock held */ |
| static void |
| schedule_method_call (GDBusConnection *connection, |
| GDBusMessage *message, |
| guint registration_id, |
| guint subtree_registration_id, |
| const GDBusInterfaceInfo *interface_info, |
| const GDBusMethodInfo *method_info, |
| const GDBusPropertyInfo *property_info, |
| GVariant *parameters, |
| const GDBusInterfaceVTable *vtable, |
| GMainContext *main_context, |
| gpointer user_data) |
| { |
| GDBusMethodInvocation *invocation; |
| GSource *idle_source; |
| |
| invocation = _g_dbus_method_invocation_new (g_dbus_message_get_sender (message), |
| g_dbus_message_get_path (message), |
| g_dbus_message_get_interface (message), |
| g_dbus_message_get_member (message), |
| method_info, |
| property_info, |
| connection, |
| message, |
| parameters, |
| user_data); |
| |
| /* TODO: would be nicer with a real MethodData like we already |
| * have PropertyData and PropertyGetAllData... */ |
| g_object_set_data (G_OBJECT (invocation), "g-dbus-interface-vtable", (gpointer) vtable); |
| g_object_set_data (G_OBJECT (invocation), "g-dbus-registration-id", GUINT_TO_POINTER (registration_id)); |
| g_object_set_data (G_OBJECT (invocation), "g-dbus-subtree-registration-id", GUINT_TO_POINTER (subtree_registration_id)); |
| |
| idle_source = g_idle_source_new (); |
| g_source_set_priority (idle_source, G_PRIORITY_DEFAULT); |
| g_source_set_callback (idle_source, |
| call_in_idle_cb, |
| g_steal_pointer (&invocation), |
| g_object_unref); |
| g_source_set_static_name (idle_source, "[gio, " __FILE__ "] call_in_idle_cb"); |
| g_source_attach (idle_source, main_context); |
| g_source_unref (idle_source); |
| } |
| |
| /* called in GDBusWorker thread with connection's lock held */ |
| static gboolean |
| validate_and_maybe_schedule_method_call (GDBusConnection *connection, |
| GDBusMessage *message, |
| guint registration_id, |
| guint subtree_registration_id, |
| GDBusInterfaceInfo *interface_info, |
| const GDBusInterfaceVTable *vtable, |
| GMainContext *main_context, |
| gpointer user_data) |
| { |
| GDBusMethodInfo *method_info; |
| GDBusMessage *reply; |
| GVariant *parameters; |
| gboolean handled; |
| GVariantType *in_type; |
| |
| handled = FALSE; |
| |
| /* TODO: the cost of this is O(n) - it might be worth caching the result */ |
| method_info = g_dbus_interface_info_lookup_method (interface_info, g_dbus_message_get_member (message)); |
| |
| /* if the method doesn't exist, return the org.freedesktop.DBus.Error.UnknownMethod |
| * error to the caller |
| */ |
| if (method_info == NULL) |
| { |
| reply = g_dbus_message_new_method_error (message, |
| "org.freedesktop.DBus.Error.UnknownMethod", |
| _("No such method “%s”"), |
| g_dbus_message_get_member (message)); |
| g_dbus_connection_send_message_unlocked (connection, reply, G_DBUS_SEND_MESSAGE_FLAGS_NONE, NULL, NULL); |
| g_object_unref (reply); |
| handled = TRUE; |
| goto out; |
| } |
| |
| parameters = g_dbus_message_get_body (message); |
| if (parameters == NULL) |
| { |
| parameters = g_variant_new ("()"); |
| g_variant_ref_sink (parameters); |
| } |
| else |
| { |
| g_variant_ref (parameters); |
| } |
| |
| /* Check that the incoming args are of the right type - if they are not, return |
| * the org.freedesktop.DBus.Error.InvalidArgs error to the caller |
| */ |
| in_type = _g_dbus_compute_complete_signature (method_info->in_args); |
| if (!g_variant_is_of_type (parameters, in_type)) |
| { |
| gchar *type_string; |
| |
| type_string = g_variant_type_dup_string (in_type); |
| |
| reply = g_dbus_message_new_method_error (message, |
| "org.freedesktop.DBus.Error.InvalidArgs", |
| _("Type of message, “%s”, does not match expected type “%s”"), |
| g_variant_get_type_string (parameters), |
| type_string); |
| g_dbus_connection_send_message_unlocked (connection, reply, G_DBUS_SEND_MESSAGE_FLAGS_NONE, NULL, NULL); |
| g_variant_type_free (in_type); |
| g_variant_unref (parameters); |
| g_object_unref (reply); |
| g_free (type_string); |
| handled = TRUE; |
| goto out; |
| } |
| g_variant_type_free (in_type); |
| |
| /* schedule the call in idle */ |
| schedule_method_call (connection, message, registration_id, subtree_registration_id, |
| interface_info, method_info, NULL, parameters, |
| vtable, main_context, user_data); |
| g_variant_unref (parameters); |
| handled = TRUE; |
| |
| out: |
| return handled; |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| /* called in GDBusWorker thread with connection's lock held */ |
| static gboolean |
| obj_message_func (GDBusConnection *connection, |
| ExportedObject *eo, |
| GDBusMessage *message, |
| gboolean *object_found) |
| { |
| const gchar *interface_name; |
| const gchar *member; |
| const gchar *signature; |
| gboolean handled; |
| |
| handled = FALSE; |
| |
| interface_name = g_dbus_message_get_interface (message); |
| member = g_dbus_message_get_member (message); |
| signature = g_dbus_message_get_signature (message); |
| |
| /* see if we have an interface for handling this call */ |
| if (interface_name != NULL) |
| { |
| ExportedInterface *ei; |
| ei = g_hash_table_lookup (eo->map_if_name_to_ei, interface_name); |
| if (ei != NULL) |
| { |
| /* we do - invoke the handler in idle in the right thread */ |
| |
| /* handle no vtable or handler being present */ |
| if (ei->vtable == NULL || ei->vtable->method_call == NULL) |
| goto out; |
| |
| handled = validate_and_maybe_schedule_method_call (connection, |
| message, |
| ei->id, |
| 0, |
| ei->interface_info, |
| ei->vtable, |
| ei->context, |
| ei->user_data); |
| goto out; |
| } |
| else |
| { |
| *object_found = TRUE; |
| } |
| } |
| |
| if (g_strcmp0 (interface_name, "org.freedesktop.DBus.Introspectable") == 0 && |
| g_strcmp0 (member, "Introspect") == 0 && |
| g_strcmp0 (signature, "") == 0) |
| { |
| handled = handle_introspect (connection, eo, message); |
| goto out; |
| } |
| else if (g_strcmp0 (interface_name, "org.freedesktop.DBus.Properties") == 0 && |
| g_strcmp0 (member, "Get") == 0 && |
| g_strcmp0 (signature, "ss") == 0) |
| { |
| handled = handle_getset_property (connection, eo, message, TRUE); |
| goto out; |
| } |
| else if (g_strcmp0 (interface_name, "org.freedesktop.DBus.Properties") == 0 && |
| g_strcmp0 (member, "Set") == 0 && |
| g_strcmp0 (signature, "ssv") == 0) |
| { |
| handled = handle_getset_property (connection, eo, message, FALSE); |
| goto out; |
| } |
| else if (g_strcmp0 (interface_name, "org.freedesktop.DBus.Properties") == 0 && |
| g_strcmp0 (member, "GetAll") == 0 && |
| g_strcmp0 (signature, "s") == 0) |
| { |
| handled = handle_get_all_properties (connection, eo, message); |
| goto out; |
| } |
| |
| out: |
| return handled; |
| } |
| |
| /** |
| * g_dbus_connection_register_object: |
| * @connection: a #GDBusConnection |
| * @object_path: the object path to register at |
| * @interface_info: introspection data for the interface |
| * @vtable: (nullable): a #GDBusInterfaceVTable to call into or %NULL |
| * @user_data: (nullable): data to pass to functions in @vtable |
| * @user_data_free_func: function to call when the object path is unregistered |
| * @error: return location for error or %NULL |
| * |
| * Registers callbacks for exported objects at @object_path with the |
| * D-Bus interface that is described in @interface_info. |
| * |
| * Calls to functions in @vtable (and @user_data_free_func) will happen |
| * in the |
| * [thread-default main context][g-main-context-push-thread-default] |
| * of the thread you are calling this method from. |
| * |
| * Note that all #GVariant values passed to functions in @vtable will match |
| * the signature given in @interface_info - if a remote caller passes |
| * incorrect values, the `org.freedesktop.DBus.Error.InvalidArgs` |
| * is returned to the remote caller. |
| * |
| * Additionally, if the remote caller attempts to invoke methods or |
| * access properties not mentioned in @interface_info the |
| * `org.freedesktop.DBus.Error.UnknownMethod` resp. |
| * `org.freedesktop.DBus.Error.InvalidArgs` errors |
| * are returned to the caller. |
| * |
| * It is considered a programming error if the |
| * #GDBusInterfaceGetPropertyFunc function in @vtable returns a |
| * #GVariant of incorrect type. |
| * |
| * If an existing callback is already registered at @object_path and |
| * @interface_name, then @error is set to %G_IO_ERROR_EXISTS. |
| * |
| * GDBus automatically implements the standard D-Bus interfaces |
| * org.freedesktop.DBus.Properties, org.freedesktop.DBus.Introspectable |
| * and org.freedesktop.Peer, so you don't have to implement those for the |
| * objects you export. You can implement org.freedesktop.DBus.Properties |
| * yourself, e.g. to handle getting and setting of properties asynchronously. |
| * |
| * Note that the reference count on @interface_info will be |
| * incremented by 1 (unless allocated statically, e.g. if the |
| * reference count is -1, see g_dbus_interface_info_ref()) for as long |
| * as the object is exported. Also note that @vtable will be copied. |
| * |
| * See this [server][gdbus-server] for an example of how to use this method. |
| * |
| * Returns: 0 if @error is set, otherwise a registration id (never 0) |
| * that can be used with g_dbus_connection_unregister_object() |
| * |
| * Since: 2.26 |
| */ |
| guint |
| g_dbus_connection_register_object (GDBusConnection *connection, |
| const gchar *object_path, |
| GDBusInterfaceInfo *interface_info, |
| const GDBusInterfaceVTable *vtable, |
| gpointer user_data, |
| GDestroyNotify user_data_free_func, |
| GError **error) |
| { |
| ExportedObject *eo; |
| ExportedInterface *ei; |
| guint ret; |
| |
| g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), 0); |
| g_return_val_if_fail (object_path != NULL && g_variant_is_object_path (object_path), 0); |
| g_return_val_if_fail (interface_info != NULL, 0); |
| g_return_val_if_fail (g_dbus_is_interface_name (interface_info->name), 0); |
| g_return_val_if_fail (error == NULL || *error == NULL, 0); |
| g_return_val_if_fail (check_initialized (connection), 0); |
| |
| ret = 0; |
| |
| CONNECTION_LOCK (connection); |
| |
| eo = g_hash_table_lookup (connection->map_object_path_to_eo, object_path); |
| if (eo == NULL) |
| { |
| eo = g_new0 (ExportedObject, 1); |
| eo->object_path = g_strdup (object_path); |
| eo->connection = connection; |
| eo->map_if_name_to_ei = g_hash_table_new_full (g_str_hash, |
| g_str_equal, |
| NULL, |
| (GDestroyNotify) exported_interface_unref); |
| g_hash_table_insert (connection->map_object_path_to_eo, eo->object_path, eo); |
| } |
| |
| ei = g_hash_table_lookup (eo->map_if_name_to_ei, interface_info->name); |
| if (ei != NULL) |
| { |
| g_set_error (error, |
| G_IO_ERROR, |
| G_IO_ERROR_EXISTS, |
| _("An object is already exported for the interface %s at %s"), |
| interface_info->name, |
| object_path); |
| goto out; |
| } |
| |
| ei = g_new0 (ExportedInterface, 1); |
| ei->refcount = 1; |
| ei->id = (guint) g_atomic_int_add (&_global_registration_id, 1); /* TODO: overflow etc. */ |
| ei->eo = eo; |
| ei->user_data = user_data; |
| ei->user_data_free_func = user_data_free_func; |
| ei->vtable = _g_dbus_interface_vtable_copy (vtable); |
| ei->interface_info = g_dbus_interface_info_ref (interface_info); |
| g_dbus_interface_info_cache_build (ei->interface_info); |
| ei->interface_name = g_strdup (interface_info->name); |
| ei->context = g_main_context_ref_thread_default (); |
| |
| g_hash_table_insert (eo->map_if_name_to_ei, |
| (gpointer) ei->interface_name, |
| ei); |
| g_hash_table_insert (connection->map_id_to_ei, |
| GUINT_TO_POINTER (ei->id), |
| ei); |
| |
| ret = ei->id; |
| |
| out: |
| CONNECTION_UNLOCK (connection); |
| |
| return ret; |
| } |
| |
| /** |
| * g_dbus_connection_unregister_object: |
| * @connection: a #GDBusConnection |
| * @registration_id: a registration id obtained from |
| * g_dbus_connection_register_object() |
| * |
| * Unregisters an object. |
| * |
| * Returns: %TRUE if the object was unregistered, %FALSE otherwise |
| * |
| * Since: 2.26 |
| */ |
| gboolean |
| g_dbus_connection_unregister_object (GDBusConnection *connection, |
| guint registration_id) |
| { |
| ExportedInterface *ei; |
| ExportedObject *eo; |
| gboolean ret; |
| |
| g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE); |
| g_return_val_if_fail (check_initialized (connection), FALSE); |
| |
| ret = FALSE; |
| |
| CONNECTION_LOCK (connection); |
| |
| ei = g_hash_table_lookup (connection->map_id_to_ei, |
| GUINT_TO_POINTER (registration_id)); |
| if (ei == NULL) |
| goto out; |
| |
| eo = ei->eo; |
| |
| g_warn_if_fail (g_hash_table_remove (connection->map_id_to_ei, GUINT_TO_POINTER (ei->id))); |
| g_warn_if_fail (g_hash_table_remove (eo->map_if_name_to_ei, ei->interface_name)); |
| /* unregister object path if we have no more exported interfaces */ |
| if (g_hash_table_size (eo->map_if_name_to_ei) == 0) |
| g_warn_if_fail (g_hash_table_remove (connection->map_object_path_to_eo, |
| eo->object_path)); |
| |
| ret = TRUE; |
| |
| out: |
| CONNECTION_UNLOCK (connection); |
| |
| return ret; |
| } |
| |
| typedef struct { |
| GClosure *method_call_closure; |
| GClosure *get_property_closure; |
| GClosure *set_property_closure; |
| } RegisterObjectData; |
| |
| static RegisterObjectData * |
| register_object_data_new (GClosure *method_call_closure, |
| GClosure *get_property_closure, |
| GClosure *set_property_closure) |
| { |
| RegisterObjectData *data; |
| |
| data = g_new0 (RegisterObjectData, 1); |
| |
| if (method_call_closure != NULL) |
| { |
| data->method_call_closure = g_closure_ref (method_call_closure); |
| g_closure_sink (method_call_closure); |
| if (G_CLOSURE_NEEDS_MARSHAL (method_call_closure)) |
| g_closure_set_marshal (method_call_closure, g_cclosure_marshal_generic); |
| } |
| |
| if (get_property_closure != NULL) |
| { |
| data->get_property_closure = g_closure_ref (get_property_closure); |
| g_closure_sink (get_property_closure); |
| if (G_CLOSURE_NEEDS_MARSHAL (get_property_closure)) |
| g_closure_set_marshal (get_property_closure, g_cclosure_marshal_generic); |
| } |
| |
| if (set_property_closure != NULL) |
| { |
| data->set_property_closure = g_closure_ref (set_property_closure); |
| g_closure_sink (set_property_closure); |
| if (G_CLOSURE_NEEDS_MARSHAL (set_property_closure)) |
| g_closure_set_marshal (set_property_closure, g_cclosure_marshal_generic); |
| } |
| |
| return data; |
| } |
| |
| static void |
| register_object_free_func (gpointer user_data) |
| { |
| RegisterObjectData *data = user_data; |
| |
| g_clear_pointer (&data->method_call_closure, g_closure_unref); |
| g_clear_pointer (&data->get_property_closure, g_closure_unref); |
| g_clear_pointer (&data->set_property_closure, g_closure_unref); |
| |
| g_free (data); |
| } |
| |
| static void |
| register_with_closures_on_method_call (GDBusConnection *connection, |
| const gchar *sender, |
| const gchar *object_path, |
| const gchar *interface_name, |
| const gchar *method_name, |
| GVariant *parameters, |
| GDBusMethodInvocation *invocation, |
| gpointer user_data) |
| { |
| RegisterObjectData *data = user_data; |
| GValue params[] = { G_VALUE_INIT, G_VALUE_INIT, G_VALUE_INIT, G_VALUE_INIT, G_VALUE_INIT, G_VALUE_INIT, G_VALUE_INIT }; |
| |
| g_value_init (¶ms[0], G_TYPE_DBUS_CONNECTION); |
| g_value_set_object (¶ms[0], connection); |
| |
| g_value_init (¶ms[1], G_TYPE_STRING); |
| g_value_set_string (¶ms[1], sender); |
| |
| g_value_init (¶ms[2], G_TYPE_STRING); |
| g_value_set_string (¶ms[2], object_path); |
| |
| g_value_init (¶ms[3], G_TYPE_STRING); |
| g_value_set_string (¶ms[3], interface_name); |
| |
| g_value_init (¶ms[4], G_TYPE_STRING); |
| g_value_set_string (¶ms[4], method_name); |
| |
| g_value_init (¶ms[5], G_TYPE_VARIANT); |
| g_value_set_variant (¶ms[5], parameters); |
| |
| g_value_init (¶ms[6], G_TYPE_DBUS_METHOD_INVOCATION); |
| g_value_set_object (¶ms[6], invocation); |
| |
| g_closure_invoke (data->method_call_closure, NULL, G_N_ELEMENTS (params), params, NULL); |
| |
| g_value_unset (params + 0); |
| g_value_unset (params + 1); |
| g_value_unset (params + 2); |
| g_value_unset (params + 3); |
| g_value_unset (params + 4); |
| g_value_unset (params + 5); |
| g_value_unset (params + 6); |
| } |
| |
| static GVariant * |
| register_with_closures_on_get_property (GDBusConnection *connection, |
| const gchar *sender, |
| const gchar *object_path, |
| const gchar *interface_name, |
| const gchar *property_name, |
| GError **error, |
| gpointer user_data) |
| { |
| RegisterObjectData *data = user_data; |
| GValue params[] = { G_VALUE_INIT, G_VALUE_INIT, G_VALUE_INIT, G_VALUE_INIT, G_VALUE_INIT }; |
| GValue result_value = G_VALUE_INIT; |
| GVariant *result; |
| |
| g_value_init (¶ms[0], G_TYPE_DBUS_CONNECTION); |
| g_value_set_object (¶ms[0], connection); |
| |
| g_value_init (¶ms[1], G_TYPE_STRING); |
| g_value_set_string (¶ms[1], sender); |
| |
| g_value_init (¶ms[2], G_TYPE_STRING); |
| g_value_set_string (¶ms[2], object_path); |
| |
| g_value_init (¶ms[3], G_TYPE_STRING); |
| g_value_set_string (¶ms[3], interface_name); |
| |
| g_value_init (¶ms[4], G_TYPE_STRING); |
| g_value_set_string (¶ms[4], property_name); |
| |
| g_value_init (&result_value, G_TYPE_VARIANT); |
| |
| g_closure_invoke (data->get_property_closure, &result_value, G_N_ELEMENTS (params), params, NULL); |
| |
| result = g_value_get_variant (&result_value); |
| if (result) |
| g_variant_ref (result); |
| |
| g_value_unset (params + 0); |
| g_value_unset (params + 1); |
| g_value_unset (params + 2); |
| g_value_unset (params + 3); |
| g_value_unset (params + 4); |
| g_value_unset (&result_value); |
| |
| if (!result) |
| g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, |
| _("Unable to retrieve property %s.%s"), |
| interface_name, property_name); |
| |
| return result; |
| } |
| |
| static gboolean |
| register_with_closures_on_set_property (GDBusConnection *connection, |
| const gchar *sender, |
| const gchar *object_path, |
| const gchar *interface_name, |
| const gchar *property_name, |
| GVariant *value, |
| GError **error, |
| gpointer user_data) |
| { |
| RegisterObjectData *data = user_data; |
| GValue params[] = { G_VALUE_INIT, G_VALUE_INIT, G_VALUE_INIT, G_VALUE_INIT, G_VALUE_INIT, G_VALUE_INIT }; |
| GValue result_value = G_VALUE_INIT; |
| gboolean result; |
| |
| g_value_init (¶ms[0], G_TYPE_DBUS_CONNECTION); |
| g_value_set_object (¶ms[0], connection); |
| |
| g_value_init (¶ms[1], G_TYPE_STRING); |
| g_value_set_string (¶ms[1], sender); |
| |
| g_value_init (¶ms[2], G_TYPE_STRING); |
| g_value_set_string (¶ms[2], object_path); |
| |
| g_value_init (¶ms[3], G_TYPE_STRING); |
| g_value_set_string (¶ms[3], interface_name); |
| |
| g_value_init (¶ms[4], G_TYPE_STRING); |
| g_value_set_string (¶ms[4], property_name); |
| |
| g_value_init (¶ms[5], G_TYPE_VARIANT); |
| g_value_set_variant (¶ms[5], value); |
| |
| g_value_init (&result_value, G_TYPE_BOOLEAN); |
| |
| g_closure_invoke (data->set_property_closure, &result_value, G_N_ELEMENTS (params), params, NULL); |
| |
| result = g_value_get_boolean (&result_value); |
| |
| g_value_unset (params + 0); |
| g_value_unset (params + 1); |
| g_value_unset (params + 2); |
| g_value_unset (params + 3); |
| g_value_unset (params + 4); |
| g_value_unset (params + 5); |
| g_value_unset (&result_value); |
| |
| if (!result) |
| g_set_error (error, |
| G_DBUS_ERROR, G_DBUS_ERROR_FAILED, |
| _("Unable to set property %s.%s"), |
| interface_name, property_name); |
| |
| return result; |
| } |
| |
| /** |
| * g_dbus_connection_register_object_with_closures: (rename-to g_dbus_connection_register_object) |
| * @connection: A #GDBusConnection. |
| * @object_path: The object path to register at. |
| * @interface_info: Introspection data for the interface. |
| * @method_call_closure: (nullable): #GClosure for handling incoming method calls. |
| * @get_property_closure: (nullable): #GClosure for getting a property. |
| * @set_property_closure: (nullable): #GClosure for setting a property. |
| * @error: Return location for error or %NULL. |
| * |
| * Version of g_dbus_connection_register_object() using closures instead of a |
| * #GDBusInterfaceVTable for easier binding in other languages. |
| * |
| * Returns: 0 if @error is set, otherwise a registration ID (never 0) |
| * that can be used with g_dbus_connection_unregister_object() . |
| * |
| * Since: 2.46 |
| */ |
| guint |
| g_dbus_connection_register_object_with_closures (GDBusConnection *connection, |
| const gchar *object_path, |
| GDBusInterfaceInfo *interface_info, |
| GClosure *method_call_closure, |
| GClosure *get_property_closure, |
| GClosure *set_property_closure, |
| GError **error) |
| { |
| RegisterObjectData *data; |
| GDBusInterfaceVTable vtable = |
| { |
| method_call_closure != NULL ? register_with_closures_on_method_call : NULL, |
| get_property_closure != NULL ? register_with_closures_on_get_property : NULL, |
| set_property_closure != NULL ? register_with_closures_on_set_property : NULL, |
| { 0 } |
| }; |
| |
| data = register_object_data_new (method_call_closure, get_property_closure, set_property_closure); |
| |
| return g_dbus_connection_register_object (connection, |
| object_path, |
| interface_info, |
| &vtable, |
| data, |
| register_object_free_func, |
| error); |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| /** |
| * g_dbus_connection_emit_signal: |
| * @connection: a #GDBusConnection |
| * @destination_bus_name: (nullable): the unique bus name for the destination |
| * for the signal or %NULL to emit to all listeners |
| * @object_path: path of remote object |
| * @interface_name: D-Bus interface to emit a signal on |
| * @signal_name: the name of the signal to emit |
| * @parameters: (nullable): a #GVariant tuple with parameters for the signal |
| * or %NULL if not passing parameters |
| * @error: Return location for error or %NULL |
| * |
| * Emits a signal. |
| * |
| * If the parameters GVariant is floating, it is consumed. |
| * |
| * This can only fail if @parameters is not compatible with the D-Bus protocol |
| * (%G_IO_ERROR_INVALID_ARGUMENT), or if @connection has been closed |
| * (%G_IO_ERROR_CLOSED). |
| * |
| * Returns: %TRUE unless @error is set |
| * |
| * Since: 2.26 |
| */ |
| gboolean |
| g_dbus_connection_emit_signal (GDBusConnection *connection, |
| const gchar *destination_bus_name, |
| const gchar *object_path, |
| const gchar *interface_name, |
| const gchar *signal_name, |
| GVariant *parameters, |
| GError **error) |
| { |
| GDBusMessage *message; |
| gboolean ret; |
| |
| message = NULL; |
| ret = FALSE; |
| |
| g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE); |
| g_return_val_if_fail (destination_bus_name == NULL || g_dbus_is_name (destination_bus_name), FALSE); |
| g_return_val_if_fail (object_path != NULL && g_variant_is_object_path (object_path), FALSE); |
| g_return_val_if_fail (interface_name != NULL && g_dbus_is_interface_name (interface_name), FALSE); |
| g_return_val_if_fail (signal_name != NULL && g_dbus_is_member_name (signal_name), FALSE); |
| g_return_val_if_fail (parameters == NULL || g_variant_is_of_type (parameters, G_VARIANT_TYPE_TUPLE), FALSE); |
| g_return_val_if_fail (check_initialized (connection), FALSE); |
| |
| if (G_UNLIKELY (_g_dbus_debug_emission ())) |
| { |
| _g_dbus_debug_print_lock (); |
| g_print ("========================================================================\n" |
| "GDBus-debug:Emission:\n" |
| " >>>> SIGNAL EMISSION %s.%s()\n" |
| " on object %s\n" |
| " destination %s\n", |
| interface_name, signal_name, |
| object_path, |
| destination_bus_name != NULL ? destination_bus_name : "(none)"); |
| _g_dbus_debug_print_unlock (); |
| } |
| |
| message = g_dbus_message_new_signal (object_path, |
| interface_name, |
| signal_name); |
| |
| if (destination_bus_name != NULL) |
| g_dbus_message_set_header (message, |
| G_DBUS_MESSAGE_HEADER_FIELD_DESTINATION, |
| g_variant_new_string (destination_bus_name)); |
| |
| if (parameters != NULL) |
| g_dbus_message_set_body (message, parameters); |
| |
| ret = g_dbus_connection_send_message (connection, message, G_DBUS_SEND_MESSAGE_FLAGS_NONE, NULL, error); |
| g_object_unref (message); |
| |
| return ret; |
| } |
| |
| static void |
| add_call_flags (GDBusMessage *message, |
| GDBusCallFlags flags) |
| { |
| GDBusMessageFlags msg_flags = 0; |
| |
| if (flags & G_DBUS_CALL_FLAGS_NO_AUTO_START) |
| msg_flags |= G_DBUS_MESSAGE_FLAGS_NO_AUTO_START; |
| if (flags & G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION) |
| msg_flags |= G_DBUS_MESSAGE_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION; |
| if (msg_flags) |
| g_dbus_message_set_flags (message, msg_flags); |
| } |
| |
| static GVariant * |
| decode_method_reply (GDBusMessage *reply, |
| const gchar *method_name, |
| const GVariantType *reply_type, |
| GUnixFDList **out_fd_list, |
| GError **error) |
| { |
| GVariant *result; |
| |
| result = NULL; |
| switch (g_dbus_message_get_message_type (reply)) |
| { |
| case G_DBUS_MESSAGE_TYPE_METHOD_RETURN: |
| result = g_dbus_message_get_body (reply); |
| if (result == NULL) |
| { |
| result = g_variant_new ("()"); |
| g_variant_ref_sink (result); |
| } |
| else |
| { |
| g_variant_ref (result); |
| } |
| |
| if (!g_variant_is_of_type (result, reply_type)) |
| { |
| gchar *type_string = g_variant_type_dup_string (reply_type); |
| |
| g_set_error (error, |
| G_IO_ERROR, |
| G_IO_ERROR_INVALID_ARGUMENT, |
| _("Method “%s” returned type “%s”, but expected “%s”"), |
| method_name, g_variant_get_type_string (result), type_string); |
| |
| g_variant_unref (result); |
| g_free (type_string); |
| result = NULL; |
| } |
| |
| #ifdef G_OS_UNIX |
| if (result != NULL) |
| { |
| if (out_fd_list != NULL) |
| { |
| *out_fd_list = g_dbus_message_get_unix_fd_list (reply); |
| if (*out_fd_list != NULL) |
| g_object_ref (*out_fd_list); |
| } |
| } |
| #endif |
| break; |
| |
| case G_DBUS_MESSAGE_TYPE_ERROR: |
| g_dbus_message_to_gerror (reply, error); |
| break; |
| |
| default: |
| g_assert_not_reached (); |
| break; |
| } |
| |
| return result; |
| } |
| |
| |
| typedef struct |
| { |
| GVariantType *reply_type; |
| gchar *method_name; /* for error message */ |
| |
| GUnixFDList *fd_list; |
| } CallState; |
| |
| static void |
| call_state_free (CallState *state) |
| { |
| g_variant_type_free (state->reply_type); |
| g_free (state->method_name); |
| |
| if (state->fd_list != NULL) |
| g_object_unref (state->fd_list); |
| g_slice_free (CallState, state); |
| } |
| |
| /* called in any thread, with the connection's lock not held */ |
| static void |
| g_dbus_connection_call_done (GObject *source, |
| GAsyncResult *result, |
| gpointer user_data) |
| { |
| GDBusConnection *connection = G_DBUS_CONNECTION (source); |
| GTask *task = user_data; |
| CallState *state = g_task_get_task_data (task); |
| GError *error = NULL; |
| GDBusMessage *reply; |
| GVariant *value = NULL; |
| |
| reply = g_dbus_connection_send_message_with_reply_finish (connection, |
| result, |
| &error); |
| |
| if (G_UNLIKELY (_g_dbus_debug_call ())) |
| { |
| _g_dbus_debug_print_lock (); |
| g_print ("========================================================================\n" |
| "GDBus-debug:Call:\n" |
| " <<<< ASYNC COMPLETE %s()", |
| state->method_name); |
| |
| if (reply != NULL) |
| { |
| g_print (" (serial %d)\n" |
| " SUCCESS\n", |
| g_dbus_message_get_reply_serial (reply)); |
| } |
| else |
| { |
| g_print ("\n" |
| " FAILED: %s\n", |
| error->message); |
| } |
| _g_dbus_debug_print_unlock (); |
| } |
| |
| if (reply != NULL) |
| value = decode_method_reply (reply, state->method_name, state->reply_type, &state->fd_list, &error); |
| |
| if (error != NULL) |
| g_task_return_error (task, error); |
| else |
| g_task_return_pointer (task, value, (GDestroyNotify) g_variant_unref); |
| |
| g_clear_object (&reply); |
| g_object_unref (task); |
| } |
| |
| /* called in any thread, with the connection's lock not held */ |
| static void |
| g_dbus_connection_call_internal (GDBusConnection *connection, |
| const gchar *bus_name, |
| const gchar *object_path, |
| const gchar *interface_name, |
| const gchar *method_name, |
| GVariant *parameters, |
| const GVariantType *reply_type, |
| GDBusCallFlags flags, |
| gint timeout_msec, |
| GUnixFDList *fd_list, |
| GCancellable *cancellable, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GDBusMessage *message; |
| guint32 serial; |
| |
| g_return_if_fail (G_IS_DBUS_CONNECTION (connection)); |
| g_return_if_fail (bus_name == NULL || g_dbus_is_name (bus_name)); |
| g_return_if_fail (object_path != NULL && g_variant_is_object_path (object_path)); |
| g_return_if_fail (interface_name != NULL && g_dbus_is_interface_name (interface_name)); |
| g_return_if_fail (method_name != NULL && g_dbus_is_member_name (method_name)); |
| g_return_if_fail (timeout_msec >= 0 || timeout_msec == -1); |
| g_return_if_fail ((parameters == NULL) || g_variant_is_of_type (parameters, G_VARIANT_TYPE_TUPLE)); |
| g_return_if_fail (check_initialized (connection)); |
| #ifdef G_OS_UNIX |
| g_return_if_fail (fd_list == NULL || G_IS_UNIX_FD_LIST (fd_list)); |
| #else |
| g_return_if_fail (fd_list == NULL); |
| #endif |
| |
| message = g_dbus_message_new_method_call (bus_name, |
| object_path, |
| interface_name, |
| method_name); |
| add_call_flags (message, flags); |
| if (parameters != NULL) |
| g_dbus_message_set_body (message, parameters); |
| |
| #ifdef G_OS_UNIX |
| if (fd_list != NULL) |
| g_dbus_message_set_unix_fd_list (message, fd_list); |
| #endif |
| |
| /* If the user has no callback then we can just send the message with |
| * the G_DBUS_MESSAGE_FLAGS_NO_REPLY_EXPECTED flag set and skip all |
| * the logic for processing the reply. If the service sends the reply |
| * anyway then it will just be ignored. |
| */ |
| if (callback != NULL) |
| { |
| CallState *state; |
| GTask *task; |
| |
| state = g_slice_new0 (CallState); |
| state->method_name = g_strjoin (".", interface_name, method_name, NULL); |
| |
| if (reply_type == NULL) |
| reply_type = G_VARIANT_TYPE_ANY; |
| |
| state->reply_type = g_variant_type_copy (reply_type); |
| |
| task = g_task_new (connection, cancellable, callback, user_data); |
| g_task_set_source_tag (task, g_dbus_connection_call_internal); |
| g_task_set_task_data (task, state, (GDestroyNotify) call_state_free); |
| |
| g_dbus_connection_send_message_with_reply (connection, |
| message, |
| G_DBUS_SEND_MESSAGE_FLAGS_NONE, |
| timeout_msec, |
| &serial, |
| cancellable, |
| g_dbus_connection_call_done, |
| task); |
| } |
| else |
| { |
| GDBusMessageFlags msg_flags; |
| |
| msg_flags = g_dbus_message_get_flags (message); |
| msg_flags |= G_DBUS_MESSAGE_FLAGS_NO_REPLY_EXPECTED; |
| g_dbus_message_set_flags (message, msg_flags); |
| |
| g_dbus_connection_send_message (connection, |
| message, |
| G_DBUS_SEND_MESSAGE_FLAGS_NONE, |
| &serial, NULL); |
| } |
| |
| if (G_UNLIKELY (_g_dbus_debug_call ())) |
| { |
| _g_dbus_debug_print_lock (); |
| g_print ("========================================================================\n" |
| "GDBus-debug:Call:\n" |
| " >>>> ASYNC %s.%s()\n" |
| " on object %s\n" |
| " owned by name %s (serial %d)\n", |
| interface_name, |
| method_name, |
| object_path, |
| bus_name != NULL ? bus_name : "(none)", |
| serial); |
| _g_dbus_debug_print_unlock (); |
| } |
| |
| if (message != NULL) |
| g_object_unref (message); |
| } |
| |
| /* called in any thread, with the connection's lock not held */ |
| static GVariant * |
| g_dbus_connection_call_finish_internal (GDBusConnection *connection, |
| GUnixFDList **out_fd_list, |
| GAsyncResult *res, |
| GError **error) |
| { |
| GTask *task; |
| CallState *state; |
| GVariant *ret; |
| |
| g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL); |
| g_return_val_if_fail (g_task_is_valid (res, connection), NULL); |
| g_return_val_if_fail (error == NULL || *error == NULL, NULL); |
| |
| task = G_TASK (res); |
| state = g_task_get_task_data (task); |
| |
| ret = g_task_propagate_pointer (task, error); |
| if (!ret) |
| return NULL; |
| |
| if (out_fd_list != NULL) |
| *out_fd_list = state->fd_list != NULL ? g_object_ref (state->fd_list) : NULL; |
| return ret; |
| } |
| |
| /* called in any user thread, with the connection's lock not held */ |
| static GVariant * |
| g_dbus_connection_call_sync_internal (GDBusConnection *connection, |
| const gchar *bus_name, |
| const gchar *object_path, |
| const gchar *interface_name, |
| const gchar *method_name, |
| GVariant *parameters, |
| const GVariantType *reply_type, |
| GDBusCallFlags flags, |
| gint timeout_msec, |
| GUnixFDList *fd_list, |
| GUnixFDList **out_fd_list, |
| GCancellable *cancellable, |
| GError **error) |
| { |
| GDBusMessage *message; |
| GDBusMessage *reply; |
| GVariant *result; |
| GError *local_error; |
| GDBusSendMessageFlags send_flags; |
| |
| message = NULL; |
| reply = NULL; |
| result = NULL; |
| |
| g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL); |
| g_return_val_if_fail (bus_name == NULL || g_dbus_is_name (bus_name), NULL); |
| g_return_val_if_fail (object_path != NULL && g_variant_is_object_path (object_path), NULL); |
| g_return_val_if_fail (interface_name != NULL && g_dbus_is_interface_name (interface_name), NULL); |
| g_return_val_if_fail (method_name != NULL && g_dbus_is_member_name (method_name), NULL); |
| g_return_val_if_fail (timeout_msec >= 0 || timeout_msec == -1, NULL); |
| g_return_val_if_fail ((parameters == NULL) || g_variant_is_of_type (parameters, G_VARIANT_TYPE_TUPLE), NULL); |
| #ifdef G_OS_UNIX |
| g_return_val_if_fail (fd_list == NULL || G_IS_UNIX_FD_LIST (fd_list), NULL); |
| #else |
| g_return_val_if_fail (fd_list == NULL, NULL); |
| #endif |
| g_return_val_if_fail (error == NULL || *error == NULL, NULL); |
| |
| if (!(flags & CALL_FLAGS_INITIALIZING)) |
| g_return_val_if_fail (check_initialized (connection), FALSE); |
| |
| if (reply_type == NULL) |
| reply_type = G_VARIANT_TYPE_ANY; |
| |
| message = g_dbus_message_new_method_call (bus_name, |
| object_path, |
| interface_name, |
| method_name); |
| add_call_flags (message, flags); |
| if (parameters != NULL) |
| g_dbus_message_set_body (message, parameters); |
| |
| #ifdef G_OS_UNIX |
| if (fd_list != NULL) |
| g_dbus_message_set_unix_fd_list (message, fd_list); |
| #endif |
| |
| if (G_UNLIKELY (_g_dbus_debug_call ())) |
| { |
| _g_dbus_debug_print_lock (); |
| g_print ("========================================================================\n" |
| "GDBus-debug:Call:\n" |
| " >>>> SYNC %s.%s()\n" |
| " on object %s\n" |
| " owned by name %s\n", |
| interface_name, |
| method_name, |
| object_path, |
| bus_name != NULL ? bus_name : "(none)"); |
| _g_dbus_debug_print_unlock (); |
| } |
| |
| local_error = NULL; |
| |
| send_flags = G_DBUS_SEND_MESSAGE_FLAGS_NONE; |
| |
| /* translate from one flavour of flags to another... */ |
| if (flags & CALL_FLAGS_INITIALIZING) |
| send_flags |= SEND_MESSAGE_FLAGS_INITIALIZING; |
| |
| reply = g_dbus_connection_send_message_with_reply_sync (connection, |
| message, |
| send_flags, |
| timeout_msec, |
| NULL, /* guint32 *out_serial */ |
| cancellable, |
| &local_error); |
| |
| if (G_UNLIKELY (_g_dbus_debug_call ())) |
| { |
| _g_dbus_debug_print_lock (); |
| g_print ("========================================================================\n" |
| "GDBus-debug:Call:\n" |
| " <<<< SYNC COMPLETE %s.%s()\n" |
| " ", |
| interface_name, |
| method_name); |
| if (reply != NULL) |
| { |
| g_print ("SUCCESS\n"); |
| } |
| else |
| { |
| g_print ("FAILED: %s\n", |
| local_error->message); |
| } |
| _g_dbus_debug_print_unlock (); |
| } |
| |
| if (reply == NULL) |
| { |
| if (error != NULL) |
| *error = local_error; |
| else |
| g_error_free (local_error); |
| goto out; |
| } |
| |
| result = decode_method_reply (reply, method_name, reply_type, out_fd_list, error); |
| |
| out: |
| if (message != NULL) |
| g_object_unref (message); |
| if (reply != NULL) |
| g_object_unref (reply); |
| |
| return result; |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| /** |
| * g_dbus_connection_call: |
| * @connection: a #GDBusConnection |
| * @bus_name: (nullable): a unique or well-known bus name or %NULL if |
| * @connection is not a message bus connection |
| * @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 |
| * @flags: flags from the #GDBusCallFlags enumeration |
| * @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 |
| * |
| * Asynchronously invokes the @method_name method on the |
| * @interface_name D-Bus interface on the remote object at |
| * @object_path owned by @bus_name. |
| * |
| * If @connection is closed then the operation will fail with |
| * %G_IO_ERROR_CLOSED. If @cancellable is canceled, the operation will |
| * fail with %G_IO_ERROR_CANCELLED. If @parameters contains a value |
| * not compatible with the D-Bus protocol, the operation fails with |
| * %G_IO_ERROR_INVALID_ARGUMENT. |
| * |
| * If @reply_type is non-%NULL then the reply will be checked for having this type and an |
| * error will be raised if it does not match. Said another way, if you give a @reply_type |
| * then any non-%NULL return value will be of this type. Unless it’s |
| * %G_VARIANT_TYPE_UNIT, the @reply_type will be a tuple containing one or more |
| * values. |
| * |
| * If the @parameters #GVariant is floating, it is consumed. This allows |
| * convenient 'inline' use of g_variant_new(), e.g.: |
| * |[<!-- language="C" --> |
| * g_dbus_connection_call (connection, |
| * "org.freedesktop.StringThings", |
| * "/org/freedesktop/StringThings", |
| * "org.freedesktop.StringThings", |
| * "TwoStrings", |
| * g_variant_new ("(ss)", |
| * "Thing One", |
| * "Thing Two"), |
| * NULL, |
| * G_DBUS_CALL_FLAGS_NONE, |
| * -1, |
| * NULL, |
| * (GAsyncReadyCallback) two_strings_done, |
| * NULL); |
| * ]| |
| * |
| * This is an asynchronous method. When the operation is finished, |
| * @callback will be invoked in the |
| * [thread-default main context][g-main-context-push-thread-default] |
| * of the thread you are calling this method from. You can then call |
| * g_dbus_connection_call_finish() to get the result of the operation. |
| * See g_dbus_connection_call_sync() for the synchronous version of this |
| * function. |
| * |
| * If @callback is %NULL then the D-Bus method call message will be sent with |
| * the %G_DBUS_MESSAGE_FLAGS_NO_REPLY_EXPECTED flag set. |
| * |
| * Since: 2.26 |
| */ |
| void |
| g_dbus_connection_call (GDBusConnection *connection, |
| const gchar *bus_name, |
| const gchar *object_path, |
| const gchar *interface_name, |
| const gchar *method_name, |
| GVariant *parameters, |
| const GVariantType *reply_type, |
| GDBusCallFlags flags, |
| gint timeout_msec, |
| GCancellable *cancellable, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| g_dbus_connection_call_internal (connection, bus_name, object_path, interface_name, method_name, parameters, reply_type, flags, timeout_msec, NULL, cancellable, callback, user_data); |
| } |
| |
| /** |
| * g_dbus_connection_call_finish: |
| * @connection: a #GDBusConnection |
| * @res: a #GAsyncResult obtained from the #GAsyncReadyCallback passed to g_dbus_connection_call() |
| * @error: return location for error or %NULL |
| * |
| * Finishes an operation started with g_dbus_connection_call(). |
| * |
| * Returns: (transfer full): %NULL if @error is set. Otherwise a non-floating |
| * #GVariant tuple with return values. Free with g_variant_unref(). |
| * |
| * Since: 2.26 |
| */ |
| GVariant * |
| g_dbus_connection_call_finish (GDBusConnection *connection, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_dbus_connection_call_finish_internal (connection, NULL, res, error); |
| } |
| |
| /** |
| * g_dbus_connection_call_sync: |
| * @connection: a #GDBusConnection |
| * @bus_name: (nullable): a unique or well-known bus name or %NULL if |
| * @connection is not a message bus connection |
| * @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, or %NULL |
| * @flags: flags from the #GDBusCallFlags enumeration |
| * @timeout_msec: the timeout in milliseconds, -1 to use the default |
| * timeout or %G_MAXINT for no timeout |
| * @cancellable: (nullable): a #GCancellable or %NULL |
| * @error: return location for error or %NULL |
| * |
| * Synchronously invokes the @method_name method on the |
| * @interface_name D-Bus interface on the remote object at |
| * @object_path owned by @bus_name. |
| * |
| * If @connection is closed then the operation will fail with |
| * %G_IO_ERROR_CLOSED. If @cancellable is canceled, the |
| * operation will fail with %G_IO_ERROR_CANCELLED. If @parameters |
| * contains a value not compatible with the D-Bus protocol, the operation |
| * fails with %G_IO_ERROR_INVALID_ARGUMENT. |
| * |
| * If @reply_type is non-%NULL then the reply will be checked for having |
| * this type and an error will be raised if it does not match. Said |
| * another way, if you give a @reply_type then any non-%NULL return |
| * value will be of this type. |
| * |
| * If the @parameters #GVariant is floating, it is consumed. |
| * This allows convenient 'inline' use of g_variant_new(), e.g.: |
| * |[<!-- language="C" --> |
| * g_dbus_connection_call_sync (connection, |
| * "org.freedesktop.StringThings", |
| * "/org/freedesktop/StringThings", |
| * "org.freedesktop.StringThings", |
| * "TwoStrings", |
| * g_variant_new ("(ss)", |
| * "Thing One", |
| * "Thing Two"), |
| * NULL, |
| * G_DBUS_CALL_FLAGS_NONE, |
| * -1, |
| * NULL, |
| * &error); |
| * ]| |
| * |
| * The calling thread is blocked until a reply is received. See |
| * g_dbus_connection_call() for the asynchronous version of |
| * this method. |
| * |
| * Returns: (transfer full): %NULL if @error is set. Otherwise a non-floating |
| * #GVariant tuple with return values. Free with g_variant_unref(). |
| * |
| * Since: 2.26 |
| */ |
| GVariant * |
| g_dbus_connection_call_sync (GDBusConnection *connection, |
| const gchar *bus_name, |
| const gchar *object_path, |
| const gchar *interface_name, |
| const gchar *method_name, |
| GVariant *parameters, |
| const GVariantType *reply_type, |
| GDBusCallFlags flags, |
| gint timeout_msec, |
| GCancellable *cancellable, |
| GError **error) |
| { |
| return g_dbus_connection_call_sync_internal (connection, bus_name, object_path, interface_name, method_name, parameters, reply_type, flags, timeout_msec, NULL, NULL, cancellable, error); |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| #ifdef G_OS_UNIX |
| |
| /** |
| * g_dbus_connection_call_with_unix_fd_list: |
| * @connection: a #GDBusConnection |
| * @bus_name: (nullable): a unique or well-known bus name or %NULL if |
| * @connection is not a message bus connection |
| * @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, or %NULL |
| * @flags: flags from the #GDBusCallFlags enumeration |
| * @timeout_msec: the timeout in milliseconds, -1 to use the default |
| * timeout or %G_MAXINT for no timeout |
| * @fd_list: (nullable): a #GUnixFDList or %NULL |
| * @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 g_dbus_connection_call() but also takes a #GUnixFDList object. |
| * |
| * The file descriptors normally correspond to %G_VARIANT_TYPE_HANDLE |
| * values in the body of the message. For example, if a message contains |
| * two file descriptors, @fd_list would have length 2, and |
| * `g_variant_new_handle (0)` and `g_variant_new_handle (1)` would appear |
| * somewhere in the body of the message (not necessarily in that order!) |
| * to represent the file descriptors at indexes 0 and 1 respectively. |
| * |
| * When designing D-Bus APIs that are intended to be interoperable, |
| * please note that non-GDBus implementations of D-Bus can usually only |
| * access file descriptors if they are referenced in this way by a |
| * value of type %G_VARIANT_TYPE_HANDLE in the body of the message. |
| * |
| * This method is only available on UNIX. |
| * |
| * Since: 2.30 |
| */ |
| void |
| g_dbus_connection_call_with_unix_fd_list (GDBusConnection *connection, |
| const gchar *bus_name, |
| const gchar *object_path, |
| const gchar *interface_name, |
| const gchar *method_name, |
| GVariant *parameters, |
| const GVariantType *reply_type, |
| GDBusCallFlags flags, |
| gint timeout_msec, |
| GUnixFDList *fd_list, |
| GCancellable *cancellable, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| g_dbus_connection_call_internal (connection, bus_name, object_path, interface_name, method_name, parameters, reply_type, flags, timeout_msec, fd_list, cancellable, callback, user_data); |
| } |
| |
| /** |
| * g_dbus_connection_call_with_unix_fd_list_finish: |
| * @connection: a #GDBusConnection |
| * @out_fd_list: (out) (optional): return location for a #GUnixFDList or %NULL |
| * @res: a #GAsyncResult obtained from the #GAsyncReadyCallback passed to |
| * g_dbus_connection_call_with_unix_fd_list() |
| * @error: return location for error or %NULL |
| * |
| * Finishes an operation started with g_dbus_connection_call_with_unix_fd_list(). |
| * |
| * The file descriptors normally correspond to %G_VARIANT_TYPE_HANDLE |
| * values in the body of the message. For example, |
| * if g_variant_get_handle() returns 5, that is intended to be a reference |
| * to the file descriptor that can be accessed by |
| * `g_unix_fd_list_get (*out_fd_list, 5, ...)`. |
| * |
| * When designing D-Bus APIs that are intended to be interoperable, |
| * please note that non-GDBus implementations of D-Bus can usually only |
| * access file descriptors if they are referenced in this way by a |
| * value of type %G_VARIANT_TYPE_HANDLE in the body of the message. |
| * |
| * Returns: (transfer full): %NULL if @error is set. Otherwise a non-floating |
| * #GVariant tuple with return values. Free with g_variant_unref(). |
| * |
| * Since: 2.30 |
| */ |
| GVariant * |
| g_dbus_connection_call_with_unix_fd_list_finish (GDBusConnection *connection, |
| GUnixFDList **out_fd_list, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_dbus_connection_call_finish_internal (connection, out_fd_list, res, error); |
| } |
| |
| /** |
| * g_dbus_connection_call_with_unix_fd_list_sync: |
| * @connection: a #GDBusConnection |
| * @bus_name: (nullable): a unique or well-known bus name or %NULL |
| * if @connection is not a message bus connection |
| * @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, or %NULL |
| * @flags: flags from the #GDBusCallFlags enumeration |
| * @timeout_msec: the timeout in milliseconds, -1 to use the default |
| * timeout or %G_MAXINT for no timeout |
| * @fd_list: (nullable): a #GUnixFDList or %NULL |
| * @out_fd_list: (out) (optional): return location for a #GUnixFDList or %NULL |
| * @cancellable: (nullable): a #GCancellable or %NULL |
| * @error: return location for error or %NULL |
| * |
| * Like g_dbus_connection_call_sync() but also takes and returns #GUnixFDList objects. |
| * See g_dbus_connection_call_with_unix_fd_list() and |
| * g_dbus_connection_call_with_unix_fd_list_finish() for more details. |
| * |
| * This method is only available on UNIX. |
| * |
| * Returns: (transfer full): %NULL if @error is set. Otherwise a non-floating |
| * #GVariant tuple with return values. Free with g_variant_unref(). |
| * |
| * Since: 2.30 |
| */ |
| GVariant * |
| g_dbus_connection_call_with_unix_fd_list_sync (GDBusConnection *connection, |
| const gchar *bus_name, |
| const gchar *object_path, |
| const gchar *interface_name, |
| const gchar *method_name, |
| GVariant *parameters, |
| const GVariantType *reply_type, |
| GDBusCallFlags flags, |
| gint timeout_msec, |
| GUnixFDList *fd_list, |
| GUnixFDList **out_fd_list, |
| GCancellable *cancellable, |
| GError **error) |
| { |
| return g_dbus_connection_call_sync_internal (connection, bus_name, object_path, interface_name, method_name, parameters, reply_type, flags, timeout_msec, fd_list, out_fd_list, cancellable, error); |
| } |
| |
| #endif /* G_OS_UNIX */ |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| /* called without lock held in the thread where the caller registered |
| * the subtree |
| */ |
| static gboolean |
| handle_subtree_introspect (GDBusConnection *connection, |
| ExportedSubtree *es, |
| GDBusMessage *message) |
| { |
| GString *s; |
| gboolean handled; |
| GDBusMessage *reply; |
| gchar **children; |
| gboolean is_root; |
| const gchar *sender; |
| const gchar *requested_object_path; |
| const gchar *requested_node; |
| GDBusInterfaceInfo **interfaces; |
| guint n; |
| gchar **subnode_paths; |
| gboolean has_properties_interface; |
| gboolean has_introspectable_interface; |
| |
| handled = FALSE; |
| |
| requested_object_path = g_dbus_message_get_path (message); |
| sender = g_dbus_message_get_sender (message); |
| is_root = (g_strcmp0 (requested_object_path, es->object_path) == 0); |
| |
| s = g_string_new (NULL); |
| introspect_append_header (s); |
| |
| /* Strictly we don't need the children in dynamic mode, but we avoid the |
| * conditionals to preserve code clarity |
| */ |
| children = es->vtable->enumerate (es->connection, |
| sender, |
| es->object_path, |
| es->user_data); |
| |
| if (!is_root) |
| { |
| requested_node = strrchr (requested_object_path, '/') + 1; |
| |
| /* Assert existence of object if we are not dynamic */ |
| if (!(es->flags & G_DBUS_SUBTREE_FLAGS_DISPATCH_TO_UNENUMERATED_NODES) && |
| !g_strv_contains ((const gchar * const *) children, requested_node)) |
| goto out; |
| } |
| else |
| { |
| requested_node = NULL; |
| } |
| |
| interfaces = es->vtable->introspect (es->connection, |
| sender, |
| es->object_path, |
| requested_node, |
| es->user_data); |
| if (interfaces != NULL) |
| { |
| has_properties_interface = FALSE; |
| has_introspectable_interface = FALSE; |
| |
| for (n = 0; interfaces[n] != NULL; n++) |
| { |
| if (strcmp (interfaces[n]->name, "org.freedesktop.DBus.Properties") == 0) |
| has_properties_interface = TRUE; |
| else if (strcmp (interfaces[n]->name, "org.freedesktop.DBus.Introspectable") == 0) |
| has_introspectable_interface = TRUE; |
| } |
| if (!has_properties_interface) |
| g_string_append (s, introspect_properties_interface); |
| if (!has_introspectable_interface) |
| g_string_append (s, introspect_introspectable_interface); |
| |
| for (n = 0; interfaces[n] != NULL; n++) |
| { |
| g_dbus_interface_info_generate_xml (interfaces[n], 2, s); |
| g_dbus_interface_info_unref (interfaces[n]); |
| } |
| g_free (interfaces); |
| } |
| |
| /* then include <node> entries from the Subtree for the root */ |
| if (is_root) |
| { |
| for (n = 0; children != NULL && children[n] != NULL; n++) |
| g_string_append_printf (s, " <node name=\"%s\"/>\n", children[n]); |
| } |
| |
| /* finally include nodes registered below us */ |
| subnode_paths = g_dbus_connection_list_registered (es->connection, requested_object_path); |
| for (n = 0; subnode_paths != NULL && subnode_paths[n] != NULL; n++) |
| g_string_append_printf (s, " <node name=\"%s\"/>\n", subnode_paths[n]); |
| g_strfreev (subnode_paths); |
| |
| g_string_append (s, "</node>\n"); |
| |
| reply = g_dbus_message_new_method_reply (message); |
| g_dbus_message_set_body (reply, g_variant_new ("(s)", s->str)); |
| g_dbus_connection_send_message (connection, reply, G_DBUS_SEND_MESSAGE_FLAGS_NONE, NULL, NULL); |
| g_object_unref (reply); |
| |
| handled = TRUE; |
| |
| out: |
| g_string_free (s, TRUE); |
| g_strfreev (children); |
| return handled; |
| } |
| |
| /* called without lock held in the thread where the caller registered |
| * the subtree |
| */ |
| static gboolean |
| handle_subtree_method_invocation (GDBusConnection *connection, |
| ExportedSubtree *es, |
| GDBusMessage *message) |
| { |
| gboolean handled; |
| const gchar *sender; |
| const gchar *interface_name; |
| const gchar *member; |
| const gchar *signature; |
| const gchar *requested_object_path; |
| const gchar *requested_node; |
| gboolean is_root; |
| GDBusInterfaceInfo *interface_info; |
| const GDBusInterfaceVTable *interface_vtable; |
| gpointer interface_user_data; |
| guint n; |
| GDBusInterfaceInfo **interfaces; |
| gboolean is_property_get; |
| gboolean is_property_set; |
| gboolean is_property_get_all; |
| |
| handled = FALSE; |
| interfaces = NULL; |
| |
| requested_object_path = g_dbus_message_get_path (message); |
| sender = g_dbus_message_get_sender (message); |
| interface_name = g_dbus_message_get_interface (message); |
| member = g_dbus_message_get_member (message); |
| signature = g_dbus_message_get_signature (message); |
| is_root = (g_strcmp0 (requested_object_path, es->object_path) == 0); |
| |
| is_property_get = FALSE; |
| is_property_set = FALSE; |
| is_property_get_all = FALSE; |
| if (g_strcmp0 (interface_name, "org.freedesktop.DBus.Properties") == 0) |
| { |
| if (g_strcmp0 (member, "Get") == 0 && g_strcmp0 (signature, "ss") == 0) |
| is_property_get = TRUE; |
| else if (g_strcmp0 (member, "Set") == 0 && g_strcmp0 (signature, "ssv") == 0) |
| is_property_set = TRUE; |
| else if (g_strcmp0 (member, "GetAll") == 0 && g_strcmp0 (signature, "s") == 0) |
| is_property_get_all = TRUE; |
| } |
| |
| if (!is_root) |
| { |
| requested_node = strrchr (requested_object_path, '/') + 1; |
| |
| if (~es->flags & G_DBUS_SUBTREE_FLAGS_DISPATCH_TO_UNENUMERATED_NODES) |
| { |
| /* We don't want to dispatch to unenumerated |
| * nodes, so ensure that the child exists. |
| */ |
| gchar **children; |
| gboolean exists; |
| |
| children = es->vtable->enumerate (es->connection, |
| sender, |
| es->object_path, |
| es->user_data); |
| |
| exists = g_strv_contains ((const gchar * const *) children, requested_node); |
| g_strfreev (children); |
| |
| if (!exists) |
| goto out; |
| } |
| } |
| else |
| { |
| requested_node = NULL; |
| } |
| |
| /* get introspection data for the node */ |
| interfaces = es->vtable->introspect (es->connection, |
| sender, |
| requested_object_path, |
| requested_node, |
| es->user_data); |
| |
| if (interfaces == NULL) |
| goto out; |
| |
| interface_info = NULL; |
| for (n = 0; interfaces[n] != NULL; n++) |
| { |
| if (g_strcmp0 (interfaces[n]->name, interface_name) == 0) |
| interface_info = interfaces[n]; |
| } |
| |
| /* dispatch the call if the user wants to handle it */ |
| if (interface_info != NULL) |
| { |
| /* figure out where to dispatch the method call */ |
| interface_user_data = NULL; |
| interface_vtable = es->vtable->dispatch (es->connection, |
| sender, |
| es->object_path, |
| interface_name, |
| requested_node, |
| &interface_user_data, |
| es->user_data); |
| if (interface_vtable == NULL) |
| goto out; |
| |
| CONNECTION_LOCK (connection); |
| handled = validate_and_maybe_schedule_method_call (es->connection, |
| message, |
| 0, |
| es->id, |
| interface_info, |
| interface_vtable, |
| es->context, |
| interface_user_data); |
| CONNECTION_UNLOCK (connection); |
| } |
| /* handle org.freedesktop.DBus.Properties interface if not explicitly handled */ |
| else if (is_property_get || is_property_set || is_property_get_all) |
| { |
| if (is_property_get) |
| g_variant_get (g_dbus_message_get_body (message), "(&s&s)", &interface_name, NULL); |
| else if (is_property_set) |
| g_variant_get (g_dbus_message_get_body (message), "(&s&sv)", &interface_name, NULL, NULL); |
| else if (is_property_get_all) |
| g_variant_get (g_dbus_message_get_body (message), "(&s)", &interface_name, NULL, NULL); |
| else |
| g_assert_not_reached (); |
| |
| /* see if the object supports this interface at all */ |
| for (n = 0; interfaces[n] != NULL; n++) |
| { |
| if (g_strcmp0 (interfaces[n]->name, interface_name) == 0) |
| interface_info = interfaces[n]; |
| } |
| |
| /* Fail with org.freedesktop.DBus.Error.InvalidArgs if the user-code |
| * claims it won't support the interface |
| */ |
| if (interface_info == NULL) |
| { |
| GDBusMessage *reply; |
| reply = g_dbus_message_new_method_error (message, |
| "org.freedesktop.DBus.Error.InvalidArgs", |
| _("No such interface “%s”"), |
| interface_name); |
| g_dbus_connection_send_message (es->connection, reply, G_DBUS_SEND_MESSAGE_FLAGS_NONE, NULL, NULL); |
| g_object_unref (reply); |
| handled = TRUE; |
| goto out; |
| } |
| |
| /* figure out where to dispatch the property get/set/getall calls */ |
| interface_user_data = NULL; |
| interface_vtable = es->vtable->dispatch (es->connection, |
| sender, |
| es->object_path, |
| interface_name, |
| requested_node, |
| &interface_user_data, |
| es->user_data); |
| if (interface_vtable == NULL) |
| { |
| g_warning ("The subtree introspection function indicates that '%s' " |
| "is a valid interface name, but calling the dispatch " |
| "function on that interface gave us NULL", interface_name); |
| goto out; |
| } |
| |
| if (is_property_get || is_property_set) |
| { |
| CONNECTION_LOCK (connection); |
| handled = validate_and_maybe_schedule_property_getset (es->connection, |
| message, |
| 0, |
| es->id, |
| is_property_get, |
| interface_info, |
| interface_vtable, |
| es->context, |
| interface_user_data); |
| CONNECTION_UNLOCK (connection); |
| } |
| else if (is_property_get_all) |
| { |
| CONNECTION_LOCK (connection); |
| handled = validate_and_maybe_schedule_property_get_all (es->connection, |
| message, |
| 0, |
| es->id, |
| interface_info, |
| interface_vtable, |
| es->context, |
| interface_user_data); |
| CONNECTION_UNLOCK (connection); |
| } |
| } |
| |
| out: |
| if (interfaces != NULL) |
| { |
| for (n = 0; interfaces[n] != NULL; n++) |
| g_dbus_interface_info_unref (interfaces[n]); |
| g_free (interfaces); |
| } |
| |
| return handled; |
| } |
| |
| typedef struct |
| { |
| GDBusMessage *message; /* (owned) */ |
| ExportedSubtree *es; /* (owned) */ |
| } SubtreeDeferredData; |
| |
| static void |
| subtree_deferred_data_free (SubtreeDeferredData *data) |
| { |
| g_clear_object (&data->message); |
| exported_subtree_unref (data->es); |
| g_free (data); |
| } |
| |
| /* called without lock held in the thread where the caller registered the subtree */ |
| static gboolean |
| process_subtree_vtable_message_in_idle_cb (gpointer _data) |
| { |
| SubtreeDeferredData *data = _data; |
| gboolean handled; |
| |
| handled = FALSE; |
| |
| if (g_strcmp0 (g_dbus_message_get_interface (data->message), "org.freedesktop.DBus.Introspectable") == 0 && |
| g_strcmp0 (g_dbus_message_get_member (data->message), "Introspect") == 0 && |
| g_strcmp0 (g_dbus_message_get_signature (data->message), "") == 0) |
| handled = handle_subtree_introspect (data->es->connection, |
| data->es, |
| data->message); |
| else |
| handled = handle_subtree_method_invocation (data->es->connection, |
| data->es, |
| data->message); |
| |
| if (!handled) |
| { |
| CONNECTION_LOCK (data->es->connection); |
| handled = handle_generic_unlocked (data->es->connection, data->message); |
| CONNECTION_UNLOCK (data->es->connection); |
| } |
| |
| /* if we couldn't handle the request, just bail with the UnknownMethod error */ |
| if (!handled) |
| { |
| GDBusMessage *reply; |
| reply = g_dbus_message_new_method_error (data->message, |
| "org.freedesktop.DBus.Error.UnknownMethod", |
| _("Method “%s” on interface “%s” with signature “%s” does not exist"), |
| g_dbus_message_get_member (data->message), |
| g_dbus_message_get_interface (data->message), |
| g_dbus_message_get_signature (data->message)); |
| g_dbus_connection_send_message (data->es->connection, reply, G_DBUS_SEND_MESSAGE_FLAGS_NONE, NULL, NULL); |
| g_object_unref (reply); |
| } |
| |
| return FALSE; |
| } |
| |
| /* called in GDBusWorker thread with connection's lock held */ |
| static gboolean |
| subtree_message_func (GDBusConnection *connection, |
| ExportedSubtree *es, |
| GDBusMessage *message) |
| { |
| GSource *idle_source; |
| SubtreeDeferredData *data; |
| |
| data = g_new0 (SubtreeDeferredData, 1); |
| data->message = g_object_ref (message); |
| data->es = exported_subtree_ref (es); |
| |
| /* defer this call to an idle handler in the right thread */ |
| idle_source = g_idle_source_new (); |
| g_source_set_priority (idle_source, G_PRIORITY_HIGH); |
| g_source_set_callback (idle_source, |
| process_subtree_vtable_message_in_idle_cb, |
| data, |
| (GDestroyNotify) subtree_deferred_data_free); |
| g_source_set_static_name (idle_source, "[gio] process_subtree_vtable_message_in_idle_cb"); |
| g_source_attach (idle_source, es->context); |
| g_source_unref (idle_source); |
| |
| /* since we own the entire subtree, handlers for objects not in the subtree have been |
| * tried already by libdbus-1 - so we just need to ensure that we're always going |
| * to reply to the message |
| */ |
| return TRUE; |
| } |
| |
| /** |
| * g_dbus_connection_register_subtree: |
| * @connection: a #GDBusConnection |
| * @object_path: the object path to register the subtree at |
| * @vtable: a #GDBusSubtreeVTable to enumerate, introspect and |
| * dispatch nodes in the subtree |
| * @flags: flags used to fine tune the behavior of the subtree |
| * @user_data: data to pass to functions in @vtable |
| * @user_data_free_func: function to call when the subtree is unregistered |
| * @error: return location for error or %NULL |
| * |
| * Registers a whole subtree of dynamic objects. |
| * |
| * The @enumerate and @introspection functions in @vtable are used to |
| * convey, to remote callers, what nodes exist in the subtree rooted |
| * by @object_path. |
| * |
| * When handling remote calls into any node in the subtree, first the |
| * @enumerate function is used to check if the node exists. If the node exists |
| * or the %G_DBUS_SUBTREE_FLAGS_DISPATCH_TO_UNENUMERATED_NODES flag is set |
| * the @introspection function is used to check if the node supports the |
| * requested method. If so, the @dispatch function is used to determine |
| * where to dispatch the call. The collected #GDBusInterfaceVTable and |
| * #gpointer will be used to call into the interface vtable for processing |
| * the request. |
| * |
| * All calls into user-provided code will be invoked in the |
| * [thread-default main context][g-main-context-push-thread-default] |
| * of the thread you are calling this method from. |
| * |
| * If an existing subtree is already registered at @object_path or |
| * then @error is set to %G_IO_ERROR_EXISTS. |
| * |
| * Note that it is valid to register regular objects (using |
| * g_dbus_connection_register_object()) in a subtree registered with |
| * g_dbus_connection_register_subtree() - if so, the subtree handler |
| * is tried as the last resort. One way to think about a subtree |
| * handler is to consider it a fallback handler for object paths not |
| * registered via g_dbus_connection_register_object() or other bindings. |
| * |
| * Note that @vtable will be copied so you cannot change it after |
| * registration. |
| * |
| * See this [server][gdbus-subtree-server] for an example of how to use |
| * this method. |
| * |
| * Returns: 0 if @error is set, otherwise a subtree registration ID (never 0) |
| * that can be used with g_dbus_connection_unregister_subtree() |
| * |
| * Since: 2.26 |
| */ |
| guint |
| g_dbus_connection_register_subtree (GDBusConnection *connection, |
| const gchar *object_path, |
| const GDBusSubtreeVTable *vtable, |
| GDBusSubtreeFlags flags, |
| gpointer user_data, |
| GDestroyNotify user_data_free_func, |
| GError **error) |
| { |
| guint ret; |
| ExportedSubtree *es; |
| |
| g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), 0); |
| g_return_val_if_fail (object_path != NULL && g_variant_is_object_path (object_path), 0); |
| g_return_val_if_fail (vtable != NULL, 0); |
| g_return_val_if_fail (error == NULL || *error == NULL, 0); |
| g_return_val_if_fail (check_initialized (connection), 0); |
| |
| ret = 0; |
| |
| CONNECTION_LOCK (connection); |
| |
| es = g_hash_table_lookup (connection->map_object_path_to_es, object_path); |
| if (es != NULL) |
| { |
| g_set_error (error, |
| G_IO_ERROR, |
| G_IO_ERROR_EXISTS, |
| _("A subtree is already exported for %s"), |
| object_path); |
| goto out; |
| } |
| |
| es = g_new0 (ExportedSubtree, 1); |
| es->refcount = 1; |
| es->object_path = g_strdup (object_path); |
| es->connection = connection; |
| |
| es->vtable = _g_dbus_subtree_vtable_copy (vtable); |
| es->flags = flags; |
| es->id = (guint) g_atomic_int_add (&_global_subtree_registration_id, 1); /* TODO: overflow etc. */ |
| es->user_data = user_data; |
| es->user_data_free_func = user_data_free_func; |
| es->context = g_main_context_ref_thread_default (); |
| |
| g_hash_table_insert (connection->map_object_path_to_es, es->object_path, es); |
| g_hash_table_insert (connection->map_id_to_es, |
| GUINT_TO_POINTER (es->id), |
| es); |
| |
| ret = es->id; |
| |
| out: |
| CONNECTION_UNLOCK (connection); |
| |
| return ret; |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| /** |
| * g_dbus_connection_unregister_subtree: |
| * @connection: a #GDBusConnection |
| * @registration_id: a subtree registration id obtained from |
| * g_dbus_connection_register_subtree() |
| * |
| * Unregisters a subtree. |
| * |
| * Returns: %TRUE if the subtree was unregistered, %FALSE otherwise |
| * |
| * Since: 2.26 |
| */ |
| gboolean |
| g_dbus_connection_unregister_subtree (GDBusConnection *connection, |
| guint registration_id) |
| { |
| ExportedSubtree *es; |
| gboolean ret; |
| |
| g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE); |
| g_return_val_if_fail (check_initialized (connection), FALSE); |
| |
| ret = FALSE; |
| |
| CONNECTION_LOCK (connection); |
| |
| es = g_hash_table_lookup (connection->map_id_to_es, |
| GUINT_TO_POINTER (registration_id)); |
| if (es == NULL) |
| goto out; |
| |
| g_warn_if_fail (g_hash_table_remove (connection->map_id_to_es, GUINT_TO_POINTER (es->id))); |
| g_warn_if_fail (g_hash_table_remove (connection->map_object_path_to_es, es->object_path)); |
| |
| ret = TRUE; |
| |
| out: |
| CONNECTION_UNLOCK (connection); |
| |
| return ret; |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| /* may be called in any thread, with connection's lock held */ |
| static void |
| handle_generic_ping_unlocked (GDBusConnection *connection, |
| const gchar *object_path, |
| GDBusMessage *message) |
| { |
| GDBusMessage *reply; |
| reply = g_dbus_message_new_method_reply (message); |
| g_dbus_connection_send_message_unlocked (connection, reply, G_DBUS_SEND_MESSAGE_FLAGS_NONE, NULL, NULL); |
| g_object_unref (reply); |
| } |
| |
| /* may be called in any thread, with connection's lock held */ |
| static void |
| handle_generic_get_machine_id_unlocked (GDBusConnection *connection, |
| const gchar *object_path, |
| GDBusMessage *message) |
| { |
| GDBusMessage *reply; |
| |
| reply = NULL; |
| if (connection->machine_id == NULL) |
| { |
| GError *error; |
| |
| error = NULL; |
| connection->machine_id = _g_dbus_get_machine_id (&error); |
| if (connection->machine_id == NULL) |
| { |
| reply = g_dbus_message_new_method_error_literal (message, |
| "org.freedesktop.DBus.Error.Failed", |
| error->message); |
| g_error_free (error); |
| } |
| } |
| |
| if (reply == NULL) |
| { |
| reply = g_dbus_message_new_method_reply (message); |
| g_dbus_message_set_body (reply, g_variant_new ("(s)", connection->machine_id)); |
| } |
| g_dbus_connection_send_message_unlocked (connection, reply, G_DBUS_SEND_MESSAGE_FLAGS_NONE, NULL, NULL); |
| g_object_unref (reply); |
| } |
| |
| /* may be called in any thread, with connection's lock held */ |
| static void |
| handle_generic_introspect_unlocked (GDBusConnection *connection, |
| const gchar *object_path, |
| GDBusMessage *message) |
| { |
| guint n; |
| GString *s; |
| gchar **registered; |
| GDBusMessage *reply; |
| |
| /* first the header */ |
| s = g_string_new (NULL); |
| introspect_append_header (s); |
| |
| registered = g_dbus_connection_list_registered_unlocked (connection, object_path); |
| for (n = 0; registered != NULL && registered[n] != NULL; n++) |
| g_string_append_printf (s, " <node name=\"%s\"/>\n", registered[n]); |
| g_strfreev (registered); |
| g_string_append (s, "</node>\n"); |
| |
| reply = g_dbus_message_new_method_reply (message); |
| g_dbus_message_set_body (reply, g_variant_new ("(s)", s->str)); |
| g_dbus_connection_send_message_unlocked (connection, reply, G_DBUS_SEND_MESSAGE_FLAGS_NONE, NULL, NULL); |
| g_object_unref (reply); |
| g_string_free (s, TRUE); |
| } |
| |
| /* may be called in any thread, with connection's lock held */ |
| static gboolean |
| handle_generic_unlocked (GDBusConnection *connection, |
| GDBusMessage *message) |
| { |
| gboolean handled; |
| const gchar *interface_name; |
| const gchar *member; |
| const gchar *signature; |
| const gchar *path; |
| |
| CONNECTION_ENSURE_LOCK (connection); |
| |
| handled = FALSE; |
| |
| interface_name = g_dbus_message_get_interface (message); |
| member = g_dbus_message_get_member (message); |
| signature = g_dbus_message_get_signature (message); |
| path = g_dbus_message_get_path (message); |
| |
| if (g_strcmp0 (interface_name, "org.freedesktop.DBus.Introspectable") == 0 && |
| g_strcmp0 (member, "Introspect") == 0 && |
| g_strcmp0 (signature, "") == 0) |
| { |
| handle_generic_introspect_unlocked (connection, path, message); |
| handled = TRUE; |
| } |
| else if (g_strcmp0 (interface_name, "org.freedesktop.DBus.Peer") == 0 && |
| g_strcmp0 (member, "Ping") == 0 && |
| g_strcmp0 (signature, "") == 0) |
| { |
| handle_generic_ping_unlocked (connection, path, message); |
| handled = TRUE; |
| } |
| else if (g_strcmp0 (interface_name, "org.freedesktop.DBus.Peer") == 0 && |
| g_strcmp0 (member, "GetMachineId") == 0 && |
| g_strcmp0 (signature, "") == 0) |
| { |
| handle_generic_get_machine_id_unlocked (connection, path, message); |
| handled = TRUE; |
| } |
| |
| return handled; |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| /* called in GDBusWorker thread with connection's lock held */ |
| static void |
| distribute_method_call (GDBusConnection *connection, |
| GDBusMessage *message) |
| { |
| GDBusMessage *reply; |
| ExportedObject *eo; |
| ExportedSubtree *es; |
| const gchar *path; |
| const gchar *interface_name; |
| const gchar *member; |
| gchar *subtree_path; |
| gchar *needle; |
| gboolean object_found = FALSE; |
| |
| g_assert (g_dbus_message_get_message_type (message) == G_DBUS_MESSAGE_TYPE_METHOD_CALL); |
| |
| /* these are required, and should have been validated by validate_headers() |
| * in gdbusmessage.c already */ |
| member = g_dbus_message_get_member (message); |
| path = g_dbus_message_get_path (message); |
| |
| g_assert (member != NULL); |
| g_assert (path != NULL); |
| |
| /* this is optional */ |
| interface_name = g_dbus_message_get_interface (message); |
| |
| subtree_path = g_strdup (path); |
| needle = strrchr (subtree_path, '/'); |
| if (needle != NULL && needle != subtree_path) |
| { |
| *needle = '\0'; |
| } |
| else |
| { |
| g_free (subtree_path); |
| subtree_path = NULL; |
| } |
| |
| if (G_UNLIKELY (_g_dbus_debug_incoming ())) |
| { |
| _g_dbus_debug_print_lock (); |
| g_print ("========================================================================\n" |
| "GDBus-debug:Incoming:\n" |
| " <<<< METHOD INVOCATION %s.%s()\n" |
| " on object %s\n" |
| " invoked by name %s\n" |
| " serial %d\n", |
| interface_name, member, |
| path, |
| g_dbus_message_get_sender (message) != NULL ? g_dbus_message_get_sender (message) : "(none)", |
| g_dbus_message_get_serial (message)); |
| _g_dbus_debug_print_unlock (); |
| } |
| |
| eo = g_hash_table_lookup (connection->map_object_path_to_eo, path); |
| if (eo != NULL) |
| { |
| if (obj_message_func (connection, eo, message, &object_found)) |
| goto out; |
| } |
| |
| es = g_hash_table_lookup (connection->map_object_path_to_es, path); |
| if (es != NULL) |
| { |
| if (subtree_message_func (connection, es, message)) |
| goto out; |
| } |
| |
| if (subtree_path != NULL) |
| { |
| es = g_hash_table_lookup (connection->map_object_path_to_es, subtree_path); |
| if (es != NULL) |
| { |
| if (subtree_message_func (connection, es, message)) |
| goto out; |
| } |
| } |
| |
| if (handle_generic_unlocked (connection, message)) |
| goto out; |
| |
| /* if we end up here, the message has not been not handled - so return an error saying this */ |
| if (object_found == TRUE) |
| { |
| reply = g_dbus_message_new_method_error (message, |
| "org.freedesktop.DBus.Error.UnknownMethod", |
| _("No such interface “%s” on object at path %s"), |
| interface_name, |
| path); |
| } |
| else |
| { |
| reply = g_dbus_message_new_method_error (message, |
| "org.freedesktop.DBus.Error.UnknownMethod", |
| _("Object does not exist at path “%s”"), |
| path); |
| } |
| |
| g_dbus_connection_send_message_unlocked (connection, reply, G_DBUS_SEND_MESSAGE_FLAGS_NONE, NULL, NULL); |
| g_object_unref (reply); |
| |
| out: |
| g_free (subtree_path); |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| /* Called in any user thread, with the message_bus_lock held. */ |
| static GWeakRef * |
| message_bus_get_singleton (GBusType bus_type, |
| GError **error) |
| { |
| GWeakRef *ret; |
| const gchar *starter_bus; |
| |
| ret = NULL; |
| |
| switch (bus_type) |
| { |
| case G_BUS_TYPE_SESSION: |
| ret = &the_session_bus; |
| break; |
| |
| case G_BUS_TYPE_SYSTEM: |
| ret = &the_system_bus; |
| break; |
| |
| case G_BUS_TYPE_STARTER: |
| starter_bus = g_getenv ("DBUS_STARTER_BUS_TYPE"); |
| if (g_strcmp0 (starter_bus, "session") == 0) |
| { |
| ret = message_bus_get_singleton (G_BUS_TYPE_SESSION, error); |
| goto out; |
| } |
| else if (g_strcmp0 (starter_bus, "system") == 0) |
| { |
| ret = message_bus_get_singleton (G_BUS_TYPE_SYSTEM, error); |
| goto out; |
| } |
| else |
| { |
| if (starter_bus != NULL) |
| { |
| g_set_error (error, |
| G_IO_ERROR, |
| G_IO_ERROR_INVALID_ARGUMENT, |
| _("Cannot determine bus address from DBUS_STARTER_BUS_TYPE environment variable" |
| " — unknown value “%s”"), |
| starter_bus); |
| } |
| else |
| { |
| g_set_error_literal (error, |
| G_IO_ERROR, |
| G_IO_ERROR_INVALID_ARGUMENT, |
| _("Cannot determine bus address because the DBUS_STARTER_BUS_TYPE environment " |
| "variable is not set")); |
| } |
| } |
| break; |
| |
| default: |
| g_assert_not_reached (); |
| break; |
| } |
| |
| out: |
| return ret; |
| } |
| |
| /* Called in any user thread, without holding locks. */ |
| static GDBusConnection * |
| get_uninitialized_connection (GBusType bus_type, |
| GCancellable *cancellable, |
| GError **error) |
| { |
| GWeakRef *singleton; |
| GDBusConnection *ret; |
| |
| ret = NULL; |
| |
| G_LOCK (message_bus_lock); |
| singleton = message_bus_get_singleton (bus_type, error); |
| if (singleton == NULL) |
| goto out; |
| |
| ret = g_weak_ref_get (singleton); |
| |
| if (ret == NULL) |
| { |
| gchar *address; |
| address = g_dbus_address_get_for_bus_sync (bus_type, cancellable, error); |
| if (address == NULL) |
| goto out; |
| ret = g_object_new (G_TYPE_DBUS_CONNECTION, |
| "address", address, |
| "flags", G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT | |
| #ifdef __linux__ |
| G_DBUS_CONNECTION_FLAGS_CROSS_NAMESPACE | |
| #endif |
| G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION, |
| "exit-on-close", TRUE, |
| NULL); |
| |
| g_weak_ref_set (singleton, ret); |
| g_free (address); |
| } |
| |
| g_assert (ret != NULL); |
| |
| out: |
| G_UNLOCK (message_bus_lock); |
| return ret; |
| } |
| |
| /* May be called from any thread. Must not hold message_bus_lock. */ |
| GDBusConnection * |
| _g_bus_get_singleton_if_exists (GBusType bus_type) |
| { |
| GWeakRef *singleton; |
| GDBusConnection *ret = NULL; |
| |
| G_LOCK (message_bus_lock); |
| singleton = message_bus_get_singleton (bus_type, NULL); |
| if (singleton == NULL) |
| goto out; |
| |
| ret = g_weak_ref_get (singleton); |
| |
| out: |
| G_UNLOCK (message_bus_lock); |
| return ret; |
| } |
| |
| /* May be called from any thread. Must not hold message_bus_lock. */ |
| void |
| _g_bus_forget_singleton (GBusType bus_type) |
| { |
| GWeakRef *singleton; |
| |
| G_LOCK (message_bus_lock); |
| |
| singleton = message_bus_get_singleton (bus_type, NULL); |
| |
| if (singleton != NULL) |
| g_weak_ref_set (singleton, NULL); |
| |
| G_UNLOCK (message_bus_lock); |
| } |
| |
| /** |
| * g_bus_get_sync: |
| * @bus_type: a #GBusType |
| * @cancellable: (nullable): a #GCancellable or %NULL |
| * @error: return location for error or %NULL |
| * |
| * Synchronously connects to the message bus specified by @bus_type. |
| * Note that the returned object may shared with other callers, |
| * e.g. if two separate parts of a process calls this function with |
| * the same @bus_type, they will share the same object. |
| * |
| * This is a synchronous failable function. See g_bus_get() and |
| * g_bus_get_finish() for the asynchronous version. |
| * |
| * The returned object is a singleton, that is, shared with other |
| * callers of g_bus_get() and g_bus_get_sync() for @bus_type. In the |
| * event that you need a private message bus connection, use |
| * g_dbus_address_get_for_bus_sync() and |
| * g_dbus_connection_new_for_address() with |
| * G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT and |
| * G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION flags. |
| * |
| * Note that the returned #GDBusConnection object will (usually) have |
| * the #GDBusConnection:exit-on-close property set to %TRUE. |
| * |
| * Returns: (transfer full): a #GDBusConnection or %NULL if @error is set. |
| * Free with g_object_unref(). |
| * |
| * Since: 2.26 |
| */ |
| GDBusConnection * |
| g_bus_get_sync (GBusType bus_type, |
| GCancellable *cancellable, |
| GError **error) |
| { |
| GDBusConnection *connection; |
| |
| _g_dbus_initialize (); |
| |
| g_return_val_if_fail (error == NULL || *error == NULL, NULL); |
| |
| connection = get_uninitialized_connection (bus_type, cancellable, error); |
| if (connection == NULL) |
| goto out; |
| |
| if (!g_initable_init (G_INITABLE (connection), cancellable, error)) |
| { |
| g_object_unref (connection); |
| connection = NULL; |
| } |
| |
| out: |
| return connection; |
| } |
| |
| static void |
| bus_get_async_initable_cb (GObject *source_object, |
| GAsyncResult *res, |
| gpointer user_data) |
| { |
| GTask *task = user_data; |
| GError *error = NULL; |
| |
| if (!g_async_initable_init_finish (G_ASYNC_INITABLE (source_object), |
| res, |
| &error)) |
| { |
| g_assert (error != NULL); |
| g_task_return_error (task, error); |
| g_object_unref (source_object); |
| } |
| else |
| { |
| g_task_return_pointer (task, source_object, g_object_unref); |
| } |
| g_object_unref (task); |
| } |
| |
| /** |
| * g_bus_get: |
| * @bus_type: a #GBusType |
| * @cancellable: (nullable): a #GCancellable or %NULL |
| * @callback: a #GAsyncReadyCallback to call when the request is satisfied |
| * @user_data: the data to pass to @callback |
| * |
| * Asynchronously connects to the message bus specified by @bus_type. |
| * |
| * When the operation is finished, @callback will be invoked. You can |
| * then call g_bus_get_finish() to get the result of the operation. |
| * |
| * This is an asynchronous failable function. See g_bus_get_sync() for |
| * the synchronous version. |
| * |
| * Since: 2.26 |
| */ |
| void |
| g_bus_get (GBusType bus_type, |
| GCancellable *cancellable, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GDBusConnection *connection; |
| GTask *task; |
| GError *error = NULL; |
| |
| _g_dbus_initialize (); |
| |
| task = g_task_new (NULL, cancellable, callback, user_data); |
| g_task_set_source_tag (task, g_bus_get); |
| |
| connection = get_uninitialized_connection (bus_type, cancellable, &error); |
| if (connection == NULL) |
| { |
| g_assert (error != NULL); |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| } |
| else |
| { |
| g_async_initable_init_async (G_ASYNC_INITABLE (connection), |
| G_PRIORITY_DEFAULT, |
| cancellable, |
| bus_get_async_initable_cb, |
| task); |
| } |
| } |
| |
| /** |
| * g_bus_get_finish: |
| * @res: a #GAsyncResult obtained from the #GAsyncReadyCallback passed |
| * to g_bus_get() |
| * @error: return location for error or %NULL |
| * |
| * Finishes an operation started with g_bus_get(). |
| * |
| * The returned object is a singleton, that is, shared with other |
| * callers of g_bus_get() and g_bus_get_sync() for @bus_type. In the |
| * event that you need a private message bus connection, use |
| * g_dbus_address_get_for_bus_sync() and |
| * g_dbus_connection_new_for_address() with |
| * G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT and |
| * G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION flags. |
| * |
| * Note that the returned #GDBusConnection object will (usually) have |
| * the #GDBusConnection:exit-on-close property set to %TRUE. |
| * |
| * Returns: (transfer full): a #GDBusConnection or %NULL if @error is set. |
| * Free with g_object_unref(). |
| * |
| * Since: 2.26 |
| */ |
| GDBusConnection * |
| g_bus_get_finish (GAsyncResult *res, |
| GError **error) |
| { |
| g_return_val_if_fail (g_task_is_valid (res, NULL), NULL); |
| g_return_val_if_fail (error == NULL || *error == NULL, NULL); |
| |
| return g_task_propagate_pointer (G_TASK (res), error); |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |