ladish/cdbus/object_path.c

437 lines
13 KiB
C

/* -*- Mode: C ; c-basic-offset: 2 -*- */
/*
* LADI Session Handler (ladish)
*
* Copyright (C) 2008,2009,2010,2011,2012 Nedko Arnaudov <nedko@arnaudov.name>
* Copyright (C) 2008 Juuso Alasuutari <juuso.alasuutari@gmail.com>
*
**************************************************************************
* This file contains D-Bus object path helpers
**************************************************************************
*
* Licensed under the Academic Free License version 2.1
*
* LADI Session Handler is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* LADI Session Handler 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 LADI Session Handler. If not, see <http://www.gnu.org/licenses/>
* or write to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "../common.h"
#include "helpers.h"
struct cdbus_object_path_interface
{
const struct cdbus_interface_descriptor * iface;
void * iface_context;
};
struct cdbus_object_path
{
char * name;
DBusMessage * introspection;
struct cdbus_object_path_interface * ifaces;
bool registered;
};
#define write_buf(args...) buf_ptr += sprintf(buf_ptr, ## args)
DBusMessage * cdbus_introspection_new(struct cdbus_object_path * opath_ptr)
{
char *xml_data, *buf_ptr;
const struct cdbus_object_path_interface * iface_ptr;
const struct cdbus_method_descriptor * method_ptr;
const struct cdbus_method_arg_descriptor * method_arg_ptr;
const struct cdbus_signal_descriptor * signal_ptr;
const struct cdbus_signal_arg_descriptor * signal_arg_ptr;
DBusMessage * msg;
DBusMessageIter iter;
log_debug("Creating introspection message");
/*
* Create introspection XML data.
*/
/* TODO: we assume that 16 KiB is enough to hold introspection xml.
* If it gets larger, memory corruption will occur.
* Use realloc-like algorithm instead */
xml_data = malloc(16384);
if (xml_data == NULL)
{
return NULL;
}
buf_ptr = xml_data;
write_buf("<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\"\n"
" \"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n"
"<node name=\"%s\">\n", opath_ptr->name);
/* Add the object path's interfaces. */
for (iface_ptr = opath_ptr->ifaces; iface_ptr->iface != NULL; iface_ptr++)
{
write_buf(" <interface name=\"%s\">\n", iface_ptr->iface->name);
if (iface_ptr->iface->methods != NULL)
{
/* Add the interface's methods. */
for (method_ptr = iface_ptr->iface->methods; method_ptr->name != NULL; method_ptr++)
{
write_buf(" <method name=\"%s\">\n", method_ptr->name);
/* Add the method's arguments. */
for (method_arg_ptr = method_ptr->args; method_arg_ptr->name != NULL; method_arg_ptr++)
{
write_buf(
" <arg name=\"%s\" type=\"%s\" direction=\"%s\" />\n",
method_arg_ptr->name,
method_arg_ptr->type,
method_arg_ptr->direction_in ? "in" : "out");
}
write_buf(" </method>\n");
}
}
if (iface_ptr->iface->signals != NULL)
{
/* Add the interface's signals. */
for (signal_ptr = iface_ptr->iface->signals; signal_ptr->name != NULL; signal_ptr++)
{
write_buf(" <signal name=\"%s\">\n", signal_ptr->name);
/* Add the signal's arguments. */
for (signal_arg_ptr = signal_ptr->args; signal_arg_ptr->name != NULL; signal_arg_ptr++)
{
write_buf(" <arg name=\"%s\" type=\"%s\" />\n", signal_arg_ptr->name, signal_arg_ptr->type);
}
write_buf(" </signal>\n");
}
}
write_buf(" </interface>\n");
}
write_buf("</node>\n");
/*
* Create a D-Bus message from the XML data.
*/
if ((msg = dbus_message_new(DBUS_MESSAGE_TYPE_METHOD_RETURN)))
{
dbus_message_iter_init_append(msg, &iter);
if (dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, (const void *) &xml_data))
{
dbus_message_set_no_reply(msg, TRUE);
}
else
{
dbus_message_unref(msg);
msg = NULL;
log_error("Failed to append data to introspection message");
}
}
else
{
log_error("Failed to create introspection message");
}
free(xml_data);
return msg;
}
#undef write_buf
void cdbus_introspection_destroy(struct cdbus_object_path *path)
{
log_debug("Destroying introspection message");
if (path && path->introspection) {
dbus_message_unref(path->introspection);
path->introspection = NULL;
}
else
{
log_debug("Nothing to destroy");
}
}
static
bool
cdbus_introspection_handler(
const struct cdbus_interface_descriptor * UNUSED(interface),
struct cdbus_method_call * call_ptr)
{
if (strcmp(call_ptr->method_name, "Introspect") != 0)
{
/* The requested method wasn't "Introspect". */
return false;
}
/* Try to construct the instrospection message */
call_ptr->reply = dbus_message_copy(call_ptr->iface_context); /* context contains the reply message */
if (call_ptr->reply == NULL)
{
log_error("Ran out of memory trying to copy introspection message");
goto fail;
}
if (!dbus_message_set_destination(call_ptr->reply, dbus_message_get_sender(call_ptr->message)))
{
log_error("dbus_message_set_destination() failed.");
goto unref_reply;
}
if (!dbus_message_set_reply_serial(call_ptr->reply, dbus_message_get_serial(call_ptr->message)))
{
log_error("dbus_message_set_reply_serial() failed.");
goto unref_reply;
}
return true;
unref_reply:
dbus_message_unref(call_ptr->reply);
call_ptr->reply = NULL;
fail:
/* Even after an error we need to return true, because the
handler is only supposed to return false if a nonexistent
method is requested. */
return true;
}
CDBUS_METHOD_ARGS_BEGIN(Introspect, "Get introspection XML")
CDBUS_METHOD_ARG_DESCRIBE_OUT("xml_data", "s", "XML description of the object")
CDBUS_METHOD_ARGS_END
CDBUS_METHODS_BEGIN
CDBUS_METHOD_DESCRIBE(Introspect, NULL)
CDBUS_METHODS_END
CDBUS_INTERFACE_BEGIN(g_dbus_interface_dtor_introspectable, "org.freedesktop.DBus.Introspectable")
CDBUS_INTERFACE_HANDLER(cdbus_introspection_handler)
CDBUS_INTERFACE_EXPOSE_METHODS
CDBUS_INTERFACE_END
cdbus_object_path cdbus_object_path_new(const char *name, const struct cdbus_interface_descriptor * iface1_ptr, ...)
{
struct cdbus_object_path * opath_ptr;
va_list ap;
const struct cdbus_interface_descriptor * iface_src_ptr;
struct cdbus_object_path_interface * iface_dst_ptr;
size_t len;
log_debug("Creating object path");
opath_ptr = malloc(sizeof(struct cdbus_object_path));
if (opath_ptr == NULL)
{
log_error("malloc() failed to allocate struct cdbus_object_path.");
goto fail;
}
opath_ptr->name = strdup(name);
if (opath_ptr->name == NULL)
{
log_error("malloc() failed to allocate struct cdbus_object_path.");
goto free;
}
va_start(ap, iface1_ptr);
iface_src_ptr = iface1_ptr;
len = 0;
while (iface_src_ptr != NULL)
{
va_arg(ap, void *); /* skip interface context */
iface_src_ptr = va_arg(ap, const struct cdbus_interface_descriptor *);
len++;
}
va_end(ap);
opath_ptr->ifaces = malloc((len + 2) * sizeof(struct cdbus_object_path_interface));
if (opath_ptr->ifaces == NULL)
{
log_error("malloc failed to allocate interfaces array");
goto free_name;
}
va_start(ap, iface1_ptr);
iface_src_ptr = iface1_ptr;
iface_dst_ptr = opath_ptr->ifaces;
while (iface_src_ptr != NULL)
{
iface_dst_ptr->iface = iface_src_ptr;
iface_dst_ptr->iface_context = va_arg(ap, void *);
iface_src_ptr = va_arg(ap, const struct cdbus_interface_descriptor *);
iface_dst_ptr++;
len--;
}
va_end(ap);
ASSERT(len == 0);
iface_dst_ptr->iface = NULL;
opath_ptr->introspection = cdbus_introspection_new(opath_ptr);
if (opath_ptr->introspection == NULL)
{
log_error("introspection_new() failed.");
goto free_ifaces;
}
iface_dst_ptr->iface = &g_dbus_interface_dtor_introspectable;
iface_dst_ptr->iface_context = opath_ptr->introspection;
iface_dst_ptr++;
iface_dst_ptr->iface = NULL;
opath_ptr->registered = false;
return (cdbus_object_path)opath_ptr;
free_ifaces:
free(opath_ptr->ifaces);
free_name:
free(opath_ptr->name);
free:
free(opath_ptr);
fail:
return NULL;
}
#define opath_ptr ((struct cdbus_object_path *)data)
void cdbus_object_path_unregister(DBusConnection * connection_ptr, cdbus_object_path data)
{
ASSERT(opath_ptr->registered);
if (!dbus_connection_unregister_object_path(connection_ptr, opath_ptr->name))
{
log_error("dbus_connection_unregister_object_path() failed.");
}
}
void cdbus_object_path_destroy(DBusConnection * connection_ptr, cdbus_object_path data)
{
log_debug("Destroying object path");
if (opath_ptr->registered && connection_ptr != NULL && !dbus_connection_unregister_object_path(connection_ptr, opath_ptr->name))
{
log_error("dbus_connection_unregister_object_path() failed.");
}
cdbus_introspection_destroy(opath_ptr);
free(opath_ptr->ifaces);
free(opath_ptr->name);
free(opath_ptr);
}
static DBusHandlerResult cdbus_object_path_handler(DBusConnection * connection, DBusMessage * message, void * data)
{
const char * iface_name;
const struct cdbus_object_path_interface * iface_ptr;
struct cdbus_method_call call;
/* Check if the message is a method call. If not, ignore it. */
if (dbus_message_get_type(message) != DBUS_MESSAGE_TYPE_METHOD_CALL)
{
goto handled;
}
/* Get the invoked method's name and make sure it's non-NULL. */
call.method_name = dbus_message_get_member(message);
if (call.method_name == NULL)
{
cdbus_error(&call, DBUS_ERROR_UNKNOWN_METHOD, "Received method call with empty method name");
goto send_return;
}
/* Initialize our data. */
call.connection = connection;
call.message = message;
call.iface = NULL; /* To be set by the default interface handler */
call.reply = NULL;
/* Check if there's an interface specified for this method call. */
iface_name = dbus_message_get_interface(message);
if (iface_name != NULL)
{
for (iface_ptr = opath_ptr->ifaces; iface_ptr->iface != NULL; iface_ptr++)
{
if (strcmp(iface_name, iface_ptr->iface->name) == 0)
{
call.iface_context = iface_ptr->iface_context;
if (!iface_ptr->iface->handler(iface_ptr->iface, &call))
{
/* unknown method */
break;
}
goto send_return;
}
}
}
else
{
/* No interface was specified so we have to try them all. D-Bus spec states:
*
* Optionally, the message has an INTERFACE field giving the interface the method is a part of.
* In the absence of an INTERFACE field, if two interfaces on the same object have a method with
* the same name, it is undefined which of the two methods will be invoked.
* Implementations may also choose to return an error in this ambiguous case.
* However, if a method name is unique implementations must not require an interface field.
*/
for (iface_ptr = opath_ptr->ifaces; iface_ptr->iface != NULL; iface_ptr++)
{
call.iface_context = iface_ptr->iface_context;
if (!iface_ptr->iface->handler(iface_ptr->iface, &call))
{
/* known method */
goto send_return;
}
}
}
cdbus_error(&call, DBUS_ERROR_UNKNOWN_METHOD, "Method \"%s\" with signature \"%s\" on interface \"%s\" doesn't exist", call.method_name, dbus_message_get_signature(message), iface_name);
send_return:
cdbus_method_return_send(&call);
handled:
return DBUS_HANDLER_RESULT_HANDLED;
}
static void cdbus_object_path_handler_unregister(DBusConnection * UNUSED(connection_ptr), void * data)
{
log_debug("Message handler of object path %s was unregistered", (opath_ptr && opath_ptr->name) ? opath_ptr->name : "<unknown>");
}
bool cdbus_object_path_register(DBusConnection * connection_ptr, cdbus_object_path data)
{
log_debug("Registering object path \"%s\"", opath_ptr->name);
ASSERT(!opath_ptr->registered);
DBusObjectPathVTable vtable =
{
cdbus_object_path_handler_unregister,
cdbus_object_path_handler,
NULL, NULL, NULL, NULL
};
if (!dbus_connection_register_object_path(connection_ptr, opath_ptr->name, &vtable, opath_ptr))
{
log_error("dbus_connection_register_object_path() failed.");
return false;
}
opath_ptr->registered = true;
return true;
}
#undef opath_ptr