2 This file is part of systemd.
4 Copyright 2014 Lennart Poettering
6 systemd is free software; you can redistribute it and/or modify it
7 under the terms of the GNU Lesser General Public License as published by
8 the Free Software Foundation; either version 2.1 of the License, or
9 (at your option) any later version.
11 systemd is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
16 You should have received a copy of the GNU Lesser General Public License
17 along with systemd; If not, see <http://www.gnu.org/licenses/>.
22 #include "alloc-util.h"
23 #include "busctl-introspect.h"
24 #include "string-util.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 c
->interface_name
= mfree(c
->interface_name
);
59 c
->interface_flags
= 0;
61 context_reset_member(c
);
64 static int parse_xml_annotation(Context
*context
, uint64_t *flags
) {
70 } state
= STATE_ANNOTATION
;
72 _cleanup_free_
char *field
= NULL
, *value
= NULL
;
77 _cleanup_free_
char *name
= NULL
;
81 t
= xml_tokenize(&context
->current
, &name
, &context
->xml_state
, NULL
);
83 log_error("XML parse error.");
88 log_error("Premature end of XML data.");
94 case STATE_ANNOTATION
:
96 if (t
== XML_ATTRIBUTE_NAME
) {
98 if (streq_ptr(name
, "name"))
101 else if (streq_ptr(name
, "value"))
105 log_error("Unexpected <annotation> attribute %s.", name
);
109 } else if (t
== XML_TAG_CLOSE_EMPTY
||
110 (t
== XML_TAG_CLOSE
&& streq_ptr(name
, "annotation"))) {
113 if (streq_ptr(field
, "org.freedesktop.DBus.Deprecated")) {
115 if (streq_ptr(value
, "true"))
116 *flags
|= SD_BUS_VTABLE_DEPRECATED
;
118 } else if (streq_ptr(field
, "org.freedesktop.DBus.Method.NoReply")) {
120 if (streq_ptr(value
, "true"))
121 *flags
|= SD_BUS_VTABLE_METHOD_NO_REPLY
;
123 } else if (streq_ptr(field
, "org.freedesktop.DBus.Property.EmitsChangedSignal")) {
125 if (streq_ptr(value
, "const"))
126 *flags
= (*flags
& ~(SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION
|SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
)) | SD_BUS_VTABLE_PROPERTY_CONST
;
127 else if (streq_ptr(value
, "invalidates"))
128 *flags
= (*flags
& ~(SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
|SD_BUS_VTABLE_PROPERTY_CONST
)) | SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION
;
129 else if (streq_ptr(value
, "false"))
130 *flags
= *flags
& ~(SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
|SD_BUS_VTABLE_PROPERTY_CONST
|SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION
);
136 } else if (t
!= XML_TEXT
|| !in_charset(name
, WHITESPACE
)) {
137 log_error("Unexpected token in <annotation>. (1)");
145 if (t
== XML_ATTRIBUTE_VALUE
) {
146 free_and_replace(field
, name
);
148 state
= STATE_ANNOTATION
;
150 log_error("Unexpected token in <annotation>. (2)");
158 if (t
== XML_ATTRIBUTE_VALUE
) {
159 free_and_replace(value
, name
);
161 state
= STATE_ANNOTATION
;
163 log_error("Unexpected token in <annotation>. (3)");
170 assert_not_reached("Bad state");
175 static int parse_xml_node(Context
*context
, const char *prefix
, unsigned n_depth
) {
181 STATE_INTERFACE_NAME
,
185 STATE_METHOD_ARG_NAME
,
186 STATE_METHOD_ARG_TYPE
,
187 STATE_METHOD_ARG_DIRECTION
,
191 STATE_SIGNAL_ARG_NAME
,
192 STATE_SIGNAL_ARG_TYPE
,
193 STATE_SIGNAL_ARG_DIRECTION
,
197 STATE_PROPERTY_ACCESS
,
198 } state
= STATE_NODE
;
200 _cleanup_free_
char *node_path
= NULL
, *argument_type
= NULL
, *argument_direction
= NULL
;
201 const char *np
= prefix
;
207 if (n_depth
> NODE_DEPTH_MAX
) {
208 log_error("<node> depth too high.");
213 _cleanup_free_
char *name
= NULL
;
216 t
= xml_tokenize(&context
->current
, &name
, &context
->xml_state
, NULL
);
218 log_error("XML parse error.");
223 log_error("Premature end of XML data.");
230 if (t
== XML_ATTRIBUTE_NAME
) {
232 if (streq_ptr(name
, "name"))
233 state
= STATE_NODE_NAME
;
235 log_error("Unexpected <node> attribute %s.", name
);
239 } else if (t
== XML_TAG_OPEN
) {
241 if (streq_ptr(name
, "interface"))
242 state
= STATE_INTERFACE
;
243 else if (streq_ptr(name
, "node")) {
245 r
= parse_xml_node(context
, np
, n_depth
+1);
249 log_error("Unexpected <node> tag %s.", name
);
253 } else if (t
== XML_TAG_CLOSE_EMPTY
||
254 (t
== XML_TAG_CLOSE
&& streq_ptr(name
, "node"))) {
256 if (context
->ops
->on_path
) {
257 r
= context
->ops
->on_path(node_path
? node_path
: np
, context
->userdata
);
264 } else if (t
!= XML_TEXT
|| !in_charset(name
, WHITESPACE
)) {
265 log_error("Unexpected token in <node>. (1)");
271 case STATE_NODE_NAME
:
273 if (t
== XML_ATTRIBUTE_VALUE
) {
277 if (name
[0] == '/') {
282 if (endswith(prefix
, "/"))
283 node_path
= strappend(prefix
, name
);
285 node_path
= strjoin(prefix
, "/", name
);
293 log_error("Unexpected token in <node>. (2)");
299 case STATE_INTERFACE
:
301 if (t
== XML_ATTRIBUTE_NAME
) {
302 if (streq_ptr(name
, "name"))
303 state
= STATE_INTERFACE_NAME
;
305 log_error("Unexpected <interface> attribute %s.", name
);
309 } else if (t
== XML_TAG_OPEN
) {
310 if (streq_ptr(name
, "method"))
311 state
= STATE_METHOD
;
312 else if (streq_ptr(name
, "signal"))
313 state
= STATE_SIGNAL
;
314 else if (streq_ptr(name
, "property")) {
315 context
->member_flags
|= SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
;
316 state
= STATE_PROPERTY
;
317 } else if (streq_ptr(name
, "annotation")) {
318 r
= parse_xml_annotation(context
, &context
->interface_flags
);
322 log_error("Unexpected <interface> tag %s.", name
);
325 } else if (t
== XML_TAG_CLOSE_EMPTY
||
326 (t
== XML_TAG_CLOSE
&& streq_ptr(name
, "interface"))) {
329 if (context
->ops
->on_interface
) {
330 r
= context
->ops
->on_interface(context
->interface_name
, context
->interface_flags
, context
->userdata
);
335 context_reset_interface(context
);
340 } else if (t
!= XML_TEXT
|| !in_charset(name
, WHITESPACE
)) {
341 log_error("Unexpected token in <interface>. (1)");
347 case STATE_INTERFACE_NAME
:
349 if (t
== XML_ATTRIBUTE_VALUE
) {
351 free_and_replace(context
->interface_name
, name
);
353 state
= STATE_INTERFACE
;
355 log_error("Unexpected token in <interface>. (2)");
363 if (t
== XML_ATTRIBUTE_NAME
) {
364 if (streq_ptr(name
, "name"))
365 state
= STATE_METHOD_NAME
;
367 log_error("Unexpected <method> attribute %s", name
);
370 } else if (t
== XML_TAG_OPEN
) {
371 if (streq_ptr(name
, "arg"))
372 state
= STATE_METHOD_ARG
;
373 else if (streq_ptr(name
, "annotation")) {
374 r
= parse_xml_annotation(context
, &context
->member_flags
);
378 log_error("Unexpected <method> tag %s.", name
);
381 } else if (t
== XML_TAG_CLOSE_EMPTY
||
382 (t
== XML_TAG_CLOSE
&& streq_ptr(name
, "method"))) {
385 if (context
->ops
->on_method
) {
386 r
= context
->ops
->on_method(context
->interface_name
, context
->member_name
, context
->member_signature
, context
->member_result
, context
->member_flags
, context
->userdata
);
391 context_reset_member(context
);
394 state
= STATE_INTERFACE
;
396 } else if (t
!= XML_TEXT
|| !in_charset(name
, WHITESPACE
)) {
397 log_error("Unexpected token in <method> (1).");
403 case STATE_METHOD_NAME
:
405 if (t
== XML_ATTRIBUTE_VALUE
) {
407 free_and_replace(context
->member_name
, name
);
409 state
= STATE_METHOD
;
411 log_error("Unexpected token in <method> (2).");
417 case STATE_METHOD_ARG
:
419 if (t
== XML_ATTRIBUTE_NAME
) {
420 if (streq_ptr(name
, "name"))
421 state
= STATE_METHOD_ARG_NAME
;
422 else if (streq_ptr(name
, "type"))
423 state
= STATE_METHOD_ARG_TYPE
;
424 else if (streq_ptr(name
, "direction"))
425 state
= STATE_METHOD_ARG_DIRECTION
;
427 log_error("Unexpected method <arg> attribute %s.", name
);
430 } else if (t
== XML_TAG_OPEN
) {
431 if (streq_ptr(name
, "annotation")) {
432 r
= parse_xml_annotation(context
, NULL
);
436 log_error("Unexpected method <arg> tag %s.", name
);
439 } else if (t
== XML_TAG_CLOSE_EMPTY
||
440 (t
== XML_TAG_CLOSE
&& streq_ptr(name
, "arg"))) {
445 if (!argument_direction
|| streq(argument_direction
, "in")) {
446 if (!strextend(&context
->member_signature
, argument_type
, NULL
))
448 } else if (streq(argument_direction
, "out")) {
449 if (!strextend(&context
->member_result
, argument_type
, NULL
))
452 log_error("Unexpected method <arg> direction value '%s'.", argument_direction
);
455 argument_type
= mfree(argument_type
);
456 argument_direction
= mfree(argument_direction
);
459 state
= STATE_METHOD
;
460 } else if (t
!= XML_TEXT
|| !in_charset(name
, WHITESPACE
)) {
461 log_error("Unexpected token in method <arg>. (1)");
467 case STATE_METHOD_ARG_NAME
:
469 if (t
== XML_ATTRIBUTE_VALUE
)
470 state
= STATE_METHOD_ARG
;
472 log_error("Unexpected token in method <arg>. (2)");
478 case STATE_METHOD_ARG_TYPE
:
480 if (t
== XML_ATTRIBUTE_VALUE
) {
481 free_and_replace(argument_type
, name
);
483 state
= STATE_METHOD_ARG
;
485 log_error("Unexpected token in method <arg>. (3)");
491 case STATE_METHOD_ARG_DIRECTION
:
493 if (t
== XML_ATTRIBUTE_VALUE
) {
494 free_and_replace(argument_direction
, name
);
496 state
= STATE_METHOD_ARG
;
498 log_error("Unexpected token in method <arg>. (4)");
506 if (t
== XML_ATTRIBUTE_NAME
) {
507 if (streq_ptr(name
, "name"))
508 state
= STATE_SIGNAL_NAME
;
510 log_error("Unexpected <signal> attribute %s.", name
);
513 } else if (t
== XML_TAG_OPEN
) {
514 if (streq_ptr(name
, "arg"))
515 state
= STATE_SIGNAL_ARG
;
516 else if (streq_ptr(name
, "annotation")) {
517 r
= parse_xml_annotation(context
, &context
->member_flags
);
521 log_error("Unexpected <signal> tag %s.", name
);
524 } else if (t
== XML_TAG_CLOSE_EMPTY
||
525 (t
== XML_TAG_CLOSE
&& streq_ptr(name
, "signal"))) {
528 if (context
->ops
->on_signal
) {
529 r
= context
->ops
->on_signal(context
->interface_name
, context
->member_name
, context
->member_signature
, context
->member_flags
, context
->userdata
);
534 context_reset_member(context
);
537 state
= STATE_INTERFACE
;
539 } else if (t
!= XML_TEXT
|| !in_charset(name
, WHITESPACE
)) {
540 log_error("Unexpected token in <signal>. (1)");
546 case STATE_SIGNAL_NAME
:
548 if (t
== XML_ATTRIBUTE_VALUE
) {
550 free_and_replace(context
->member_name
, name
);
552 state
= STATE_SIGNAL
;
554 log_error("Unexpected token in <signal>. (2)");
561 case STATE_SIGNAL_ARG
:
563 if (t
== XML_ATTRIBUTE_NAME
) {
564 if (streq_ptr(name
, "name"))
565 state
= STATE_SIGNAL_ARG_NAME
;
566 else if (streq_ptr(name
, "type"))
567 state
= STATE_SIGNAL_ARG_TYPE
;
568 else if (streq_ptr(name
, "direction"))
569 state
= STATE_SIGNAL_ARG_DIRECTION
;
571 log_error("Unexpected signal <arg> attribute %s.", name
);
574 } else if (t
== XML_TAG_OPEN
) {
575 if (streq_ptr(name
, "annotation")) {
576 r
= parse_xml_annotation(context
, NULL
);
580 log_error("Unexpected signal <arg> tag %s.", name
);
583 } else if (t
== XML_TAG_CLOSE_EMPTY
||
584 (t
== XML_TAG_CLOSE
&& streq_ptr(name
, "arg"))) {
587 if (!argument_direction
|| streq(argument_direction
, "out")) {
588 if (!strextend(&context
->member_signature
, argument_type
, NULL
))
591 log_error("Unexpected signal <arg> direction value '%s'.", argument_direction
);
593 argument_type
= mfree(argument_type
);
596 state
= STATE_SIGNAL
;
597 } else if (t
!= XML_TEXT
|| !in_charset(name
, WHITESPACE
)) {
598 log_error("Unexpected token in signal <arg> (1).");
604 case STATE_SIGNAL_ARG_NAME
:
606 if (t
== XML_ATTRIBUTE_VALUE
)
607 state
= STATE_SIGNAL_ARG
;
609 log_error("Unexpected token in signal <arg> (2).");
615 case STATE_SIGNAL_ARG_TYPE
:
617 if (t
== XML_ATTRIBUTE_VALUE
) {
618 free_and_replace(argument_type
, name
);
620 state
= STATE_SIGNAL_ARG
;
622 log_error("Unexpected token in signal <arg> (3).");
628 case STATE_SIGNAL_ARG_DIRECTION
:
630 if (t
== XML_ATTRIBUTE_VALUE
) {
631 free_and_replace(argument_direction
, name
);
633 state
= STATE_SIGNAL_ARG
;
635 log_error("Unexpected token in signal <arg>. (4)");
643 if (t
== XML_ATTRIBUTE_NAME
) {
644 if (streq_ptr(name
, "name"))
645 state
= STATE_PROPERTY_NAME
;
646 else if (streq_ptr(name
, "type"))
647 state
= STATE_PROPERTY_TYPE
;
648 else if (streq_ptr(name
, "access"))
649 state
= STATE_PROPERTY_ACCESS
;
651 log_error("Unexpected <property> attribute %s.", name
);
654 } else if (t
== XML_TAG_OPEN
) {
656 if (streq_ptr(name
, "annotation")) {
657 r
= parse_xml_annotation(context
, &context
->member_flags
);
661 log_error("Unexpected <property> tag %s.", name
);
665 } else if (t
== XML_TAG_CLOSE_EMPTY
||
666 (t
== XML_TAG_CLOSE
&& streq_ptr(name
, "property"))) {
669 if (context
->ops
->on_property
) {
670 r
= context
->ops
->on_property(context
->interface_name
, context
->member_name
, context
->member_signature
, context
->member_writable
, context
->member_flags
, context
->userdata
);
675 context_reset_member(context
);
678 state
= STATE_INTERFACE
;
680 } else if (t
!= XML_TEXT
|| !in_charset(name
, WHITESPACE
)) {
681 log_error("Unexpected token in <property>. (1)");
687 case STATE_PROPERTY_NAME
:
689 if (t
== XML_ATTRIBUTE_VALUE
) {
691 free_and_replace(context
->member_name
, name
);
693 state
= STATE_PROPERTY
;
695 log_error("Unexpected token in <property>. (2)");
701 case STATE_PROPERTY_TYPE
:
703 if (t
== XML_ATTRIBUTE_VALUE
) {
705 free_and_replace(context
->member_signature
, name
);
707 state
= STATE_PROPERTY
;
709 log_error("Unexpected token in <property>. (3)");
715 case STATE_PROPERTY_ACCESS
:
717 if (t
== XML_ATTRIBUTE_VALUE
) {
719 if (streq(name
, "readwrite") || streq(name
, "write"))
720 context
->member_writable
= true;
722 state
= STATE_PROPERTY
;
724 log_error("Unexpected token in <property>. (4)");
733 int parse_xml_introspect(const char *prefix
, const char *xml
, const XMLIntrospectOps
*ops
, void *userdata
) {
736 .userdata
= userdata
,
747 _cleanup_free_
char *name
= NULL
;
749 r
= xml_tokenize(&context
.current
, &name
, &context
.xml_state
, NULL
);
751 log_error("XML parse error");
760 if (r
== XML_TAG_OPEN
) {
762 if (streq(name
, "node")) {
763 r
= parse_xml_node(&context
, prefix
, 0);
767 log_error("Unexpected tag '%s' in introspection data.", name
);
771 } else if (r
!= XML_TEXT
|| !in_charset(name
, WHITESPACE
)) {
772 log_error("Unexpected token.");
779 context_reset_interface(&context
);