blob: 09f54c7b4685d6010acdba76ec618a96a6a2faab [file] [log] [blame]
/* 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;
Argument * argument;
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 ();
nih_free (argument);
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;
}