chiark / gitweb /
nspawn: compress mount table a bit
[elogind.git] / src / 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 General Public License as published by
10   the Free Software Foundation; either version 2 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   General Public License for more details.
17
18   You should have received a copy of the GNU 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 *e;
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                         free(e);
68                         return FALSE;
69                 }
70
71                 /* Hmm, bloody D-Bus creates multiple watches on the
72                  * same fd. epoll() does not like that. As a dirty
73                  * hack we simply dup() the fd and hence get a second
74                  * one we can safely add to the epoll(). */
75
76                 e->fd = dup(e->fd);
77                 if (e->fd < 0) {
78                         free(e);
79                         return FALSE;
80                 }
81
82                 if (epoll_ctl(PTR_TO_INT(data), EPOLL_CTL_ADD, e->fd, &ev) < 0) {
83                         close_nointr_nofail(e->fd);
84                         free(e);
85                         return FALSE;
86                 }
87
88                 e->fd_is_dupped = true;
89         }
90
91         dbus_watch_set_data(watch, e, NULL);
92
93         return TRUE;
94 }
95
96 static void remove_watch(DBusWatch *watch, void *data) {
97         EpollData *e;
98
99         assert(watch);
100
101         e = dbus_watch_get_data(watch);
102         if (!e)
103                 return;
104
105         assert_se(epoll_ctl(PTR_TO_INT(data), EPOLL_CTL_DEL, e->fd, NULL) >= 0);
106
107         if (e->fd_is_dupped)
108                 close_nointr_nofail(e->fd);
109
110         free(e);
111 }
112
113 static void toggle_watch(DBusWatch *watch, void *data) {
114         EpollData *e;
115         struct epoll_event ev;
116
117         assert(watch);
118
119         e = dbus_watch_get_data(watch);
120         if (!e)
121                 return;
122
123         zero(ev);
124         ev.events = bus_flags_to_events(watch);
125         ev.data.ptr = e;
126
127         assert_se(epoll_ctl(PTR_TO_INT(data), EPOLL_CTL_MOD, e->fd, &ev) == 0);
128 }
129
130 static int timeout_arm(EpollData *e) {
131         struct itimerspec its;
132
133         assert(e);
134         assert(e->is_timeout);
135
136         zero(its);
137
138         if (dbus_timeout_get_enabled(e->object)) {
139                 timespec_store(&its.it_value, dbus_timeout_get_interval(e->object) * USEC_PER_MSEC);
140                 its.it_interval = its.it_value;
141         }
142
143         if (timerfd_settime(e->fd, 0, &its, NULL) < 0)
144                 return -errno;
145
146         return 0;
147 }
148
149 static dbus_bool_t add_timeout(DBusTimeout *timeout, void *data) {
150         EpollData *e;
151         struct epoll_event ev;
152
153         assert(timeout);
154
155         e = new0(EpollData, 1);
156         if (!e)
157                 return FALSE;
158
159         e->fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK|TFD_CLOEXEC);
160         if (e->fd < 0)
161                 goto fail;
162
163         e->object = timeout;
164         e->is_timeout = true;
165
166         if (timeout_arm(e) < 0)
167                 goto fail;
168
169         zero(ev);
170         ev.events = EPOLLIN;
171         ev.data.ptr = e;
172
173         if (epoll_ctl(PTR_TO_INT(data), EPOLL_CTL_ADD, e->fd, &ev) < 0)
174                 goto fail;
175
176         dbus_timeout_set_data(timeout, e, NULL);
177
178         return TRUE;
179
180 fail:
181         if (e->fd >= 0)
182                 close_nointr_nofail(e->fd);
183
184         free(e);
185         return FALSE;
186 }
187
188 static void remove_timeout(DBusTimeout *timeout, void *data) {
189         EpollData *e;
190
191         assert(timeout);
192
193         e = dbus_timeout_get_data(timeout);
194         if (!e)
195                 return;
196
197         assert_se(epoll_ctl(PTR_TO_INT(data), EPOLL_CTL_DEL, e->fd, NULL) >= 0);
198         close_nointr_nofail(e->fd);
199         free(e);
200 }
201
202 static void toggle_timeout(DBusTimeout *timeout, void *data) {
203         EpollData *e;
204         int r;
205
206         assert(timeout);
207
208         e = dbus_timeout_get_data(timeout);
209         if (!e)
210                 return;
211
212         r = timeout_arm(e);
213         if (r < 0)
214                 log_error("Failed to rearm timer: %s", strerror(-r));
215 }
216
217 int bus_loop_open(DBusConnection *c) {
218         int fd;
219
220         assert(c);
221
222         fd = epoll_create1(EPOLL_CLOEXEC);
223         if (fd < 0)
224                 return -errno;
225
226         if (!dbus_connection_set_watch_functions(c, add_watch, remove_watch, toggle_watch, INT_TO_PTR(fd), NULL) ||
227             !dbus_connection_set_timeout_functions(c, add_timeout, remove_timeout, toggle_timeout, INT_TO_PTR(fd), NULL)) {
228                 close_nointr_nofail(fd);
229                 return -ENOMEM;
230         }
231
232         return fd;
233 }
234
235 int bus_loop_dispatch(int fd) {
236         int n;
237         struct epoll_event event;
238         EpollData *d;
239
240         assert(fd >= 0);
241
242         zero(event);
243
244         n = epoll_wait(fd, &event, 1, 0);
245         if (n < 0)
246                 return errno == EAGAIN || errno == EINTR ? 0 : -errno;
247
248         assert_se(d = event.data.ptr);
249
250         if (d->is_timeout) {
251                 DBusTimeout *t = d->object;
252
253                 if (dbus_timeout_get_enabled(t))
254                         dbus_timeout_handle(t);
255         } else {
256                 DBusWatch *w = d->object;
257
258                 if (dbus_watch_get_enabled(w))
259                         dbus_watch_handle(w, bus_events_to_flags(event.events));
260         }
261
262         return 0;
263 }