dbus-core.c revision 706f2543
1/*
2 * Copyright © 2006-2007 Daniel Stone
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice (including the next
12 * paragraph) shall be included in all copies or substantial portions of the
13 * Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 * DEALINGS IN THE SOFTWARE.
22 *
23 * Author: Daniel Stone <daniel@fooishbar.org>
24 */
25
26#ifdef HAVE_DIX_CONFIG_H
27#include <dix-config.h>
28#endif
29
30#include <dbus/dbus.h>
31#include <sys/select.h>
32
33#include "config-backends.h"
34#include "dix.h"
35#include "os.h"
36
37/* How often to attempt reconnecting when we get booted off the bus. */
38#define RECONNECT_DELAY (10 * 1000) /* in ms */
39
40struct dbus_core_info {
41    int fd;
42    DBusConnection *connection;
43    OsTimerPtr timer;
44    struct config_dbus_core_hook *hooks;
45};
46static struct dbus_core_info bus_info;
47
48static CARD32 reconnect_timer(OsTimerPtr timer, CARD32 time, pointer arg);
49
50static void
51wakeup_handler(pointer data, int err, pointer read_mask)
52{
53    struct dbus_core_info *info = data;
54
55    if (info->connection && FD_ISSET(info->fd, (fd_set *) read_mask)) {
56        do {
57            dbus_connection_read_write_dispatch(info->connection, 0);
58        } while (info->connection &&
59                 dbus_connection_get_is_connected(info->connection) &&
60                 dbus_connection_get_dispatch_status(info->connection) == DBUS_DISPATCH_DATA_REMAINS);
61    }
62}
63
64static void
65block_handler(pointer data, struct timeval **tv, pointer read_mask)
66{
67}
68
69/**
70 * Disconnect (if we haven't already been forcefully disconnected), clean up
71 * after ourselves, and call all registered disconnect hooks.
72 */
73static void
74teardown(void)
75{
76    struct config_dbus_core_hook *hook;
77
78    if (bus_info.timer) {
79        TimerFree(bus_info.timer);
80        bus_info.timer = NULL;
81    }
82
83    /* We should really have pre-disconnect hooks and run them here, for
84     * completeness.  But then it gets awkward, given that you can't
85     * guarantee that they'll be called ... */
86    if (bus_info.connection)
87        dbus_connection_unref(bus_info.connection);
88
89    RemoveBlockAndWakeupHandlers(block_handler, wakeup_handler, &bus_info);
90    if (bus_info.fd != -1)
91        RemoveGeneralSocket(bus_info.fd);
92    bus_info.fd = -1;
93    bus_info.connection = NULL;
94
95    for (hook = bus_info.hooks; hook; hook = hook->next) {
96        if (hook->disconnect)
97            hook->disconnect(hook->data);
98    }
99}
100
101/**
102 * This is a filter, which only handles the disconnected signal, which
103 * doesn't go to the normal message handling function.  This takes
104 * precedence over the message handling function, so have have to be
105 * careful to ignore anything we don't want to deal with here.
106 */
107static DBusHandlerResult
108message_filter(DBusConnection *connection, DBusMessage *message, void *data)
109{
110    /* If we get disconnected, then take everything down, and attempt to
111     * reconnect immediately (assuming it's just a restart).  The
112     * connection isn't valid at this point, so throw it out immediately. */
113    if (dbus_message_is_signal(message, DBUS_INTERFACE_LOCAL,
114                                    "Disconnected")) {
115        DebugF("[config/dbus-core] disconnected from bus\n");
116        bus_info.connection = NULL;
117        teardown();
118
119        if (bus_info.timer)
120            TimerFree(bus_info.timer);
121        bus_info.timer = TimerSet(NULL, 0, 1, reconnect_timer, NULL);
122
123        return DBUS_HANDLER_RESULT_HANDLED;
124    }
125
126    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
127}
128
129/**
130 * Attempt to connect to the system bus, and set a filter to deal with
131 * disconnection (see message_filter above).
132 *
133 * @return 1 on success, 0 on failure.
134 */
135static int
136connect_to_bus(void)
137{
138    DBusError error;
139    struct config_dbus_core_hook *hook;
140
141    dbus_error_init(&error);
142    bus_info.connection = dbus_bus_get(DBUS_BUS_SYSTEM, &error);
143    if (!bus_info.connection || dbus_error_is_set(&error)) {
144        DebugF("[config/dbus-core] error connecting to system bus: %s (%s)\n",
145               error.name, error.message);
146        goto err_begin;
147    }
148
149    /* Thankyou.  Really, thankyou. */
150    dbus_connection_set_exit_on_disconnect(bus_info.connection, FALSE);
151
152    if (!dbus_connection_get_unix_fd(bus_info.connection, &bus_info.fd)) {
153        ErrorF("[config/dbus-core] couldn't get fd for system bus\n");
154        goto err_unref;
155    }
156
157    if (!dbus_connection_add_filter(bus_info.connection, message_filter,
158                                    &bus_info, NULL)) {
159        ErrorF("[config/dbus-core] couldn't add filter: %s (%s)\n", error.name,
160               error.message);
161        goto err_fd;
162    }
163
164    dbus_error_free(&error);
165    AddGeneralSocket(bus_info.fd);
166
167    RegisterBlockAndWakeupHandlers(block_handler, wakeup_handler, &bus_info);
168
169    for (hook = bus_info.hooks; hook; hook = hook->next) {
170        if (hook->connect)
171            hook->connect(bus_info.connection, hook->data);
172    }
173
174    return 1;
175
176err_fd:
177    bus_info.fd = -1;
178err_unref:
179    dbus_connection_unref(bus_info.connection);
180    bus_info.connection = NULL;
181err_begin:
182    dbus_error_free(&error);
183
184    return 0;
185}
186
187static CARD32
188reconnect_timer(OsTimerPtr timer, CARD32 time, pointer arg)
189{
190    if (connect_to_bus()) {
191        TimerFree(bus_info.timer);
192        bus_info.timer = NULL;
193        return 0;
194    }
195    else {
196        return RECONNECT_DELAY;
197    }
198}
199
200int
201config_dbus_core_add_hook(struct config_dbus_core_hook *hook)
202{
203    struct config_dbus_core_hook **prev;
204
205    for (prev = &bus_info.hooks; *prev; prev = &(*prev)->next)
206        ;
207
208    hook->next = NULL;
209    *prev = hook;
210
211    /* If we're already connected, call the connect hook. */
212    if (bus_info.connection)
213        hook->connect(bus_info.connection, hook->data);
214
215    return 1;
216}
217
218void
219config_dbus_core_remove_hook(struct config_dbus_core_hook *hook)
220{
221    struct config_dbus_core_hook **prev;
222
223    for (prev = &bus_info.hooks; *prev; prev = &(*prev)->next) {
224        if (*prev == hook) {
225            *prev = hook->next;
226            break;
227        }
228    }
229}
230
231int
232config_dbus_core_init(void)
233{
234    memset(&bus_info, 0, sizeof(bus_info));
235    bus_info.fd = -1;
236    bus_info.hooks = NULL;
237    bus_info.connection = NULL;
238    bus_info.timer = TimerSet(NULL, 0, 1, reconnect_timer, NULL);
239
240    return 1;
241}
242
243void
244config_dbus_core_fini(void)
245{
246    teardown();
247}
248