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
20namespace rtc {
21
22// Avoid static object construction/destruction on startup/shutdown.
23static pthread_once_t g_dbus_init_once = PTHREAD_ONCE_INIT;
24static LibDBusGlibSymbolTable *g_dbus_symbol = NULL;
25
26// Releases DBus-Glib symbols.
27static 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.
35static 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
51inline static LibDBusGlibSymbolTable *GetSymbols() {
52  return DBusMonitor::GetDBusGlibSymbolTable();
53}
54
55// Implementation of class DBusSigMessageData
56DBusSigMessageData::DBusSigMessageData(DBusMessage *message)
57    : TypedMessageData<DBusMessage *>(message) {
58  GetSymbols()->dbus_message_ref()(data());
59}
60
61DBusSigMessageData::~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.
68std::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.
91DBusHandlerResult 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.
102DBusHandlerResult 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.
111void 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.
126class DBusMonitor::DBusMonitoringThread : public rtc::Thread {
127 public:
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
144  virtual ~DBusMonitoringThread() {
145    Stop();
146  }
147
148  // Override virtual method of Thread. Context: worker-thread.
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.
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.
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.
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.
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.
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.
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.
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.
307LibDBusGlibSymbolTable *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
315DBusMonitor *DBusMonitor::Create(DBusBusType type) {
316  if (NULL == DBusMonitor::GetDBusGlibSymbolTable()) {
317    return NULL;
318  }
319  return new DBusMonitor(type);
320}
321
322DBusMonitor::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
329DBusMonitor::~DBusMonitor() {
330  StopMonitoring();
331}
332
333bool 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
344bool DBusMonitor::StartMonitoring() {
345  if (!monitoring_thread_) {
346    g_type_init();
347    g_thread_init(NULL);
348    GetSymbols()->dbus_g_thread_init()();
349
350    GMainContext *context = g_main_context_new();
351    if (NULL == context) {
352      LOG(LS_ERROR) << "g_main_context_new() failed.";
353      return false;
354    }
355
356    GMainLoop *mainloop = g_main_loop_new(context, FALSE);
357    if (NULL == mainloop) {
358      LOG(LS_ERROR) << "g_main_loop_new() failed.";
359      g_main_context_unref(context);
360      return false;
361    }
362
363    monitoring_thread_ = new DBusMonitoringThread(this, context, mainloop,
364                                                  &filter_list_);
365    if (monitoring_thread_ == NULL) {
366      LOG(LS_ERROR) << "Failed to create DBus monitoring thread.";
367      g_main_context_unref(context);
368      g_main_loop_unref(mainloop);
369      return false;
370    }
371    monitoring_thread_->Start();
372  }
373  return true;
374}
375
376bool DBusMonitor::StopMonitoring() {
377  if (monitoring_thread_) {
378    monitoring_thread_->Stop();
379    monitoring_thread_ = NULL;
380  }
381  return true;
382}
383
384DBusMonitor::DBusMonitorStatus DBusMonitor::GetStatus() {
385  return status_;
386}
387
388void DBusMonitor::OnMonitoringStatusChanged(DBusMonitorStatus status) {
389  status_ = status;
390}
391
392#undef LATE
393
394}  // namespace rtc
395
396#endif  // HAVE_DBUS_GLIB
397