| // Copyright 2014 The ChromiumOS Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include <base/check.h> | |
| #include <brillo/dbus/dbus_object.h> | |
| #include <memory> | |
| #include <string> | |
| #include <utility> | |
| #include <vector> | |
| #include <base/functional/bind.h> | |
| #include <base/functional/callback_helpers.h> | |
| #include <base/logging.h> | |
| #include <brillo/dbus/async_event_sequencer.h> | |
| #include <brillo/dbus/exported_object_manager.h> | |
| #include <brillo/dbus/exported_property_set.h> | |
| #include <dbus/property.h> | |
| namespace brillo { | |
| namespace dbus_utils { | |
| namespace { | |
| void SetupDefaultPropertyHandlers(DBusInterface* prop_interface, | |
| ExportedPropertySet* property_set) { | |
| prop_interface->AddSimpleMethodHandler(dbus::kPropertiesGetAll, | |
| base::Unretained(property_set), | |
| &ExportedPropertySet::HandleGetAll); | |
| prop_interface->AddSimpleMethodHandlerWithError( | |
| dbus::kPropertiesGet, base::Unretained(property_set), | |
| &ExportedPropertySet::HandleGet); | |
| prop_interface->AddSimpleMethodHandlerWithError( | |
| dbus::kPropertiesSet, base::Unretained(property_set), | |
| &ExportedPropertySet::HandleSet); | |
| } | |
| } // namespace | |
| ////////////////////////////////////////////////////////////////////////////// | |
| DBusInterface::DBusInterface(DBusObject* dbus_object, | |
| const std::string& interface_name) | |
| : dbus_object_(dbus_object), | |
| interface_name_(interface_name), | |
| release_interface_cb_(base::DoNothing()) {} | |
| void DBusInterface::AddProperty(const std::string& property_name, | |
| ExportedPropertyBase* prop_base) { | |
| dbus_object_->property_set_.RegisterProperty(interface_name_, property_name, | |
| prop_base); | |
| } | |
| void DBusInterface::RemoveProperty(const std::string& property_name) { | |
| dbus_object_->property_set_.UnregisterProperty(interface_name_, | |
| property_name); | |
| } | |
| void DBusInterface::ExportAsync( | |
| ExportedObjectManager* object_manager, | |
| dbus::Bus* /* bus */, | |
| dbus::ExportedObject* exported_object, | |
| const dbus::ObjectPath& object_path, | |
| AsyncEventSequencer::CompletionAction completion_callback) { | |
| VLOG(1) << "Registering D-Bus interface '" << interface_name_ << "' for '" | |
| << object_path.value() << "'"; | |
| scoped_refptr<AsyncEventSequencer> sequencer(new AsyncEventSequencer()); | |
| for (const auto& pair : handlers_) { | |
| std::string method_name = pair.first; | |
| VLOG(1) << "Exporting method: " << interface_name_ << "." << method_name; | |
| std::string export_error = "Failed exporting " + method_name + " method"; | |
| auto export_handler = sequencer->GetExportHandler( | |
| interface_name_, method_name, export_error, true); | |
| auto method_handler = base::BindRepeating(&DBusInterface::HandleMethodCall, | |
| base::Unretained(this)); | |
| exported_object->ExportMethod(interface_name_, method_name, method_handler, | |
| std::move(export_handler)); | |
| } | |
| std::vector<AsyncEventSequencer::CompletionAction> actions; | |
| if (object_manager) { | |
| auto property_writer_callback = | |
| dbus_object_->property_set_.GetPropertyWriter(interface_name_); | |
| actions.push_back(base::BindOnce( | |
| &DBusInterface::ClaimInterface, weak_factory_.GetWeakPtr(), | |
| object_manager->AsWeakPtr(), object_path, property_writer_callback)); | |
| } | |
| actions.push_back(std::move(completion_callback)); | |
| auto actions_cb = base::BindOnce( | |
| [](std::vector<AsyncEventSequencer::CompletionAction> actions, | |
| bool all_succeeded) { | |
| for (auto& action : actions) { | |
| std::move(action).Run(all_succeeded); | |
| } | |
| }, | |
| std::move(actions)); | |
| sequencer->OnAllTasksCompletedCall(std::move(actions_cb)); | |
| } | |
| void DBusInterface::ExportAndBlock(ExportedObjectManager* object_manager, | |
| dbus::Bus* /* bus */, | |
| dbus::ExportedObject* exported_object, | |
| const dbus::ObjectPath& object_path) { | |
| VLOG(1) << "Registering D-Bus interface '" << interface_name_ << "' for '" | |
| << object_path.value() << "'"; | |
| for (const auto& pair : handlers_) { | |
| std::string method_name = pair.first; | |
| VLOG(1) << "Exporting method: " << interface_name_ << "." << method_name; | |
| auto method_handler = base::BindRepeating(&DBusInterface::HandleMethodCall, | |
| base::Unretained(this)); | |
| if (!exported_object->ExportMethodAndBlock(interface_name_, method_name, | |
| method_handler)) { | |
| LOG(FATAL) << "Failed exporting " << method_name << " method"; | |
| } | |
| } | |
| if (object_manager) { | |
| auto property_writer_callback = | |
| dbus_object_->property_set_.GetPropertyWriter(interface_name_); | |
| ClaimInterface(object_manager->AsWeakPtr(), object_path, | |
| property_writer_callback, true); | |
| } | |
| } | |
| void DBusInterface::UnexportAsync( | |
| ExportedObjectManager* object_manager, | |
| dbus::ExportedObject* exported_object, | |
| const dbus::ObjectPath& object_path, | |
| AsyncEventSequencer::CompletionAction completion_callback) { | |
| VLOG(1) << "Unexporting D-Bus interface " << interface_name_ << " for " | |
| << object_path.value(); | |
| // Release the interface. | |
| release_interface_cb_.RunAndReset(); | |
| // Unexport all method handlers. | |
| scoped_refptr<AsyncEventSequencer> sequencer(new AsyncEventSequencer()); | |
| for (const auto& pair : handlers_) { | |
| std::string method_name = pair.first; | |
| VLOG(1) << "Unexporting method: " << interface_name_ << "." << method_name; | |
| std::string export_error = "Failed unexporting " + method_name + " method"; | |
| auto export_handler = sequencer->GetExportHandler( | |
| interface_name_, method_name, export_error, true); | |
| exported_object->UnexportMethod(interface_name_, method_name, | |
| std::move(export_handler)); | |
| } | |
| sequencer->OnAllTasksCompletedCall(std::move(completion_callback)); | |
| } | |
| void DBusInterface::UnexportAndBlock(ExportedObjectManager* object_manager, | |
| dbus::ExportedObject* exported_object, | |
| const dbus::ObjectPath& object_path) { | |
| VLOG(1) << "Unexporting D-Bus interface " << interface_name_ << " for " | |
| << object_path.value(); | |
| // Release the interface. | |
| release_interface_cb_.RunAndReset(); | |
| // Unexport all method handlers. | |
| for (const auto& pair : handlers_) { | |
| std::string method_name = pair.first; | |
| VLOG(1) << "Unexporting method: " << interface_name_ << "." << method_name; | |
| if (!exported_object->UnexportMethodAndBlock(interface_name_, method_name)) | |
| LOG(FATAL) << "Failed unexporting " << method_name << " method"; | |
| } | |
| } | |
| void DBusInterface::ClaimInterface( | |
| base::WeakPtr<ExportedObjectManager> object_manager, | |
| const dbus::ObjectPath& object_path, | |
| const ExportedPropertySet::PropertyWriter& writer, | |
| bool all_succeeded) { | |
| if (!all_succeeded || !object_manager) { | |
| LOG(ERROR) << "Skipping claiming interface: " << interface_name_; | |
| return; | |
| } | |
| object_manager->ClaimInterface(object_path, interface_name_, writer); | |
| release_interface_cb_.RunAndReset(); | |
| release_interface_cb_.ReplaceClosure( | |
| base::BindOnce(&ExportedObjectManager::ReleaseInterface, object_manager, | |
| object_path, interface_name_)); | |
| } | |
| std::vector<std::string> DBusInterface::GetMethodNames() const { | |
| std::vector<std::string> names; | |
| names.reserve(handlers_.size()); | |
| for (const auto& [name, handler] : handlers_) { | |
| names.push_back(name); | |
| } | |
| return names; | |
| } | |
| void DBusInterface::HandleMethodCall(dbus::MethodCall* method_call, | |
| ResponseSender sender) { | |
| std::string method_name = method_call->GetMember(); | |
| // Make a local copy of |interface_name_| because calling HandleMethod() | |
| // can potentially kill this interface object... | |
| std::string interface_name = interface_name_; | |
| VLOG(1) << "Received method call request: " << interface_name << "." | |
| << method_name << "(" << method_call->GetSignature() << ")"; | |
| auto pair = handlers_.find(method_name); | |
| if (pair == handlers_.end()) { | |
| auto response = dbus::ErrorResponse::FromMethodCall( | |
| method_call, DBUS_ERROR_UNKNOWN_METHOD, | |
| "Unknown method: " + method_name); | |
| std::move(sender).Run(std::move(response)); | |
| return; | |
| } | |
| VLOG(1) << "Dispatching DBus method call: " << method_name; | |
| pair->second->HandleMethod(method_call, std::move(sender)); | |
| } | |
| void DBusInterface::AddHandlerImpl( | |
| const std::string& method_name, | |
| std::unique_ptr<DBusInterfaceMethodHandlerInterface> handler) { | |
| VLOG(1) << "Declaring method handler: " << interface_name_ << "." | |
| << method_name; | |
| auto res = handlers_.insert(std::make_pair(method_name, std::move(handler))); | |
| CHECK(res.second) << "Method '" << method_name << "' already exists"; | |
| } | |
| void DBusInterface::AddSignalImpl( | |
| const std::string& signal_name, | |
| const std::shared_ptr<DBusSignalBase>& signal) { | |
| VLOG(1) << "Declaring a signal sink: " << interface_name_ << "." | |
| << signal_name; | |
| CHECK(signals_.insert(std::make_pair(signal_name, signal)).second) | |
| << "The signal '" << signal_name << "' is already registered"; | |
| } | |
| /////////////////////////////////////////////////////////////////////////////// | |
| DBusObject::DBusObject(ExportedObjectManager* object_manager, | |
| const scoped_refptr<dbus::Bus>& bus, | |
| const dbus::ObjectPath& object_path) | |
| : DBusObject::DBusObject(object_manager, | |
| bus, | |
| object_path, | |
| base::BindOnce(&SetupDefaultPropertyHandlers)) {} | |
| DBusObject::DBusObject( | |
| ExportedObjectManager* object_manager, | |
| const scoped_refptr<dbus::Bus>& bus, | |
| const dbus::ObjectPath& object_path, | |
| PropertyHandlerSetupCallback property_handler_setup_callback) | |
| : property_set_(bus.get()), | |
| bus_(bus), | |
| object_path_(object_path), | |
| property_handler_setup_callback_( | |
| std::move(property_handler_setup_callback)) { | |
| if (object_manager) | |
| object_manager_ = object_manager->AsWeakPtr(); | |
| } | |
| DBusObject::~DBusObject() { | |
| if (exported_object_) | |
| exported_object_->Unregister(); | |
| } | |
| DBusInterface* DBusObject::AddOrGetInterface( | |
| const std::string& interface_name) { | |
| auto iter = interfaces_.find(interface_name); | |
| if (iter == interfaces_.end()) { | |
| VLOG(1) << "Adding an interface '" << interface_name << "' to object '" | |
| << object_path_.value() << "'."; | |
| // Interface doesn't exist yet. Create one... | |
| std::unique_ptr<DBusInterface> new_itf( | |
| new DBusInterface(this, interface_name)); | |
| iter = | |
| interfaces_.insert(std::make_pair(interface_name, std::move(new_itf))) | |
| .first; | |
| } | |
| return iter->second.get(); | |
| } | |
| DBusInterface* DBusObject::FindInterface( | |
| const std::string& interface_name) const { | |
| auto itf_iter = interfaces_.find(interface_name); | |
| return (itf_iter == interfaces_.end()) ? nullptr : itf_iter->second.get(); | |
| } | |
| void DBusObject::RemoveInterface(const std::string& interface_name) { | |
| auto itf_iter = interfaces_.find(interface_name); | |
| CHECK(itf_iter != interfaces_.end()) | |
| << "Interface " << interface_name << " has not been added."; | |
| interfaces_.erase(itf_iter); | |
| } | |
| void DBusObject::ExportInterfaceAsync( | |
| const std::string& interface_name, | |
| AsyncEventSequencer::CompletionAction completion_callback) { | |
| AddOrGetInterface(interface_name) | |
| ->ExportAsync(object_manager_.get(), bus_.get(), exported_object_, | |
| object_path_, std::move(completion_callback)); | |
| } | |
| void DBusObject::ExportInterfaceAndBlock(const std::string& interface_name) { | |
| AddOrGetInterface(interface_name) | |
| ->ExportAndBlock(object_manager_.get(), bus_.get(), exported_object_, | |
| object_path_); | |
| } | |
| void DBusObject::UnexportInterfaceAsync( | |
| const std::string& interface_name, | |
| AsyncEventSequencer::CompletionAction completion_callback) { | |
| AddOrGetInterface(interface_name) | |
| ->UnexportAsync(object_manager_.get(), exported_object_, object_path_, | |
| std::move(completion_callback)); | |
| } | |
| void DBusObject::UnexportInterfaceAndBlock(const std::string& interface_name) { | |
| AddOrGetInterface(interface_name) | |
| ->UnexportAndBlock(object_manager_.get(), exported_object_, object_path_); | |
| } | |
| void DBusObject::RegisterAsync( | |
| AsyncEventSequencer::CompletionAction completion_callback) { | |
| VLOG(1) << "Registering D-Bus object '" << object_path_.value() << "'."; | |
| CHECK(exported_object_ == nullptr) << "Object already registered."; | |
| scoped_refptr<AsyncEventSequencer> sequencer(new AsyncEventSequencer()); | |
| exported_object_ = bus_->GetExportedObject(object_path_); | |
| RegisterPropertiesInterface(); | |
| // Export interface methods | |
| for (const auto& pair : interfaces_) { | |
| pair.second->ExportAsync( | |
| object_manager_.get(), bus_.get(), exported_object_, object_path_, | |
| sequencer->GetHandler("Failed to export interface " + pair.first, | |
| false)); | |
| } | |
| sequencer->OnAllTasksCompletedCall(std::move(completion_callback)); | |
| } | |
| void DBusObject::RegisterAndBlock() { | |
| VLOG(1) << "Registering D-Bus object '" << object_path_.value() << "'."; | |
| CHECK(exported_object_ == nullptr) << "Object already registered."; | |
| exported_object_ = bus_->GetExportedObject(object_path_); | |
| RegisterPropertiesInterface(); | |
| // Export interface methods | |
| for (const auto& pair : interfaces_) { | |
| pair.second->ExportAndBlock(object_manager_.get(), bus_.get(), | |
| exported_object_, object_path_); | |
| } | |
| } | |
| void DBusObject::UnregisterAndBlock() { | |
| VLOG(1) << "Unregistering D-Bus object '" << object_path_.value() << "'."; | |
| CHECK(exported_object_ != nullptr) << "Object not registered."; | |
| // This will unregister the object path from the bus. | |
| exported_object_->Unregister(); | |
| // This will remove |exported_object_| from bus's object table. This function | |
| // will also post a task to unregister |exported_object_| (same as the call | |
| // above), which will be a no-op since it is already done by then. | |
| // By doing both in here, the object path is guarantee to be reusable upon | |
| // return from this function. | |
| bus_->UnregisterExportedObject(object_path_); | |
| exported_object_ = nullptr; | |
| } | |
| bool DBusObject::SendSignal(dbus::Signal* signal) { | |
| if (exported_object_) { | |
| exported_object_->SendSignal(signal); | |
| return true; | |
| } | |
| LOG(ERROR) << "Trying to send a signal from an object that is not exported"; | |
| return false; | |
| } | |
| void DBusObject::RegisterPropertiesInterface() { | |
| DBusInterface* prop_interface = AddOrGetInterface(dbus::kPropertiesInterface); | |
| std::move(property_handler_setup_callback_) | |
| .Run(prop_interface, &property_set_); | |
| property_set_.OnPropertiesInterfaceExported(prop_interface); | |
| } | |
| } // namespace dbus_utils | |
| } // namespace brillo |