glib/gio/gdbusnamewatching.c
Ryan Lortie 3160bcad6a GDBusConnection.call(): add 'reply_type' argument
This allows the caller to specify the reply type that they are expecting
for this call.  If the reply comes back with the wrong type, GDBus will
generate an appropriate error internally.

  - add a GVariantType * argument to g_dbus_connection_call() and
    _call_sync().

  - move the internal API for computing message types from introspection
    data to be based on GVariantType instead of strings.  Update users
    of this code.

  - have GDBusProxy pass this calculated GVariantType into
    g_dbus_connection_call().  Remove the checks done in GDBusProxy.

  - Update other users of the code (test cases, gdbus-tool, GSettings
    tool, etc).  In some cases, remove redundant checks; in some other
    cases, we are fixing bugs because no checking was done where it
    should have been.

Closes bug #619391.
2010-05-24 17:00:04 -04:00

694 lines
22 KiB
C

/* GDBus - GLib D-Bus Library
*
* Copyright (C) 2008-2010 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307, USA.
*
* Author: David Zeuthen <davidz@redhat.com>
*/
#include "config.h"
#include <stdlib.h>
#include <string.h>
#include "gdbusutils.h"
#include "gdbusnamewatching.h"
#include "gdbuserror.h"
#include "gdbusprivate.h"
#include "gdbusconnection.h"
#include "glibintl.h"
#include "gioalias.h"
/**
* SECTION:gdbusnamewatching
* @title: Watching Bus Names
* @short_description: Simple API for watching bus names
* @include: gio/gio.h
*
* Convenience API for watching bus names.
*
* <example id="gdbus-watching-names"><title>Simple application watching a name</title><programlisting><xi:include xmlns:xi="http://www.w3.org/2001/XInclude" parse="text" href="../../../../gio/tests/gdbus-example-watch-name.c"><xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback></xi:include></programlisting></example>
*/
G_LOCK_DEFINE_STATIC (lock);
/* ---------------------------------------------------------------------------------------------------- */
typedef enum
{
PREVIOUS_CALL_NONE = 0,
PREVIOUS_CALL_APPEARED,
PREVIOUS_CALL_VANISHED,
} PreviousCall;
typedef struct
{
volatile gint ref_count;
guint id;
gchar *name;
GBusNameWatcherFlags flags;
gchar *name_owner;
GBusNameAppearedCallback name_appeared_handler;
GBusNameVanishedCallback name_vanished_handler;
gpointer user_data;
GDestroyNotify user_data_free_func;
GMainContext *main_context;
GDBusConnection *connection;
gulong disconnected_signal_handler_id;
guint name_owner_changed_subscription_id;
PreviousCall previous_call;
gboolean cancelled;
gboolean initialized;
} Client;
static guint next_global_id = 1;
static GHashTable *map_id_to_client = NULL;
static Client *
client_ref (Client *client)
{
g_atomic_int_inc (&client->ref_count);
return client;
}
static void
client_unref (Client *client)
{
if (g_atomic_int_dec_and_test (&client->ref_count))
{
if (client->connection != NULL)
{
if (client->name_owner_changed_subscription_id > 0)
g_dbus_connection_signal_unsubscribe (client->connection, client->name_owner_changed_subscription_id);
if (client->disconnected_signal_handler_id > 0)
g_signal_handler_disconnect (client->connection, client->disconnected_signal_handler_id);
g_object_unref (client->connection);
}
g_free (client->name);
g_free (client->name_owner);
if (client->main_context != NULL)
g_main_context_unref (client->main_context);
if (client->user_data_free_func != NULL)
client->user_data_free_func (client->user_data);
g_free (client);
}
}
/* ---------------------------------------------------------------------------------------------------- */
typedef enum
{
CALL_TYPE_NAME_APPEARED,
CALL_TYPE_NAME_VANISHED
} CallType;
typedef struct
{
Client *client;
/* keep this separate because client->connection may
* be set to NULL after scheduling the call
*/
GDBusConnection *connection;
/* ditto */
gchar *name_owner;
CallType call_type;
} CallHandlerData;
static void
call_handler_data_free (CallHandlerData *data)
{
if (data->connection != NULL)
g_object_unref (data->connection);
g_free (data->name_owner);
client_unref (data->client);
g_free (data);
}
static void
actually_do_call (Client *client, GDBusConnection *connection, const gchar *name_owner, CallType call_type)
{
switch (call_type)
{
case CALL_TYPE_NAME_APPEARED:
if (client->name_appeared_handler != NULL)
{
client->name_appeared_handler (connection,
client->name,
name_owner,
client->user_data);
}
break;
case CALL_TYPE_NAME_VANISHED:
if (client->name_vanished_handler != NULL)
{
client->name_vanished_handler (connection,
client->name,
client->user_data);
}
break;
default:
g_assert_not_reached ();
break;
}
}
static gboolean
call_in_idle_cb (gpointer _data)
{
CallHandlerData *data = _data;
actually_do_call (data->client, data->connection, data->name_owner, data->call_type);
return FALSE;
}
static void
schedule_call_in_idle (Client *client, CallType call_type)
{
CallHandlerData *data;
GSource *idle_source;
data = g_new0 (CallHandlerData, 1);
data->client = client_ref (client);
data->connection = client->connection != NULL ? g_object_ref (client->connection) : NULL;
data->name_owner = g_strdup (client->name_owner);
data->call_type = call_type;
idle_source = g_idle_source_new ();
g_source_set_priority (idle_source, G_PRIORITY_HIGH);
g_source_set_callback (idle_source,
call_in_idle_cb,
data,
(GDestroyNotify) call_handler_data_free);
g_source_attach (idle_source, client->main_context);
g_source_unref (idle_source);
}
static void
do_call (Client *client, CallType call_type)
{
/* only schedule in idle if we're not in the right thread */
if (g_main_context_get_thread_default () != client->main_context)
schedule_call_in_idle (client, call_type);
else
actually_do_call (client, client->connection, client->name_owner, call_type);
}
static void
call_appeared_handler (Client *client)
{
if (client->previous_call != PREVIOUS_CALL_APPEARED)
{
client->previous_call = PREVIOUS_CALL_APPEARED;
if (!client->cancelled && client->name_appeared_handler != NULL)
{
do_call (client, CALL_TYPE_NAME_APPEARED);
}
}
}
static void
call_vanished_handler (Client *client,
gboolean ignore_cancelled)
{
if (client->previous_call != PREVIOUS_CALL_VANISHED)
{
client->previous_call = PREVIOUS_CALL_VANISHED;
if (((!client->cancelled) || ignore_cancelled) && client->name_vanished_handler != NULL)
{
do_call (client, CALL_TYPE_NAME_VANISHED);
}
}
}
/* ---------------------------------------------------------------------------------------------------- */
static void
on_connection_disconnected (GDBusConnection *connection,
gboolean remote_peer_vanished,
GError *error,
gpointer user_data)
{
Client *client = user_data;
if (client->name_owner_changed_subscription_id > 0)
g_dbus_connection_signal_unsubscribe (client->connection, client->name_owner_changed_subscription_id);
if (client->disconnected_signal_handler_id > 0)
g_signal_handler_disconnect (client->connection, client->disconnected_signal_handler_id);
g_object_unref (client->connection);
client->disconnected_signal_handler_id = 0;
client->name_owner_changed_subscription_id = 0;
client->connection = NULL;
call_vanished_handler (client, FALSE);
}
/* ---------------------------------------------------------------------------------------------------- */
static void
on_name_owner_changed (GDBusConnection *connection,
const gchar *sender_name,
const gchar *object_path,
const gchar *interface_name,
const gchar *signal_name,
GVariant *parameters,
gpointer user_data)
{
Client *client = user_data;
const gchar *name;
const gchar *old_owner;
const gchar *new_owner;
if (!client->initialized)
goto out;
if (g_strcmp0 (object_path, "/org/freedesktop/DBus") != 0 ||
g_strcmp0 (interface_name, "org.freedesktop.DBus") != 0 ||
g_strcmp0 (sender_name, "org.freedesktop.DBus") != 0)
goto out;
g_variant_get (parameters,
"(&s&s&s)",
&name,
&old_owner,
&new_owner);
/* we only care about a specific name */
if (g_strcmp0 (name, client->name) != 0)
goto out;
if ((old_owner != NULL && strlen (old_owner) > 0) && client->name_owner != NULL)
{
g_free (client->name_owner);
client->name_owner = NULL;
call_vanished_handler (client, FALSE);
}
if (new_owner != NULL && strlen (new_owner) > 0)
{
g_warn_if_fail (client->name_owner == NULL);
g_free (client->name_owner);
client->name_owner = g_strdup (new_owner);
call_appeared_handler (client);
}
out:
;
}
/* ---------------------------------------------------------------------------------------------------- */
static void
get_name_owner_cb (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
Client *client = user_data;
GVariant *result;
const char *name_owner;
name_owner = NULL;
result = NULL;
result = g_dbus_connection_call_finish (client->connection,
res,
NULL);
if (result != NULL)
{
g_variant_get (result, "(&s)", &name_owner);
}
if (name_owner != NULL)
{
g_warn_if_fail (client->name_owner == NULL);
client->name_owner = g_strdup (name_owner);
call_appeared_handler (client);
}
else
{
call_vanished_handler (client, FALSE);
}
client->initialized = TRUE;
if (result != NULL)
g_variant_unref (result);
client_unref (client);
}
/* ---------------------------------------------------------------------------------------------------- */
static void
invoke_get_name_owner (Client *client)
{
g_dbus_connection_call (client->connection,
"org.freedesktop.DBus", /* bus name */
"/org/freedesktop/DBus", /* object path */
"org.freedesktop.DBus", /* interface name */
"GetNameOwner", /* method name */
g_variant_new ("(s)", client->name),
G_VARIANT_TYPE ("(s)"),
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
(GAsyncReadyCallback) get_name_owner_cb,
client_ref (client));
}
/* ---------------------------------------------------------------------------------------------------- */
static void
start_service_by_name_cb (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
Client *client = user_data;
GVariant *result;
result = NULL;
result = g_dbus_connection_call_finish (client->connection,
res,
NULL);
if (result != NULL)
{
guint32 start_service_result;
g_variant_get (result, "(u)", &start_service_result);
if (start_service_result == 1) /* DBUS_START_REPLY_SUCCESS */
{
invoke_get_name_owner (client);
}
else if (start_service_result == 2) /* DBUS_START_REPLY_ALREADY_RUNNING */
{
invoke_get_name_owner (client);
}
else
{
g_warning ("Unexpected reply %d from StartServiceByName() method", start_service_result);
call_vanished_handler (client, FALSE);
client->initialized = TRUE;
}
}
else
{
/* Errors are not unexpected; the bus will reply e.g.
*
* org.freedesktop.DBus.Error.ServiceUnknown: The name org.gnome.Epiphany2
* was not provided by any .service files
*
* This doesn't mean that the name doesn't have an owner, just
* that it's not provided by a .service file. So proceed to
* invoke GetNameOwner().
*/
invoke_get_name_owner (client);
}
if (result != NULL)
g_variant_unref (result);
client_unref (client);
}
/* ---------------------------------------------------------------------------------------------------- */
static void
has_connection (Client *client)
{
/* listen for disconnection */
client->disconnected_signal_handler_id = g_signal_connect (client->connection,
"closed",
G_CALLBACK (on_connection_disconnected),
client);
/* start listening to NameOwnerChanged messages immediately */
client->name_owner_changed_subscription_id = g_dbus_connection_signal_subscribe (client->connection,
"org.freedesktop.DBus", /* name */
"org.freedesktop.DBus", /* if */
"NameOwnerChanged", /* signal */
"/org/freedesktop/DBus", /* path */
client->name,
on_name_owner_changed,
client,
NULL);
if (client->flags & G_BUS_NAME_WATCHER_FLAGS_AUTO_START)
{
g_dbus_connection_call (client->connection,
"org.freedesktop.DBus", /* bus name */
"/org/freedesktop/DBus", /* object path */
"org.freedesktop.DBus", /* interface name */
"StartServiceByName", /* method name */
g_variant_new ("(su)", client->name, 0),
G_VARIANT_TYPE ("(u)"),
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
(GAsyncReadyCallback) start_service_by_name_cb,
client_ref (client));
}
else
{
/* check owner */
invoke_get_name_owner (client);
}
}
static void
connection_get_cb (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
Client *client = user_data;
client->connection = g_bus_get_finish (res, NULL);
if (client->connection == NULL)
{
call_vanished_handler (client, FALSE);
goto out;
}
has_connection (client);
out:
client_unref (client);
}
/* ---------------------------------------------------------------------------------------------------- */
/**
* g_bus_watch_name:
* @bus_type: The type of bus to watch a name on.
* @name: The name (well-known or unique) to watch.
* @flags: Flags from the #GBusNameWatcherFlags enumeration.
* @name_appeared_handler: Handler to invoke when @name is known to exist or %NULL.
* @name_vanished_handler: Handler to invoke when @name is known to not exist or %NULL.
* @user_data: User data to pass to handlers.
* @user_data_free_func: Function for freeing @user_data or %NULL.
*
* Starts watching @name on the bus specified by @bus_type and calls
* @name_appeared_handler and @name_vanished_handler when the name is
* known to have a owner respectively known to lose its
* owner. Callbacks will be invoked in the <link
* linkend="g-main-context-push-thread-default">thread-default main
* loop</link> of the thread you are calling this function from.
*
* You are guaranteed that one of the handlers will be invoked after
* calling this function. When you are done watching the name, just
* call g_bus_unwatch_name() with the watcher id this function
* returns.
*
* If the name vanishes or appears (for example the application owning
* the name could restart), the handlers are also invoked. If the
* #GDBusConnection that is used for watching the name disconnects, then
* @name_vanished_handler is invoked since it is no longer
* possible to access the name.
*
* Another guarantee is that invocations of @name_appeared_handler
* and @name_vanished_handler are guaranteed to alternate; that
* is, if @name_appeared_handler is invoked then you are
* guaranteed that the next time one of the handlers is invoked, it
* will be @name_vanished_handler. The reverse is also true.
*
* This behavior makes it very simple to write applications that wants
* to take action when a certain name exists, see <xref
* linkend="gdbus-watching-names"/>. Basically, the application
* should create object proxies in @name_appeared_handler and destroy
* them again (if any) in @name_vanished_handler.
*
* Returns: An identifier (never 0) that an be used with
* g_bus_unwatch_name() to stop watching the name.
*
* Since: 2.26
*/
guint
g_bus_watch_name (GBusType bus_type,
const gchar *name,
GBusNameWatcherFlags flags,
GBusNameAppearedCallback name_appeared_handler,
GBusNameVanishedCallback name_vanished_handler,
gpointer user_data,
GDestroyNotify user_data_free_func)
{
Client *client;
g_return_val_if_fail (g_dbus_is_name (name), 0);
G_LOCK (lock);
client = g_new0 (Client, 1);
client->ref_count = 1;
client->id = next_global_id++; /* TODO: uh oh, handle overflow */
client->name = g_strdup (name);
client->flags = flags;
client->name_appeared_handler = name_appeared_handler;
client->name_vanished_handler = name_vanished_handler;
client->user_data = user_data;
client->user_data_free_func = user_data_free_func;
client->main_context = g_main_context_get_thread_default ();
if (client->main_context != NULL)
g_main_context_ref (client->main_context);
if (map_id_to_client == NULL)
{
map_id_to_client = g_hash_table_new (g_direct_hash, g_direct_equal);
}
g_hash_table_insert (map_id_to_client,
GUINT_TO_POINTER (client->id),
client);
g_bus_get (bus_type,
NULL,
connection_get_cb,
client_ref (client));
G_UNLOCK (lock);
return client->id;
}
/**
* g_bus_watch_name_on_connection:
* @connection: A #GDBusConnection that is not closed.
* @name: The name (well-known or unique) to watch.
* @flags: Flags from the #GBusNameWatcherFlags enumeration.
* @name_appeared_handler: Handler to invoke when @name is known to exist or %NULL.
* @name_vanished_handler: Handler to invoke when @name is known to not exist or %NULL.
* @user_data: User data to pass to handlers.
* @user_data_free_func: Function for freeing @user_data or %NULL.
*
* Like g_bus_watch_name() but takes a #GDBusConnection instead of a
* #GBusType.
*
* Returns: An identifier (never 0) that an be used with
* g_bus_unwatch_name() to stop watching the name.
*
* Since: 2.26
*/
guint g_bus_watch_name_on_connection (GDBusConnection *connection,
const gchar *name,
GBusNameWatcherFlags flags,
GBusNameAppearedCallback name_appeared_handler,
GBusNameVanishedCallback name_vanished_handler,
gpointer user_data,
GDestroyNotify user_data_free_func)
{
Client *client;
g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), 0);
g_return_val_if_fail (g_dbus_is_name (name), 0);
G_LOCK (lock);
client = g_new0 (Client, 1);
client->ref_count = 1;
client->id = next_global_id++; /* TODO: uh oh, handle overflow */
client->name = g_strdup (name);
client->flags = flags;
client->name_appeared_handler = name_appeared_handler;
client->name_vanished_handler = name_vanished_handler;
client->user_data = user_data;
client->user_data_free_func = user_data_free_func;
client->main_context = g_main_context_get_thread_default ();
if (client->main_context != NULL)
g_main_context_ref (client->main_context);
if (map_id_to_client == NULL)
{
map_id_to_client = g_hash_table_new (g_direct_hash, g_direct_equal);
}
g_hash_table_insert (map_id_to_client,
GUINT_TO_POINTER (client->id),
client);
client->connection = g_object_ref (connection);
G_UNLOCK (lock);
has_connection (client);
return client->id;
}
/**
* g_bus_unwatch_name:
* @watcher_id: An identifier obtained from g_bus_watch_name()
*
* Stops watching a name.
*
* Since: 2.26
*/
void
g_bus_unwatch_name (guint watcher_id)
{
Client *client;
g_return_if_fail (watcher_id > 0);
client = NULL;
G_LOCK (lock);
if (watcher_id == 0 ||
map_id_to_client == NULL ||
(client = g_hash_table_lookup (map_id_to_client, GUINT_TO_POINTER (watcher_id))) == NULL)
{
g_warning ("Invalid id %d passed to g_bus_unwatch_name()", watcher_id);
goto out;
}
client->cancelled = TRUE;
g_warn_if_fail (g_hash_table_remove (map_id_to_client, GUINT_TO_POINTER (watcher_id)));
out:
G_UNLOCK (lock);
/* do callback without holding lock */
if (client != NULL)
{
client_unref (client);
}
}
#define __G_DBUS_NAME_WATCHING_C__
#include "gioaliasdef.c"