blob: 8e071c7c7a8ca586cc747ce80cb6f2ddb6e8d89a [file] [log] [blame]
henrike@webrtc.org28e20752013-07-10 00:45:36 +00001/*
2 * libjingle
3 * Copyright 2004--2011, Google Inc.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright notice,
11 * this list of conditions and the following disclaimer in the documentation
12 * and/or other materials provided with the distribution.
13 * 3. The name of the author may not be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28#ifdef HAVE_DBUS_GLIB
29
30#include "talk/base/dbus.h"
31
32#include <glib.h>
33
34#include "talk/base/logging.h"
35#include "talk/base/thread.h"
36
37namespace talk_base {
38
39// Avoid static object construction/destruction on startup/shutdown.
40static pthread_once_t g_dbus_init_once = PTHREAD_ONCE_INIT;
41static LibDBusGlibSymbolTable *g_dbus_symbol = NULL;
42
43// Releases DBus-Glib symbols.
44static void ReleaseDBusGlibSymbol() {
45 if (g_dbus_symbol != NULL) {
46 delete g_dbus_symbol;
47 g_dbus_symbol = NULL;
48 }
49}
50
51// Loads DBus-Glib symbols.
52static void InitializeDBusGlibSymbol() {
53 // This is thread safe.
54 if (NULL == g_dbus_symbol) {
55 g_dbus_symbol = new LibDBusGlibSymbolTable();
56
57 // Loads dbus-glib
58 if (NULL == g_dbus_symbol || !g_dbus_symbol->Load()) {
59 LOG(LS_WARNING) << "Failed to load dbus-glib symbol table.";
60 ReleaseDBusGlibSymbol();
61 } else {
62 // Nothing we can do if atexit() failed. Just ignore its returned value.
63 atexit(ReleaseDBusGlibSymbol);
64 }
65 }
66}
67
68inline static LibDBusGlibSymbolTable *GetSymbols() {
69 return DBusMonitor::GetDBusGlibSymbolTable();
70}
71
72// Implementation of class DBusSigMessageData
73DBusSigMessageData::DBusSigMessageData(DBusMessage *message)
74 : TypedMessageData<DBusMessage *>(message) {
75 GetSymbols()->dbus_message_ref()(data());
76}
77
78DBusSigMessageData::~DBusSigMessageData() {
79 GetSymbols()->dbus_message_unref()(data());
80}
81
82// Implementation of class DBusSigFilter
83
84// Builds a DBus filter string from given DBus path, interface and member.
85std::string DBusSigFilter::BuildFilterString(const std::string &path,
86 const std::string &interface,
87 const std::string &member) {
88 std::string ret(DBUS_TYPE "='" DBUS_SIGNAL "'");
89 if (!path.empty()) {
90 ret += ("," DBUS_PATH "='");
91 ret += path;
92 ret += "'";
93 }
94 if (!interface.empty()) {
95 ret += ("," DBUS_INTERFACE "='");
96 ret += interface;
97 ret += "'";
98 }
99 if (!member.empty()) {
100 ret += ("," DBUS_MEMBER "='");
101 ret += member;
102 ret += "'";
103 }
104 return ret;
105}
106
107// Forwards the message to the given instance.
108DBusHandlerResult DBusSigFilter::DBusCallback(DBusConnection *dbus_conn,
109 DBusMessage *message,
110 void *instance) {
111 ASSERT(instance);
112 if (instance) {
113 return static_cast<DBusSigFilter *>(instance)->Callback(message);
114 }
115 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
116}
117
118// Posts a message to caller thread.
119DBusHandlerResult DBusSigFilter::Callback(DBusMessage *message) {
120 if (caller_thread_) {
121 caller_thread_->Post(this, DSM_SIGNAL, new DBusSigMessageData(message));
122 }
123 // Don't "eat" the message here. Let it pop up.
124 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
125}
126
127// From MessageHandler.
128void DBusSigFilter::OnMessage(Message *message) {
129 if (message != NULL && DSM_SIGNAL == message->message_id) {
130 DBusSigMessageData *msg =
131 static_cast<DBusSigMessageData *>(message->pdata);
132 if (msg) {
133 ProcessSignal(msg->data());
134 delete msg;
135 }
136 }
137}
138
139// Definition of private class DBusMonitoringThread.
140// It creates a worker-thread to listen signals on DBus. The worker-thread will
141// be running in a priate GMainLoop forever until either Stop() has been invoked
142// or it hits an error.
143class DBusMonitor::DBusMonitoringThread : public talk_base::Thread {
144 public:
145 explicit DBusMonitoringThread(DBusMonitor *monitor,
146 GMainContext *context,
147 GMainLoop *mainloop,
148 std::vector<DBusSigFilter *> *filter_list)
149 : monitor_(monitor),
150 context_(context),
151 mainloop_(mainloop),
152 connection_(NULL),
153 idle_source_(NULL),
154 filter_list_(filter_list) {
155 ASSERT(monitor_);
156 ASSERT(context_);
157 ASSERT(mainloop_);
158 ASSERT(filter_list_);
159 }
160
161 // Override virtual method of Thread. Context: worker-thread.
162 virtual void Run() {
163 ASSERT(NULL == connection_);
164
165 // Setup DBus connection and start monitoring.
166 monitor_->OnMonitoringStatusChanged(DMS_INITIALIZING);
167 if (!Setup()) {
168 LOG(LS_ERROR) << "DBus monitoring setup failed.";
169 monitor_->OnMonitoringStatusChanged(DMS_FAILED);
170 CleanUp();
171 return;
172 }
173 monitor_->OnMonitoringStatusChanged(DMS_RUNNING);
174 g_main_loop_run(mainloop_);
175 monitor_->OnMonitoringStatusChanged(DMS_STOPPED);
176
177 // Done normally. Clean up DBus connection.
178 CleanUp();
179 return;
180 }
181
182 // Override virtual method of Thread. Context: caller-thread.
183 virtual void Stop() {
184 ASSERT(NULL == idle_source_);
185 // Add an idle source and let the gmainloop quit on idle.
186 idle_source_ = g_idle_source_new();
187 if (idle_source_) {
188 g_source_set_callback(idle_source_, &Idle, this, NULL);
189 g_source_attach(idle_source_, context_);
190 } else {
191 LOG(LS_ERROR) << "g_idle_source_new() failed.";
192 QuitGMainloop(); // Try to quit anyway.
193 }
194
195 Thread::Stop(); // Wait for the thread.
196 }
197
198 private:
199 // Registers all DBus filters.
200 void RegisterAllFilters() {
201 ASSERT(NULL != GetSymbols()->dbus_g_connection_get_connection()(
202 connection_));
203
204 for (std::vector<DBusSigFilter *>::iterator it = filter_list_->begin();
205 it != filter_list_->end(); ++it) {
206 DBusSigFilter *filter = (*it);
207 if (!filter) {
208 LOG(LS_ERROR) << "DBusSigFilter list corrupted.";
209 continue;
210 }
211
212 GetSymbols()->dbus_bus_add_match()(
213 GetSymbols()->dbus_g_connection_get_connection()(connection_),
214 filter->filter().c_str(), NULL);
215
216 if (!GetSymbols()->dbus_connection_add_filter()(
217 GetSymbols()->dbus_g_connection_get_connection()(connection_),
218 &DBusSigFilter::DBusCallback, filter, NULL)) {
219 LOG(LS_ERROR) << "dbus_connection_add_filter() failed."
220 << "Filter: " << filter->filter();
221 continue;
222 }
223 }
224 }
225
226 // Unregisters all DBus filters.
227 void UnRegisterAllFilters() {
228 ASSERT(NULL != GetSymbols()->dbus_g_connection_get_connection()(
229 connection_));
230
231 for (std::vector<DBusSigFilter *>::iterator it = filter_list_->begin();
232 it != filter_list_->end(); ++it) {
233 DBusSigFilter *filter = (*it);
234 if (!filter) {
235 LOG(LS_ERROR) << "DBusSigFilter list corrupted.";
236 continue;
237 }
238 GetSymbols()->dbus_connection_remove_filter()(
239 GetSymbols()->dbus_g_connection_get_connection()(connection_),
240 &DBusSigFilter::DBusCallback, filter);
241 }
242 }
243
244 // Sets up the monitoring thread.
245 bool Setup() {
246 g_main_context_push_thread_default(context_);
247
248 // Start connection to dbus.
249 // If dbus daemon is not running, returns false immediately.
250 connection_ = GetSymbols()->dbus_g_bus_get_private()(monitor_->type_,
251 context_, NULL);
252 if (NULL == connection_) {
253 LOG(LS_ERROR) << "dbus_g_bus_get_private() unable to get connection.";
254 return false;
255 }
256 if (NULL == GetSymbols()->dbus_g_connection_get_connection()(connection_)) {
257 LOG(LS_ERROR) << "dbus_g_connection_get_connection() returns NULL. "
258 << "DBus daemon is probably not running.";
259 return false;
260 }
261
262 // Application don't exit if DBus daemon die.
263 GetSymbols()->dbus_connection_set_exit_on_disconnect()(
264 GetSymbols()->dbus_g_connection_get_connection()(connection_), FALSE);
265
266 // Connect all filters.
267 RegisterAllFilters();
268
269 return true;
270 }
271
272 // Cleans up the monitoring thread.
273 void CleanUp() {
274 if (idle_source_) {
275 // We did an attach() with the GSource, so we need to destroy() it.
276 g_source_destroy(idle_source_);
277 // We need to unref() the GSource to end the last reference we got.
278 g_source_unref(idle_source_);
279 idle_source_ = NULL;
280 }
281 if (connection_) {
282 if (GetSymbols()->dbus_g_connection_get_connection()(connection_)) {
283 UnRegisterAllFilters();
284 GetSymbols()->dbus_connection_close()(
285 GetSymbols()->dbus_g_connection_get_connection()(connection_));
286 }
287 GetSymbols()->dbus_g_connection_unref()(connection_);
288 connection_ = NULL;
289 }
290 g_main_loop_unref(mainloop_);
291 mainloop_ = NULL;
292 g_main_context_unref(context_);
293 context_ = NULL;
294 }
295
296 // Handles callback on Idle. We only add this source when ready to stop.
297 static gboolean Idle(gpointer data) {
298 static_cast<DBusMonitoringThread *>(data)->QuitGMainloop();
299 return TRUE;
300 }
301
302 // We only hit this when ready to quit.
303 void QuitGMainloop() {
304 g_main_loop_quit(mainloop_);
305 }
306
307 DBusMonitor *monitor_;
308
309 GMainContext *context_;
310 GMainLoop *mainloop_;
311 DBusGConnection *connection_;
312 GSource *idle_source_;
313
314 std::vector<DBusSigFilter *> *filter_list_;
315};
316
317// Implementation of class DBusMonitor
318
319// Returns DBus-Glib symbol handle. Initialize it first if hasn't.
320LibDBusGlibSymbolTable *DBusMonitor::GetDBusGlibSymbolTable() {
321 // This is multi-thread safe.
322 pthread_once(&g_dbus_init_once, InitializeDBusGlibSymbol);
323
324 return g_dbus_symbol;
325};
326
327// Creates an instance of DBusMonitor
328DBusMonitor *DBusMonitor::Create(DBusBusType type) {
329 if (NULL == DBusMonitor::GetDBusGlibSymbolTable()) {
330 return NULL;
331 }
332 return new DBusMonitor(type);
333}
334
335DBusMonitor::DBusMonitor(DBusBusType type)
336 : type_(type),
337 status_(DMS_NOT_INITIALIZED),
338 monitoring_thread_(NULL) {
339 ASSERT(type_ == DBUS_BUS_SYSTEM || type_ == DBUS_BUS_SESSION);
340}
341
342DBusMonitor::~DBusMonitor() {
343 StopMonitoring();
344}
345
346bool DBusMonitor::AddFilter(DBusSigFilter *filter) {
347 if (monitoring_thread_) {
348 return false;
349 }
350 if (!filter) {
351 return false;
352 }
353 filter_list_.push_back(filter);
354 return true;
355}
356
357bool DBusMonitor::StartMonitoring() {
358 if (!monitoring_thread_) {
359 g_type_init();
360 g_thread_init(NULL);
361 GetSymbols()->dbus_g_thread_init()();
362
363 GMainContext *context = g_main_context_new();
364 if (NULL == context) {
365 LOG(LS_ERROR) << "g_main_context_new() failed.";
366 return false;
367 }
368
369 GMainLoop *mainloop = g_main_loop_new(context, FALSE);
370 if (NULL == mainloop) {
371 LOG(LS_ERROR) << "g_main_loop_new() failed.";
372 g_main_context_unref(context);
373 return false;
374 }
375
376 monitoring_thread_ = new DBusMonitoringThread(this, context, mainloop,
377 &filter_list_);
378 if (monitoring_thread_ == NULL) {
379 LOG(LS_ERROR) << "Failed to create DBus monitoring thread.";
380 g_main_context_unref(context);
381 g_main_loop_unref(mainloop);
382 return false;
383 }
384 monitoring_thread_->Start();
385 }
386 return true;
387}
388
389bool DBusMonitor::StopMonitoring() {
390 if (monitoring_thread_) {
391 monitoring_thread_->Stop();
392 monitoring_thread_ = NULL;
393 }
394 return true;
395}
396
397DBusMonitor::DBusMonitorStatus DBusMonitor::GetStatus() {
398 return status_;
399}
400
401void DBusMonitor::OnMonitoringStatusChanged(DBusMonitorStatus status) {
402 status_ = status;
403}
404
405#undef LATE
406
407} // namespace talk_base
408
409#endif // HAVE_DBUS_GLIB