chiark / gitweb /
core: add minimal templating system
[elogind.git] / dbus-manager.c
1 /*-*- Mode: C; c-basic-offset: 8 -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2010 Lennart Poettering
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU General Public License as published by
10   the Free Software Foundation; either version 2 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   General Public License for more details.
17
18   You should have received a copy of the GNU General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <errno.h>
23
24 #include "dbus.h"
25 #include "log.h"
26
27 #define INTROSPECTION_BEGIN                                             \
28         DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                       \
29         "<node>"                                                        \
30         " <interface name=\"org.freedesktop.systemd1\">"                \
31         "  <method name=\"GetUnit\">"                                   \
32         "   <arg name=\"name\" type=\"s\" direction=\"in\"/>"           \
33         "   <arg name=\"unit\" type=\"o\" direction=\"out\"/>"          \
34         "  </method>"                                                   \
35         "  <method name=\"LoadUnit\">"                                  \
36         "   <arg name=\"name\" type=\"s\" direction=\"in\"/>"           \
37         "   <arg name=\"unit\" type=\"o\" direction=\"out\"/>"          \
38         "  </method>"                                                   \
39         "  <method name=\"GetJob\">"                                    \
40         "   <arg name=\"id\" type=\"u\" direction=\"in\"/>"             \
41         "   <arg name=\"job\" type=\"o\" direction=\"out\"/>"           \
42         "  </method>"                                                   \
43         "  <method name=\"ClearJobs\"/>"                                \
44         "  <method name=\"ListUnits\">"                                 \
45         "   <arg name=\"units\" type=\"a(sssssouso)\" direction=\"out\"/>" \
46         "  </method>"                                                   \
47         "  <method name=\"ListJobs\">"                                  \
48         "   <arg name=\"jobs\" type=\"a(usssoo)\" direction=\"out\"/>"  \
49         "  </method>"                                                   \
50         "  <method name=\"Subscribe\"/>"                                \
51         "  <method name=\"Unsubscribe\"/>"                              \
52         "  <method name=\"Dump\"/>"                                     \
53         "  <signal name=\"UnitNew\">"                                   \
54         "   <arg name=\"id\" type=\"s\"/>"                              \
55         "   <arg name=\"unit\" type=\"o\"/>"                            \
56         "  </signal>"                                                   \
57         "  <signal name=\"UnitRemoved\">"                               \
58         "   <arg name=\"id\" type=\"s\"/>"                              \
59         "   <arg name=\"unit\" type=\"o\"/>"                            \
60         "  </signal>"                                                   \
61         "  <signal name=\"JobNew\">"                                    \
62         "   <arg name=\"id\" type=\"u\"/>"                              \
63         "   <arg name=\"job\" type=\"o\"/>"                             \
64         "  </signal>"                                                   \
65         "  <signal name=\"JobRemoved\">"                                \
66         "   <arg name=\"id\" type=\"u\"/>"                              \
67         "   <arg name=\"job\" type=\"o\"/>"                             \
68         "  </signal>"                                                   \
69         "  <property name=\"Version\" type=\"s\" access=\"read\"/>"     \
70         "  <property name=\"RunningAs\" type=\"s\" access=\"read\"/>"   \
71         "  <property name=\"BootTimestamp\" type=\"t\" access=\"read\"/>" \
72         "  <property name=\"LogLevel\" type=\"s\" access=\"readwrite\"/>" \
73         "  <property name=\"LogTarget\" type=\"s\" access=\"readwrite\"/>" \
74         " </interface>"                                                 \
75         BUS_PROPERTIES_INTERFACE                                        \
76         BUS_INTROSPECTABLE_INTERFACE
77
78 #define INTROSPECTION_END                                               \
79         "</node>"
80
81 DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_manager_append_running_as, manager_running_as, ManagerRunningAs);
82
83 static int bus_manager_append_log_target(Manager *m, DBusMessageIter *i, const char *property, void *data) {
84         const char *t;
85
86         assert(m);
87         assert(i);
88         assert(property);
89
90         t = log_target_to_string(log_get_target());
91
92         if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &t))
93                 return -ENOMEM;
94
95         return 0;
96 }
97
98 static int bus_manager_append_log_level(Manager *m, DBusMessageIter *i, const char *property, void *data) {
99         const char *t;
100
101         assert(m);
102         assert(i);
103         assert(property);
104
105         t = log_level_to_string(log_get_max_level());
106
107         if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &t))
108                 return -ENOMEM;
109
110         return 0;
111 }
112
113 static DBusHandlerResult bus_manager_message_handler(DBusConnection  *connection, DBusMessage *message, void *data) {
114         Manager *m = data;
115
116         const BusProperty properties[] = {
117                 { "org.freedesktop.systemd1", "Version",       bus_property_append_string, "s",    PACKAGE_VERSION    },
118                 { "org.freedesktop.systemd1", "RunningAs",     bus_manager_append_running_as, "s", &m->running_as     },
119                 { "org.freedesktop.systemd1", "BootTimestamp", bus_property_append_uint64, "t",    &m->boot_timestamp },
120                 { "org.freedesktop.systemd1", "LogLevel",      bus_manager_append_log_level, "s",  NULL               },
121                 { "org.freedesktop.systemd1", "LogTarget",     bus_manager_append_log_target, "s", NULL               },
122                 { NULL, NULL, NULL, NULL, NULL }
123         };
124
125         int r;
126         DBusError error;
127         DBusMessage *reply = NULL;
128         char * path = NULL;
129
130         assert(connection);
131         assert(message);
132         assert(m);
133
134         dbus_error_init(&error);
135
136         log_debug("Got D-Bus request: %s.%s() on %s",
137                   dbus_message_get_interface(message),
138                   dbus_message_get_member(message),
139                   dbus_message_get_path(message));
140
141         if (dbus_message_is_method_call(message, "org.freedesktop.systemd1", "GetUnit")) {
142                 const char *name;
143                 Unit *u;
144
145                 if (!dbus_message_get_args(
146                                     message,
147                                     &error,
148                                     DBUS_TYPE_STRING, &name,
149                                     DBUS_TYPE_INVALID))
150                         return bus_send_error_reply(m, message, &error, -EINVAL);
151
152                 if (!(u = manager_get_unit(m, name)))
153                         return bus_send_error_reply(m, message, NULL, -ENOENT);
154
155                 if (!(reply = dbus_message_new_method_return(message)))
156                         goto oom;
157
158                 if (!(path = unit_dbus_path(u)))
159                         goto oom;
160
161                 if (!dbus_message_append_args(
162                                     reply,
163                                     DBUS_TYPE_OBJECT_PATH, &path,
164                                     DBUS_TYPE_INVALID))
165                         goto oom;
166
167         } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1", "LoadUnit")) {
168                 const char *name;
169                 Unit *u;
170
171                 if (!dbus_message_get_args(
172                                     message,
173                                     &error,
174                                     DBUS_TYPE_STRING, &name,
175                                     DBUS_TYPE_INVALID))
176                         return bus_send_error_reply(m, message, &error, -EINVAL);
177
178                 if ((r = manager_load_unit(m, name, NULL, &u)) < 0)
179                         return bus_send_error_reply(m, message, NULL, r);
180
181                 if (!(reply = dbus_message_new_method_return(message)))
182                         goto oom;
183
184                 if (!(path = unit_dbus_path(u)))
185                         goto oom;
186
187                 if (!dbus_message_append_args(
188                                     reply,
189                                     DBUS_TYPE_OBJECT_PATH, &path,
190                                     DBUS_TYPE_INVALID))
191                         goto oom;
192
193         } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1", "GetJob")) {
194                 uint32_t id;
195                 Job *j;
196
197                 if (!dbus_message_get_args(
198                                     message,
199                                     &error,
200                                     DBUS_TYPE_UINT32, &id,
201                                     DBUS_TYPE_INVALID))
202                         return bus_send_error_reply(m, message, &error, -EINVAL);
203
204                 if (!(j = manager_get_job(m, id)))
205                         return bus_send_error_reply(m, message, NULL, -ENOENT);
206
207                 if (!(reply = dbus_message_new_method_return(message)))
208                         goto oom;
209
210                 if (!(path = job_dbus_path(j)))
211                         goto oom;
212
213                 if (!dbus_message_append_args(
214                                     reply,
215                                     DBUS_TYPE_OBJECT_PATH, &path,
216                                     DBUS_TYPE_INVALID))
217                         goto oom;
218
219         } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1", "ClearJobs")) {
220
221                 manager_clear_jobs(m);
222
223                 if (!(reply = dbus_message_new_method_return(message)))
224                         goto oom;
225
226         } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1", "ListUnits")) {
227                 DBusMessageIter iter, sub;
228                 Iterator i;
229                 Unit *u;
230                 const char *k;
231
232                 if (!(reply = dbus_message_new_method_return(message)))
233                         goto oom;
234
235                 dbus_message_iter_init_append(reply, &iter);
236
237                 if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "(sssssouso)", &sub))
238                         goto oom;
239
240                 HASHMAP_FOREACH_KEY(u, k, m->units, i) {
241                         char *u_path, *j_path;
242                         const char *description, *load_state, *active_state, *sub_state, *job_type;
243                         DBusMessageIter sub2;
244                         uint32_t job_id;
245
246                         if (k != u->meta.id)
247                                 continue;
248
249                         if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2))
250                                 goto oom;
251
252                         description = unit_description(u);
253                         load_state = unit_load_state_to_string(u->meta.load_state);
254                         active_state = unit_active_state_to_string(unit_active_state(u));
255                         sub_state = unit_sub_state_to_string(u);
256
257                         if (!(u_path = unit_dbus_path(u)))
258                                 goto oom;
259
260                         if (u->meta.job) {
261                                 job_id = (uint32_t) u->meta.job->id;
262
263                                 if (!(j_path = job_dbus_path(u->meta.job))) {
264                                         free(u_path);
265                                         goto oom;
266                                 }
267
268                                 job_type = job_type_to_string(u->meta.job->type);
269                         } else {
270                                 job_id = 0;
271                                 j_path = u_path;
272                                 job_type = "";
273                         }
274
275                         if (!dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &u->meta.id) ||
276                             !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &description) ||
277                             !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &load_state) ||
278                             !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &active_state) ||
279                             !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &sub_state) ||
280                             !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_OBJECT_PATH, &u_path) ||
281                             !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_UINT32, &job_id) ||
282                             !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &job_type) ||
283                             !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_OBJECT_PATH, &j_path)) {
284                                 free(u_path);
285                                 if (u->meta.job)
286                                         free(j_path);
287                                 goto oom;
288                         }
289
290                         free(u_path);
291                         if (u->meta.job)
292                                 free(j_path);
293
294                         if (!dbus_message_iter_close_container(&sub, &sub2))
295                                 goto oom;
296                 }
297
298                 if (!dbus_message_iter_close_container(&iter, &sub))
299                         goto oom;
300
301         } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1", "ListJobs")) {
302                 DBusMessageIter iter, sub;
303                 Iterator i;
304                 Job *j;
305
306                 if (!(reply = dbus_message_new_method_return(message)))
307                         goto oom;
308
309                 dbus_message_iter_init_append(reply, &iter);
310
311                 if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "(usssoo)", &sub))
312                         goto oom;
313
314                 HASHMAP_FOREACH(j, m->jobs, i) {
315                         char *u_path, *j_path;
316                         const char *state, *type;
317                         uint32_t id;
318                         DBusMessageIter sub2;
319
320                         if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_STRUCT, NULL, &sub2))
321                                 goto oom;
322
323                         id = (uint32_t) j->id;
324                         state = job_state_to_string(j->state);
325                         type = job_type_to_string(j->type);
326
327                         if (!(j_path = job_dbus_path(j)))
328                                 goto oom;
329
330                         if (!(u_path = unit_dbus_path(j->unit))) {
331                                 free(j_path);
332                                 goto oom;
333                         }
334
335                         if (!dbus_message_iter_append_basic(&sub2, DBUS_TYPE_UINT32, &id) ||
336                             !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &j->unit->meta.id) ||
337                             !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &type) ||
338                             !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &state) ||
339                             !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_OBJECT_PATH, &j_path) ||
340                             !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_OBJECT_PATH, &u_path)) {
341                                 free(j_path);
342                                 free(u_path);
343                                 goto oom;
344                         }
345
346                         free(j_path);
347                         free(u_path);
348
349                         if (!dbus_message_iter_close_container(&sub, &sub2))
350                                 goto oom;
351                 }
352
353                 if (!dbus_message_iter_close_container(&iter, &sub))
354                         goto oom;
355
356         } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1", "Subscribe")) {
357                 char *client;
358
359                 if (!(client = strdup(dbus_message_get_sender(message))))
360                         goto oom;
361
362                 r = set_put(m->subscribed, client);
363
364                 if (r < 0)
365                         return bus_send_error_reply(m, message, NULL, r);
366
367                 if (!(reply = dbus_message_new_method_return(message)))
368                         goto oom;
369
370         } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1", "Unsubscribe")) {
371                 char *client;
372
373                 if (!(client = set_remove(m->subscribed, (char*) dbus_message_get_sender(message))))
374                         return bus_send_error_reply(m, message, NULL, -ENOENT);
375
376                 free(client);
377
378                 if (!(reply = dbus_message_new_method_return(message)))
379                         goto oom;
380
381         } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1", "Dump")) {
382                 FILE *f;
383                 char *dump = NULL;
384                 size_t size;
385
386                 if (!(reply = dbus_message_new_method_return(message)))
387                         goto oom;
388
389                 if (!(f = open_memstream(&dump, &size)))
390                         goto oom;
391
392                 manager_dump_units(m, f, NULL);
393                 manager_dump_jobs(m, f, NULL);
394
395                 if (ferror(f)) {
396                         fclose(f);
397                         free(dump);
398                         goto oom;
399                 }
400
401                 fclose(f);
402
403                 if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &dump, DBUS_TYPE_INVALID)) {
404                         free(dump);
405                         goto oom;
406                 }
407
408                 free(dump);
409
410         } else if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Introspectable", "Introspect")) {
411                 char *introspection = NULL;
412                 FILE *f;
413                 Iterator i;
414                 Unit *u;
415                 Job *j;
416                 const char *k;
417                 size_t size;
418
419                 if (!(reply = dbus_message_new_method_return(message)))
420                         goto oom;
421
422                 /* We roll our own introspection code here, instead of
423                  * relying on bus_default_message_handler() because we
424                  * need to generate our introspection string
425                  * dynamically. */
426
427                 if (!(f = open_memstream(&introspection, &size)))
428                         goto oom;
429
430                 fputs(INTROSPECTION_BEGIN, f);
431
432                 HASHMAP_FOREACH_KEY(u, k, m->units, i) {
433                         char *p;
434
435                         if (k != u->meta.id)
436                                 continue;
437
438                         if (!(p = bus_path_escape(k))) {
439                                 fclose(f);
440                                 free(introspection);
441                                 goto oom;
442                         }
443
444                         fprintf(f, "<node name=\"unit/%s\"/>", p);
445                         free(p);
446                 }
447
448                 HASHMAP_FOREACH(j, m->jobs, i)
449                         fprintf(f, "<node name=\"job/%lu\"/>", (unsigned long) j->id);
450
451                 fputs(INTROSPECTION_END, f);
452
453                 if (ferror(f)) {
454                         fclose(f);
455                         free(introspection);
456                         goto oom;
457                 }
458
459                 fclose(f);
460
461                 if (!introspection)
462                         goto oom;
463
464                 if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &introspection, DBUS_TYPE_INVALID)) {
465                         free(introspection);
466                         goto oom;
467                 }
468
469                 free(introspection);
470
471         } else
472                 return bus_default_message_handler(m, message, NULL, properties);
473
474         free(path);
475
476         if (reply) {
477                 if (!dbus_connection_send(connection, reply, NULL))
478                         goto oom;
479
480                 dbus_message_unref(reply);
481         }
482
483         return DBUS_HANDLER_RESULT_HANDLED;
484
485 oom:
486         free(path);
487
488         if (reply)
489                 dbus_message_unref(reply);
490
491         dbus_error_free(&error);
492
493         return DBUS_HANDLER_RESULT_NEED_MEMORY;
494 }
495
496 const DBusObjectPathVTable bus_manager_vtable = {
497         .message_function = bus_manager_message_handler
498 };