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