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
) {
278 if (name
[0] == '/') {
283 if (endswith(prefix
, "/"))
284 node_path
= strappend(prefix
, name
);
286 node_path
= strjoin(prefix
, "/", name
);
294 log_error("Unexpected token in <node>. (2)");
300 case STATE_INTERFACE
:
302 if (t
== XML_ATTRIBUTE_NAME
) {
303 if (streq_ptr(name
, "name"))
304 state
= STATE_INTERFACE_NAME
;
306 log_error("Unexpected <interface> attribute %s.", name
);
310 } else if (t
== XML_TAG_OPEN
) {
311 if (streq_ptr(name
, "method"))
312 state
= STATE_METHOD
;
313 else if (streq_ptr(name
, "signal"))
314 state
= STATE_SIGNAL
;
315 else if (streq_ptr(name
, "property")) {
316 context
->member_flags
|= SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE
;
317 state
= STATE_PROPERTY
;
318 } else if (streq_ptr(name
, "annotation")) {
319 r
= parse_xml_annotation(context
, &context
->interface_flags
);
323 log_error("Unexpected <interface> tag %s.", name
);
326 } else if (t
== XML_TAG_CLOSE_EMPTY
||
327 (t
== XML_TAG_CLOSE
&& streq_ptr(name
, "interface"))) {
330 if (context
->ops
->on_interface
) {
331 r
= context
->ops
->on_interface(context
->interface_name
, context
->interface_flags
, context
->userdata
);
336 context_reset_interface(context
);
341 } else if (t
!= XML_TEXT
|| !in_charset(name
, WHITESPACE
)) {
342 log_error("Unexpected token in <interface>. (1)");
348 case STATE_INTERFACE_NAME
:
350 if (t
== XML_ATTRIBUTE_VALUE
) {
352 free_and_replace(context
->interface_name
, name
);
354 state
= STATE_INTERFACE
;
356 log_error("Unexpected token in <interface>. (2)");
364 if (t
== XML_ATTRIBUTE_NAME
) {
365 if (streq_ptr(name
, "name"))
366 state
= STATE_METHOD_NAME
;
368 log_error("Unexpected <method> attribute %s", name
);
371 } else if (t
== XML_TAG_OPEN
) {
372 if (streq_ptr(name
, "arg"))
373 state
= STATE_METHOD_ARG
;
374 else if (streq_ptr(name
, "annotation")) {
375 r
= parse_xml_annotation(context
, &context
->member_flags
);
379 log_error("Unexpected <method> tag %s.", name
);
382 } else if (t
== XML_TAG_CLOSE_EMPTY
||
383 (t
== XML_TAG_CLOSE
&& streq_ptr(name
, "method"))) {
386 if (context
->ops
->on_method
) {
387 r
= context
->ops
->on_method(context
->interface_name
, context
->member_name
, context
->member_signature
, context
->member_result
, context
->member_flags
, context
->userdata
);
392 context_reset_member(context
);
395 state
= STATE_INTERFACE
;
397 } else if (t
!= XML_TEXT
|| !in_charset(name
, WHITESPACE
)) {
398 log_error("Unexpected token in <method> (1).");
404 case STATE_METHOD_NAME
:
406 if (t
== XML_ATTRIBUTE_VALUE
) {
408 free_and_replace(context
->member_name
, name
);
410 state
= STATE_METHOD
;
412 log_error("Unexpected token in <method> (2).");
418 case STATE_METHOD_ARG
:
420 if (t
== XML_ATTRIBUTE_NAME
) {
421 if (streq_ptr(name
, "name"))
422 state
= STATE_METHOD_ARG_NAME
;
423 else if (streq_ptr(name
, "type"))
424 state
= STATE_METHOD_ARG_TYPE
;
425 else if (streq_ptr(name
, "direction"))
426 state
= STATE_METHOD_ARG_DIRECTION
;
428 log_error("Unexpected method <arg> attribute %s.", name
);
431 } else if (t
== XML_TAG_OPEN
) {
432 if (streq_ptr(name
, "annotation")) {
433 r
= parse_xml_annotation(context
, NULL
);
437 log_error("Unexpected method <arg> tag %s.", name
);
440 } else if (t
== XML_TAG_CLOSE_EMPTY
||
441 (t
== XML_TAG_CLOSE
&& streq_ptr(name
, "arg"))) {
446 if (!argument_direction
|| streq(argument_direction
, "in")) {
447 if (!strextend(&context
->member_signature
, argument_type
, NULL
))
449 } else if (streq(argument_direction
, "out")) {
450 if (!strextend(&context
->member_result
, argument_type
, NULL
))
453 log_error("Unexpected method <arg> direction value '%s'.", argument_direction
);
456 argument_type
= mfree(argument_type
);
457 argument_direction
= mfree(argument_direction
);
460 state
= STATE_METHOD
;
461 } else if (t
!= XML_TEXT
|| !in_charset(name
, WHITESPACE
)) {
462 log_error("Unexpected token in method <arg>. (1)");
468 case STATE_METHOD_ARG_NAME
:
470 if (t
== XML_ATTRIBUTE_VALUE
)
471 state
= STATE_METHOD_ARG
;
473 log_error("Unexpected token in method <arg>. (2)");
479 case STATE_METHOD_ARG_TYPE
:
481 if (t
== XML_ATTRIBUTE_VALUE
) {
482 free_and_replace(argument_type
, name
);
484 state
= STATE_METHOD_ARG
;
486 log_error("Unexpected token in method <arg>. (3)");
492 case STATE_METHOD_ARG_DIRECTION
:
494 if (t
== XML_ATTRIBUTE_VALUE
) {
495 free_and_replace(argument_direction
, name
);
497 state
= STATE_METHOD_ARG
;
499 log_error("Unexpected token in method <arg>. (4)");
507 if (t
== XML_ATTRIBUTE_NAME
) {
508 if (streq_ptr(name
, "name"))
509 state
= STATE_SIGNAL_NAME
;
511 log_error("Unexpected <signal> attribute %s.", name
);
514 } else if (t
== XML_TAG_OPEN
) {
515 if (streq_ptr(name
, "arg"))
516 state
= STATE_SIGNAL_ARG
;
517 else if (streq_ptr(name
, "annotation")) {
518 r
= parse_xml_annotation(context
, &context
->member_flags
);
522 log_error("Unexpected <signal> tag %s.", name
);
525 } else if (t
== XML_TAG_CLOSE_EMPTY
||
526 (t
== XML_TAG_CLOSE
&& streq_ptr(name
, "signal"))) {
529 if (context
->ops
->on_signal
) {
530 r
= context
->ops
->on_signal(context
->interface_name
, context
->member_name
, context
->member_signature
, context
->member_flags
, context
->userdata
);
535 context_reset_member(context
);
538 state
= STATE_INTERFACE
;
540 } else if (t
!= XML_TEXT
|| !in_charset(name
, WHITESPACE
)) {
541 log_error("Unexpected token in <signal>. (1)");
547 case STATE_SIGNAL_NAME
:
549 if (t
== XML_ATTRIBUTE_VALUE
) {
551 free_and_replace(context
->member_name
, name
);
553 state
= STATE_SIGNAL
;
555 log_error("Unexpected token in <signal>. (2)");
562 case STATE_SIGNAL_ARG
:
564 if (t
== XML_ATTRIBUTE_NAME
) {
565 if (streq_ptr(name
, "name"))
566 state
= STATE_SIGNAL_ARG_NAME
;
567 else if (streq_ptr(name
, "type"))
568 state
= STATE_SIGNAL_ARG_TYPE
;
569 else if (streq_ptr(name
, "direction"))
570 state
= STATE_SIGNAL_ARG_DIRECTION
;
572 log_error("Unexpected signal <arg> attribute %s.", name
);
575 } else if (t
== XML_TAG_OPEN
) {
576 if (streq_ptr(name
, "annotation")) {
577 r
= parse_xml_annotation(context
, NULL
);
581 log_error("Unexpected signal <arg> tag %s.", name
);
584 } else if (t
== XML_TAG_CLOSE_EMPTY
||
585 (t
== XML_TAG_CLOSE
&& streq_ptr(name
, "arg"))) {
588 if (!argument_direction
|| streq(argument_direction
, "out")) {
589 if (!strextend(&context
->member_signature
, argument_type
, NULL
))
592 log_error("Unexpected signal <arg> direction value '%s'.", argument_direction
);
594 argument_type
= mfree(argument_type
);
597 state
= STATE_SIGNAL
;
598 } else if (t
!= XML_TEXT
|| !in_charset(name
, WHITESPACE
)) {
599 log_error("Unexpected token in signal <arg> (1).");
605 case STATE_SIGNAL_ARG_NAME
:
607 if (t
== XML_ATTRIBUTE_VALUE
)
608 state
= STATE_SIGNAL_ARG
;
610 log_error("Unexpected token in signal <arg> (2).");
616 case STATE_SIGNAL_ARG_TYPE
:
618 if (t
== XML_ATTRIBUTE_VALUE
) {
619 free_and_replace(argument_type
, name
);
621 state
= STATE_SIGNAL_ARG
;
623 log_error("Unexpected token in signal <arg> (3).");
629 case STATE_SIGNAL_ARG_DIRECTION
:
631 if (t
== XML_ATTRIBUTE_VALUE
) {
632 free_and_replace(argument_direction
, name
);
634 state
= STATE_SIGNAL_ARG
;
636 log_error("Unexpected token in signal <arg>. (4)");
644 if (t
== XML_ATTRIBUTE_NAME
) {
645 if (streq_ptr(name
, "name"))
646 state
= STATE_PROPERTY_NAME
;
647 else if (streq_ptr(name
, "type"))
648 state
= STATE_PROPERTY_TYPE
;
649 else if (streq_ptr(name
, "access"))
650 state
= STATE_PROPERTY_ACCESS
;
652 log_error("Unexpected <property> attribute %s.", name
);
655 } else if (t
== XML_TAG_OPEN
) {
657 if (streq_ptr(name
, "annotation")) {
658 r
= parse_xml_annotation(context
, &context
->member_flags
);
662 log_error("Unexpected <property> tag %s.", name
);
666 } else if (t
== XML_TAG_CLOSE_EMPTY
||
667 (t
== XML_TAG_CLOSE
&& streq_ptr(name
, "property"))) {
670 if (context
->ops
->on_property
) {
671 r
= context
->ops
->on_property(context
->interface_name
, context
->member_name
, context
->member_signature
, context
->member_writable
, context
->member_flags
, context
->userdata
);
676 context_reset_member(context
);
679 state
= STATE_INTERFACE
;
681 } else if (t
!= XML_TEXT
|| !in_charset(name
, WHITESPACE
)) {
682 log_error("Unexpected token in <property>. (1)");
688 case STATE_PROPERTY_NAME
:
690 if (t
== XML_ATTRIBUTE_VALUE
) {
692 free_and_replace(context
->member_name
, name
);
694 state
= STATE_PROPERTY
;
696 log_error("Unexpected token in <property>. (2)");
702 case STATE_PROPERTY_TYPE
:
704 if (t
== XML_ATTRIBUTE_VALUE
) {
706 free_and_replace(context
->member_signature
, name
);
708 state
= STATE_PROPERTY
;
710 log_error("Unexpected token in <property>. (3)");
716 case STATE_PROPERTY_ACCESS
:
718 if (t
== XML_ATTRIBUTE_VALUE
) {
720 if (streq(name
, "readwrite") || streq(name
, "write"))
721 context
->member_writable
= true;
723 state
= STATE_PROPERTY
;
725 log_error("Unexpected token in <property>. (4)");
734 int parse_xml_introspect(const char *prefix
, const char *xml
, const XMLIntrospectOps
*ops
, void *userdata
) {
737 .userdata
= userdata
,
748 _cleanup_free_
char *name
= NULL
;
750 r
= xml_tokenize(&context
.current
, &name
, &context
.xml_state
, NULL
);
752 log_error("XML parse error");
761 if (r
== XML_TAG_OPEN
) {
763 if (streq(name
, "node")) {
764 r
= parse_xml_node(&context
, prefix
, 0);
768 log_error("Unexpected tag '%s' in introspection data.", name
);
772 } else if (r
!= XML_TEXT
|| !in_charset(name
, WHITESPACE
)) {
773 log_error("Unexpected token.");
780 context_reset_interface(&context
);