diff options
Diffstat (limited to 'dbus')
-rw-r--r-- | dbus/bus.cc | 180 | ||||
-rw-r--r-- | dbus/bus.h | 55 | ||||
-rw-r--r-- | dbus/bus_unittest.cc | 122 |
3 files changed, 354 insertions, 3 deletions
diff --git a/dbus/bus.cc b/dbus/bus.cc index 1ddeeb8..c830f06 100644 --- a/dbus/bus.cc +++ b/dbus/bus.cc @@ -9,10 +9,12 @@ #include "base/message_loop.h" #include "base/message_loop/message_loop_proxy.h" #include "base/stl_util.h" +#include "base/stringprintf.h" #include "base/threading/thread.h" #include "base/threading/thread_restrictions.h" #include "base/time.h" #include "dbus/exported_object.h" +#include "dbus/message.h" #include "dbus/object_manager.h" #include "dbus/object_path.h" #include "dbus/object_proxy.h" @@ -27,6 +29,15 @@ const char kDisconnectedMatchRule[] = "type='signal', path='/org/freedesktop/DBus/Local'," "interface='org.freedesktop.DBus.Local', member='Disconnected'"; +// The NameOwnerChanged member in org.freedesktop.DBus +const char kNameOwnerChangedSignal[] = "NameOwnerChanged"; + +// The match rule used to filter for changes to a given service name owner. +const char kServiceNameOwnerChangeMatchRule[] = + "type='signal',interface='org.freedesktop.DBus'," + "member='NameOwnerChanged',path='/org/freedesktop/DBus'," + "sender='org.freedesktop.DBus',arg0='%s'"; + // The class is used for watching the file descriptor used for D-Bus // communication. class Watch : public base::MessagePumpLibevent::Watcher { @@ -908,6 +919,112 @@ void Bus::GetServiceOwnerInternal(const std::string& service_name, PostTaskToOriginThread(FROM_HERE, base::Bind(callback, service_owner)); } +void Bus::ListenForServiceOwnerChange( + const std::string& service_name, + const GetServiceOwnerCallback& callback) { + AssertOnOriginThread(); + DCHECK(!service_name.empty()); + DCHECK(!callback.is_null()); + + PostTaskToDBusThread(FROM_HERE, + base::Bind(&Bus::ListenForServiceOwnerChangeInternal, + this, service_name, callback)); +} + +void Bus::ListenForServiceOwnerChangeInternal( + const std::string& service_name, + const GetServiceOwnerCallback& callback) { + AssertOnDBusThread(); + DCHECK(!service_name.empty()); + DCHECK(!callback.is_null()); + + if (!Connect() || !SetUpAsyncOperations()) + return; + + if (service_owner_changed_listener_map_.empty()) { + bool filter_added = + AddFilterFunction(Bus::OnServiceOwnerChangedFilter, this); + DCHECK(filter_added); + } + + ServiceOwnerChangedListenerMap::iterator it = + service_owner_changed_listener_map_.find(service_name); + if (it == service_owner_changed_listener_map_.end()) { + // Add a match rule for the new service name. + const std::string name_owner_changed_match_rule = + base::StringPrintf(kServiceNameOwnerChangeMatchRule, + service_name.c_str()); + ScopedDBusError error; + AddMatch(name_owner_changed_match_rule, error.get()); + if (error.is_set()) { + LOG(ERROR) << "Failed to add match rule for " << service_name + << ". Got " << error.name() << ": " << error.message(); + return; + } + + service_owner_changed_listener_map_[service_name].push_back(callback); + return; + } + + // Check if the callback has already been added. + std::vector<GetServiceOwnerCallback>& callbacks = it->second; + for (size_t i = 0; i < callbacks.size(); ++i) { + if (callbacks[i].Equals(callback)) + return; + } + callbacks.push_back(callback); +} + +void Bus::UnlistenForServiceOwnerChange( + const std::string& service_name, + const GetServiceOwnerCallback& callback) { + AssertOnOriginThread(); + DCHECK(!service_name.empty()); + DCHECK(!callback.is_null()); + + PostTaskToDBusThread(FROM_HERE, + base::Bind(&Bus::UnlistenForServiceOwnerChangeInternal, + this, service_name, callback)); +} + +void Bus::UnlistenForServiceOwnerChangeInternal( + const std::string& service_name, + const GetServiceOwnerCallback& callback) { + AssertOnDBusThread(); + DCHECK(!service_name.empty()); + DCHECK(!callback.is_null()); + + ServiceOwnerChangedListenerMap::iterator it = + service_owner_changed_listener_map_.find(service_name); + if (it == service_owner_changed_listener_map_.end()) + return; + + std::vector<GetServiceOwnerCallback>& callbacks = it->second; + for (size_t i = 0; i < callbacks.size(); ++i) { + if (callbacks[i].Equals(callback)) { + callbacks.erase(callbacks.begin() + i); + break; // There can be only one. + } + } + if (!callbacks.empty()) + return; + + // Last callback for |service_name| has been removed, remove match rule. + const std::string name_owner_changed_match_rule = + base::StringPrintf(kServiceNameOwnerChangeMatchRule, + service_name.c_str()); + ScopedDBusError error; + RemoveMatch(name_owner_changed_match_rule, error.get()); + // And remove |service_owner_changed_listener_map_| entry. + service_owner_changed_listener_map_.erase(it); + + if (service_owner_changed_listener_map_.empty()) { + bool filter_removed = + RemoveFilterFunction(Bus::OnServiceOwnerChangedFilter, this); + DCHECK(filter_removed); + } +} + dbus_bool_t Bus::OnAddWatch(DBusWatch* raw_watch) { AssertOnDBusThread(); @@ -1000,36 +1117,81 @@ void Bus::OnConnectionDisconnected(DBusConnection* connection) { ShutdownAndBlock(); } +void Bus::OnServiceOwnerChanged(DBusMessage* message) { + DCHECK(message); + AssertOnDBusThread(); + + // |message| will be unrefed on exit of the function. Increment the + // reference so we can use it in Signal::FromRawMessage() below. + dbus_message_ref(message); + scoped_ptr<Signal> signal(Signal::FromRawMessage(message)); + + // Confirm the validity of the NameOwnerChanged signal. + if (signal->GetMember() != kNameOwnerChangedSignal || + signal->GetInterface() != DBUS_INTERFACE_DBUS || + signal->GetSender() != DBUS_SERVICE_DBUS) { + return; + } + + MessageReader reader(signal.get()); + std::string service_name; + std::string old_owner; + std::string new_owner; + if (!reader.PopString(&service_name) || + !reader.PopString(&old_owner) || + !reader.PopString(&new_owner)) { + return; + } + + ServiceOwnerChangedListenerMap::const_iterator it = + service_owner_changed_listener_map_.find(service_name); + if (it == service_owner_changed_listener_map_.end()) + return; + + const std::vector<GetServiceOwnerCallback>& callbacks = it->second; + for (size_t i = 0; i < callbacks.size(); ++i) { + PostTaskToOriginThread(FROM_HERE, + base::Bind(callbacks[i], new_owner)); + } +} + +// static dbus_bool_t Bus::OnAddWatchThunk(DBusWatch* raw_watch, void* data) { Bus* self = static_cast<Bus*>(data); return self->OnAddWatch(raw_watch); } +// static void Bus::OnRemoveWatchThunk(DBusWatch* raw_watch, void* data) { Bus* self = static_cast<Bus*>(data); self->OnRemoveWatch(raw_watch); } +// static void Bus::OnToggleWatchThunk(DBusWatch* raw_watch, void* data) { Bus* self = static_cast<Bus*>(data); self->OnToggleWatch(raw_watch); } +// static dbus_bool_t Bus::OnAddTimeoutThunk(DBusTimeout* raw_timeout, void* data) { Bus* self = static_cast<Bus*>(data); return self->OnAddTimeout(raw_timeout); } +// static void Bus::OnRemoveTimeoutThunk(DBusTimeout* raw_timeout, void* data) { Bus* self = static_cast<Bus*>(data); self->OnRemoveTimeout(raw_timeout); } +// static void Bus::OnToggleTimeoutThunk(DBusTimeout* raw_timeout, void* data) { Bus* self = static_cast<Bus*>(data); self->OnToggleTimeout(raw_timeout); } +// static void Bus::OnDispatchStatusChangedThunk(DBusConnection* connection, DBusDispatchStatus status, void* data) { @@ -1037,6 +1199,7 @@ void Bus::OnDispatchStatusChangedThunk(DBusConnection* connection, self->OnDispatchStatusChanged(connection, status); } +// static DBusHandlerResult Bus::OnConnectionDisconnectedFilter( DBusConnection* connection, DBusMessage* message, @@ -1045,11 +1208,26 @@ DBusHandlerResult Bus::OnConnectionDisconnectedFilter( DBUS_INTERFACE_LOCAL, kDisconnectedSignal)) { Bus* self = static_cast<Bus*>(data); - self->AssertOnDBusThread(); self->OnConnectionDisconnected(connection); return DBUS_HANDLER_RESULT_HANDLED; } return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } +// static +DBusHandlerResult Bus::OnServiceOwnerChangedFilter( + DBusConnection* connection, + DBusMessage* message, + void* data) { + if (dbus_message_is_signal(message, + DBUS_INTERFACE_DBUS, + kNameOwnerChangedSignal)) { + Bus* self = static_cast<Bus*>(data); + self->OnServiceOwnerChanged(message); + } + // Always return unhandled to let others, e.g. ObjectProxies, handle the same + // signal. + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + } // namespace dbus @@ -11,6 +11,7 @@ #include <set> #include <string> #include <utility> +#include <vector> #include "base/callback.h" #include "base/memory/ref_counted.h" @@ -22,7 +23,6 @@ namespace base { class SequencedTaskRunner; class SingleThreadTaskRunner; -class Thread; } namespace tracked_objects { @@ -563,6 +563,28 @@ class CHROME_DBUS_EXPORT Bus : public base::RefCountedThreadSafe<Bus> { virtual void GetServiceOwner(const std::string& service_name, const GetServiceOwnerCallback& callback); + // Whenever the owner for |service_name| changes, run |callback| with the + // name of the new owner. If the owner goes away, then |callback| receives + // an empty string. + // + // Any unique (service_name, callback) can be used. Duplicate are ignored. + // |service_name| must not be empty and |callback| must not be null. + // + // Must be called in the origin thread. + virtual void ListenForServiceOwnerChange( + const std::string& service_name, + const GetServiceOwnerCallback& callback); + + // Stop listening for |service_name| owner changes for |callback|. + // Any unique (service_name, callback) can be used. Non-registered callbacks + // for a given service name are ignored. + // |service_name| must not be empty and |callback| must not be null. + // + // Must be called in the origin thread. + virtual void UnlistenForServiceOwnerChange( + const std::string& service_name, + const GetServiceOwnerCallback& callback); + // Returns true if the bus is connected to D-Bus. bool is_connected() { return connection_ != NULL; } @@ -592,6 +614,16 @@ class CHROME_DBUS_EXPORT Bus : public base::RefCountedThreadSafe<Bus> { void GetServiceOwnerInternal(const std::string& service_name, const GetServiceOwnerCallback& callback); + // Helper function used for ListenForServiceOwnerChange(). + void ListenForServiceOwnerChangeInternal( + const std::string& service_name, + const GetServiceOwnerCallback& callback); + + // Helper function used for UnListenForServiceOwnerChange(). + void UnlistenForServiceOwnerChangeInternal( + const std::string& service_name, + const GetServiceOwnerCallback& callback); + // Processes the all incoming data to the connection, if any. // // BLOCKING CALL. @@ -625,6 +657,9 @@ class CHROME_DBUS_EXPORT Bus : public base::RefCountedThreadSafe<Bus> { // Called when the connection is diconnected. void OnConnectionDisconnected(DBusConnection* connection); + // Called when a service owner change occurs. + void OnServiceOwnerChanged(DBusMessage* message); + // Callback helper functions. Redirects to the corresponding member function. static dbus_bool_t OnAddWatchThunk(DBusWatch* raw_watch, void* data); static void OnRemoveWatchThunk(DBusWatch* raw_watch, void* data); @@ -636,12 +671,18 @@ class CHROME_DBUS_EXPORT Bus : public base::RefCountedThreadSafe<Bus> { DBusDispatchStatus status, void* data); - // Calls OnConnectionDisconnected if the Diconnected signal is received. + // Calls OnConnectionDisconnected if the Disconnected signal is received. static DBusHandlerResult OnConnectionDisconnectedFilter( DBusConnection* connection, DBusMessage* message, void* user_data); + // Calls OnServiceOwnerChanged for a NameOwnerChanged signal. + static DBusHandlerResult OnServiceOwnerChangedFilter( + DBusConnection* connection, + DBusMessage* message, + void* user_data); + const BusType bus_type_; const ConnectionType connection_type_; scoped_refptr<base::SequencedTaskRunner> dbus_task_runner_; @@ -684,6 +725,16 @@ class CHROME_DBUS_EXPORT Bus : public base::RefCountedThreadSafe<Bus> { scoped_refptr<dbus::ObjectManager> > ObjectManagerTable; ObjectManagerTable object_manager_table_; + // A map of NameOwnerChanged signals to listen for and the callbacks to run + // on the origin thread when the owner changes. + // Only accessed on the DBus thread. + // Key: Service name + // Value: Vector of callbacks. Unique and expected to be small. Not using + // std::set here because base::Callbacks don't have a '<' operator. + typedef std::map<std::string, std::vector<GetServiceOwnerCallback> > + ServiceOwnerChangedListenerMap; + ServiceOwnerChangedListenerMap service_owner_changed_listener_map_; + bool async_operations_set_up_; bool shutdown_completed_; diff --git a/dbus/bus_unittest.cc b/dbus/bus_unittest.cc index 82949d3..3c453f7 100644 --- a/dbus/bus_unittest.cc +++ b/dbus/bus_unittest.cc @@ -7,11 +7,13 @@ #include "base/bind.h" #include "base/message_loop.h" #include "base/memory/ref_counted.h" +#include "base/run_loop.h" #include "base/threading/thread.h" #include "dbus/exported_object.h" #include "dbus/object_path.h" #include "dbus/object_proxy.h" #include "dbus/scoped_dbus_error.h" +#include "dbus/test_service.h" #include "testing/gtest/include/gtest/gtest.h" @@ -24,6 +26,49 @@ DBusHandlerResult DummyHandler(DBusConnection* connection, return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } +// Test helper for BusTest.ListenForServiceOwnerChange that wraps a +// base::RunLoop. At Run() time, the caller pass in the expected number of +// quit calls, and at QuitIfConditionIsSatisified() time, only quit the RunLoop +// if the expected number of quit calls have been reached. +class RunLoopWithExpectedCount { + public: + RunLoopWithExpectedCount() : expected_quit_calls_(0), actual_quit_calls_(0) {} + ~RunLoopWithExpectedCount() {} + + void Run(int expected_quit_calls) { + DCHECK_EQ(0, expected_quit_calls_); + DCHECK_EQ(0, actual_quit_calls_); + expected_quit_calls_ = expected_quit_calls; + run_loop_.reset(new base::RunLoop()); + run_loop_->Run(); + } + + void QuitIfConditionIsSatisified() { + if (++actual_quit_calls_ != expected_quit_calls_) + return; + run_loop_->Quit(); + expected_quit_calls_ = 0; + actual_quit_calls_ = 0; + } + + private: + scoped_ptr<base::RunLoop> run_loop_; + int expected_quit_calls_; + int actual_quit_calls_; + + DISALLOW_COPY_AND_ASSIGN(RunLoopWithExpectedCount); +}; + +// Test helper for BusTest.ListenForServiceOwnerChange. +void OnServiceOwnerChanged(RunLoopWithExpectedCount* run_loop_state, + std::string* service_owner, + int* num_of_owner_changes, + const std::string& new_service_owner) { + *service_owner = new_service_owner; + ++(*num_of_owner_changes); + run_loop_state->QuitIfConditionIsSatisified(); +} + } // namespace TEST(BusTest, GetObjectProxy) { @@ -296,3 +341,80 @@ TEST(BusTest, DoubleAddAndRemoveMatch) { bus->ShutdownAndBlock(); } + +TEST(BusTest, ListenForServiceOwnerChange) { + // Setup the current thread's MessageLoop. Must be of TYPE_IO for the + // listeners to work. + base::MessageLoop message_loop(base::MessageLoop::TYPE_IO); + RunLoopWithExpectedCount run_loop_state; + + // Create the bus. + dbus::Bus::Options bus_options; + scoped_refptr<dbus::Bus> bus = new dbus::Bus(bus_options); + + // Add a listener. + std::string service_owner1; + int num_of_owner_changes1 = 0; + dbus::Bus::GetServiceOwnerCallback callback1 = + base::Bind(&OnServiceOwnerChanged, + &run_loop_state, + &service_owner1, + &num_of_owner_changes1); + bus->ListenForServiceOwnerChange("org.chromium.TestService", callback1); + // This should be a no-op. + bus->ListenForServiceOwnerChange("org.chromium.TestService", callback1); + base::RunLoop().RunUntilIdle(); + + // Nothing has happened yet. Check initial state. + EXPECT_TRUE(service_owner1.empty()); + EXPECT_EQ(0, num_of_owner_changes1); + + // Make an ownership change. + ASSERT_TRUE(bus->RequestOwnershipAndBlock("org.chromium.TestService")); + run_loop_state.Run(1); + + { + // Get the current service owner and check to make sure the listener got + // the right value. + std::string current_service_owner = + bus->GetServiceOwnerAndBlock("org.chromium.TestService", + dbus::Bus::REPORT_ERRORS); + ASSERT_FALSE(current_service_owner.empty()); + + // Make sure the listener heard about the new owner. + EXPECT_EQ(current_service_owner, service_owner1); + + // Test the second ListenForServiceOwnerChange() above is indeed a no-op. + EXPECT_EQ(1, num_of_owner_changes1); + } + + // Add a second listener. + std::string service_owner2; + int num_of_owner_changes2 = 0; + dbus::Bus::GetServiceOwnerCallback callback2 = + base::Bind(&OnServiceOwnerChanged, + &run_loop_state, + &service_owner2, + &num_of_owner_changes2); + bus->ListenForServiceOwnerChange("org.chromium.TestService", callback2); + base::RunLoop().RunUntilIdle(); + + // Release the ownership and make sure the service owner listeners fire with + // the right values and the right number of times. + ASSERT_TRUE(bus->ReleaseOwnership("org.chromium.TestService")); + run_loop_state.Run(2); + + EXPECT_TRUE(service_owner1.empty()); + EXPECT_TRUE(service_owner2.empty()); + EXPECT_EQ(2, num_of_owner_changes1); + EXPECT_EQ(1, num_of_owner_changes2); + + // Unlisten so shutdown can proceed correctly. + bus->UnlistenForServiceOwnerChange("org.chromium.TestService", callback1); + bus->UnlistenForServiceOwnerChange("org.chromium.TestService", callback2); + base::RunLoop().RunUntilIdle(); + + // Shut down synchronously. + bus->ShutdownAndBlock(); + EXPECT_TRUE(bus->shutdown_completed()); +} |