chiark / gitweb /
Report about syntax errors with metadata
[elogind.git] / src / core / dbus-job.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
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 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 <errno.h>
23
24 #include "dbus.h"
25 #include "log.h"
26 #include "dbus-job.h"
27 #include "dbus-common.h"
28 #include "selinux-access.h"
29
30 #define BUS_JOB_INTERFACE                                             \
31         " <interface name=\"org.freedesktop.systemd1.Job\">\n"        \
32         "  <method name=\"Cancel\"/>\n"                               \
33         "  <property name=\"Id\" type=\"u\" access=\"read\"/>\n"      \
34         "  <property name=\"Unit\" type=\"(so)\" access=\"read\"/>\n" \
35         "  <property name=\"JobType\" type=\"s\" access=\"read\"/>\n" \
36         "  <property name=\"State\" type=\"s\" access=\"read\"/>\n"   \
37         " </interface>\n"
38
39 #define INTROSPECTION                                                 \
40         DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                     \
41         "<node>\n"                                                    \
42         BUS_JOB_INTERFACE                                             \
43         BUS_PROPERTIES_INTERFACE                                      \
44         BUS_PEER_INTERFACE                                            \
45         BUS_INTROSPECTABLE_INTERFACE                                  \
46         "</node>\n"
47
48 const char bus_job_interface[] _introspect_("Job") = BUS_JOB_INTERFACE;
49
50 #define INTERFACES_LIST                              \
51         BUS_GENERIC_INTERFACES_LIST                  \
52         "org.freedesktop.systemd1.Job\0"
53
54 #define INVALIDATING_PROPERTIES                 \
55         "State\0"
56
57 static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_job_append_state, job_state, JobState);
58 static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_job_append_type, job_type, JobType);
59
60 static int bus_job_append_unit(DBusMessageIter *i, const char *property, void *data) {
61         Job *j = data;
62         DBusMessageIter sub;
63         char *p;
64
65         assert(i);
66         assert(property);
67         assert(j);
68
69         if (!dbus_message_iter_open_container(i, DBUS_TYPE_STRUCT, NULL, &sub))
70                 return -ENOMEM;
71
72         p = unit_dbus_path(j->unit);
73         if (!p)
74                 return -ENOMEM;
75
76         if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &j->unit->id) ||
77             !dbus_message_iter_append_basic(&sub, DBUS_TYPE_OBJECT_PATH, &p)) {
78                 free(p);
79                 return -ENOMEM;
80         }
81
82         free(p);
83
84         if (!dbus_message_iter_close_container(i, &sub))
85                 return -ENOMEM;
86
87         return 0;
88 }
89
90 static const BusProperty bus_job_properties[] = {
91         { "Id",      bus_property_append_uint32, "u", offsetof(Job, id)    },
92         { "State",   bus_job_append_state,       "s", offsetof(Job, state) },
93         { "JobType", bus_job_append_type,        "s", offsetof(Job, type)  },
94         { "Unit",    bus_job_append_unit,     "(so)", 0 },
95         { NULL, }
96 };
97
98 static DBusHandlerResult bus_job_message_dispatch(Job *j, DBusConnection *connection, DBusMessage *message) {
99         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
100
101         if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Job", "Cancel")) {
102
103                 SELINUX_UNIT_ACCESS_CHECK(j->unit, connection, message, "stop");
104                 job_finish_and_invalidate(j, JOB_CANCELED, true);
105
106                 reply = dbus_message_new_method_return(message);
107                 if (!reply)
108                         return DBUS_HANDLER_RESULT_NEED_MEMORY;
109         } else {
110                 const BusBoundProperties bps[] = {
111                         { "org.freedesktop.systemd1.Job", bus_job_properties, j },
112                         { NULL, }
113                 };
114
115                 SELINUX_UNIT_ACCESS_CHECK(j->unit, connection, message, "status");
116                 return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps);
117         }
118
119         if (!bus_maybe_send_reply(connection, message, reply))
120                 return DBUS_HANDLER_RESULT_NEED_MEMORY;
121
122         return DBUS_HANDLER_RESULT_HANDLED;
123 }
124
125 static DBusHandlerResult bus_job_message_handler(DBusConnection *connection, DBusMessage  *message, void *data) {
126         Manager *m = data;
127         Job *j;
128         int r;
129         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
130
131         assert(connection);
132         assert(message);
133         assert(m);
134
135         if (streq(dbus_message_get_path(message), "/org/freedesktop/systemd1/job")) {
136                 /* Be nice to gdbus and return introspection data for our mid-level paths */
137
138                 if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Introspectable", "Introspect")) {
139                         char *introspection = NULL;
140                         FILE *f;
141                         Iterator i;
142                         size_t size;
143
144                         SELINUX_ACCESS_CHECK(connection, message, "status");
145
146                         reply = dbus_message_new_method_return(message);
147                         if (!reply)
148                                 goto oom;
149
150                         /* We roll our own introspection code here, instead of
151                          * relying on bus_default_message_handler() because we
152                          * need to generate our introspection string
153                          * dynamically. */
154
155                         f = open_memstream(&introspection, &size);
156                         if (!f)
157                                 goto oom;
158
159                         fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
160                               "<node>\n", f);
161
162                         fputs(BUS_INTROSPECTABLE_INTERFACE, f);
163                         fputs(BUS_PEER_INTERFACE, f);
164
165                         HASHMAP_FOREACH(j, m->jobs, i)
166                                 fprintf(f, "<node name=\"job/%lu\"/>", (unsigned long) j->id);
167
168                         fputs("</node>\n", f);
169
170                         if (ferror(f)) {
171                                 fclose(f);
172                                 free(introspection);
173                                 goto oom;
174                         }
175
176                         fclose(f);
177
178                         if (!introspection)
179                                 goto oom;
180
181                         if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &introspection, DBUS_TYPE_INVALID)) {
182                                 free(introspection);
183                                 goto oom;
184                         }
185
186                         free(introspection);
187
188                         if (!bus_maybe_send_reply(connection, message, reply))
189                                 goto oom;
190
191                         return DBUS_HANDLER_RESULT_HANDLED;
192                 }
193
194                 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
195         }
196
197         r = manager_get_job_from_dbus_path(m, dbus_message_get_path(message), &j);
198         if (r == -ENOMEM)
199                 goto oom;
200         if (r == -ENOENT) {
201                 DBusError e;
202
203                 dbus_error_init(&e);
204                 dbus_set_error_const(&e, DBUS_ERROR_UNKNOWN_OBJECT, "Unknown job");
205                 return bus_send_error_reply(connection, message, &e, r);
206         }
207         if (r < 0)
208                 return bus_send_error_reply(connection, message, NULL, r);
209
210         return bus_job_message_dispatch(j, connection, message);
211
212 oom:
213         return DBUS_HANDLER_RESULT_NEED_MEMORY;
214 }
215
216 const DBusObjectPathVTable bus_job_vtable = {
217         .message_function = bus_job_message_handler
218 };
219
220 static int job_send_message(Job *j, DBusMessage* (*new_message)(Job *j)) {
221         DBusMessage *m = NULL;
222         int r;
223
224         assert(j);
225         assert(new_message);
226
227         if (bus_has_subscriber(j->manager) || j->forgot_bus_clients) {
228                 m = new_message(j);
229                 if (!m)
230                         goto oom;
231                 r = bus_broadcast(j->manager, m);
232                 dbus_message_unref(m);
233                 if (r < 0)
234                         return r;
235
236         } else {
237                 /* If nobody is subscribed, we just send the message
238                  * to the client(s) which created the job */
239                 JobBusClient *cl;
240                 assert(j->bus_client_list);
241                 LIST_FOREACH(client, cl, j->bus_client_list) {
242                         assert(cl->bus);
243
244                         m = new_message(j);
245                         if (!m)
246                                 goto oom;
247
248                         if (!dbus_message_set_destination(m, cl->name))
249                                 goto oom;
250
251                         if (!dbus_connection_send(cl->bus, m, NULL))
252                                 goto oom;
253
254                         dbus_message_unref(m);
255                         m = NULL;
256                 }
257         }
258
259         return 0;
260 oom:
261         if (m)
262                 dbus_message_unref(m);
263         return -ENOMEM;
264 }
265
266 static DBusMessage* new_change_signal_message(Job *j) {
267         DBusMessage *m = NULL;
268         char *p = NULL;
269
270         p = job_dbus_path(j);
271         if (!p)
272                 goto oom;
273
274         if (j->sent_dbus_new_signal) {
275                 /* Send a properties changed signal */
276                 m = bus_properties_changed_new(p, "org.freedesktop.systemd1.Job", INVALIDATING_PROPERTIES);
277                 if (!m)
278                         goto oom;
279
280         } else {
281                 /* Send a new signal */
282
283                 m = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "JobNew");
284                 if (!m)
285                         goto oom;
286
287                 if (!dbus_message_append_args(m,
288                                               DBUS_TYPE_UINT32, &j->id,
289                                               DBUS_TYPE_OBJECT_PATH, &p,
290                                               DBUS_TYPE_STRING, &j->unit->id,
291                                               DBUS_TYPE_INVALID))
292                         goto oom;
293         }
294
295         return m;
296
297 oom:
298         if (m)
299                 dbus_message_unref(m);
300         free(p);
301         return NULL;
302 }
303
304 static DBusMessage* new_removed_signal_message(Job *j) {
305         DBusMessage *m = NULL;
306         char *p = NULL;
307         const char *r;
308
309         p = job_dbus_path(j);
310         if (!p)
311                 goto oom;
312
313         m = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "JobRemoved");
314         if (!m)
315                 goto oom;
316
317         r = job_result_to_string(j->result);
318
319         if (!dbus_message_append_args(m,
320                                       DBUS_TYPE_UINT32, &j->id,
321                                       DBUS_TYPE_OBJECT_PATH, &p,
322                                       DBUS_TYPE_STRING, &j->unit->id,
323                                       DBUS_TYPE_STRING, &r,
324                                       DBUS_TYPE_INVALID))
325                 goto oom;
326
327         return m;
328
329 oom:
330         if (m)
331                 dbus_message_unref(m);
332         free(p);
333         return NULL;
334 }
335
336 void bus_job_send_change_signal(Job *j) {
337         assert(j);
338
339         if (j->in_dbus_queue) {
340                 LIST_REMOVE(Job, dbus_queue, j->manager->dbus_job_queue, j);
341                 j->in_dbus_queue = false;
342         }
343
344         if (!bus_has_subscriber(j->manager) && !j->bus_client_list && !j->forgot_bus_clients) {
345                 j->sent_dbus_new_signal = true;
346                 return;
347         }
348
349         if (job_send_message(j, new_change_signal_message) < 0)
350                 goto oom;
351
352         j->sent_dbus_new_signal = true;
353
354         return;
355
356 oom:
357         log_error("Failed to allocate job change signal.");
358 }
359
360 void bus_job_send_removed_signal(Job *j) {
361         assert(j);
362
363         if (!bus_has_subscriber(j->manager) && !j->bus_client_list && !j->forgot_bus_clients)
364                 return;
365
366         if (!j->sent_dbus_new_signal)
367                 bus_job_send_change_signal(j);
368
369         if (job_send_message(j, new_removed_signal_message) < 0)
370                 goto oom;
371
372         return;
373
374 oom:
375         log_error("Failed to allocate job remove signal.");
376 }