]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/core/dbus-job.c
transaction: cancel jobs non-recursively on isolate
[thirdparty/systemd.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 }