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