blob: 85e219995411131bbde30a429990955de02fec0f [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 "src/graphics/bin/vulkan_loader/icd_component.h"
#include <fidl/fuchsia.component.decl/cpp/wire.h>
#include <lib/async/cpp/task.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/io.h>
#include <lib/fit/defer.h>
#include <lib/syslog/cpp/macros.h>
#include <fbl/unique_fd.h>
#include "rapidjson/prettywriter.h"
#include "rapidjson/schema.h"
#include "src/graphics/bin/vulkan_loader/app.h"
#include "src/lib/files/file.h"
#include "src/lib/json_parser/json_parser.h"
#include "src/lib/json_parser/pretty_print.h"
namespace {
const char* kSchema = R"(
{
"$schema": "http://json-schema.org/schema#",
"type": "object",
"properties": {
"version": {"type":"number", "maximum": 1, "minimum": 1},
"file_path": {"type":"string"},
"manifest_path": {"type":"string"}
},
"required": ["version", "file_path", "manifest_path"]
}
)";
const char* kManifestSchema = R"(
{
"$schema":"http://json-schema.org/schema#",
"type":"object",
"properties":{
"file_format_version":{
"type":"string"
},
"ICD":{
"type":"object",
"properties":{
"library_path":{
"type":"string"
},
"api_version":{
"type":"string"
}
},
"required":[
"library_path",
"api_version"
]
}
},
"required":[
"file_format_version",
"ICD"
]
}
)";
const char* kCollectionName = "icd-loaders";
} // namespace
IcdComponent::IcdComponent(LoaderApp* app, fidl::ClientEnd<fuchsia_component::Realm> realm,
std::string component_url)
: app_(app),
realm_(std::move(realm), app->dispatcher()),
component_url_(std::move(component_url)) {}
IcdComponent::~IcdComponent() {
RemoveManifestFromFs();
if (realm_ && !child_instance_name_.empty()) {
fuchsia_component_decl::wire::ChildRef child_ref{
.name = fidl::StringView::FromExternal(child_instance_name_),
.collection = fidl::StringView::FromExternal(kCollectionName),
};
auto result = realm_.sync()->DestroyChild(child_ref);
if (!result.ok()) {
FX_LOGS(ERROR) << child_instance_name_ << " DestroyChild transport error: " << result;
} else if (result->is_error()) {
FX_LOGS(ERROR) << child_instance_name_
<< " DestroyChild error: " << static_cast<uint32_t>(result->error_value());
}
}
}
void IcdComponent::AddManifestToFs() {
assert(manifest_file_);
std::optional<std::string> manifest_file_name = GetManifestFileName();
assert(manifest_file_name);
app_->manifest_fs_root_node()->AddEntry(*manifest_file_name, manifest_file_);
}
void IcdComponent::RemoveManifestFromFs() {
if (!manifest_file_)
return;
std::optional<std::string> manifest_file_name = GetManifestFileName();
if (manifest_file_name) {
app_->manifest_fs_root_node()->RemoveEntry(*manifest_file_name, manifest_file_.get());
}
}
void IcdComponent::Initialize(inspect::Node* parent_node) {
static uint64_t name_id;
auto pending_action_token = app_->GetPendingActionToken();
child_instance_name_ = std::to_string(name_id++);
node_ = parent_node->CreateChild(child_instance_name_);
initialization_status_ = node_.CreateString("status", "uninitialized");
node_.CreateString("component_url", component_url_, &value_list_);
fidl::Arena arena;
fuchsia_component_decl::wire::CollectionRef collection{
.name = fidl::StringView::FromExternal(kCollectionName),
};
fuchsia_component_decl::wire::Child decl =
fuchsia_component_decl::wire::Child::Builder(arena)
.name(child_instance_name_)
.url(component_url_)
.startup(fuchsia_component_decl::wire::StartupMode::kLazy)
.Build();
auto failure_callback =
fit::defer_callback([this, pending_action_token = std::move(pending_action_token)]() {
std::lock_guard lock(vmo_lock_);
stage_ = LookupStages::kFailed;
app_->NotifyIcdsChanged();
});
realm_->CreateChild(collection, decl, fuchsia_component::wire::CreateChildArgs())
.Then([this, failure_callback = std::move(failure_callback)](
fidl::WireUnownedResult<fuchsia_component::Realm::CreateChild>& result) mutable {
if (!result.ok()) {
FX_LOGS(ERROR) << component_url_ << " CreateChild transport error: " << result;
node_.CreateUint("create_response", static_cast<uint32_t>(result.status()), &value_list_);
child_instance_name_ = "";
return;
}
if (result->is_error()) {
FX_LOGS(ERROR) << component_url_
<< " CreateChild error: " << static_cast<uint32_t>(result->error_value());
node_.CreateUint("create_response", static_cast<uint32_t>(result->error_value()),
&value_list_);
child_instance_name_ = "";
return;
}
initialization_status_.Set("created");
fuchsia_component_decl::wire::ChildRef child_ref{
.name = fidl::StringView::FromExternal(child_instance_name_),
.collection = fidl::StringView::FromExternal(kCollectionName),
};
auto endpoints = fidl::CreateEndpoints<fuchsia_io::Directory>();
if (endpoints.is_error()) {
FX_LOGS(ERROR) << "Failed to create endpoints: " << endpoints.status_string();
return;
}
realm_->OpenExposedDir(child_ref, std::move(endpoints->server))
.Then([this, client_end = std::move(endpoints->client),
failure_callback = std::move(failure_callback)](
fidl::WireUnownedResult<fuchsia_component::Realm::OpenExposedDir>&
result) mutable {
if (!result.ok()) {
FX_LOGS(ERROR) << component_url_ << " OpenExposedDir transport error: " << result;
node_.CreateUint("bind_response", static_cast<uint32_t>(result.status()),
&value_list_);
return;
}
if (result->is_error()) {
FX_LOGS(ERROR) << component_url_ << " OpenExposedDir error: "
<< static_cast<uint32_t>(result->error_value());
node_.CreateUint("bind_response", static_cast<uint32_t>(result->error_value()),
&value_list_);
return;
}
initialization_status_.Set("bound");
async::PostTask(
app_->fdio_loop_dispatcher(), [this, shared_this = this->shared_from_this(),
failure_callback = std::move(failure_callback),
directory = std::move(client_end)]() mutable {
ReadFromComponent(std::move(failure_callback), std::move(directory));
});
});
});
}
std::optional<std::string> IcdComponent::ReadManifest(int contents_dir_fd,
const std::string& manifest_path) {
std::string manifest_result;
if (!files::ReadFileToStringAt(contents_dir_fd, manifest_path, &manifest_result)) {
FX_LOGS(ERROR) << component_url_ << " Failed to read manifest path " << manifest_path;
return {};
}
json_parser::JSONParser manifest_parser;
auto manifest_doc = manifest_parser.ParseFromString(manifest_result, manifest_path);
if (manifest_parser.HasError()) {
FX_LOGS(ERROR) << component_url_ << " JSON parser had error " << manifest_parser.error_str();
return {};
}
if (!ValidateManifestJson(component_url_, manifest_doc)) {
return {};
}
// Update library_path in manifest with a unique name.
auto& library_path_node = manifest_doc["ICD"].GetObject()["library_path"];
std::string library_path = library_path_node.GetString();
library_path = child_instance_name_ + "-" + library_path;
node_.CreateString("library_path", library_path, &value_list_);
library_path_node.SetString(library_path.c_str(), manifest_doc.GetAllocator());
manifest_result = json_parser::JsonValueToPrettyString(manifest_doc);
node_.CreateString("manifest_contents", manifest_result, &value_list_);
manifest_file_ =
fbl::MakeRefCounted<fs::BufferedPseudoFile>([manifest_result](fbl::String* out_string) {
*out_string = manifest_result.c_str();
return ZX_OK;
});
return library_path;
}
// static
bool IcdComponent::ValidateMetadataJson(const std::string& component_url,
const rapidjson::GenericDocument<rapidjson::UTF8<>>& doc) {
rapidjson::Document schema_doc;
schema_doc.Parse(kSchema);
FX_CHECK(!schema_doc.HasParseError()) << schema_doc.GetParseError();
rapidjson::SchemaDocument schema(schema_doc);
rapidjson::SchemaValidator validator(schema);
if (!doc.Accept(validator)) {
rapidjson::StringBuffer sb;
rapidjson::PrettyWriter<rapidjson::StringBuffer> w(sb);
validator.GetError().Accept(w);
FX_LOGS(ERROR) << component_url << " metadata.json failed validation " << sb.GetString();
return false;
}
return true;
}
bool IcdComponent::ValidateManifestJson(const std::string& component_url,
const rapidjson::GenericDocument<rapidjson::UTF8<>>& doc) {
rapidjson::Document schema_doc;
schema_doc.Parse(kManifestSchema);
FX_CHECK(!schema_doc.HasParseError()) << schema_doc.GetParseError();
rapidjson::SchemaDocument schema(schema_doc);
rapidjson::SchemaValidator validator(schema);
if (!doc.Accept(validator)) {
rapidjson::StringBuffer sb;
rapidjson::PrettyWriter<rapidjson::StringBuffer> w(sb);
validator.GetError().Accept(w);
FX_LOGS(ERROR) << component_url << " manifest.json failed validation " << sb.GetString();
return false;
}
return true;
}
zx::result<zx::vmo> IcdComponent::CloneVmo() const {
std::lock_guard lock(vmo_lock_);
if (!vmo_info_)
return zx::error(ZX_ERR_BAD_STATE);
uint64_t size;
zx_status_t status = vmo_info_->vmo.get_size(&size);
if (status != ZX_OK)
return zx::error(status);
zx::vmo vmo;
// Snapshot is ok because we never modify our VMO, and blobfs should never modify it either. We
// use ZX_VMO_CHILD_NO_WRITE because otherwise ZX_RIGHT_EXECUTE is removed.
status = vmo_info_->vmo.create_child(
ZX_VMO_CHILD_SNAPSHOT_AT_LEAST_ON_WRITE | ZX_VMO_CHILD_NO_WRITE, 0, size, &vmo);
if (status != ZX_OK)
return zx::error(status);
return zx::ok(std::move(vmo));
}
// See the accompanying README.md for a description of what a Vulkan component needs to have.
void IcdComponent::ReadFromComponent(fit::deferred_callback failure_callback,
fidl::ClientEnd<fuchsia_io::Directory> out_dir) {
initialization_status_.Set("reading from package");
auto metadata_endpoints = fidl::CreateEndpoints<fuchsia_io::Directory>();
if (metadata_endpoints.is_error()) {
FX_LOGS(ERROR) << "Failed to create endpoints: " << metadata_endpoints.status_string();
return;
}
zx_status_t status = fdio_open_at(out_dir.channel().get(), "metadata",
static_cast<uint32_t>(fuchsia_io::OpenFlags::kRightReadable),
metadata_endpoints->server.TakeChannel().release());
if (status != ZX_OK) {
FX_PLOGS(ERROR, status) << component_url_ << " Failed opening metadata dir";
return;
}
auto contents_endpoints = fidl::CreateEndpoints<fuchsia_io::Directory>();
if (contents_endpoints.is_error()) {
FX_LOGS(ERROR) << "Failed to create endpoints: " << contents_endpoints.status_string();
return;
}
status = fdio_open_at(out_dir.channel().get(), "contents",
static_cast<uint32_t>(fuchsia_io::OpenFlags::kRightReadable |
fuchsia_io::OpenFlags::kRightExecutable),
contents_endpoints->server.TakeChannel().release());
if (status != ZX_OK) {
FX_PLOGS(ERROR, status) << component_url_ << " Failed opening pkg dir";
return;
}
fbl::unique_fd metadata_dir_fd;
status = fdio_fd_create(metadata_endpoints->client.TakeChannel().release(),
metadata_dir_fd.reset_and_get_address());
if (status != ZX_OK) {
FX_PLOGS(ERROR, status) << component_url_ << " Failed creating FD for metadata";
return;
}
json_parser::JSONParser parser;
auto doc = parser.ParseFromFileAt(metadata_dir_fd.get(), "metadata.json");
if (parser.HasError()) {
FX_LOGS(ERROR) << component_url_ << " JSON parser had error " << parser.error_str();
return;
}
fbl::unique_fd contents_dir_fd;
status = fdio_fd_create(contents_endpoints->client.TakeChannel().release(),
contents_dir_fd.reset_and_get_address());
if (status != ZX_OK) {
FX_PLOGS(ERROR, status) << component_url_ << " Failed creating FD";
return;
}
if (!ValidateMetadataJson(component_url_, doc)) {
return;
}
node_.CreateUint("version", doc["version"].GetInt(), &value_list_);
std::string file_path = doc["file_path"].GetString();
node_.CreateString("file_path", file_path, &value_list_);
initialization_status_.Set("opening manifest");
std::string manifest_path = doc["manifest_path"].GetString();
std::optional<std::string> library_path_option =
ReadManifest(contents_dir_fd.get(), manifest_path);
if (!library_path_option) {
return;
}
// Manifest file will be added to the filesystem in IcdList::UpdateCurrentComponent.
fbl::unique_fd fd;
initialization_status_.Set("opening VMO");
status = fdio_open_fd_at(contents_dir_fd.get(), file_path.c_str(),
static_cast<uint32_t>(fuchsia_io::OpenFlags::kRightReadable |
fuchsia_io::OpenFlags::kRightExecutable),
fd.reset_and_get_address());
if (status != ZX_OK) {
FX_PLOGS(ERROR, status) << component_url_ << " Could not open path " << file_path;
return;
}
zx::vmo vmo;
status = fdio_get_vmo_exec(fd.get(), vmo.reset_and_get_address());
fd.reset();
if (status != ZX_OK) {
FX_PLOGS(ERROR, status) << component_url_ << " Could not clone vmo exec";
return;
}
// Create another pending action token to keep everything alive until we're done initializing
// the data.
auto pending_action_token = app_->GetPendingActionToken();
VmoInfo info;
info.library_path = *library_path_option;
info.vmo = std::move(vmo);
{
std::lock_guard lock(vmo_lock_);
vmo_info_ = std::move(info);
failure_callback.cancel();
stage_ = LookupStages::kFinished;
}
app_->NotifyIcdsChanged();
initialization_status_.Set("initialized");
}