blob: 57d77934b08d714109e3ca577aefead99ebe26d5 [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.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;
}