blob: 7be3de6b8e73c90b428efb0126dc3f0f90a7c47d [file] [log] [blame]
/* nih-dbus-tool
*
* parse.c - parse XML introspection data and tool-specific annotations
*
* 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 <expat.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <nih/macros.h>
#include <nih/alloc.h>
#include <nih/list.h>
#include <nih/error.h>
#include <nih/logging.h>
#include "node.h"
#include "interface.h"
#include "method.h"
#include "signal.h"
#include "property.h"
#include "argument.h"
#include "annotation.h"
#include "parse.h"
#include "errors.h"
/**
* BUF_SIZE:
*
* Size of buffer we use when parsing.
**/
#define BUF_SIZE 80
/**
* parse_stack_push:
* @parent: parent object of new stack entry,
* @stack: stack to push onto,
* @type: type of object to push,
* @data: pointer to object data.
*
* Allocates a new Stack object with the @type and @data specified
* and pushes it onto @stack.
*
* The entry can be removed from the stack by freeing it, though this will
* not free the associated @data unless you arrange that by references.
*
* If @parent is not NULL, it should be a pointer to another object which
* will be used as a parent for the returned entry. When all parents
* of the returned node are freed, the returned entry will also be
* freed.
*
* Returns: new entry or NULL if the allocation failed.
**/
ParseStack *
parse_stack_push (const void * parent,
NihList * stack,
ParseStackType type,
void * data)
{
ParseStack *entry;
nih_assert (stack != NULL);
entry = nih_new (parent, ParseStack);
if (! entry)
return NULL;
nih_list_init (&entry->entry);
nih_alloc_set_destructor (entry, nih_list_destroy);
entry->type = type;
entry->data = data;
if ((entry->type != PARSE_IGNORED)
&& (entry->type != PARSE_ANNOTATION)) {
nih_assert (entry->data != NULL);
nih_ref (entry->data, entry);
} else {
nih_assert (entry->data == NULL);
}
nih_list_add_after (stack, &entry->entry);
return entry;
}
/**
* parse_stack_top:
* @stack: stack to return from.
*
* Returns: the top entry in @stack or NULL if the stack is empty.
**/
ParseStack *
parse_stack_top (NihList *stack)
{
nih_assert (stack != NULL);
if (! NIH_LIST_EMPTY (stack)) {
return (ParseStack *)stack->next;
} else {
return NULL;
}
}
/**
* parse_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 intended to be used as the start element handler for
* the XML parser @xmlp and called by that parser. It looks at the tag
* name @tag and calls one of the other *_start_tag() functions to
* handle the tag.
*
* Unknown tags result in a warning and are otherwise ignored, the stack
* contains an ignore element and the content of those tags will also be
* ignored with no warnings generated.
*
* Errors are raised and reported by stopping the parser, other element
* handlers should check that the parsing status is not finished as they
* may be called as part of the unwinding process. XML_ParseBuffer will
* return to indicate an error, the XML error code will be XML_ERROR_ABORTED
* and the actual error can be retrieved with nih_error_get().
**/
void
parse_start_tag (XML_Parser xmlp,
const char * tag,
char * const *attr)
{
XML_ParsingStatus status;
ParseContext * context;
ParseStack * parent;
int ret = 0;
nih_assert (xmlp != NULL);
nih_assert (tag != NULL);
nih_assert (attr != NULL);
XML_GetParsingStatus (xmlp, &status);
if (status.parsing == XML_FINISHED)
return;
nih_debug ("Parsed '%s' tag", tag);
context = XML_GetUserData (xmlp);
nih_assert (context != NULL);
/* Ignore any tag inside an ignored tag */
parent = parse_stack_top (&context->stack);
if (parent && (parent->type == PARSE_IGNORED)) {
if (! parse_stack_push (NULL, &context->stack,
PARSE_IGNORED, NULL)) {
nih_error_raise_system ();
ret = -1;
}
goto exit;
}
/* Otherwise call out to handle the tag */
if (! strcmp (tag, "node")) {
ret = node_start_tag (xmlp, tag, attr);
} else if (! strcmp (tag, "interface")) {
ret = interface_start_tag (xmlp, tag, attr);
} else if (! strcmp (tag, "method")) {
ret = method_start_tag (xmlp, tag, attr);
} else if (! strcmp (tag, "signal")) {
ret = signal_start_tag (xmlp, tag, attr);
} else if (! strcmp (tag, "property")) {
ret = property_start_tag (xmlp, tag, attr);
} else if (! strcmp (tag, "arg")) {
ret = argument_start_tag (xmlp, tag, attr);
} else if (! strcmp (tag, "annotation")) {
ret = annotation_start_tag (xmlp, tag, attr);
} else {
nih_warn ("%s:%zu:%zu: %s: %s", context->filename,
(size_t)XML_GetCurrentLineNumber (xmlp),
(size_t)XML_GetCurrentColumnNumber (xmlp),
_("Ignored unknown tag"),
tag);
if (! parse_stack_push (NULL, &context->stack,
PARSE_IGNORED, NULL)) {
nih_error_raise_system ();
ret = -1;
}
}
exit:
if (ret < 0)
nih_assert (XML_StopParser (xmlp, FALSE) == XML_STATUS_OK);
}
/**
* parse_end_tag:
* @xmlp: XML parser,
* @tag: name of XML tag being parsed.
*
* This function is intended to be used as the end element handler for
* the XML parser @xmlp and called by that parser. It looks at the tag
* name @tag and calls one of the other *_end_tag() functions to
* handle the tag.
*
* The end tags whose start was ignored are ignored without any warning.
*
* Errors are raised and reported by stopping the parser, other element
* handlers should check that the parsing status is not finished as they
* may be called as part of the unwinding process. XML_ParseBuffer will
* return to indicate an error, the XML error code will be XML_ERROR_ABORTED
* and the actual error can be retrieved with nih_error_get().
**/
void
parse_end_tag (XML_Parser xmlp,
const char *tag)
{
XML_ParsingStatus status;
ParseContext * context;
ParseStack * entry;
int ret = 0;
nih_assert (xmlp != NULL);
nih_assert (tag != NULL);
XML_GetParsingStatus (xmlp, &status);
if (status.parsing == XML_FINISHED)
return;
nih_debug ("Parsed '%s' end tag", tag);
context = XML_GetUserData (xmlp);
nih_assert (context != NULL);
/* Ignore the end tag of any ignored tag */
entry = parse_stack_top (&context->stack);
nih_assert (entry != NULL);
if (entry->type == PARSE_IGNORED) {
nih_free (entry);
goto exit;
}
/* Otherwise call out to handle the tag */
if (! strcmp (tag, "node")) {
ret = node_end_tag (xmlp, tag);
} else if (! strcmp (tag, "interface")) {
ret = interface_end_tag (xmlp, tag);
} else if (! strcmp (tag, "method")) {
ret = method_end_tag (xmlp, tag);
} else if (! strcmp (tag, "signal")) {
ret = signal_end_tag (xmlp, tag);
} else if (! strcmp (tag, "property")) {
ret = property_end_tag (xmlp, tag);
} else if (! strcmp (tag, "arg")) {
ret = argument_end_tag (xmlp, tag);
} else if (! strcmp (tag, "annotation")) {
ret = annotation_end_tag (xmlp, tag);
} else {
nih_assert_not_reached ();
}
exit:
if (ret < 0)
nih_assert (XML_StopParser (xmlp, FALSE) == XML_STATUS_OK);
}
/**
* parse_xml:
* @parent: parent object of new node,
* @fd: file descriptor to parse from,
* @filename: filename for error reporting.
*
* Parse XML data from @fd according to the D-Bus Introspection
* specification, returning the top-level Node which contains the
* Interfaces defined by that object.
*
* Errors in parsing are output within this function, since it has the
* line and column number available to it. @filename is used when reporting
* these errors.
*
* In general, the parser is fairly liberal and will ignore unexpected tags,
* attributes and any character data. However it is strict about restrictions
* in the specification, for example it will not allow missing attributes or
* unknown values in them.
*
* If @parent is not NULL, it should be a pointer to another object which
* will be used as a parent for the returned node. When all parents
* of the returned node are freed, the returned node will also be
* freed.
*
* Returns: newly allocated Node on success, NULL on error.
**/
Node *
parse_xml (const void *parent,
int fd,
const char *filename)
{
ParseContext context;
XML_Parser xmlp;
ssize_t len;
context.parent = parent;
nih_list_init (&context.stack);
context.filename = filename;
context.node = NULL;
xmlp = XML_ParserCreate ("UTF-8");
if (! xmlp) {
nih_error ("%s: %s", _("Unable to create XML Parser"),
strerror (ENOMEM));
return NULL;
}
XML_SetUserData (xmlp, &context);
XML_UseParserAsHandlerArg (xmlp);
XML_SetElementHandler (xmlp,
(XML_StartElementHandler)parse_start_tag,
(XML_EndElementHandler)parse_end_tag);
do {
enum XML_Status status;
enum XML_Error error;
void * buf;
buf = XML_GetBuffer (xmlp, BUF_SIZE);
if (! buf) {
error = XML_GetErrorCode (xmlp);
nih_error ("%s: %s", _("Unable to allocate parsing buffer"),
XML_ErrorString (error));
goto error;
}
len = read (fd, buf, BUF_SIZE);
if (len < 0) {
nih_error ("%s: %s: %s", context.filename,
_("Read error"), strerror (errno));
goto error;
}
status = XML_ParseBuffer (xmlp, len, len == 0 ? TRUE : FALSE);
if (status != XML_STATUS_OK) {
error = XML_GetErrorCode (xmlp);
if (error == XML_ERROR_ABORTED) {
NihError *err;
err = nih_error_get ();
nih_error ("%s:%zu:%zu: %s", context.filename,
(size_t)XML_GetCurrentLineNumber (xmlp),
(size_t)XML_GetCurrentColumnNumber (xmlp),
err->message);
nih_free (err);
} else {
nih_error ("%s:%zu:%zu: %s: %s",
context.filename,
(size_t)XML_GetCurrentLineNumber (xmlp),
(size_t)XML_GetCurrentColumnNumber (xmlp),
_("XML parse error"),
XML_ErrorString (error));
}
goto error;
}
} while (len > 0);
nih_assert (NIH_LIST_EMPTY (&context.stack));
if (! context.node) {
nih_error ("%s: %s", context.filename,
_("No node present"));
goto error;
}
XML_ParserFree (xmlp);
return context.node;
error:
NIH_LIST_FOREACH_SAFE (&context.stack, iter) {
ParseStack *entry = (ParseStack *)iter;
nih_free (entry);
}
if (context.node)
nih_free (context.node);
XML_ParserFree (xmlp);
return NULL;
}