1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright 2014 Lennart Poettering
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
23 #include "alloc-util.h"
24 #include "busctl-introspect.h"
25 #include "string-util.h"
29 #define NODE_DEPTH_MAX 16
31 typedef struct Context
{
32 const XMLIntrospectOps
*ops
;
36 uint64_t interface_flags
;
39 char *member_signature
;
41 uint64_t member_flags
;
48 static void context_reset_member(Context
*c
) {
50 free(c
->member_signature
);
51 free(c
->member_result
);
53 c
->member_name
= c
->member_signature
= c
->member_result
= NULL
;
55 c
->member_writable
= false;
58 static void context_reset_interface(Context
*c
) {
59 c
->interface_name
= mfree(c
->interface_name
);
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
) {
147 free_and_replace(field
, name
);
149 state
= STATE_ANNOTATION
;
151 log_error("Unexpected token in <annotation>. (2)");
159 if (t
== XML_ATTRIBUTE_VALUE
) {
160 free_and_replace(value
, name
);
162 state
= STATE_ANNOTATION
;
164 log_error("Unexpected token in <annotation>. (3)");
171 assert_not_reached("Bad state");
176 static int parse_xml_node(Context
*context
, const char *prefix
, unsigned n_depth
) {
182 STATE_INTERFACE_NAME
,
186 STATE_METHOD_ARG_NAME
,
187 STATE_METHOD_ARG_TYPE
,
188 STATE_METHOD_ARG_DIRECTION
,
192 STATE_SIGNAL_ARG_NAME
,
193 STATE_SIGNAL_ARG_TYPE
,
194 STATE_SIGNAL_ARG_DIRECTION
,
198 STATE_PROPERTY_ACCESS
,
199 } state
= STATE_NODE
;
201 _cleanup_free_
char *node_path
= NULL
, *argument_type
= NULL
, *argument_direction
= NULL
;
202 const char *np
= prefix
;
208 if (n_depth
> NODE_DEPTH_MAX
) {
209 log_error("<node> depth too high.");
214 _cleanup_free_
char *name
= NULL
;
217 t
= xml_tokenize(&context
->current
, &name
, &context
->xml_state
, NULL
);
219 log_error("XML parse error.");
224 log_error("Premature end of XML data.");
231 if (t
== XML_ATTRIBUTE_NAME
) {
233 if (streq_ptr(name
, "name"))
234 state
= STATE_NODE_NAME
;
236 log_error("Unexpected <node> attribute %s.", name
);
240 } else if (t
== XML_TAG_OPEN
) {
242 if (streq_ptr(name
, "interface"))
243 state
= STATE_INTERFACE
;
244 else if (streq_ptr(name
, "node")) {
246 r
= parse_xml_node(context
, np
, n_depth
+1);
250 log_error("Unexpected <node> tag %s.", name
);
254 } else if (t
== XML_TAG_CLOSE_EMPTY
||
255 (t
== XML_TAG_CLOSE
&& streq_ptr(name
, "node"))) {
257 if (context
->ops
->on_path
) {
258 r
= context
->ops
->on_path(node_path
? node_path
: np
, context
->userdata
);
265 } else if (t
!= XML_TEXT
|| !in_charset(name
, WHITESPACE
)) {
266 log_error("Unexpected token in <node>. (1)");
272 case STATE_NODE_NAME
:
274 if (t
== XML_ATTRIBUTE_VALUE
) {
279 node_path
= TAKE_PTR(name
);
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
);