]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/busctl/busctl-introspect.c
Add SPDX license identifiers to source files under the LGPL
[thirdparty/systemd.git] / src / busctl / busctl-introspect.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3 This file is part of systemd.
4
5 Copyright 2014 Lennart Poettering
6
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.
11
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.
16
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/>.
19 ***/
20
21 #include "sd-bus.h"
22
23 #include "alloc-util.h"
24 #include "busctl-introspect.h"
25 #include "string-util.h"
26 #include "util.h"
27 #include "xml.h"
28
29 #define NODE_DEPTH_MAX 16
30
31 typedef struct Context {
32 const XMLIntrospectOps *ops;
33 void *userdata;
34
35 char *interface_name;
36 uint64_t interface_flags;
37
38 char *member_name;
39 char *member_signature;
40 char *member_result;
41 uint64_t member_flags;
42 bool member_writable;
43
44 const char *current;
45 void *xml_state;
46 } Context;
47
48 static void context_reset_member(Context *c) {
49 free(c->member_name);
50 free(c->member_signature);
51 free(c->member_result);
52
53 c->member_name = c->member_signature = c->member_result = NULL;
54 c->member_flags = 0;
55 c->member_writable = false;
56 }
57
58 static void context_reset_interface(Context *c) {
59 c->interface_name = mfree(c->interface_name);
60 c->interface_flags = 0;
61
62 context_reset_member(c);
63 }
64
65 static int parse_xml_annotation(Context *context, uint64_t *flags) {
66
67 enum {
68 STATE_ANNOTATION,
69 STATE_NAME,
70 STATE_VALUE
71 } state = STATE_ANNOTATION;
72
73 _cleanup_free_ char *field = NULL, *value = NULL;
74
75 assert(context);
76
77 for (;;) {
78 _cleanup_free_ char *name = NULL;
79
80 int t;
81
82 t = xml_tokenize(&context->current, &name, &context->xml_state, NULL);
83 if (t < 0) {
84 log_error("XML parse error.");
85 return t;
86 }
87
88 if (t == XML_END) {
89 log_error("Premature end of XML data.");
90 return -EBADMSG;
91 }
92
93 switch (state) {
94
95 case STATE_ANNOTATION:
96
97 if (t == XML_ATTRIBUTE_NAME) {
98
99 if (streq_ptr(name, "name"))
100 state = STATE_NAME;
101
102 else if (streq_ptr(name, "value"))
103 state = STATE_VALUE;
104
105 else {
106 log_error("Unexpected <annotation> attribute %s.", name);
107 return -EBADMSG;
108 }
109
110 } else if (t == XML_TAG_CLOSE_EMPTY ||
111 (t == XML_TAG_CLOSE && streq_ptr(name, "annotation"))) {
112
113 if (flags) {
114 if (streq_ptr(field, "org.freedesktop.DBus.Deprecated")) {
115
116 if (streq_ptr(value, "true"))
117 *flags |= SD_BUS_VTABLE_DEPRECATED;
118
119 } else if (streq_ptr(field, "org.freedesktop.DBus.Method.NoReply")) {
120
121 if (streq_ptr(value, "true"))
122 *flags |= SD_BUS_VTABLE_METHOD_NO_REPLY;
123
124 } else if (streq_ptr(field, "org.freedesktop.DBus.Property.EmitsChangedSignal")) {
125
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);
132 }
133 }
134
135 return 0;
136
137 } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
138 log_error("Unexpected token in <annotation>. (1)");
139 return -EINVAL;
140 }
141
142 break;
143
144 case STATE_NAME:
145
146 if (t == XML_ATTRIBUTE_VALUE) {
147 free_and_replace(field, name);
148
149 state = STATE_ANNOTATION;
150 } else {
151 log_error("Unexpected token in <annotation>. (2)");
152 return -EINVAL;
153 }
154
155 break;
156
157 case STATE_VALUE:
158
159 if (t == XML_ATTRIBUTE_VALUE) {
160 free_and_replace(value, name);
161
162 state = STATE_ANNOTATION;
163 } else {
164 log_error("Unexpected token in <annotation>. (3)");
165 return -EINVAL;
166 }
167
168 break;
169
170 default:
171 assert_not_reached("Bad state");
172 }
173 }
174 }
175
176 static int parse_xml_node(Context *context, const char *prefix, unsigned n_depth) {
177
178 enum {
179 STATE_NODE,
180 STATE_NODE_NAME,
181 STATE_INTERFACE,
182 STATE_INTERFACE_NAME,
183 STATE_METHOD,
184 STATE_METHOD_NAME,
185 STATE_METHOD_ARG,
186 STATE_METHOD_ARG_NAME,
187 STATE_METHOD_ARG_TYPE,
188 STATE_METHOD_ARG_DIRECTION,
189 STATE_SIGNAL,
190 STATE_SIGNAL_NAME,
191 STATE_SIGNAL_ARG,
192 STATE_SIGNAL_ARG_NAME,
193 STATE_SIGNAL_ARG_TYPE,
194 STATE_SIGNAL_ARG_DIRECTION,
195 STATE_PROPERTY,
196 STATE_PROPERTY_NAME,
197 STATE_PROPERTY_TYPE,
198 STATE_PROPERTY_ACCESS,
199 } state = STATE_NODE;
200
201 _cleanup_free_ char *node_path = NULL, *argument_type = NULL, *argument_direction = NULL;
202 const char *np = prefix;
203 int r;
204
205 assert(context);
206 assert(prefix);
207
208 if (n_depth > NODE_DEPTH_MAX) {
209 log_error("<node> depth too high.");
210 return -EINVAL;
211 }
212
213 for (;;) {
214 _cleanup_free_ char *name = NULL;
215 int t;
216
217 t = xml_tokenize(&context->current, &name, &context->xml_state, NULL);
218 if (t < 0) {
219 log_error("XML parse error.");
220 return t;
221 }
222
223 if (t == XML_END) {
224 log_error("Premature end of XML data.");
225 return -EBADMSG;
226 }
227
228 switch (state) {
229
230 case STATE_NODE:
231 if (t == XML_ATTRIBUTE_NAME) {
232
233 if (streq_ptr(name, "name"))
234 state = STATE_NODE_NAME;
235 else {
236 log_error("Unexpected <node> attribute %s.", name);
237 return -EBADMSG;
238 }
239
240 } else if (t == XML_TAG_OPEN) {
241
242 if (streq_ptr(name, "interface"))
243 state = STATE_INTERFACE;
244 else if (streq_ptr(name, "node")) {
245
246 r = parse_xml_node(context, np, n_depth+1);
247 if (r < 0)
248 return r;
249 } else {
250 log_error("Unexpected <node> tag %s.", name);
251 return -EBADMSG;
252 }
253
254 } else if (t == XML_TAG_CLOSE_EMPTY ||
255 (t == XML_TAG_CLOSE && streq_ptr(name, "node"))) {
256
257 if (context->ops->on_path) {
258 r = context->ops->on_path(node_path ? node_path : np, context->userdata);
259 if (r < 0)
260 return r;
261 }
262
263 return 0;
264
265 } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
266 log_error("Unexpected token in <node>. (1)");
267 return -EINVAL;
268 }
269
270 break;
271
272 case STATE_NODE_NAME:
273
274 if (t == XML_ATTRIBUTE_VALUE) {
275
276 free(node_path);
277
278 if (name[0] == '/') {
279 node_path = name;
280 name = NULL;
281 } else {
282
283 if (endswith(prefix, "/"))
284 node_path = strappend(prefix, name);
285 else
286 node_path = strjoin(prefix, "/", name);
287 if (!node_path)
288 return log_oom();
289 }
290
291 np = node_path;
292 state = STATE_NODE;
293 } else {
294 log_error("Unexpected token in <node>. (2)");
295 return -EINVAL;
296 }
297
298 break;
299
300 case STATE_INTERFACE:
301
302 if (t == XML_ATTRIBUTE_NAME) {
303 if (streq_ptr(name, "name"))
304 state = STATE_INTERFACE_NAME;
305 else {
306 log_error("Unexpected <interface> attribute %s.", name);
307 return -EBADMSG;
308 }
309
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);
320 if (r < 0)
321 return r;
322 } else {
323 log_error("Unexpected <interface> tag %s.", name);
324 return -EINVAL;
325 }
326 } else if (t == XML_TAG_CLOSE_EMPTY ||
327 (t == XML_TAG_CLOSE && streq_ptr(name, "interface"))) {
328
329 if (n_depth == 0) {
330 if (context->ops->on_interface) {
331 r = context->ops->on_interface(context->interface_name, context->interface_flags, context->userdata);
332 if (r < 0)
333 return r;
334 }
335
336 context_reset_interface(context);
337 }
338
339 state = STATE_NODE;
340
341 } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
342 log_error("Unexpected token in <interface>. (1)");
343 return -EINVAL;
344 }
345
346 break;
347
348 case STATE_INTERFACE_NAME:
349
350 if (t == XML_ATTRIBUTE_VALUE) {
351 if (n_depth == 0)
352 free_and_replace(context->interface_name, name);
353
354 state = STATE_INTERFACE;
355 } else {
356 log_error("Unexpected token in <interface>. (2)");
357 return -EINVAL;
358 }
359
360 break;
361
362 case STATE_METHOD:
363
364 if (t == XML_ATTRIBUTE_NAME) {
365 if (streq_ptr(name, "name"))
366 state = STATE_METHOD_NAME;
367 else {
368 log_error("Unexpected <method> attribute %s", name);
369 return -EBADMSG;
370 }
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);
376 if (r < 0)
377 return r;
378 } else {
379 log_error("Unexpected <method> tag %s.", name);
380 return -EINVAL;
381 }
382 } else if (t == XML_TAG_CLOSE_EMPTY ||
383 (t == XML_TAG_CLOSE && streq_ptr(name, "method"))) {
384
385 if (n_depth == 0) {
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);
388 if (r < 0)
389 return r;
390 }
391
392 context_reset_member(context);
393 }
394
395 state = STATE_INTERFACE;
396
397 } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
398 log_error("Unexpected token in <method> (1).");
399 return -EINVAL;
400 }
401
402 break;
403
404 case STATE_METHOD_NAME:
405
406 if (t == XML_ATTRIBUTE_VALUE) {
407 if (n_depth == 0)
408 free_and_replace(context->member_name, name);
409
410 state = STATE_METHOD;
411 } else {
412 log_error("Unexpected token in <method> (2).");
413 return -EINVAL;
414 }
415
416 break;
417
418 case STATE_METHOD_ARG:
419
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;
427 else {
428 log_error("Unexpected method <arg> attribute %s.", name);
429 return -EBADMSG;
430 }
431 } else if (t == XML_TAG_OPEN) {
432 if (streq_ptr(name, "annotation")) {
433 r = parse_xml_annotation(context, NULL);
434 if (r < 0)
435 return r;
436 } else {
437 log_error("Unexpected method <arg> tag %s.", name);
438 return -EINVAL;
439 }
440 } else if (t == XML_TAG_CLOSE_EMPTY ||
441 (t == XML_TAG_CLOSE && streq_ptr(name, "arg"))) {
442
443 if (n_depth == 0) {
444
445 if (argument_type) {
446 if (!argument_direction || streq(argument_direction, "in")) {
447 if (!strextend(&context->member_signature, argument_type, NULL))
448 return log_oom();
449 } else if (streq(argument_direction, "out")) {
450 if (!strextend(&context->member_result, argument_type, NULL))
451 return log_oom();
452 } else
453 log_error("Unexpected method <arg> direction value '%s'.", argument_direction);
454 }
455
456 argument_type = mfree(argument_type);
457 argument_direction = mfree(argument_direction);
458 }
459
460 state = STATE_METHOD;
461 } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
462 log_error("Unexpected token in method <arg>. (1)");
463 return -EINVAL;
464 }
465
466 break;
467
468 case STATE_METHOD_ARG_NAME:
469
470 if (t == XML_ATTRIBUTE_VALUE)
471 state = STATE_METHOD_ARG;
472 else {
473 log_error("Unexpected token in method <arg>. (2)");
474 return -EINVAL;
475 }
476
477 break;
478
479 case STATE_METHOD_ARG_TYPE:
480
481 if (t == XML_ATTRIBUTE_VALUE) {
482 free_and_replace(argument_type, name);
483
484 state = STATE_METHOD_ARG;
485 } else {
486 log_error("Unexpected token in method <arg>. (3)");
487 return -EINVAL;
488 }
489
490 break;
491
492 case STATE_METHOD_ARG_DIRECTION:
493
494 if (t == XML_ATTRIBUTE_VALUE) {
495 free_and_replace(argument_direction, name);
496
497 state = STATE_METHOD_ARG;
498 } else {
499 log_error("Unexpected token in method <arg>. (4)");
500 return -EINVAL;
501 }
502
503 break;
504
505 case STATE_SIGNAL:
506
507 if (t == XML_ATTRIBUTE_NAME) {
508 if (streq_ptr(name, "name"))
509 state = STATE_SIGNAL_NAME;
510 else {
511 log_error("Unexpected <signal> attribute %s.", name);
512 return -EBADMSG;
513 }
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);
519 if (r < 0)
520 return r;
521 } else {
522 log_error("Unexpected <signal> tag %s.", name);
523 return -EINVAL;
524 }
525 } else if (t == XML_TAG_CLOSE_EMPTY ||
526 (t == XML_TAG_CLOSE && streq_ptr(name, "signal"))) {
527
528 if (n_depth == 0) {
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);
531 if (r < 0)
532 return r;
533 }
534
535 context_reset_member(context);
536 }
537
538 state = STATE_INTERFACE;
539
540 } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
541 log_error("Unexpected token in <signal>. (1)");
542 return -EINVAL;
543 }
544
545 break;
546
547 case STATE_SIGNAL_NAME:
548
549 if (t == XML_ATTRIBUTE_VALUE) {
550 if (n_depth == 0)
551 free_and_replace(context->member_name, name);
552
553 state = STATE_SIGNAL;
554 } else {
555 log_error("Unexpected token in <signal>. (2)");
556 return -EINVAL;
557 }
558
559 break;
560
561
562 case STATE_SIGNAL_ARG:
563
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;
571 else {
572 log_error("Unexpected signal <arg> attribute %s.", name);
573 return -EBADMSG;
574 }
575 } else if (t == XML_TAG_OPEN) {
576 if (streq_ptr(name, "annotation")) {
577 r = parse_xml_annotation(context, NULL);
578 if (r < 0)
579 return r;
580 } else {
581 log_error("Unexpected signal <arg> tag %s.", name);
582 return -EINVAL;
583 }
584 } else if (t == XML_TAG_CLOSE_EMPTY ||
585 (t == XML_TAG_CLOSE && streq_ptr(name, "arg"))) {
586
587 if (argument_type) {
588 if (!argument_direction || streq(argument_direction, "out")) {
589 if (!strextend(&context->member_signature, argument_type, NULL))
590 return log_oom();
591 } else
592 log_error("Unexpected signal <arg> direction value '%s'.", argument_direction);
593
594 argument_type = mfree(argument_type);
595 }
596
597 state = STATE_SIGNAL;
598 } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
599 log_error("Unexpected token in signal <arg> (1).");
600 return -EINVAL;
601 }
602
603 break;
604
605 case STATE_SIGNAL_ARG_NAME:
606
607 if (t == XML_ATTRIBUTE_VALUE)
608 state = STATE_SIGNAL_ARG;
609 else {
610 log_error("Unexpected token in signal <arg> (2).");
611 return -EINVAL;
612 }
613
614 break;
615
616 case STATE_SIGNAL_ARG_TYPE:
617
618 if (t == XML_ATTRIBUTE_VALUE) {
619 free_and_replace(argument_type, name);
620
621 state = STATE_SIGNAL_ARG;
622 } else {
623 log_error("Unexpected token in signal <arg> (3).");
624 return -EINVAL;
625 }
626
627 break;
628
629 case STATE_SIGNAL_ARG_DIRECTION:
630
631 if (t == XML_ATTRIBUTE_VALUE) {
632 free_and_replace(argument_direction, name);
633
634 state = STATE_SIGNAL_ARG;
635 } else {
636 log_error("Unexpected token in signal <arg>. (4)");
637 return -EINVAL;
638 }
639
640 break;
641
642 case STATE_PROPERTY:
643
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;
651 else {
652 log_error("Unexpected <property> attribute %s.", name);
653 return -EBADMSG;
654 }
655 } else if (t == XML_TAG_OPEN) {
656
657 if (streq_ptr(name, "annotation")) {
658 r = parse_xml_annotation(context, &context->member_flags);
659 if (r < 0)
660 return r;
661 } else {
662 log_error("Unexpected <property> tag %s.", name);
663 return -EINVAL;
664 }
665
666 } else if (t == XML_TAG_CLOSE_EMPTY ||
667 (t == XML_TAG_CLOSE && streq_ptr(name, "property"))) {
668
669 if (n_depth == 0) {
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);
672 if (r < 0)
673 return r;
674 }
675
676 context_reset_member(context);
677 }
678
679 state = STATE_INTERFACE;
680
681 } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
682 log_error("Unexpected token in <property>. (1)");
683 return -EINVAL;
684 }
685
686 break;
687
688 case STATE_PROPERTY_NAME:
689
690 if (t == XML_ATTRIBUTE_VALUE) {
691 if (n_depth == 0)
692 free_and_replace(context->member_name, name);
693
694 state = STATE_PROPERTY;
695 } else {
696 log_error("Unexpected token in <property>. (2)");
697 return -EINVAL;
698 }
699
700 break;
701
702 case STATE_PROPERTY_TYPE:
703
704 if (t == XML_ATTRIBUTE_VALUE) {
705 if (n_depth == 0)
706 free_and_replace(context->member_signature, name);
707
708 state = STATE_PROPERTY;
709 } else {
710 log_error("Unexpected token in <property>. (3)");
711 return -EINVAL;
712 }
713
714 break;
715
716 case STATE_PROPERTY_ACCESS:
717
718 if (t == XML_ATTRIBUTE_VALUE) {
719
720 if (streq(name, "readwrite") || streq(name, "write"))
721 context->member_writable = true;
722
723 state = STATE_PROPERTY;
724 } else {
725 log_error("Unexpected token in <property>. (4)");
726 return -EINVAL;
727 }
728
729 break;
730 }
731 }
732 }
733
734 int parse_xml_introspect(const char *prefix, const char *xml, const XMLIntrospectOps *ops, void *userdata) {
735 Context context = {
736 .ops = ops,
737 .userdata = userdata,
738 .current = xml,
739 };
740
741 int r;
742
743 assert(prefix);
744 assert(xml);
745 assert(ops);
746
747 for (;;) {
748 _cleanup_free_ char *name = NULL;
749
750 r = xml_tokenize(&context.current, &name, &context.xml_state, NULL);
751 if (r < 0) {
752 log_error("XML parse error");
753 goto finish;
754 }
755
756 if (r == XML_END) {
757 r = 0;
758 break;
759 }
760
761 if (r == XML_TAG_OPEN) {
762
763 if (streq(name, "node")) {
764 r = parse_xml_node(&context, prefix, 0);
765 if (r < 0)
766 goto finish;
767 } else {
768 log_error("Unexpected tag '%s' in introspection data.", name);
769 r = -EBADMSG;
770 goto finish;
771 }
772 } else if (r != XML_TEXT || !in_charset(name, WHITESPACE)) {
773 log_error("Unexpected token.");
774 r = -EBADMSG;
775 goto finish;
776 }
777 }
778
779 finish:
780 context_reset_interface(&context);
781
782 return r;
783 }