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 <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  virtual ~DBusMonitoringThread() {
162    Stop();
163  }
164
165  // Override virtual method of Thread. Context: worker-thread.
166  virtual void Run() {
167    ASSERT(NULL == connection_);
168
169    // Setup DBus connection and start monitoring.
170    monitor_->OnMonitoringStatusChanged(DMS_INITIALIZING);
171    if (!Setup()) {
172      LOG(LS_ERROR) << "DBus monitoring setup failed.";
173      monitor_->OnMonitoringStatusChanged(DMS_FAILED);
174      CleanUp();
175      return;
176    }
177    monitor_->OnMonitoringStatusChanged(DMS_RUNNING);
178    g_main_loop_run(mainloop_);
179    monitor_->OnMonitoringStatusChanged(DMS_STOPPED);
180
181    // Done normally. Clean up DBus connection.
182    CleanUp();
183    return;
184  }
185
186  // Override virtual method of Thread. Context: caller-thread.
187  virtual void Stop() {
188    ASSERT(NULL == idle_source_);
189    // Add an idle source and let the gmainloop quit on idle.
190    idle_source_ = g_idle_source_new();
191    if (idle_source_) {
192      g_source_set_callback(idle_source_, &Idle, this, NULL);
193      g_source_attach(idle_source_, context_);
194    } else {
195      LOG(LS_ERROR) << "g_idle_source_new() failed.";
196      QuitGMainloop();  // Try to quit anyway.
197    }
198
199    Thread::Stop();  // Wait for the thread.
200  }
201
202 private:
203  // Registers all DBus filters.
204  void RegisterAllFilters() {
205    ASSERT(NULL != GetSymbols()->dbus_g_connection_get_connection()(
206        connection_));
207
208    for (std::vector<DBusSigFilter *>::iterator it = filter_list_->begin();
209         it != filter_list_->end(); ++it) {
210      DBusSigFilter *filter = (*it);
211      if (!filter) {
212        LOG(LS_ERROR) << "DBusSigFilter list corrupted.";
213        continue;
214      }
215
216      GetSymbols()->dbus_bus_add_match()(
217          GetSymbols()->dbus_g_connection_get_connection()(connection_),
218          filter->filter().c_str(), NULL);
219
220      if (!GetSymbols()->dbus_connection_add_filter()(
221              GetSymbols()->dbus_g_connection_get_connection()(connection_),
222              &DBusSigFilter::DBusCallback, filter, NULL)) {
223        LOG(LS_ERROR) << "dbus_connection_add_filter() failed."
224                      << "Filter: " << filter->filter();
225        continue;
226      }
227    }
228  }
229
230  // Unregisters all DBus filters.
231  void UnRegisterAllFilters() {
232    ASSERT(NULL != GetSymbols()->dbus_g_connection_get_connection()(
233        connection_));
234
235    for (std::vector<DBusSigFilter *>::iterator it = filter_list_->begin();
236         it != filter_list_->end(); ++it) {
237      DBusSigFilter *filter = (*it);
238      if (!filter) {
239        LOG(LS_ERROR) << "DBusSigFilter list corrupted.";
240        continue;
241      }
242      GetSymbols()->dbus_connection_remove_filter()(
243          GetSymbols()->dbus_g_connection_get_connection()(connection_),
244          &DBusSigFilter::DBusCallback, filter);
245    }
246  }
247
248  // Sets up the monitoring thread.
249  bool Setup() {
250    g_main_context_push_thread_default(context_);
251
252    // Start connection to dbus.
253    // If dbus daemon is not running, returns false immediately.
254    connection_ = GetSymbols()->dbus_g_bus_get_private()(monitor_->type_,
255        context_, NULL);
256    if (NULL == connection_) {
257      LOG(LS_ERROR) << "dbus_g_bus_get_private() unable to get connection.";
258      return false;
259    }
260    if (NULL == GetSymbols()->dbus_g_connection_get_connection()(connection_)) {
261      LOG(LS_ERROR) << "dbus_g_connection_get_connection() returns NULL. "
262                    << "DBus daemon is probably not running.";
263      return false;
264    }
265
266    // Application don't exit if DBus daemon die.
267    GetSymbols()->dbus_connection_set_exit_on_disconnect()(
268        GetSymbols()->dbus_g_connection_get_connection()(connection_), FALSE);
269
270    // Connect all filters.
271    RegisterAllFilters();
272
273    return true;
274  }
275
276  // Cleans up the monitoring thread.
277  void CleanUp() {
278    if (idle_source_) {
279      // We did an attach() with the GSource, so we need to destroy() it.
280      g_source_destroy(idle_source_);
281      // We need to unref() the GSource to end the last reference we got.
282      g_source_unref(idle_source_);
283      idle_source_ = NULL;
284    }
285    if (connection_) {
286      if (GetSymbols()->dbus_g_connection_get_connection()(connection_)) {
287        UnRegisterAllFilters();
288        GetSymbols()->dbus_connection_close()(
289            GetSymbols()->dbus_g_connection_get_connection()(connection_));
290      }
291      GetSymbols()->dbus_g_connection_unref()(connection_);
292      connection_ = NULL;
293    }
294    g_main_loop_unref(mainloop_);
295    mainloop_ = NULL;
296    g_main_context_unref(context_);
297    context_ = NULL;
298  }
299
300  // Handles callback on Idle. We only add this source when ready to stop.
301  static gboolean Idle(gpointer data) {
302    static_cast<DBusMonitoringThread *>(data)->QuitGMainloop();
303    return TRUE;
304  }
305
306  // We only hit this when ready to quit.
307  void QuitGMainloop() {
308    g_main_loop_quit(mainloop_);
309  }
310
311  DBusMonitor *monitor_;
312
313  GMainContext *context_;
314  GMainLoop *mainloop_;
315  DBusGConnection *connection_;
316  GSource *idle_source_;
317
318  std::vector<DBusSigFilter *> *filter_list_;
319};
320
321// Implementation of class DBusMonitor
322
323// Returns DBus-Glib symbol handle. Initialize it first if hasn't.
324LibDBusGlibSymbolTable *DBusMonitor::GetDBusGlibSymbolTable() {
325  // This is multi-thread safe.
326  pthread_once(&g_dbus_init_once, InitializeDBusGlibSymbol);
327
328  return g_dbus_symbol;
329};
330
331// Creates an instance of DBusMonitor
332DBusMonitor *DBusMonitor::Create(DBusBusType type) {
333  if (NULL == DBusMonitor::GetDBusGlibSymbolTable()) {
334    return NULL;
335  }
336  return new DBusMonitor(type);
337}
338
339DBusMonitor::DBusMonitor(DBusBusType type)
340    : type_(type),
341      status_(DMS_NOT_INITIALIZED),
342      monitoring_thread_(NULL) {
343  ASSERT(type_ == DBUS_BUS_SYSTEM || type_ == DBUS_BUS_SESSION);
344}
345
346DBusMonitor::~DBusMonitor() {
347  StopMonitoring();
348}
349
350bool DBusMonitor::AddFilter(DBusSigFilter *filter) {
351  if (monitoring_thread_) {
352    return false;
353  }
354  if (!filter) {
355    return false;
356  }
357  filter_list_.push_back(filter);
358  return true;
359}
360
361bool DBusMonitor::StartMonitoring() {
362  if (!monitoring_thread_) {
363    g_type_init();
364    g_thread_init(NULL);
365    GetSymbols()->dbus_g_thread_init()();
366
367    GMainContext *context = g_main_context_new();
368    if (NULL == context) {
369      LOG(LS_ERROR) << "g_main_context_new() failed.";
370      return false;
371    }
372
373    GMainLoop *mainloop = g_main_loop_new(context, FALSE);
374    if (NULL == mainloop) {
375      LOG(LS_ERROR) << "g_main_loop_new() failed.";
376      g_main_context_unref(context);
377      return false;
378    }
379
380    monitoring_thread_ = new DBusMonitoringThread(this, context, mainloop,
381                                                  &filter_list_);
382    if (monitoring_thread_ == NULL) {
383      LOG(LS_ERROR) << "Failed to create DBus monitoring thread.";
384      g_main_context_unref(context);
385      g_main_loop_unref(mainloop);
386      return false;
387    }
388    monitoring_thread_->Start();
389  }
390  return true;
391}
392
393bool DBusMonitor::StopMonitoring() {
394  if (monitoring_thread_) {
395    monitoring_thread_->Stop();
396    monitoring_thread_ = NULL;
397  }
398  return true;
399}
400
401DBusMonitor::DBusMonitorStatus DBusMonitor::GetStatus() {
402  return status_;
403}
404
405void DBusMonitor::OnMonitoringStatusChanged(DBusMonitorStatus status) {
406  status_ = status;
407}
408
409#undef LATE
410
411}  // namespace talk_base
412
413#endif  // HAVE_DBUS_GLIB
414