]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/libsystemd/sd-bus/busctl-introspect.c
tmpfiles: accurately report creation results
[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 free(c->interface_name);
59 c->interface_name = NULL;
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 free(argument_type);
466 free(argument_direction);
467 argument_type = argument_direction = NULL;
468 }
469
470 state = STATE_METHOD;
471 } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
472 log_error("Unexpected token in method <arg>. (1)");
473 return -EINVAL;
474 }
475
476 break;
477
478 case STATE_METHOD_ARG_NAME:
479
480 if (t == XML_ATTRIBUTE_VALUE)
481 state = STATE_METHOD_ARG;
482 else {
483 log_error("Unexpected token in method <arg>. (2)");
484 return -EINVAL;
485 }
486
487 break;
488
489 case STATE_METHOD_ARG_TYPE:
490
491 if (t == XML_ATTRIBUTE_VALUE) {
492 free(argument_type);
493 argument_type = name;
494 name = NULL;
495
496 state = STATE_METHOD_ARG;
497 } else {
498 log_error("Unexpected token in method <arg>. (3)");
499 return -EINVAL;
500 }
501
502 break;
503
504 case STATE_METHOD_ARG_DIRECTION:
505
506 if (t == XML_ATTRIBUTE_VALUE) {
507 free(argument_direction);
508 argument_direction = name;
509 name = NULL;
510
511 state = STATE_METHOD_ARG;
512 } else {
513 log_error("Unexpected token in method <arg>. (4)");
514 return -EINVAL;
515 }
516
517 break;
518
519 case STATE_SIGNAL:
520
521 if (t == XML_ATTRIBUTE_NAME) {
522 if (streq_ptr(name, "name"))
523 state = STATE_SIGNAL_NAME;
524 else {
525 log_error("Unexpected <signal> attribute %s.", name);
526 return -EBADMSG;
527 }
528 } else if (t == XML_TAG_OPEN) {
529 if (streq_ptr(name, "arg"))
530 state = STATE_SIGNAL_ARG;
531 else if (streq_ptr(name, "annotation")) {
532 r = parse_xml_annotation(context, &context->member_flags);
533 if (r < 0)
534 return r;
535 } else {
536 log_error("Unexpected <signal> tag %s.", name);
537 return -EINVAL;
538 }
539 } else if (t == XML_TAG_CLOSE_EMPTY ||
540 (t == XML_TAG_CLOSE && streq_ptr(name, "signal"))) {
541
542 if (n_depth == 0) {
543 if (context->ops->on_signal) {
544 r = context->ops->on_signal(context->interface_name, context->member_name, context->member_signature, context->member_flags, context->userdata);
545 if (r < 0)
546 return r;
547 }
548
549 context_reset_member(context);
550 }
551
552 state = STATE_INTERFACE;
553
554 } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
555 log_error("Unexpected token in <signal>. (1)");
556 return -EINVAL;
557 }
558
559 break;
560
561 case STATE_SIGNAL_NAME:
562
563 if (t == XML_ATTRIBUTE_VALUE) {
564
565 if (n_depth == 0) {
566 free(context->member_name);
567 context->member_name = name;
568 name = NULL;
569 }
570
571 state = STATE_SIGNAL;
572 } else {
573 log_error("Unexpected token in <signal>. (2)");
574 return -EINVAL;
575 }
576
577 break;
578
579
580 case STATE_SIGNAL_ARG:
581
582 if (t == XML_ATTRIBUTE_NAME) {
583 if (streq_ptr(name, "name"))
584 state = STATE_SIGNAL_ARG_NAME;
585 else if (streq_ptr(name, "type"))
586 state = STATE_SIGNAL_ARG_TYPE;
587 else {
588 log_error("Unexpected signal <arg> attribute %s.", name);
589 return -EBADMSG;
590 }
591 } else if (t == XML_TAG_OPEN) {
592 if (streq_ptr(name, "annotation")) {
593 r = parse_xml_annotation(context, NULL);
594 if (r < 0)
595 return r;
596 } else {
597 log_error("Unexpected signal <arg> tag %s.", name);
598 return -EINVAL;
599 }
600 } else if (t == XML_TAG_CLOSE_EMPTY ||
601 (t == XML_TAG_CLOSE && streq_ptr(name, "arg"))) {
602
603 if (argument_type) {
604 if (!strextend(&context->member_signature, argument_type, NULL))
605 return log_oom();
606
607 free(argument_type);
608 argument_type = NULL;
609 }
610
611 state = STATE_SIGNAL;
612 } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
613 log_error("Unexpected token in signal <arg> (1).");
614 return -EINVAL;
615 }
616
617 break;
618
619 case STATE_SIGNAL_ARG_NAME:
620
621 if (t == XML_ATTRIBUTE_VALUE)
622 state = STATE_SIGNAL_ARG;
623 else {
624 log_error("Unexpected token in signal <arg> (2).");
625 return -EINVAL;
626 }
627
628 break;
629
630 case STATE_SIGNAL_ARG_TYPE:
631
632 if (t == XML_ATTRIBUTE_VALUE) {
633 free(argument_type);
634 argument_type = name;
635 name = NULL;
636
637 state = STATE_SIGNAL_ARG;
638 } else {
639 log_error("Unexpected token in signal <arg> (3).");
640 return -EINVAL;
641 }
642
643 break;
644
645 case STATE_PROPERTY:
646
647 if (t == XML_ATTRIBUTE_NAME) {
648 if (streq_ptr(name, "name"))
649 state = STATE_PROPERTY_NAME;
650 else if (streq_ptr(name, "type"))
651 state = STATE_PROPERTY_TYPE;
652 else if (streq_ptr(name, "access"))
653 state = STATE_PROPERTY_ACCESS;
654 else {
655 log_error("Unexpected <property> attribute %s.", name);
656 return -EBADMSG;
657 }
658 } else if (t == XML_TAG_OPEN) {
659
660 if (streq_ptr(name, "annotation")) {
661 r = parse_xml_annotation(context, &context->member_flags);
662 if (r < 0)
663 return r;
664 } else {
665 log_error("Unexpected <property> tag %s.", name);
666 return -EINVAL;
667 }
668
669 } else if (t == XML_TAG_CLOSE_EMPTY ||
670 (t == XML_TAG_CLOSE && streq_ptr(name, "property"))) {
671
672 if (n_depth == 0) {
673 if (context->ops->on_property) {
674 r = context->ops->on_property(context->interface_name, context->member_name, context->member_signature, context->member_writable, context->member_flags, context->userdata);
675 if (r < 0)
676 return r;
677 }
678
679 context_reset_member(context);
680 }
681
682 state = STATE_INTERFACE;
683
684 } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
685 log_error("Unexpected token in <property>. (1)");
686 return -EINVAL;
687 }
688
689 break;
690
691 case STATE_PROPERTY_NAME:
692
693 if (t == XML_ATTRIBUTE_VALUE) {
694
695 if (n_depth == 0) {
696 free(context->member_name);
697 context->member_name = name;
698 name = NULL;
699 }
700 state = STATE_PROPERTY;
701 } else {
702 log_error("Unexpected token in <property>. (2)");
703 return -EINVAL;
704 }
705
706 break;
707
708 case STATE_PROPERTY_TYPE:
709
710 if (t == XML_ATTRIBUTE_VALUE) {
711
712 if (n_depth == 0) {
713 free(context->member_signature);
714 context->member_signature = name;
715 name = NULL;
716 }
717
718 state = STATE_PROPERTY;
719 } else {
720 log_error("Unexpected token in <property>. (3)");
721 return -EINVAL;
722 }
723
724 break;
725
726 case STATE_PROPERTY_ACCESS:
727
728 if (t == XML_ATTRIBUTE_VALUE) {
729
730 if (streq(name, "readwrite") || streq(name, "write"))
731 context->member_writable = true;
732
733 state = STATE_PROPERTY;
734 } else {
735 log_error("Unexpected token in <property>. (4)");
736 return -EINVAL;
737 }
738
739 break;
740 }
741 }
742 }
743
744 int parse_xml_introspect(const char *prefix, const char *xml, const XMLIntrospectOps *ops, void *userdata) {
745 Context context = {
746 .ops = ops,
747 .userdata = userdata,
748 .current = xml,
749 };
750
751 int r;
752
753 assert(prefix);
754 assert(xml);
755 assert(ops);
756
757 for (;;) {
758 _cleanup_free_ char *name = NULL;
759
760 r = xml_tokenize(&context.current, &name, &context.xml_state, NULL);
761 if (r < 0) {
762 log_error("XML parse error");
763 goto finish;
764 }
765
766 if (r == XML_END) {
767 r = 0;
768 break;
769 }
770
771 if (r == XML_TAG_OPEN) {
772
773 if (streq(name, "node")) {
774 r = parse_xml_node(&context, prefix, 0);
775 if (r < 0)
776 goto finish;
777 } else {
778 log_error("Unexpected tag '%s' in introspection data.", name);
779 r = -EBADMSG;
780 goto finish;
781 }
782 } else if (r != XML_TEXT || !in_charset(name, WHITESPACE)) {
783 log_error("Unexpected token.");
784 r = -EBADMSG;
785 goto finish;
786 }
787 }
788
789 finish:
790 context_reset_interface(&context);
791
792 return r;
793 }