]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/busctl/busctl-introspect.c
cce68a480b757691246d51bca023bcb6a67ae575
[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 = TAKE_PTR(name);
280 else {
281
282 if (endswith(prefix, "/"))
283 node_path = strappend(prefix, name);
284 else
285 node_path = strjoin(prefix, "/", name);
286 if (!node_path)
287 return log_oom();
288 }
289
290 np = node_path;
291 state = STATE_NODE;
292 } else {
293 log_error("Unexpected token in <node>. (2)");
294 return -EINVAL;
295 }
296
297 break;
298
299 case STATE_INTERFACE:
300
301 if (t == XML_ATTRIBUTE_NAME) {
302 if (streq_ptr(name, "name"))
303 state = STATE_INTERFACE_NAME;
304 else {
305 log_error("Unexpected <interface> attribute %s.", name);
306 return -EBADMSG;
307 }
308
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);
319 if (r < 0)
320 return r;
321 } else {
322 log_error("Unexpected <interface> tag %s.", name);
323 return -EINVAL;
324 }
325 } else if (t == XML_TAG_CLOSE_EMPTY ||
326 (t == XML_TAG_CLOSE && streq_ptr(name, "interface"))) {
327
328 if (n_depth == 0) {
329 if (context->ops->on_interface) {
330 r = context->ops->on_interface(context->interface_name, context->interface_flags, context->userdata);
331 if (r < 0)
332 return r;
333 }
334
335 context_reset_interface(context);
336 }
337
338 state = STATE_NODE;
339
340 } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
341 log_error("Unexpected token in <interface>. (1)");
342 return -EINVAL;
343 }
344
345 break;
346
347 case STATE_INTERFACE_NAME:
348
349 if (t == XML_ATTRIBUTE_VALUE) {
350 if (n_depth == 0)
351 free_and_replace(context->interface_name, name);
352
353 state = STATE_INTERFACE;
354 } else {
355 log_error("Unexpected token in <interface>. (2)");
356 return -EINVAL;
357 }
358
359 break;
360
361 case STATE_METHOD:
362
363 if (t == XML_ATTRIBUTE_NAME) {
364 if (streq_ptr(name, "name"))
365 state = STATE_METHOD_NAME;
366 else {
367 log_error("Unexpected <method> attribute %s", name);
368 return -EBADMSG;
369 }
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);
375 if (r < 0)
376 return r;
377 } else {
378 log_error("Unexpected <method> tag %s.", name);
379 return -EINVAL;
380 }
381 } else if (t == XML_TAG_CLOSE_EMPTY ||
382 (t == XML_TAG_CLOSE && streq_ptr(name, "method"))) {
383
384 if (n_depth == 0) {
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);
387 if (r < 0)
388 return r;
389 }
390
391 context_reset_member(context);
392 }
393
394 state = STATE_INTERFACE;
395
396 } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
397 log_error("Unexpected token in <method> (1).");
398 return -EINVAL;
399 }
400
401 break;
402
403 case STATE_METHOD_NAME:
404
405 if (t == XML_ATTRIBUTE_VALUE) {
406 if (n_depth == 0)
407 free_and_replace(context->member_name, name);
408
409 state = STATE_METHOD;
410 } else {
411 log_error("Unexpected token in <method> (2).");
412 return -EINVAL;
413 }
414
415 break;
416
417 case STATE_METHOD_ARG:
418
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;
426 else {
427 log_error("Unexpected method <arg> attribute %s.", name);
428 return -EBADMSG;
429 }
430 } else if (t == XML_TAG_OPEN) {
431 if (streq_ptr(name, "annotation")) {
432 r = parse_xml_annotation(context, NULL);
433 if (r < 0)
434 return r;
435 } else {
436 log_error("Unexpected method <arg> tag %s.", name);
437 return -EINVAL;
438 }
439 } else if (t == XML_TAG_CLOSE_EMPTY ||
440 (t == XML_TAG_CLOSE && streq_ptr(name, "arg"))) {
441
442 if (n_depth == 0) {
443
444 if (argument_type) {
445 if (!argument_direction || streq(argument_direction, "in")) {
446 if (!strextend(&context->member_signature, argument_type, NULL))
447 return log_oom();
448 } else if (streq(argument_direction, "out")) {
449 if (!strextend(&context->member_result, argument_type, NULL))
450 return log_oom();
451 } else
452 log_error("Unexpected method <arg> direction value '%s'.", argument_direction);
453 }
454
455 argument_type = mfree(argument_type);
456 argument_direction = mfree(argument_direction);
457 }
458
459 state = STATE_METHOD;
460 } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
461 log_error("Unexpected token in method <arg>. (1)");
462 return -EINVAL;
463 }
464
465 break;
466
467 case STATE_METHOD_ARG_NAME:
468
469 if (t == XML_ATTRIBUTE_VALUE)
470 state = STATE_METHOD_ARG;
471 else {
472 log_error("Unexpected token in method <arg>. (2)");
473 return -EINVAL;
474 }
475
476 break;
477
478 case STATE_METHOD_ARG_TYPE:
479
480 if (t == XML_ATTRIBUTE_VALUE) {
481 free_and_replace(argument_type, name);
482
483 state = STATE_METHOD_ARG;
484 } else {
485 log_error("Unexpected token in method <arg>. (3)");
486 return -EINVAL;
487 }
488
489 break;
490
491 case STATE_METHOD_ARG_DIRECTION:
492
493 if (t == XML_ATTRIBUTE_VALUE) {
494 free_and_replace(argument_direction, name);
495
496 state = STATE_METHOD_ARG;
497 } else {
498 log_error("Unexpected token in method <arg>. (4)");
499 return -EINVAL;
500 }
501
502 break;
503
504 case STATE_SIGNAL:
505
506 if (t == XML_ATTRIBUTE_NAME) {
507 if (streq_ptr(name, "name"))
508 state = STATE_SIGNAL_NAME;
509 else {
510 log_error("Unexpected <signal> attribute %s.", name);
511 return -EBADMSG;
512 }
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);
518 if (r < 0)
519 return r;
520 } else {
521 log_error("Unexpected <signal> tag %s.", name);
522 return -EINVAL;
523 }
524 } else if (t == XML_TAG_CLOSE_EMPTY ||
525 (t == XML_TAG_CLOSE && streq_ptr(name, "signal"))) {
526
527 if (n_depth == 0) {
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);
530 if (r < 0)
531 return r;
532 }
533
534 context_reset_member(context);
535 }
536
537 state = STATE_INTERFACE;
538
539 } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
540 log_error("Unexpected token in <signal>. (1)");
541 return -EINVAL;
542 }
543
544 break;
545
546 case STATE_SIGNAL_NAME:
547
548 if (t == XML_ATTRIBUTE_VALUE) {
549 if (n_depth == 0)
550 free_and_replace(context->member_name, name);
551
552 state = STATE_SIGNAL;
553 } else {
554 log_error("Unexpected token in <signal>. (2)");
555 return -EINVAL;
556 }
557
558 break;
559
560
561 case STATE_SIGNAL_ARG:
562
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;
570 else {
571 log_error("Unexpected signal <arg> attribute %s.", name);
572 return -EBADMSG;
573 }
574 } else if (t == XML_TAG_OPEN) {
575 if (streq_ptr(name, "annotation")) {
576 r = parse_xml_annotation(context, NULL);
577 if (r < 0)
578 return r;
579 } else {
580 log_error("Unexpected signal <arg> tag %s.", name);
581 return -EINVAL;
582 }
583 } else if (t == XML_TAG_CLOSE_EMPTY ||
584 (t == XML_TAG_CLOSE && streq_ptr(name, "arg"))) {
585
586 if (argument_type) {
587 if (!argument_direction || streq(argument_direction, "out")) {
588 if (!strextend(&context->member_signature, argument_type, NULL))
589 return log_oom();
590 } else
591 log_error("Unexpected signal <arg> direction value '%s'.", argument_direction);
592
593 argument_type = mfree(argument_type);
594 }
595
596 state = STATE_SIGNAL;
597 } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
598 log_error("Unexpected token in signal <arg> (1).");
599 return -EINVAL;
600 }
601
602 break;
603
604 case STATE_SIGNAL_ARG_NAME:
605
606 if (t == XML_ATTRIBUTE_VALUE)
607 state = STATE_SIGNAL_ARG;
608 else {
609 log_error("Unexpected token in signal <arg> (2).");
610 return -EINVAL;
611 }
612
613 break;
614
615 case STATE_SIGNAL_ARG_TYPE:
616
617 if (t == XML_ATTRIBUTE_VALUE) {
618 free_and_replace(argument_type, name);
619
620 state = STATE_SIGNAL_ARG;
621 } else {
622 log_error("Unexpected token in signal <arg> (3).");
623 return -EINVAL;
624 }
625
626 break;
627
628 case STATE_SIGNAL_ARG_DIRECTION:
629
630 if (t == XML_ATTRIBUTE_VALUE) {
631 free_and_replace(argument_direction, name);
632
633 state = STATE_SIGNAL_ARG;
634 } else {
635 log_error("Unexpected token in signal <arg>. (4)");
636 return -EINVAL;
637 }
638
639 break;
640
641 case STATE_PROPERTY:
642
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;
650 else {
651 log_error("Unexpected <property> attribute %s.", name);
652 return -EBADMSG;
653 }
654 } else if (t == XML_TAG_OPEN) {
655
656 if (streq_ptr(name, "annotation")) {
657 r = parse_xml_annotation(context, &context->member_flags);
658 if (r < 0)
659 return r;
660 } else {
661 log_error("Unexpected <property> tag %s.", name);
662 return -EINVAL;
663 }
664
665 } else if (t == XML_TAG_CLOSE_EMPTY ||
666 (t == XML_TAG_CLOSE && streq_ptr(name, "property"))) {
667
668 if (n_depth == 0) {
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);
671 if (r < 0)
672 return r;
673 }
674
675 context_reset_member(context);
676 }
677
678 state = STATE_INTERFACE;
679
680 } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
681 log_error("Unexpected token in <property>. (1)");
682 return -EINVAL;
683 }
684
685 break;
686
687 case STATE_PROPERTY_NAME:
688
689 if (t == XML_ATTRIBUTE_VALUE) {
690 if (n_depth == 0)
691 free_and_replace(context->member_name, name);
692
693 state = STATE_PROPERTY;
694 } else {
695 log_error("Unexpected token in <property>. (2)");
696 return -EINVAL;
697 }
698
699 break;
700
701 case STATE_PROPERTY_TYPE:
702
703 if (t == XML_ATTRIBUTE_VALUE) {
704 if (n_depth == 0)
705 free_and_replace(context->member_signature, name);
706
707 state = STATE_PROPERTY;
708 } else {
709 log_error("Unexpected token in <property>. (3)");
710 return -EINVAL;
711 }
712
713 break;
714
715 case STATE_PROPERTY_ACCESS:
716
717 if (t == XML_ATTRIBUTE_VALUE) {
718
719 if (streq(name, "readwrite") || streq(name, "write"))
720 context->member_writable = true;
721
722 state = STATE_PROPERTY;
723 } else {
724 log_error("Unexpected token in <property>. (4)");
725 return -EINVAL;
726 }
727
728 break;
729 }
730 }
731 }
732
733 int parse_xml_introspect(const char *prefix, const char *xml, const XMLIntrospectOps *ops, void *userdata) {
734 Context context = {
735 .ops = ops,
736 .userdata = userdata,
737 .current = xml,
738 };
739
740 int r;
741
742 assert(prefix);
743 assert(xml);
744 assert(ops);
745
746 for (;;) {
747 _cleanup_free_ char *name = NULL;
748
749 r = xml_tokenize(&context.current, &name, &context.xml_state, NULL);
750 if (r < 0) {
751 log_error("XML parse error");
752 goto finish;
753 }
754
755 if (r == XML_END) {
756 r = 0;
757 break;
758 }
759
760 if (r == XML_TAG_OPEN) {
761
762 if (streq(name, "node")) {
763 r = parse_xml_node(&context, prefix, 0);
764 if (r < 0)
765 goto finish;
766 } else {
767 log_error("Unexpected tag '%s' in introspection data.", name);
768 r = -EBADMSG;
769 goto finish;
770 }
771 } else if (r != XML_TEXT || !in_charset(name, WHITESPACE)) {
772 log_error("Unexpected token.");
773 r = -EBADMSG;
774 goto finish;
775 }
776 }
777
778 finish:
779 context_reset_interface(&context);
780
781 return r;
782 }