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