blob: e3eacce13950483290be4d6d6431b0db6f2542c6 [file] [log] [blame]
/* libnih
*
* dbus_proxy.c - D-Bus remote object proxy implementation
*
* Copyright © 2009 Scott James Remnant <scott@netsplit.com>.
* Copyright © 2009 Canonical Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2, as
* published by the Free Software Foundation.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif /* HAVE_CONFIG_H */
#include <dbus/dbus.h>
#include <string.h>
#include <nih/macros.h>
#include <nih/alloc.h>
#include <nih/string.h>
#include <nih/logging.h>
#include <nih/error.h>
#include <nih-dbus/dbus_error.h>
#include <nih-dbus/dbus_object.h>
#include "dbus_proxy.h"
/* Prototypes for static functions */
static int nih_dbus_proxy_destroy (NihDBusProxy *proxy);
static int nih_dbus_proxy_name_track (NihDBusProxy *proxy)
__attribute__ ((warn_unused_result));
static char *nih_dbus_proxy_name_rule (const void *parent,
NihDBusProxy *proxy)
__attribute__ ((warn_unused_result, malloc));
static int nih_dbus_proxy_signal_destroy (NihDBusProxySignal *proxied);
static char *nih_dbus_proxy_signal_rule (const void *parent,
NihDBusProxySignal *proxied)
__attribute__ ((warn_unused_result, malloc));
/* Prototypes for handler functions */
static DBusHandlerResult nih_dbus_proxy_name_owner_changed (DBusConnection *connection,
DBusMessage *message,
NihDBusProxy *proxy);
/**
* nih_dbus_proxy_new:
* @parent: parent object for new proxy,
* @connection: D-Bus connection to associate with,
* @name: well-known name of object owner,
* @path: path of object,
* @lost_handler: optional handler for remote object loss.
* @data: data pointer for handlers.
*
* Creates a new D-Bus proxy for a remote object on @connection with the
* well-known or unique bus name @name at @path. The returned structure
* is allocated with nih_alloc() and holds a reference to @connection.
*
* @name may be NULL for peer-to-peer D-Bus connections.
*
* Proxies are not generally bound to the life-time of the connection or
* the remote object, thus there may be periods when functions will fail
* or signal filter functions left dormant due to unavailability of the
* remote object or even cease permanently when the bus connection is
* disconnected.
*
* @name will be tracked on the bus, with the current owner's unique name
* being available in the returned structure's owner member. Should the
* name be lost from the bus, the optional @lost_handler function will be
* called to allow clean-up of the proxy.
* If @parent is not NULL, it should be a pointer to another object which
* will be used as a parent for the returned proxy. When all parents
* of the returned proxy are freed, the returned proxy will also be
* freed.
*
* Returns: new NihDBusProxy structure on success, or NULL on raised
* error.
**/
NihDBusProxy *
nih_dbus_proxy_new (const void * parent,
DBusConnection * connection,
const char * name,
const char * path,
NihDBusLostHandler lost_handler,
void * data)
{
NihDBusProxy *proxy;
nih_assert (connection != NULL);
nih_assert (path != NULL);
nih_assert ((lost_handler == NULL) || (name != NULL));
proxy = nih_new (parent, NihDBusProxy);
if (! proxy)
nih_return_no_memory_error (NULL);
proxy->connection = connection;
proxy->name = NULL;
if (name) {
proxy->name = nih_strdup (proxy, name);
if (! proxy->name) {
nih_free (proxy);
nih_return_no_memory_error (NULL);
}
}
proxy->owner = NULL;
proxy->path = nih_strdup (proxy, path);
if (! proxy->path) {
nih_free (proxy);
nih_return_no_memory_error (NULL);
}
proxy->auto_start = TRUE;
proxy->lost_handler = lost_handler;
proxy->data = data;
if (proxy->name) {
if (nih_dbus_proxy_name_track (proxy) < 0) {
nih_free (proxy);
return NULL;
}
}
dbus_connection_ref (proxy->connection);
nih_alloc_set_destructor (proxy, nih_dbus_proxy_destroy);
return proxy;
}
/**
* nih_dbus_proxy_destroy:
* @proxy: proxy object being destroyed.
*
* Destructor function for an NihDBusProxy structure; drops the bus rule
* matching the NameOwnerChanged signal, the associated filter function,
* and the reference to the D-Bus connection it holds.
*
* Returns: always zero.
**/
static int
nih_dbus_proxy_destroy (NihDBusProxy *proxy)
{
nih_local char *rule = NULL;
DBusError dbus_error;
nih_assert (proxy != NULL);
if (proxy->name) {
rule = NIH_MUST (nih_dbus_proxy_name_rule (NULL, proxy));
dbus_error_init (&dbus_error);
dbus_bus_remove_match (proxy->connection, rule, &dbus_error);
dbus_error_free (&dbus_error);
dbus_connection_remove_filter (proxy->connection,
(DBusHandleMessageFunction)nih_dbus_proxy_name_owner_changed,
proxy);
}
dbus_connection_unref (proxy->connection);
return 0;
}
/**
* nih_dbus_proxy_name_track:
* @proxy: proxy object.
*
* Set up name tracking for the given @proxy object. We get the current
* owner of the name in a synchronous call and set the connection up to
* watch for a change in that owner updating the proxy's owner member in
* both cases.
*
* If the proxy has no owner, the connection is instead set up to wait
* for it to come onto the bus, and then reset later.
*
* Returns: 0 on success, negative value on raised error.
**/
static int
nih_dbus_proxy_name_track (NihDBusProxy *proxy)
{
nih_local char *rule = NULL;
DBusError dbus_error;
DBusMessage * method_call;
DBusMessage * reply;
const char * owner;
nih_assert (proxy != NULL);
nih_assert (proxy->name != NULL);
/* Add the filter function that handles the NameOwnerChanged
* signal. We need to do this first so that we can handle anything
* that arrives after we add the signal match.
*/
if (! dbus_connection_add_filter (proxy->connection,
(DBusHandleMessageFunction)nih_dbus_proxy_name_owner_changed,
proxy, NULL))
nih_return_no_memory_error (-1);
/* Ask the bus to send us matching signals. We've put the filter
* function in place so we'll get callbacks straight away; but we
* still need to do this before asking for the current name so
* we don't miss something.
*/
rule = nih_dbus_proxy_name_rule (NULL, proxy);
if (! rule) {
nih_error_raise_no_memory ();
goto error_after_filter;
}
dbus_error_init (&dbus_error);
dbus_bus_add_match (proxy->connection, rule, &dbus_error);
if (dbus_error_is_set (&dbus_error)) {
if (dbus_error_has_name (&dbus_error, DBUS_ERROR_NO_MEMORY)) {
nih_error_raise_no_memory ();
} else {
nih_dbus_error_raise (dbus_error.name,
dbus_error.message);
}
dbus_error_free (&dbus_error);
goto error_after_filter;
}
/* Now that the bus will send us signals about changes in the name's
* owner, and we'll handle them, we can get the current owner of the
* name. We may have some signals in the queue that predate this,
* but the end result will be the same.
*/
method_call = dbus_message_new_method_call (DBUS_SERVICE_DBUS,
DBUS_PATH_DBUS,
DBUS_INTERFACE_DBUS,
"GetNameOwner");
if (! method_call) {
nih_error_raise_no_memory ();
dbus_error_free (&dbus_error);
goto error_after_match;
}
if (! dbus_message_append_args (method_call,
DBUS_TYPE_STRING, &proxy->name,
DBUS_TYPE_INVALID)) {
nih_error_raise_no_memory ();
dbus_message_unref (method_call);
dbus_error_free (&dbus_error);
goto error_after_match;
}
/* Parse the reply; an owner is returned, we fill in the owner
* member of the proxy - otherwise we set it to NULL.
*/
reply = dbus_connection_send_with_reply_and_block (proxy->connection, method_call,
-1, &dbus_error);
if (! reply) {
if (dbus_error_has_name (&dbus_error,
DBUS_ERROR_NAME_HAS_NO_OWNER)) {
nih_debug ("%s is not currently owned", proxy->name);
dbus_message_unref (method_call);
dbus_error_free (&dbus_error);
/* Not an error */
return 0;
} else if (dbus_error_has_name (&dbus_error,
DBUS_ERROR_NO_MEMORY)) {
nih_error_raise_no_memory ();
} else {
nih_dbus_error_raise (dbus_error.name,
dbus_error.message);
}
dbus_message_unref (method_call);
dbus_error_free (&dbus_error);
goto error_after_match;
}
dbus_message_unref (method_call);
if (! dbus_message_get_args (reply, &dbus_error,
DBUS_TYPE_STRING, &owner,
DBUS_TYPE_INVALID)) {
if (dbus_error_has_name (&dbus_error, DBUS_ERROR_NO_MEMORY)) {
nih_error_raise_no_memory ();
} else {
nih_dbus_error_raise (dbus_error.name,
dbus_error.message);
}
dbus_message_unref (reply);
dbus_error_free (&dbus_error);
goto error_after_match;
}
dbus_error_free (&dbus_error);
proxy->owner = nih_strdup (proxy, owner);
if (! proxy->owner) {
nih_error_raise_no_memory ();
dbus_message_unref (reply);
goto error_after_match;
}
dbus_message_unref (reply);
nih_debug ("%s is currently owned by %s", proxy->name, proxy->owner);
return 0;
error_after_match:
dbus_error_init (&dbus_error);
dbus_bus_remove_match (proxy->connection, rule, &dbus_error);
dbus_error_free (&dbus_error);
error_after_filter:
dbus_connection_remove_filter (proxy->connection,
(DBusHandleMessageFunction)nih_dbus_proxy_name_owner_changed,
proxy);
return -1;
}
/**
* nih_dbus_proxy_name_rule:
* @parent: parent object for new string,
* @proxy: proxy object.
*
* Generates a D-Bus match rule for the NameOwnerChanged signal for the
* given @proxy.
*
* If @parent is not NULL, it should be a pointer to another object which
* will be used as a parent for the returned string. When all parents
* of the returned string are freed, the returned string will also be
* freed.
*
* Returns: newly allocated string or NULL on insufficient memory.
**/
static char *
nih_dbus_proxy_name_rule (const void * parent,
NihDBusProxy *proxy)
{
char *rule;
nih_assert (proxy != NULL);
nih_assert (proxy->name != NULL);
rule = nih_sprintf (parent, ("type='%s',sender='%s',path='%s',"
"interface='%s',member='%s',"
"arg0='%s'"),
"signal",
DBUS_SERVICE_DBUS,
DBUS_PATH_DBUS,
DBUS_INTERFACE_DBUS,
"NameOwnerChanged",
proxy->name);
return rule;
}
/**
* nih_dbus_proxy_name_owner_changed:
* @connection: D-Bus connection signal received on,
* @message: signal message,
* @proxy: associated proxy object.
*
* This function is called by D-Bus on receipt of the NameOwnerChanged
* signal for the registered name that @proxy represents. The proxy's
* lost_handler function is called to decide what to do about it.
*
* Returns: usually DBUS_HANDLER_RESULT_NOT_YET_HANDLED so other signal
* handlers also get a look-in, DBUS_HANDLED_RESULT_NEED_MEMORY if
* insufficient memory.
**/
static DBusHandlerResult
nih_dbus_proxy_name_owner_changed (DBusConnection *connection,
DBusMessage * message,
NihDBusProxy * proxy)
{
DBusError dbus_error;
const char *name;
const char *old_owner;
const char *new_owner;
nih_assert (connection != NULL);
nih_assert (message != NULL);
nih_assert (proxy->connection == connection);
nih_assert (proxy->name != NULL);
if (! dbus_message_is_signal (message, DBUS_INTERFACE_DBUS,
"NameOwnerChanged"))
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
if (! dbus_message_has_path (message, DBUS_PATH_DBUS))
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
if (! dbus_message_has_sender (message, DBUS_SERVICE_DBUS))
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
dbus_error_init (&dbus_error);
if (! dbus_message_get_args (message, &dbus_error,
DBUS_TYPE_STRING, &name,
DBUS_TYPE_STRING, &old_owner,
DBUS_TYPE_STRING, &new_owner,
DBUS_TYPE_INVALID)) {
dbus_error_free (&dbus_error);
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
dbus_error_free (&dbus_error);
if (strcmp (name, proxy->name))
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
/* Ok, it's really the right NameOwnerChanged signal. If the name
* has a new owner, update the owner property (tracking a well known
* name between instances) otherwise call the lost handler.
*/
if (strlen (new_owner)) {
nih_debug ("%s changed owner from %s to %s",
proxy->name, old_owner, new_owner);
if (proxy->owner)
nih_unref (proxy->owner, proxy);
proxy->owner = NIH_MUST (nih_strdup (proxy, new_owner));
} else {
nih_debug ("%s owner left the bus", proxy->name);
if (proxy->owner)
nih_unref (proxy->owner, proxy);
proxy->owner = NULL;
if (proxy->lost_handler) {
nih_error_push_context ();
proxy->lost_handler (proxy->data, proxy);
nih_error_pop_context ();
}
}
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
/**
* nih_dbus_proxy_connect:
* @proxy: proxy for remote object,
* @interface: signal interface definition,
* @name: name of signal,
* @handler: signal handler function,
* @data: data to pass to @handler.
*
* Connect the signal @name on @interface to @proxy so that the @handler
* function is passed to the filter function defined by @signal when it
* is received on the proxied D-Bus connection.
*
* The signal can be disconnected by freeing the returned structure, the
* signal is also bound to the lifetime of @proxy so that the signal is
* disconnected when the proxy is freed.
*
* Returns: newly allocated NihDBusProxySignal structure or NULL on raised
* error.
**/
NihDBusProxySignal *
nih_dbus_proxy_connect (NihDBusProxy * proxy,
const NihDBusInterface *interface,
const char * name,
NihDBusSignalHandler handler,
void * data)
{
NihDBusProxySignal *proxied;
nih_local char * rule = NULL;
DBusError dbus_error;
nih_assert (proxy != NULL);
nih_assert (interface != NULL);
nih_assert (name != NULL);
nih_assert (handler != NULL);
proxied = nih_new (proxy, NihDBusProxySignal);
if (! proxied)
nih_return_no_memory_error (NULL);
proxied->proxy = proxy;
proxied->interface = interface;
proxied->signal = NULL;
proxied->handler = handler;
proxied->data = data;
for (const NihDBusSignal *signal = interface->signals;
signal && signal->name; signal++) {
if (! strcmp (signal->name, name)) {
proxied->signal = signal;
break;
}
}
nih_assert (proxied->signal != NULL);
if (! dbus_connection_add_filter (proxied->proxy->connection,
(DBusHandleMessageFunction)proxied->signal->filter,
proxied, NULL)) {
nih_free (proxied);
nih_return_no_memory_error (NULL);
}
if (proxied->proxy->name) {
rule = nih_dbus_proxy_signal_rule (NULL, proxied);
if (! rule) {
nih_error_raise_no_memory ();
goto error;
}
dbus_error_init (&dbus_error);
dbus_bus_add_match (proxied->proxy->connection, rule, &dbus_error);
if (dbus_error_is_set (&dbus_error)) {
if (dbus_error_has_name (&dbus_error, DBUS_ERROR_NO_MEMORY)) {
nih_error_raise_no_memory ();
} else {
nih_dbus_error_raise (dbus_error.name,
dbus_error.message);
}
dbus_error_free (&dbus_error);
goto error;
}
}
nih_alloc_set_destructor (proxied, nih_dbus_proxy_signal_destroy);
return proxied;
error:
dbus_connection_remove_filter (proxied->proxy->connection,
(DBusHandleMessageFunction)proxied->signal->filter,
proxied);
nih_free (proxied);
return NULL;
}
/**
* nih_dbus_proxy_signal_destroy:
* @proxied: proxied signal being destroyed.
*
* Destructor function for an NihDBusProxySignal structure; drops the bus
* rule matching the signal and the associated filter function.
*
* Returns: always zero.
**/
static int
nih_dbus_proxy_signal_destroy (NihDBusProxySignal *proxied)
{
nih_local char *rule = NULL;
DBusError dbus_error;
nih_assert (proxied != NULL);
if (proxied->proxy->name) {
rule = NIH_MUST (nih_dbus_proxy_signal_rule (NULL, proxied));
dbus_error_init (&dbus_error);
dbus_bus_remove_match (proxied->proxy->connection,
rule, &dbus_error);
dbus_error_free (&dbus_error);
}
dbus_connection_remove_filter (proxied->proxy->connection,
(DBusHandleMessageFunction)proxied->signal->filter,
proxied);
return 0;
}
/**
* nih_dbus_proxy_signal_rule:
* @parent: parent object for new string,
* @proxied: proxied signal.
*
* Generates a D-Bus match rule for the @proxied signal.
*
* If @parent is not NULL, it should be a pointer to another object which
* will be used as a parent for the returned string. When all parents
* of the returned string are freed, the returned string will also be
* freed.
*
* Returns: newly allocated string or NULL on insufficient memory.
**/
static char *
nih_dbus_proxy_signal_rule (const void * parent,
NihDBusProxySignal *proxied)
{
char *rule;
nih_assert (proxied != NULL);
nih_assert (proxied->proxy->name != NULL);
rule = nih_sprintf (parent, ("type='%s',sender='%s',path='%s',"
"interface='%s',member='%s'"),
"signal",
proxied->proxy->name,
proxied->proxy->path,
proxied->interface->name,
proxied->signal->name);
return rule;
}