chiark / gitweb /
busctl: split out introspection parser from tree logic so that we can reuse it for...
[elogind.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 }