/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- */ /* Libnul - Another Utility Library * Copyright (C) 2008 Søren Sandmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library 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. */ #include #include #include #include "libnul.h" #include "libnul-private.h" #define INTROSPECT_INTERFACE "org.freedesktop.DBus.Introspectable" #define INTROSPECT_METHOD "Introspect" struct nul_dbus_service_t { char *name; nul_dbus_object_t **objects; connection_t *connection; gboolean running; DBusBusType type; }; struct nul_dbus_object_t { char *name; nul_ptr_t data; nul_dbus_interface_t **interfaces; }; struct nul_dbus_interface_t { char *name; nul_dbus_member_t **members; }; struct nul_dbus_member_t { char * name; /* For now only methods are supported, although * later we may also have properties and signals */ nul_dbus_interface_t * interface; /* containing interface */ nul_dbus_function_t function; nul_dbus_parameter_t ** parameters; int n_inputs; int n_outputs; nul_fun_def_t * fun_def; nul_fun_def_t * reply_fun; }; struct nul_dbus_parameter_t { char * name; nul_dbus_type_t * type; gboolean out; }; struct nul_dbus_type_t { primitive_t type; }; static nul_ptr_t *free_us; static int idle_handler; static gboolean do_free (gpointer data) { nul_ptr_t *p; for (p = free_us; *p; p += 2) { nul_free_func_t func = *p; func (*(p + 1)); } nul_array_free (free_us); free_us = nul_array_new (nul_ptr_t); idle_handler = 0; return FALSE; } static gpointer idle_free (gpointer p, nul_free_func_t free_func) { if (!free_us) free_us = nul_array_new (nul_ptr_t); if (free_func) free_us = nul_array_append (free_us, (void *)free_func); else free_us = nul_array_append (free_us, (void *)g_free); free_us = nul_array_append (free_us, p); if (!idle_handler) idle_handler = g_idle_add (do_free, NULL); return p; } static const char * make_signature (nul_dbus_type_t *type) { switch (type->type) { case UINT32: return "u"; case INT32: return "i"; case STRING: return "s"; } return ""; } static const char * introspect (nul_dbus_object_t *object) { nul_string_t *xml = nul_string_new (); nul_dbus_interface_t **itf; xml = nul_string_append_printf (xml, "\n"); for (itf = object->interfaces; *itf; ++itf) { nul_dbus_interface_t *interface = *itf; nul_dbus_member_t **mem; xml = nul_string_append_printf ( xml, " \n", interface->name); for (mem = interface->members; *mem; ++mem) { nul_dbus_member_t *member = *mem; int i; xml = nul_string_append_printf ( xml, " \n", member->name); for (i = 0; member->parameters[i] != NULL; ++i) { nul_dbus_parameter_t *parameter = member->parameters[i]; xml = nul_string_append_printf ( xml, " \n", parameter->name, parameter->out? "out" : "in", make_signature (parameter->type)); } xml = nul_string_append_printf ( xml, " \n"); } xml = nul_string_append_printf (xml, " \n"); } xml = nul_string_append_printf (xml, "\n"); return idle_free (xml, (nul_free_func_t)nul_string_free); } static gboolean decode_arg (nul_arg_t **args, DBusMessageIter *iter, nul_dbus_parameter_t *parameter, char **err) { int dbus_type = dbus_message_iter_get_arg_type (iter); nul_arg_t arg; switch (parameter->type->type) { case UINT32: if (dbus_type != DBUS_TYPE_UINT32) { *err = "Received wrong type, should be UINT32"; return FALSE; } dbus_message_iter_get_basic (iter, &arg.v_uint32); break; case INT32: if (dbus_type != DBUS_TYPE_INT32) { *err = "Received wrong type, should be INT32"; return FALSE; } dbus_message_iter_get_basic (iter, &arg.v_int32); break; case STRING: if (dbus_type != DBUS_TYPE_STRING) { *err = "Received wrong type, should be STRING"; return FALSE; } dbus_message_iter_get_basic (iter, &arg.v_pointer); break; } *args = nul_array_append (*args, arg); return TRUE; } static gboolean decode_message (nul_arg_t **args, DBusMessage *message, int n_parameters, nul_dbus_parameter_t **parameters, char **err) { DBusMessageIter iter; char *d; int i; if (!err) err = &d; i = 0; if (dbus_message_iter_init (message, &iter)) { do { if (i >= n_parameters) { *err = "Too many arguments received"; goto fail; } if (!decode_arg (args, &iter, parameters[i], err)) goto fail; i++; } while (dbus_message_iter_next (&iter)); } if (i < n_parameters) { *err = "Too few arguments received"; goto fail; } return TRUE; fail: while (i < n_parameters) { nul_arg_t arg; memset (&arg, 0, sizeof (arg)); *args = nul_array_append (*args, arg); i++; } return FALSE; } static void encode_message (DBusMessage *message, int n_parameters, nul_dbus_parameter_t **parameters, nul_arg_t *args) { DBusMessageIter iter; int i; dbus_message_iter_init_append (message, &iter); for (i = 0; i < n_parameters; ++i) { nul_dbus_parameter_t *parameter = parameters[i]; switch (parameter->type->type) { case UINT32: dbus_message_iter_append_basic ( &iter, DBUS_TYPE_UINT32, &(args[i].v_uint32)); break; case INT32: dbus_message_iter_append_basic ( &iter, DBUS_TYPE_INT32, &(args[i].v_int32)); break; case STRING: dbus_message_iter_append_basic ( &iter, DBUS_TYPE_STRING, &(args[i].v_pointer)); /* FIXME: it feels wrong that we are freeing data * passed in by the user. Maybe we should just make the * user idle_free it. * * Even better actually: Have a nul_dbus_return (...) that * the user is expected to call. */ g_free (args[i].v_pointer); break; } } } static DBusHandlerResult invoke (connection_t *connection, nul_dbus_object_t *object, nul_dbus_member_t *member, DBusMessage *message) { nul_arg_t *args = nul_array_new (nul_arg_t); DBusMessage *error_reply; nul_dbus_parameter_t **inputs; DBusHandlerResult result; nul_arg_t rv; nul_arg_t arg; char *err; int i; if (!member->function) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; /* Add the data pointer */ arg.v_pointer = object->data; args = nul_array_append (args, arg); inputs = (nul_dbus_parameter_t **)member->parameters; if (!decode_message (&args, message, member->n_inputs, inputs, &err)) { error_reply = dbus_message_new_error ( message, DBUS_ERROR_FAILED, "FIXME: generate a real error"); connection_send (connection, error_reply); result = DBUS_HANDLER_RESULT_HANDLED; goto out; } for (i = member->n_inputs; member->parameters[i] != NULL; ++i) { nul_dbus_parameter_t *parameter = member->parameters[i]; nul_ptr_t *pa; g_assert (parameter->out); args = nul_array_append (args, arg); pa = &(args[nul_array_len (args) - 1].v_pointer); *pa = pa; } rv = nul_fun_def_invoke (member->fun_def, (nul_function_t)member->function, args); if (rv.v_int32) { DBusMessage *reply = dbus_message_new_method_return (message); encode_message (reply, member->n_outputs, (nul_dbus_parameter_t **)&(member->parameters[member->n_inputs]), &(args[member->n_inputs + 1])); connection_send (connection, reply); dbus_message_unref (reply); result = DBUS_HANDLER_RESULT_HANDLED; } else { result = DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } out: nul_array_free (args); return result; } static gboolean message_function (connection_t *connection, DBusMessage *message, gpointer data) { const char *msg_path = dbus_message_get_path (message); const char *msg_interface = dbus_message_get_interface (message); const char *msg_member = dbus_message_get_member (message); nul_dbus_object_t *object = data; nul_dbus_interface_t **itf; g_assert (strcmp (object->name, msg_path) == 0); /* Introspection */ if (strcmp (msg_interface, INTROSPECT_INTERFACE) == 0 && strcmp (msg_member, INTROSPECT_METHOD) == 0) { DBusMessage *reply = dbus_message_new_method_return (message); const char *xml = introspect (object); dbus_message_append_args (reply, DBUS_TYPE_STRING, &xml, DBUS_TYPE_INVALID); connection_send (connection, reply); return TRUE; } /* Normal method */ for (itf = object->interfaces; *itf; ++itf) { nul_dbus_interface_t *interface = *itf; if (strcmp (msg_interface, interface->name) == 0) { nul_dbus_member_t **mem; for (mem = interface->members; *mem; ++mem) { nul_dbus_member_t *member = *mem; if (strcmp (member->name, msg_member) == 0) return invoke (connection, object, member, message); } } } g_print ("got unknown message\n"); return FALSE; } static nul_ptr_t * make_ptr_array (gpointer first, va_list parameters) { nul_ptr_t *array = nul_array_new (nul_ptr_t); gpointer v; if (first) { array = nul_array_append (array, first); v = va_arg (parameters, gpointer); while (v) { array = nul_array_append (array, v); v = va_arg (parameters, gpointer); } } return array; } static nul_dbus_service_t * make_service (const char *name, connection_t *connection, nul_dbus_object_t *object1, va_list args) { nul_dbus_service_t *service = g_new0 (nul_dbus_service_t, 1); service->name = g_strdup (name); service->objects = (nul_dbus_object_t **)make_ptr_array (object1, args); service->connection = connection; if (!service->connection) { /* FIXME */ return NULL; } return service; } nul_dbus_service_t * nul_dbus_session_service (const char *name, nul_dbus_object_t *object1, ...) { va_list args; nul_dbus_service_t *service; va_start (args, object1); service = make_service (name, connection_new_session(), object1, args); va_end (args); return service; } gboolean nul_dbus_service_start (nul_dbus_service_t *service) { int i; if (service->running) return FALSE; if (connection_request_name (service->connection, service->name)) { /* Register objects */ for (i = 0; service->objects[i] != NULL; ++i) { nul_dbus_object_t *object = service->objects[i]; connection_register_object ( service->connection, object->name, message_function, object); } service->running = TRUE; return TRUE; } return FALSE; } void nul_dbus_service_stop (nul_dbus_service_t *service) { nul_dbus_object_t **obj; if (!service->running) return; for (obj = service->objects; *obj != NULL; ++obj) { nul_dbus_object_t *object = *obj; connection_unregister_object ( service->connection, object->name); } connection_release_name (service->connection, service->name); service->running = FALSE; } nul_dbus_object_t * nul_dbus_object (const char *name, gpointer data, nul_dbus_interface_t *interface1, ...) { nul_dbus_object_t *object = g_new0 (nul_dbus_object_t, 1); va_list args; object->name = g_strdup (name); object->data = data; va_start (args, interface1); object->interfaces = (nul_dbus_interface_t **)make_ptr_array (interface1, args); va_end (args); /* Add introspection interface */ /* Note: the actual introspection is special cased in invoke(), but we * still add it here so that introspection can treat the introspection * interface as any other interface */ object->interfaces = nul_array_append ( object->interfaces, nul_dbus_interface ( "org.freedesktop.DBus.Introspectable", nul_dbus_method ( "Introspect", (nul_dbus_function_t)NULL, nul_dbus_parameter_out ( "data", nul_dbus_type_string()), NULL), NULL)); return object; } nul_dbus_interface_t * nul_dbus_interface (const char *name, nul_dbus_member_t *member1, ...) { nul_dbus_interface_t *interface = g_new0 (nul_dbus_interface_t, 1); va_list parameters; nul_dbus_member_t **mem; interface->name = g_strdup (name); va_start (parameters, member1); interface->members = (nul_dbus_member_t **)make_ptr_array (member1, parameters); va_end (parameters); for (mem = interface->members; *mem; ++mem) (* mem)->interface = interface; return interface; } static nul_dbus_type_t * type_copy (const nul_dbus_type_t *type) { nul_dbus_type_t *copy = g_new0 (nul_dbus_type_t, 1); copy->type = type->type; return copy; } static nul_type_t invoke_type_from_nul_dbus_type (nul_dbus_type_t *t) { switch (t->type) { case INT32: return NUL_TYPE_INT32; case UINT32: return NUL_TYPE_UINT32; case STRING: return NUL_TYPE_POINTER; } g_warning ("Unknown type %d\n", t->type); return -1; } static nul_fun_def_t * make_fun_def (nul_dbus_member_t *member) { nul_type_t *types; int i; types = idle_free (g_new0 (nul_type_t, nul_array_len (member->parameters) + 1), NULL); types[0] = NUL_TYPE_POINTER; /* For object->data */ for (i = 1; i < nul_array_len (member->parameters); ++i) { nul_dbus_parameter_t *par = member->parameters[i - 1]; if (par->out) types[i] = NUL_TYPE_POINTER; else types[i] = invoke_type_from_nul_dbus_type (par->type); } return nul_fun_def_new (NUL_TYPE_INT, nul_array_len (member->parameters) + 1, types); } static nul_fun_def_t * make_reply_fun (nul_dbus_member_t *member) { nul_type_t *types; nul_dbus_parameter_t **p; int n; types = idle_free ( g_new0 (nul_type_t, nul_array_len (member->parameters) + 2), NULL); n = 0; for (p = member->parameters; *p; ++p) { nul_dbus_parameter_t *par = *p; g_assert (*p != NULL); g_assert (par != NULL); if (par->out) types[n++] = invoke_type_from_nul_dbus_type (par->type); } types[n++] = NUL_TYPE_POINTER; /* data */ types[n++] = NUL_TYPE_POINTER; /* const GError *err */ return nul_fun_def_new (NUL_TYPE_VOID, n, types); } nul_dbus_member_t * nul_dbus_method (const char *name, nul_dbus_function_t function, nul_dbus_parameter_t *parameter1, ...) { nul_dbus_member_t *member = g_new0 (nul_dbus_member_t, 1); va_list parameters; nul_dbus_parameter_t **p; member->name = g_strdup (name); member->function = function; va_start (parameters, parameter1); member->parameters = (nul_dbus_parameter_t **)make_ptr_array (parameter1, parameters); va_end (parameters); member->n_inputs = 0; member->n_outputs = 0; for (p = member->parameters; *p; ++p) { nul_dbus_parameter_t *par = *p; g_assert (*p != NULL); g_assert (par != NULL); if (par->out) { member->n_outputs++; } else { member->n_inputs++; if (member->n_outputs) { g_critical ("All output parameters must be specified after the input parameters"); return NULL; } } } member->fun_def = make_fun_def (member); member->reply_fun = make_reply_fun (member); return member; } static nul_dbus_parameter_t * make_parameter (const char *name, const nul_dbus_type_t *type, gboolean out) { nul_dbus_parameter_t *parameter = g_new0 (nul_dbus_parameter_t, 1); parameter->name = g_strdup (name); parameter->type = type_copy (type); parameter->out = out; return parameter; } nul_dbus_parameter_t * nul_dbus_parameter_in (const char *name, const nul_dbus_type_t *type) { return make_parameter (name, type, FALSE); } nul_dbus_parameter_t * nul_dbus_parameter_out (const char *name, const nul_dbus_type_t *type) { return make_parameter (name, type, TRUE); } static nul_dbus_type_t * make_type (nul_type_t t) { nul_dbus_type_t *type = idle_free (g_new0 (nul_dbus_type_t, 1), NULL); type->type = t; return type; } const nul_dbus_type_t * nul_dbus_type_int32 (void) { return make_type (INT32); } const nul_dbus_type_t * nul_dbus_type_uint32 (void) { return make_type (UINT32); } const nul_dbus_type_t * nul_dbus_type_string (void) { return make_type (STRING); } typedef struct { nul_dbus_function_t callback; gpointer data; nul_dbus_member_t *method; } InvokeInfo; static gboolean on_reply (connection_t *connection, DBusMessage *reply, gpointer data) { InvokeInfo *info = data; int n_outputs = info->method->n_outputs; nul_dbus_parameter_t **outputs; nul_arg_t *args; nul_arg_t arg; char *err; if (!info->callback) return TRUE; outputs = (nul_dbus_parameter_t **)&(info->method->parameters[info->method->n_inputs]); args = nul_array_new (nul_arg_t); if (!decode_message (&args, reply, n_outputs, outputs, &err)) arg.v_pointer = err; else arg.v_pointer = NULL; args = nul_array_append (args, arg); /* error */ arg.v_pointer = info->data; args = nul_array_append (args, arg); nul_fun_def_invoke (info->method->reply_fun, (nul_function_t)info->callback, args); nul_array_free (args); return TRUE; } /* Decodes strings like this: * * /org.gnome.ScreenSaver.Throttle * */ static gboolean decode_method_desc (const char *method_desc, char **object_path, char **interface, char **method) { char *m; g_return_val_if_fail (method_desc != NULL, FALSE); g_return_val_if_fail (strchr (method_desc, '/') != NULL, FALSE); g_return_val_if_fail (strchr (method_desc, '.') != NULL, FALSE); m = strrchr (method_desc, '/'); *object_path = g_strndup (method_desc, m - method_desc); method_desc = m + 1; m = strrchr (method_desc, '.'); *interface = g_strndup (method_desc, m - method_desc); method_desc = m; *method = g_strdup (m + 1); return TRUE; } static nul_dbus_object_t * find_object (nul_dbus_service_t *service, const char *name) { nul_dbus_object_t **obj; for (obj = service->objects; *obj != NULL; ++obj) { nul_dbus_object_t *object = *obj; if (strcmp (object->name, name) == 0) return object; } return NULL; } static nul_dbus_interface_t * find_interface (nul_dbus_object_t *object, const char *name) { nul_dbus_interface_t **itf; for (itf = object->interfaces; *itf; ++itf) { nul_dbus_interface_t *interface = *itf; if (strcmp (interface->name, name) == 0) return interface; } return NULL; } static nul_dbus_member_t * find_member (nul_dbus_interface_t *interface, const char *name) { nul_dbus_member_t **mem; for (mem = interface->members; *mem; ++mem) { nul_dbus_member_t *member = *mem; if (strcmp (member->name, name) == 0) return member; } return NULL; } void nul_dbus_invoke (nul_dbus_service_t *service, const char *method_desc, nul_dbus_function_t callback, gpointer data, ...) { char *object_str, *interface_str, *method_str; nul_dbus_object_t *object; nul_dbus_interface_t *interface; nul_dbus_member_t *method; DBusMessage *message; DBusMessageIter iter; connection_t *connection; va_list args; InvokeInfo *info; nul_dbus_parameter_t **p; g_return_if_fail (service != NULL); if (!decode_method_desc (method_desc, &object_str, &interface_str, &method_str)) return; object = find_object (service, object_str); if (!object) { g_critical ("Object '%s' not found", object_str); return; } interface = find_interface (object, interface_str); if (!interface) { g_critical ("Interface '%s' not found", interface_str); return; } method = find_member (interface, method_str); if (!method) { g_critical ("Method '%s' not found", method_str); return; } message = dbus_message_new_method_call ( service->name, object->name, interface->name, method->name); dbus_message_iter_init_append (message, &iter); va_start (args, data); for (p = method->parameters; *p; ++p) { nul_dbus_parameter_t *parameter = *p; int32_t i32; uint32_t u32; const char *s; if (parameter->out) continue; switch (parameter->type->type) { case INT32: i32 = va_arg (args, int32_t); dbus_message_iter_append_basic (&iter, DBUS_TYPE_INT32, &i32); break; case UINT32: u32 = va_arg (args, uint32_t); dbus_message_iter_append_basic (&iter, DBUS_TYPE_UINT32, &u32); break; case STRING: s = va_arg (args, const char *); dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &s); break; } } va_end (args); connection = service->connection; info = g_new0 (InvokeInfo, 1); info->callback = callback; info->data = data; info->method = method; if (!connection_send_with_reply (connection, message, on_reply, info)) { /* FIXME: generate an error and call the callback */ /* Or alternatively, we should deal with getting disconnected, * though it's not clear what we should do with that */ } dbus_message_unref (message); } void nul_dbus_service_set_object_data (nul_dbus_service_t *service, const char *obj_name, gpointer data) { nul_dbus_object_t *object = find_object (service, obj_name); g_return_if_fail (object != NULL); object->data = data; }