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