]>
Commit | Line | Data |
---|---|---|
ea430986 LP |
1 | /*-*- Mode: C; c-basic-offset: 8 -*-*/ |
2 | ||
3 | #include <dbus/dbus.h> | |
4 | ||
5 | #include <sys/epoll.h> | |
6 | #include <sys/timerfd.h> | |
7 | #include <errno.h> | |
8 | #include <unistd.h> | |
9 | ||
10 | #include "dbus.h" | |
11 | #include "log.h" | |
12 | #include "strv.h" | |
13 | ||
14 | static void bus_dispatch_status(DBusConnection *bus, DBusDispatchStatus status, void *data) { | |
15 | Manager *m = data; | |
16 | ||
17 | assert(bus); | |
18 | assert(m); | |
19 | ||
20 | m->request_bus_dispatch = status != DBUS_DISPATCH_COMPLETE; | |
21 | } | |
22 | ||
23 | static uint32_t bus_flags_to_events(DBusWatch *bus_watch) { | |
24 | unsigned flags; | |
25 | uint32_t events = 0; | |
26 | ||
27 | assert(bus_watch); | |
28 | ||
29 | /* no watch flags for disabled watches */ | |
30 | if (!dbus_watch_get_enabled(bus_watch)) | |
31 | return 0; | |
32 | ||
33 | flags = dbus_watch_get_flags(bus_watch); | |
34 | ||
35 | if (flags & DBUS_WATCH_READABLE) | |
36 | events |= EPOLLIN; | |
37 | if (flags & DBUS_WATCH_WRITABLE) | |
38 | events |= EPOLLOUT; | |
39 | ||
40 | return events | EPOLLHUP | EPOLLERR; | |
41 | } | |
42 | ||
43 | static unsigned events_to_bus_flags(uint32_t events) { | |
44 | unsigned flags = 0; | |
45 | ||
46 | if (events & EPOLLIN) | |
47 | flags |= DBUS_WATCH_READABLE; | |
48 | if (events & EPOLLOUT) | |
49 | flags |= DBUS_WATCH_WRITABLE; | |
50 | if (events & EPOLLHUP) | |
51 | flags |= DBUS_WATCH_HANGUP; | |
52 | if (events & EPOLLERR) | |
53 | flags |= DBUS_WATCH_ERROR; | |
54 | ||
55 | return flags; | |
56 | } | |
57 | ||
58 | void bus_watch_event(Manager *m, Watch *w, int events) { | |
59 | assert(m); | |
60 | assert(w); | |
61 | ||
62 | /* This is called by the event loop whenever there is | |
63 | * something happening on D-Bus' file handles. */ | |
64 | ||
65 | if (!(dbus_watch_get_enabled(w->data.bus_watch))) | |
66 | return; | |
67 | ||
68 | dbus_watch_handle(w->data.bus_watch, events_to_bus_flags(events)); | |
69 | } | |
70 | ||
71 | static dbus_bool_t bus_add_watch(DBusWatch *bus_watch, void *data) { | |
72 | Manager *m = data; | |
73 | Watch *w; | |
74 | struct epoll_event ev; | |
75 | ||
76 | assert(bus_watch); | |
77 | assert(m); | |
78 | ||
79 | if (!(w = new0(Watch, 1))) | |
80 | return FALSE; | |
81 | ||
82 | w->fd = dbus_watch_get_unix_fd(bus_watch); | |
83 | w->type = WATCH_DBUS_WATCH; | |
84 | w->data.bus_watch = bus_watch; | |
85 | ||
86 | zero(ev); | |
87 | ev.events = bus_flags_to_events(bus_watch); | |
88 | ev.data.ptr = w; | |
89 | ||
90 | if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, w->fd, &ev) < 0) { | |
91 | ||
92 | if (errno != EEXIST) { | |
93 | free(w); | |
94 | return FALSE; | |
95 | } | |
96 | ||
97 | /* Hmm, bloody D-Bus creates multiple watches on the | |
98 | * same fd. epoll() does not like that. As a dirty | |
99 | * hack we simply dup() the fd and hence get a second | |
100 | * one we can safely add to the epoll(). */ | |
101 | ||
102 | if ((w->fd = dup(w->fd)) < 0) { | |
103 | free(w); | |
104 | return FALSE; | |
105 | } | |
106 | ||
107 | if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, w->fd, &ev) < 0) { | |
108 | free(w); | |
109 | close_nointr_nofail(w->fd); | |
110 | return FALSE; | |
111 | } | |
112 | ||
113 | w->fd_is_dupped = true; | |
114 | } | |
115 | ||
116 | dbus_watch_set_data(bus_watch, w, NULL); | |
117 | ||
118 | return TRUE; | |
119 | } | |
120 | ||
121 | static void bus_remove_watch(DBusWatch *bus_watch, void *data) { | |
122 | Manager *m = data; | |
123 | Watch *w; | |
124 | ||
125 | assert(bus_watch); | |
126 | assert(m); | |
127 | ||
128 | if (!(w = dbus_watch_get_data(bus_watch))) | |
129 | return; | |
130 | ||
131 | assert(w->type == WATCH_DBUS_WATCH); | |
132 | assert_se(epoll_ctl(m->epoll_fd, EPOLL_CTL_DEL, w->fd, NULL) >= 0); | |
133 | ||
134 | if (w->fd_is_dupped) | |
135 | close_nointr_nofail(w->fd); | |
136 | ||
137 | free(w); | |
138 | } | |
139 | ||
140 | static void bus_toggle_watch(DBusWatch *bus_watch, void *data) { | |
141 | Manager *m = data; | |
142 | Watch *w; | |
143 | struct epoll_event ev; | |
144 | ||
145 | assert(bus_watch); | |
146 | assert(m); | |
147 | ||
148 | assert_se(w = dbus_watch_get_data(bus_watch)); | |
149 | assert(w->type == WATCH_DBUS_WATCH); | |
150 | ||
151 | zero(ev); | |
152 | ev.events = bus_flags_to_events(bus_watch); | |
153 | ev.data.ptr = w; | |
154 | ||
155 | assert_se(epoll_ctl(m->epoll_fd, EPOLL_CTL_MOD, w->fd, &ev) == 0); | |
156 | } | |
157 | ||
158 | static int bus_timeout_arm(Manager *m, Watch *w) { | |
159 | struct itimerspec its; | |
160 | ||
161 | assert(m); | |
162 | assert(w); | |
163 | ||
164 | zero(its); | |
165 | ||
166 | if (dbus_timeout_get_enabled(w->data.bus_timeout)) { | |
167 | timespec_store(&its.it_value, dbus_timeout_get_interval(w->data.bus_timeout) * USEC_PER_MSEC); | |
168 | its.it_interval = its.it_interval; | |
169 | } | |
170 | ||
171 | if (timerfd_settime(w->fd, 0, &its, NULL) < 0) | |
172 | return -errno; | |
173 | ||
174 | return 0; | |
175 | } | |
176 | ||
177 | void bus_timeout_event(Manager *m, Watch *w, int events) { | |
178 | assert(m); | |
179 | assert(w); | |
180 | ||
181 | /* This is called by the event loop whenever there is | |
182 | * something happening on D-Bus' file handles. */ | |
183 | ||
184 | if (!(dbus_timeout_get_enabled(w->data.bus_timeout))) | |
185 | return; | |
186 | ||
187 | dbus_timeout_handle(w->data.bus_timeout); | |
188 | } | |
189 | ||
190 | static dbus_bool_t bus_add_timeout(DBusTimeout *timeout, void *data) { | |
191 | Manager *m = data; | |
192 | Watch *w; | |
193 | struct epoll_event ev; | |
194 | ||
195 | assert(timeout); | |
196 | assert(m); | |
197 | ||
198 | if (!(w = new0(Watch, 1))) | |
199 | return FALSE; | |
200 | ||
201 | if (!(w->fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK|TFD_CLOEXEC)) < 0) | |
202 | goto fail; | |
203 | ||
204 | w->type = WATCH_DBUS_TIMEOUT; | |
205 | w->data.bus_timeout = timeout; | |
206 | ||
207 | if (bus_timeout_arm(m, w) < 0) | |
208 | goto fail; | |
209 | ||
210 | zero(ev); | |
211 | ev.events = EPOLLIN; | |
212 | ev.data.ptr = w; | |
213 | ||
214 | if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, w->fd, &ev) < 0) | |
215 | goto fail; | |
216 | ||
217 | dbus_timeout_set_data(timeout, w, NULL); | |
218 | ||
219 | return TRUE; | |
220 | ||
221 | fail: | |
222 | if (w->fd >= 0) | |
223 | close_nointr_nofail(w->fd); | |
224 | ||
225 | free(w); | |
226 | return FALSE; | |
227 | } | |
228 | ||
229 | static void bus_remove_timeout(DBusTimeout *timeout, void *data) { | |
230 | Manager *m = data; | |
231 | Watch *w; | |
232 | ||
233 | assert(timeout); | |
234 | assert(m); | |
235 | ||
236 | if (!(w = dbus_timeout_get_data(timeout))) | |
237 | return; | |
238 | ||
239 | assert(w->type == WATCH_DBUS_TIMEOUT); | |
240 | assert_se(epoll_ctl(m->epoll_fd, EPOLL_CTL_DEL, w->fd, NULL) >= 0); | |
241 | close_nointr_nofail(w->fd); | |
242 | free(w); | |
243 | } | |
244 | ||
245 | static void bus_toggle_timeout(DBusTimeout *timeout, void *data) { | |
246 | Manager *m = data; | |
247 | Watch *w; | |
248 | int r; | |
249 | ||
250 | assert(timeout); | |
251 | assert(m); | |
252 | ||
253 | assert_se(w = dbus_timeout_get_data(timeout)); | |
254 | assert(w->type == WATCH_DBUS_TIMEOUT); | |
255 | ||
256 | if ((r = bus_timeout_arm(m, w)) < 0) | |
257 | log_error("Failed to rearm timer: %s", strerror(-r)); | |
258 | } | |
259 | ||
260 | void bus_dispatch(Manager *m) { | |
261 | assert(m); | |
262 | ||
263 | if (dbus_connection_dispatch(m->bus) == DBUS_DISPATCH_COMPLETE) | |
264 | m->request_bus_dispatch = false; | |
265 | } | |
266 | ||
267 | static int request_name(Manager *m) { | |
268 | DBusMessage *message; | |
269 | const char *name = "org.freedesktop.systemd1"; | |
270 | uint32_t flags = 0; | |
271 | ||
272 | if (!(message = dbus_message_new_method_call( | |
273 | DBUS_SERVICE_DBUS, | |
274 | DBUS_PATH_DBUS, | |
275 | DBUS_INTERFACE_DBUS, | |
276 | "RequestName"))) | |
277 | return -ENOMEM; | |
278 | ||
279 | if (!dbus_message_append_args( | |
280 | message, | |
281 | DBUS_TYPE_STRING, &name, | |
282 | DBUS_TYPE_UINT32, &flags, | |
283 | DBUS_TYPE_INVALID)) { | |
284 | dbus_message_unref(message); | |
285 | return -ENOMEM; | |
286 | } | |
287 | ||
288 | if (!dbus_connection_send(m->bus, message, NULL)) { | |
289 | dbus_message_unref(message); | |
290 | return -ENOMEM; | |
291 | } | |
292 | ||
293 | /* We simple ask for the name and don't wait for it. Sooner or | |
294 | * later we'll have it, and we wouldn't know what to do on | |
295 | * error anyway. */ | |
296 | ||
297 | dbus_message_unref(message); | |
298 | ||
299 | return 0; | |
300 | } | |
301 | ||
302 | int bus_init(Manager *m) { | |
303 | DBusError error; | |
304 | char *id; | |
305 | int r; | |
306 | ||
307 | assert(m); | |
308 | ||
309 | if (m->bus) | |
310 | return 0; | |
311 | ||
312 | dbus_connection_set_change_sigpipe(FALSE); | |
313 | ||
314 | dbus_error_init(&error); | |
315 | if (!(m->bus = dbus_bus_get_private(m->is_init ? DBUS_BUS_SYSTEM : DBUS_BUS_SESSION, &error))) { | |
316 | log_error("Failed to get D-Bus connection: %s", error.message); | |
317 | dbus_error_free(&error); | |
318 | return -ECONNREFUSED; | |
319 | } | |
320 | ||
321 | dbus_connection_set_exit_on_disconnect(m->bus, FALSE); | |
322 | dbus_connection_set_dispatch_status_function(m->bus, bus_dispatch_status, m, NULL); | |
323 | if (!dbus_connection_set_watch_functions(m->bus, bus_add_watch, bus_remove_watch, bus_toggle_watch, m, NULL) || | |
324 | !dbus_connection_set_timeout_functions(m->bus, bus_add_timeout, bus_remove_timeout, bus_toggle_timeout, m, NULL) || | |
325 | !dbus_connection_register_object_path(m->bus, "/org/freedesktop/systemd1", &bus_manager_vtable, m) || | |
326 | !dbus_connection_register_fallback(m->bus, "/org/freedesktop/systemd1/unit", &bus_unit_vtable, m) || | |
327 | !dbus_connection_register_fallback(m->bus, "/org/freedesktop/systemd1/job", &bus_job_vtable, m)) { | |
328 | bus_done(m); | |
329 | return -ENOMEM; | |
330 | } | |
331 | ||
332 | if ((r = request_name(m)) < 0) { | |
333 | bus_done(m); | |
334 | return r; | |
335 | } | |
336 | ||
337 | log_debug("Successfully connected to D-Bus bus %s as %s", | |
338 | strnull((id = dbus_connection_get_server_id(m->bus))), | |
339 | strnull(dbus_bus_get_unique_name(m->bus))); | |
340 | dbus_free(id); | |
341 | ||
342 | m->request_bus_dispatch = true; | |
343 | ||
344 | return 0; | |
345 | } | |
346 | ||
347 | void bus_done(Manager *m) { | |
348 | assert(m); | |
349 | ||
350 | if (m->bus) { | |
351 | dbus_connection_close(m->bus); | |
352 | dbus_connection_unref(m->bus); | |
353 | m->bus = NULL; | |
354 | } | |
355 | } | |
356 | ||
357 | DBusHandlerResult bus_default_message_handler(Manager *m, DBusMessage *message, const char*introspection, const BusProperty *properties) { | |
358 | DBusError error; | |
359 | DBusMessage *reply = NULL; | |
360 | int r; | |
361 | ||
362 | assert(m); | |
363 | assert(message); | |
364 | ||
365 | dbus_error_init(&error); | |
366 | ||
367 | if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Introspectable", "Introspect") && introspection) { | |
368 | ||
369 | if (!(reply = dbus_message_new_method_return(message))) | |
370 | goto oom; | |
371 | ||
372 | if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &introspection, DBUS_TYPE_INVALID)) | |
373 | goto oom; | |
374 | ||
375 | } else if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Properties", "Get") && properties) { | |
376 | const char *interface, *property; | |
377 | const BusProperty *p; | |
378 | ||
379 | if (!dbus_message_get_args( | |
380 | message, | |
381 | &error, | |
382 | DBUS_TYPE_STRING, &interface, | |
383 | DBUS_TYPE_STRING, &property, | |
384 | DBUS_TYPE_INVALID)) | |
385 | return bus_send_error_reply(m, message, &error, -EINVAL); | |
386 | ||
387 | for (p = properties; p->property; p++) | |
388 | if (streq(p->interface, interface) && streq(p->property, property)) | |
389 | break; | |
390 | ||
391 | if (p->property) { | |
392 | DBusMessageIter iter, sub; | |
393 | ||
394 | if (!(reply = dbus_message_new_method_return(message))) | |
395 | goto oom; | |
396 | ||
397 | dbus_message_iter_init_append(reply, &iter); | |
398 | ||
399 | if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, p->signature, &sub)) | |
400 | goto oom; | |
401 | ||
402 | if ((r = p->append(m, &sub, property, p->data)) < 0) { | |
403 | ||
404 | if (r == -ENOMEM) | |
405 | goto oom; | |
406 | ||
407 | dbus_message_unref(reply); | |
408 | return bus_send_error_reply(m, message, NULL, r); | |
409 | } | |
410 | ||
411 | if (!dbus_message_iter_close_container(&iter, &sub)) | |
412 | goto oom; | |
413 | } | |
414 | } else if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Properties", "GetAll") && properties) { | |
415 | const char *interface; | |
416 | const BusProperty *p; | |
417 | DBusMessageIter iter, sub, sub2, sub3; | |
418 | bool any = false; | |
419 | ||
420 | if (!dbus_message_get_args( | |
421 | message, | |
422 | &error, | |
423 | DBUS_TYPE_STRING, &interface, | |
424 | DBUS_TYPE_INVALID)) | |
425 | return bus_send_error_reply(m, message, &error, -EINVAL); | |
426 | ||
427 | if (!(reply = dbus_message_new_method_return(message))) | |
428 | goto oom; | |
429 | ||
430 | dbus_message_iter_init_append(reply, &iter); | |
431 | ||
432 | if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &sub)) | |
433 | goto oom; | |
434 | ||
435 | for (p = properties; p->property; p++) { | |
436 | if (!streq(p->interface, interface)) | |
437 | continue; | |
438 | ||
439 | if (!dbus_message_iter_open_container(&sub, DBUS_TYPE_DICT_ENTRY, "sv" , &sub2) || | |
440 | !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &p->property) || | |
441 | !dbus_message_iter_open_container(&sub2, DBUS_TYPE_VARIANT, p->signature, &sub3)) | |
442 | goto oom; | |
443 | ||
444 | if ((r = p->append(m, &sub3, p->property, p->data)) < 0) { | |
445 | ||
446 | if (r == -ENOMEM) | |
447 | goto oom; | |
448 | ||
449 | dbus_message_unref(reply); | |
450 | return bus_send_error_reply(m, message, NULL, r); | |
451 | } | |
452 | ||
453 | if (!dbus_message_iter_close_container(&sub2, &sub3) || | |
454 | !dbus_message_iter_close_container(&sub, &sub2)) | |
455 | goto oom; | |
456 | ||
457 | any = true; | |
458 | } | |
459 | ||
460 | if (!dbus_message_iter_close_container(&iter, &sub)) | |
461 | goto oom; | |
462 | } | |
463 | ||
464 | if (reply) { | |
465 | if (!dbus_connection_send(m->bus, reply, NULL)) | |
466 | goto oom; | |
467 | ||
468 | dbus_message_unref(reply); | |
469 | return DBUS_HANDLER_RESULT_HANDLED; | |
470 | } | |
471 | ||
472 | return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; | |
473 | ||
474 | oom: | |
475 | if (reply) | |
476 | dbus_message_unref(reply); | |
477 | ||
478 | dbus_error_free(&error); | |
479 | ||
480 | return DBUS_HANDLER_RESULT_NEED_MEMORY; | |
481 | } | |
482 | ||
483 | ||
484 | ||
485 | static const char *error_to_dbus(int error) { | |
486 | ||
487 | switch(error) { | |
488 | ||
489 | case -EINVAL: | |
490 | return DBUS_ERROR_INVALID_ARGS; | |
491 | ||
492 | case -ENOMEM: | |
493 | return DBUS_ERROR_NO_MEMORY; | |
494 | ||
495 | case -EPERM: | |
496 | case -EACCES: | |
497 | return DBUS_ERROR_ACCESS_DENIED; | |
498 | ||
499 | case -ESRCH: | |
500 | return DBUS_ERROR_UNIX_PROCESS_ID_UNKNOWN; | |
501 | ||
502 | case -ENOENT: | |
503 | return DBUS_ERROR_FILE_NOT_FOUND; | |
504 | ||
505 | case -EEXIST: | |
506 | return DBUS_ERROR_FILE_EXISTS; | |
507 | ||
508 | case -ETIMEDOUT: | |
509 | return DBUS_ERROR_TIMEOUT; | |
510 | ||
511 | case -EIO: | |
512 | return DBUS_ERROR_IO_ERROR; | |
513 | ||
514 | case -ENETRESET: | |
515 | case -ECONNABORTED: | |
516 | case -ECONNRESET: | |
517 | return DBUS_ERROR_DISCONNECTED; | |
518 | } | |
519 | ||
520 | return DBUS_ERROR_FAILED; | |
521 | } | |
522 | ||
523 | DBusHandlerResult bus_send_error_reply(Manager *m, DBusMessage *message, DBusError *bus_error, int error) { | |
524 | DBusMessage *reply = NULL; | |
525 | const char *name, *text; | |
526 | ||
527 | if (bus_error && dbus_error_is_set(bus_error)) { | |
528 | name = bus_error->name; | |
529 | text = bus_error->message; | |
530 | } else { | |
531 | name = error_to_dbus(error); | |
532 | text = strerror(-error); | |
533 | } | |
534 | ||
535 | if (!(reply = dbus_message_new_error(message, name, text))) | |
536 | goto oom; | |
537 | ||
538 | if (!dbus_connection_send(m->bus, reply, NULL)) | |
539 | goto oom; | |
540 | ||
541 | dbus_message_unref(reply); | |
542 | ||
543 | if (bus_error) | |
544 | dbus_error_free(bus_error); | |
545 | ||
546 | return DBUS_HANDLER_RESULT_HANDLED; | |
547 | ||
548 | oom: | |
549 | if (reply) | |
550 | dbus_message_unref(reply); | |
551 | ||
552 | if (bus_error) | |
553 | dbus_error_free(bus_error); | |
554 | ||
555 | return DBUS_HANDLER_RESULT_NEED_MEMORY; | |
556 | } | |
557 | ||
558 | int bus_property_append_string(Manager *m, DBusMessageIter *i, const char *property, void *data) { | |
559 | const char *t = data; | |
560 | ||
561 | assert(m); | |
562 | assert(i); | |
563 | assert(property); | |
564 | assert(t); | |
565 | ||
566 | if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &t)) | |
567 | return -ENOMEM; | |
568 | ||
569 | return 0; | |
570 | } | |
571 | ||
572 | int bus_property_append_strv(Manager *m, DBusMessageIter *i, const char *property, void *data) { | |
573 | DBusMessageIter sub; | |
574 | char **t = data; | |
575 | ||
576 | assert(m); | |
577 | assert(i); | |
578 | assert(property); | |
579 | ||
580 | if (!dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "s", &sub)) | |
581 | return -ENOMEM; | |
582 | ||
583 | STRV_FOREACH(t, t) | |
584 | if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, t)) | |
585 | return -ENOMEM; | |
586 | ||
587 | if (!dbus_message_iter_close_container(i, &sub)) | |
588 | return -ENOMEM; | |
589 | ||
590 | return 0; | |
591 | } | |
592 | ||
593 | int bus_property_append_bool(Manager *m, DBusMessageIter *i, const char *property, void *data) { | |
594 | bool *b = data; | |
595 | dbus_bool_t db; | |
596 | ||
597 | assert(m); | |
598 | assert(i); | |
599 | assert(property); | |
600 | assert(b); | |
601 | ||
602 | db = *b; | |
603 | ||
604 | if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &db)) | |
605 | return -ENOMEM; | |
606 | ||
607 | return 0; | |
608 | } |