]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/core/dbus-job.c
relicense to LGPLv2.1 (with exceptions)
[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);
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 *m) {
229 int r;
230
231 assert(j);
232 assert(m);
233
234 if (bus_has_subscriber(j->manager)) {
235 if ((r = bus_broadcast(j->manager, m)) < 0)
236 return r;
237
238 } else if (j->bus_client) {
239 /* If nobody is subscribed, we just send the message
240 * to the client which created the job */
241
242 assert(j->bus);
243
244 if (!dbus_message_set_destination(m, j->bus_client))
245 return -ENOMEM;
246
247 if (!dbus_connection_send(j->bus, m, NULL))
248 return -ENOMEM;
249 }
250
251 return 0;
252 }
253
254 void bus_job_send_change_signal(Job *j) {
255 char *p = NULL;
256 DBusMessage *m = NULL;
257
258 assert(j);
259
260 if (j->in_dbus_queue) {
261 LIST_REMOVE(Job, dbus_queue, j->manager->dbus_job_queue, j);
262 j->in_dbus_queue = false;
263 }
264
265 if (!bus_has_subscriber(j->manager) && !j->bus_client) {
266 j->sent_dbus_new_signal = true;
267 return;
268 }
269
270 if (!(p = job_dbus_path(j)))
271 goto oom;
272
273 if (j->sent_dbus_new_signal) {
274 /* Send a properties changed signal */
275
276 if (!(m = bus_properties_changed_new(p, "org.freedesktop.systemd1.Job", INVALIDATING_PROPERTIES)))
277 goto oom;
278
279 } else {
280 /* Send a new signal */
281
282 if (!(m = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "JobNew")))
283 goto oom;
284
285 if (!dbus_message_append_args(m,
286 DBUS_TYPE_UINT32, &j->id,
287 DBUS_TYPE_OBJECT_PATH, &p,
288 DBUS_TYPE_INVALID))
289 goto oom;
290 }
291
292 if (job_send_message(j, m) < 0)
293 goto oom;
294
295 free(p);
296 dbus_message_unref(m);
297
298 j->sent_dbus_new_signal = true;
299
300 return;
301
302 oom:
303 free(p);
304
305 if (m)
306 dbus_message_unref(m);
307
308 log_error("Failed to allocate job change signal.");
309 }
310
311 void bus_job_send_removed_signal(Job *j) {
312 char *p = NULL;
313 DBusMessage *m = NULL;
314 const char *r;
315
316 assert(j);
317
318 if (!bus_has_subscriber(j->manager) && !j->bus_client)
319 return;
320
321 if (!j->sent_dbus_new_signal)
322 bus_job_send_change_signal(j);
323
324 if (!(p = job_dbus_path(j)))
325 goto oom;
326
327 if (!(m = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "JobRemoved")))
328 goto oom;
329
330 r = job_result_to_string(j->result);
331
332 if (!dbus_message_append_args(m,
333 DBUS_TYPE_UINT32, &j->id,
334 DBUS_TYPE_OBJECT_PATH, &p,
335 DBUS_TYPE_STRING, &r,
336 DBUS_TYPE_INVALID))
337 goto oom;
338
339 if (job_send_message(j, m) < 0)
340 goto oom;
341
342 free(p);
343 dbus_message_unref(m);
344
345 return;
346
347 oom:
348 free(p);
349
350 if (m)
351 dbus_message_unref(m);
352
353 log_error("Failed to allocate job remove signal.");
354 }