chiark / gitweb /
login: introduce sd_session_get_tty()
[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 *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 }