1 /* SPDX-License-Identifier: LGPL-2.1+ */
5 #include "alloc-util.h"
6 #include "busctl-introspect.h"
7 #include "string-util.h"
11 #define NODE_DEPTH_MAX 16
13 typedef struct Context
{
14 const XMLIntrospectOps
*ops
;
18 uint64_t interface_flags
;
21 char *member_signature
;
23 uint64_t member_flags
;
30 static void context_reset_member(Context
*c
) {
32 free(c
->member_signature
);
33 free(c
->member_result
);
35 c
->member_name
= c
->member_signature
= c
->member_result
= NULL
;
37 c
->member_writable
= false;
40 static void context_reset_interface(Context
*c
) {
41 c
->interface_name
= mfree(c
->interface_name
);
42 c
->interface_flags
= 0;
44 context_reset_member(c
);
47 static int parse_xml_annotation(Context
*context
, uint64_t *flags
) {
53 } state
= STATE_ANNOTATION
;
55 _cleanup_free_
char *field
= NULL
, *value
= NULL
;
60 _cleanup_free_
char *name
= NULL
;
64 t
= xml_tokenize(&context
->current
, &name
, &context
->xml_state
, NULL
);
66 log_error("XML parse error.");
71 log_error("Premature end of XML data.");
77 case STATE_ANNOTATION
:
79 if (t
== XML_ATTRIBUTE_NAME
) {
81 if (streq_ptr(name
, "name"))
84 else if (streq_ptr(name
, "value"))
88 log_error("Unexpected <annotation> attribute %s.", name
);
92 } else if (t
== XML_TAG_CLOSE_EMPTY
||
93 (t
== XML_TAG_CLOSE
&& streq_ptr(name
, "annotation"))) {
96 if (streq_ptr(field
, "org.freedesktop.DBus.Deprecated")) {
98 if (streq_ptr(value
, "true"))
99 *flags
|= SD_BUS_VTABLE_DEPRECATED
;
101 } else if (streq_ptr(field
, "org.freedesktop.DBus.Method.NoReply")) {
103 if (streq_ptr(value
, "true"))
104 *flags
|= SD_BUS_VTABLE_METHOD_NO_REPLY
;
106 } else if (streq_ptr(field
, "org.freedesktop.DBus.Property.EmitsChangedSignal")) {
108 if (streq_ptr(value
, "const"))
109 *flags
= (*flags
& ~(SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION
|SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
)) | SD_BUS_VTABLE_PROPERTY_CONST
;
110 else if (streq_ptr(value
, "invalidates"))
111 *flags
= (*flags
& ~(SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
|SD_BUS_VTABLE_PROPERTY_CONST
)) | SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION
;
112 else if (streq_ptr(value
, "false"))
113 *flags
= *flags
& ~(SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
|SD_BUS_VTABLE_PROPERTY_CONST
|SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION
);
119 } else if (t
!= XML_TEXT
|| !in_charset(name
, WHITESPACE
)) {
120 log_error("Unexpected token in <annotation>. (1)");
128 if (t
== XML_ATTRIBUTE_VALUE
) {
129 free_and_replace(field
, name
);
131 state
= STATE_ANNOTATION
;
133 log_error("Unexpected token in <annotation>. (2)");
141 if (t
== XML_ATTRIBUTE_VALUE
) {
142 free_and_replace(value
, name
);
144 state
= STATE_ANNOTATION
;
146 log_error("Unexpected token in <annotation>. (3)");
153 assert_not_reached("Bad state");
158 static int parse_xml_node(Context
*context
, const char *prefix
, unsigned n_depth
) {
164 STATE_INTERFACE_NAME
,
168 STATE_METHOD_ARG_NAME
,
169 STATE_METHOD_ARG_TYPE
,
170 STATE_METHOD_ARG_DIRECTION
,
174 STATE_SIGNAL_ARG_NAME
,
175 STATE_SIGNAL_ARG_TYPE
,
176 STATE_SIGNAL_ARG_DIRECTION
,
180 STATE_PROPERTY_ACCESS
,
181 } state
= STATE_NODE
;
183 _cleanup_free_
char *node_path
= NULL
, *argument_type
= NULL
, *argument_direction
= NULL
;
184 const char *np
= prefix
;
190 if (n_depth
> NODE_DEPTH_MAX
) {
191 log_error("<node> depth too high.");
196 _cleanup_free_
char *name
= NULL
;
199 t
= xml_tokenize(&context
->current
, &name
, &context
->xml_state
, NULL
);
201 log_error("XML parse error.");
206 log_error("Premature end of XML data.");
213 if (t
== XML_ATTRIBUTE_NAME
) {
215 if (streq_ptr(name
, "name"))
216 state
= STATE_NODE_NAME
;
218 log_error("Unexpected <node> attribute %s.", name
);
222 } else if (t
== XML_TAG_OPEN
) {
224 if (streq_ptr(name
, "interface"))
225 state
= STATE_INTERFACE
;
226 else if (streq_ptr(name
, "node")) {
228 r
= parse_xml_node(context
, np
, n_depth
+1);
232 log_error("Unexpected <node> tag %s.", name
);
236 } else if (t
== XML_TAG_CLOSE_EMPTY
||
237 (t
== XML_TAG_CLOSE
&& streq_ptr(name
, "node"))) {
239 if (context
->ops
->on_path
) {
240 r
= context
->ops
->on_path(node_path
? node_path
: np
, context
->userdata
);
247 } else if (t
!= XML_TEXT
|| !in_charset(name
, WHITESPACE
)) {
248 log_error("Unexpected token in <node>. (1)");
254 case STATE_NODE_NAME
:
256 if (t
== XML_ATTRIBUTE_VALUE
) {
261 node_path
= TAKE_PTR(name
);
264 if (endswith(prefix
, "/"))
265 node_path
= strappend(prefix
, name
);
267 node_path
= strjoin(prefix
, "/", name
);
275 log_error("Unexpected token in <node>. (2)");
281 case STATE_INTERFACE
:
283 if (t
== XML_ATTRIBUTE_NAME
) {
284 if (streq_ptr(name
, "name"))
285 state
= STATE_INTERFACE_NAME
;
287 log_error("Unexpected <interface> attribute %s.", name
);
291 } else if (t
== XML_TAG_OPEN
) {
292 if (streq_ptr(name
, "method"))
293 state
= STATE_METHOD
;
294 else if (streq_ptr(name
, "signal"))
295 state
= STATE_SIGNAL
;
296 else if (streq_ptr(name
, "property")) {
297 context
->member_flags
|= SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
;
298 state
= STATE_PROPERTY
;
299 } else if (streq_ptr(name
, "annotation")) {
300 r
= parse_xml_annotation(context
, &context
->interface_flags
);
304 log_error("Unexpected <interface> tag %s.", name
);
307 } else if (t
== XML_TAG_CLOSE_EMPTY
||
308 (t
== XML_TAG_CLOSE
&& streq_ptr(name
, "interface"))) {
311 if (context
->ops
->on_interface
) {
312 r
= context
->ops
->on_interface(context
->interface_name
, context
->interface_flags
, context
->userdata
);
317 context_reset_interface(context
);
322 } else if (t
!= XML_TEXT
|| !in_charset(name
, WHITESPACE
)) {
323 log_error("Unexpected token in <interface>. (1)");
329 case STATE_INTERFACE_NAME
:
331 if (t
== XML_ATTRIBUTE_VALUE
) {
333 free_and_replace(context
->interface_name
, name
);
335 state
= STATE_INTERFACE
;
337 log_error("Unexpected token in <interface>. (2)");
345 if (t
== XML_ATTRIBUTE_NAME
) {
346 if (streq_ptr(name
, "name"))
347 state
= STATE_METHOD_NAME
;
349 log_error("Unexpected <method> attribute %s", name
);
352 } else if (t
== XML_TAG_OPEN
) {
353 if (streq_ptr(name
, "arg"))
354 state
= STATE_METHOD_ARG
;
355 else if (streq_ptr(name
, "annotation")) {
356 r
= parse_xml_annotation(context
, &context
->member_flags
);
360 log_error("Unexpected <method> tag %s.", name
);
363 } else if (t
== XML_TAG_CLOSE_EMPTY
||
364 (t
== XML_TAG_CLOSE
&& streq_ptr(name
, "method"))) {
367 if (context
->ops
->on_method
) {
368 r
= context
->ops
->on_method(context
->interface_name
, context
->member_name
, context
->member_signature
, context
->member_result
, context
->member_flags
, context
->userdata
);
373 context_reset_member(context
);
376 state
= STATE_INTERFACE
;
378 } else if (t
!= XML_TEXT
|| !in_charset(name
, WHITESPACE
)) {
379 log_error("Unexpected token in <method> (1).");
385 case STATE_METHOD_NAME
:
387 if (t
== XML_ATTRIBUTE_VALUE
) {
389 free_and_replace(context
->member_name
, name
);
391 state
= STATE_METHOD
;
393 log_error("Unexpected token in <method> (2).");
399 case STATE_METHOD_ARG
:
401 if (t
== XML_ATTRIBUTE_NAME
) {
402 if (streq_ptr(name
, "name"))
403 state
= STATE_METHOD_ARG_NAME
;
404 else if (streq_ptr(name
, "type"))
405 state
= STATE_METHOD_ARG_TYPE
;
406 else if (streq_ptr(name
, "direction"))
407 state
= STATE_METHOD_ARG_DIRECTION
;
409 log_error("Unexpected method <arg> attribute %s.", name
);
412 } else if (t
== XML_TAG_OPEN
) {
413 if (streq_ptr(name
, "annotation")) {
414 r
= parse_xml_annotation(context
, NULL
);
418 log_error("Unexpected method <arg> tag %s.", name
);
421 } else if (t
== XML_TAG_CLOSE_EMPTY
||
422 (t
== XML_TAG_CLOSE
&& streq_ptr(name
, "arg"))) {
427 if (!argument_direction
|| streq(argument_direction
, "in")) {
428 if (!strextend(&context
->member_signature
, argument_type
, NULL
))
430 } else if (streq(argument_direction
, "out")) {
431 if (!strextend(&context
->member_result
, argument_type
, NULL
))
434 log_error("Unexpected method <arg> direction value '%s'.", argument_direction
);
437 argument_type
= mfree(argument_type
);
438 argument_direction
= mfree(argument_direction
);
441 state
= STATE_METHOD
;
442 } else if (t
!= XML_TEXT
|| !in_charset(name
, WHITESPACE
)) {
443 log_error("Unexpected token in method <arg>. (1)");
449 case STATE_METHOD_ARG_NAME
:
451 if (t
== XML_ATTRIBUTE_VALUE
)
452 state
= STATE_METHOD_ARG
;
454 log_error("Unexpected token in method <arg>. (2)");
460 case STATE_METHOD_ARG_TYPE
:
462 if (t
== XML_ATTRIBUTE_VALUE
) {
463 free_and_replace(argument_type
, name
);
465 state
= STATE_METHOD_ARG
;
467 log_error("Unexpected token in method <arg>. (3)");
473 case STATE_METHOD_ARG_DIRECTION
:
475 if (t
== XML_ATTRIBUTE_VALUE
) {
476 free_and_replace(argument_direction
, name
);
478 state
= STATE_METHOD_ARG
;
480 log_error("Unexpected token in method <arg>. (4)");
488 if (t
== XML_ATTRIBUTE_NAME
) {
489 if (streq_ptr(name
, "name"))
490 state
= STATE_SIGNAL_NAME
;
492 log_error("Unexpected <signal> attribute %s.", name
);
495 } else if (t
== XML_TAG_OPEN
) {
496 if (streq_ptr(name
, "arg"))
497 state
= STATE_SIGNAL_ARG
;
498 else if (streq_ptr(name
, "annotation")) {
499 r
= parse_xml_annotation(context
, &context
->member_flags
);
503 log_error("Unexpected <signal> tag %s.", name
);
506 } else if (t
== XML_TAG_CLOSE_EMPTY
||
507 (t
== XML_TAG_CLOSE
&& streq_ptr(name
, "signal"))) {
510 if (context
->ops
->on_signal
) {
511 r
= context
->ops
->on_signal(context
->interface_name
, context
->member_name
, context
->member_signature
, context
->member_flags
, context
->userdata
);
516 context_reset_member(context
);
519 state
= STATE_INTERFACE
;
521 } else if (t
!= XML_TEXT
|| !in_charset(name
, WHITESPACE
)) {
522 log_error("Unexpected token in <signal>. (1)");
528 case STATE_SIGNAL_NAME
:
530 if (t
== XML_ATTRIBUTE_VALUE
) {
532 free_and_replace(context
->member_name
, name
);
534 state
= STATE_SIGNAL
;
536 log_error("Unexpected token in <signal>. (2)");
542 case STATE_SIGNAL_ARG
:
544 if (t
== XML_ATTRIBUTE_NAME
) {
545 if (streq_ptr(name
, "name"))
546 state
= STATE_SIGNAL_ARG_NAME
;
547 else if (streq_ptr(name
, "type"))
548 state
= STATE_SIGNAL_ARG_TYPE
;
549 else if (streq_ptr(name
, "direction"))
550 state
= STATE_SIGNAL_ARG_DIRECTION
;
552 log_error("Unexpected signal <arg> attribute %s.", name
);
555 } else if (t
== XML_TAG_OPEN
) {
556 if (streq_ptr(name
, "annotation")) {
557 r
= parse_xml_annotation(context
, NULL
);
561 log_error("Unexpected signal <arg> tag %s.", name
);
564 } else if (t
== XML_TAG_CLOSE_EMPTY
||
565 (t
== XML_TAG_CLOSE
&& streq_ptr(name
, "arg"))) {
568 if (!argument_direction
|| streq(argument_direction
, "out")) {
569 if (!strextend(&context
->member_signature
, argument_type
, NULL
))
572 log_error("Unexpected signal <arg> direction value '%s'.", argument_direction
);
574 argument_type
= mfree(argument_type
);
577 state
= STATE_SIGNAL
;
578 } else if (t
!= XML_TEXT
|| !in_charset(name
, WHITESPACE
)) {
579 log_error("Unexpected token in signal <arg> (1).");
585 case STATE_SIGNAL_ARG_NAME
:
587 if (t
== XML_ATTRIBUTE_VALUE
)
588 state
= STATE_SIGNAL_ARG
;
590 log_error("Unexpected token in signal <arg> (2).");
596 case STATE_SIGNAL_ARG_TYPE
:
598 if (t
== XML_ATTRIBUTE_VALUE
) {
599 free_and_replace(argument_type
, name
);
601 state
= STATE_SIGNAL_ARG
;
603 log_error("Unexpected token in signal <arg> (3).");
609 case STATE_SIGNAL_ARG_DIRECTION
:
611 if (t
== XML_ATTRIBUTE_VALUE
) {
612 free_and_replace(argument_direction
, name
);
614 state
= STATE_SIGNAL_ARG
;
616 log_error("Unexpected token in signal <arg>. (4)");
624 if (t
== XML_ATTRIBUTE_NAME
) {
625 if (streq_ptr(name
, "name"))
626 state
= STATE_PROPERTY_NAME
;
627 else if (streq_ptr(name
, "type"))
628 state
= STATE_PROPERTY_TYPE
;
629 else if (streq_ptr(name
, "access"))
630 state
= STATE_PROPERTY_ACCESS
;
632 log_error("Unexpected <property> attribute %s.", name
);
635 } else if (t
== XML_TAG_OPEN
) {
637 if (streq_ptr(name
, "annotation")) {
638 r
= parse_xml_annotation(context
, &context
->member_flags
);
642 log_error("Unexpected <property> tag %s.", name
);
646 } else if (t
== XML_TAG_CLOSE_EMPTY
||
647 (t
== XML_TAG_CLOSE
&& streq_ptr(name
, "property"))) {
650 if (context
->ops
->on_property
) {
651 r
= context
->ops
->on_property(context
->interface_name
, context
->member_name
, context
->member_signature
, context
->member_writable
, context
->member_flags
, context
->userdata
);
656 context_reset_member(context
);
659 state
= STATE_INTERFACE
;
661 } else if (t
!= XML_TEXT
|| !in_charset(name
, WHITESPACE
)) {
662 log_error("Unexpected token in <property>. (1)");
668 case STATE_PROPERTY_NAME
:
670 if (t
== XML_ATTRIBUTE_VALUE
) {
672 free_and_replace(context
->member_name
, name
);
674 state
= STATE_PROPERTY
;
676 log_error("Unexpected token in <property>. (2)");
682 case STATE_PROPERTY_TYPE
:
684 if (t
== XML_ATTRIBUTE_VALUE
) {
686 free_and_replace(context
->member_signature
, name
);
688 state
= STATE_PROPERTY
;
690 log_error("Unexpected token in <property>. (3)");
696 case STATE_PROPERTY_ACCESS
:
698 if (t
== XML_ATTRIBUTE_VALUE
) {
700 if (streq(name
, "readwrite") || streq(name
, "write"))
701 context
->member_writable
= true;
703 state
= STATE_PROPERTY
;
705 log_error("Unexpected token in <property>. (4)");
714 int parse_xml_introspect(const char *prefix
, const char *xml
, const XMLIntrospectOps
*ops
, void *userdata
) {
717 .userdata
= userdata
,
728 _cleanup_free_
char *name
= NULL
;
730 r
= xml_tokenize(&context
.current
, &name
, &context
.xml_state
, NULL
);
732 log_error("XML parse error");
741 if (r
== XML_TAG_OPEN
) {
743 if (streq(name
, "node")) {
744 r
= parse_xml_node(&context
, prefix
, 0);
748 log_error("Unexpected tag '%s' in introspection data.", name
);
752 } else if (r
!= XML_TEXT
|| !in_charset(name
, WHITESPACE
)) {
753 log_error("Unexpected token.");
760 context_reset_interface(&context
);