chiark / gitweb /
1b86e96624369016d05b122545c1fac3dfb3d539
[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);
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 }