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