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