1 /*
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 <dbus/dbus-glib-lowlevel.h>
33
34 #include "talk/base/thread.h"
35
36 namespace talk_base {
37
38 // Avoid static object construction/destruction on startup/shutdown.
39 static pthread_once_t g_dbus_init_once = PTHREAD_ONCE_INIT;
40 static LibDBusGlibSymbolTable *g_dbus_symbol = NULL;
41
42 // Releases DBus-Glib symbols.
ReleaseDBusGlibSymbol()43 static void ReleaseDBusGlibSymbol() {
44 if (g_dbus_symbol != NULL) {
45 delete g_dbus_symbol;
46 g_dbus_symbol = NULL;
47 }
48 }
49
50 // Loads DBus-Glib symbols.
InitializeDBusGlibSymbol()51 static void InitializeDBusGlibSymbol() {
52 // This is thread safe.
53 if (NULL == g_dbus_symbol) {
54 g_dbus_symbol = new LibDBusGlibSymbolTable();
55
56 // Loads dbus-glib
57 if (NULL == g_dbus_symbol || !g_dbus_symbol->Load()) {
58 LOG(LS_WARNING) << "Failed to load dbus-glib symbol table.";
59 ReleaseDBusGlibSymbol();
60 } else {
61 // Nothing we can do if atexit() failed. Just ignore its returned value.
62 atexit(ReleaseDBusGlibSymbol);
63 }
64 }
65 }
66
67 // Returns a reference to the given late-binded symbol, with the correct type.
68 #define LATE(sym) LATESYM_GET(LibDBusGlibSymbolTable, \
69 DBusMonitor::GetDBusGlibSymbolTable(), sym)
70
71 // Implementation of class DBusSigMessageData
DBusSigMessageData(DBusMessage * message)72 DBusSigMessageData::DBusSigMessageData(DBusMessage *message)
73 : TypedMessageData<DBusMessage *>(message) {
74 LATE(dbus_message_ref)(data());
75 }
76
~DBusSigMessageData()77 DBusSigMessageData::~DBusSigMessageData() {
78 LATE(dbus_message_unref)(data());
79 }
80
81 // Implementation of class DBusSigFilter
82
83 // Builds a DBus filter string from given DBus path, interface and member.
BuildFilterString(const std::string & path,const std::string & interface,const std::string & member)84 std::string DBusSigFilter::BuildFilterString(const std::string &path,
85 const std::string &interface,
86 const std::string &member) {
87 std::string ret(DBUS_TYPE "='" DBUS_SIGNAL "'");
88 if (!path.empty()) {
89 ret += ("," DBUS_PATH "='");
90 ret += path;
91 ret += "'";
92 }
93 if (!interface.empty()) {
94 ret += ("," DBUS_INTERFACE "='");
95 ret += interface;
96 ret += "'";
97 }
98 if (!member.empty()) {
99 ret += ("," DBUS_MEMBER "='");
100 ret += member;
101 ret += "'";
102 }
103 return ret;
104 }
105
106 // Forwards the message to the given instance.
DBusCallback(DBusConnection * dbus_conn,DBusMessage * message,void * instance)107 DBusHandlerResult DBusSigFilter::DBusCallback(DBusConnection *dbus_conn,
108 DBusMessage *message,
109 void *instance) {
110 ASSERT(instance);
111 if (instance) {
112 return static_cast<DBusSigFilter *>(instance)->Callback(message);
113 }
114 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
115 }
116
117 // Posts a message to caller thread.
Callback(DBusMessage * message)118 DBusHandlerResult DBusSigFilter::Callback(DBusMessage *message) {
119 if (caller_thread_) {
120 caller_thread_->Post(this, DSM_SIGNAL, new DBusSigMessageData(message));
121 }
122 // Don't "eat" the message here. Let it pop up.
123 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
124 }
125
126 // From MessageHandler.
OnMessage(Message * message)127 void DBusSigFilter::OnMessage(Message *message) {
128 if (message != NULL && DSM_SIGNAL == message->message_id) {
129 DBusSigMessageData *msg =
130 static_cast<DBusSigMessageData *>(message->pdata);
131 if (msg) {
132 ProcessSignal(msg->data());
133 delete msg;
134 }
135 }
136 }
137
138 // Definition of private class DBusMonitoringThread.
139 // It creates a worker-thread to listen signals on DBus. The worker-thread will
140 // be running in a priate GMainLoop forever until either Stop() has been invoked
141 // or it hits an error.
142 class DBusMonitor::DBusMonitoringThread : public talk_base::Thread {
143 public:
DBusMonitoringThread(DBusMonitor * monitor,GMainContext * context,GMainLoop * mainloop,std::vector<DBusSigFilter * > * filter_list)144 explicit DBusMonitoringThread(DBusMonitor *monitor,
145 GMainContext *context,
146 GMainLoop *mainloop,
147 std::vector<DBusSigFilter *> *filter_list)
148 : monitor_(monitor),
149 context_(context),
150 mainloop_(mainloop),
151 connection_(NULL),
152 idle_source_(NULL),
153 filter_list_(filter_list) {
154 ASSERT(monitor_);
155 ASSERT(context_);
156 ASSERT(mainloop_);
157 ASSERT(filter_list_);
158 }
159
160 // Override virtual method of Thread. Context: worker-thread.
Run()161 virtual void Run() {
162 ASSERT(NULL == connection_);
163
164 // Setup DBus connection and start monitoring.
165 monitor_->OnMonitoringStatusChanged(DMS_INITIALIZING);
166 if (!Setup()) {
167 LOG(LS_ERROR) << "DBus monitoring setup failed.";
168 monitor_->OnMonitoringStatusChanged(DMS_FAILED);
169 CleanUp();
170 return;
171 }
172 monitor_->OnMonitoringStatusChanged(DMS_RUNNING);
173 LATE(g_main_loop_run)(mainloop_);
174 monitor_->OnMonitoringStatusChanged(DMS_STOPPED);
175
176 // Done normally. Clean up DBus connection.
177 CleanUp();
178 return;
179 }
180
181 // Override virtual method of Thread. Context: caller-thread.
Stop()182 virtual void Stop() {
183 ASSERT(NULL == idle_source_);
184 // Add an idle source and let the gmainloop quit on idle.
185 idle_source_ = LATE(g_idle_source_new)();
186 if (idle_source_) {
187 LATE(g_source_set_callback)(idle_source_, &Idle, this, NULL);
188 LATE(g_source_attach)(idle_source_, context_);
189 } else {
190 LOG(LS_ERROR) << "g_idle_source_new() failed.";
191 QuitGMainloop(); // Try to quit anyway.
192 }
193
194 Thread::Stop(); // Wait for the thread.
195 }
196
197 private:
198 // Registers all DBus filters.
RegisterAllFilters()199 void RegisterAllFilters() {
200 ASSERT(NULL != LATE(dbus_g_connection_get_connection)(connection_));
201
202 for (std::vector<DBusSigFilter *>::iterator it = filter_list_->begin();
203 it != filter_list_->end(); ++it) {
204 DBusSigFilter *filter = (*it);
205 if (!filter) {
206 LOG(LS_ERROR) << "DBusSigFilter list corrupted.";
207 continue;
208 }
209
210 LATE(dbus_bus_add_match)(
211 LATE(dbus_g_connection_get_connection)(connection_),
212 filter->filter().c_str(), NULL);
213
214 if (!LATE(dbus_connection_add_filter)(
215 LATE(dbus_g_connection_get_connection)(connection_),
216 &DBusSigFilter::DBusCallback, filter, NULL)) {
217 LOG(LS_ERROR) << "dbus_connection_add_filter() failed."
218 << "Filter: " << filter->filter();
219 continue;
220 }
221 }
222 }
223
224 // Unregisters all DBus filters.
UnRegisterAllFilters()225 void UnRegisterAllFilters() {
226 ASSERT(NULL != LATE(dbus_g_connection_get_connection)(connection_));
227
228 for (std::vector<DBusSigFilter *>::iterator it = filter_list_->begin();
229 it != filter_list_->end(); ++it) {
230 DBusSigFilter *filter = (*it);
231 if (!filter) {
232 LOG(LS_ERROR) << "DBusSigFilter list corrupted.";
233 continue;
234 }
235 LATE(dbus_connection_remove_filter)(
236 LATE(dbus_g_connection_get_connection)(connection_),
237 &DBusSigFilter::DBusCallback, filter);
238 }
239 }
240
241 // Sets up the monitoring thread.
Setup()242 bool Setup() {
243 LATE(g_main_context_push_thread_default)(context_);
244
245 // Start connection to dbus.
246 // If dbus daemon is not running, returns false immediately.
247 connection_ = LATE(dbus_g_bus_get_private)(monitor_->type_, context_, NULL);
248 if (NULL == connection_) {
249 LOG(LS_ERROR) << "dbus_g_bus_get_private() unable to get connection.";
250 return false;
251 }
252 if (NULL == LATE(dbus_g_connection_get_connection)(connection_)) {
253 LOG(LS_ERROR) << "dbus_g_connection_get_connection() returns NULL. "
254 << "DBus daemon is probably not running.";
255 return false;
256 }
257
258 // Application don't exit if DBus daemon die.
259 LATE(dbus_connection_set_exit_on_disconnect)(
260 LATE(dbus_g_connection_get_connection)(connection_), FALSE);
261
262 // Connect all filters.
263 RegisterAllFilters();
264
265 return true;
266 }
267
268 // Cleans up the monitoring thread.
CleanUp()269 void CleanUp() {
270 if (idle_source_) {
271 // We did an attach() with the GSource, so we need to destroy() it.
272 LATE(g_source_destroy)(idle_source_);
273 // We need to unref() the GSource to end the last reference we got.
274 LATE(g_source_unref)(idle_source_);
275 idle_source_ = NULL;
276 }
277 if (connection_) {
278 if (LATE(dbus_g_connection_get_connection)(connection_)) {
279 UnRegisterAllFilters();
280 LATE(dbus_connection_close)(
281 LATE(dbus_g_connection_get_connection)(connection_));
282 }
283 LATE(dbus_g_connection_unref)(connection_);
284 connection_ = NULL;
285 }
286 LATE(g_main_loop_unref)(mainloop_);
287 mainloop_ = NULL;
288 LATE(g_main_context_unref)(context_);
289 context_ = NULL;
290 }
291
292 // Handles callback on Idle. We only add this source when ready to stop.
Idle(gpointer data)293 static gboolean Idle(gpointer data) {
294 static_cast<DBusMonitoringThread *>(data)->QuitGMainloop();
295 return TRUE;
296 }
297
298 // We only hit this when ready to quit.
QuitGMainloop()299 void QuitGMainloop() {
300 LATE(g_main_loop_quit)(mainloop_);
301 }
302
303 DBusMonitor *monitor_;
304
305 GMainContext *context_;
306 GMainLoop *mainloop_;
307 DBusGConnection *connection_;
308 GSource *idle_source_;
309
310 std::vector<DBusSigFilter *> *filter_list_;
311 };
312
313 // Implementation of class DBusMonitor
314
315 // Returns DBus-Glib symbol handle. Initialize it first if hasn't.
GetDBusGlibSymbolTable()316 LibDBusGlibSymbolTable *DBusMonitor::GetDBusGlibSymbolTable() {
317 // This is multi-thread safe.
318 pthread_once(&g_dbus_init_once, InitializeDBusGlibSymbol);
319
320 return g_dbus_symbol;
321 };
322
323 // Creates an instance of DBusMonitor
Create(DBusBusType type)324 DBusMonitor *DBusMonitor::Create(DBusBusType type) {
325 if (NULL == DBusMonitor::GetDBusGlibSymbolTable()) {
326 return NULL;
327 }
328 return new DBusMonitor(type);
329 }
330
DBusMonitor(DBusBusType type)331 DBusMonitor::DBusMonitor(DBusBusType type)
332 : type_(type),
333 status_(DMS_NOT_INITIALIZED),
334 monitoring_thread_(NULL) {
335 ASSERT(type_ == DBUS_BUS_SYSTEM || type_ == DBUS_BUS_SESSION);
336 }
337
~DBusMonitor()338 DBusMonitor::~DBusMonitor() {
339 StopMonitoring();
340 }
341
AddFilter(DBusSigFilter * filter)342 bool DBusMonitor::AddFilter(DBusSigFilter *filter) {
343 if (monitoring_thread_) {
344 return false;
345 }
346 if (!filter) {
347 return false;
348 }
349 filter_list_.push_back(filter);
350 return true;
351 }
352
StartMonitoring()353 bool DBusMonitor::StartMonitoring() {
354 if (!monitoring_thread_) {
355 LATE(g_type_init)();
356 LATE(g_thread_init)(NULL);
357 LATE(dbus_g_thread_init)();
358
359 GMainContext *context = LATE(g_main_context_new)();
360 if (NULL == context) {
361 LOG(LS_ERROR) << "g_main_context_new() failed.";
362 return false;
363 }
364
365 GMainLoop *mainloop = LATE(g_main_loop_new)(context, FALSE);
366 if (NULL == mainloop) {
367 LOG(LS_ERROR) << "g_main_loop_new() failed.";
368 LATE(g_main_context_unref)(context);
369 return false;
370 }
371
372 monitoring_thread_ = new DBusMonitoringThread(this, context, mainloop,
373 &filter_list_);
374 if (monitoring_thread_ == NULL) {
375 LOG(LS_ERROR) << "Failed to create DBus monitoring thread.";
376 LATE(g_main_context_unref)(context);
377 LATE(g_main_loop_unref)(mainloop);
378 return false;
379 }
380 monitoring_thread_->Start();
381 }
382 return true;
383 }
384
StopMonitoring()385 bool DBusMonitor::StopMonitoring() {
386 if (monitoring_thread_) {
387 monitoring_thread_->Stop();
388 monitoring_thread_ = NULL;
389 }
390 return true;
391 }
392
GetStatus()393 DBusMonitor::DBusMonitorStatus DBusMonitor::GetStatus() {
394 return status_;
395 }
396
OnMonitoringStatusChanged(DBusMonitorStatus status)397 void DBusMonitor::OnMonitoringStatusChanged(DBusMonitorStatus status) {
398 status_ = status;
399 }
400
401 #undef LATE
402
403 } // namespace talk_base
404
405 #endif // HAVE_DBUS_GLIB
406