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

argument.c

/* nih-dbus-tool
 *
 * argument.c - argument parsing and handling
 *
 * 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 <nih-dbus/dbus_object.h>

#include "symbol.h"
#include "argument.h"
#include "parse.h"
#include "errors.h"


/**
 * argument_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 argument.
 *
 * Returns: TRUE if valid, FALSE if not.
 **/
int
argument_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 */
      if (strlen (name) < 1)
            return FALSE;

      return TRUE;
}


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

      nih_assert (type != NULL);

      argument = nih_new (parent, Argument);
      if (! argument)
            return NULL;

      nih_list_init (&argument->entry);

      nih_alloc_set_destructor (argument, nih_list_destroy);

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

      argument->symbol = NULL;

      argument->type = nih_strdup (argument, type);
      if (! argument->type) {
            nih_free (argument);
            return NULL;
      }

      argument->direction = direction;

      return argument;
}


/**
 * argument_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 an "argument"
 * start tag, which may be a child of either the "method" or "signal" tags
 * defining an argument for the method or signal.
 *
 * If the argument does not appear within a method or signal tag a warning
 * is emitted and the tag will be ignored.
 *
 * Arguments must have a "type" attribute containing the D-Bus type
 * signature, they usually have a "name" attribute specifying the D-Bus name
 * but it's technically optional and they may also have a "direction"
 * attribute specifying whether the argument is input (default for methods)
 * or output (default for signals).
 *
 * Any unknown attributes result in a warning and will be ignored.
 *
 * An Argument object will be allocated and pushed onto the stack, this is
 * not added to the method or signal until the end tag is found.
 *
 * Returns: zero on success, negative value on raised error.
 **/
int
argument_start_tag (XML_Parser    xmlp,
                const char *  tag,
                char * const *attr)
{
      ParseContext *      context;
      ParseStack *        parent;
      nih_local Argument *argument = NULL;
      char * const *      key;
      char * const *      value;
      const char *        name = NULL;
      const char *        type = NULL;
      const char *        direction_str = NULL;
      NihDBusArgDir       direction;
      DBusError           error;

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

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

      /* Arguments should only appear inside methods or signals. */
      parent = parse_stack_top (&context->stack);
      if ((! parent) || ((parent->type != PARSE_METHOD)
                     && (parent->type != PARSE_SIGNAL)))
      {
            nih_warn ("%s:%zu:%zu: %s", context->filename,
                    (size_t)XML_GetCurrentLineNumber (xmlp),
                    (size_t)XML_GetCurrentColumnNumber (xmlp),
                    _("Ignored unexpected <arg> tag"));

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

            return 0;
      }

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

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

      /* Check we have a type and that it's valid (name and direction
       * are optional).  We also check the name is valid according to
       * member rules, strictly speaking there is no such restriction,
       * but we hereby invent one.
       */
      if (name && (! argument_name_valid (name)))
            nih_return_error (-1, ARGUMENT_INVALID_NAME,
                          _(ARGUMENT_INVALID_NAME_STR));

      if (! type)
            nih_return_error (-1, ARGUMENT_MISSING_TYPE,
                          _(ARGUMENT_MISSING_TYPE_STR));

      dbus_error_init (&error);
      if (! dbus_signature_validate_single (type, &error)) {
            nih_error_raise_printf (ARGUMENT_INVALID_TYPE, "%s: %s",
                              _(ARGUMENT_INVALID_TYPE_STR),
                              error.message);
            dbus_error_free (&error);
            return -1;
      }

      switch (parent->type) {
      case PARSE_METHOD:
            if (! direction_str) {
                  direction = NIH_DBUS_ARG_IN;
            } else if (! strcmp (direction_str, "in")) {
                  direction = NIH_DBUS_ARG_IN;
            } else if (! strcmp (direction_str, "out")) {
                  direction = NIH_DBUS_ARG_OUT;
            } else {
                  nih_return_error (-1, ARGUMENT_ILLEGAL_METHOD_DIRECTION,
                                _(ARGUMENT_ILLEGAL_METHOD_DIRECTION_STR));
            }
            break;
      case PARSE_SIGNAL:
            if (! direction_str) {
                  direction = NIH_DBUS_ARG_OUT;
            } else if (! strcmp (direction_str, "out")) {
                  direction = NIH_DBUS_ARG_OUT;
            } else {
                  nih_return_error (-1, ARGUMENT_ILLEGAL_SIGNAL_DIRECTION,
                                _(ARGUMENT_ILLEGAL_SIGNAL_DIRECTION_STR));
            }
            break;
      default:
            nih_assert_not_reached ();
      }

      /* Allocate an Argument object and push onto the stack */
      argument = argument_new (NULL, name, type, direction);
      if (! argument)
            nih_return_system_error (-1);

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

      return 0;
}

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

      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_ARGUMENT);
      argument = entry->argument;

      /* Generate a symbol from the name if we have one */
      if (argument->name && (! argument->symbol)) {
            argument->symbol = symbol_from_name (argument, argument->name);
            if (! argument->symbol)
                  nih_return_no_memory_error (-1);
      }

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

      switch (parent->type) {
      case PARSE_METHOD:
            method = parent->method;

            /* Otherwise generate a symbol from the argument count */
            if (! argument->symbol) {
                  size_t count = 0;

                  NIH_LIST_FOREACH (&method->arguments, iter)
                        count++;

                  argument->symbol = nih_sprintf (argument, "arg%zu",
                                          ++count);
                  if (! argument->symbol) {
                        nih_list_add_after (&context->stack,
                                        &entry->entry);
                        nih_return_no_memory_error (-1);
                  }
            }

            /* Make sure there's not a conflict before adding the arg */
            conflict = method_lookup_argument (method, argument->symbol);
            if (conflict) {
                  nih_error_raise_printf (ARGUMENT_DUPLICATE_SYMBOL,
                                    _(ARGUMENT_DUPLICATE_SYMBOL_STR),
                                    argument->symbol, conflict->name);
                  return -1;
            }

            nih_debug ("Add %s argument to %s method",
                     argument->name ?: "(unknown)",
                     method->name);
            nih_list_add (&method->arguments, &argument->entry);
            nih_ref (argument, method);
            break;
      case PARSE_SIGNAL:
            signal = parent->signal;

            /* Otherwise generate a symbol from the argument count */
            if (! argument->symbol) {
                  size_t count = 0;

                  NIH_LIST_FOREACH (&signal->arguments, iter)
                        count++;

                  argument->symbol = nih_sprintf (argument, "arg%zu",
                                          ++count);
                  if (! argument->symbol) {
                        nih_list_add_after (&context->stack,
                                        &entry->entry);
                        nih_return_no_memory_error (-1);
                  }
            }

            /* Make sure there's not a conflict before adding the arg */
            conflict = signal_lookup_argument (signal, argument->symbol);
            if (conflict) {
                  nih_error_raise_printf (ARGUMENT_DUPLICATE_SYMBOL,
                                    _(ARGUMENT_DUPLICATE_SYMBOL_STR),
                                    argument->symbol, conflict->name);
                  return -1;
            }

            nih_debug ("Add %s argument to %s signal",
                     argument->name ?: "(unknown)",
                     signal->name);
            nih_list_add (&signal->arguments, &argument->entry);
            nih_ref (argument, signal);
            break;
      default:
            nih_assert_not_reached ();
      }

      nih_free (entry);

      return 0;
}


/**
 * argument_annotation:
 * @argument: argument object annotation applies to,
 * @name: annotation name,
 * @value: annotation value.
 *
 * Handles applying the annotation @name with value @value to the argument
 * @argument.  While the D-Bus Introspection specification does not permit
 * annotations for arguments, this is an nih-dbus-tool extension.  Arguments
 * may be annotated with 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
argument_annotation (Argument *  argument,
                 const char *name,
                 const char *value)
{
      nih_assert (argument != NULL);
      nih_assert (name != NULL);
      nih_assert (value != NULL);

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

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

                  nih_debug ("Set %s argument symbol to %s",
                           argument->name ?: "(unknown)", argument->symbol);
            } else {
                  nih_return_error (-1, ARGUMENT_INVALID_SYMBOL,
                                _(ARGUMENT_INVALID_SYMBOL_STR));
            }
      } else {
            nih_error_raise_printf (ARGUMENT_UNKNOWN_ANNOTATION,
                              "%s: %s: %s",
                              _(ARGUMENT_UNKNOWN_ANNOTATION_STR),
                              argument->name ?: "(unnamed)",
                              name);
            return -1;
      }

      return 0;
}

Generated by  Doxygen 1.6.0   Back to index