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