| // Copyright 2021 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 <fidl/fuchsia.component.decl/cpp/fidl.h> |
| #include <fidl/fuchsia.component.resolution/cpp/wire.h> |
| #include <fidl/fuchsia.io/cpp/wire.h> |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/async-loop/default.h> |
| #include <lib/component/incoming/cpp/clone.h> |
| #include <lib/component/outgoing/cpp/outgoing_directory.h> |
| #include <lib/fdio/directory.h> |
| #include <lib/syslog/cpp/macros.h> |
| #include <lib/zx/vmo.h> |
| #include <zircon/errors.h> |
| #include <zircon/status.h> |
| #include <zircon/types.h> |
| |
| #include <cstdint> |
| #include <fstream> |
| #include <string_view> |
| #include <vector> |
| |
| namespace { |
| |
| class FakeComponentResolver final |
| : public fidl::WireServer<fuchsia_component_resolution::Resolver> { |
| public: |
| explicit FakeComponentResolver(fidl::ClientEnd<fuchsia_io::Directory> boot_dir, |
| fidl::ClientEnd<fuchsia_io::Directory> pkg_dir) |
| : boot_dir_(std::move(boot_dir)), pkg_dir_(std::move(pkg_dir)) {} |
| |
| private: |
| void Resolve(ResolveRequestView request, ResolveCompleter::Sync& completer) override { |
| std::string_view kBootPrefix = "fuchsia-boot:///"; |
| std::string_view kPkgPrefix = "fuchsia-pkg://fuchsia.com/"; |
| std::string_view relative_path = request->component_url.get(); |
| FX_SLOG(DEBUG, "Resolving", FX_KV("url", relative_path)); |
| |
| if (!cpp20::starts_with(relative_path, kBootPrefix) && |
| !cpp20::starts_with(relative_path, kPkgPrefix)) { |
| FX_SLOG(ERROR, "FakeComponentResolver request not supported.", |
| FX_KV("url", std::string(relative_path).c_str())); |
| completer.ReplyError(fuchsia_component_resolution::wire::ResolverError::kInvalidArgs); |
| return; |
| } |
| |
| // FakeComponentResolver looks at the prefix to determine which directory to look in (pkg or |
| // boot), then looks at the path following '#' to find the relative path within that directory. |
| // Note that subpackaging is ignored: it's assumed that the components can be found by the |
| // relative path. |
| bool is_boot = cpp20::starts_with(relative_path, kBootPrefix); |
| size_t pos = relative_path.find('#'); |
| std::string_view pkg_url = relative_path.substr(0, pos); |
| relative_path.remove_prefix(pos + 1); |
| |
| zx::result manifest_vmo = ReadFileToVmo(relative_path, is_boot); |
| if (manifest_vmo.is_error()) { |
| FX_SLOG(ERROR, "Failed to read manifest.", |
| FX_KV("manifest", std::string(relative_path).c_str())); |
| completer.ReplyError(fuchsia_component_resolution::wire::ResolverError::kIo); |
| return; |
| } |
| |
| uint64_t manifest_size; |
| zx_status_t status = manifest_vmo->get_prop_content_size(&manifest_size); |
| if (status != ZX_OK) { |
| FX_SLOG(ERROR, "Failed to get vmo size.", |
| FX_KV("manifest", std::string(relative_path).c_str())); |
| completer.ReplyError(fuchsia_component_resolution::wire::ResolverError::kIo); |
| return; |
| } |
| |
| std::vector<uint8_t> manifest; |
| manifest.resize(manifest_size); |
| status = manifest_vmo->read(manifest.data(), 0, manifest_size); |
| if (status != ZX_OK) { |
| FX_SLOG(ERROR, "Failed to read manifest vmo.", |
| FX_KV("manifest", std::string(relative_path).c_str())); |
| completer.ReplyError(fuchsia_component_resolution::wire::ResolverError::kIo); |
| return; |
| } |
| |
| auto declaration = fidl::Unpersist<fuchsia_component_decl::Component>(manifest); |
| if (declaration.is_error()) { |
| FX_SLOG(ERROR, "Failed to parse component manifest.", |
| FX_KV("manifest", std::string(relative_path).c_str())); |
| completer.ReplyError(fuchsia_component_resolution::wire::ResolverError::kInvalidManifest); |
| return; |
| } |
| |
| // Check if there is a config file associated with the driver, and read if it exists. |
| uint64_t config_size = 0; |
| zx::vmo config_vmo; |
| |
| if (declaration->config() && |
| declaration->config()->value_source()->package_path().has_value()) { |
| std::string config_path = declaration->config()->value_source()->package_path().value(); |
| zx::result config_file = ReadFileToVmo(config_path, is_boot); |
| if (config_file.is_error()) { |
| FX_SLOG(ERROR, "Failed to read config vmo.", FX_KV("config", config_path.c_str())); |
| completer.ReplyError(fuchsia_component_resolution::wire::ResolverError::kIo); |
| return; |
| } |
| |
| status = config_file->get_prop_content_size(&config_size); |
| if (status != ZX_OK) { |
| FX_SLOG(ERROR, "Failed to get vmo size.", FX_KV("config", config_path.c_str())); |
| completer.ReplyError(fuchsia_component_resolution::wire::ResolverError::kIo); |
| return; |
| } |
| |
| config_vmo = std::move(*config_file); |
| } |
| |
| // Not all boot components resolved by this resolver are packaged (e.g. root.cm) so they do |
| // not have an ABI revision. As a workaround, apply the ABI revision of this package so |
| // components can pass runtime ABI compatibility checks during testing. |
| std::ifstream abi_revision_file("/pkg/meta/fuchsia.abi/abi-revision"); |
| if (!abi_revision_file) { |
| FX_SLOG(ERROR, "Failed to open abi-revision."); |
| completer.ReplyError(fuchsia_component_resolution::wire::ResolverError::kIo); |
| return; |
| } |
| |
| uint64_t abi_revision; |
| abi_revision_file.read(reinterpret_cast<char*>(&abi_revision), sizeof(abi_revision)); |
| if (!abi_revision_file) { |
| FX_SLOG(ERROR, "Failed to read abi-revision."); |
| completer.ReplyError(fuchsia_component_resolution::wire::ResolverError::kIo); |
| return; |
| } |
| abi_revision_file.close(); |
| |
| zx::result<fidl::ClientEnd<fuchsia_io::Directory>> dir_clone_result; |
| if (is_boot) { |
| dir_clone_result = component::Clone(boot_dir_); |
| } else { |
| dir_clone_result = component::Clone(pkg_dir_); |
| } |
| |
| if (dir_clone_result.is_error()) { |
| FX_SLOG(ERROR, "Failed to clone directory."); |
| completer.ReplyError(fuchsia_component_resolution::wire::ResolverError::kInternal); |
| return; |
| } |
| |
| fidl::Arena arena; |
| auto package = fuchsia_component_resolution::wire::Package::Builder(arena) |
| .url(pkg_url) |
| .directory(std::move(dir_clone_result.value())) |
| .Build(); |
| |
| auto builder = |
| fuchsia_component_resolution::wire::Component::Builder(arena) |
| .url(request->component_url) |
| .abi_revision(abi_revision) |
| .decl(fuchsia_mem::wire::Data::WithBuffer(arena, |
| fuchsia_mem::wire::Buffer{ |
| .vmo = std::move(*manifest_vmo), |
| .size = manifest_size, |
| })) |
| .package(package); |
| |
| if (config_vmo.is_valid()) { |
| builder.config_values( |
| fuchsia_mem::wire::Data::WithBuffer(arena, fuchsia_mem::wire::Buffer{ |
| .vmo = std::move(config_vmo), |
| .size = config_size, |
| })); |
| } |
| |
| auto component = builder.Build(); |
| |
| FX_SLOG(DEBUG, "Successfully Resolved", FX_KV("url", relative_path)); |
| completer.ReplySuccess(component); |
| } |
| |
| void ResolveWithContext(ResolveWithContextRequestView request, |
| ResolveWithContextCompleter::Sync& completer) override { |
| FX_SLOG(ERROR, "FakeComponentResolver does not currently support ResolveWithContext"); |
| completer.ReplyError(fuchsia_component_resolution::wire::ResolverError::kInvalidArgs); |
| } |
| |
| zx::result<zx::vmo> ReadFileToVmo(std::string_view path, bool is_boot) { |
| zx_handle_t dir = pkg_dir_.channel().get(); |
| if (is_boot) { |
| dir = boot_dir_.channel().get(); |
| } |
| |
| auto file_ep = fidl::CreateEndpoints<fuchsia_io::File>(); |
| if (file_ep.is_error()) { |
| FX_SLOG(ERROR, "Failed to create file endpoints"); |
| return zx::error(ZX_ERR_INTERNAL); |
| } |
| |
| zx_status_t status = |
| fdio_open_at(dir, std::string(path).data(), |
| static_cast<uint32_t>(fuchsia_io::wire::OpenFlags::kRightReadable), |
| file_ep->server.channel().release()); |
| if (status != ZX_OK) { |
| FX_SLOG(ERROR, "Failed to open file.", FX_KV("file", std::string(path).c_str())); |
| return zx::error(ZX_ERR_IO); |
| } |
| |
| fidl::WireResult result = |
| fidl::WireCall(file_ep->client)->GetBackingMemory(fuchsia_io::wire::VmoFlags::kRead); |
| if (!result.ok()) { |
| FX_SLOG(DEBUG, "Failed to read file.", FX_KV("file", std::string(path).c_str())); |
| return zx::error(ZX_ERR_NOT_FOUND); |
| } |
| |
| auto& response = result.value(); |
| if (response.is_error()) { |
| FX_SLOG(ERROR, "Failed to read file.", FX_KV("file", std::string(path).c_str())); |
| return zx::error(ZX_ERR_IO); |
| } |
| |
| return zx::ok(std::move(response.value()->vmo)); |
| } |
| |
| fidl::ClientEnd<fuchsia_io::Directory> boot_dir_; |
| fidl::ClientEnd<fuchsia_io::Directory> pkg_dir_; |
| }; |
| |
| zx::result<fidl::ClientEnd<fuchsia_io::Directory>> ConnectDir(const char* dir) { |
| auto endpoints = fidl::CreateEndpoints<fuchsia_io::Directory>(); |
| if (endpoints.is_error()) { |
| return endpoints.take_error(); |
| } |
| zx_status_t status = |
| fdio_open(dir, |
| static_cast<uint32_t>(fuchsia_io::wire::OpenFlags::kDirectory | |
| fuchsia_io::wire::OpenFlags::kRightReadable | |
| fuchsia_io::wire::OpenFlags::kRightExecutable), |
| endpoints->server.channel().release()); |
| if (status != ZX_OK) { |
| return zx::error(status); |
| } |
| |
| return zx::ok(std::move(endpoints->client)); |
| } |
| |
| } // namespace |
| |
| int main() { |
| async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread); |
| |
| component::OutgoingDirectory outgoing(loop.dispatcher()); |
| zx::result<> serve_result = outgoing.ServeFromStartupInfo(); |
| if (serve_result.is_error()) { |
| return serve_result.status_value(); |
| } |
| |
| zx::result<fidl::ClientEnd<fuchsia_io::Directory>> connect_boot_result = ConnectDir("/boot"); |
| if (connect_boot_result.is_error()) { |
| return connect_boot_result.status_value(); |
| } |
| |
| zx::result<fidl::ClientEnd<fuchsia_io::Directory>> connect_pkg_result = |
| ConnectDir("/pkg_drivers"); |
| if (connect_pkg_result.is_error()) { |
| return connect_pkg_result.status_value(); |
| } |
| |
| zx::result<> add_protocol_result = outgoing.AddProtocol<fuchsia_component_resolution::Resolver>( |
| std::make_unique<FakeComponentResolver>(std::move(connect_boot_result.value()), |
| std::move(connect_pkg_result.value()))); |
| if (add_protocol_result.is_error()) { |
| return add_protocol_result.status_value(); |
| } |
| |
| loop.Run(); |
| return 0; |
| } |