chiark / gitweb /
fec8998bc157f91d34014a626ce8bd7ca9cb76fe
[elogind.git] / src / shared / dbus-loop.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2011 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 <stdbool.h>
23 #include <assert.h>
24 #include <sys/epoll.h>
25 #include <string.h>
26 #include <errno.h>
27 #include <sys/timerfd.h>
28 #include <unistd.h>
29
30 #include "dbus-loop.h"
31 #include "dbus-common.h"
32 #include "util.h"
33
34 /* Minimal implementation of the dbus loop which integrates all dbus
35  * events into a single epoll fd which we can triviall integrate with
36  * other loops. Note that this is not used in the main systemd daemon
37  * since we run a more elaborate mainloop there. */
38
39 typedef struct EpollData {
40         int fd;
41         void *object;
42         bool is_timeout:1;
43         bool fd_is_dupped:1;
44 } EpollData;
45
46 static dbus_bool_t add_watch(DBusWatch *watch, void *data) {
47         EpollData _cleanup_free_ *e = NULL;
48         struct epoll_event ev;
49
50         assert(watch);
51
52         e = new0(EpollData, 1);
53         if (!e)
54                 return FALSE;
55
56         e->fd = dbus_watch_get_unix_fd(watch);
57         e->object = watch;
58         e->is_timeout = false;
59
60         zero(ev);
61         ev.events = bus_flags_to_events(watch);
62         ev.data.ptr = e;
63
64         if (epoll_ctl(PTR_TO_INT(data), EPOLL_CTL_ADD, e->fd, &ev) < 0) {
65
66                 if (errno != EEXIST)
67                         return FALSE;
68
69                 /* Hmm, bloody D-Bus creates multiple watches on the
70                  * same fd. epoll() does not like that. As a dirty
71                  * hack we simply dup() the fd and hence get a second
72                  * one we can safely add to the epoll(). */
73
74                 e->fd = dup(e->fd);
75                 if (e->fd < 0)
76                         return FALSE;
77
78                 if (epoll_ctl(PTR_TO_INT(data), EPOLL_CTL_ADD, e->fd, &ev) < 0) {
79                         close_nointr_nofail(e->fd);
80                         return FALSE;
81                 }
82
83                 e->fd_is_dupped = true;
84         }
85
86         dbus_watch_set_data(watch, e, NULL);
87         e = NULL; /* prevent freeing */
88
89         return TRUE;
90 }
91
92 static void remove_watch(DBusWatch *watch, void *data) {
93         EpollData _cleanup_free_ *e = NULL;
94
95         assert(watch);
96
97         e = dbus_watch_get_data(watch);
98         if (!e)
99                 return;
100
101         assert_se(epoll_ctl(PTR_TO_INT(data), EPOLL_CTL_DEL, e->fd, NULL) >= 0);
102
103         if (e->fd_is_dupped)
104                 close_nointr_nofail(e->fd);
105 }
106
107 static void toggle_watch(DBusWatch *watch, void *data) {
108         EpollData *e;
109         struct epoll_event ev;
110
111         assert(watch);
112
113         e = dbus_watch_get_data(watch);
114         if (!e)
115                 return;
116
117         zero(ev);
118         ev.events = bus_flags_to_events(watch);
119         ev.data.ptr = e;
120
121         assert_se(epoll_ctl(PTR_TO_INT(data), EPOLL_CTL_MOD, e->fd, &ev) == 0);
122 }
123
124 static int timeout_arm(EpollData *e) {
125         struct itimerspec its;
126
127         assert(e);
128         assert(e->is_timeout);
129
130         zero(its);
131
132         if (dbus_timeout_get_enabled(e->object)) {
133                 timespec_store(&its.it_value, dbus_timeout_get_interval(e->object) * USEC_PER_MSEC);
134                 its.it_interval = its.it_value;
135         }
136
137         if (timerfd_settime(e->fd, 0, &its, NULL) < 0)
138                 return -errno;
139
140         return 0;
141 }
142
143 static dbus_bool_t add_timeout(DBusTimeout *timeout, void *data) {
144         EpollData *e;
145         struct epoll_event ev;
146
147         assert(timeout);
148
149         e = new0(EpollData, 1);
150         if (!e)
151                 return FALSE;
152
153         e->fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK|TFD_CLOEXEC);
154         if (e->fd < 0)
155                 goto fail;
156
157         e->object = timeout;
158         e->is_timeout = true;
159
160         if (timeout_arm(e) < 0)
161                 goto fail;
162
163         zero(ev);
164         ev.events = EPOLLIN;
165         ev.data.ptr = e;
166
167         if (epoll_ctl(PTR_TO_INT(data), EPOLL_CTL_ADD, e->fd, &ev) < 0)
168                 goto fail;
169
170         dbus_timeout_set_data(timeout, e, NULL);
171
172         return TRUE;
173
174 fail:
175         if (e->fd >= 0)
176                 close_nointr_nofail(e->fd);
177
178         free(e);
179         return FALSE;
180 }
181
182 static void remove_timeout(DBusTimeout *timeout, void *data) {
183         EpollData _cleanup_free_ *e = NULL;
184
185         assert(timeout);
186
187         e = dbus_timeout_get_data(timeout);
188         if (!e)
189                 return;
190
191         assert_se(epoll_ctl(PTR_TO_INT(data), EPOLL_CTL_DEL, e->fd, NULL) >= 0);
192         close_nointr_nofail(e->fd);
193 }
194
195 static void toggle_timeout(DBusTimeout *timeout, void *data) {
196         EpollData *e;
197         int r;
198
199         assert(timeout);
200
201         e = dbus_timeout_get_data(timeout);
202         if (!e)
203                 return;
204
205         r = timeout_arm(e);
206         if (r < 0)
207                 log_error("Failed to rearm timer: %s", strerror(-r));
208 }
209
210 int bus_loop_open(DBusConnection *c) {
211         int fd;
212
213         assert(c);
214
215         fd = epoll_create1(EPOLL_CLOEXEC);
216         if (fd < 0)
217                 return -errno;
218
219         if (!dbus_connection_set_watch_functions(c, add_watch, remove_watch, toggle_watch, INT_TO_PTR(fd), NULL) ||
220             !dbus_connection_set_timeout_functions(c, add_timeout, remove_timeout, toggle_timeout, INT_TO_PTR(fd), NULL)) {
221                 close_nointr_nofail(fd);
222                 return -ENOMEM;
223         }
224
225         return fd;
226 }
227
228 int bus_loop_dispatch(int fd) {
229         int n;
230         struct epoll_event event;
231         EpollData *d;
232
233         assert(fd >= 0);
234
235         zero(event);
236
237         n = epoll_wait(fd, &event, 1, 0);
238         if (n < 0)
239                 return errno == EAGAIN || errno == EINTR ? 0 : -errno;
240
241         assert_se(d = event.data.ptr);
242
243         if (d->is_timeout) {
244                 DBusTimeout *t = d->object;
245
246                 if (dbus_timeout_get_enabled(t))
247                         dbus_timeout_handle(t);
248         } else {
249                 DBusWatch *w = d->object;
250
251                 if (dbus_watch_get_enabled(w))
252                         dbus_watch_handle(w, bus_events_to_flags(event.events));
253         }
254
255         return 0;
256 }