// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "dbus/object_manager.h"
#include <stddef.h>
#include "base/bind.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/metrics/histogram.h"
#include "base/strings/stringprintf.h"
#include "base/task_runner_util.h"
#include "dbus/bus.h"
#include "dbus/dbus_statistics.h"
#include "dbus/message.h"
#include "dbus/object_proxy.h"
#include "dbus/property.h"
#include "dbus/scoped_dbus_error.h"
#include "dbus/util.h"
namespace dbus {
ObjectManager::Object::Object()
: object_proxy(nullptr) {
}
ObjectManager::Object::~Object() = default;
ObjectManager::ObjectManager(Bus* bus,
const std::string& service_name,
const ObjectPath& object_path)
: bus_(bus),
service_name_(service_name),
object_path_(object_path),
setup_success_(false),
cleanup_called_(false),
weak_ptr_factory_(this) {
LOG_IF(FATAL, !object_path_.IsValid()) << object_path_.value();
DVLOG(1) << "Creating ObjectManager for " << service_name_
<< " " << object_path_.value();
DCHECK(bus_);
bus_->AssertOnOriginThread();
object_proxy_ = bus_->GetObjectProxy(service_name_, object_path_);
object_proxy_->SetNameOwnerChangedCallback(
base::Bind(&ObjectManager::NameOwnerChanged,
weak_ptr_factory_.GetWeakPtr()));
// Set up a match rule and a filter function to handle PropertiesChanged
// signals from the service. This is important to avoid any race conditions
// that might cause us to miss PropertiesChanged signals once all objects are
// initialized via GetManagedObjects.
base::PostTaskAndReplyWithResult(
bus_->GetDBusTaskRunner(),
FROM_HERE,
base::Bind(&ObjectManager::SetupMatchRuleAndFilter, this),
base::Bind(&ObjectManager::OnSetupMatchRuleAndFilterComplete, this));
}
ObjectManager::~ObjectManager() {
// Clean up Object structures
for (ObjectMap::iterator iter = object_map_.begin();
iter != object_map_.end(); ++iter) {
Object* object = iter->second;
for (Object::PropertiesMap::iterator piter = object->properties_map.begin();
piter != object->properties_map.end(); ++piter) {
PropertySet* properties = piter->second;
delete properties;
}
delete object;
}
}
void ObjectManager::RegisterInterface(const std::string& interface_name,
Interface* interface) {
interface_map_[interface_name] = interface;
}
void ObjectManager::UnregisterInterface(const std::string& interface_name) {
InterfaceMap::iterator iter = interface_map_.find(interface_name);
if (iter != interface_map_.end())
interface_map_.erase(iter);
}
std::vector<ObjectPath> ObjectManager::GetObjects() {
std::vector<ObjectPath> object_paths;
for (ObjectMap::iterator iter = object_map_.begin();
iter != object_map_.end(); ++iter)
object_paths.push_back(iter->first);
return object_paths;
}
std::vector<ObjectPath> ObjectManager::GetObjectsWithInterface(
const std::string& interface_name) {
std::vector<ObjectPath> object_paths;
for (ObjectMap::iterator oiter = object_map_.begin();
oiter != object_map_.end(); ++oiter) {
Object* object = oiter->second;
Object::PropertiesMap::iterator piter =
object->properties_map.find(interface_name);
if (piter != object->properties_map.end())
object_paths.push_back(oiter->first);
}
return object_paths;
}
ObjectProxy* ObjectManager::GetObjectProxy(const ObjectPath& object_path) {
ObjectMap::iterator iter = object_map_.find(object_path);
if (iter == object_map_.end())
return nullptr;
Object* object = iter->second;
return object->object_proxy;
}
PropertySet* ObjectManager::GetProperties(const ObjectPath& object_path,
const std::string& interface_name) {
ObjectMap::iterator iter = object_map_.find(object_path);
if (iter == object_map_.end())
return nullptr;
Object* object = iter->second;
Object::PropertiesMap::iterator piter =
object->properties_map.find(interface_name);
if (piter == object->properties_map.end())
return nullptr;
return piter->second;
}
void ObjectManager::GetManagedObjects() {
MethodCall method_call(kObjectManagerInterface,
kObjectManagerGetManagedObjects);
object_proxy_->CallMethod(
&method_call,
ObjectProxy::TIMEOUT_USE_DEFAULT,
base::Bind(&ObjectManager::OnGetManagedObjects,
weak_ptr_factory_.GetWeakPtr()));
}
void ObjectManager::CleanUp() {
DCHECK(bus_);
bus_->AssertOnDBusThread();
DCHECK(!cleanup_called_);
cleanup_called_ = true;
if (!setup_success_)
return;
bus_->RemoveFilterFunction(&ObjectManager::HandleMessageThunk, this);
ScopedDBusError error;
bus_->RemoveMatch(match_rule_, error.get());
if (error.is_set())
LOG(ERROR) << "Failed to remove match rule: " << match_rule_;
match_rule_.clear();
}
bool ObjectManager::SetupMatchRuleAndFilter() {
DCHECK(bus_);
DCHECK(!setup_success_);
bus_->AssertOnDBusThread();
if (cleanup_called_)
return false;
if (!bus_->Connect() || !bus_->SetUpAsyncOperations())
return false;
service_name_owner_ =
bus_->GetServiceOwnerAndBlock(service_name_, Bus::SUPPRESS_ERRORS);
const std::string match_rule =
base::StringPrintf(
"type='signal', sender='%s', interface='%s', member='%s'",
service_name_.c_str(),
kPropertiesInterface,
kPropertiesChanged);
bus_->AddFilterFunction(&ObjectManager::HandleMessageThunk, this);
ScopedDBusError error;
bus_->AddMatch(match_rule, error.get());
if (error.is_set()) {
LOG(ERROR) << "ObjectManager failed to add match rule \"" << match_rule
<< "\". Got " << error.name() << ": " << error.message();
bus_->RemoveFilterFunction(&ObjectManager::HandleMessageThunk, this);
return false;
}
match_rule_ = match_rule;
setup_success_ = true;
return true;
}
void ObjectManager::OnSetupMatchRuleAndFilterComplete(bool success) {
if (!success) {
LOG(WARNING) << service_name_ << " " << object_path_.value()
<< ": Failed to set up match rule.";
return;
}
DCHECK(bus_);
DCHECK(object_proxy_);
DCHECK(setup_success_);
// |object_proxy_| is no longer valid if the Bus was shut down before this
// call. Don't initiate any other action from the origin thread.
if (cleanup_called_)
return;
object_proxy_->ConnectToSignal(
kObjectManagerInterface,
kObjectManagerInterfacesAdded,
base::Bind(&ObjectManager::InterfacesAddedReceived,
weak_ptr_factory_.GetWeakPtr()),
base::Bind(&ObjectManager::InterfacesAddedConnected,
weak_ptr_factory_.GetWeakPtr()));
object_proxy_->ConnectToSignal(
kObjectManagerInterface,
kObjectManagerInterfacesRemoved,
base::Bind(&ObjectManager::InterfacesRemovedReceived,
weak_ptr_factory_.GetWeakPtr()),
base::Bind(&ObjectManager::InterfacesRemovedConnected,
weak_ptr_factory_.GetWeakPtr()));
if (!service_name_owner_.empty())
GetManagedObjects();
}
// static
DBusHandlerResult ObjectManager::HandleMessageThunk(DBusConnection* connection,
DBusMessage* raw_message,
void* user_data) {
ObjectManager* self = reinterpret_cast<ObjectManager*>(user_data);
return self->HandleMessage(connection, raw_message);
}
DBusHandlerResult ObjectManager::HandleMessage(DBusConnection* connection,
DBusMessage* raw_message) {
DCHECK(bus_);
bus_->AssertOnDBusThread();
// Handle the message only if it is a signal.
// Note that the match rule in SetupMatchRuleAndFilter() is configured to
// only accept signals, but we check here just in case.
if (dbus_message_get_type(raw_message) != DBUS_MESSAGE_TYPE_SIGNAL)
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
// raw_message will be unrefed on exit of the function. Increment the
// reference so we can use it in Signal.
dbus_message_ref(raw_message);
std::unique_ptr<Signal> signal(Signal::FromRawMessage(raw_message));
const std::string interface = signal->GetInterface();
const std::string member = signal->GetMember();
statistics::AddReceivedSignal(service_name_, interface, member);
// Handle the signal only if it is PropertiesChanged.
// Note that the match rule in SetupMatchRuleAndFilter() is configured to
// only accept PropertiesChanged signals, but we check here just in case.
const std::string absolute_signal_name =
GetAbsoluteMemberName(interface, member);
const std::string properties_changed_signal_name =
GetAbsoluteMemberName(kPropertiesInterface, kPropertiesChanged);
if (absolute_signal_name != properties_changed_signal_name)
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
VLOG(1) << "Signal received: " << signal->ToString();
// Handle the signal only if it is from the service that the ObjectManager
// instance is interested in.
// Note that the match rule in SetupMatchRuleAndFilter() is configured to
// only accept messages from the service name of our interest. However, the
// service='...' filter does not work as intended. See crbug.com/507206#14
// and #15 for details, hence it's necessary to check the sender here.
std::string sender = signal->GetSender();
if (service_name_owner_ != sender)
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
const ObjectPath path = signal->GetPath();
if (bus_->HasDBusThread()) {
// Post a task to run the method in the origin thread. Transfer ownership of
// |signal| to NotifyPropertiesChanged, which will handle the clean up.
Signal* released_signal = signal.release();
bus_->GetOriginTaskRunner()->PostTask(
FROM_HERE,
base::Bind(&ObjectManager::NotifyPropertiesChanged,
this, path,
released_signal));
} else {
// If the D-Bus thread is not used, just call the callback on the
// current thread. Transfer the ownership of |signal| to
// NotifyPropertiesChanged.
NotifyPropertiesChanged(path, signal.release());
}
// We don't return DBUS_HANDLER_RESULT_HANDLED for signals because other
// objects may be interested in them. (e.g. Signals from org.freedesktop.DBus)
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
void ObjectManager::NotifyPropertiesChanged(
const dbus::ObjectPath object_path,
Signal* signal) {
DCHECK(bus_);
bus_->AssertOnOriginThread();
NotifyPropertiesChangedHelper(object_path, signal);
// Delete the message on the D-Bus thread. See comments in HandleMessage.
bus_->GetDBusTaskRunner()->PostTask(
FROM_HERE,
base::Bind(&base::DeletePointer<Signal>, signal));
}
void ObjectManager::NotifyPropertiesChangedHelper(
const dbus::ObjectPath object_path,
Signal* signal) {
DCHECK(bus_);
bus_->AssertOnOriginThread();
MessageReader reader(signal);
std::string interface;
if (!reader.PopString(&interface)) {
LOG(WARNING) << "Property changed signal has wrong parameters: "
<< "expected interface name: " << signal->ToString();
return;
}
PropertySet* properties = GetProperties(object_path, interface);
if (properties)
properties->ChangedReceived(signal);
}
void ObjectManager::OnGetManagedObjects(Response* response) {
if (response != nullptr) {
MessageReader reader(response);
MessageReader array_reader(nullptr);
if (!reader.PopArray(&array_reader))
return;
while (array_reader.HasMoreData()) {
MessageReader dict_entry_reader(nullptr);
ObjectPath object_path;
if (!array_reader.PopDictEntry(&dict_entry_reader) ||
!dict_entry_reader.PopObjectPath(&object_path))
continue;
UpdateObject(object_path, &dict_entry_reader);
}
} else {
LOG(WARNING) << service_name_ << " " << object_path_.value()
<< ": Failed to get managed objects";
}
}
void ObjectManager::InterfacesAddedReceived(Signal* signal) {
DCHECK(signal);
MessageReader reader(signal);
ObjectPath object_path;
if (!reader.PopObjectPath(&object_path)) {
LOG(WARNING) << service_name_ << " " << object_path_.value()
<< ": InterfacesAdded signal has incorrect parameters: "
<< signal->ToString();
return;
}
UpdateObject(object_path, &reader);
}
void ObjectManager::InterfacesAddedConnected(const std::string& interface_name,
const std::string& signal_name,
bool success) {
LOG_IF(WARNING, !success) << service_name_ << " " << object_path_.value()
<< ": Failed to connect to InterfacesAdded signal.";
}
void ObjectManager::InterfacesRemovedReceived(Signal* signal) {
DCHECK(signal);
MessageReader reader(signal);
ObjectPath object_path;
std::vector<std::string> interface_names;
if (!reader.PopObjectPath(&object_path) ||
!reader.PopArrayOfStrings(&interface_names)) {
LOG(WARNING) << service_name_ << " " << object_path_.value()
<< ": InterfacesRemoved signal has incorrect parameters: "
<< signal->ToString();
return;
}
for (size_t i = 0; i < interface_names.size(); ++i)
RemoveInterface(object_path, interface_names[i]);
}
void ObjectManager::InterfacesRemovedConnected(
const std::string& interface_name,
const std::string& signal_name,
bool success) {
LOG_IF(WARNING, !success) << service_name_ << " " << object_path_.value()
<< ": Failed to connect to "
<< "InterfacesRemoved signal.";
}
void ObjectManager::UpdateObject(const ObjectPath& object_path,
MessageReader* reader) {
DCHECK(reader);
MessageReader array_reader(nullptr);
if (!reader->PopArray(&array_reader))
return;
while (array_reader.HasMoreData()) {
MessageReader dict_entry_reader(nullptr);
std::string interface_name;
if (!array_reader.PopDictEntry(&dict_entry_reader) ||
!dict_entry_reader.PopString(&interface_name))
continue;
AddInterface(object_path, interface_name, &dict_entry_reader);
}
}
void ObjectManager::AddInterface(const ObjectPath& object_path,
const std::string& interface_name,
MessageReader* reader) {
InterfaceMap::iterator iiter = interface_map_.find(interface_name);
if (iiter == interface_map_.end())
return;
Interface* interface = iiter->second;
ObjectMap::iterator oiter = object_map_.find(object_path);
Object* object;
if (oiter == object_map_.end()) {
object = object_map_[object_path] = new Object;
object->object_proxy = bus_->GetObjectProxy(service_name_, object_path);
} else
object = oiter->second;
Object::PropertiesMap::iterator piter =
object->properties_map.find(interface_name);
PropertySet* property_set;
const bool interface_added = (piter == object->properties_map.end());
if (interface_added) {
property_set = object->properties_map[interface_name] =
interface->CreateProperties(object->object_proxy,
object_path, interface_name);
} else
property_set = piter->second;
property_set->UpdatePropertiesFromReader(reader);
if (interface_added)
interface->ObjectAdded(object_path, interface_name);
}
void ObjectManager::RemoveInterface(const ObjectPath& object_path,
const std::string& interface_name) {
ObjectMap::iterator oiter = object_map_.find(object_path);
if (oiter == object_map_.end())
return;
Object* object = oiter->second;
Object::PropertiesMap::iterator piter =
object->properties_map.find(interface_name);
if (piter == object->properties_map.end())
return;
// Inform the interface before removing the properties structure or object
// in case it needs details from them to make its own decisions.
InterfaceMap::iterator iiter = interface_map_.find(interface_name);
if (iiter != interface_map_.end()) {
Interface* interface = iiter->second;
interface->ObjectRemoved(object_path, interface_name);
}
delete piter->second;
object->properties_map.erase(piter);
if (object->properties_map.empty()) {
object_map_.erase(oiter);
delete object;
}
}
void ObjectManager::NameOwnerChanged(const std::string& old_owner,
const std::string& new_owner) {
service_name_owner_ = new_owner;
if (!old_owner.empty()) {
ObjectMap::iterator iter = object_map_.begin();
while (iter != object_map_.end()) {
ObjectMap::iterator tmp = iter;
++iter;
// PropertiesMap is mutated by RemoveInterface, and also Object is
// destroyed; easier to collect the object path and interface names
// and remove them safely.
const dbus::ObjectPath object_path = tmp->first;
Object* object = tmp->second;
std::vector<std::string> interfaces;
for (Object::PropertiesMap::iterator piter =
object->properties_map.begin();
piter != object->properties_map.end(); ++piter)
interfaces.push_back(piter->first);
for (std::vector<std::string>::iterator iiter = interfaces.begin();
iiter != interfaces.end(); ++iiter)
RemoveInterface(object_path, *iiter);
}
}
if (!new_owner.empty())
GetManagedObjects();
}
} // namespace dbus