/* * libjingle * Copyright 2004--2011, Google Inc. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifdef HAVE_DBUS_GLIB #include "talk/base/dbus.h" #include #include "talk/base/logging.h" #include "talk/base/thread.h" namespace talk_base { // Avoid static object construction/destruction on startup/shutdown. static pthread_once_t g_dbus_init_once = PTHREAD_ONCE_INIT; static LibDBusGlibSymbolTable *g_dbus_symbol = NULL; // Releases DBus-Glib symbols. static void ReleaseDBusGlibSymbol() { if (g_dbus_symbol != NULL) { delete g_dbus_symbol; g_dbus_symbol = NULL; } } // Loads DBus-Glib symbols. static void InitializeDBusGlibSymbol() { // This is thread safe. if (NULL == g_dbus_symbol) { g_dbus_symbol = new LibDBusGlibSymbolTable(); // Loads dbus-glib if (NULL == g_dbus_symbol || !g_dbus_symbol->Load()) { LOG(LS_WARNING) << "Failed to load dbus-glib symbol table."; ReleaseDBusGlibSymbol(); } else { // Nothing we can do if atexit() failed. Just ignore its returned value. atexit(ReleaseDBusGlibSymbol); } } } inline static LibDBusGlibSymbolTable *GetSymbols() { return DBusMonitor::GetDBusGlibSymbolTable(); } // Implementation of class DBusSigMessageData DBusSigMessageData::DBusSigMessageData(DBusMessage *message) : TypedMessageData(message) { GetSymbols()->dbus_message_ref()(data()); } DBusSigMessageData::~DBusSigMessageData() { GetSymbols()->dbus_message_unref()(data()); } // Implementation of class DBusSigFilter // Builds a DBus filter string from given DBus path, interface and member. std::string DBusSigFilter::BuildFilterString(const std::string &path, const std::string &interface, const std::string &member) { std::string ret(DBUS_TYPE "='" DBUS_SIGNAL "'"); if (!path.empty()) { ret += ("," DBUS_PATH "='"); ret += path; ret += "'"; } if (!interface.empty()) { ret += ("," DBUS_INTERFACE "='"); ret += interface; ret += "'"; } if (!member.empty()) { ret += ("," DBUS_MEMBER "='"); ret += member; ret += "'"; } return ret; } // Forwards the message to the given instance. DBusHandlerResult DBusSigFilter::DBusCallback(DBusConnection *dbus_conn, DBusMessage *message, void *instance) { ASSERT(instance); if (instance) { return static_cast(instance)->Callback(message); } return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } // Posts a message to caller thread. DBusHandlerResult DBusSigFilter::Callback(DBusMessage *message) { if (caller_thread_) { caller_thread_->Post(this, DSM_SIGNAL, new DBusSigMessageData(message)); } // Don't "eat" the message here. Let it pop up. return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } // From MessageHandler. void DBusSigFilter::OnMessage(Message *message) { if (message != NULL && DSM_SIGNAL == message->message_id) { DBusSigMessageData *msg = static_cast(message->pdata); if (msg) { ProcessSignal(msg->data()); delete msg; } } } // Definition of private class DBusMonitoringThread. // It creates a worker-thread to listen signals on DBus. The worker-thread will // be running in a priate GMainLoop forever until either Stop() has been invoked // or it hits an error. class DBusMonitor::DBusMonitoringThread : public talk_base::Thread { public: explicit DBusMonitoringThread(DBusMonitor *monitor, GMainContext *context, GMainLoop *mainloop, std::vector *filter_list) : monitor_(monitor), context_(context), mainloop_(mainloop), connection_(NULL), idle_source_(NULL), filter_list_(filter_list) { ASSERT(monitor_); ASSERT(context_); ASSERT(mainloop_); ASSERT(filter_list_); } virtual ~DBusMonitoringThread() { Stop(); } // Override virtual method of Thread. Context: worker-thread. virtual void Run() { ASSERT(NULL == connection_); // Setup DBus connection and start monitoring. monitor_->OnMonitoringStatusChanged(DMS_INITIALIZING); if (!Setup()) { LOG(LS_ERROR) << "DBus monitoring setup failed."; monitor_->OnMonitoringStatusChanged(DMS_FAILED); CleanUp(); return; } monitor_->OnMonitoringStatusChanged(DMS_RUNNING); g_main_loop_run(mainloop_); monitor_->OnMonitoringStatusChanged(DMS_STOPPED); // Done normally. Clean up DBus connection. CleanUp(); return; } // Override virtual method of Thread. Context: caller-thread. virtual void Stop() { ASSERT(NULL == idle_source_); // Add an idle source and let the gmainloop quit on idle. idle_source_ = g_idle_source_new(); if (idle_source_) { g_source_set_callback(idle_source_, &Idle, this, NULL); g_source_attach(idle_source_, context_); } else { LOG(LS_ERROR) << "g_idle_source_new() failed."; QuitGMainloop(); // Try to quit anyway. } Thread::Stop(); // Wait for the thread. } private: // Registers all DBus filters. void RegisterAllFilters() { ASSERT(NULL != GetSymbols()->dbus_g_connection_get_connection()( connection_)); for (std::vector::iterator it = filter_list_->begin(); it != filter_list_->end(); ++it) { DBusSigFilter *filter = (*it); if (!filter) { LOG(LS_ERROR) << "DBusSigFilter list corrupted."; continue; } GetSymbols()->dbus_bus_add_match()( GetSymbols()->dbus_g_connection_get_connection()(connection_), filter->filter().c_str(), NULL); if (!GetSymbols()->dbus_connection_add_filter()( GetSymbols()->dbus_g_connection_get_connection()(connection_), &DBusSigFilter::DBusCallback, filter, NULL)) { LOG(LS_ERROR) << "dbus_connection_add_filter() failed." << "Filter: " << filter->filter(); continue; } } } // Unregisters all DBus filters. void UnRegisterAllFilters() { ASSERT(NULL != GetSymbols()->dbus_g_connection_get_connection()( connection_)); for (std::vector::iterator it = filter_list_->begin(); it != filter_list_->end(); ++it) { DBusSigFilter *filter = (*it); if (!filter) { LOG(LS_ERROR) << "DBusSigFilter list corrupted."; continue; } GetSymbols()->dbus_connection_remove_filter()( GetSymbols()->dbus_g_connection_get_connection()(connection_), &DBusSigFilter::DBusCallback, filter); } } // Sets up the monitoring thread. bool Setup() { g_main_context_push_thread_default(context_); // Start connection to dbus. // If dbus daemon is not running, returns false immediately. connection_ = GetSymbols()->dbus_g_bus_get_private()(monitor_->type_, context_, NULL); if (NULL == connection_) { LOG(LS_ERROR) << "dbus_g_bus_get_private() unable to get connection."; return false; } if (NULL == GetSymbols()->dbus_g_connection_get_connection()(connection_)) { LOG(LS_ERROR) << "dbus_g_connection_get_connection() returns NULL. " << "DBus daemon is probably not running."; return false; } // Application don't exit if DBus daemon die. GetSymbols()->dbus_connection_set_exit_on_disconnect()( GetSymbols()->dbus_g_connection_get_connection()(connection_), FALSE); // Connect all filters. RegisterAllFilters(); return true; } // Cleans up the monitoring thread. void CleanUp() { if (idle_source_) { // We did an attach() with the GSource, so we need to destroy() it. g_source_destroy(idle_source_); // We need to unref() the GSource to end the last reference we got. g_source_unref(idle_source_); idle_source_ = NULL; } if (connection_) { if (GetSymbols()->dbus_g_connection_get_connection()(connection_)) { UnRegisterAllFilters(); GetSymbols()->dbus_connection_close()( GetSymbols()->dbus_g_connection_get_connection()(connection_)); } GetSymbols()->dbus_g_connection_unref()(connection_); connection_ = NULL; } g_main_loop_unref(mainloop_); mainloop_ = NULL; g_main_context_unref(context_); context_ = NULL; } // Handles callback on Idle. We only add this source when ready to stop. static gboolean Idle(gpointer data) { static_cast(data)->QuitGMainloop(); return TRUE; } // We only hit this when ready to quit. void QuitGMainloop() { g_main_loop_quit(mainloop_); } DBusMonitor *monitor_; GMainContext *context_; GMainLoop *mainloop_; DBusGConnection *connection_; GSource *idle_source_; std::vector *filter_list_; }; // Implementation of class DBusMonitor // Returns DBus-Glib symbol handle. Initialize it first if hasn't. LibDBusGlibSymbolTable *DBusMonitor::GetDBusGlibSymbolTable() { // This is multi-thread safe. pthread_once(&g_dbus_init_once, InitializeDBusGlibSymbol); return g_dbus_symbol; }; // Creates an instance of DBusMonitor DBusMonitor *DBusMonitor::Create(DBusBusType type) { if (NULL == DBusMonitor::GetDBusGlibSymbolTable()) { return NULL; } return new DBusMonitor(type); } DBusMonitor::DBusMonitor(DBusBusType type) : type_(type), status_(DMS_NOT_INITIALIZED), monitoring_thread_(NULL) { ASSERT(type_ == DBUS_BUS_SYSTEM || type_ == DBUS_BUS_SESSION); } DBusMonitor::~DBusMonitor() { StopMonitoring(); } bool DBusMonitor::AddFilter(DBusSigFilter *filter) { if (monitoring_thread_) { return false; } if (!filter) { return false; } filter_list_.push_back(filter); return true; } bool DBusMonitor::StartMonitoring() { if (!monitoring_thread_) { g_type_init(); g_thread_init(NULL); GetSymbols()->dbus_g_thread_init()(); GMainContext *context = g_main_context_new(); if (NULL == context) { LOG(LS_ERROR) << "g_main_context_new() failed."; return false; } GMainLoop *mainloop = g_main_loop_new(context, FALSE); if (NULL == mainloop) { LOG(LS_ERROR) << "g_main_loop_new() failed."; g_main_context_unref(context); return false; } monitoring_thread_ = new DBusMonitoringThread(this, context, mainloop, &filter_list_); if (monitoring_thread_ == NULL) { LOG(LS_ERROR) << "Failed to create DBus monitoring thread."; g_main_context_unref(context); g_main_loop_unref(mainloop); return false; } monitoring_thread_->Start(); } return true; } bool DBusMonitor::StopMonitoring() { if (monitoring_thread_) { monitoring_thread_->Stop(); monitoring_thread_ = NULL; } return true; } DBusMonitor::DBusMonitorStatus DBusMonitor::GetStatus() { return status_; } void DBusMonitor::OnMonitoringStatusChanged(DBusMonitorStatus status) { status_ = status; } #undef LATE } // namespace talk_base #endif // HAVE_DBUS_GLIB