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