| // Copyright 2022 The Fuchsia 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 "src/devices/bin/driver_manager/node.h" |
| |
| #include <fidl/fuchsia.component/cpp/common_types_format.h> |
| #include <fidl/fuchsia.driver.framework/cpp/common_types_format.h> |
| #include <fidl/fuchsia.power.broker/cpp/fidl.h> |
| #include <lib/component/incoming/cpp/directory_watcher.h> |
| #include <lib/driver/component/cpp/internal/start_args.h> |
| #include <lib/driver/component/cpp/node_add_args.h> |
| #include <zircon/errors.h> |
| |
| #include <algorithm> |
| #include <deque> |
| #include <optional> |
| #include <queue> |
| #include <unordered_set> |
| #include <utility> |
| #include <variant> |
| |
| #include <bind/fuchsia/cpp/bind.h> |
| #include <bind/fuchsia/platform/cpp/bind.h> |
| |
| #include "src/devices/bin/driver_manager/async_sharder.h" |
| #include "src/devices/bin/driver_manager/bind/bind_result_tracker.h" |
| #include "src/devices/bin/driver_manager/bootup_tracker.h" |
| #include "src/devices/bin/driver_manager/controller_allowlist_passthrough.h" |
| #include "src/devices/bin/driver_manager/node_property_conversion.h" |
| #include "src/devices/bin/driver_manager/shutdown/node_removal_tracker.h" |
| #include "src/devices/lib/log/log.h" |
| #include "src/lib/fxl/strings/join_strings.h" |
| |
| template <> |
| struct std::formatter<driver_manager::OfferTransport> : std::formatter<std::string_view> { |
| auto format(const driver_manager::OfferTransport& transport, std::format_context& ctx) const { |
| switch (transport) { |
| case driver_manager::OfferTransport::ZirconTransport: |
| return std::formatter<std::string_view>::format("ZirconTransport", ctx); |
| case driver_manager::OfferTransport::DriverTransport: |
| return std::formatter<std::string_view>::format("DriverTransport", ctx); |
| case driver_manager::OfferTransport::Dictionary: |
| return std::formatter<std::string_view>::format("ZirconTransport", ctx); |
| } |
| } |
| }; |
| |
| namespace fdf { |
| using namespace fuchsia_driver_framework; |
| } // namespace fdf |
| namespace fdecl = fuchsia_component_decl; |
| namespace fcomponent = fuchsia_component; |
| |
| namespace driver_manager { |
| |
| void DriverHostConnection::on_fidl_error(fidl::UnbindInfo info) { |
| node_->OnDriverHostFidlError(info); |
| } |
| |
| void ComponentControllerConnection::on_fidl_error(fidl::UnbindInfo info) { |
| node_->OnComponentControllerFidlError(info); |
| } |
| |
| namespace { |
| |
| const std::string kUnboundUrl = "unbound"; |
| const std::string kOwnedByParentUrl = "owned by parent"; |
| const std::string kCompositeParent = "owned by composite(s)"; |
| |
| // TODO(https://fxbug.dev/42075799): Remove this flag once composite node spec rebind once all |
| // clients are updated to the new Rebind() behavior and this is fully implemented on both DFv1 and |
| // DFv2. |
| constexpr bool kEnableCompositeNodeSpecRebind = false; |
| |
| const char* CollectionName(Collection collection) { |
| switch (collection) { |
| case Collection::kNone: |
| return ""; |
| case Collection::kBoot: |
| return "boot-drivers"; |
| case Collection::kPackage: |
| return "base-drivers"; |
| case Collection::kFullPackage: |
| return "full-drivers"; |
| } |
| } |
| |
| zx::result<> RegistrationErrorToResult(fuchsia_power_broker::RegisterDependencyTokenError e) { |
| switch (e) { |
| case fuchsia_power_broker::RegisterDependencyTokenError::kAlreadyInUse: |
| return zx::error(ZX_ERR_ALREADY_EXISTS); |
| case fuchsia_power_broker::RegisterDependencyTokenError::kInternal: |
| return zx::error(ZX_ERR_INTERNAL); |
| default: |
| ZX_PANIC("Unknown register dependency token error"); |
| } |
| } |
| |
| // Processes the offer by validating it has a source_name and adding a source ref to it. |
| // Returns the offer back out. |
| fit::result<fdf::NodeError, NodeOffer> ProcessNodeOffer(const fdf::Offer& add_offer, |
| Collection source_collection, |
| std::string_view source_name) { |
| std::optional<OfferTransport> transport; |
| std::optional<fdecl::Offer> fdecl_offer; |
| |
| switch (add_offer.Which()) { |
| case fdf::Offer::Tag::kZirconTransport: |
| fdecl_offer.emplace(add_offer.zircon_transport().value()); |
| transport.emplace(OfferTransport::ZirconTransport); |
| break; |
| case fdf::Offer::Tag::kDriverTransport: |
| fdecl_offer.emplace(add_offer.driver_transport().value()); |
| transport.emplace(OfferTransport::DriverTransport); |
| break; |
| case fdf::Offer::Tag::kDictionaryOffer: |
| fdecl_offer.emplace(add_offer.dictionary_offer().value()); |
| transport.emplace(OfferTransport::Dictionary); |
| break; |
| default: |
| fdf_log::error("Unknown offer transport type {}", static_cast<uint32_t>(add_offer.Which())); |
| return fit::error(fdf::NodeError::kInternal); |
| } |
| |
| auto service_offer = fdecl_offer->service(); |
| if (!service_offer.has_value()) { |
| return fit::as_error(fdf::NodeError::kUnsupportedArgs); |
| } |
| |
| if (!service_offer->source_name().has_value()) { |
| return fit::as_error(fdf::NodeError::kOfferSourceNameMissing); |
| } |
| |
| if (service_offer->target_name().has_value() && |
| service_offer->target_name().value() != service_offer->source_name().value()) { |
| return fit::as_error(fdf::NodeError::kUnsupportedArgs); |
| } |
| |
| if (service_offer->source().has_value() || service_offer->target().has_value()) { |
| return fit::as_error(fdf::NodeError::kOfferRefExists); |
| } |
| |
| if (!service_offer->source_instance_filter().has_value()) { |
| return fit::as_error(fdf::NodeError::kOfferSourceInstanceFilterMissing); |
| } |
| |
| if (!service_offer->renamed_instances().has_value()) { |
| return fit::as_error(fdf::NodeError::kOfferRenamedInstancesMissing); |
| } |
| |
| if (transport.value() == OfferTransport::Dictionary) { |
| source_name = "dictionary"; |
| source_collection = Collection::kNone; |
| } |
| |
| return fit::ok(NodeOffer{ |
| .source_name = std::string(source_name), |
| .source_collection = source_collection, |
| .transport = transport.value(), |
| .service_name = service_offer->source_name().value(), |
| .source_instance_filter = service_offer->source_instance_filter().value(), |
| .renamed_instances = service_offer->renamed_instances().value(), |
| }); |
| } |
| |
| // Processes the offer by validating it has a source_name and adding a source ref to it. |
| // Returns a tuple containing the offer as well as node property that provides transport |
| // information for the offer. |
| fit::result<fdf::NodeError, std::tuple<NodeOffer, fdf::NodeProperty2>> |
| ProcessNodeOfferWithTransportProperty(const fdf::Offer& add_offer, Collection source_collection, |
| std::string_view source_name) { |
| fit::result result = ProcessNodeOffer(add_offer, source_collection, source_name); |
| if (result.is_error()) { |
| return result.take_error(); |
| } |
| |
| NodeOffer processed_offer = std::move(result.value()); |
| |
| const std::string& name = processed_offer.service_name; |
| auto node_property = |
| fdf::MakeProperty2(name, std::format("{}.{}", name, processed_offer.transport)); |
| |
| return fit::ok(std::make_tuple(std::move(processed_offer), std::move(node_property))); |
| } |
| |
| bool IsDefaultOffer(std::string_view target_name) { return target_name == "default"; } |
| |
| template <typename T> |
| void CloseIfExists(std::optional<fidl::ServerBinding<T>>& ref) { |
| if (ref) { |
| ref->Close(ZX_OK); |
| } |
| } |
| |
| fit::result<fdf::NodeError> ValidateSymbols(std::vector<fdf::NodeSymbol>& symbols) { |
| std::unordered_set<std::string_view> names; |
| for (auto& symbol : symbols) { |
| if (!symbol.name().has_value()) { |
| fdf_log::error("SymbolError: a symbol is missing a name"); |
| return fit::error(fdf::NodeError::kSymbolNameMissing); |
| } |
| if (!symbol.address().has_value()) { |
| fdf_log::error("SymbolError: symbol '{}' is missing an address", symbol.name().value()); |
| return fit::error(fdf::NodeError::kSymbolAddressMissing); |
| } |
| auto [_, inserted] = names.emplace(symbol.name().value()); |
| if (!inserted) { |
| fdf_log::error("SymbolError: symbol '{}' already exists", symbol.name().value()); |
| return fit::error(fdf::NodeError::kSymbolAlreadyExists); |
| } |
| } |
| return fit::ok(); |
| } |
| |
| } // namespace |
| |
| fdf::Offer ToFidl(const NodeOffer& offer) { |
| auto service = fdecl::OfferService{{ |
| .source = fdecl::Ref::WithChild(fdecl::ChildRef{{ |
| .name = offer.source_name, |
| .collection = CollectionName(offer.source_collection), |
| }}), |
| .source_name = offer.service_name, |
| .target_name = offer.service_name, |
| .source_instance_filter = offer.source_instance_filter, |
| .renamed_instances = offer.renamed_instances, |
| }}; |
| |
| auto fdecl_offer = fdecl::Offer::WithService(service); |
| switch (offer.transport) { |
| case OfferTransport::ZirconTransport: |
| return fdf::Offer::WithZirconTransport(std::move(fdecl_offer)); |
| case OfferTransport::DriverTransport: |
| return fdf::Offer::WithDriverTransport(std::move(fdecl_offer)); |
| case OfferTransport::Dictionary: |
| return fdf::Offer::WithDictionaryOffer(std::move(fdecl_offer)); |
| } |
| } |
| |
| NodeOffer CreateCompositeOffer(const NodeOffer& offer, std::string_view parents_name, |
| bool primary_parent) { |
| size_t new_instance_count = offer.renamed_instances.size(); |
| if (primary_parent) { |
| for (const auto& instance : offer.renamed_instances) { |
| if (IsDefaultOffer(instance.target_name())) { |
| new_instance_count++; |
| } |
| } |
| } |
| |
| size_t new_filter_count = offer.source_instance_filter.size(); |
| if (primary_parent) { |
| for (const auto& filter : offer.source_instance_filter) { |
| if (IsDefaultOffer(filter)) { |
| new_filter_count++; |
| } |
| } |
| } |
| |
| std::vector<fdecl::NameMapping> mappings; |
| mappings.reserve(new_instance_count); |
| for (const auto& instance : offer.renamed_instances) { |
| // The instance is not "default", so copy it over. |
| if (!IsDefaultOffer(instance.target_name())) { |
| mappings.emplace_back(instance); |
| continue; |
| } |
| |
| // We are the primary parent, so add the "default" offer. |
| if (primary_parent) { |
| mappings.emplace_back(instance); |
| } |
| |
| // Rename the instance to match the parent's name. |
| mappings.emplace_back(instance.source_name(), std::string(parents_name)); |
| } |
| ZX_ASSERT(mappings.size() == new_instance_count); |
| |
| std::vector<std::string> filters; |
| filters.reserve(new_filter_count); |
| for (const auto& filter : offer.source_instance_filter) { |
| // The filter is not "default", so copy it over. |
| if (!IsDefaultOffer(filter)) { |
| filters.push_back(filter); |
| continue; |
| } |
| |
| // We are the primary parent, so add the "default" filter. |
| if (primary_parent) { |
| filters.push_back("default"); |
| } |
| |
| // Rename the filter to match the parent's name. |
| filters.emplace_back(parents_name); |
| } |
| ZX_ASSERT(filters.size() == new_filter_count); |
| |
| return NodeOffer{ |
| .source_name = offer.source_name, |
| .source_collection = offer.source_collection, |
| .transport = offer.transport, |
| .service_name = offer.service_name, |
| .source_instance_filter = std::move(filters), |
| .renamed_instances = std::move(mappings), |
| }; |
| } |
| |
| Node::Node(std::string_view name, std::weak_ptr<Node> parent, NodeManager* node_manager, |
| async_dispatcher_t* dispatcher) |
| : name_(name), |
| type_(Normal{.parent_ = std::move(parent)}), |
| node_manager_(node_manager), |
| dispatcher_(dispatcher), |
| driver_host_handler_(this), |
| component_controller_handler_(this) { |
| // By default, we set `driver_host_` to match the primary parent's |
| // `driver_host_`. If the node is then subsequently bound to a driver in a |
| // different driver host, this value will be updated to match. |
| if (auto* parent = GetPrimaryParent(); parent) { |
| driver_host_ = parent->driver_host_; |
| } |
| zx::event::create(0, &power_element_token_); |
| } |
| |
| Node::Node(std::string_view name, std::vector<std::weak_ptr<Node>> parents, |
| std::vector<std::string> parents_names, NodeManager* node_manager, |
| async_dispatcher_t* dispatcher, uint32_t primary_index) |
| : name_(name), |
| type_(Composite{ |
| .parents_ = std::move(parents), |
| .parents_names_ = std::move(parents_names), |
| .primary_index_ = primary_index, |
| }), |
| node_manager_(node_manager), |
| dispatcher_(dispatcher), |
| driver_host_handler_(this), |
| component_controller_handler_(this) { |
| ZX_ASSERT(primary_index < std::get<Composite>(type_).parents_.size()); |
| |
| // By default, we set `driver_host_` to match the primary parent's |
| // `driver_host_`. If the node is then subsequently bound to a driver in a |
| // different driver host, this value will be updated to match. |
| driver_host_ = GetPrimaryParent()->driver_host_; |
| zx::event::create(0, &power_element_token_); |
| } |
| |
| zx::result<std::shared_ptr<Node>> Node::CreateCompositeNode( |
| std::string_view node_name, std::vector<std::weak_ptr<Node>> parents, |
| std::vector<std::string> parents_names, |
| const std::vector<fuchsia_driver_framework::NodePropertyEntry2>& parent_properties, |
| NodeManager* driver_binder, async_dispatcher_t* dispatcher, |
| std::string_view driver_host_name_for_colocation, uint32_t primary_index) { |
| ZX_ASSERT(!parents.empty()); |
| |
| if (parents.size() != parent_properties.size()) { |
| fdf_log::error( |
| "Missing parent properties. Expected {} entries, equal to the number of parents {}.", |
| parent_properties.size(), parents.size()); |
| return zx::error(ZX_ERR_INVALID_ARGS); |
| } |
| |
| if (primary_index >= parents.size()) { |
| fdf_log::error("Primary node index is out of bounds"); |
| return zx::error(ZX_ERR_INVALID_ARGS); |
| } |
| |
| auto primary_node_ptr = parents[primary_index].lock(); |
| if (!primary_node_ptr) { |
| fdf_log::error("Primary node freed before use"); |
| return zx::error(ZX_ERR_INTERNAL); |
| } |
| std::shared_ptr composite = |
| std::make_shared<Node>(node_name, std::move(parents), std::move(parents_names), driver_binder, |
| dispatcher, primary_index); |
| |
| composite->SetCompositeParentProperties(parent_properties); |
| composite->driver_host_name_for_colocation_ = driver_host_name_for_colocation; |
| |
| Node* primary = composite->GetPrimaryParent(); |
| // We know that our device has a parent because we're creating it. |
| ZX_ASSERT(primary); |
| |
| // Copy the symbols from the primary parent. |
| composite->symbols_ = primary->symbols_; |
| |
| bool has_dictionary_offer = false; |
| |
| // Copy the offers from each parent. |
| std::vector<NodeOffer> node_offers; |
| size_t parent_index = 0; |
| for (const std::weak_ptr<Node>& parent : std::get<Composite>(composite->type_).parents_) { |
| auto parent_ptr = parent.lock(); |
| if (!parent_ptr) { |
| fdf_log::error("Composite parent node freed before use"); |
| return zx::error(ZX_ERR_INTERNAL); |
| } |
| auto parent_offers = parent_ptr->offers(); |
| node_offers.reserve(node_offers.size() + parent_offers.size()); |
| |
| for (auto& parent_offer : parent_offers) { |
| NodeOffer offer = CreateCompositeOffer( |
| parent_offer, std::get<Composite>(composite->type_).parents_names_[parent_index], |
| parent_index == primary_index); |
| |
| if (offer.transport == OfferTransport::Dictionary) { |
| has_dictionary_offer = true; |
| } |
| |
| node_offers.push_back(std::move(offer)); |
| } |
| |
| parent_index++; |
| } |
| |
| // Copy the subtree dictionary of the primary parent node down to the composite. |
| if (primary->subtree_dictionary_ref_.has_value()) { |
| composite->subtree_dictionary_ref_ = primary->subtree_dictionary_ref_; |
| ZX_ASSERT_MSG(!has_dictionary_offer, "Cannot use dictionary offers on node %s", |
| std::string(node_name).c_str()); |
| } |
| |
| composite->offers_ = std::move(node_offers); |
| composite->AddToParents(); |
| ZX_ASSERT_MSG(primary->devfs_device_.topological_node().has_value(), "%s", |
| composite->MakeTopologicalPath().c_str()); |
| |
| // TODO(https://fxbug.dev/331779666): disable controller access for composite nodes |
| primary->devfs_device_.topological_node().value().add_child( |
| composite->name_, std::nullopt, |
| composite->CreateDevfsPassthrough(std::nullopt, std::nullopt, true, ""), |
| composite->devfs_device_); |
| composite->devfs_device_.publish(); |
| return zx::ok(std::move(composite)); |
| } |
| |
| Node::~Node() { |
| // TODO(https://fxbug.dev/42085057): Notify the NodeRemovalTracker if the node is deallocated |
| // before shutdown is complete. |
| if (GetNodeState() != NodeState::kDestroyed) { |
| fdf_log::info("Node {} deallocating while at state {}", MakeComponentMoniker(), |
| GetNodeShutdownCoordinator().NodeStateAsString()); |
| } |
| |
| if (bind_wait_completer_) { |
| bind_wait_completer_(zx::error(ZX_ERR_CANCELED)); |
| } |
| CloseIfExists(controller_ref_); |
| if (auto* driver_component = std::get_if<DriverComponent>(&state_); driver_component) { |
| driver_component->node_ref_.Close(ZX_OK); |
| } else if (auto* node = std::get_if<OwnedByParent>(&state_); node) { |
| node->node_ref_.Close(ZX_OK); |
| } |
| component_controller_ = {}; |
| |
| for (auto& completer : unbinding_children_completers_) { |
| completer.Reply(zx::error(ZX_ERR_CANCELED)); |
| } |
| |
| if (pending_bind_completer_) { |
| pending_bind_completer_(zx::error(ZX_ERR_CANCELED)); |
| } |
| |
| if (composite_rebind_completer_) { |
| fdf_log::warn("Unable to rebind node {} since it deallocated before completing shutdown", |
| MakeComponentMoniker()); |
| composite_rebind_completer_(zx::error(ZX_ERR_CANCELED)); |
| } |
| } |
| |
| const std::string& Node::driver_url() const { |
| if (auto* driver_component = std::get_if<DriverComponent>(&state_); driver_component) { |
| return driver_component->driver_url; |
| } |
| |
| if (auto* starting = std::get_if<Starting>(&state_); starting) { |
| return starting->driver_url; |
| } |
| |
| if (auto* quarantined = std::get_if<Quarantined>(&state_); quarantined) { |
| return quarantined->driver_url; |
| } |
| |
| if (std::holds_alternative<OwnedByParent>(state_)) { |
| return kOwnedByParentUrl; |
| } |
| |
| if (std::holds_alternative<CompositeParent>(state_)) { |
| return kCompositeParent; |
| } |
| |
| return kUnboundUrl; |
| } |
| |
| std::string Node::MakeTopologicalPath(bool deduplicate) const { |
| std::deque<std::string_view> names; |
| std::string_view prev; |
| for (auto node = this; node != nullptr; node = node->GetPrimaryParent()) { |
| std::string_view name = node->name(); |
| if (!deduplicate || name != prev) { |
| names.push_front(name); |
| prev = name; |
| } |
| } |
| return fxl::JoinStrings(names, "/"); |
| } |
| |
| std::string Node::MakeComponentMoniker() const { |
| std::string topo_path = MakeTopologicalPath(true); |
| |
| const std::string_view kPrefix = "dev/sys/platform/pt/"; |
| const std::string_view kPrefix2 = "dev/sys/platform/"; |
| if (topo_path == "dev") { |
| topo_path = "root"; |
| } else if (topo_path == "dev/sys/platform/pt") { |
| topo_path = "board"; |
| } else if (topo_path.starts_with(kPrefix)) { |
| topo_path.erase(0, kPrefix.length()); |
| } else if (topo_path.starts_with(kPrefix2)) { |
| topo_path.erase(0, kPrefix2.length()); |
| } |
| |
| // The driver's component name is based on the node name, which means that the |
| // node name cam only have [a-z0-9-_.] characters. DFv1 composites contain ':' |
| // which is not allowed, so replace those characters. |
| // TODO(https://fxbug.dev/42062456): Migrate driver names to only use CF valid characters. |
| std::ranges::replace(topo_path, ':', '_'); |
| // Since we use '.' to denote topology, replace them with '_'. |
| std::ranges::replace(topo_path, '.', '_'); |
| std::ranges::replace(topo_path, '/', '.'); |
| return topo_path; |
| } |
| |
| void Node::SetController(fidl::ClientEnd<fcomponent::Controller> component_controller) { |
| component_controller_.Bind(std::move(component_controller), dispatcher_, |
| &component_controller_handler_); |
| } |
| |
| void Node::SetShouldDestroy() { should_destroy_ = true; } |
| |
| bool g_use_test_process_koid = false; |
| |
| void Node::OnBind() { |
| if (controller_ref_) { |
| zx::result<zx::event> node_token = DuplicateNodeToken(); |
| if (node_token.is_error()) { |
| return; |
| } |
| |
| fit::result result = |
| fidl::SendEvent(*controller_ref_)->OnBind({{.node_token = std::move(node_token.value())}}); |
| if (result.is_error()) { |
| fdf_log::error("Failed to send OnBind event: {}", result.error_value().FormatDescription()); |
| } |
| } |
| |
| zx::result node_token = DuplicateNodeToken(); |
| if (node_token.is_error()) { |
| return; |
| } |
| auto koid = token_koid(); |
| if (!koid) { |
| return; |
| } |
| |
| zx_koid_t process_koid; |
| if (g_use_test_process_koid) { |
| process_koid = 1337; |
| } else { |
| zx::result result = driver_host()->GetProcessKoid(); |
| if (result.is_error()) { |
| return; |
| } |
| process_koid = result.value(); |
| } |
| node_manager_.value()->memory_attributor().AddDriver(std::move(node_token.value()), koid.value(), |
| process_koid); |
| |
| // Report on successes immediately without waiting for boot-up to complete. |
| bind_err_ = {}; |
| |
| if (IsComposite()) { |
| for (auto& parent_ref : parents()) { |
| std::shared_ptr<Node> parent = parent_ref.lock(); |
| if (parent && parent->bind_wait_completer_) { |
| zx::result<zx::event> node_token = DuplicateNodeToken(); |
| if (node_token.is_error()) { |
| return; |
| } |
| |
| parent->bind_wait_completer_(zx::ok( |
| fdf::wire::DriverResult::WithDriverStartedNodeToken(std::move(node_token.value())))); |
| } |
| } |
| } else if (bind_wait_completer_) { |
| zx::result<zx::event> node_token = DuplicateNodeToken(); |
| if (node_token.is_error()) { |
| return; |
| } |
| bind_wait_completer_( |
| zx::ok(fdf::wire::DriverResult::WithDriverStartedNodeToken(std::move(node_token.value())))); |
| } |
| } |
| |
| void Node::OnMatchError(zx_status_t status) { |
| // Set a value that we can use in the boot-up callback that we set in WaitForDriver. |
| bind_err_ = fdf::wire::DriverResult::WithMatchError(status); |
| } |
| void Node::OnStartError(zx_status_t status) { |
| // Set a value that we can use in the boot-up callback that we set in WaitForDriver. |
| bind_err_ = fdf::wire::DriverResult::WithStartError(status); |
| } |
| |
| void Node::handle_unknown_method( |
| fidl::UnknownMethodMetadata<fuchsia_component_runner::ComponentController> metadata, |
| fidl::UnknownMethodCompleter::Sync& completer) { |
| fdf_log::info("Unknown ComponentController method request received: {}", metadata.method_ordinal); |
| } |
| |
| void Node::Stop(StopCompleter::Sync& completer) { |
| fdf_log::debug("Calling Remove on {} because of Stop() from component framework.", name()); |
| Remove(RemovalSet::kAll, nullptr); |
| } |
| |
| void Node::Kill(KillCompleter::Sync& completer) { |
| fdf_log::debug("Calling Remove on {} because of Kill() from component framework.", name()); |
| Remove(RemovalSet::kAll, nullptr); |
| } |
| |
| void Node::CompleteBind(zx::result<> result) { |
| ZX_ASSERT(!std::holds_alternative<OwnedByParent>(state_)); |
| |
| if (result.is_error()) { |
| fdf_log::warn("Bind failed for node '{}'", MakeComponentMoniker()); |
| |
| if (GetNodeState() == NodeState::kRunning && !std::holds_alternative<Unbound>(state_)) { |
| fdf_log::debug("Quarantining node '{}'", MakeComponentMoniker()); |
| QuarantineNode(); |
| } else { |
| state_ = Unbound{}; |
| } |
| } |
| |
| if (auto* driver_component = std::get_if<DriverComponent>(&state_); driver_component) { |
| if (driver_component->state == DriverState::kStopped) { |
| fdf_log::warn( |
| "Node {} in state {} completed bind with result {} but the component is already Stopped.", |
| name(), GetNodeShutdownCoordinator().NodeStateAsString(), result); |
| |
| // Even if the driver started successfully, the component is no longer there (this can happen |
| // if component framework is shutting down the system) so we can't say the bind succeeded. |
| result = zx::error(ZX_ERR_CANCELED); |
| } else { |
| ZX_ASSERT_MSG(driver_component->state == DriverState::kBinding, |
| "Node %s CompleteBind() invoked at invalid state %d", name().c_str(), |
| driver_component->state); |
| driver_component->state = DriverState::kRunning; |
| OnBind(); |
| } |
| } |
| |
| if (pending_bind_completer_) { |
| pending_bind_completer_(result); |
| } |
| |
| GetNodeShutdownCoordinator().CheckNodeState(); |
| } |
| |
| void Node::AddToParents() { |
| auto this_node = shared_from_this(); |
| for (auto& parent : parents()) { |
| if (auto ptr = parent.lock(); ptr) { |
| ptr->children_.push_back(this_node); |
| continue; |
| } |
| fdf_log::warn("Parent freed before child {} could be added to it", name()); |
| } |
| } |
| |
| NodeShutdownCoordinator& Node::GetNodeShutdownCoordinator() { |
| if (!node_shutdown_coordinator_) { |
| bool is_shutdown_test_delay_enabled = |
| node_manager_.has_value() && node_manager_.value()->IsTestShutdownDelayEnabled(); |
| auto shutdown_rng = node_manager_.has_value() ? node_manager_.value()->GetShutdownTestRng() |
| : std::weak_ptr<std::mt19937>(); |
| node_shutdown_coordinator_ = std::make_unique<NodeShutdownCoordinator>( |
| name_, this, dispatcher_, is_shutdown_test_delay_enabled, shutdown_rng); |
| } |
| return *node_shutdown_coordinator_; |
| } |
| |
| // TODO(https://fxbug.dev/42075799): If the node invoking this function cannot multibind to |
| // composites, is parenting one composite node, and is not in a state for removal, then it should |
| // attempt to bind to something else. |
| void Node::RemoveChild(const std::shared_ptr<Node>& child) { |
| fdf_log::debug("RemoveChild {} from parent {}", child->name(), name()); |
| std::erase(children_, child); |
| if (!unbinding_children_completers_.empty() && children_.empty()) { |
| for (auto& completer : unbinding_children_completers_) { |
| completer.ReplySuccess(); |
| } |
| unbinding_children_completers_.clear(); |
| } |
| GetNodeShutdownCoordinator().CheckNodeState(); |
| } |
| |
| void Node::FinishShutdown(fit::callback<void()> shutdown_callback) { |
| ZX_ASSERT_MSG(GetNodeState() == NodeState::kWaitingOnDestroy, |
| "FinishShutdown called in invalid node state: %s", |
| GetNodeShutdownCoordinator().NodeStateAsString()); |
| if (auto koid = token_koid(); koid.has_value()) { |
| node_manager_.value()->memory_attributor().RemoveDriver(koid.value()); |
| } |
| if (shutdown_intent() == ShutdownIntent::kRestart) { |
| fdf_log::debug("Node: {} finishing restart", name()); |
| shutdown_callback(); |
| FinishRestart(); |
| return; |
| } |
| |
| if (shutdown_intent() == ShutdownIntent::kQuarantine) { |
| fdf_log::debug("Node: {} finishing quarantine", name()); |
| shutdown_callback(); |
| FinishQuarantine(); |
| return; |
| } |
| |
| if (bind_wait_completer_) { |
| bind_wait_completer_(zx::error(ZX_ERR_CANCELED)); |
| } |
| fdf_log::debug("Node: {} finishing shutdown", name()); |
| CloseIfExists(controller_ref_); |
| if (auto* driver_component = std::get_if<DriverComponent>(&state_); driver_component) { |
| driver_component->node_ref_.Close(ZX_OK); |
| } else if (auto* node = std::get_if<OwnedByParent>(&state_); node) { |
| node->node_ref_.Close(ZX_OK); |
| } |
| devfs_device_.unpublish(); |
| |
| // Store a shared_ptr to ourselves so we won't be freed halfway through this function. |
| std::shared_ptr this_node = shared_from_this(); |
| for (auto& parent : parents()) { |
| if (auto ptr = parent.lock(); ptr) { |
| ptr->RemoveChild(this_node); |
| continue; |
| } |
| fdf_log::warn("Parent freed before child {} could be removed from it", name()); |
| } |
| state_ = Unbound{}; |
| |
| if (IsComposite()) { |
| std::get<Composite>(type_).parents_.clear(); |
| } else { |
| std::get<Normal>(type_).parent_ = {}; |
| } |
| |
| shutdown_callback(); |
| |
| if (remove_complete_callback_) { |
| remove_complete_callback_(); |
| } |
| |
| if (shutdown_intent() == ShutdownIntent::kRebindComposite && composite_rebind_completer_) { |
| composite_rebind_completer_(zx::ok()); |
| } |
| } |
| |
| void Node::FinishRestart() { |
| ZX_ASSERT_MSG(shutdown_intent() == ShutdownIntent::kRestart, |
| "FinishRestart called when node is not restarting."); |
| |
| GetNodeShutdownCoordinator().ResetShutdown(); |
| |
| // Store previous url before we reset the state_. |
| std::string previous_url = driver_url(); |
| |
| // Perform cleanups for previous driver before we try to start the next driver. |
| if (auto* driver_component = std::get_if<DriverComponent>(&state_); driver_component) { |
| driver_component->node_ref_.Close(ZX_OK); |
| } else if (auto* node = std::get_if<OwnedByParent>(&state_); node) { |
| node->node_ref_.Close(ZX_OK); |
| } |
| state_ = Unbound{}; |
| |
| if (restart_driver_url_suffix_.has_value()) { |
| auto tracker = CreateBindResultTracker(); |
| node_manager_.value()->BindToUrl(*this, restart_driver_url_suffix_.value(), std::move(tracker)); |
| restart_driver_url_suffix_.reset(); |
| return; |
| } |
| |
| zx::result start_result = |
| node_manager_.value()->StartDriver(*this, previous_url, driver_package_type_); |
| if (start_result.is_error()) { |
| fdf_log::error("Failed to start driver '{}': {}", name(), start_result); |
| } |
| } |
| |
| void Node::FinishQuarantine() { |
| ZX_ASSERT_MSG(shutdown_intent() == ShutdownIntent::kQuarantine, |
| "FinishQuarantine called when node is not quarantining."); |
| |
| GetNodeShutdownCoordinator().ResetShutdown(); |
| |
| // |QuarantineNode()| sets this. |
| ZX_ASSERT_MSG(std::holds_alternative<Quarantined>(state_), |
| "Node::state_ was not set to Quarantined"); |
| } |
| |
| void Node::ClearHostDriver() { |
| if (auto* driver_component = std::get_if<DriverComponent>(&state_); driver_component) { |
| driver_component->driver = {}; |
| } |
| } |
| |
| // State table for package driver: |
| // Initial States |
| // Running | Prestop| WoC | WoDriver | Stopping |
| // Remove(kPkg) WoC | WoC | Ignore | Error! | Error! |
| // Remove(kAll) WoC | WoC | WoC | Error! | Error! |
| // children empty N/A | N/A |WoDriver| Error! | Error! |
| // Driver exit WoC | WoC | WoC | Stopping | Error! |
| // |
| // State table for boot driver: |
| // Initial States |
| // Running | Prestop | WoC | WoDriver | Stopping |
| // Remove(kPkg) Prestop | Ignore | Ignore | Ignore | Ignore |
| // Remove(kAll) WoC | WoC | Ignore | Ignore | Ignore |
| // children empty N/A | N/A |WoDriver| Ignore | Ignore |
| // Driver exit WoC | WoC | WoC | Stopping | Ignore |
| // Boot drivers go into the Prestop state when Remove(kPackage) is set, to signify that |
| // a removal is taking place, but this node will not be removed yet, even if all its children |
| // are removed. |
| void Node::Remove(RemovalSet removal_set, NodeRemovalTracker* removal_tracker) { |
| NodeShutdownCoordinator::Remove(shared_from_this(), removal_set, removal_tracker); |
| } |
| |
| void Node::RestartNode() { |
| GetNodeShutdownCoordinator().set_shutdown_intent(ShutdownIntent::kRestart); |
| Remove(RemovalSet::kAll, nullptr); |
| } |
| |
| void Node::QuarantineNode() { |
| // Perform cleanups for previous driver. |
| if (auto* driver_component = std::get_if<DriverComponent>(&state_); driver_component) { |
| driver_component->node_ref_.Close(ZX_OK); |
| } else { |
| ZX_ASSERT(std::holds_alternative<Starting>(state_)); |
| } |
| state_ = Quarantined{.driver_url = driver_url()}; |
| |
| GetNodeShutdownCoordinator().set_shutdown_intent(ShutdownIntent::kQuarantine); |
| Remove(RemovalSet::kAll, nullptr); |
| } |
| |
| // TODO(https://fxbug.dev/42082343): Handle the case in which this function is called during node |
| // removal. |
| void Node::RestartNodeWithRematch(std::optional<std::string> restart_driver_url_suffix, |
| fit::callback<void(zx::result<>)> completer) { |
| if (pending_bind_completer_) { |
| completer(zx::error(ZX_ERR_ALREADY_EXISTS)); |
| return; |
| } |
| |
| pending_bind_completer_ = std::move(completer); |
| restart_driver_url_suffix_ = std::move(restart_driver_url_suffix); |
| RestartNode(); |
| } |
| |
| void Node::RestartNodeWithRematch() { |
| RestartNodeWithRematch("", [](zx::result<> result) {}); |
| } |
| |
| // TODO(https://fxbug.dev/42082343): Handle the case in which this function is called during node |
| // removal. |
| void Node::RemoveCompositeNodeForRebind(fit::callback<void(zx::result<>)> completer) { |
| if (composite_rebind_completer_) { |
| completer(zx::error(ZX_ERR_ALREADY_EXISTS)); |
| return; |
| } |
| |
| if (!IsComposite()) { |
| completer(zx::error(ZX_ERR_NOT_SUPPORTED)); |
| return; |
| } |
| |
| composite_rebind_completer_ = std::move(completer); |
| GetNodeShutdownCoordinator().set_shutdown_intent(ShutdownIntent::kRebindComposite); |
| Remove(RemovalSet::kAll, nullptr); |
| } |
| |
| std::shared_ptr<BindResultTracker> Node::CreateBindResultTracker(bool silent) { |
| return std::make_shared<BindResultTracker>( |
| 1, [weak_self = weak_from_this(), |
| silent](fidl::VectorView<fuchsia_driver_development::wire::NodeBindingInfo> info) { |
| std::shared_ptr self = weak_self.lock(); |
| if (!self) { |
| return; |
| } |
| // We expect a single successful "bind". If we don't get it, we can assume the bind |
| // request failed. If we do get it, we will continue to wait for the driver's start hook |
| // to complete, which will only occur after the successful bind. The remaining flow will |
| // be similar to the RestartNode flow. |
| if (info.size() < 1) { |
| // Failed binding attempt should make the node have an unbound url. Reset this in case |
| // there was a previous driver on this node that had failed to start and was stored |
| // as Quarantined{} in state_ as part of the node quarantining. |
| self->state_ = Unbound{}; |
| |
| self->OnMatchError(ZX_ERR_NOT_FOUND); |
| if (!silent) { |
| self->CompleteBind(zx::error(ZX_ERR_NOT_FOUND)); |
| } |
| } else if (info.size() > 1) { |
| fdf_log::error("Unexpectedly bound multiple drivers to a single node"); |
| self->OnMatchError(ZX_ERR_BAD_STATE); |
| if (!silent) { |
| self->CompleteBind(zx::error(ZX_ERR_BAD_STATE)); |
| } |
| } |
| }); |
| } |
| |
| std::vector<Node::Property> Node::ToProperty(std::span<const fdf::NodeProperty2> properties) { |
| std::vector<Property> node_properties; |
| node_properties.reserve(properties.size()); |
| std::ranges::transform(properties, std::back_inserter(node_properties), [](const auto& property) { |
| if (const auto& val = property.value().string_value(); val) { |
| return Property{ |
| .key = property.key(), |
| .value = val.value(), |
| }; |
| } |
| if (const auto& val = property.value().bool_value(); val) { |
| return Property{ |
| .key = property.key(), |
| .value = val.value(), |
| }; |
| } |
| if (const auto& val = property.value().int_value(); val) { |
| return Property{ |
| .key = property.key(), |
| .value = val.value(), |
| }; |
| } |
| if (const auto& val = property.value().enum_value(); val) { |
| return Property{ |
| .key = property.key(), |
| .value = EnumValue{.value = val.value()}, |
| }; |
| } |
| return Property{ |
| .key = property.key(), |
| .value = std::monostate(), |
| }; |
| }); |
| return node_properties; |
| } |
| |
| std::vector<fdf::NodeProperty2> Node::PropertyToFidl(std::span<const Property> properties) { |
| std::vector<fdf::NodeProperty2> node_properties; |
| node_properties.reserve(properties.size()); |
| std::ranges::transform(properties, std::back_inserter(node_properties), [](const auto& property) { |
| if (const auto* val = std::get_if<std::string>(&property.value); val) { |
| return fdf::NodeProperty2{{ |
| .key = property.key, |
| .value = fdf::NodePropertyValue::WithStringValue(*val), |
| }}; |
| } |
| if (const auto* val = std::get_if<bool>(&property.value); val) { |
| return fdf::NodeProperty2{{ |
| .key = property.key, |
| .value = fdf::NodePropertyValue::WithBoolValue(*val), |
| }}; |
| } |
| if (const auto* val = std::get_if<uint32_t>(&property.value); val) { |
| return fdf::NodeProperty2{{ |
| .key = property.key, |
| .value = fdf::NodePropertyValue::WithIntValue(*val), |
| }}; |
| } |
| if (const auto* val = std::get_if<EnumValue>(&property.value); val) { |
| return fdf::NodeProperty2{{ |
| .key = property.key, |
| .value = fdf::NodePropertyValue::WithEnumValue(val->value), |
| }}; |
| } |
| return fdf::NodeProperty2{{ |
| .key = property.key, |
| .value = fdf::NodePropertyValue::WithStringValue("UNKNOWN"), |
| }}; |
| }); |
| |
| return node_properties; |
| } |
| |
| void Node::SetNonCompositeProperties(std::span<const fdf::NodeProperty2> properties) { |
| properties_ = std::vector<PropertiesEntry>{ |
| PropertiesEntry{ |
| .name = "default", |
| .properties = ToProperty(properties), |
| }, |
| }; |
| } |
| |
| void Node::SetCompositeParentProperties(const fdf::NodePropertyDictionary2& parent_properties) { |
| std::vector<PropertiesEntry> properties; |
| properties.reserve(parent_properties.size() + 1); |
| std::ranges::transform(parent_properties, std::back_inserter(properties), [](const auto& entry) { |
| return PropertiesEntry{ |
| .name = entry.name(), |
| .properties = ToProperty(entry.properties()), |
| }; |
| }); |
| |
| auto& composite = std::get<Composite>(type_); |
| ZX_ASSERT(composite.primary_index_ < composite.parents_.size()); |
| const auto& default_node_properties = parent_properties[composite.primary_index_].properties(); |
| properties.emplace_back(PropertiesEntry{ |
| .name = "default", |
| .properties = ToProperty(default_node_properties), |
| }); |
| |
| properties_ = std::move(properties); |
| } |
| |
| std::vector<fdf::BusInfo> Node::GetBusTopology() const { |
| std::vector<fdf::BusInfo> segments; |
| for (auto node = this; node != nullptr; node = node->GetPrimaryParent()) { |
| if (node->bus_info_.has_value()) { |
| segments.push_back(node->bus_info_.value()); |
| } |
| } |
| std::ranges::reverse(segments); |
| return segments; |
| } |
| |
| Node::OwnedByParent::OwnedByParent(fidl::ServerEnd<fdf::Node> node, Node* child) |
| : node_ref_(child->dispatcher_, std::move(node), child, |
| [](Node* node, fidl::UnbindInfo info) { node->OnNodeServerUnbound(info); }) {} |
| |
| void Node::AddChildHelper(fuchsia_driver_framework::NodeAddArgs args, |
| fidl::ServerEnd<fuchsia_driver_framework::NodeController> controller, |
| fidl::ServerEnd<fuchsia_driver_framework::Node> node, |
| AddNodeResultCallback callback) { |
| if (!unbinding_children_completers_.empty()) { |
| fdf_log::error("Failed to add node: Node is currently unbinding all of its children"); |
| callback(fit::as_error(fdf::NodeError::kUnbindChildrenInProgress)); |
| return; |
| } |
| if (node_manager_ == nullptr) { |
| fdf_log::warn("Failed to add Node, as this Node '{}' was removed", name()); |
| callback(fit::as_error(fdf::NodeError::kNodeRemoved)); |
| return; |
| } |
| if (GetNodeShutdownCoordinator().IsShuttingDown()) { |
| fdf_log::warn("Failed to add Node, as this Node '{}' is being removed", name()); |
| callback(fit::as_error(fdf::NodeError::kNodeRemoved)); |
| return; |
| } |
| if (!args.name().has_value()) { |
| fdf_log::error("Failed to add Node, a name must be provided"); |
| callback(fit::as_error(fdf::NodeError::kNameMissing)); |
| return; |
| } |
| std::string_view name = args.name().value(); |
| for (auto& child : children_) { |
| if (child->name() == name) { |
| fdf_log::error("Failed to add Node '{}', name already exists among siblings", name); |
| callback(fit::as_error(fdf::NodeError::kNameAlreadyExists)); |
| return; |
| } |
| }; |
| std::shared_ptr child = |
| std::make_shared<Node>(name, weak_from_this(), *node_manager_, dispatcher_); |
| |
| auto& fdf_offers = args.offers2(); |
| std::vector<fuchsia_driver_framework::NodeProperty2> properties; |
| |
| const auto& arg_properties = args.properties2(); |
| if (arg_properties.has_value()) { |
| properties = arg_properties.value(); |
| } |
| |
| const auto& arg_deprecated_properties = args.properties(); |
| if (arg_deprecated_properties.has_value()) { |
| if (arg_properties.has_value()) { |
| fdf_log::error( |
| "Failed to add Node '{}'. Found values for both properties and properties2 are set. Only one of the fields can be set.", |
| name); |
| callback(fit::as_error(fdf::NodeError::kUnsupportedArgs)); |
| return; |
| } |
| |
| properties.reserve(arg_deprecated_properties->size()); |
| for (auto& property : arg_deprecated_properties.value()) { |
| if (property.key().Which() == fuchsia_driver_framework::NodePropertyKey::Tag::kIntValue) { |
| fdf_log::error( |
| "Failed to add Node '{}'. Found integer-based key {} which is no longer supported.", |
| name, property.key().int_value().value()); |
| callback(fit::as_error(fdf::NodeError::kUnsupportedArgs)); |
| return; |
| } |
| properties.emplace_back(ToProperty2(property)); |
| } |
| } |
| if (args.driver_host()) { |
| child->driver_host_name_for_colocation_ = args.driver_host().value(); |
| } |
| |
| bool has_dictionary_offer = false; |
| if (fdf_offers.has_value()) { |
| child->offers_.reserve(fdf_offers.value().size()); |
| |
| // Find a parent node with a collection. This indicates that a driver has |
| // been bound to the node, and the driver is running within the collection. |
| Node* source_node = this; |
| while (source_node && source_node->collection_ == Collection::kNone) { |
| if (source_node->GetPrimaryParent() == nullptr) { |
| break; |
| } |
| source_node = source_node->GetPrimaryParent(); |
| } |
| std::string source_name = source_node->MakeComponentMoniker(); |
| Collection source_collection = source_node->collection_; |
| |
| for (auto& fdf_offer : fdf_offers.value()) { |
| if (fdf_offer.Which() == fuchsia_driver_framework::Offer::Tag::kDictionaryOffer) { |
| has_dictionary_offer = true; |
| } |
| |
| fit::result new_offer = |
| ProcessNodeOfferWithTransportProperty(fdf_offer, source_collection, source_name); |
| if (new_offer.is_error()) { |
| fdf_log::error("Failed to add Node '{}': Bad add offer: {}", child->MakeTopologicalPath(), |
| new_offer.error_value()); |
| callback(new_offer.take_error()); |
| return; |
| } |
| auto [processed_offer, property] = std::move(new_offer.value()); |
| child->offers_.emplace_back(processed_offer); |
| properties.emplace_back(property); |
| } |
| } |
| |
| child->bus_info_ = std::move(args.bus_info()); |
| |
| // Copy the subtree dictionary of a parent node down to the child. |
| if (subtree_dictionary_ref_.has_value()) { |
| child->subtree_dictionary_ref_ = subtree_dictionary_ref_; |
| ZX_ASSERT_MSG(!has_dictionary_offer, "Cannot use dictionary offers on node %s", |
| std::string(name).c_str()); |
| } |
| |
| child->SetNonCompositeProperties(properties); |
| |
| if (auto& symbols = args.symbols(); symbols.has_value()) { |
| auto is_valid = ValidateSymbols(symbols.value()); |
| if (is_valid.is_error()) { |
| fdf_log::error("Failed to add Node '{}', bad symbols", name); |
| callback(fit::as_error(is_valid.error_value())); |
| return; |
| } |
| |
| child->symbols_ = std::move(args.symbols().value()); |
| } |
| |
| Devnode::Target devfs_target; |
| std::optional<std::string_view> devfs_class_path; |
| std::string class_name = "Unknown_Class_name"; |
| auto& devfs_args = args.devfs_args(); |
| if (devfs_args.has_value()) { |
| if (devfs_args->class_name().has_value()) { |
| devfs_class_path = devfs_args->class_name(); |
| class_name = std::string(devfs_args->class_name().value()); |
| } |
| // We do not populate the connection to the controller unless it is specifically |
| // supported through the connector_supports argument. |
| bool allow_controller_connection = (devfs_args->connector_supports().has_value() && |
| (devfs_args->connector_supports().value() & |
| fuchsia_device_fs::ConnectionType::kController)); |
| if (allow_controller_connection && !devfs_args->class_name().has_value()) { |
| class_name = "No_class_name_but_driver_url_is_" + driver_url(); |
| } |
| |
| devfs_target = child->CreateDevfsPassthrough(std::move(devfs_args->connector()), |
| std::move(devfs_args->controller_connector()), |
| allow_controller_connection, class_name); |
| } else { |
| devfs_target = child->CreateDevfsPassthrough(std::nullopt, std::nullopt, false, class_name); |
| } |
| ZX_ASSERT(devfs_device_.topological_node().has_value()); |
| zx_status_t status = devfs_device_.topological_node()->add_child( |
| child->name_, devfs_class_path, std::move(devfs_target), child->devfs_device_); |
| ZX_ASSERT_MSG(status == ZX_OK, "%s failed to export: %s", child->MakeTopologicalPath().c_str(), |
| zx_status_get_string(status)); |
| ZX_ASSERT(child->devfs_device_.topological_node().has_value()); |
| child->devfs_device_.publish(); |
| |
| if (controller.is_valid()) { |
| child->controller_ref_.emplace(dispatcher_, std::move(controller), child.get(), |
| [weak = child->weak_from_this()](fidl::UnbindInfo info) { |
| std::shared_ptr self = weak.lock(); |
| if (self && self->controller_ref_) { |
| self->controller_ref_.reset(); |
| } |
| }); |
| } |
| |
| auto finish = [this, child, node = std::move(node)]() mutable { |
| if (node.is_valid()) { |
| child->state_.emplace<OwnedByParent>(std::move(node), child.get()); |
| } else { |
| auto tracker = child->CreateBindResultTracker(/*silent=*/true); |
| (*node_manager_)->Bind(*child, std::move(tracker)); |
| } |
| |
| child->AddToParents(); |
| }; |
| |
| if ((has_dictionary_offer && !args.offers_dictionary().has_value()) || |
| (!has_dictionary_offer && args.offers_dictionary().has_value())) { |
| callback(fit::as_error(fdf::NodeError::kUnsupportedArgs)); |
| return; |
| } |
| |
| if (args.offers_dictionary().has_value()) { |
| (*node_manager_) |
| ->dictionary_util() |
| .ImportDictionary( |
| std::move(args.offers_dictionary()).value(), |
| [child, callback = std::move(callback), finish = std::move(finish)]( |
| zx::result<fuchsia_component_sandbox::wire::NewCapabilityId> result) mutable { |
| // If the import failed it will log a warning, but we don't need to |
| // fail the child creation. |
| if (!result.is_ok()) { |
| finish(); |
| callback(fit::ok(child)); |
| return; |
| } |
| |
| std::unordered_map<std::string, |
| fidl::ClientEnd<fuchsia_component_sandbox::DirReceiver>> |
| map; |
| for (const auto& offer : child->offers()) { |
| if (offer.transport != OfferTransport::Dictionary) { |
| continue; |
| } |
| |
| auto [client, server] = |
| fidl::Endpoints<fuchsia_component_sandbox::DirReceiver>::Create(); |
| map[offer.service_name] = std::move(client); |
| |
| (*child->node_manager_) |
| ->dictionary_util() |
| .DictionaryDirConnectorOpen( |
| result.value(), offer.service_name, |
| [child, server = std::move(server), |
| offer](zx::result<fidl::ClientEnd<fuchsia_io::Directory>> result) mutable { |
| if (result.is_ok()) { |
| std::unordered_map<std::string, std::string> |
| target_to_source_instance_mapping; |
| for (auto& mapping : offer.renamed_instances) { |
| target_to_source_instance_mapping[mapping.target_name()] = |
| mapping.source_name(); |
| } |
| |
| std::vector<DirInfo> dirs; |
| dirs.emplace_back(DirInfo{ |
| .dir = std::move(result.value()), |
| .target_service_name = offer.service_name, |
| .target_to_source_instance_mapping = |
| target_to_source_instance_mapping, |
| .is_primary = true, |
| }); |
| |
| child->dir_receivers_.emplace_back(std::move(server), std::move(dirs), |
| child->dispatcher_); |
| } |
| }); |
| } |
| |
| (*child->node_manager_) |
| ->dictionary_util() |
| .CreateDictionaryWith( |
| std::move(map), |
| [child, finish = std::move(finish), callback = std::move(callback)]( |
| zx::result<fuchsia_component_sandbox::CapabilityId> result) mutable { |
| if (!result.is_ok()) { |
| finish(); |
| callback(fit::ok(child)); |
| return; |
| } |
| |
| ZX_ASSERT_MSG(!child->subtree_dictionary_ref_.has_value(), |
| "Cannot set dictionary_ref_ on node %s", |
| child->name().c_str()); |
| child->dictionary_ref_ = result.value(); |
| finish(); |
| callback(fit::ok(child)); |
| }); |
| }); |
| } else { |
| finish(); |
| callback(fit::ok(child)); |
| } |
| } |
| |
| void Node::WaitForChildToExit(std::string_view name, |
| fit::callback<void(fit::result<fdf::NodeError>)> callback) { |
| for (auto& child : children_) { |
| if (child->name() != name) { |
| continue; |
| } |
| if (!child->GetNodeShutdownCoordinator().IsShuttingDown()) { |
| fdf_log::error("Failed to add Node '{}', name already exists among siblings", name); |
| callback(fit::as_error(fdf::NodeError::kNameAlreadyExists)); |
| return; |
| } |
| if (child->remove_complete_callback_) { |
| fdf_log::error( |
| "Failed to add Node '{}': Node with name already exists and is marked to be replaced.", |
| name); |
| callback(fit::as_error(fdf::NodeError::kNameAlreadyExists)); |
| return; |
| } |
| child->remove_complete_callback_ = [callback = std::move(callback)]() mutable { |
| callback(fit::success()); |
| }; |
| child->GetNodeShutdownCoordinator().CheckNodeState(); |
| return; |
| }; |
| callback(fit::success()); |
| } |
| |
| void Node::AddChild(fuchsia_driver_framework::NodeAddArgs args, |
| fidl::ServerEnd<fuchsia_driver_framework::NodeController> controller, |
| fidl::ServerEnd<fuchsia_driver_framework::Node> node, |
| AddNodeResultCallback callback) { |
| if (!args.name().has_value()) { |
| fdf_log::error("Failed to add Node, a name must be provided"); |
| callback(fit::as_error(fdf::NodeError::kNameMissing)); |
| return; |
| } |
| |
| // Verify the properties. |
| if (args.properties().has_value() && args.properties2().has_value()) { |
| fdf_log::error("Failed to add Node, both properties and properties2 fields were set"); |
| callback(fit::as_error(fdf::NodeError::kUnsupportedArgs)); |
| return; |
| } |
| |
| // Only check for unique property keys for properties2 since properties is deprecated. |
| if (args.properties2().has_value()) { |
| std::unordered_set<std::string> property_keys; |
| for (auto& property : args.properties2().value()) { |
| if (property_keys.contains(property.key())) { |
| fdf_log::error( |
| "Failed to add Node since properties2 contain multiple properties with the same key"); |
| callback(fit::as_error(fdf::NodeError::kDuplicatePropertyKeys)); |
| return; |
| } |
| property_keys.insert(property.key()); |
| } |
| } |
| |
| std::string name = args.name().value(); |
| WaitForChildToExit( |
| name, [self = shared_from_this(), args = std::move(args), controller = std::move(controller), |
| node = std::move(node), |
| callback = std::move(callback)](fit::result<fdf::NodeError> result) mutable { |
| if (result.is_error()) { |
| callback(result.take_error()); |
| return; |
| } |
| self->AddChildHelper(std::move(args), std::move(controller), std::move(node), |
| std::move(callback)); |
| }); |
| } |
| |
| void Node::OnNodeServerUnbound(fidl::UnbindInfo info) { |
| // If the unbind is initiated from us, we don't need to do anything to handle |
| // the closure. |
| if (info.is_user_initiated()) { |
| return; |
| } |
| |
| // If the driver fails to bind to the node, don't remove the node. |
| if (auto* driver_component = std::get_if<DriverComponent>(&state_); |
| driver_component && driver_component->state == DriverState::kBinding) { |
| fdf_log::warn("The driver for node {} failed to bind.", name()); |
| return; |
| } |
| |
| if (GetNodeState() == NodeState::kRunning) { |
| // If the node is running but this node closure has happened, then we want to restart |
| // the node if it has the host_restart_on_crash_ enabled on it. |
| if (host_restart_on_crash_) { |
| fdf_log::info("Restarting node {} due to node closure while running.", name()); |
| RestartNode(); |
| return; |
| } |
| |
| fdf_log::warn("fdf::Node binding for node {} closed while the node was running: {}", name(), |
| info.FormatDescription()); |
| } |
| |
| Remove(RemovalSet::kAll, nullptr); |
| } |
| |
| void Node::Remove(RemoveCompleter::Sync& completer) { |
| fdf_log::debug("Remove() Fidl call for {}", name()); |
| SetShouldDestroy(); |
| Remove(RemovalSet::kAll, nullptr); |
| } |
| |
| void Node::RequestBind(RequestBindRequestView request, RequestBindCompleter::Sync& completer) { |
| bool force_rebind = false; |
| if (request->has_force_rebind()) { |
| force_rebind = request->force_rebind(); |
| } |
| |
| std::optional<std::string> driver_url_suffix; |
| if (request->has_driver_url_suffix()) { |
| driver_url_suffix = std::string(request->driver_url_suffix().get()); |
| } |
| |
| BindHelper(force_rebind, std::move(driver_url_suffix), |
| [completer = completer.ToAsync()](zx_status_t status) mutable { |
| if (status == ZX_OK) { |
| completer.ReplySuccess(); |
| } else { |
| completer.ReplyError(status); |
| } |
| }); |
| } |
| |
| void Node::WaitForDriver(WaitForDriverCompleter::Sync& completer) { |
| fidl::Arena arena; |
| |
| // First check if we are a driver component that is already started and running. |
| if (auto* driver_component = std::get_if<DriverComponent>(&state_); driver_component) { |
| if (driver_component->state == DriverState::kRunning) { |
| zx::result token_res = DuplicateNodeToken(); |
| if (token_res.is_ok()) { |
| completer.ReplySuccess( |
| fdf::wire::DriverResult::WithDriverStartedNodeToken(std::move(token_res.value()))); |
| } else { |
| completer.ReplyError(token_res.error_value()); |
| } |
| |
| return; |
| } |
| } |
| |
| // Then if we are a composite check if we have a child (completed composite) that is started and |
| // running. |
| if (auto* composite_parent = std::get_if<CompositeParent>(&state_); composite_parent) { |
| if (children().size() > 1) { |
| fdf_log::warn( |
| "Node {} is a composite parent to multiple composites, the WaitForDriver will only report on the first child to be found running.", |
| name()); |
| } |
| for (const auto& child : children()) { |
| if (auto* driver_component = std::get_if<DriverComponent>(&child->state_); driver_component) { |
| if (driver_component->state == DriverState::kRunning) { |
| zx::result token_res = child->DuplicateNodeToken(); |
| if (token_res.is_ok()) { |
| completer.ReplySuccess( |
| fdf::wire::DriverResult::WithDriverStartedNodeToken(std::move(token_res.value()))); |
| } else { |
| completer.ReplyError(token_res.error_value()); |
| } |
| |
| return; |
| } |
| } |
| } |
| } |
| |
| if (bind_wait_completer_) { |
| fdf_log::warn("WaitForDriver for {} is already called.", name()); |
| completer.ReplyError(ZX_ERR_ALREADY_EXISTS); |
| return; |
| } |
| |
| // Otherwise set our completer, which will be called when OnBind happens or we boot-up without a |
| // successful bind on this node or a child node for the composite case. |
| bind_wait_completer_ = |
| [completer = completer.ToAsync()](zx::result<fdf::wire::DriverResult> result) mutable { |
| if (result.is_error()) { |
| completer.ReplyError(result.error_value()); |
| } else { |
| completer.ReplySuccess(std::move(result.value())); |
| } |
| }; |
| |
| // If boot-up completes without us having reported a success, then failure will be sent here. |
| // Otherwise this will just be a no-op since the bind_err_ is reset on success. |
| // For multiple errors, the last error will be reported since it will be read from the bind_err_. |
| (*node_manager_)->WaitForBootup([weak = weak_from_this()]() { |
| std::shared_ptr<Node> self = weak.lock(); |
| if (!self) { |
| return; |
| } |
| |
| if (!self->bind_err_) { |
| return; |
| } |
| |
| if (self->IsComposite()) { |
| for (auto& parent_ref : self->parents()) { |
| std::shared_ptr<Node> parent = parent_ref.lock(); |
| if (parent && parent->bind_wait_completer_) { |
| parent->bind_wait_completer_(zx::ok(*std::move(self->bind_err_))); |
| } |
| } |
| } else if (self->bind_wait_completer_) { |
| self->bind_wait_completer_(zx::ok(*std::move(self->bind_err_))); |
| } |
| }); |
| } |
| |
| void Node::BindHelper(bool force_rebind, std::optional<std::string> driver_url_suffix, |
| fit::callback<void(zx_status_t)> on_bind_complete) { |
| if (std::holds_alternative<DriverComponent>(state_) && !force_rebind) { |
| on_bind_complete(ZX_ERR_ALREADY_BOUND); |
| return; |
| } |
| |
| if (pending_bind_completer_) { |
| on_bind_complete(ZX_ERR_ALREADY_EXISTS); |
| return; |
| } |
| |
| auto completer_wrapper = [on_bind_complete = |
| std::move(on_bind_complete)](zx::result<> result) mutable { |
| on_bind_complete(result.status_value()); |
| }; |
| |
| if (std::holds_alternative<DriverComponent>(state_)) { |
| RestartNodeWithRematch(driver_url_suffix, std::move(completer_wrapper)); |
| return; |
| } |
| |
| pending_bind_completer_ = std::move(completer_wrapper); |
| auto tracker = CreateBindResultTracker(); |
| if (driver_url_suffix.has_value()) { |
| node_manager_.value()->BindToUrl(*this, driver_url_suffix.value(), std::move(tracker)); |
| } else { |
| node_manager_.value()->Bind(*this, std::move(tracker)); |
| } |
| } |
| |
| void Node::handle_unknown_method( |
| fidl::UnknownMethodMetadata<fuchsia_driver_framework::NodeController> metadata, |
| fidl::UnknownMethodCompleter::Sync& completer) { |
| std::string method_type; |
| switch (metadata.unknown_method_type) { |
| case fidl::UnknownMethodType::kOneWay: |
| method_type = "one-way"; |
| break; |
| case fidl::UnknownMethodType::kTwoWay: |
| method_type = "two-way"; |
| break; |
| }; |
| |
| fdf_log::warn("fdf::NodeController received unknown {} method. Ordinal: {}", method_type, |
| metadata.method_ordinal); |
| } |
| |
| void Node::AddChild(AddChildRequestView request, AddChildCompleter::Sync& completer) { |
| AddChild(fidl::ToNatural(request->args), std::move(request->controller), std::move(request->node), |
| [completer = completer.ToAsync()]( |
| fit::result<fdf::NodeError, std::shared_ptr<Node>> result) mutable { |
| if (result.is_error()) { |
| completer.Reply(result.take_error()); |
| } else { |
| completer.ReplySuccess(); |
| } |
| }); |
| } |
| |
| void Node::handle_unknown_method( |
| fidl::UnknownMethodMetadata<fuchsia_driver_framework::Node> metadata, |
| fidl::UnknownMethodCompleter::Sync& completer) { |
| std::string method_type; |
| switch (metadata.unknown_method_type) { |
| case fidl::UnknownMethodType::kOneWay: |
| method_type = "one-way"; |
| break; |
| case fidl::UnknownMethodType::kTwoWay: |
| method_type = "two-way"; |
| break; |
| }; |
| |
| fdf_log::warn("fdf::Node received unknown {} method. Ordinal: {}", method_type, |
| metadata.method_ordinal); |
| } |
| |
| void Node::LeaseDriverPowerElement(fit::callback<void(zx::result<>)> cb) { |
| pe_handles_->lessor->Lease({{.level = 1}}) |
| .Then([this, cb = std::move(cb)]( |
| fidl::Result<fuchsia_power_broker::Lessor::Lease>& lease_result) mutable { |
| if (lease_result.is_error() && lease_result.error_value().is_framework_error()) { |
| cb(zx::error(lease_result.error_value().framework_error().status())); |
| return; |
| } |
| |
| if (lease_result.is_error()) { |
| zx_status_t status; |
| switch (static_cast<uint32_t>(lease_result.error_value().domain_error())) { |
| case static_cast<uint32_t>(fuchsia_power_broker::LeaseError::kInternal): |
| status = ZX_ERR_INTERNAL; |
| break; |
| case static_cast<uint32_t>(fuchsia_power_broker::LeaseError::kInvalidLevel): |
| status = ZX_ERR_INVALID_ARGS; |
| break; |
| case static_cast<uint32_t>(fuchsia_power_broker::LeaseError::kNotAuthorized): |
| status = ZX_ERR_ACCESS_DENIED; |
| break; |
| default: |
| status = ZX_ERR_OUT_OF_RANGE; |
| break; |
| } |
| cb(zx::error(status)); |
| return; |
| } |
| |
| (*node_manager_)->AddLeaseControlChannel(std::move(lease_result->lease_control())); |
| cb(zx::ok()); |
| }); |
| } |
| |
| void Node::StartDriver( |
| fuchsia_component_runner::wire::ComponentStartInfo start_info, |
| fidl::ServerEnd<fuchsia_component_runner::ComponentController> component_controller, |
| fit::callback<void(zx::result<>)> cb) { |
| if (GetNodeState() == NodeState::kStopped) { |
| GetNodeShutdownCoordinator().ResetShutdown(); |
| } |
| |
| auto url = start_info.resolved_url().get(); |
| bool colocate = |
| fdf_internal::ProgramValue(start_info.program(), "colocate").value_or("") == "true"; |
| bool host_restart_on_crash = |
| fdf_internal::ProgramValue(start_info.program(), "host_restart_on_crash").value_or("") == |
| "true"; |
| bool use_next_vdso = |
| fdf_internal::ProgramValue(start_info.program(), "use_next_vdso").value_or("") == "true"; |
| bool use_dynamic_linker = |
| fdf_internal::ProgramValue(start_info.program(), "use_dynamic_linker").value_or("") == "true"; |
| |
| state_ = Starting{.driver_url = std::string(url)}; |
| |
| if (host_restart_on_crash && colocate) { |
| fdf_log::error( |
| "Failed to start driver '{}'. Both host_restart_on_crash and colocate cannot be true.", |
| url); |
| cb(zx::error(ZX_ERR_INVALID_ARGS)); |
| return; |
| } |
| |
| host_restart_on_crash_ = host_restart_on_crash; |
| |
| if (colocate && !driver_host_) { |
| fdf_log::error( |
| "Failed to start driver '{}', driver is colocated but does not have a parent with a " |
| "driver host", |
| url); |
| cb(zx::error(ZX_ERR_INVALID_ARGS)); |
| return; |
| } |
| |
| bool found_driver_host = colocate; |
| |
| if (!found_driver_host) { |
| // Attempt to get a cached driver host by name if available. Otherwise this will be started. |
| driver_host_ = (*node_manager_)->GetDriverHost(driver_host_name_for_colocation_); |
| if (driver_host_) { |
| found_driver_host = true; |
| } |
| } |
| |
| fidl::Arena arena; |
| |
| auto symbols = fidl::VectorView<fdf::wire::NodeSymbol>(); |
| if (colocate) { |
| symbols = fidl::ToWire(arena, this->symbols()); |
| } |
| |
| std::vector<fdf::Offer> natural_offers; |
| natural_offers.reserve(offers_.size()); |
| std::ranges::transform(offers_, std::back_inserter(natural_offers), ToFidl); |
| auto offers = fidl::ToWire(arena, natural_offers); |
| auto properties = fidl::ToWire(arena, GetNodePropertyDict()); |
| |
| if (found_driver_host) { |
| // Whether dynamic linking is enabled for a driver host is determined by the first driver in the |
| // host. Otherwise for colocated drivers, we need to match what has been set for the driver |
| // host. |
| if (use_dynamic_linker != driver_host()->IsDynamicLinkingEnabled()) { |
| fdf_log::error( |
| "Failed to start driver '{}', driver is colocated and set use_dynamic_linker={} " |
| "but its driver host is not configured for this", |
| url, use_dynamic_linker ? "true" : "false"); |
| cb(zx::error(ZX_ERR_INVALID_ARGS)); |
| return; |
| } |
| } |
| std::optional<DriverHost::DriverLoadArgs> dynamic_linker_load_args; |
| std::optional<DriverHost::DriverStartArgs> dynamic_linker_start_args; |
| if (use_dynamic_linker) { |
| auto result = DriverHost::DriverLoadArgs::Create(start_info); |
| if (result.is_error()) { |
| cb(result.take_error()); |
| return; |
| } |
| dynamic_linker_load_args = std::move(*result); |
| dynamic_linker_start_args = |
| DriverHost::DriverStartArgs(properties, symbols, offers, start_info); |
| } |
| |
| // These are the start args used if we're not starting with the dynamic linker. |
| DriverHost::DriverStartArgs normal_start_args = |
| DriverHost::DriverStartArgs(properties, symbols, offers, start_info); |
| std::vector<fuchsia_power_broker::DependencyToken> deps; |
| |
| // Only create a series of dependencies if this is a suspend-enabled platform. On non-enabled |
| // platforms we'll use an empty vector for the rest of this function, which is valid. The |
| // rest of the driver start code also has checks so it works properly on non-enabled platforms. |
| if ((*node_manager_)->SuspendEnabled()) { |
| std::span<const std::weak_ptr<Node>> parent_nodes = parents(); |
| std::queue<std::weak_ptr<Node>> ancestors; |
| for (auto parent : parent_nodes) { |
| ancestors.push(parent); |
| } |
| |
| std::unordered_set<zx_handle_t> visited_ancestor_koids; |
| |
| // Collect dependencies on the most immediate ancestor nodes which have attached drivers. |
| // A parent driver might give us a valid token if: |
| // * the parent went away after we identified it |
| // * the parent failed registering a dependency token when it was created |
| // * duplication of the parent's dependency token fails |
| while (!ancestors.empty()) { |
| std::shared_ptr<Node> ancestor = ancestors.front().lock(); |
| ancestors.pop(); |
| if (!ancestor) { |
| continue; |
| } |
| |
| if (!ancestor->is_bound()) { |
| for (const auto& elder : ancestor->parents()) { |
| ancestors.push(elder); |
| } |
| continue; |
| } |
| |
| // Prevent multiple dependencies on the same ancestor. |
| if (visited_ancestor_koids.contains(ancestor->GetPowerTokenHandle())) { |
| // We arrived back at the same ancestors through multiple parents. |
| continue; |
| } |
| |
| zx::event token_copy = ancestor->DuplicatePowerToken(); |
| if (!token_copy.is_valid()) { |
| fdf_log::error("Power token invalid on suspend-enabled platform for node: {}", |
| std::string(url)); |
| cb(zx::error(ZX_ERR_BAD_STATE)); |
| return; |
| } |
| |
| visited_ancestor_koids.insert(ancestor->GetPowerTokenHandle()); |
| deps.push_back(std::move(token_copy)); |
| } |
| } |
| |
| zx::event clone; |
| ZX_ASSERT_MSG(power_element_token_.duplicate(ZX_RIGHT_SAME_RIGHTS, &clone) == ZX_OK, |
| "Event duplication failed while collecting power element dependencies"); |
| std::span<fuchsia_power_broker::DependencyToken> deps_span(deps); |
| |
| auto [lessor_client, lessor_server] = fidl::Endpoints<fuchsia_power_broker::Lessor>::Create(); |
| auto [element_control_client, element_control_server] = |
| fidl::Endpoints<fuchsia_power_broker::ElementControl>::Create(); |
| auto [element_runner_client, element_runner_server] = |
| fidl::Endpoints<fuchsia_power_broker::ElementRunner>::Create(); |
| |
| // Create a shared pointer because we want to create a callbacks below that have access to these |
| // handles and then after these callbacks are created we use one of the handles to make a FIDL |
| // call. |
| std::shared_ptr<PowerElementHandles> handles_ptr = std::make_shared<PowerElementHandles>( |
| fidl::Client<fuchsia_power_broker::ElementControl>(std::move(element_control_client), |
| dispatcher_), |
| std::move(element_runner_server), |
| fidl::Client<fuchsia_power_broker::Lessor>(std::move(lessor_client), dispatcher_)); |
| |
| auto url_str = std::string(start_info.resolved_url().get()); |
| |
| (*node_manager_) |
| ->CreatePowerElement( |
| name_, std::move(clone), deps_span, std::move(element_control_server), |
| std::move(element_runner_client), std::move(lessor_server), |
| [weak_self = weak_from_this(), handles_ptr = std::move(handles_ptr), cb = std::move(cb), |
| use_dynamic_linker = use_dynamic_linker, url = url_str, |
| found_driver_host = found_driver_host, |
| dynamic_linker_start_args = std::move(dynamic_linker_start_args), |
| dynamic_linker_load_args = std::move(dynamic_linker_load_args), |
| component_controller = std::move(component_controller), use_next_vdso = use_next_vdso, |
| normal_start_args = std::move(normal_start_args)](zx::result<bool> pe_created) mutable { |
| std::shared_ptr<driver_manager::Node> self = weak_self.lock(); |
| if (!self) { |
| cb(zx::error(ZX_ERR_CANCELED)); |
| return; |
| } |
| |
| if (pe_created.is_error()) { |
| fdf_log::warn("Failed creating power element for driver {}", url); |
| cb(pe_created.take_error()); |
| return; |
| } |
| zx::event copy; |
| ZX_ASSERT_MSG( |
| self->power_element_token_.duplicate(ZX_RIGHT_SAME_RIGHTS, ©) == ZX_OK, |
| "Power element token duplication failed after element creation."); |
| |
| // Run this after we create the lease for the power element, or |
| // immediately if lease is not created because this is not a |
| // power-enabled platform. |
| fit::callback<void(zx::result<>)> create_host_and_start_driver_cb = |
| [weak_self = self->weak_from_this(), found_driver_host = found_driver_host, |
| use_dynamic_linker = use_dynamic_linker, |
| dynamic_linker_load_args = std::move(dynamic_linker_load_args), |
| dynamic_linker_start_args = std::move(dynamic_linker_start_args), url = url, |
| component_controller = std::move(component_controller), |
| use_next_vdso = use_next_vdso, cb = std::move(cb), |
| normal_start_args = |
| std::move(normal_start_args)](zx::result<> power_setup) mutable { |
| std::shared_ptr<driver_manager::Node> self = weak_self.lock(); |
| if (!self) { |
| return; |
| } |
| |
| if (power_setup.is_error()) { |
| fdf_log::warn("{} failed to start because power element setup failed", |
| std::string(url)); |
| cb(power_setup); |
| return; |
| } |
| |
| PowerElementStartArgs pe_args; |
| // Package up power-related args for sending to the driver host. |
| if (self->pe_handles_.has_value()) { |
| auto element_control_channel = |
| self->pe_handles_->element_control.UnbindMaybeGetEndpoint(); |
| ZX_ASSERT_MSG(element_control_channel.is_ok(), |
| "Failed unbinding element control channel for transfer"); |
| pe_args.element_control = std::move(*element_control_channel); |
| |
| auto lessor_channel = self->pe_handles_->lessor.UnbindMaybeGetEndpoint(); |
| ZX_ASSERT_MSG(lessor_channel.is_ok(), |
| "Failed unbinding lessor channel for transfer"); |
| pe_args.lessor = std::move(*lessor_channel); |
| |
| zx::event token_copy = self->DuplicatePowerToken(); |
| ZX_ASSERT_MSG(token_copy.is_valid(), |
| "Power element token invalid on suspend-enabled platform."); |
| pe_args.power_element_token = std::move(token_copy); |
| |
| pe_args.element_runner = std::move(self->pe_handles_->element_runner); |
| } |
| // If we're using the dynamic linker, we don't continue |
| // starting the driver here. |
| if (use_dynamic_linker) { |
| self->CreateHostAndStartDriverWithDynamicLinker( |
| std::move(*dynamic_linker_load_args), std::move(*dynamic_linker_start_args), |
| url, std::move(component_controller), std::move(pe_args), found_driver_host, |
| std::move(cb)); |
| return; |
| } |
| |
| // Since we're not colocating we need to create a new driver host. |
| if (!found_driver_host) { |
| auto result = (*self->node_manager_) |
| ->CreateDriverHost(use_next_vdso, |
| self->driver_host_name_for_colocation_); |
| if (result.is_error()) { |
| cb(result.take_error()); |
| return; |
| } |
| self->driver_host_ = result.value(); |
| } |
| |
| // Finally, talk to the driver host and start the driver. |
| |
| // Bind the Node associated with the driver. |
| auto [client_end, server_end] = fidl::Endpoints<fdf::Node>::Create(); |
| fdf_log::info("Binding {} to {}", url, self->name()); |
| auto driver_endpoints = fidl::Endpoints<fuchsia_driver_host::Driver>::Create(); |
| |
| zx::event node_token; |
| if (normal_start_args.start_info_.component_instance().has_value()) { |
| node_token = std::move(*normal_start_args.start_info_.component_instance()); |
| } else { |
| fdf_log::warn("Component instance not provided in start request"); |
| ZX_ASSERT(zx::event::create(0, &node_token) == ZX_OK); |
| } |
| |
| zx::event node_token_dup; |
| ZX_ASSERT(node_token.duplicate(ZX_RIGHT_SAME_RIGHTS, &node_token_dup) == ZX_OK); |
| |
| self->state_.emplace<DriverComponent>( |
| *self, std::string(url), std::move(component_controller), |
| std::move(server_end), std::move(driver_endpoints.client), |
| std::move(node_token_dup)); |
| fidl::Arena arena; |
| auto properties = fidl::ToWire(arena, normal_start_args.node_properties_); |
| auto symbols = fidl::ToWire(arena, normal_start_args.symbols_); |
| auto offers = fidl::ToWire(arena, normal_start_args.offers_); |
| auto start_info = fidl::ToWire(arena, std::move(normal_start_args.start_info_)); |
| self->driver_host_.value()->Start( |
| std::move(client_end), self->name_, properties, symbols, offers, start_info, |
| std::move(node_token), std::move(driver_endpoints.server), std::move(pe_args), |
| [weak_self = self->weak_from_this(), name = self->name_, |
| cb = std::move(cb)](zx::result<> result) mutable { |
| auto node_ptr = weak_self.lock(); |
| if (!node_ptr) { |
| fdf_log::warn("Node '{}' freed before it is used", name); |
| cb(result); |
| return; |
| } |
| |
| if (result.is_error()) { |
| fdf_log::warn("Failed to start driver host for {}", |
| node_ptr->MakeComponentMoniker()); |
| } |
| cb(result); |
| }); |
| }; |
| |
| fit::callback<void(fit::callback<void(zx::result<>)>)> post_dependency_registration = |
| [weak_self = |
| self->weak_from_this()](fit::callback<void(zx::result<>)> post_lease) mutable { |
| std::shared_ptr<driver_manager::Node> self = weak_self.lock(); |
| if (!self) { |
| return; |
| } |
| self->LeaseDriverPowerElement(std::move(post_lease)); |
| }; |
| |
| // If this is a suspend-enabled platform, CreatePowerElement returns zx::result<true>. |
| // If suspend isn't enabled, we get a zx::result<false>, which is not an error, but |
| // the element channels are not valid, so we shouldn't stash them and we shouldn't |
| // register a dependency token or lease the power element since we didn't create |
| // it. |
| if (pe_created.value()) { |
| // Now that the power element is created, move the handles for it into the node |
| self->pe_handles_ = std::move(*handles_ptr); |
| |
| // Register a dependency token on the power element so other elements can depend on |
| // it. After doing this we'll call `post_dependency_registration`, which leases the |
| // element. |
| self->pe_handles_->element_control |
| ->RegisterDependencyToken({{ |
| .token = std::move(copy), |
| }}) |
| .Then([weak_self = self->weak_from_this(), |
| post_dependency_registration = std::move(post_dependency_registration), |
| create_host_and_start_driver_cb = |
| std::move(create_host_and_start_driver_cb), |
| url](fidl::Result< |
| fuchsia_power_broker::ElementControl::RegisterDependencyToken> |
| call_result) mutable { |
| std::shared_ptr<driver_manager::Node> self = weak_self.lock(); |
| if (!self) { |
| create_host_and_start_driver_cb(zx::error(ZX_ERR_CANCELED)); |
| return; |
| } |
| if (call_result.is_error()) { |
| fdf_log::warn( |
| "Failed creating power dependency token for {}, this may lead to improper ", |
| std::string(url), "power management behavior."); |
| self->power_element_token_.reset(); |
| if (call_result.error_value().is_framework_error()) { |
| create_host_and_start_driver_cb( |
| zx::error(call_result.error_value().framework_error().status())); |
| } else { |
| create_host_and_start_driver_cb( |
| RegistrationErrorToResult(call_result.error_value().domain_error())); |
| } |
| return; |
| } |
| post_dependency_registration(std::move(create_host_and_start_driver_cb)); |
| }); |
| } else { |
| // CreatePowerElement returned zx::ok(false). |
| create_host_and_start_driver_cb(zx::ok()); |
| } |
| }); |
| } |
| |
| void Node::OnComponentStarted(const std::weak_ptr<BootupTracker>& bootup_tracker, |
| const std::string& moniker, zx::result<StartedComponent> component) { |
| if (component.is_error()) { |
| OnStartError(component.error_value()); |
| CompleteBind(component.take_error()); |
| if (auto tracker_ptr = bootup_tracker.lock(); tracker_ptr) { |
| tracker_ptr->NotifyStartComplete(moniker); |
| } |
| return; |
| } |
| |
| fidl::Arena arena; |
| StartDriver(fidl::ToWire(arena, std::move(component->info)), |
| std::move(component->component_controller), |
| [node_weak = weak_from_this(), moniker, bootup_tracker](zx::result<> result) { |
| if (std::shared_ptr node = node_weak.lock(); node) { |
| if (result.is_error()) { |
| node->OnStartError(result.error_value()); |
| } |
| node->CompleteBind(result); |
| } |
| |
| if (auto tracker_ptr = bootup_tracker.lock(); tracker_ptr) { |
| tracker_ptr->NotifyStartComplete(moniker); |
| } |
| }); |
| } |
| |
| void Node::RequestStartComponent(fuchsia_process::wire::HandleInfo startup_handle, |
| const std::string& moniker, |
| const std::weak_ptr<BootupTracker>& bootup_tracker) { |
| fidl::Arena arena; |
| fidl::VectorView<fuchsia_process::wire::HandleInfo> handles(arena, 1); |
| handles[0] = std::move(startup_handle); |
| |
| auto [client, server] = fidl::Endpoints<fuchsia_component::ExecutionController>::Create(); |
| |
| component_controller_ |
| ->Start( |
| fuchsia_component::wire::StartChildArgs::Builder(arena).numbered_handles(handles).Build(), |
| std::move(server)) |
| .Then([bootup_tracker, moniker, node_weak = weak_from_this()]( |
| fidl::WireUnownedResult<fcomponent::Controller::Start>& result) { |
| bool is_error = false; |
| if (!result.ok()) { |
| fdf_log::error("Failed to send StartComponent. {}", result.status_string()); |
| is_error = true; |
| } else if (result->is_error()) { |
| fdf_log::error("Failed to StartComponent. {}", result->error_value()); |
| is_error = true; |
| } |
| |
| if (is_error) { |
| if (std::shared_ptr node = node_weak.lock(); node) { |
| node->OnComponentStarted(bootup_tracker, moniker, zx::error(ZX_ERR_INTERNAL)); |
| } |
| } |
| }); |
| } |
| |
| void Node::CreateHostAndStartDriverWithDynamicLinker( |
| DriverHost::DriverLoadArgs load_args, DriverHost::DriverStartArgs start_args, |
| std::string_view url, |
| fidl::ServerEnd<fuchsia_component_runner::ComponentController> component_controller, |
| PowerElementStartArgs power_element_args, bool found_driver_host, |
| fit::callback<void(zx::result<>)> cb) { |
| if (found_driver_host) { |
| StartDriverWithDynamicLinker(std::move(load_args), std::move(start_args), url, |
| std::move(component_controller), std::move(power_element_args), |
| std::move(cb)); |
| return; |
| } |
| |
| (*node_manager_) |
| ->CreateDriverHostDynamicLinker( |
| driver_host_name_for_colocation_, |
| [weak_self = weak_from_this(), name = name_, load_args = std::move(load_args), |
| start_args = std::move(start_args), url = std::string(url), |
| component_controller = std::move(component_controller), |
| power_element_args = std::move(power_element_args), |
| cb = std::move(cb)](zx::result<DriverHost*> driver_host) mutable { |
| std::shared_ptr<driver_manager::Node> node_ptr = weak_self.lock(); |
| if (!node_ptr) { |
| fdf_log::warn("Node '{}' freed before it is used", name); |
| cb(zx::error(ZX_ERR_BAD_STATE)); |
| return; |
| } |
| |
| if (driver_host.is_error()) { |
| cb(driver_host.take_error()); |
| return; |
| } |
| node_ptr->driver_host_ = driver_host.value(); |
| node_ptr->StartDriverWithDynamicLinker(std::move(load_args), std::move(start_args), url, |
| std::move(component_controller), |
| std::move(power_element_args), std::move(cb)); |
| }); |
| } |
| |
| void Node::StartDriverWithDynamicLinker( |
| DriverHost::DriverLoadArgs load_args, DriverHost::DriverStartArgs start_args, |
| std::string_view url, |
| fidl::ServerEnd<fuchsia_component_runner::ComponentController> component_controller, |
| PowerElementStartArgs power_element_args, fit::callback<void(zx::result<>)> cb) { |
| auto [client_end, server_end] = fidl::Endpoints<fdf::Node>::Create(); |
| |
| auto driver_endpoints = fidl::Endpoints<fuchsia_driver_host::Driver>::Create(); |
| |
| zx::event node_token, node_token_dup; |
| if (start_args.start_info_.component_instance().has_value()) { |
| node_token = std::move(start_args.start_info_.component_instance().value()); |
| } else { |
| ZX_ASSERT(zx::event::create(0, &node_token) == ZX_OK); |
| } |
| ZX_ASSERT(node_token.duplicate(ZX_RIGHT_SAME_RIGHTS, &node_token_dup) == ZX_OK); |
| |
| state_.emplace<DriverComponent>(*this, std::string(url), std::move(component_controller), |
| std::move(server_end), std::move(driver_endpoints.client), |
| std::move(node_token_dup)); |
| driver_host()->StartWithDynamicLinker(std::move(client_end), name_, std::move(load_args), |
| std::move(start_args), std::move(node_token), |
| std::move(driver_endpoints.server), |
| std::move(power_element_args), std::move(cb)); |
| } |
| |
| zx::result<zx::event> Node::DuplicateNodeToken() { |
| ZX_ASSERT(std::holds_alternative<DriverComponent>(state_)); |
| |
| zx::event node_token; |
| zx_status_t status = std::get<DriverComponent>(state_).component_instance.duplicate( |
| ZX_RIGHT_SAME_RIGHTS, &node_token); |
| if (status != ZX_OK) { |
| fdf_log::error("Failed to DuplicateNodeToken: {}", zx_status_get_string(status)); |
| return zx::error(status); |
| } |
| |
| return zx::ok(std::move(node_token)); |
| } |
| |
| void Node::PrepareDictionary(fit::callback<void(zx::result<>)> callback) { |
| std::optional<fuchsia_component_sandbox::CapabilityId> to_export; |
| if (subtree_dictionary_ref_.has_value()) { |
| to_export = subtree_dictionary_ref_; |
| } else if (dictionary_ref_.has_value()) { |
| to_export = dictionary_ref_; |
| } |
| |
| if (to_export) { |
| (*node_manager_) |
| ->dictionary_util() |
| .CopyExportDictionary( |
| to_export.value(), |
| [this, callback = std::move(callback)]( |
| zx::result<fuchsia_component_sandbox::DictionaryRef> result) mutable { |
| if (result.is_error()) { |
| callback(result.take_error()); |
| return; |
| } |
| |
| offers_dictionary_ = std::move(result.value()); |
| callback(zx::ok()); |
| }); |
| return; |
| } |
| |
| if (!IsComposite()) { |
| // No dictionary and not a composite. |
| callback(fit::ok()); |
| return; |
| } |
| |
| // We are a composite with parents who may or may not have dictionaries. |
| struct MapEntry { |
| fuchsia_component_sandbox::CapabilityId dictionary_ref; |
| NodeOffer offer; |
| std::string parent_name; |
| bool is_primary; |
| }; |
| std::unordered_map<std::string, std::vector<MapEntry>> service_map; |
| |
| const auto& parent_names = std::get<Composite>(type_).parents_names_; |
| auto primary_index = std::get<Composite>(type_).primary_index_; |
| |
| // Populate the service map. |
| for (size_t i = 0; i < parents().size(); i++) { |
| const auto& weak_parent = parents()[i]; |
| std::shared_ptr<Node> parent = weak_parent.lock(); |
| if (!parent) { |
| continue; |
| } |
| |
| if (!parent->dictionary_ref_.has_value()) { |
| continue; |
| } |
| |
| for (const auto& offer : parent->offers()) { |
| if (offer.transport != OfferTransport::Dictionary) { |
| continue; |
| } |
| |
| service_map[offer.service_name].emplace_back(MapEntry{ |
| .dictionary_ref = parent->dictionary_ref_.value(), |
| .offer = offer, |
| .parent_name = parent_names[i], |
| .is_primary = primary_index == i, |
| }); |
| } |
| } |
| |
| size_t total_shards = 0; |
| for (auto& [_, entries] : service_map) { |
| total_shards += entries.size(); |
| } |
| |
| // No dictionary based services. |
| if (total_shards == 0) { |
| callback(fit::ok()); |
| return; |
| } |
| |
| struct ShardResult { |
| fidl::ClientEnd<fuchsia_io::Directory> dir; |
| NodeOffer offer; |
| std::string parent_name; |
| bool is_primary; |
| }; |
| |
| std::shared_ptr<ResultAsyncSharder<ShardResult>> sharder = |
| std::make_shared<ResultAsyncSharder<ShardResult>>( |
| total_shards, [this, callback = std::move(callback)]( |
| zx::result<std::vector<ShardResult>> result) mutable { |
| if (result.is_error()) { |
| callback(result.take_error()); |
| return; |
| } |
| |
| std::unordered_map<std::string, std::vector<DirInfo>> dirs; |
| for (ShardResult& shard : result.value()) { |
| std::unordered_map<std::string, std::string> target_to_source_instance_mapping; |
| for (auto& mapping : shard.offer.renamed_instances) { |
| target_to_source_instance_mapping[mapping.target_name()] = mapping.source_name(); |
| } |
| |
| dirs[shard.offer.service_name].emplace_back(DirInfo{ |
| .dir = std::move(shard.dir), |
| .target_service_name = shard.offer.service_name, |
| .target_to_source_instance_mapping = target_to_source_instance_mapping, |
| .parent_name = shard.parent_name, |
| .is_primary = shard.is_primary, |
| }); |
| } |
| |
| std::unordered_map<std::string, fidl::ClientEnd<fuchsia_component_sandbox::DirReceiver>> |
| map; |
| |
| for (auto& offer : offers()) { |
| auto [client, server] = |
| fidl::Endpoints<fuchsia_component_sandbox::DirReceiver>::Create(); |
| map[offer.service_name] = std::move(client); |
| dir_receivers_.emplace_back(std::move(server), std::move(dirs[offer.service_name]), |
| dispatcher_); |
| } |
| |
| (*node_manager_) |
| ->dictionary_util() |
| .CreateDictionaryWith( |
| std::move(map), |
| [this, callback = std::move(callback)]( |
| zx::result<fuchsia_component_sandbox::CapabilityId> result) mutable { |
| if (!result.is_ok()) { |
| callback(result.take_error()); |
| return; |
| } |
| |
| ZX_ASSERT_MSG(!subtree_dictionary_ref_.has_value(), |
| "Cannot set dictionary_ref_ on node %s", name().c_str()); |
| dictionary_ref_ = result.value(); |
| (*node_manager_) |
| ->dictionary_util() |
| .CopyExportDictionary( |
| dictionary_ref_.value(), |
| [this, callback = std::move(callback)]( |
| zx::result<fuchsia_component_sandbox::DictionaryRef> |
| result) mutable { |
| if (result.is_error()) { |
| callback(result.take_error()); |
| return; |
| } |
| |
| offers_dictionary_ = std::move(result.value()); |
| callback(zx::ok()); |
| }); |
| }); |
| }); |
| |
| for (auto& [_, entries] : service_map) { |
| for (const auto& [dictionary_ref, offer, parent_name, is_primary] : entries) { |
| (*node_manager_) |
| ->dictionary_util() |
| .DictionaryDirConnectorOpen( |
| dictionary_ref, offer.service_name, |
| [sharder, offer, parent_name, |
| is_primary](zx::result<fidl::ClientEnd<fuchsia_io::Directory>> result) { |
| if (result.is_ok()) { |
| sharder->CompleteShard({ |
| .dir = std::move(result.value()), |
| .offer = offer, |
| .parent_name = parent_name, |
| .is_primary = is_primary, |
| }); |
| } else { |
| sharder->CompleteShardError(result.error_value()); |
| } |
| }); |
| } |
| } |
| } |
| |
| bool Node::EvaluateRematchFlags(fuchsia_driver_development::RestartRematchFlags rematch_flags, |
| std::string_view requested_url) const { |
| if (IsComposite() && |
| !(rematch_flags & fuchsia_driver_development::RestartRematchFlags::kCompositeSpec)) { |
| return false; |
| } |
| |
| if (driver_url() == requested_url && |
| !(rematch_flags & fuchsia_driver_development::RestartRematchFlags::kRequested)) { |
| return false; |
| } |
| |
| if (driver_url() != requested_url && |
| !(rematch_flags & fuchsia_driver_development::RestartRematchFlags::kNonRequested)) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| NodeInfo Node::GetRemovalTrackerInfo() { |
| return NodeInfo{ |
| .name = MakeComponentMoniker(), |
| .driver_url = driver_url(), |
| .collection = collection_, |
| .state = GetNodeState(), |
| }; |
| } |
| |
| void Node::StopDriver() { |
| ZX_ASSERT_MSG(GetNodeState() == NodeState::kWaitingOnChildren, |
| "StopDriver called in invalid node state: %s", |
| GetNodeShutdownCoordinator().NodeStateAsString()); |
| if (!HasDriver()) { |
| return; |
| } |
| auto& driver_component = std::get<DriverComponent>(state_); |
| |
| if (driver_component.state == DriverState::kBinding) { |
| fdf_log::warn("Stopping driver '{}' for node '{}' while bind is in process", |
| driver_component.driver_url, MakeComponentMoniker()); |
| return; |
| } |
| |
| fidl::OneWayStatus result = driver_component.driver->Stop(); |
| if (result.ok()) { |
| return; // We'll now wait for the channel to close |
| } |
| |
| fdf_log::error("Node: {} failed to stop driver: {}", name(), result.FormatDescription()); |
| // Continue to clear out the driver, since we can't talk to it. |
| ClearHostDriver(); |
| } |
| |
| void Node::StopDriverComponent() { |
| ZX_ASSERT_MSG(GetNodeState() == NodeState::kWaitingOnDriver, |
| "StopDriverComponent called in invalid node state: %s", |
| GetNodeShutdownCoordinator().NodeStateAsString()); |
| auto* driver_component = std::get_if<DriverComponent>(&state_); |
| if (!driver_component) { |
| return; |
| } |
| |
| // If its already stopped we don't need to send the OnStop. |
| if (driver_component->state == DriverState::kStopped) { |
| return; |
| } |
| |
| // Send an epitaph to the component manager and close the connection. The |
| // server of a `ComponentController` protocol is expected to send an epitaph |
| // before closing the associated connection. |
| fit::result<fidl::OneWayError> res = |
| fidl::SendEvent(driver_component->runner_component_controller_ref) |
| ->OnStop(fuchsia_component_runner::ComponentStopInfo{}); |
| if (res.is_error()) { |
| fdf_log::warn("Node::StopDriverComponent failed to send OnStop event."); |
| } |
| } |
| |
| bool Node::MaybeDestroyDriverComponent() { |
| ZX_ASSERT_MSG(component_controller_.is_valid(), |
| "DestroyDriverComponent called without a valid controller."); |
| |
| // The non-removal intent flows require destroying the component and making a new one. |
| // Also if we are waiting for the removal, we want to destroy the component as either a |
| // new node is intended to replace the existing which requires a new component, or the |
| // parent node is trying to remove the child node in which case the child has to destroy |
| // its component to proceed with removal. We also want to destroy the root node if it is |
| // stopping, hence the empty parent check. |
| if ((shutdown_intent() != ShutdownIntent::kRemoval || remove_complete_callback_ || |
| should_destroy_ || parents().empty())) { |
| component_controller_->Destroy().Then( |
| [weak_self = weak_from_this()]( |
| fidl::WireUnownedResult<fuchsia_component::Controller::Destroy>& result) { |
| std::shared_ptr self = weak_self.lock(); |
| if (!self) { |
| return; |
| } |
| |
| if (!result.ok()) { |
| fdf_log::error("Node: {}: Failed to send request to destroy component: {}", self->name_, |
| result.error().FormatDescription()); |
| } else if (result->is_error()) { |
| fdf_log::error("Node: {}: Failed to destroy driver component: {}", self->name_, |
| result->error_value()); |
| } |
| |
| fdf_log::debug("Destroy component started for {}", self->MakeComponentMoniker()); |
| // Reset this flag since we used it. |
| self->should_destroy_ = false; |
| }); |
| |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void Node::OnDriverHostFidlError(fidl::UnbindInfo info) { |
| ClearHostDriver(); |
| |
| // The only valid way a driver host should shut down the Driver channel |
| // is with the ZX_OK epitaph. |
| // TODO(b/322235974): Increase the log severity to ERROR once we resolve the component |
| // shutdown order in DriverTestRealm. |
| if (info.reason() != fidl::Reason::kPeerClosedWhileReading || info.status() != ZX_OK) { |
| fdf_log::warn("Node: {}: driver channel shutdown with: {}", name(), info.FormatDescription()); |
| } |
| |
| // Expected driver host closure. |
| if (GetNodeState() == NodeState::kWaitingOnDriver) { |
| fdf_log::debug("Node: {}: driver host channel had expected shutdown.", MakeComponentMoniker()); |
| GetNodeShutdownCoordinator().CheckNodeState(); |
| return; |
| } |
| |
| // Unexpected driver host closure. |
| |
| if (host_restart_on_crash_) { |
| fdf_log::warn("Restarting node {} because of unexpected driver channel shutdown.", name()); |
| RestartNode(); |
| return; |
| } |
| |
| // If the driver fails to bind to the node, don't remove the node. |
| if (IsPendingBind()) { |
| fdf_log::debug("Node: {}: driver channel closed during binding.", MakeComponentMoniker()); |
| return; |
| } |
| |
| fdf_log::warn("Removing node {} because of unexpected driver channel shutdown.", name()); |
| Remove(RemovalSet::kAll, nullptr); |
| } |
| |
| void Node::OnComponentControllerFidlError(fidl::UnbindInfo info) { |
| component_controller_ = {}; |
| |
| // Expected component controller closure. |
| if (GetNodeState() == NodeState::kWaitingOnDestroy) { |
| fdf_log::debug("Node: {}: component controller channel had expected shutdown. {}", name(), |
| info.FormatDescription()); |
| GetNodeShutdownCoordinator().CheckNodeState(); |
| return; |
| } |
| |
| // Unexpected component controller closure. |
| |
| fdf_log::warn("Node: {}: unexpected component controller channel shutdown. in state {}. {}", |
| name(), GetNodeShutdownCoordinator().NodeStateAsString(), info.FormatDescription()); |
| } |
| |
| std::optional<std::vector<fdf::NodeProperty2>> Node::GetNodeProperties( |
| std::string_view parent_name) const { |
| for (const auto& entry : properties_) { |
| if (entry.name == parent_name) { |
| return PropertyToFidl(entry.properties); |
| } |
| } |
| return std::nullopt; |
| } |
| |
| fdf::NodePropertyDictionary2 Node::GetNodePropertyDict() const { |
| fdf::NodePropertyDictionary2 dict; |
| dict.reserve(properties_.size()); |
| std::ranges::transform(properties_, std::back_inserter(dict), [this](const auto& entry) { |
| return fdf::NodePropertyEntry2{{ |
| .name = entry.name, |
| .properties = std::move(GetNodeProperties(entry.name).value()), |
| }}; |
| }); |
| return dict; |
| } |
| |
| Node::DriverComponent::DriverComponent( |
| Node& node, std::string url, |
| fidl::ServerEnd<fuchsia_component_runner::ComponentController> component_controller, |
| fidl::ServerEnd<fuchsia_driver_framework::Node> node_server, |
| fidl::ClientEnd<fuchsia_driver_host::Driver> driver, zx::event component_inst) |
| : runner_component_controller_ref( |
| node.dispatcher_, std::move(component_controller), &node, |
| [](Node* node, fidl::UnbindInfo info) { |
| if (auto* driver_component = std::get_if<DriverComponent>(&node->state_); |
| driver_component) { |
| if (node->GetNodeState() == NodeState::kWaitingOnDriverComponent) { |
| fdf_log::debug("Node: {}: runner component controller channel had expected close.", |
| node->name()); |
| driver_component->state = DriverState::kStopped; |
| node->GetNodeShutdownCoordinator().CheckNodeState(); |
| } else { |
| fdf_log::warn( |
| "Node: {}: runner component controller channel had unexpected close. Node state: {}", |
| node->name(), node->GetNodeShutdownCoordinator().NodeStateAsString()); |
| driver_component->state = DriverState::kStopped; |
| node->Remove(RemovalSet::kAll, nullptr); |
| } |
| } |
| }), |
| node_ref_(node.dispatcher_, std::move(node_server), &node, |
| [](Node* node, fidl::UnbindInfo info) { node->OnNodeServerUnbound(info); }), |
| driver(std::move(driver), node.dispatcher_, &node.driver_host_handler_), |
| driver_url(std::move(url)), |
| component_instance(std::move(component_inst)) { |
| zx_info_handle_basic_t info; |
| zx_status_t status = |
| component_instance.get_info(ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, nullptr); |
| ZX_ASSERT_MSG(status == ZX_OK, "status %s", zx_status_get_string(status)); |
| component_instance_koid = info.koid; |
| } |
| |
| void Node::ConnectToDeviceFidl(ConnectToDeviceFidlRequestView request, |
| ConnectToDeviceFidlCompleter::Sync& completer) { |
| zx_status_t status = ConnectDeviceInterface(std::move(request->server)); |
| if (status != ZX_OK) { |
| fdf_log::error("{}: Failed to connect to device fidl: ", zx_status_get_string(status)); |
| } |
| } |
| |
| void Node::ConnectToController(ConnectToControllerRequestView request, |
| ConnectToControllerCompleter::Sync& completer) { |
| // This should never be called |
| ZX_ASSERT_MSG(false, |
| "Connect To controller should never be called in node.cc," |
| " as it is intercepted by the ControllerAllowlistPassthrough"); |
| } |
| |
| void Node::Bind(BindRequestView request, BindCompleter::Sync& completer) { |
| BindHelper(false, {{request->driver.data(), request->driver.size()}}, |
| [completer = completer.ToAsync()](zx_status_t status) mutable { |
| if (status == ZX_OK) { |
| completer.ReplySuccess(); |
| } else { |
| completer.ReplyError(status); |
| } |
| }); |
| } |
| |
| void Node::Rebind(RebindRequestView request, RebindCompleter::Sync& completer) { |
| std::optional<std::string> url; |
| if (!request->driver.is_null() && !request->driver.empty()) { |
| url = std::string(request->driver.get()); |
| } |
| |
| auto rebind_callback = [completer = completer.ToAsync()](zx::result<> result) mutable { |
| if (result.is_ok()) { |
| completer.ReplySuccess(); |
| } else { |
| completer.ReplyError(result.error_value()); |
| } |
| }; |
| |
| if (kEnableCompositeNodeSpecRebind && IsComposite()) { |
| node_manager_.value()->RebindComposite(name_, url, std::move(rebind_callback)); |
| return; |
| } |
| |
| RestartNodeWithRematch(url, std::move(rebind_callback)); |
| } |
| |
| void Node::UnbindChildren(UnbindChildrenCompleter::Sync& completer) { |
| if (children_.empty()) { |
| completer.ReplySuccess(); |
| return; |
| } |
| |
| unbinding_children_completers_.emplace_back(completer.ToAsync()); |
| if (unbinding_children_completers_.size() == 1) { |
| // Iterate over a copy of `children_` because `children_` may be modified during |
| // `Node::Remove` which would mess up the for loop. |
| std::vector<std::shared_ptr<Node>> children{children_.begin(), children_.end()}; |
| for (const auto& child : children) { |
| child->SetShouldDestroy(); |
| child->Remove(RemovalSet::kAll, nullptr); |
| } |
| } |
| } |
| |
| void Node::ScheduleUnbind(ScheduleUnbindCompleter::Sync& completer) { |
| Remove(RemovalSet::kAll, nullptr); |
| completer.ReplySuccess(); |
| } |
| void Node::GetTopologicalPath(GetTopologicalPathCompleter::Sync& completer) { |
| completer.ReplySuccess(fidl::StringView::FromExternal("/" + MakeTopologicalPath())); |
| } |
| |
| zx_status_t Node::ConnectDeviceInterface(zx::channel channel) { |
| if (!devfs_connector_.has_value()) { |
| return ZX_ERR_INTERNAL; |
| } |
| return fidl::WireCall(devfs_connector_.value())->Connect(std::move(channel)).status(); |
| } |
| |
| Devnode::Target Node::CreateDevfsPassthrough( |
| std::optional<fidl::ClientEnd<fuchsia_device_fs::Connector>> connector, |
| std::optional<fidl::ClientEnd<fuchsia_device_fs::Connector>> controller_connector, |
| bool allow_controller_connection, const std::string& class_name) { |
| controller_allowlist_passthrough_ = ControllerAllowlistPassthrough::Create( |
| std::move(controller_connector), weak_from_this(), dispatcher_, class_name); |
| devfs_connector_ = std::move(connector); |
| return Devnode::PassThrough( |
| [node = weak_from_this(), node_name = name_](zx::channel server_end) { |
| std::shared_ptr locked_node = node.lock(); |
| if (!locked_node) { |
| fdf_log::error("Node was freed before it was used for {}.", node_name); |
| return ZX_ERR_BAD_STATE; |
| } |
| return locked_node->ConnectDeviceInterface(std::move(server_end)); |
| }, |
| [node = weak_from_this(), allow_controller_connection, |
| node_name = name_](fidl::ServerEnd<fuchsia_device::Controller> server_end) { |
| if (!allow_controller_connection) { |
| fdf_log::warn( |
| "Connection to {} controller interface failed, as that node did not" |
| " include controller support in its DevAddArgs", |
| node_name); |
| return ZX_ERR_PROTOCOL_NOT_SUPPORTED; |
| } |
| std::shared_ptr locked_node = node.lock(); |
| if (!locked_node) { |
| fdf_log::error("Node was freed before it was used for {}.", node_name); |
| return ZX_ERR_BAD_STATE; |
| } |
| return locked_node->controller_allowlist_passthrough_->Connect(std::move(server_end)); |
| }); |
| } |
| |
| } // namespace driver_manager |