diff options
author | thestig@chromium.org <thestig@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-06-10 22:52:34 +0000 |
---|---|---|
committer | thestig@chromium.org <thestig@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-06-10 22:52:34 +0000 |
commit | 049616e0afc089f4816ee521e937eafd0beb335d (patch) | |
tree | 14fa8b98ed6f07d448a1308403bf34d2838f6fa1 /dbus/bus.cc | |
parent | 36116cea8b862285f0e133170fb2ec7cd09f72ae (diff) | |
download | chromium_src-049616e0afc089f4816ee521e937eafd0beb335d.zip chromium_src-049616e0afc089f4816ee521e937eafd0beb335d.tar.gz chromium_src-049616e0afc089f4816ee521e937eafd0beb335d.tar.bz2 |
Linux/CrOS: Listen for mtpd service owner change events and communicate with the new service owner.
The mtpd dbus service may not start right away. Any attempts to use it may be racy due to the lack of a service owner. Listening for service owner changes fixes this race.
BUG=241302
TEST=Manual, see bug for repro case.
Review URL: https://chromiumcodereview.appspot.com/15741025
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@205331 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'dbus/bus.cc')
-rw-r--r-- | dbus/bus.cc | 180 |
1 files changed, 179 insertions, 1 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 |