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