blob: c7d59fe5f464dd03c1ea1ba8b91d0c8ba9dcc932 [file] [log] [blame]
// 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.driver.test/cpp/fidl.h>
#include <fidl/fuchsia.io/cpp/wire.h>
#include <fidl/fuchsia.pkg/cpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/component/incoming/cpp/clone.h>
#include <lib/component/incoming/cpp/protocol.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 <format>
#include <fstream>
#include <list>
#include <string_view>
#include <unordered_set>
#include <vector>
#include <fbl/unique_fd.h>
#include <rapidjson/document.h>
#include <src/lib/files/directory.h>
#include <src/lib/files/file.h>
namespace {
zx::result<fidl::ClientEnd<fuchsia_io::Directory>> OpenPkgDir() {
auto endpoints = fidl::CreateEndpoints<fuchsia_io::Directory>();
if (endpoints.is_error()) {
return zx::error(ZX_ERR_INTERNAL);
}
zx_status_t status = fdio_open3(
"/pkg",
static_cast<uint64_t>(fuchsia_io::wire::Flags::kProtocolDirectory |
fuchsia_io::wire::kPermReadable | fuchsia_io::wire::kPermExecutable),
endpoints->server.TakeChannel().release());
if (status != ZX_OK) {
return zx::error(ZX_ERR_INTERNAL);
}
return zx::ok(std::move(endpoints->client));
}
struct ClientListEntry {
fidl::ClientEnd<fuchsia_io::Directory> client;
std::string url_prefix;
std::string pkg_name;
};
struct BootAndBaseConfigResult {
std::vector<std::string> boot_drivers;
std::vector<std::string> base_drivers;
};
class DriverLists : public fidl::WireServer<fuchsia_driver_test::DriverLists> {
public:
explicit DriverLists(BootAndBaseConfigResult lists) : lists_(std::move(lists)) {}
void GetDriverLists(GetDriverListsCompleter::Sync& completer) override {
fidl::Arena arena;
completer.ReplySuccess(fidl::ToWire(arena, lists_.boot_drivers),
fidl::ToWire(arena, lists_.base_drivers));
}
private:
BootAndBaseConfigResult lists_;
};
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> test_pkg_dir)
: boot_dir_(std::move(boot_dir)), test_pkg_dir_(std::move(test_pkg_dir)) {}
private:
void handle_unknown_method(
fidl::UnknownMethodMetadata<fuchsia_component_resolution::Resolver> metadata,
fidl::UnknownMethodCompleter::Sync& completer) override {
FX_LOG_KV(WARNING, "Unknown Resolver request", FX_KV("ordinal", metadata.method_ordinal));
}
void Resolve(ResolveRequestView request, ResolveCompleter::Sync& completer) override {
std::string_view kTestPackagePrefix = "dtr-test-pkg://fuchsia.com/";
std::string_view kBootPrefix = "fuchsia-boot:///";
std::string_view kPkgPrefix = "fuchsia-pkg://fuchsia.com/";
std::string_view relative_path = request->component_url.get();
FX_LOG_KV(DEBUG, "Resolving", FX_KV("url", relative_path));
if (!cpp20::starts_with(relative_path, kTestPackagePrefix) &&
!cpp20::starts_with(relative_path, kBootPrefix) &&
!cpp20::starts_with(relative_path, kPkgPrefix)) {
FX_LOG_KV(ERROR, "FakeComponentResolver request not supported.",
FX_KV("url", std::string(relative_path).c_str()));
completer.ReplyError(fuchsia_component_resolution::wire::ResolverError::kInvalidArgs);
return;
}
std::string_view pkg_url;
fidl::ClientEnd<fuchsia_io::Directory> dir_to_use;
// kTestPackagePrefix can resolve items from the test package, whether they are in the
// package directly or part of a subpackage.
if (cpp20::starts_with(relative_path, kTestPackagePrefix)) {
relative_path.remove_prefix(kTestPackagePrefix.length());
// The internal client is used for both direct and subpackage.
auto internal_client = component::Connect<fuchsia_driver_test::Internal>();
if (internal_client.is_error()) {
FX_LOG_KV(ERROR, "Failed to connect to internal protocol.");
completer.ReplyError(fuchsia_component_resolution::wire::ResolverError::kInternal);
return;
}
if (!cpp20::starts_with(relative_path, "#")) {
// This is a subpackage of the test so we have to use its resolution context to resolve the
// subpackage.
ResolveSubpackage(relative_path, *internal_client, completer);
return;
}
// Remove the "#"
relative_path.remove_prefix(1);
zx::result<fidl::ClientEnd<fuchsia_io::Directory>> dir_clone_result =
component::Clone(test_pkg_dir_);
if (dir_clone_result.is_error()) {
FX_LOG_KV(ERROR, "Failed to clone directory.");
completer.ReplyError(fuchsia_component_resolution::wire::ResolverError::kInternal);
return;
}
dir_to_use = std::move(dir_clone_result.value());
} else {
// Legacy resolver logic:
// Looks at the prefix to determine which directory to look in (pkg or
// boot), then looks at the path following '#' to find the fragment path within that
// directory. It's assumed that the components can be found by the fragment path.
bool is_boot = cpp20::starts_with(relative_path, kBootPrefix);
size_t pos = relative_path.find('#');
pkg_url = relative_path.substr(0, pos);
relative_path.remove_prefix(pos + 1);
zx::result<fidl::ClientEnd<fuchsia_io::Directory>> dir_clone_result;
if (is_boot) {
dir_clone_result = component::Clone(boot_dir_);
} else {
dir_clone_result = OpenPkgDir();
}
if (dir_clone_result.is_error()) {
FX_LOG_KV(ERROR, "Failed to clone directory.");
completer.ReplyError(fuchsia_component_resolution::wire::ResolverError::kInternal);
return;
}
dir_to_use = std::move(dir_clone_result.value());
}
if (!dir_to_use.is_valid()) {
FX_LOG_KV(ERROR, "Failed to set dir_to_use.");
}
zx::result manifest_vmo = ReadFileToVmo(relative_path, dir_to_use.channel().get());
if (manifest_vmo.is_error()) {
FX_LOG_KV(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_LOG_KV(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_LOG_KV(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_LOG_KV(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, dir_to_use.channel().get());
if (config_file.is_error()) {
FX_LOG_KV(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_LOG_KV(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_LOG_KV(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_LOG_KV(ERROR, "Failed to read abi-revision.");
completer.ReplyError(fuchsia_component_resolution::wire::ResolverError::kIo);
return;
}
abi_revision_file.close();
fidl::Arena arena;
auto package = fuchsia_component_resolution::wire::Package::Builder(arena)
.url(pkg_url)
.directory(std::move(dir_to_use))
.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_LOG_KV(DEBUG, "Successfully Resolved", FX_KV("url", relative_path));
completer.ReplySuccess(component);
}
void ResolveWithContext(ResolveWithContextRequestView request,
ResolveWithContextCompleter::Sync& completer) override {
FX_LOG_KV(ERROR, "FakeComponentResolver does not currently support ResolveWithContext");
completer.ReplyError(fuchsia_component_resolution::wire::ResolverError::kInvalidArgs);
}
static void ResolveSubpackage(
std::string_view relative_path,
fidl::UnownedClientEnd<fuchsia_driver_test::Internal> internal_client,
ResolveCompleter::Sync& completer) {
auto resolution_context = fidl::WireCall(internal_client)->GetTestResolutionContext();
if (!resolution_context.ok()) {
FX_LOG_KV(ERROR, "Failed to get resolution context.",
FX_KV("description", resolution_context.FormatDescription().c_str()));
completer.ReplyError(fuchsia_component_resolution::wire::ResolverError::kInternal);
return;
}
if (resolution_context.value().is_error()) {
FX_LOG_KV(ERROR, "Failed to get resolution context.",
FX_KV("error", zx_status_get_string(resolution_context.value().error_value())));
completer.ReplyError(fuchsia_component_resolution::wire::ResolverError::kInternal);
return;
}
auto resolver = component::Connect<fuchsia_component_resolution::Resolver>(
"/svc/fuchsia.component.resolution.Resolver-hermetic");
if (resolver.is_error()) {
FX_LOG_KV(ERROR, "Failed to connect to resolver protocol.");
completer.ReplyError(fuchsia_component_resolution::wire::ResolverError::kInternal);
return;
}
fidl::Arena arena;
auto result = fidl::WireCall(*resolver)->ResolveWithContext(
fidl::StringView(arena, relative_path), *resolution_context.value().value()->context.get());
if (!result.ok() || result.value().is_error()) {
FX_LOG_KV(ERROR, "Failed to resolve.");
completer.ReplyError(fuchsia_component_resolution::wire::ResolverError::kInternal);
return;
}
completer.ReplySuccess(result->value()->component);
}
static zx::result<zx::vmo> ReadFileToVmo(std::string_view path, zx_handle_t dir) {
auto file_ep = fidl::CreateEndpoints<fuchsia_io::File>();
if (file_ep.is_error()) {
FX_LOG_KV(ERROR, "Failed to create file endpoints");
return zx::error(ZX_ERR_INTERNAL);
}
zx_status_t status = fdio_open3_at(dir, std::string(path).data(),
static_cast<uint64_t>(fuchsia_io::wire::kPermReadable),
file_ep->server.channel().release());
if (status != ZX_OK) {
FX_LOG_KV(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_LOG_KV(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_LOG_KV(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> test_pkg_dir_;
};
zx::result<std::list<ClientListEntry>> MakeClientList(
fidl::UnownedClientEnd<fuchsia_io::Directory> boot_drivers_dir,
fidl::UnownedClientEnd<fuchsia_io::Directory> test_pkg_dir,
const std::optional<fuchsia_component_resolution::Context>& test_resolution_context) {
std::list<ClientListEntry> client_list;
zx::result cloned_boot_drivers_dir = component::Clone(boot_drivers_dir);
if (cloned_boot_drivers_dir.is_error()) {
FX_LOG_KV(ERROR, "Unable to clone dir");
return zx::error(ZX_ERR_IO);
}
client_list.emplace_back(ClientListEntry{
*std::move(cloned_boot_drivers_dir),
"fuchsia-boot:///",
"dtr",
});
if (test_pkg_dir.is_valid()) {
zx::result cloned_test_pkg_dir = component::Clone(test_pkg_dir);
if (cloned_test_pkg_dir.is_error()) {
FX_LOG_KV(ERROR, "Unable to clone dir");
return zx::error(ZX_ERR_IO);
}
// Add the test package itself.
client_list.emplace_back(ClientListEntry{
*std::move(cloned_test_pkg_dir),
"dtr-test-pkg://fuchsia.com/",
"",
});
// Need to clone again to use in fdio_fd_create.
cloned_test_pkg_dir = component::Clone(test_pkg_dir);
if (cloned_test_pkg_dir.is_error()) {
FX_LOG_KV(ERROR, "Unable to clone dir");
return zx::error(ZX_ERR_IO);
}
fbl::unique_fd dir_fd;
zx_status_t status =
fdio_fd_create(cloned_test_pkg_dir->TakeHandle().release(), dir_fd.reset_and_get_address());
if (status != ZX_OK) {
FX_LOG_KV(ERROR, "Failed to turn dir into fd");
return zx::error(ZX_ERR_IO);
}
if (files::IsFileAt(dir_fd.get(), "meta/fuchsia.pkg/subpackages")) {
// Read off the subpackages that exist in the test package.
std::string result;
files::ReadFileToStringAt(dir_fd.get(), "meta/fuchsia.pkg/subpackages", &result);
rapidjson::Document subpackages_doc;
subpackages_doc.Parse(result.c_str());
auto& subpackages = subpackages_doc["subpackages"];
std::vector<std::string> subpackage_names;
subpackage_names.reserve(subpackages.MemberCount());
for (rapidjson::Value::ConstMemberIterator itr = subpackages.MemberBegin();
itr != subpackages.MemberEnd(); ++itr) {
subpackage_names.push_back(itr->name.GetString());
}
if (!subpackage_names.empty()) {
// Resolve the subpackage using the context.
auto resolver = component::Connect<fuchsia_pkg::PackageResolver>(
"/svc/fuchsia.pkg.PackageResolver-hermetic");
if (resolver.is_error()) {
FX_LOG_KV(ERROR, "Failed to connect to resolver protocol.",
FX_KV("error", zx_status_get_string(resolver.error_value())));
return resolver.take_error();
}
if (!test_resolution_context.has_value()) {
FX_LOG_KV(
WARNING,
"Subpackages exist in this package but a resolution context was not provided with the test_component. Skipping.");
} else {
fidl::Arena arena;
fuchsia_pkg::wire::ResolutionContext converted_context(
{.bytes = fidl::ToWire(arena, test_resolution_context->bytes())});
// Add the subpackages of the test package into the list.
for (auto& subpackage_name : subpackage_names) {
auto [client, server] = fidl::Endpoints<fuchsia_io::Directory>::Create();
auto resolve_result = fidl::WireCall(*resolver)->ResolveWithContext(
fidl::StringView(arena, subpackage_name), converted_context, std::move(server));
if (!resolve_result.ok() || resolve_result->is_error()) {
FX_LOG_KV(ERROR, "Failed to resolve_with_context in the test package.",
FX_KV("error", resolve_result.FormatDescription().c_str()));
return zx::error(ZX_ERR_INTERNAL);
}
client_list.emplace_back(ClientListEntry{
std::move(client),
"dtr-test-pkg://fuchsia.com/",
subpackage_name,
});
}
}
}
}
}
return zx::ok(std::move(client_list));
}
zx::result<BootAndBaseConfigResult> ConstructBootAndBaseConfig(
std::list<ClientListEntry> client_list,
const std::unordered_set<std::string>& boot_driver_components) {
std::unordered_set<std::string> inserted;
std::vector<std::string> boot_drivers;
std::vector<std::string> base_drivers;
for (auto& [dir, url_prefix, pkg_name] : client_list) {
// Check each manifest to see if it uses the driver runner.
fbl::unique_fd dir_fd;
zx_status_t status = fdio_fd_create(dir.TakeHandle().release(), dir_fd.reset_and_get_address());
if (status != ZX_OK) {
FX_LOG_KV(ERROR, "Failed to turn dir into fd");
return zx::error(ZX_ERR_IO);
}
std::vector<std::string> manifests;
if (!files::ReadDirContentsAt(dir_fd.get(), "meta", &manifests)) {
FX_LOG_KV(WARNING, "Unable to read dir contents.", FX_KV("url_prefix", url_prefix),
FX_KV("pkg_name", pkg_name));
continue;
}
for (const auto& manifest : manifests) {
std::string manifest_path = "meta/" + manifest;
if (!files::IsFileAt(dir_fd.get(), manifest_path) ||
!cpp20::ends_with(std::string_view(manifest), ".cm")) {
continue;
}
std::vector<uint8_t> manifest_bytes;
if (!files::ReadFileToVectorAt(dir_fd.get(), manifest_path, &manifest_bytes)) {
FX_LOG_KV(ERROR, "Unable to read file contents for", FX_KV("manifest", manifest_path));
return zx::error(ZX_ERR_IO);
}
fit::result component = fidl::Unpersist<fuchsia_component_decl::Component>(manifest_bytes);
if (component.is_error()) {
FX_LOG_KV(ERROR, "Unable to unpersist component manifest",
FX_KV("manifest", manifest_path));
return zx::error(ZX_ERR_IO);
}
if (!component->program() || !component->program()->runner() ||
*component->program()->runner() != "driver") {
continue;
}
// Construct the url entry from the pieces provided in the list entry.
std::string entry = std::format("{}{}#meta/{}", url_prefix, pkg_name, manifest);
// Protect against duplicate drivers. Only add the driver if it is unique.
if (inserted.insert(std::format("{}", manifest)).second) {
// Add to corresponding list.
if (entry.starts_with("fuchsia-boot:///") ||
boot_driver_components.find(manifest) != boot_driver_components.end()) {
boot_drivers.push_back(entry);
} else {
base_drivers.push_back(entry);
}
}
}
}
return zx::ok(BootAndBaseConfigResult{
.boot_drivers = std::move(boot_drivers),
.base_drivers = std::move(base_drivers),
});
}
zx::result<BootAndBaseConfigResult> SetupLists(
fidl::UnownedClientEnd<fuchsia_io::Directory> boot_dir,
fidl::UnownedClientEnd<fuchsia_io::Directory> test_pkg_dir,
const std::optional<fuchsia_component_resolution::Context>& test_resolution_context,
const std::unordered_set<std::string>& boot_driver_components_set) {
auto client_list = MakeClientList(boot_dir, test_pkg_dir, test_resolution_context);
if (client_list.is_error()) {
return client_list.take_error();
}
return ConstructBootAndBaseConfig(*std::move(client_list), boot_driver_components_set);
}
} // namespace
int main() {
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
auto internal_client = component::Connect<fuchsia_driver_test::Internal>();
if (internal_client.is_error()) {
FX_LOG_KV(ERROR, "Failed to connect to internal protocol.");
return internal_client.status_value();
}
fidl::ClientEnd<fuchsia_io::Directory> boot_dir;
auto boot_result = fidl::WireCall(*internal_client)->GetBootDirectory();
if (!boot_result.ok() || boot_result->is_error() || !boot_result->value()->boot_dir.is_valid()) {
zx::result self_pkg = OpenPkgDir();
if (self_pkg.is_error()) {
return self_pkg.status_value();
}
boot_dir = std::move(self_pkg.value());
} else {
boot_dir = std::move(boot_result->value()->boot_dir);
}
fidl::ClientEnd<fuchsia_io::Directory> test_pkg_dir;
std::optional<fuchsia_component_resolution::Context> test_resolution_context;
auto test_pkg_result = fidl::WireCall(*internal_client)->GetTestPackage();
if (test_pkg_result.ok() && test_pkg_result->is_ok() &&
test_pkg_result->value()->test_pkg_dir.is_valid()) {
test_pkg_dir = std::move(test_pkg_result->value()->test_pkg_dir);
auto test_res_context_result = fidl::WireCall(*internal_client)->GetTestResolutionContext();
if (test_res_context_result.ok() && test_res_context_result->is_ok() &&
test_res_context_result->value()->context) {
test_resolution_context.emplace(
fidl::ToNatural(*test_res_context_result->value()->context.get()));
}
}
std::unordered_set<std::string> boot_driver_components_set;
auto boot_overrides_result = fidl::WireCall(*internal_client)->GetBootDriverOverrides();
if (boot_overrides_result.ok() && boot_overrides_result->is_ok() &&
!boot_overrides_result->value()->boot_overrides.empty()) {
for (auto override : boot_overrides_result->value()->boot_overrides) {
boot_driver_components_set.emplace(override.get());
}
}
// Only index pkg if only one of boot or pkg was overridden.
zx::result<BootAndBaseConfigResult> list_setup =
SetupLists(boot_dir, test_pkg_dir, test_resolution_context, boot_driver_components_set);
if (list_setup.is_error()) {
return list_setup.status_value();
}
component::OutgoingDirectory outgoing(loop.dispatcher());
zx::result<> serve_result = outgoing.ServeFromStartupInfo();
if (serve_result.is_error()) {
return serve_result.status_value();
}
// Server up driver lists.
zx::result<> add_protocol_result = outgoing.AddProtocol<fuchsia_driver_test::DriverLists>(
std::make_unique<DriverLists>(std::move(list_setup.value())));
add_protocol_result = outgoing.AddProtocol<fuchsia_component_resolution::Resolver>(
std::make_unique<FakeComponentResolver>(std::move(boot_dir), std::move(test_pkg_dir)));
if (add_protocol_result.is_error()) {
return add_protocol_result.status_value();
}
loop.Run();
return 0;
}