]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/libsystemd/sd-bus/busctl-introspect.c
sd-bus: refuse properties that claim to be both writable and constant at the same...
[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
42 const char *current;
43 void *xml_state;
44 } Context;
45
46 static int parse_xml_annotation(Context *context, uint64_t *flags) {
47
48 enum {
49 STATE_ANNOTATION,
50 STATE_NAME,
51 STATE_VALUE
52 } state = STATE_ANNOTATION;
53
54 _cleanup_free_ char *field = NULL, *value = NULL;
55
56 assert(context);
57
58 for (;;) {
59 _cleanup_free_ char *name = NULL;
60
61 int t;
62
63 t = xml_tokenize(&context->current, &name, &context->xml_state, NULL);
64 if (t < 0) {
65 log_error("XML parse error.");
66 return t;
67 }
68
69 if (t == XML_END) {
70 log_error("Premature end of XML data.");
71 return -EBADMSG;
72 }
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 log_error("Unexpected <annotation> attribute %s.", name);
88 return -EBADMSG;
89 }
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 |= SD_BUS_VTABLE_PROPERTY_CONST;
109 else if (streq_ptr(value, "invalidates"))
110 *flags |= SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION;
111 else if (streq_ptr(value, "false"))
112 *flags |= SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE;
113 }
114 }
115
116 return 0;
117
118 } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
119 log_error("Unexpected token in <annotation>. (1)");
120 return -EINVAL;
121 }
122
123 break;
124
125 case STATE_NAME:
126
127 if (t == XML_ATTRIBUTE_VALUE) {
128 free(field);
129 field = name;
130 name = NULL;
131
132 state = STATE_ANNOTATION;
133 } else {
134 log_error("Unexpected token in <annotation>. (2)");
135 return -EINVAL;
136 }
137
138 break;
139
140 case STATE_VALUE:
141
142 if (t == XML_ATTRIBUTE_VALUE) {
143 free(value);
144 value = name;
145 name = NULL;
146
147 state = STATE_ANNOTATION;
148 } else {
149 log_error("Unexpected token in <annotation>. (3)");
150 return -EINVAL;
151 }
152
153 break;
154
155 default:
156 assert_not_reached("Bad state");
157 }
158 }
159 }
160
161 static int parse_xml_node(Context *context, const char *prefix, unsigned n_depth) {
162
163 enum {
164 STATE_NODE,
165 STATE_NODE_NAME,
166 STATE_INTERFACE,
167 STATE_INTERFACE_NAME,
168 STATE_METHOD,
169 STATE_METHOD_NAME,
170 STATE_METHOD_ARG,
171 STATE_METHOD_ARG_NAME,
172 STATE_METHOD_ARG_TYPE,
173 STATE_METHOD_ARG_DIRECTION,
174 STATE_SIGNAL,
175 STATE_SIGNAL_NAME,
176 STATE_SIGNAL_ARG,
177 STATE_SIGNAL_ARG_NAME,
178 STATE_SIGNAL_ARG_TYPE,
179 STATE_PROPERTY,
180 STATE_PROPERTY_NAME,
181 STATE_PROPERTY_TYPE,
182 STATE_PROPERTY_ACCESS,
183 } state = STATE_NODE;
184
185 _cleanup_free_ char *node_path = NULL;
186 const char *np = prefix;
187 int r;
188
189 assert(context);
190 assert(prefix);
191
192 if (n_depth > NODE_DEPTH_MAX) {
193 log_error("<node> depth too high.");
194 return -EINVAL;
195 }
196
197 for (;;) {
198 _cleanup_free_ char *name = NULL;
199 int t;
200
201 t = xml_tokenize(&context->current, &name, &context->xml_state, NULL);
202 if (t < 0) {
203 log_error("XML parse error.");
204 return t;
205 }
206
207 if (t == XML_END) {
208 log_error("Premature end of XML data.");
209 return -EBADMSG;
210 }
211
212 switch (state) {
213
214 case STATE_NODE:
215 if (t == XML_ATTRIBUTE_NAME) {
216
217 if (streq_ptr(name, "name"))
218 state = STATE_NODE_NAME;
219 else {
220 log_error("Unexpected <node> attribute %s.", name);
221 return -EBADMSG;
222 }
223
224 } else if (t == XML_TAG_OPEN) {
225
226 if (streq_ptr(name, "interface"))
227 state = STATE_INTERFACE;
228
229 else if (streq_ptr(name, "node")) {
230
231 r = parse_xml_node(context, np, n_depth+1);
232 if (r < 0)
233 return r;
234 } else {
235 log_error("Unexpected <node> tag %s.", name);
236 return -EBADMSG;
237 }
238
239 } else if (t == XML_TAG_CLOSE_EMPTY ||
240 (t == XML_TAG_CLOSE && streq_ptr(name, "node"))) {
241
242 if (context->ops->on_path) {
243 r = context->ops->on_path(node_path ? node_path : np, context->userdata);
244 if (r < 0)
245 return r;
246 }
247
248 return 0;
249
250 } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
251 log_error("Unexpected token in <node>. (1)");
252 return -EINVAL;
253 }
254
255 break;
256
257 case STATE_NODE_NAME:
258
259 if (t == XML_ATTRIBUTE_VALUE) {
260
261 free(node_path);
262
263 if (name[0] == '/') {
264 node_path = name;
265 name = NULL;
266 } else {
267
268 if (endswith(prefix, "/"))
269 node_path = strappend(prefix, name);
270 else
271 node_path = strjoin(prefix, "/", name, NULL);
272 if (!node_path)
273 return log_oom();
274 }
275
276 np = node_path;
277 state = STATE_NODE;
278 } else {
279 log_error("Unexpected token in <node>. (2)");
280 return -EINVAL;
281 }
282
283 break;
284
285 case STATE_INTERFACE:
286
287 if (t == XML_ATTRIBUTE_NAME) {
288 if (streq_ptr(name, "name"))
289 state = STATE_INTERFACE_NAME;
290 else {
291 log_error("Unexpected <interface> attribute %s.", name);
292 return -EBADMSG;
293 }
294
295 } else if (t == XML_TAG_OPEN) {
296 if (streq_ptr(name, "method"))
297 state = STATE_METHOD;
298 else if (streq_ptr(name, "signal"))
299 state = STATE_SIGNAL;
300 else if (streq_ptr(name, "property"))
301 state = STATE_PROPERTY;
302 else if (streq_ptr(name, "annotation")) {
303 r = parse_xml_annotation(context, &context->interface_flags);
304 if (r < 0)
305 return r;
306 } else {
307 log_error("Unexpected <interface> tag %s.", name);
308 return -EINVAL;
309 }
310 } else if (t == XML_TAG_CLOSE_EMPTY ||
311 (t == XML_TAG_CLOSE && streq_ptr(name, "interface")))
312
313 state = STATE_NODE;
314
315 else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
316 log_error("Unexpected token in <interface>. (1)");
317 return -EINVAL;
318 }
319
320 break;
321
322 case STATE_INTERFACE_NAME:
323
324 if (t == XML_ATTRIBUTE_VALUE)
325 state = STATE_INTERFACE;
326 else {
327 log_error("Unexpected token in <interface>. (2)");
328 return -EINVAL;
329 }
330
331 break;
332
333 case STATE_METHOD:
334
335 if (t == XML_ATTRIBUTE_NAME) {
336 if (streq_ptr(name, "name"))
337 state = STATE_METHOD_NAME;
338 else {
339 log_error("Unexpected <method> attribute %s", name);
340 return -EBADMSG;
341 }
342 } else if (t == XML_TAG_OPEN) {
343 if (streq_ptr(name, "arg"))
344 state = STATE_METHOD_ARG;
345 else if (streq_ptr(name, "annotation")) {
346 r = parse_xml_annotation(context, &context->member_flags);
347 if (r < 0)
348 return r;
349 } else {
350 log_error("Unexpected <method> tag %s.", name);
351 return -EINVAL;
352 }
353 } else if (t == XML_TAG_CLOSE_EMPTY ||
354 (t == XML_TAG_CLOSE && streq_ptr(name, "method")))
355
356 state = STATE_INTERFACE;
357
358 else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
359 log_error("Unexpected token in <method> (1).");
360 return -EINVAL;
361 }
362
363 break;
364
365 case STATE_METHOD_NAME:
366
367 if (t == XML_ATTRIBUTE_VALUE)
368 state = STATE_METHOD;
369 else {
370 log_error("Unexpected token in <method> (2).");
371 return -EINVAL;
372 }
373
374 break;
375
376 case STATE_METHOD_ARG:
377
378 if (t == XML_ATTRIBUTE_NAME) {
379 if (streq_ptr(name, "name"))
380 state = STATE_METHOD_ARG_NAME;
381 else if (streq_ptr(name, "type"))
382 state = STATE_METHOD_ARG_TYPE;
383 else if (streq_ptr(name, "direction"))
384 state = STATE_METHOD_ARG_DIRECTION;
385 else {
386 log_error("Unexpected method <arg> attribute %s.", name);
387 return -EBADMSG;
388 }
389 } else if (t == XML_TAG_OPEN) {
390 if (streq_ptr(name, "annotation")) {
391 r = parse_xml_annotation(context, NULL);
392 if (r < 0)
393 return r;
394 } else {
395 log_error("Unexpected method <arg> tag %s.", name);
396 return -EINVAL;
397 }
398 } else if (t == XML_TAG_CLOSE_EMPTY ||
399 (t == XML_TAG_CLOSE && streq_ptr(name, "arg")))
400
401 state = STATE_METHOD;
402
403 else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
404 log_error("Unexpected token in method <arg>. (1)");
405 return -EINVAL;
406 }
407
408 break;
409
410 case STATE_METHOD_ARG_NAME:
411
412 if (t == XML_ATTRIBUTE_VALUE)
413 state = STATE_METHOD_ARG;
414 else {
415 log_error("Unexpected token in method <arg>. (2)");
416 return -EINVAL;
417 }
418
419 break;
420
421 case STATE_METHOD_ARG_TYPE:
422
423 if (t == XML_ATTRIBUTE_VALUE)
424 state = STATE_METHOD_ARG;
425 else {
426 log_error("Unexpected token in method <arg>. (3)");
427 return -EINVAL;
428 }
429
430 break;
431
432 case STATE_METHOD_ARG_DIRECTION:
433
434 if (t == XML_ATTRIBUTE_VALUE)
435 state = STATE_METHOD_ARG;
436 else {
437 log_error("Unexpected token in method <arg>. (4)");
438 return -EINVAL;
439 }
440
441 break;
442
443 case STATE_SIGNAL:
444
445 if (t == XML_ATTRIBUTE_NAME) {
446 if (streq_ptr(name, "name"))
447 state = STATE_SIGNAL_NAME;
448 else {
449 log_error("Unexpected <signal> attribute %s.", name);
450 return -EBADMSG;
451 }
452 } else if (t == XML_TAG_OPEN) {
453 if (streq_ptr(name, "arg"))
454 state = STATE_SIGNAL_ARG;
455 else if (streq_ptr(name, "annotation")) {
456 r = parse_xml_annotation(context, &context->member_flags);
457 if (r < 0)
458 return r;
459 } else {
460 log_error("Unexpected <signal> tag %s.", name);
461 return -EINVAL;
462 }
463 } else if (t == XML_TAG_CLOSE_EMPTY ||
464 (t == XML_TAG_CLOSE && streq_ptr(name, "signal")))
465
466 state = STATE_INTERFACE;
467
468 else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
469 log_error("Unexpected token in <signal>. (1)");
470 return -EINVAL;
471 }
472
473 break;
474
475 case STATE_SIGNAL_NAME:
476
477 if (t == XML_ATTRIBUTE_VALUE)
478 state = STATE_SIGNAL;
479 else {
480 log_error("Unexpected token in <signal>. (2)");
481 return -EINVAL;
482 }
483
484 break;
485
486
487 case STATE_SIGNAL_ARG:
488
489 if (t == XML_ATTRIBUTE_NAME) {
490 if (streq_ptr(name, "name"))
491 state = STATE_SIGNAL_ARG_NAME;
492 else if (streq_ptr(name, "type"))
493 state = STATE_SIGNAL_ARG_TYPE;
494 else {
495 log_error("Unexpected signal <arg> attribute %s.", name);
496 return -EBADMSG;
497 }
498 } else if (t == XML_TAG_OPEN) {
499 if (streq_ptr(name, "annotation")) {
500 r = parse_xml_annotation(context, NULL);
501 if (r < 0)
502 return r;
503 } else {
504 log_error("Unexpected signal <arg> tag %s.", name);
505 return -EINVAL;
506 }
507 } else if (t == XML_TAG_CLOSE_EMPTY ||
508 (t == XML_TAG_CLOSE && streq_ptr(name, "arg")))
509
510 state = STATE_SIGNAL;
511
512 else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
513 log_error("Unexpected token in signal <arg> (1).");
514 return -EINVAL;
515 }
516
517 break;
518
519 case STATE_SIGNAL_ARG_NAME:
520
521 if (t == XML_ATTRIBUTE_VALUE)
522 state = STATE_SIGNAL_ARG;
523 else {
524 log_error("Unexpected token in signal <arg> (2).");
525 return -EINVAL;
526 }
527
528 break;
529
530 case STATE_SIGNAL_ARG_TYPE:
531
532 if (t == XML_ATTRIBUTE_VALUE)
533 state = STATE_SIGNAL_ARG;
534 else {
535 log_error("Unexpected token in signal <arg> (3).");
536 return -EINVAL;
537 }
538
539 break;
540
541 case STATE_PROPERTY:
542
543 if (t == XML_ATTRIBUTE_NAME) {
544 if (streq_ptr(name, "name"))
545 state = STATE_PROPERTY_NAME;
546 else if (streq_ptr(name, "type"))
547 state = STATE_PROPERTY_TYPE;
548 else if (streq_ptr(name, "access"))
549 state = STATE_PROPERTY_ACCESS;
550 else {
551 log_error("Unexpected <property> attribute %s.", name);
552 return -EBADMSG;
553 }
554 } else if (t == XML_TAG_OPEN) {
555
556 if (streq_ptr(name, "annotation")) {
557 r = parse_xml_annotation(context, &context->member_flags);
558 if (r < 0)
559 return r;
560 } else {
561 log_error("Unexpected <property> tag %s.", name);
562 return -EINVAL;
563 }
564
565 } else if (t == XML_TAG_CLOSE_EMPTY ||
566 (t == XML_TAG_CLOSE && streq_ptr(name, "property")))
567
568 state = STATE_INTERFACE;
569
570 else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
571 log_error("Unexpected token in <property>. (1)");
572 return -EINVAL;
573 }
574
575 break;
576
577 case STATE_PROPERTY_NAME:
578
579 if (t == XML_ATTRIBUTE_VALUE)
580 state = STATE_PROPERTY;
581 else {
582 log_error("Unexpected token in <property>. (2)");
583 return -EINVAL;
584 }
585
586 break;
587
588 case STATE_PROPERTY_TYPE:
589
590 if (t == XML_ATTRIBUTE_VALUE)
591 state = STATE_PROPERTY;
592 else {
593 log_error("Unexpected token in <property>. (3)");
594 return -EINVAL;
595 }
596
597 break;
598
599 case STATE_PROPERTY_ACCESS:
600
601 if (t == XML_ATTRIBUTE_VALUE)
602 state = STATE_PROPERTY;
603 else {
604 log_error("Unexpected token in <property>. (4)");
605 return -EINVAL;
606 }
607
608 break;
609 }
610 }
611 }
612
613 int parse_xml_introspect(const char *prefix, const char *xml, const XMLIntrospectOps *ops, void *userdata) {
614 Context context = {
615 .ops = ops,
616 .userdata = userdata,
617 .current = xml,
618 };
619
620 int r;
621
622 assert(prefix);
623 assert(xml);
624 assert(ops);
625
626 for (;;) {
627 _cleanup_free_ char *name = NULL;
628
629 r = xml_tokenize(&context.current, &name, &context.xml_state, NULL);
630 if (r < 0) {
631 log_error("XML parse error");
632 return r;
633 }
634
635 if (r == XML_END)
636 break;
637
638 if (r == XML_TAG_OPEN) {
639
640 if (streq(name, "node")) {
641 r = parse_xml_node(&context, prefix, 0);
642 if (r < 0)
643 return r;
644 } else {
645 log_error("Unexpected tag '%s' in introspection data.", name);
646 return -EBADMSG;
647 }
648 } else if (r != XML_TEXT || !in_charset(name, WHITESPACE)) {
649 log_error("Unexpected token.");
650 return -EINVAL;
651 }
652 }
653
654 return 0;
655 }