]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/core/dbus-job.c
dbus: fix minor memory leak when sending job change signals
[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 #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 job_finish_and_invalidate(j, JOB_CANCELED, true);
105
106 reply = dbus_message_new_method_return(message);
107 if (!reply)
108 return DBUS_HANDLER_RESULT_NEED_MEMORY;
109 } else {
110 const BusBoundProperties bps[] = {
111 { "org.freedesktop.systemd1.Job", bus_job_properties, j },
112 { NULL, }
113 };
114
115 SELINUX_UNIT_ACCESS_CHECK(j->unit, connection, message, "status");
116 return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps);
117 }
118
119 if (!bus_maybe_send_reply(connection, message, reply))
120 return DBUS_HANDLER_RESULT_NEED_MEMORY;
121
122 return DBUS_HANDLER_RESULT_HANDLED;
123 }
124
125 static DBusHandlerResult bus_job_message_handler(DBusConnection *connection, DBusMessage *message, void *data) {
126 Manager *m = data;
127 Job *j;
128 int r;
129 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
130
131 assert(connection);
132 assert(message);
133 assert(m);
134
135 if (streq(dbus_message_get_path(message), "/org/freedesktop/systemd1/job")) {
136 /* Be nice to gdbus and return introspection data for our mid-level paths */
137
138 if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Introspectable", "Introspect")) {
139 char *introspection = NULL;
140 FILE *f;
141 Iterator i;
142 size_t size;
143
144 SELINUX_ACCESS_CHECK(connection, message, "status");
145
146 reply = dbus_message_new_method_return(message);
147 if (!reply)
148 goto oom;
149
150 /* We roll our own introspection code here, instead of
151 * relying on bus_default_message_handler() because we
152 * need to generate our introspection string
153 * dynamically. */
154
155 f = open_memstream(&introspection, &size);
156 if (!f)
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 (!bus_maybe_send_reply(connection, message, reply))
189 goto oom;
190
191 return DBUS_HANDLER_RESULT_HANDLED;
192 }
193
194 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
195 }
196
197 r = manager_get_job_from_dbus_path(m, dbus_message_get_path(message), &j);
198 if (r == -ENOMEM)
199 goto oom;
200 if (r == -ENOENT) {
201 DBusError e;
202
203 dbus_error_init(&e);
204 dbus_set_error_const(&e, DBUS_ERROR_UNKNOWN_OBJECT, "Unknown job");
205 return bus_send_error_reply(connection, message, &e, r);
206 }
207 if (r < 0)
208 return bus_send_error_reply(connection, message, NULL, r);
209
210 return bus_job_message_dispatch(j, connection, message);
211
212 oom:
213 return DBUS_HANDLER_RESULT_NEED_MEMORY;
214 }
215
216 const DBusObjectPathVTable bus_job_vtable = {
217 .message_function = bus_job_message_handler
218 };
219
220 static int job_send_message(Job *j, DBusMessage* (*new_message)(Job *j)) {
221 _cleanup_dbus_message_unref_ DBusMessage *m = NULL;
222 int r;
223
224 assert(j);
225 assert(new_message);
226
227 if (bus_has_subscriber(j->manager) || j->forgot_bus_clients) {
228 m = new_message(j);
229 if (!m)
230 return -ENOMEM;
231
232 r = bus_broadcast(j->manager, m);
233 if (r < 0)
234 return r;
235
236 } else {
237 /* If nobody is subscribed, we just send the message
238 * to the client(s) which created the job */
239 JobBusClient *cl;
240 assert(j->bus_client_list);
241
242 LIST_FOREACH(client, cl, j->bus_client_list) {
243 assert(cl->bus);
244
245 m = new_message(j);
246 if (!m)
247 return -ENOMEM;
248
249 if (!dbus_message_set_destination(m, cl->name))
250 return -ENOMEM;
251
252 if (!dbus_connection_send(cl->bus, m, NULL))
253 return -ENOMEM;
254
255 dbus_message_unref(m);
256 m = NULL;
257 }
258 }
259
260 return 0;
261 }
262
263 static DBusMessage* new_change_signal_message(Job *j) {
264 _cleanup_free_ char *p = NULL;
265 DBusMessage *m;
266
267 p = job_dbus_path(j);
268 if (!p)
269 return NULL;
270
271 if (j->sent_dbus_new_signal) {
272 /* Send a properties changed signal */
273 m = bus_properties_changed_new(p, "org.freedesktop.systemd1.Job", INVALIDATING_PROPERTIES);
274 if (!m)
275 return NULL;
276
277 } else {
278 /* Send a new signal */
279
280 m = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "JobNew");
281 if (!m)
282 return NULL;
283
284 if (!dbus_message_append_args(m,
285 DBUS_TYPE_UINT32, &j->id,
286 DBUS_TYPE_OBJECT_PATH, &p,
287 DBUS_TYPE_STRING, &j->unit->id,
288 DBUS_TYPE_INVALID)) {
289 dbus_message_unref(m);
290 return NULL;
291 }
292 }
293
294 return m;
295 }
296
297 static DBusMessage* new_removed_signal_message(Job *j) {
298 _cleanup_free_ char *p = NULL;
299 DBusMessage *m;
300 const char *r;
301
302 p = job_dbus_path(j);
303 if (!p)
304 return NULL;
305
306 m = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "JobRemoved");
307 if (!m)
308 return NULL;
309
310 r = job_result_to_string(j->result);
311
312 if (!dbus_message_append_args(m,
313 DBUS_TYPE_UINT32, &j->id,
314 DBUS_TYPE_OBJECT_PATH, &p,
315 DBUS_TYPE_STRING, &j->unit->id,
316 DBUS_TYPE_STRING, &r,
317 DBUS_TYPE_INVALID)) {
318 dbus_message_unref(m);
319 return NULL;
320 }
321
322 return m;
323 }
324
325 void bus_job_send_change_signal(Job *j) {
326 assert(j);
327
328 if (j->in_dbus_queue) {
329 LIST_REMOVE(Job, dbus_queue, j->manager->dbus_job_queue, j);
330 j->in_dbus_queue = false;
331 }
332
333 if (!bus_has_subscriber(j->manager) && !j->bus_client_list && !j->forgot_bus_clients) {
334 j->sent_dbus_new_signal = true;
335 return;
336 }
337
338 if (job_send_message(j, new_change_signal_message) < 0)
339 goto oom;
340
341 j->sent_dbus_new_signal = true;
342
343 return;
344
345 oom:
346 log_error("Failed to allocate job change signal.");
347 }
348
349 void bus_job_send_removed_signal(Job *j) {
350 assert(j);
351
352 if (!bus_has_subscriber(j->manager) && !j->bus_client_list && !j->forgot_bus_clients)
353 return;
354
355 if (!j->sent_dbus_new_signal)
356 bus_job_send_change_signal(j);
357
358 if (job_send_message(j, new_removed_signal_message) < 0)
359 goto oom;
360
361 return;
362
363 oom:
364 log_error("Failed to allocate job remove signal.");
365 }