]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/libsystemd/sd-bus/busctl-introspect.c
util-lib: split our string related calls from util.[ch] into its own file string...
[thirdparty/systemd.git] / src / libsystemd / sd-bus / busctl-introspect.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4 This file is part of systemd.
5
6 Copyright 2014 Lennart Poettering
7
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include "sd-bus.h"
23
24 #include "string-util.h"
25 #include "util.h"
26 #include "xml.h"
27 #include "busctl-introspect.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(field);
148 field = name;
149 name = NULL;
150
151 state = STATE_ANNOTATION;
152 } else {
153 log_error("Unexpected token in <annotation>. (2)");
154 return -EINVAL;
155 }
156
157 break;
158
159 case STATE_VALUE:
160
161 if (t == XML_ATTRIBUTE_VALUE) {
162 free(value);
163 value = name;
164 name = NULL;
165
166 state = STATE_ANNOTATION;
167 } else {
168 log_error("Unexpected token in <annotation>. (3)");
169 return -EINVAL;
170 }
171
172 break;
173
174 default:
175 assert_not_reached("Bad state");
176 }
177 }
178 }
179
180 static int parse_xml_node(Context *context, const char *prefix, unsigned n_depth) {
181
182 enum {
183 STATE_NODE,
184 STATE_NODE_NAME,
185 STATE_INTERFACE,
186 STATE_INTERFACE_NAME,
187 STATE_METHOD,
188 STATE_METHOD_NAME,
189 STATE_METHOD_ARG,
190 STATE_METHOD_ARG_NAME,
191 STATE_METHOD_ARG_TYPE,
192 STATE_METHOD_ARG_DIRECTION,
193 STATE_SIGNAL,
194 STATE_SIGNAL_NAME,
195 STATE_SIGNAL_ARG,
196 STATE_SIGNAL_ARG_NAME,
197 STATE_SIGNAL_ARG_TYPE,
198 STATE_PROPERTY,
199 STATE_PROPERTY_NAME,
200 STATE_PROPERTY_TYPE,
201 STATE_PROPERTY_ACCESS,
202 } state = STATE_NODE;
203
204 _cleanup_free_ char *node_path = NULL, *argument_type = NULL, *argument_direction = NULL;
205 const char *np = prefix;
206 int r;
207
208 assert(context);
209 assert(prefix);
210
211 if (n_depth > NODE_DEPTH_MAX) {
212 log_error("<node> depth too high.");
213 return -EINVAL;
214 }
215
216 for (;;) {
217 _cleanup_free_ char *name = NULL;
218 int t;
219
220 t = xml_tokenize(&context->current, &name, &context->xml_state, NULL);
221 if (t < 0) {
222 log_error("XML parse error.");
223 return t;
224 }
225
226 if (t == XML_END) {
227 log_error("Premature end of XML data.");
228 return -EBADMSG;
229 }
230
231 switch (state) {
232
233 case STATE_NODE:
234 if (t == XML_ATTRIBUTE_NAME) {
235
236 if (streq_ptr(name, "name"))
237 state = STATE_NODE_NAME;
238 else {
239 log_error("Unexpected <node> attribute %s.", name);
240 return -EBADMSG;
241 }
242
243 } else if (t == XML_TAG_OPEN) {
244
245 if (streq_ptr(name, "interface"))
246 state = STATE_INTERFACE;
247 else if (streq_ptr(name, "node")) {
248
249 r = parse_xml_node(context, np, n_depth+1);
250 if (r < 0)
251 return r;
252 } else {
253 log_error("Unexpected <node> tag %s.", name);
254 return -EBADMSG;
255 }
256
257 } else if (t == XML_TAG_CLOSE_EMPTY ||
258 (t == XML_TAG_CLOSE && streq_ptr(name, "node"))) {
259
260 if (context->ops->on_path) {
261 r = context->ops->on_path(node_path ? node_path : np, context->userdata);
262 if (r < 0)
263 return r;
264 }
265
266 return 0;
267
268 } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
269 log_error("Unexpected token in <node>. (1)");
270 return -EINVAL;
271 }
272
273 break;
274
275 case STATE_NODE_NAME:
276
277 if (t == XML_ATTRIBUTE_VALUE) {
278
279 free(node_path);
280
281 if (name[0] == '/') {
282 node_path = name;
283 name = NULL;
284 } else {
285
286 if (endswith(prefix, "/"))
287 node_path = strappend(prefix, name);
288 else
289 node_path = strjoin(prefix, "/", name, NULL);
290 if (!node_path)
291 return log_oom();
292 }
293
294 np = node_path;
295 state = STATE_NODE;
296 } else {
297 log_error("Unexpected token in <node>. (2)");
298 return -EINVAL;
299 }
300
301 break;
302
303 case STATE_INTERFACE:
304
305 if (t == XML_ATTRIBUTE_NAME) {
306 if (streq_ptr(name, "name"))
307 state = STATE_INTERFACE_NAME;
308 else {
309 log_error("Unexpected <interface> attribute %s.", name);
310 return -EBADMSG;
311 }
312
313 } else if (t == XML_TAG_OPEN) {
314 if (streq_ptr(name, "method"))
315 state = STATE_METHOD;
316 else if (streq_ptr(name, "signal"))
317 state = STATE_SIGNAL;
318 else if (streq_ptr(name, "property")) {
319 context->member_flags |= SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE;
320 state = STATE_PROPERTY;
321 } else if (streq_ptr(name, "annotation")) {
322 r = parse_xml_annotation(context, &context->interface_flags);
323 if (r < 0)
324 return r;
325 } else {
326 log_error("Unexpected <interface> tag %s.", name);
327 return -EINVAL;
328 }
329 } else if (t == XML_TAG_CLOSE_EMPTY ||
330 (t == XML_TAG_CLOSE && streq_ptr(name, "interface"))) {
331
332 if (n_depth == 0) {
333 if (context->ops->on_interface) {
334 r = context->ops->on_interface(context->interface_name, context->interface_flags, context->userdata);
335 if (r < 0)
336 return r;
337 }
338
339 context_reset_interface(context);
340 }
341
342 state = STATE_NODE;
343
344 } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
345 log_error("Unexpected token in <interface>. (1)");
346 return -EINVAL;
347 }
348
349 break;
350
351 case STATE_INTERFACE_NAME:
352
353 if (t == XML_ATTRIBUTE_VALUE) {
354 if (n_depth == 0) {
355 free(context->interface_name);
356 context->interface_name = name;
357 name = NULL;
358 }
359
360 state = STATE_INTERFACE;
361 } else {
362 log_error("Unexpected token in <interface>. (2)");
363 return -EINVAL;
364 }
365
366 break;
367
368 case STATE_METHOD:
369
370 if (t == XML_ATTRIBUTE_NAME) {
371 if (streq_ptr(name, "name"))
372 state = STATE_METHOD_NAME;
373 else {
374 log_error("Unexpected <method> attribute %s", name);
375 return -EBADMSG;
376 }
377 } else if (t == XML_TAG_OPEN) {
378 if (streq_ptr(name, "arg"))
379 state = STATE_METHOD_ARG;
380 else if (streq_ptr(name, "annotation")) {
381 r = parse_xml_annotation(context, &context->member_flags);
382 if (r < 0)
383 return r;
384 } else {
385 log_error("Unexpected <method> tag %s.", name);
386 return -EINVAL;
387 }
388 } else if (t == XML_TAG_CLOSE_EMPTY ||
389 (t == XML_TAG_CLOSE && streq_ptr(name, "method"))) {
390
391 if (n_depth == 0) {
392 if (context->ops->on_method) {
393 r = context->ops->on_method(context->interface_name, context->member_name, context->member_signature, context->member_result, context->member_flags, context->userdata);
394 if (r < 0)
395 return r;
396 }
397
398 context_reset_member(context);
399 }
400
401 state = STATE_INTERFACE;
402
403 } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
404 log_error("Unexpected token in <method> (1).");
405 return -EINVAL;
406 }
407
408 break;
409
410 case STATE_METHOD_NAME:
411
412 if (t == XML_ATTRIBUTE_VALUE) {
413
414 if (n_depth == 0) {
415 free(context->member_name);
416 context->member_name = name;
417 name = NULL;
418 }
419
420 state = STATE_METHOD;
421 } else {
422 log_error("Unexpected token in <method> (2).");
423 return -EINVAL;
424 }
425
426 break;
427
428 case STATE_METHOD_ARG:
429
430 if (t == XML_ATTRIBUTE_NAME) {
431 if (streq_ptr(name, "name"))
432 state = STATE_METHOD_ARG_NAME;
433 else if (streq_ptr(name, "type"))
434 state = STATE_METHOD_ARG_TYPE;
435 else if (streq_ptr(name, "direction"))
436 state = STATE_METHOD_ARG_DIRECTION;
437 else {
438 log_error("Unexpected method <arg> attribute %s.", name);
439 return -EBADMSG;
440 }
441 } else if (t == XML_TAG_OPEN) {
442 if (streq_ptr(name, "annotation")) {
443 r = parse_xml_annotation(context, NULL);
444 if (r < 0)
445 return r;
446 } else {
447 log_error("Unexpected method <arg> tag %s.", name);
448 return -EINVAL;
449 }
450 } else if (t == XML_TAG_CLOSE_EMPTY ||
451 (t == XML_TAG_CLOSE && streq_ptr(name, "arg"))) {
452
453 if (n_depth == 0) {
454
455 if (argument_type) {
456 if (!argument_direction || streq(argument_direction, "in")) {
457 if (!strextend(&context->member_signature, argument_type, NULL))
458 return log_oom();
459 } else if (streq(argument_direction, "out")) {
460 if (!strextend(&context->member_result, argument_type, NULL))
461 return log_oom();
462 }
463 }
464
465 argument_type = mfree(argument_type);
466 argument_direction = mfree(argument_direction);
467 }
468
469 state = STATE_METHOD;
470 } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
471 log_error("Unexpected token in method <arg>. (1)");
472 return -EINVAL;
473 }
474
475 break;
476
477 case STATE_METHOD_ARG_NAME:
478
479 if (t == XML_ATTRIBUTE_VALUE)
480 state = STATE_METHOD_ARG;
481 else {
482 log_error("Unexpected token in method <arg>. (2)");
483 return -EINVAL;
484 }
485
486 break;
487
488 case STATE_METHOD_ARG_TYPE:
489
490 if (t == XML_ATTRIBUTE_VALUE) {
491 free(argument_type);
492 argument_type = name;
493 name = NULL;
494
495 state = STATE_METHOD_ARG;
496 } else {
497 log_error("Unexpected token in method <arg>. (3)");
498 return -EINVAL;
499 }
500
501 break;
502
503 case STATE_METHOD_ARG_DIRECTION:
504
505 if (t == XML_ATTRIBUTE_VALUE) {
506 free(argument_direction);
507 argument_direction = name;
508 name = NULL;
509
510 state = STATE_METHOD_ARG;
511 } else {
512 log_error("Unexpected token in method <arg>. (4)");
513 return -EINVAL;
514 }
515
516 break;
517
518 case STATE_SIGNAL:
519
520 if (t == XML_ATTRIBUTE_NAME) {
521 if (streq_ptr(name, "name"))
522 state = STATE_SIGNAL_NAME;
523 else {
524 log_error("Unexpected <signal> attribute %s.", name);
525 return -EBADMSG;
526 }
527 } else if (t == XML_TAG_OPEN) {
528 if (streq_ptr(name, "arg"))
529 state = STATE_SIGNAL_ARG;
530 else if (streq_ptr(name, "annotation")) {
531 r = parse_xml_annotation(context, &context->member_flags);
532 if (r < 0)
533 return r;
534 } else {
535 log_error("Unexpected <signal> tag %s.", name);
536 return -EINVAL;
537 }
538 } else if (t == XML_TAG_CLOSE_EMPTY ||
539 (t == XML_TAG_CLOSE && streq_ptr(name, "signal"))) {
540
541 if (n_depth == 0) {
542 if (context->ops->on_signal) {
543 r = context->ops->on_signal(context->interface_name, context->member_name, context->member_signature, context->member_flags, context->userdata);
544 if (r < 0)
545 return r;
546 }
547
548 context_reset_member(context);
549 }
550
551 state = STATE_INTERFACE;
552
553 } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
554 log_error("Unexpected token in <signal>. (1)");
555 return -EINVAL;
556 }
557
558 break;
559
560 case STATE_SIGNAL_NAME:
561
562 if (t == XML_ATTRIBUTE_VALUE) {
563
564 if (n_depth == 0) {
565 free(context->member_name);
566 context->member_name = name;
567 name = NULL;
568 }
569
570 state = STATE_SIGNAL;
571 } else {
572 log_error("Unexpected token in <signal>. (2)");
573 return -EINVAL;
574 }
575
576 break;
577
578
579 case STATE_SIGNAL_ARG:
580
581 if (t == XML_ATTRIBUTE_NAME) {
582 if (streq_ptr(name, "name"))
583 state = STATE_SIGNAL_ARG_NAME;
584 else if (streq_ptr(name, "type"))
585 state = STATE_SIGNAL_ARG_TYPE;
586 else {
587 log_error("Unexpected signal <arg> attribute %s.", name);
588 return -EBADMSG;
589 }
590 } else if (t == XML_TAG_OPEN) {
591 if (streq_ptr(name, "annotation")) {
592 r = parse_xml_annotation(context, NULL);
593 if (r < 0)
594 return r;
595 } else {
596 log_error("Unexpected signal <arg> tag %s.", name);
597 return -EINVAL;
598 }
599 } else if (t == XML_TAG_CLOSE_EMPTY ||
600 (t == XML_TAG_CLOSE && streq_ptr(name, "arg"))) {
601
602 if (argument_type) {
603 if (!strextend(&context->member_signature, argument_type, NULL))
604 return log_oom();
605
606 argument_type = mfree(argument_type);
607 }
608
609 state = STATE_SIGNAL;
610 } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
611 log_error("Unexpected token in signal <arg> (1).");
612 return -EINVAL;
613 }
614
615 break;
616
617 case STATE_SIGNAL_ARG_NAME:
618
619 if (t == XML_ATTRIBUTE_VALUE)
620 state = STATE_SIGNAL_ARG;
621 else {
622 log_error("Unexpected token in signal <arg> (2).");
623 return -EINVAL;
624 }
625
626 break;
627
628 case STATE_SIGNAL_ARG_TYPE:
629
630 if (t == XML_ATTRIBUTE_VALUE) {
631 free(argument_type);
632 argument_type = name;
633 name = NULL;
634
635 state = STATE_SIGNAL_ARG;
636 } else {
637 log_error("Unexpected token in signal <arg> (3).");
638 return -EINVAL;
639 }
640
641 break;
642
643 case STATE_PROPERTY:
644
645 if (t == XML_ATTRIBUTE_NAME) {
646 if (streq_ptr(name, "name"))
647 state = STATE_PROPERTY_NAME;
648 else if (streq_ptr(name, "type"))
649 state = STATE_PROPERTY_TYPE;
650 else if (streq_ptr(name, "access"))
651 state = STATE_PROPERTY_ACCESS;
652 else {
653 log_error("Unexpected <property> attribute %s.", name);
654 return -EBADMSG;
655 }
656 } else if (t == XML_TAG_OPEN) {
657
658 if (streq_ptr(name, "annotation")) {
659 r = parse_xml_annotation(context, &context->member_flags);
660 if (r < 0)
661 return r;
662 } else {
663 log_error("Unexpected <property> tag %s.", name);
664 return -EINVAL;
665 }
666
667 } else if (t == XML_TAG_CLOSE_EMPTY ||
668 (t == XML_TAG_CLOSE && streq_ptr(name, "property"))) {
669
670 if (n_depth == 0) {
671 if (context->ops->on_property) {
672 r = context->ops->on_property(context->interface_name, context->member_name, context->member_signature, context->member_writable, context->member_flags, context->userdata);
673 if (r < 0)
674 return r;
675 }
676
677 context_reset_member(context);
678 }
679
680 state = STATE_INTERFACE;
681
682 } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
683 log_error("Unexpected token in <property>. (1)");
684 return -EINVAL;
685 }
686
687 break;
688
689 case STATE_PROPERTY_NAME:
690
691 if (t == XML_ATTRIBUTE_VALUE) {
692
693 if (n_depth == 0) {
694 free(context->member_name);
695 context->member_name = name;
696 name = NULL;
697 }
698 state = STATE_PROPERTY;
699 } else {
700 log_error("Unexpected token in <property>. (2)");
701 return -EINVAL;
702 }
703
704 break;
705
706 case STATE_PROPERTY_TYPE:
707
708 if (t == XML_ATTRIBUTE_VALUE) {
709
710 if (n_depth == 0) {
711 free(context->member_signature);
712 context->member_signature = name;
713 name = NULL;
714 }
715
716 state = STATE_PROPERTY;
717 } else {
718 log_error("Unexpected token in <property>. (3)");
719 return -EINVAL;
720 }
721
722 break;
723
724 case STATE_PROPERTY_ACCESS:
725
726 if (t == XML_ATTRIBUTE_VALUE) {
727
728 if (streq(name, "readwrite") || streq(name, "write"))
729 context->member_writable = true;
730
731 state = STATE_PROPERTY;
732 } else {
733 log_error("Unexpected token in <property>. (4)");
734 return -EINVAL;
735 }
736
737 break;
738 }
739 }
740 }
741
742 int parse_xml_introspect(const char *prefix, const char *xml, const XMLIntrospectOps *ops, void *userdata) {
743 Context context = {
744 .ops = ops,
745 .userdata = userdata,
746 .current = xml,
747 };
748
749 int r;
750
751 assert(prefix);
752 assert(xml);
753 assert(ops);
754
755 for (;;) {
756 _cleanup_free_ char *name = NULL;
757
758 r = xml_tokenize(&context.current, &name, &context.xml_state, NULL);
759 if (r < 0) {
760 log_error("XML parse error");
761 goto finish;
762 }
763
764 if (r == XML_END) {
765 r = 0;
766 break;
767 }
768
769 if (r == XML_TAG_OPEN) {
770
771 if (streq(name, "node")) {
772 r = parse_xml_node(&context, prefix, 0);
773 if (r < 0)
774 goto finish;
775 } else {
776 log_error("Unexpected tag '%s' in introspection data.", name);
777 r = -EBADMSG;
778 goto finish;
779 }
780 } else if (r != XML_TEXT || !in_charset(name, WHITESPACE)) {
781 log_error("Unexpected token.");
782 r = -EBADMSG;
783 goto finish;
784 }
785 }
786
787 finish:
788 context_reset_interface(&context);
789
790 return r;
791 }