1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2014 Lennart Poettering
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
24 #include "sd-bus-vtable.h"
26 #include "busctl-introspect.h"
28 #define NODE_DEPTH_MAX 16
30 typedef struct Context {
31 const XMLIntrospectOps *ops;
35 uint64_t interface_flags;
38 char *member_signature;
40 uint64_t member_flags;
47 static void context_reset_member(Context *c) {
49 free(c->member_signature);
50 free(c->member_result);
52 c->member_name = c->member_signature = c->member_result = NULL;
54 c->member_writable = false;
57 static void context_reset_interface(Context *c) {
58 free(c->interface_name);
59 c->interface_name = NULL;
60 c->interface_flags = 0;
62 context_reset_member(c);
65 static int parse_xml_annotation(Context *context, uint64_t *flags) {
71 } state = STATE_ANNOTATION;
73 _cleanup_free_ char *field = NULL, *value = NULL;
78 _cleanup_free_ char *name = NULL;
82 t = xml_tokenize(&context->current, &name, &context->xml_state, NULL);
84 log_error("XML parse error.");
89 log_error("Premature end of XML data.");
95 case STATE_ANNOTATION:
97 if (t == XML_ATTRIBUTE_NAME) {
99 if (streq_ptr(name, "name"))
102 else if (streq_ptr(name, "value"))
106 log_error("Unexpected <annotation> attribute %s.", name);
110 } else if (t == XML_TAG_CLOSE_EMPTY ||
111 (t == XML_TAG_CLOSE && streq_ptr(name, "annotation"))) {
114 if (streq_ptr(field, "org.freedesktop.DBus.Deprecated")) {
116 if (streq_ptr(value, "true"))
117 *flags |= SD_BUS_VTABLE_DEPRECATED;
119 } else if (streq_ptr(field, "org.freedesktop.DBus.Method.NoReply")) {
121 if (streq_ptr(value, "true"))
122 *flags |= SD_BUS_VTABLE_METHOD_NO_REPLY;
124 } else if (streq_ptr(field, "org.freedesktop.DBus.Property.EmitsChangedSignal")) {
126 if (streq_ptr(value, "const"))
127 *flags = (*flags & ~(SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION|SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE)) | SD_BUS_VTABLE_PROPERTY_CONST;
128 else if (streq_ptr(value, "invalidates"))
129 *flags = (*flags & ~(SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE|SD_BUS_VTABLE_PROPERTY_CONST)) | SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION;
130 else if (streq_ptr(value, "false"))
131 *flags = *flags & ~(SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE|SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION);
137 } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
138 log_error("Unexpected token in <annotation>. (1)");
146 if (t == XML_ATTRIBUTE_VALUE) {
151 state = STATE_ANNOTATION;
153 log_error("Unexpected token in <annotation>. (2)");
161 if (t == XML_ATTRIBUTE_VALUE) {
166 state = STATE_ANNOTATION;
168 log_error("Unexpected token in <annotation>. (3)");
175 assert_not_reached("Bad state");
180 static int parse_xml_node(Context *context, const char *prefix, unsigned n_depth) {
186 STATE_INTERFACE_NAME,
190 STATE_METHOD_ARG_NAME,
191 STATE_METHOD_ARG_TYPE,
192 STATE_METHOD_ARG_DIRECTION,
196 STATE_SIGNAL_ARG_NAME,
197 STATE_SIGNAL_ARG_TYPE,
201 STATE_PROPERTY_ACCESS,
202 } state = STATE_NODE;
204 _cleanup_free_ char *node_path = NULL, *argument_type = NULL, *argument_direction = NULL;
205 const char *np = prefix;
211 if (n_depth > NODE_DEPTH_MAX) {
212 log_error("<node> depth too high.");
217 _cleanup_free_ char *name = NULL;
220 t = xml_tokenize(&context->current, &name, &context->xml_state, NULL);
222 log_error("XML parse error.");
227 log_error("Premature end of XML data.");
234 if (t == XML_ATTRIBUTE_NAME) {
236 if (streq_ptr(name, "name"))
237 state = STATE_NODE_NAME;
239 log_error("Unexpected <node> attribute %s.", name);
243 } else if (t == XML_TAG_OPEN) {
245 if (streq_ptr(name, "interface"))
246 state = STATE_INTERFACE;
247 else if (streq_ptr(name, "node")) {
249 r = parse_xml_node(context, np, n_depth+1);
253 log_error("Unexpected <node> tag %s.", name);
257 } else if (t == XML_TAG_CLOSE_EMPTY ||
258 (t == XML_TAG_CLOSE && streq_ptr(name, "node"))) {
260 if (context->ops->on_path) {
261 r = context->ops->on_path(node_path ? node_path : np, context->userdata);
268 } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
269 log_error("Unexpected token in <node>. (1)");
275 case STATE_NODE_NAME:
277 if (t == XML_ATTRIBUTE_VALUE) {
281 if (name[0] == '/') {
286 if (endswith(prefix, "/"))
287 node_path = strappend(prefix, name);
289 node_path = strjoin(prefix, "/", name, NULL);
297 log_error("Unexpected token in <node>. (2)");
303 case STATE_INTERFACE:
305 if (t == XML_ATTRIBUTE_NAME) {
306 if (streq_ptr(name, "name"))
307 state = STATE_INTERFACE_NAME;
309 log_error("Unexpected <interface> attribute %s.", name);
313 } else if (t == XML_TAG_OPEN) {
314 if (streq_ptr(name, "method"))
315 state = STATE_METHOD;
316 else if (streq_ptr(name, "signal"))
317 state = STATE_SIGNAL;
318 else if (streq_ptr(name, "property")) {
319 context->member_flags |= SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE;
320 state = STATE_PROPERTY;
321 } else if (streq_ptr(name, "annotation")) {
322 r = parse_xml_annotation(context, &context->interface_flags);
326 log_error("Unexpected <interface> tag %s.", name);
329 } else if (t == XML_TAG_CLOSE_EMPTY ||
330 (t == XML_TAG_CLOSE && streq_ptr(name, "interface"))) {
333 if (context->ops->on_interface) {
334 r = context->ops->on_interface(context->interface_name, context->interface_flags, context->userdata);
339 context_reset_interface(context);
344 } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
345 log_error("Unexpected token in <interface>. (1)");
351 case STATE_INTERFACE_NAME:
353 if (t == XML_ATTRIBUTE_VALUE) {
355 free(context->interface_name);
356 context->interface_name = name;
360 state = STATE_INTERFACE;
362 log_error("Unexpected token in <interface>. (2)");
370 if (t == XML_ATTRIBUTE_NAME) {
371 if (streq_ptr(name, "name"))
372 state = STATE_METHOD_NAME;
374 log_error("Unexpected <method> attribute %s", name);
377 } else if (t == XML_TAG_OPEN) {
378 if (streq_ptr(name, "arg"))
379 state = STATE_METHOD_ARG;
380 else if (streq_ptr(name, "annotation")) {
381 r = parse_xml_annotation(context, &context->member_flags);
385 log_error("Unexpected <method> tag %s.", name);
388 } else if (t == XML_TAG_CLOSE_EMPTY ||
389 (t == XML_TAG_CLOSE && streq_ptr(name, "method"))) {
392 if (context->ops->on_method) {
393 r = context->ops->on_method(context->interface_name, context->member_name, context->member_signature, context->member_result, context->member_flags, context->userdata);
398 context_reset_member(context);
401 state = STATE_INTERFACE;
403 } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
404 log_error("Unexpected token in <method> (1).");
410 case STATE_METHOD_NAME:
412 if (t == XML_ATTRIBUTE_VALUE) {
415 free(context->member_name);
416 context->member_name = name;
420 state = STATE_METHOD;
422 log_error("Unexpected token in <method> (2).");
428 case STATE_METHOD_ARG:
430 if (t == XML_ATTRIBUTE_NAME) {
431 if (streq_ptr(name, "name"))
432 state = STATE_METHOD_ARG_NAME;
433 else if (streq_ptr(name, "type"))
434 state = STATE_METHOD_ARG_TYPE;
435 else if (streq_ptr(name, "direction"))
436 state = STATE_METHOD_ARG_DIRECTION;
438 log_error("Unexpected method <arg> attribute %s.", name);
441 } else if (t == XML_TAG_OPEN) {
442 if (streq_ptr(name, "annotation")) {
443 r = parse_xml_annotation(context, NULL);
447 log_error("Unexpected method <arg> tag %s.", name);
450 } else if (t == XML_TAG_CLOSE_EMPTY ||
451 (t == XML_TAG_CLOSE && streq_ptr(name, "arg"))) {
456 if (!argument_direction || streq(argument_direction, "in")) {
457 if (!strextend(&context->member_signature, argument_type, NULL))
459 } else if (streq(argument_direction, "out")) {
460 if (!strextend(&context->member_result, argument_type, NULL))
466 free(argument_direction);
467 argument_type = argument_direction = NULL;
470 state = STATE_METHOD;
471 } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
472 log_error("Unexpected token in method <arg>. (1)");
478 case STATE_METHOD_ARG_NAME:
480 if (t == XML_ATTRIBUTE_VALUE)
481 state = STATE_METHOD_ARG;
483 log_error("Unexpected token in method <arg>. (2)");
489 case STATE_METHOD_ARG_TYPE:
491 if (t == XML_ATTRIBUTE_VALUE) {
493 argument_type = name;
496 state = STATE_METHOD_ARG;
498 log_error("Unexpected token in method <arg>. (3)");
504 case STATE_METHOD_ARG_DIRECTION:
506 if (t == XML_ATTRIBUTE_VALUE) {
507 free(argument_direction);
508 argument_direction = name;
511 state = STATE_METHOD_ARG;
513 log_error("Unexpected token in method <arg>. (4)");
521 if (t == XML_ATTRIBUTE_NAME) {
522 if (streq_ptr(name, "name"))
523 state = STATE_SIGNAL_NAME;
525 log_error("Unexpected <signal> attribute %s.", name);
528 } else if (t == XML_TAG_OPEN) {
529 if (streq_ptr(name, "arg"))
530 state = STATE_SIGNAL_ARG;
531 else if (streq_ptr(name, "annotation")) {
532 r = parse_xml_annotation(context, &context->member_flags);
536 log_error("Unexpected <signal> tag %s.", name);
539 } else if (t == XML_TAG_CLOSE_EMPTY ||
540 (t == XML_TAG_CLOSE && streq_ptr(name, "signal"))) {
543 if (context->ops->on_signal) {
544 r = context->ops->on_signal(context->interface_name, context->member_name, context->member_signature, context->member_flags, context->userdata);
549 context_reset_member(context);
552 state = STATE_INTERFACE;
554 } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
555 log_error("Unexpected token in <signal>. (1)");
561 case STATE_SIGNAL_NAME:
563 if (t == XML_ATTRIBUTE_VALUE) {
566 free(context->member_name);
567 context->member_name = name;
571 state = STATE_SIGNAL;
573 log_error("Unexpected token in <signal>. (2)");
580 case STATE_SIGNAL_ARG:
582 if (t == XML_ATTRIBUTE_NAME) {
583 if (streq_ptr(name, "name"))
584 state = STATE_SIGNAL_ARG_NAME;
585 else if (streq_ptr(name, "type"))
586 state = STATE_SIGNAL_ARG_TYPE;
588 log_error("Unexpected signal <arg> attribute %s.", name);
591 } else if (t == XML_TAG_OPEN) {
592 if (streq_ptr(name, "annotation")) {
593 r = parse_xml_annotation(context, NULL);
597 log_error("Unexpected signal <arg> tag %s.", name);
600 } else if (t == XML_TAG_CLOSE_EMPTY ||
601 (t == XML_TAG_CLOSE && streq_ptr(name, "arg"))) {
604 if (!strextend(&context->member_signature, argument_type, NULL))
608 argument_type = NULL;
611 state = STATE_SIGNAL;
612 } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
613 log_error("Unexpected token in signal <arg> (1).");
619 case STATE_SIGNAL_ARG_NAME:
621 if (t == XML_ATTRIBUTE_VALUE)
622 state = STATE_SIGNAL_ARG;
624 log_error("Unexpected token in signal <arg> (2).");
630 case STATE_SIGNAL_ARG_TYPE:
632 if (t == XML_ATTRIBUTE_VALUE) {
634 argument_type = name;
637 state = STATE_SIGNAL_ARG;
639 log_error("Unexpected token in signal <arg> (3).");
647 if (t == XML_ATTRIBUTE_NAME) {
648 if (streq_ptr(name, "name"))
649 state = STATE_PROPERTY_NAME;
650 else if (streq_ptr(name, "type"))
651 state = STATE_PROPERTY_TYPE;
652 else if (streq_ptr(name, "access"))
653 state = STATE_PROPERTY_ACCESS;
655 log_error("Unexpected <property> attribute %s.", name);
658 } else if (t == XML_TAG_OPEN) {
660 if (streq_ptr(name, "annotation")) {
661 r = parse_xml_annotation(context, &context->member_flags);
665 log_error("Unexpected <property> tag %s.", name);
669 } else if (t == XML_TAG_CLOSE_EMPTY ||
670 (t == XML_TAG_CLOSE && streq_ptr(name, "property"))) {
673 if (context->ops->on_property) {
674 r = context->ops->on_property(context->interface_name, context->member_name, context->member_signature, context->member_writable, context->member_flags, context->userdata);
679 context_reset_member(context);
682 state = STATE_INTERFACE;
684 } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
685 log_error("Unexpected token in <property>. (1)");
691 case STATE_PROPERTY_NAME:
693 if (t == XML_ATTRIBUTE_VALUE) {
696 free(context->member_name);
697 context->member_name = name;
700 state = STATE_PROPERTY;
702 log_error("Unexpected token in <property>. (2)");
708 case STATE_PROPERTY_TYPE:
710 if (t == XML_ATTRIBUTE_VALUE) {
713 free(context->member_signature);
714 context->member_signature = name;
718 state = STATE_PROPERTY;
720 log_error("Unexpected token in <property>. (3)");
726 case STATE_PROPERTY_ACCESS:
728 if (t == XML_ATTRIBUTE_VALUE) {
730 if (streq(name, "readwrite") || streq(name, "write"))
731 context->member_writable = true;
733 state = STATE_PROPERTY;
735 log_error("Unexpected token in <property>. (4)");
744 int parse_xml_introspect(const char *prefix, const char *xml, const XMLIntrospectOps *ops, void *userdata) {
747 .userdata = userdata,
758 _cleanup_free_ char *name = NULL;
760 r = xml_tokenize(&context.current, &name, &context.xml_state, NULL);
762 log_error("XML parse error");
771 if (r == XML_TAG_OPEN) {
773 if (streq(name, "node")) {
774 r = parse_xml_node(&context, prefix, 0);
778 log_error("Unexpected tag '%s' in introspection data.", name);
782 } else if (r != XML_TEXT || !in_charset(name, WHITESPACE)) {
783 log_error("Unexpected token.");
790 context_reset_interface(&context);