Logo Search packages:      
Sourcecode: libnih version File versions  Download package

signal.c

/* nih-dbus-tool
 *
 * signal.c - signal parsing and generation
 *
 * 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 <string.h>

#include <nih/macros.h>
#include <nih/alloc.h>
#include <nih/list.h>
#include <nih/string.h>
#include <nih/logging.h>
#include <nih/error.h>

#include "symbol.h"
#include "indent.h"
#include "type.h"
#include "marshal.h"
#include "demarshal.h"
#include "interface.h"
#include "signal.h"
#include "argument.h"
#include "parse.h"
#include "errors.h"


/**
 * signal_name_valid:
 * @name: Member name to verify.
 *
 * Verifies whether @name matches the specification for a D-Bus interface
 * member name, and thus is valid for a signal.
 *
 * Returns: TRUE if valid, FALSE if not.
 **/
int
signal_name_valid (const char *name)
{
      nih_assert (name != NULL);

      /* We can get away with just using strlen() here even through name
       * is in UTF-8 because all the valid characters are ASCII.
       */
      for (size_t i = 0; i < strlen (name); i++) {
            /* Names may contain digits, but not at the beginning. */
            if ((name[i] >= '0') && (name[i] <= '9')) {
                  if (i == 0)
                        return FALSE;

                  continue;
            }

            /* Valid characters anywhere are [A-Za-z_] */
            if (   ((name[i] < 'A') || (name[i] > 'Z'))
                && ((name[i] < 'a') || (name[i] > 'z'))
                && (name[i] != '_'))
                  return FALSE;
      }

      /* Name must be at least 1 character and no more than 255 characters */
      if ((strlen (name) < 1) || (strlen (name) > 255))
            return FALSE;

      return TRUE;
}


/**
 * signal_new:
 * @parent: parent object for new signal,
 * @name: D-Bus name of signal.
 *
 * Allocates a new D-Bus object Signal data structure, with the D-Bus name
 * set to @name.  The returned structure is not placed into any list and
 * has no arguments.
 *
 * If @parent is not NULL, it should be a pointer to another object which
 * will be used as a parent for the returned signal.  When all parents
 * of the returned signal are freed, the returned signal will also be
 * freed.
 *
 * Returns: the new signal or NULL if the allocation failed.
 **/
Signal *
signal_new (const void *parent,
          const char *name)
{
      Signal *signal;

      nih_assert (name != NULL);

      signal = nih_new (parent, Signal);
      if (! signal)
            return NULL;

      nih_list_init (&signal->entry);

      nih_alloc_set_destructor (signal, nih_list_destroy);

      signal->name = nih_strdup (signal, name);
      if (! signal->name) {
            nih_free (signal);
            return NULL;
      }

      signal->symbol = NULL;
      signal->deprecated = FALSE;

      nih_list_init (&signal->arguments);

      return signal;
}


/**
 * signal_start_tag:
 * @xmlp: XML parser,
 * @tag: name of XML tag being parsed,
 * @attr: NULL-terminated array of attribute name and value pairs.
 *
 * This function is called by parse_start_tag() for a "signal"
 * start tag, a child of the "interface" tag that defines a signal the
 * D-Bus interface specifies.
 *
 * If the method does not appear within an interface tag a warning is
 * emitted and the tag will be ignored.
 *
 * Signals must have a "name" attribute containing the D-Bus name
 * of the signal.
 *
 * Any unknown attributes result in a warning and will be ignored.
 *
 * A Signal object will be allocated and pushed onto the stack, this is
 * not added to the interface until the end tag is found.
 *
 * Returns: zero on success, negative value on raised error.
 **/
int
signal_start_tag (XML_Parser    xmlp,
              const char *  tag,
              char * const *attr)
{
      ParseContext *    context;
      ParseStack *      parent;
      nih_local Signal *signal = NULL;
      char * const *    key;
      char * const *    value;
      const char *      name = NULL;

      nih_assert (xmlp != NULL);
      nih_assert (tag != NULL);
      nih_assert (attr != NULL);

      context = XML_GetUserData (xmlp);
      nih_assert (context != NULL);

      /* Signals should only appear inside interfaces. */
      parent = parse_stack_top (&context->stack);
      if ((! parent) || (parent->type != PARSE_INTERFACE)) {
            nih_warn ("%s:%zu:%zu: %s", context->filename,
                    (size_t)XML_GetCurrentLineNumber (xmlp),
                    (size_t)XML_GetCurrentColumnNumber (xmlp),
                    _("Ignored unexpected <signal> tag"));

            if (! parse_stack_push (NULL, &context->stack,
                              PARSE_IGNORED, NULL))
                  nih_return_system_error (-1);

            return 0;
      }

      /* Retrieve the name from the attributes */
      for (key = attr; key && *key; key += 2) {
            value = key + 1;
            nih_assert (value && *value);

            if (! strcmp (*key, "name")) {
                  name = *value;
            } else {
                  nih_warn ("%s:%zu:%zu: %s: %s", context->filename,
                          (size_t)XML_GetCurrentLineNumber (xmlp),
                          (size_t)XML_GetCurrentColumnNumber (xmlp),
                          _("Ignored unknown <signal> attribute"),
                          *key);
            }
      }

      /* Check we have a name and that it's valid */
      if (! name)
            nih_return_error (-1, SIGNAL_MISSING_NAME,
                          _(SIGNAL_MISSING_NAME_STR));
      if (! signal_name_valid (name))
            nih_return_error (-1, SIGNAL_INVALID_NAME,
                          _(SIGNAL_INVALID_NAME_STR));

      /* Allocate a Signal object and push onto the stack */
      signal = signal_new (NULL, name);
      if (! signal)
            nih_return_system_error (-1);

      if (! parse_stack_push (NULL, &context->stack, PARSE_SIGNAL, signal)) {
            nih_error_raise_system ();
            return -1;
      }

      return 0;
}

/**
 * signal_end_tag:
 * @xmlp: XML parser,
 * @tag: name of XML tag being parsed.
 *
 * This function is called by parse_end_tag() for a "signal" end
 * tag, and matches a call to signal_start_tag() made at the same parsing
 * level.
 *
 * The signal is added to the list of signals defined by the parent
 * interface.
 *
 * Returns: zero on success, negative value on raised error.
 **/
int
signal_end_tag (XML_Parser  xmlp,
            const char *tag)
{
      ParseContext *context;
      ParseStack *  entry;
      ParseStack *  parent;
      Signal *      signal;
      Signal *      conflict;
      Interface *   interface;

      nih_assert (xmlp != NULL);
      nih_assert (tag != NULL);

      context = XML_GetUserData (xmlp);
      nih_assert (context != NULL);

      entry = parse_stack_top (&context->stack);
      nih_assert (entry != NULL);
      nih_assert (entry->type == PARSE_SIGNAL);
      signal = entry->signal;

      /* Generate a symbol from the name */
      if (! signal->symbol) {
            signal->symbol = symbol_from_name (signal, signal->name);
            if (! signal->symbol)
                  nih_return_no_memory_error (-1);
      }

      nih_list_remove (&entry->entry);
      parent = parse_stack_top (&context->stack);
      nih_assert (parent != NULL);
      nih_assert (parent->type == PARSE_INTERFACE);
      interface = parent->interface;

      /* Make sure there's not a conflict before adding the signal */
      conflict = signal_lookup (interface, signal->symbol);
      if (conflict) {
            nih_error_raise_printf (SIGNAL_DUPLICATE_SYMBOL,
                              _(SIGNAL_DUPLICATE_SYMBOL_STR),
                              signal->symbol, conflict->name);
            return -1;
      }

      nih_debug ("Add %s signal to %s interface",
               signal->name, interface->name);
      nih_list_add (&interface->signals, &signal->entry);
      nih_ref (signal, interface);

      nih_free (entry);

      return 0;
}


/**
 * signal_annotation:
 * @signal: signal object annotation applies to,
 * @name: annotation name,
 * @value: annotation value.
 *
 * Handles applying the annotation @name with value @value to the signal
 * @signal.  Signals may be annotated as deprecated or may have an alternate
 * symbol name specified.
 *
 * Unknown annotations or illegal values to the known annotations result
 * in an error being raised.
 *
 * Returns: zero on success, negative value on raised error.
 **/
int
signal_annotation (Signal *    signal,
               const char *name,
               const char *value)
{
      nih_assert (signal != NULL);
      nih_assert (name != NULL);
      nih_assert (value != NULL);

      if (! strcmp (name, "org.freedesktop.DBus.Deprecated")) {
            if (! strcmp (value, "true")) {
                  nih_debug ("Marked %s signal as deprecated",
                           signal->name);
                  signal->deprecated = TRUE;
            } else if (! strcmp (value, "false")) {
                  nih_debug ("Marked %s signal as not deprecated",
                           signal->name);
                  signal->deprecated = FALSE;
            } else {
                  nih_return_error (-1, SIGNAL_ILLEGAL_DEPRECATED,
                                _(SIGNAL_ILLEGAL_DEPRECATED_STR));
            }

      } else if (! strcmp (name, "com.netsplit.Nih.Symbol")) {
            if (symbol_valid (value)) {
                  if (signal->symbol)
                        nih_unref (signal->symbol, signal);

                  signal->symbol = nih_strdup (signal, value);
                  if (! signal->symbol)
                        nih_return_no_memory_error (-1);

                  nih_debug ("Set %s signal symbol to %s",
                           signal->name, signal->symbol);
            } else {
                  nih_return_error (-1, SIGNAL_INVALID_SYMBOL,
                                _(SIGNAL_INVALID_SYMBOL_STR));
            }

      } else {
            nih_error_raise_printf (SIGNAL_UNKNOWN_ANNOTATION,
                              "%s: %s: %s",
                              _(SIGNAL_UNKNOWN_ANNOTATION_STR),
                              signal->name, name);
            return -1;
      }

      return 0;
}


/**
 * signal_lookup:
 * @interface: interface to search,
 * @symbol: signal symbol to find.
 *
 * Finds a signal in @interface's signals list which has the generated
 * or supplied C symbol @symbol.
 *
 * Returns: signal found or NULL if no signal matches.
 **/
Signal *
signal_lookup (Interface * interface,
             const char *symbol)
{
      nih_assert (interface != NULL);
      nih_assert (symbol != NULL);

      NIH_LIST_FOREACH (&interface->signals, iter) {
            Signal *signal = (Signal *)iter;

            if (signal->symbol
                && (! strcmp (signal->symbol, symbol)))
                  return signal;
      }

      return NULL;
}

/**
 * signal_lookup_argument:
 * @signal: signal to search,
 * @symbol: argument symbol to find.
 *
 * Finds a argument in @signal's arguments list which has the generated
 * or supplied C symbol @symbol.
 *
 * Returns: argument found or NULL if no argument matches.
 **/
Argument *
signal_lookup_argument (Signal *    signal,
                  const char *symbol)
{
      nih_assert (signal != NULL);
      nih_assert (symbol != NULL);

      NIH_LIST_FOREACH (&signal->arguments, iter) {
            Argument *argument = (Argument *)iter;

            if (argument->symbol
                && (! strcmp (argument->symbol, symbol)))
                  return argument;
      }

      return NULL;
}


/**
 * signal_object_function:
 * @parent: parent object for new string.
 * @prefix: prefix for function name,
 * @interface: interface of @signal,
 * @signal: signal to generate function for,
 * @prototypes: list to append function prototypes to,
 * @structs: list to append structure definitions to.
 *
 * Generates C code for a function to emit a signal @signal on @interface by
 * marshalling the arguments.
 *
 * The prototype of the returned function is returned as a TypeFunc object
 * appended to the @prototypes list.
 *
 * If any of the arguments require a structure to be defined, the
 * definition is returned as a TypeStruct object appended to the @structs
 * list.  The name is generated from @prefix, @interface and @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 return string will also be
 * freed.
 *
 * Returns: newly allocated string or NULL if insufficient memory.
 **/
char *
signal_object_function (const void *parent,
                  const char *prefix,
                  Interface * interface,
                  Signal *    signal,
                  NihList *   prototypes,
                  NihList *   structs)
{
      NihList             locals;
      NihList             signal_structs;
      nih_local char *    name = NULL;
      nih_local TypeFunc *func = NULL;
      TypeVar *           arg;
      NihListEntry *      attrib;
      nih_local char *    assert_block = NULL;
      nih_local TypeVar * signal_var = NULL;
      nih_local TypeVar * iter_var = NULL;
      nih_local char *    marshal_block = NULL;
      nih_local char *    vars_block = NULL;
      nih_local char *    body = NULL;
      char *              code = NULL;

      nih_assert (prefix != NULL);
      nih_assert (interface != NULL);
      nih_assert (signal != NULL);
      nih_assert (prototypes != NULL);
      nih_assert (structs != NULL);

      nih_list_init (&locals);
      nih_list_init (&signal_structs);

      /* The function returns an integer, and accepts an argument for
       * the connection and origin path.  The integer indicates whether
       * an error occurred, so we want a warning if the result isn't used.
       */
      name = symbol_extern (NULL, prefix, interface->symbol, "emit",
                        signal->symbol, NULL);
      if (! name)
            return NULL;

      func = type_func_new (NULL, "int", name);
      if (! func)
            return NULL;

      attrib = nih_list_entry_new (func);
      if (! attrib)
            return NULL;

      attrib->str = nih_strdup (attrib, "warn_unused_result");
      if (! attrib->str)
            return NULL;

      nih_list_add (&func->attribs, &attrib->entry);

      arg = type_var_new (func, "DBusConnection *", "connection");
      if (! arg)
            return NULL;

      nih_list_add (&func->args, &arg->entry);

      if (! nih_strcat (&assert_block, NULL,
                    "nih_assert (connection != NULL);\n"))
            return NULL;

      arg = type_var_new (func, "const char *", "origin_path");
      if (! arg)
            return NULL;

      nih_list_add (&func->args, &arg->entry);

      if (! nih_strcat (&assert_block, NULL,
                    "nih_assert (origin_path != NULL);\n"))
            return NULL;

      /* The function requires a message pointer, which we allocate,
       * and an iterator for it to append the arguments.  Rather than
       * deal with these by hand, it's far easier to put them on the
       * locals list and deal with them along with the rest.
       */
      signal_var = type_var_new (NULL, "DBusMessage *", "signal");
      if (! signal_var)
            return NULL;

      nih_list_add (&locals, &signal_var->entry);

      iter_var = type_var_new (NULL, "DBusMessageIter", "iter");
      if (! iter_var)
            return NULL;

      nih_list_add (&locals, &iter_var->entry);


      /* Create the signal and set up the iterator to append to it. */
      if (! nih_strcat_sprintf (&marshal_block, NULL,
                          "/* Construct the message. */\n"
                          "signal = dbus_message_new_signal (origin_path, \"%s\", \"%s\");\n"
                          "if (! signal)\n"
                          "\treturn -1;\n"
                          "\n"
                          "dbus_message_iter_init_append (signal, &iter);\n"
                          "\n",
                          interface->name, signal->name))
            return NULL;

      /* Iterate over the signal's output arguments, for each one we
       * append the code to the marshalling code and at the same time
       * build up our own expected arguments themselves.
       */
      NIH_LIST_FOREACH (&signal->arguments, iter) {
            Argument *        argument = (Argument *)iter;
            NihList           arg_vars;
            NihList           arg_locals;
            NihList           arg_structs;
            DBusSignatureIter iter;
            nih_local char *  oom_error_code = NULL;
            nih_local char *  type_error_code = NULL;
            nih_local char *  block = NULL;

            if (argument->direction != NIH_DBUS_ARG_OUT)
                  continue;

            nih_list_init (&arg_vars);
            nih_list_init (&arg_locals);
            nih_list_init (&arg_structs);

            dbus_signature_iter_init (&iter, argument->type);

            /* In case of out of memory, simply return; the caller
             * can try again.
             */
            oom_error_code = nih_strdup (NULL,
                                   "dbus_message_unref (signal);\n"
                                   "return -1;\n");
            if (! oom_error_code)
                  return NULL;

            block = marshal (NULL, &iter, "iter", argument->symbol,
                         oom_error_code,
                         &arg_vars, &arg_locals,
                         prefix, interface->symbol,
                         signal->symbol, argument->symbol,
                         &arg_structs);
            if (! block)
                  return NULL;

            if (! nih_strcat_sprintf (&marshal_block, NULL,
                                "%s"
                                "\n",
                                block))
                  return NULL;

            /* We take a parameter of the expected type and name of
             * the marshal input variable; if it's a pointer, we
             * assert that it's not NULL and make sure it's const.
             */
            NIH_LIST_FOREACH_SAFE (&arg_vars, iter) {
                  TypeVar *var = (TypeVar *)iter;

                  if (! type_to_const (&var->type, var))
                        return NULL;

                  if (! type_strcat_assert (&assert_block, NULL, var,
                                      func->args.prev != &func->args ? (TypeVar *)func->args.prev : NULL,
                                      _iter.next != &arg_vars ? (TypeVar *)_iter.next : NULL))
                        return NULL;

                  nih_list_add (&func->args, &var->entry);
                  nih_ref (var, func);
            }

            NIH_LIST_FOREACH_SAFE (&arg_locals, iter) {
                  TypeVar *var = (TypeVar *)iter;

                  nih_list_add (&locals, &var->entry);
                  nih_ref (var, marshal_block);
            }

            NIH_LIST_FOREACH_SAFE (&arg_structs, iter) {
                  TypeStruct *structure = (TypeStruct *)iter;

                  nih_list_add (&signal_structs, &structure->entry);
                  nih_ref (structure, marshal_block);
            }
      }

      /* Lay out the function body, indenting it all before placing it
       * in the function code.
       */
      vars_block = type_var_layout (NULL, &locals);
      if (! vars_block)
            return NULL;

      if (! nih_strcat_sprintf (&body, NULL,
                          "%s"
                          "\n"
                          "%s"
                          "\n"
                          "%s"
                          "/* Send the signal, appending it to the outgoing queue. */\n"
                          "if (! dbus_connection_send (connection, signal, NULL)) {\n"
                          "\tdbus_message_unref (signal);\n"
                          "\treturn -1;\n"
                          "}\n"
                          "\n"
                          "dbus_message_unref (signal);\n"
                          "\n"
                          "return 0;\n",
                          vars_block,
                          assert_block,
                          marshal_block))
            return NULL;

      if (! indent (&body, NULL, 1))
            return NULL;

      /* Function header */
      code = type_func_to_string (parent, func);
      if (! code)
            return NULL;

      if (! nih_strcat_sprintf (&code, parent,
                          "{\n"
                          "%s"
                          "}\n",
                          body)) {
            nih_free (code);
            return NULL;
      }

      /* Append the function to the prototypes list */
      nih_list_add (prototypes, &func->entry);
      nih_ref (func, code);

      NIH_LIST_FOREACH_SAFE (&signal_structs, iter) {
            TypeStruct *structure = (TypeStruct *)iter;

            nih_ref (structure, code);
            nih_list_add (structs, &structure->entry);
      }

      return code;
}


/**
 * signal_proxy_function:
 * @parent: parent object for new string.
 * @prefix: prefix for function name,
 * @interface: interface of @signal,
 * @signal: signal to generate function for,
 * @prototypes: list to append function prototypes to,
 * @typedefs: list to append function pointer typedef definitions to,
 * @structs: list to append structure definitions to.
 *
 * Generates C code for a function that acts as a D-Bus connection filter
 * function checking that the incoming message matches @signal on @itnerface
 * and calls a handler function after demarshalling the arguments.
 *
 * The prototype of the returned function is returned as a TypeFunc object
 * appended to the @prototypes list.
 *
 * The typedef of the handler function is returned as a TypeFunc object
 * appended to the @typedefs list.
 *
 * If any of the arguments require a structure to be defined, the
 * definition is returned as a TypeStruct object appended to the @structs
 * list.  The name is generated from @prefix, @interface and @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 return string will also be
 * freed.
 *
 * Returns: newly allocated string or NULL if insufficient memory.
 **/
char *
signal_proxy_function  (const void *parent,
                  const char *prefix,
                  Interface * interface,
                  Signal *    signal,
                  NihList *   prototypes,
                  NihList *   typedefs,
                  NihList *   structs)
{
      NihList             locals;
      NihList             signal_structs;
      nih_local char *    name = NULL;
      nih_local TypeFunc *func = NULL;
      TypeVar *           arg;
      nih_local char *    assert_block = NULL;
      nih_local TypeVar * iter_var = NULL;
      nih_local TypeVar * parent_var = NULL;
      nih_local char *    demarshal_block = NULL;
      nih_local char *    call_block = NULL;
      nih_local char *    handler_type = NULL;
      nih_local char *    handler_name = NULL;
      nih_local TypeFunc *handler_func = NULL;
      NihListEntry *      attrib;
      nih_local char *    vars_block = NULL;
      nih_local char *    body = NULL;
      char *              code;

      nih_assert (prefix != NULL);
      nih_assert (interface != NULL);
      nih_assert (signal != NULL);
      nih_assert (prototypes != NULL);
      nih_assert (typedefs != NULL);
      nih_assert (structs != NULL);

      nih_list_init (&locals);
      nih_list_init (&signal_structs);

      /* The function returns a D-Bus handler result, accepting arguments
       * for the connection, received message and proxied signal structure.
       */
      name = symbol_impl (NULL, prefix, interface->name,
                      signal->name, "signal");
      if (! name)
            return NULL;

      func = type_func_new (NULL, "DBusHandlerResult", name);
      if (! func)
            return NULL;

      arg = type_var_new (func, "DBusConnection *", "connection");
      if (! arg)
            return NULL;

      nih_list_add (&func->args, &arg->entry);

      if (! nih_strcat (&assert_block, NULL,
                    "nih_assert (connection != NULL);\n"))
            return NULL;

      arg = type_var_new (func, "DBusMessage *", "signal");
      if (! arg)
            return NULL;

      nih_list_add (&func->args, &arg->entry);

      if (! nih_strcat (&assert_block, NULL,
                    "nih_assert (signal != NULL);\n"))
            return NULL;

      arg = type_var_new (func, "NihDBusProxySignal *", "proxied");
      if (! arg)
            return NULL;

      nih_list_add (&func->args, &arg->entry);

      if (! nih_strcat (&assert_block, NULL,
                    "nih_assert (proxied != NULL);\n"))
            return NULL;

      if (! nih_strcat (&assert_block, NULL,
                    "nih_assert (connection == proxied->proxy->connection);\n"))
            return NULL;

      /* The function requires a message context to act as the parent of
       * arguments and is passed to the handler function; we also need
       * an iterator for it.
       */
      iter_var = type_var_new (NULL, "DBusMessageIter", "iter");
      if (! iter_var)
            return NULL;

      nih_list_add (&locals, &iter_var->entry);

      parent_var = type_var_new (NULL, "NihDBusMessage *", "message");
      if (! parent_var)
            return NULL;

      nih_list_add (&locals, &parent_var->entry);

      /* Begin the demarshalling block by checking that the message is
       * the signal we're looking for, and then if it is, allocating a
       * message context and iterating the arguments.
       */
      if (! nih_strcat_sprintf (&demarshal_block, NULL,
                          "if (! dbus_message_is_signal (signal, proxied->interface->name, proxied->signal->name))\n"
                          "\treturn DBUS_HANDLER_RESULT_NOT_YET_HANDLED;\n"
                          "\n"
                          "if (! dbus_message_has_path (signal, proxied->proxy->path))\n"
                          "\treturn DBUS_HANDLER_RESULT_NOT_YET_HANDLED;\n"
                          "\n"
                          "if (proxied->proxy->name)\n"
                          "\tif (! dbus_message_has_sender (signal, proxied->proxy->owner))\n"
                          "\t\treturn DBUS_HANDLER_RESULT_NOT_YET_HANDLED;\n"
                          "\n"
                          "message = nih_dbus_message_new (NULL, connection, signal);\n"
                          "if (! message)\n"
                          "\treturn DBUS_HANDLER_RESULT_NEED_MEMORY;\n"
                          "\n"
                          "/* Iterate the arguments to the signal and demarshal into arguments\n"
                          " * for our own function call.\n"
                          " */\n"
                          "dbus_message_iter_init (message->message, &iter);\n"
                          "\n"))
            return NULL;

      /* Begin the handler calling block, which is not permitted to
       * reply.  Build up the typedef for it, which we mostly use for
       * type passing reasons.
       */
      handler_type = symbol_typedef (NULL, prefix, interface->symbol, NULL,
                               signal->symbol, "Handler");
      if (! handler_type)
            return NULL;

      if (! nih_strcat_sprintf (&call_block, NULL,
                          "/* Call the handler function */\n"
                          "nih_error_push_context ();\n"
                          "((%s)proxied->handler) (proxied->data, message",
                          handler_type))
            return NULL;

      handler_name = nih_sprintf (NULL, "(*%s)", handler_type);
      if (! handler_name)
            return NULL;

      handler_func = type_func_new (NULL, "typedef void", handler_name);
      if (! handler_func)
            return NULL;

      if (signal->deprecated) {
            attrib = nih_list_entry_new (handler_func);
            if (! attrib)
                  return NULL;

            attrib->str = nih_strdup (attrib, "deprecated");
            if (! attrib->str)
                  return NULL;

            nih_list_add (&handler_func->attribs, &attrib->entry);
      }


      arg = type_var_new (handler_func, "void *", "data");
      if (! arg)
            return NULL;

      nih_list_add (&handler_func->args, &arg->entry);

      arg = type_var_new (handler_func, "NihDBusMessage *", "message");
      if (! arg)
            return NULL;

      nih_list_add (&handler_func->args, &arg->entry);

      /* Iterate over the signal arguments, for each output argument
       * we append the code to the the demarshalling code.  At the
       * same time, we build up the handler call itself and transfer
       * the actual arguments to the locals list.
       */
      NIH_LIST_FOREACH (&signal->arguments, iter) {
            Argument *        argument = (Argument *)iter;
            NihList           arg_vars;
            NihList           arg_locals;
            NihList           arg_structs;
            DBusSignatureIter iter;
            nih_local char *  local_name = NULL;
            nih_local char *  oom_error_code = NULL;
            nih_local char *  type_error_code = NULL;
            nih_local char *  block = NULL;

            if (argument->direction != NIH_DBUS_ARG_OUT)
                  continue;

            nih_list_init (&arg_vars);
            nih_list_init (&arg_locals);
            nih_list_init (&arg_structs);

            dbus_signature_iter_init (&iter, argument->type);

            /* In case of out of memory, we just return and D-Bus will
             * call us again.  In case of type error, just ignore the
             * signal entirely.
             */
            oom_error_code = nih_sprintf (NULL,
                                    "nih_free (message);\n"
                                    "return DBUS_HANDLER_RESULT_NEED_MEMORY;\n");
            if (! oom_error_code)
                  return NULL;

            type_error_code = nih_strdup (NULL,
                                    "nih_free (message);\n"
                                    "return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;\n");
            if (! type_error_code)
                  return NULL;

            block = demarshal (NULL, &iter, "message", "iter",
                           argument->symbol,
                           oom_error_code,
                           type_error_code,
                           &arg_vars, &arg_locals,
                           prefix, interface->symbol,
                           signal->symbol, argument->symbol,
                           &arg_structs);
            if (! block)
                  return NULL;

            if (! nih_strcat (&block, NULL, "\n"))
                  return NULL;

            NIH_LIST_FOREACH_SAFE (&arg_vars, iter) {
                  TypeVar *var = (TypeVar *)iter;
                  TypeVar *arg;

                  if (! nih_strcat_sprintf (&call_block, NULL,
                                      ", %s",
                                      var->name))
                        return NULL;

                  nih_list_add (&locals, &var->entry);
                  nih_ref (var, call_block);

                  /* Handler argument is const */
                  arg = type_var_new (handler_func,
                                  var->type, var->name);
                  if (! arg)
                        return NULL;

                  if (! type_to_const (&arg->type, arg))
                        return NULL;

                  nih_list_add (&handler_func->args,
                              &arg->entry);
            }

            NIH_LIST_FOREACH_SAFE (&arg_locals, iter) {
                  TypeVar *var = (TypeVar *)iter;

                  nih_list_add (&locals, &var->entry);
                  nih_ref (var, demarshal_block);
            }

            NIH_LIST_FOREACH_SAFE (&arg_structs, iter) {
                  TypeStruct *structure = (TypeStruct *)iter;

                  nih_list_add (&signal_structs, &structure->entry);
                  nih_ref (structure, demarshal_block);
            }

            if (! nih_strcat (&demarshal_block, NULL, block))
                  return NULL;
      }

      /* Complete the demarshalling block, checking for any unexpected
       * arguments which we also want to error on.
       */
      if (! nih_strcat_sprintf (&demarshal_block, NULL,
                          "if (dbus_message_iter_get_arg_type (&iter) != DBUS_TYPE_INVALID) {\n"
                          "\tnih_free (message);\n"
                          "\treturn DBUS_HANDLER_RESULT_NOT_YET_HANDLED;\n"
                          "}\n"
                          "\n"))
          return NULL;

      /* Complete the call block */
      if (! nih_strcat (&call_block, NULL, ");\n"
                    "nih_error_pop_context ();\n"
                    "nih_free (message);\n"
                    "\n"))
            return NULL;

      /* Lay out the function body, indenting it all before placing it
       * in the function code.
       */
      vars_block = type_var_layout (NULL, &locals);
      if (! vars_block)
            return NULL;

      if (! nih_strcat_sprintf (&body, NULL,
                          "%s"
                          "\n"
                          "%s"
                          "\n"
                          "%s"
                          "%s"
                          "return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;\n",
                          vars_block,
                          assert_block,
                          demarshal_block,
                          call_block))
            return NULL;

      if (! indent (&body, NULL, 1))
            return NULL;

      /* Function header */
      code = type_func_to_string (parent, func);
      if (! code)
            return NULL;

      if (! nih_strcat_sprintf (&code, parent,
                          "{\n"
                          "%s"
                          "}\n",
                          body)) {
            nih_free (code);
            return NULL;
      }

      /* Append the functions to the prototypes and typedefs lists */
      nih_list_add (prototypes, &func->entry);
      nih_ref (func, code);

      nih_list_add (typedefs, &handler_func->entry);
      nih_ref (handler_func, code);

      NIH_LIST_FOREACH_SAFE (&signal_structs, iter) {
            TypeStruct *structure = (TypeStruct *)iter;

            nih_ref (structure, code);
            nih_list_add (structs, &structure->entry);
      }

      return code;
}


/**
 * signal_args_array:
 * @parent: parent object for new string,
 * @prefix: prefix for array name,
 * @interface: interface of @signal,
 * @signal: signal to generate array for,
 * @prototypes: list to append prototype to.
 *
 * Generates C code to declare an array of NihDBusArg variables containing
 * information about the arguments of the signal @signal on @interface.
 *
 * The prototype of the returned variable declaration is returned as a
 * TypeVar object appended to the @prototypes list.
 *
 * 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 return string will also be
 * freed.
 *
 * Returns: newly allocated string or NULL if insufficient memory.
 **/
char *
signal_args_array (const void *parent,
               const char *prefix,
               Interface * interface,
               Signal *    signal,
               NihList *   prototypes)
{
      nih_local char *name = NULL;
      size_t          max_name = 0;
      size_t          max_type = 0;
      nih_local char *block = NULL;
      char *          code;
      TypeVar *       var;

      nih_assert (prefix != NULL);
      nih_assert (interface != NULL);
      nih_assert (signal != NULL);
      nih_assert (prototypes != NULL);

      name = symbol_impl (NULL, prefix, interface->name,
                      signal->name, "signal_args");
      if (! name)
            return NULL;

      /* Figure out the longest argument name and signature */
      NIH_LIST_FOREACH (&signal->arguments, iter) {
            Argument *argument = (Argument *)iter;

            if (argument->name) {
                  if (strlen (argument->name) > max_name)
                        max_name = strlen (argument->name);
            } else {
                  if (max_name < 4)
                        max_name = 4;
            }

            if (strlen (argument->type) > max_type)
                  max_type = strlen (argument->type);
      }

      /* Append each argument such that the names, types and directions
       * are all lined up with each other.
       */
      NIH_LIST_FOREACH (&signal->arguments, iter) {
            Argument *      argument = (Argument *)iter;
            nih_local char *line = NULL;
            char *          dest;

            line = nih_alloc (NULL, max_name + max_type + 31);
            if (! line)
                  return NULL;

            dest = line;

            memcpy (dest, "{ ", 2);
            dest += 2;

            if (argument->name) {
                  memcpy (dest, "\"", 1);
                  dest += 1;

                  memcpy (dest, argument->name, strlen (argument->name));
                  dest += strlen (argument->name);

                  memcpy (dest, "\", ", 3);
                  dest += 3;

                  memset (dest, ' ', max_name - strlen (argument->name));
                  dest += max_name - strlen (argument->name);
            } else {
                  memcpy (dest, "NULL, ", 6);
                  dest += 6;

                  memset (dest, ' ', max_name - 2);
                  dest += max_name - 2;
            }

            memcpy (dest, "\"", 1);
            dest += 1;

            memcpy (dest, argument->type, strlen (argument->type));
            dest += strlen (argument->type);

            memcpy (dest, "\", ", 3);
            dest += 3;

            memset (dest, ' ', max_type - strlen (argument->type));
            dest += max_type - strlen (argument->type);

            memcpy (dest, "NIH_DBUS_ARG_OUT", 16);
            dest += 16;

            memcpy (dest, " },\n", 4);
            dest += 4;

            *dest = '\0';

            if (! nih_strcat (&block, NULL, line))
                  return NULL;
      }

      /* Append the final element to the block of elements, indent and
       * surround with the structure definition.
       */
      if (! nih_strcat (&block, NULL, "{ NULL }\n"))
            return NULL;

      if (! indent (&block, NULL, 1))
            return NULL;

      code = nih_sprintf (parent,
                      "const NihDBusArg %s[] = {\n"
                      "%s"
                      "};\n",
                      name,
                      block);
      if (! code)
            return NULL;

      /* Append the prototype to the list */
      var = type_var_new (code, "const NihDBusArg", name);
      if (! var) {
            nih_free (code);
            return NULL;
      }

      var->array = TRUE;

      nih_list_add (prototypes, &var->entry);

      return code;
}

Generated by  Doxygen 1.6.0   Back to index