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