]>
Commit | Line | Data |
---|---|---|
1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ | |
2 | ||
3 | #include <stdio.h> | |
4 | ||
5 | #include "sd-bus-vtable.h" | |
6 | ||
7 | #include "bus-internal.h" | |
8 | #include "bus-introspect.h" | |
9 | #include "bus-objects.h" | |
10 | #include "bus-signature.h" | |
11 | #include "memstream-util.h" | |
12 | #include "ordered-set.h" | |
13 | #include "string-util.h" | |
14 | ||
15 | #define BUS_INTROSPECT_DOCTYPE \ | |
16 | "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\"\n" \ | |
17 | "\"https://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n" | |
18 | ||
19 | #define BUS_INTROSPECT_INTERFACE_PEER \ | |
20 | " <interface name=\"org.freedesktop.DBus.Peer\">\n" \ | |
21 | " <method name=\"Ping\"/>\n" \ | |
22 | " <method name=\"GetMachineId\">\n" \ | |
23 | " <arg type=\"s\" name=\"machine_uuid\" direction=\"out\"/>\n" \ | |
24 | " </method>\n" \ | |
25 | " </interface>\n" | |
26 | ||
27 | #define BUS_INTROSPECT_INTERFACE_INTROSPECTABLE \ | |
28 | " <interface name=\"org.freedesktop.DBus.Introspectable\">\n" \ | |
29 | " <method name=\"Introspect\">\n" \ | |
30 | " <arg name=\"xml_data\" type=\"s\" direction=\"out\"/>\n" \ | |
31 | " </method>\n" \ | |
32 | " </interface>\n" | |
33 | ||
34 | #define BUS_INTROSPECT_INTERFACE_PROPERTIES \ | |
35 | " <interface name=\"org.freedesktop.DBus.Properties\">\n" \ | |
36 | " <method name=\"Get\">\n" \ | |
37 | " <arg name=\"interface_name\" direction=\"in\" type=\"s\"/>\n" \ | |
38 | " <arg name=\"property_name\" direction=\"in\" type=\"s\"/>\n" \ | |
39 | " <arg name=\"value\" direction=\"out\" type=\"v\"/>\n" \ | |
40 | " </method>\n" \ | |
41 | " <method name=\"GetAll\">\n" \ | |
42 | " <arg name=\"interface_name\" direction=\"in\" type=\"s\"/>\n" \ | |
43 | " <arg name=\"props\" direction=\"out\" type=\"a{sv}\"/>\n" \ | |
44 | " </method>\n" \ | |
45 | " <method name=\"Set\">\n" \ | |
46 | " <arg name=\"interface_name\" direction=\"in\" type=\"s\"/>\n" \ | |
47 | " <arg name=\"property_name\" direction=\"in\" type=\"s\"/>\n" \ | |
48 | " <arg name=\"value\" direction=\"in\" type=\"v\"/>\n" \ | |
49 | " </method>\n" \ | |
50 | " <signal name=\"PropertiesChanged\">\n" \ | |
51 | " <arg type=\"s\" name=\"interface_name\"/>\n" \ | |
52 | " <arg type=\"a{sv}\" name=\"changed_properties\"/>\n" \ | |
53 | " <arg type=\"as\" name=\"invalidated_properties\"/>\n" \ | |
54 | " </signal>\n" \ | |
55 | " </interface>\n" | |
56 | ||
57 | #define BUS_INTROSPECT_INTERFACE_OBJECT_MANAGER \ | |
58 | " <interface name=\"org.freedesktop.DBus.ObjectManager\">\n" \ | |
59 | " <method name=\"GetManagedObjects\">\n" \ | |
60 | " <arg type=\"a{oa{sa{sv}}}\" name=\"object_paths_interfaces_and_properties\" direction=\"out\"/>\n" \ | |
61 | " </method>\n" \ | |
62 | " <signal name=\"InterfacesAdded\">\n" \ | |
63 | " <arg type=\"o\" name=\"object_path\"/>\n" \ | |
64 | " <arg type=\"a{sa{sv}}\" name=\"interfaces_and_properties\"/>\n" \ | |
65 | " </signal>\n" \ | |
66 | " <signal name=\"InterfacesRemoved\">\n" \ | |
67 | " <arg type=\"o\" name=\"object_path\"/>\n" \ | |
68 | " <arg type=\"as\" name=\"interfaces\"/>\n" \ | |
69 | " </signal>\n" \ | |
70 | " </interface>\n" | |
71 | ||
72 | int introspect_begin(BusIntrospect *i, bool trusted) { | |
73 | FILE *f; | |
74 | ||
75 | assert(i); | |
76 | ||
77 | *i = (BusIntrospect) { | |
78 | .trusted = trusted, | |
79 | }; | |
80 | ||
81 | f = memstream_init(&i->m); | |
82 | if (!f) | |
83 | return -ENOMEM; | |
84 | ||
85 | fputs(BUS_INTROSPECT_DOCTYPE | |
86 | "<node>\n", f); | |
87 | ||
88 | return 0; | |
89 | } | |
90 | ||
91 | int introspect_write_default_interfaces(BusIntrospect *i, bool object_manager) { | |
92 | assert(i); | |
93 | assert(i->m.f); | |
94 | ||
95 | fputs(BUS_INTROSPECT_INTERFACE_PEER | |
96 | BUS_INTROSPECT_INTERFACE_INTROSPECTABLE | |
97 | BUS_INTROSPECT_INTERFACE_PROPERTIES, i->m.f); | |
98 | ||
99 | if (object_manager) | |
100 | fputs(BUS_INTROSPECT_INTERFACE_OBJECT_MANAGER, i->m.f); | |
101 | ||
102 | return 0; | |
103 | } | |
104 | ||
105 | static int set_interface_name(BusIntrospect *i, const char *interface_name) { | |
106 | assert(i); | |
107 | assert(i->m.f); | |
108 | ||
109 | if (streq_ptr(i->interface_name, interface_name)) | |
110 | return 0; | |
111 | ||
112 | if (i->interface_name) | |
113 | fputs(" </interface>\n", i->m.f); | |
114 | ||
115 | if (interface_name) | |
116 | fprintf(i->m.f, " <interface name=\"%s\">\n", interface_name); | |
117 | ||
118 | return free_and_strdup(&i->interface_name, interface_name); | |
119 | } | |
120 | ||
121 | int introspect_write_child_nodes(BusIntrospect *i, OrderedSet *s, const char *prefix) { | |
122 | char *node; | |
123 | ||
124 | assert(i); | |
125 | assert(i->m.f); | |
126 | assert(prefix); | |
127 | ||
128 | assert_se(set_interface_name(i, NULL) >= 0); | |
129 | ||
130 | while ((node = ordered_set_steal_first(s))) { | |
131 | const char *e; | |
132 | ||
133 | e = object_path_startswith(node, prefix); | |
134 | if (e && e[0]) | |
135 | fprintf(i->m.f, " <node name=\"%s\"/>\n", e); | |
136 | ||
137 | free(node); | |
138 | } | |
139 | ||
140 | return 0; | |
141 | } | |
142 | ||
143 | static void introspect_write_flags(BusIntrospect *i, int type, uint64_t flags) { | |
144 | assert(i); | |
145 | assert(i->m.f); | |
146 | ||
147 | if (flags & SD_BUS_VTABLE_DEPRECATED) | |
148 | fputs(" <annotation name=\"org.freedesktop.DBus.Deprecated\" value=\"true\"/>\n", i->m.f); | |
149 | ||
150 | if (type == _SD_BUS_VTABLE_METHOD && (flags & SD_BUS_VTABLE_METHOD_NO_REPLY)) | |
151 | fputs(" <annotation name=\"org.freedesktop.DBus.Method.NoReply\" value=\"true\"/>\n", i->m.f); | |
152 | ||
153 | if (IN_SET(type, _SD_BUS_VTABLE_PROPERTY, _SD_BUS_VTABLE_WRITABLE_PROPERTY)) { | |
154 | if (flags & SD_BUS_VTABLE_PROPERTY_EXPLICIT) | |
155 | fputs(" <annotation name=\"org.freedesktop.systemd1.Explicit\" value=\"true\"/>\n", i->m.f); | |
156 | ||
157 | if (flags & SD_BUS_VTABLE_PROPERTY_CONST) | |
158 | fputs(" <annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"const\"/>\n", i->m.f); | |
159 | else if (flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION) | |
160 | fputs(" <annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"invalidates\"/>\n", i->m.f); | |
161 | else if (!(flags & SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE)) | |
162 | fputs(" <annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"false\"/>\n", i->m.f); | |
163 | } | |
164 | ||
165 | if (!i->trusted && | |
166 | IN_SET(type, _SD_BUS_VTABLE_METHOD, _SD_BUS_VTABLE_WRITABLE_PROPERTY) && | |
167 | !(flags & SD_BUS_VTABLE_UNPRIVILEGED)) | |
168 | fputs(" <annotation name=\"org.freedesktop.systemd1.Privileged\" value=\"true\"/>\n", i->m.f); | |
169 | } | |
170 | ||
171 | /* Note that "names" is both an input and an output parameter. It initially points to the first argument name in a | |
172 | NULL-separated list of strings, and is then advanced with each argument, and the resulting pointer is returned. */ | |
173 | static int introspect_write_arguments(BusIntrospect *i, const char *signature, const char **names, const char *direction) { | |
174 | int r; | |
175 | ||
176 | assert(i); | |
177 | assert(i->m.f); | |
178 | ||
179 | for (;;) { | |
180 | size_t l; | |
181 | ||
182 | if (!*signature) | |
183 | return 0; | |
184 | ||
185 | r = signature_element_length(signature, &l); | |
186 | if (r < 0) | |
187 | return r; | |
188 | ||
189 | fprintf(i->m.f, " <arg type=\"%.*s\"", (int) l, signature); | |
190 | ||
191 | if (**names != '\0') { | |
192 | fprintf(i->m.f, " name=\"%s\"", *names); | |
193 | *names += strlen(*names) + 1; | |
194 | } | |
195 | ||
196 | if (direction) | |
197 | fprintf(i->m.f, " direction=\"%s\"/>\n", direction); | |
198 | else | |
199 | fputs("/>\n", i->m.f); | |
200 | ||
201 | signature += l; | |
202 | } | |
203 | } | |
204 | ||
205 | int introspect_write_interface( | |
206 | BusIntrospect *i, | |
207 | const char *interface_name, | |
208 | const sd_bus_vtable *v) { | |
209 | ||
210 | const sd_bus_vtable *vtable = ASSERT_PTR(v); | |
211 | const char *names = ""; | |
212 | int r; | |
213 | ||
214 | assert(i); | |
215 | assert(i->m.f); | |
216 | assert(interface_name); | |
217 | ||
218 | r = set_interface_name(i, interface_name); | |
219 | if (r < 0) | |
220 | return r; | |
221 | ||
222 | for (; v->type != _SD_BUS_VTABLE_END; v = bus_vtable_next(vtable, v)) { | |
223 | ||
224 | /* Ignore methods, signals and properties that are | |
225 | * marked "hidden", but do show the interface | |
226 | * itself */ | |
227 | ||
228 | if (v->type != _SD_BUS_VTABLE_START && (v->flags & SD_BUS_VTABLE_HIDDEN)) | |
229 | continue; | |
230 | ||
231 | switch (v->type) { | |
232 | ||
233 | case _SD_BUS_VTABLE_START: | |
234 | if (v->flags & SD_BUS_VTABLE_DEPRECATED) | |
235 | fputs(" <annotation name=\"org.freedesktop.DBus.Deprecated\" value=\"true\"/>\n", i->m.f); | |
236 | break; | |
237 | ||
238 | case _SD_BUS_VTABLE_METHOD: | |
239 | fprintf(i->m.f, " <method name=\"%s\">\n", v->x.method.member); | |
240 | if (bus_vtable_has_names(vtable)) | |
241 | names = strempty(v->x.method.names); | |
242 | introspect_write_arguments(i, strempty(v->x.method.signature), &names, "in"); | |
243 | introspect_write_arguments(i, strempty(v->x.method.result), &names, "out"); | |
244 | introspect_write_flags(i, v->type, v->flags); | |
245 | fputs(" </method>\n", i->m.f); | |
246 | break; | |
247 | ||
248 | case _SD_BUS_VTABLE_PROPERTY: | |
249 | case _SD_BUS_VTABLE_WRITABLE_PROPERTY: | |
250 | fprintf(i->m.f, " <property name=\"%s\" type=\"%s\" access=\"%s\">\n", | |
251 | v->x.property.member, | |
252 | v->x.property.signature, | |
253 | v->type == _SD_BUS_VTABLE_WRITABLE_PROPERTY ? "readwrite" : "read"); | |
254 | introspect_write_flags(i, v->type, v->flags); | |
255 | fputs(" </property>\n", i->m.f); | |
256 | break; | |
257 | ||
258 | case _SD_BUS_VTABLE_SIGNAL: | |
259 | fprintf(i->m.f, " <signal name=\"%s\">\n", v->x.signal.member); | |
260 | if (bus_vtable_has_names(vtable)) | |
261 | names = strempty(v->x.signal.names); | |
262 | introspect_write_arguments(i, strempty(v->x.signal.signature), &names, NULL); | |
263 | introspect_write_flags(i, v->type, v->flags); | |
264 | fputs(" </signal>\n", i->m.f); | |
265 | break; | |
266 | } | |
267 | ||
268 | } | |
269 | ||
270 | return 0; | |
271 | } | |
272 | ||
273 | int introspect_finish(BusIntrospect *i, char **ret) { | |
274 | assert(i); | |
275 | assert(i->m.f); | |
276 | ||
277 | assert_se(set_interface_name(i, NULL) >= 0); | |
278 | ||
279 | fputs("</node>\n", i->m.f); | |
280 | ||
281 | return memstream_finalize(&i->m, ret, NULL); | |
282 | } | |
283 | ||
284 | void introspect_done(BusIntrospect *i) { | |
285 | assert(i); | |
286 | ||
287 | /* Normally introspect_finish() does all the work, this is just a backup for error paths */ | |
288 | ||
289 | memstream_done(&i->m); | |
290 | free(i->interface_name); | |
291 | } |