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