chiark / gitweb /
service: add options RestartPreventExitStatus and SuccessExitStatus
[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_STRING, &j->unit->id,
299                                               DBUS_TYPE_INVALID))
300                         goto oom;
301         }
302
303         return m;
304
305 oom:
306         if (m)
307                 dbus_message_unref(m);
308         free(p);
309         return NULL;
310 }
311
312 static DBusMessage* new_removed_signal_message(Job *j) {
313         DBusMessage *m = NULL;
314         char *p = NULL;
315         const char *r;
316
317         p = job_dbus_path(j);
318         if (!p)
319                 goto oom;
320
321         m = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "JobRemoved");
322         if (!m)
323                 goto oom;
324
325         r = job_result_to_string(j->result);
326
327         if (!dbus_message_append_args(m,
328                                       DBUS_TYPE_UINT32, &j->id,
329                                       DBUS_TYPE_OBJECT_PATH, &p,
330                                       DBUS_TYPE_STRING, &j->unit->id,
331                                       DBUS_TYPE_STRING, &r,
332                                       DBUS_TYPE_INVALID))
333                 goto oom;
334
335         return m;
336
337 oom:
338         if (m)
339                 dbus_message_unref(m);
340         free(p);
341         return NULL;
342 }
343
344 void bus_job_send_change_signal(Job *j) {
345         assert(j);
346
347         if (j->in_dbus_queue) {
348                 LIST_REMOVE(Job, dbus_queue, j->manager->dbus_job_queue, j);
349                 j->in_dbus_queue = false;
350         }
351
352         if (!bus_has_subscriber(j->manager) && !j->bus_client_list && !j->forgot_bus_clients) {
353                 j->sent_dbus_new_signal = true;
354                 return;
355         }
356
357         if (job_send_message(j, new_change_signal_message) < 0)
358                 goto oom;
359
360         j->sent_dbus_new_signal = true;
361
362         return;
363
364 oom:
365         log_error("Failed to allocate job change signal.");
366 }
367
368 void bus_job_send_removed_signal(Job *j) {
369         assert(j);
370
371         if (!bus_has_subscriber(j->manager) && !j->bus_client_list && !j->forgot_bus_clients)
372                 return;
373
374         if (!j->sent_dbus_new_signal)
375                 bus_job_send_change_signal(j);
376
377         if (job_send_message(j, new_removed_signal_message) < 0)
378                 goto oom;
379
380         return;
381
382 oom:
383         log_error("Failed to allocate job remove signal.");
384 }