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