]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/dbus-loop.c
fadbd439580425d4b045eb93592fa3255c112fd9
[thirdparty/systemd.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 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 EpollData _cleanup_free_ *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 EpollData _cleanup_free_ *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 }