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 "string-util.h"
27 #include "busctl-introspect.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
) {
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
))
465 argument_type
= mfree(argument_type
);
466 argument_direction
= mfree(argument_direction
);
469 state
= STATE_METHOD
;
470 } else if (t
!= XML_TEXT
|| !in_charset(name
, WHITESPACE
)) {
471 log_error("Unexpected token in method <arg>. (1)");
477 case STATE_METHOD_ARG_NAME
:
479 if (t
== XML_ATTRIBUTE_VALUE
)
480 state
= STATE_METHOD_ARG
;
482 log_error("Unexpected token in method <arg>. (2)");
488 case STATE_METHOD_ARG_TYPE
:
490 if (t
== XML_ATTRIBUTE_VALUE
) {
492 argument_type
= name
;
495 state
= STATE_METHOD_ARG
;
497 log_error("Unexpected token in method <arg>. (3)");
503 case STATE_METHOD_ARG_DIRECTION
:
505 if (t
== XML_ATTRIBUTE_VALUE
) {
506 free(argument_direction
);
507 argument_direction
= name
;
510 state
= STATE_METHOD_ARG
;
512 log_error("Unexpected token in method <arg>. (4)");
520 if (t
== XML_ATTRIBUTE_NAME
) {
521 if (streq_ptr(name
, "name"))
522 state
= STATE_SIGNAL_NAME
;
524 log_error("Unexpected <signal> attribute %s.", name
);
527 } else if (t
== XML_TAG_OPEN
) {
528 if (streq_ptr(name
, "arg"))
529 state
= STATE_SIGNAL_ARG
;
530 else if (streq_ptr(name
, "annotation")) {
531 r
= parse_xml_annotation(context
, &context
->member_flags
);
535 log_error("Unexpected <signal> tag %s.", name
);
538 } else if (t
== XML_TAG_CLOSE_EMPTY
||
539 (t
== XML_TAG_CLOSE
&& streq_ptr(name
, "signal"))) {
542 if (context
->ops
->on_signal
) {
543 r
= context
->ops
->on_signal(context
->interface_name
, context
->member_name
, context
->member_signature
, context
->member_flags
, context
->userdata
);
548 context_reset_member(context
);
551 state
= STATE_INTERFACE
;
553 } else if (t
!= XML_TEXT
|| !in_charset(name
, WHITESPACE
)) {
554 log_error("Unexpected token in <signal>. (1)");
560 case STATE_SIGNAL_NAME
:
562 if (t
== XML_ATTRIBUTE_VALUE
) {
565 free(context
->member_name
);
566 context
->member_name
= name
;
570 state
= STATE_SIGNAL
;
572 log_error("Unexpected token in <signal>. (2)");
579 case STATE_SIGNAL_ARG
:
581 if (t
== XML_ATTRIBUTE_NAME
) {
582 if (streq_ptr(name
, "name"))
583 state
= STATE_SIGNAL_ARG_NAME
;
584 else if (streq_ptr(name
, "type"))
585 state
= STATE_SIGNAL_ARG_TYPE
;
587 log_error("Unexpected signal <arg> attribute %s.", name
);
590 } else if (t
== XML_TAG_OPEN
) {
591 if (streq_ptr(name
, "annotation")) {
592 r
= parse_xml_annotation(context
, NULL
);
596 log_error("Unexpected signal <arg> tag %s.", name
);
599 } else if (t
== XML_TAG_CLOSE_EMPTY
||
600 (t
== XML_TAG_CLOSE
&& streq_ptr(name
, "arg"))) {
603 if (!strextend(&context
->member_signature
, argument_type
, NULL
))
606 argument_type
= mfree(argument_type
);
609 state
= STATE_SIGNAL
;
610 } else if (t
!= XML_TEXT
|| !in_charset(name
, WHITESPACE
)) {
611 log_error("Unexpected token in signal <arg> (1).");
617 case STATE_SIGNAL_ARG_NAME
:
619 if (t
== XML_ATTRIBUTE_VALUE
)
620 state
= STATE_SIGNAL_ARG
;
622 log_error("Unexpected token in signal <arg> (2).");
628 case STATE_SIGNAL_ARG_TYPE
:
630 if (t
== XML_ATTRIBUTE_VALUE
) {
632 argument_type
= name
;
635 state
= STATE_SIGNAL_ARG
;
637 log_error("Unexpected token in signal <arg> (3).");
645 if (t
== XML_ATTRIBUTE_NAME
) {
646 if (streq_ptr(name
, "name"))
647 state
= STATE_PROPERTY_NAME
;
648 else if (streq_ptr(name
, "type"))
649 state
= STATE_PROPERTY_TYPE
;
650 else if (streq_ptr(name
, "access"))
651 state
= STATE_PROPERTY_ACCESS
;
653 log_error("Unexpected <property> attribute %s.", name
);
656 } else if (t
== XML_TAG_OPEN
) {
658 if (streq_ptr(name
, "annotation")) {
659 r
= parse_xml_annotation(context
, &context
->member_flags
);
663 log_error("Unexpected <property> tag %s.", name
);
667 } else if (t
== XML_TAG_CLOSE_EMPTY
||
668 (t
== XML_TAG_CLOSE
&& streq_ptr(name
, "property"))) {
671 if (context
->ops
->on_property
) {
672 r
= context
->ops
->on_property(context
->interface_name
, context
->member_name
, context
->member_signature
, context
->member_writable
, context
->member_flags
, context
->userdata
);
677 context_reset_member(context
);
680 state
= STATE_INTERFACE
;
682 } else if (t
!= XML_TEXT
|| !in_charset(name
, WHITESPACE
)) {
683 log_error("Unexpected token in <property>. (1)");
689 case STATE_PROPERTY_NAME
:
691 if (t
== XML_ATTRIBUTE_VALUE
) {
694 free(context
->member_name
);
695 context
->member_name
= name
;
698 state
= STATE_PROPERTY
;
700 log_error("Unexpected token in <property>. (2)");
706 case STATE_PROPERTY_TYPE
:
708 if (t
== XML_ATTRIBUTE_VALUE
) {
711 free(context
->member_signature
);
712 context
->member_signature
= name
;
716 state
= STATE_PROPERTY
;
718 log_error("Unexpected token in <property>. (3)");
724 case STATE_PROPERTY_ACCESS
:
726 if (t
== XML_ATTRIBUTE_VALUE
) {
728 if (streq(name
, "readwrite") || streq(name
, "write"))
729 context
->member_writable
= true;
731 state
= STATE_PROPERTY
;
733 log_error("Unexpected token in <property>. (4)");
742 int parse_xml_introspect(const char *prefix
, const char *xml
, const XMLIntrospectOps
*ops
, void *userdata
) {
745 .userdata
= userdata
,
756 _cleanup_free_
char *name
= NULL
;
758 r
= xml_tokenize(&context
.current
, &name
, &context
.xml_state
, NULL
);
760 log_error("XML parse error");
769 if (r
== XML_TAG_OPEN
) {
771 if (streq(name
, "node")) {
772 r
= parse_xml_node(&context
, prefix
, 0);
776 log_error("Unexpected tag '%s' in introspection data.", name
);
780 } else if (r
!= XML_TEXT
|| !in_charset(name
, WHITESPACE
)) {
781 log_error("Unexpected token.");
788 context_reset_interface(&context
);