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