Home | History | Annotate | Download | only in base
      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 
     37 namespace talk_base {
     38 
     39 // Avoid static object construction/destruction on startup/shutdown.
     40 static pthread_once_t g_dbus_init_once = PTHREAD_ONCE_INIT;
     41 static LibDBusGlibSymbolTable *g_dbus_symbol = NULL;
     42 
     43 // Releases DBus-Glib symbols.
     44 static 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.
     52 static 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 
     68 inline static LibDBusGlibSymbolTable *GetSymbols() {
     69   return DBusMonitor::GetDBusGlibSymbolTable();
     70 }
     71 
     72 // Implementation of class DBusSigMessageData
     73 DBusSigMessageData::DBusSigMessageData(DBusMessage *message)
     74     : TypedMessageData<DBusMessage *>(message) {
     75   GetSymbols()->dbus_message_ref()(data());
     76 }
     77 
     78 DBusSigMessageData::~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.
     85 std::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.
    108 DBusHandlerResult 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.
    119 DBusHandlerResult 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.
    128 void 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.
    143 class 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.
    324 LibDBusGlibSymbolTable *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
    332 DBusMonitor *DBusMonitor::Create(DBusBusType type) {
    333   if (NULL == DBusMonitor::GetDBusGlibSymbolTable()) {
    334     return NULL;
    335   }
    336   return new DBusMonitor(type);
    337 }
    338 
    339 DBusMonitor::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 
    346 DBusMonitor::~DBusMonitor() {
    347   StopMonitoring();
    348 }
    349 
    350 bool 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 
    361 bool 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 
    393 bool DBusMonitor::StopMonitoring() {
    394   if (monitoring_thread_) {
    395     monitoring_thread_->Stop();
    396     monitoring_thread_ = NULL;
    397   }
    398   return true;
    399 }
    400 
    401 DBusMonitor::DBusMonitorStatus DBusMonitor::GetStatus() {
    402   return status_;
    403 }
    404 
    405 void DBusMonitor::OnMonitoringStatusChanged(DBusMonitorStatus status) {
    406   status_ = status;
    407 }
    408 
    409 #undef LATE
    410 
    411 }  // namespace talk_base
    412 
    413 #endif  // HAVE_DBUS_GLIB
    414