blob: f81568acf90a05e04cb33e4401ee7721703d208d [file] [log] [blame]
// Copyright 2023 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/runner.h"
#include <zircon/processargs.h>
#include "src/devices/lib/log/log.h"
namespace {
namespace fdi = fuchsia_driver_index;
namespace fio = fuchsia_io;
namespace fprocess = fuchsia_process;
namespace frunner = fuchsia_component_runner;
namespace fcomponent = fuchsia_component;
namespace fdecl = fuchsia_component_decl;
constexpr uint32_t kTokenId = PA_HND(PA_USER0, 0);
zx::result<zx_koid_t> GetKoid(zx_handle_t handle) {
zx_info_handle_basic_t info{};
if (zx_status_t status =
zx_object_get_info(handle, ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, nullptr);
status != ZX_OK) {
return zx::error(status);
}
return zx::ok(info.koid);
}
const char* GetErrorString(fcomponent::Error error) {
switch (error) {
case fcomponent::Error::kInternal:
return "INTERNAL";
case fcomponent::Error::kInvalidArguments:
return "INVALID_ARGUMENTS";
case fcomponent::Error::kUnsupported:
return "UNSUPPORTED";
case fcomponent::Error::kAccessDenied:
return "ACCESS_DENIED";
case fcomponent::Error::kInstanceNotFound:
return "INSTANCE_NOT_FOUND";
case fcomponent::Error::kInstanceAlreadyExists:
return "INSTANCE_ALREADY_EXISTS";
case fcomponent::Error::kInstanceCannotStart:
return "INSTANCE_CANNOT_START";
case fcomponent::Error::kInstanceCannotResolve:
return "INSTANCE_CANNOT_RESOLVE";
case fcomponent::Error::kCollectionNotFound:
return "COLLECTION_NOT_FOUND";
case fcomponent::Error::kResourceUnavailable:
return "RESOURCE_UNAVAILABLE";
case fcomponent::Error::kInstanceDied:
return "INSTANCE_DIED";
case fcomponent::Error::kResourceNotFound:
return "RESOURCE_NOT_FOUND";
case fcomponent::Error::kInstanceCannotUnresolve:
return "INSTANCE_CANNOT_UNRESOLVE";
case fcomponent::Error::kInstanceAlreadyStarted:
return "INSTANCE_ALREADY_STARTED";
}
}
} // namespace
namespace driver_manager {
zx::result<> Runner::Publish(component::OutgoingDirectory& outgoing) {
return outgoing.AddUnmanagedProtocol<frunner::ComponentRunner>(
bindings_.CreateHandler(this, dispatcher_, fidl::kIgnoreBindingClosure));
}
void Runner::StartDriverComponent(std::string_view moniker, std::string_view url,
std::string_view collection_name,
fidl::VectorView<fuchsia_component_decl::wire::Offer> offers,
StartCallback callback) {
zx::event token;
zx_status_t status = zx::event::create(0, &token);
if (status != ZX_OK) {
return callback(zx::error(status));
}
zx::result koid = GetKoid(token.get());
if (koid.is_error()) {
return callback(koid.take_error());
}
start_requests_.emplace(koid.value(), std::move(callback));
fidl::Arena arena;
auto child_decl = fdecl::wire::Child::Builder(arena)
.name(fidl::StringView::FromExternal(moniker))
.url(fidl::StringView::FromExternal(url))
.startup(fdecl::wire::StartupMode::kLazy)
.Build();
fprocess::wire::HandleInfo handle_info = {
.handle = std::move(token),
.id = kTokenId,
};
auto child_args_builder = fcomponent::wire::CreateChildArgs::Builder(arena).numbered_handles(
fidl::VectorView<fprocess::wire::HandleInfo>::FromExternal(&handle_info, 1));
if (!offers.empty()) {
child_args_builder.dynamic_offers(offers);
}
auto create_callback =
[this, child_moniker = std::string(moniker.data()), koid = koid.value()](
fidl::WireUnownedResult<fcomponent::Realm::CreateChild>& result) mutable {
bool is_error = false;
if (!result.ok()) {
LOGF(ERROR, "Failed to create child '%s': %s", child_moniker.c_str(),
result.FormatDescription().c_str());
is_error = true;
}
if (result.value().is_error()) {
LOGF(ERROR, "Failed to create child '%s': %s", child_moniker.c_str(),
GetErrorString(result.value().error_value()));
is_error = true;
}
if (is_error) {
zx::result result = CallCallback(koid, zx::error(ZX_ERR_INTERNAL));
if (result.is_error()) {
LOGF(ERROR, "Failed to find driver request for '%s': %s", child_moniker.c_str(),
result.status_string());
}
}
};
realm_
->CreateChild(
fdecl::wire::CollectionRef{
.name = fidl::StringView::FromExternal(collection_name),
},
child_decl, child_args_builder.Build())
.Then(std::move(create_callback));
}
void Runner::Start(StartRequestView request, StartCompleter::Sync& completer) {
std::string url = std::string(request->start_info.resolved_url().get());
// When we start a driver, we associate an unforgeable token (the KOID of a
// zx::event) with the start request, through the use of the numbered_handles
// field. We do this so:
// 1. We can securely validate the origin of the request
// 2. We avoid collisions that can occur when relying on the package URL
// 3. We avoid relying on the resolved URL matching the package URL
if (!request->start_info.has_numbered_handles()) {
LOGF(ERROR, "Failed to start driver '%s', invalid request for driver", url.c_str());
completer.Close(ZX_ERR_INVALID_ARGS);
return;
}
auto& handles = request->start_info.numbered_handles();
if (handles.count() != 1 || !handles[0].handle || handles[0].id != kTokenId) {
LOGF(ERROR, "Failed to start driver '%s', invalid request for driver", url.c_str());
completer.Close(ZX_ERR_INVALID_ARGS);
return;
}
zx::result koid = GetKoid(handles[0].handle.get());
if (koid.is_error()) {
completer.Close(ZX_ERR_INVALID_ARGS);
return;
}
zx::result result = CallCallback(koid.value(), zx::ok(StartedComponent{
.info = fidl::ToNatural(request->start_info),
.controller = std::move(request->controller),
}));
if (result.is_error()) {
LOGF(ERROR, "Failed to start driver '%s', unknown request for driver", url.c_str());
completer.Close(ZX_ERR_UNAVAILABLE);
}
}
zx::result<> Runner::CallCallback(zx_koid_t koid, zx::result<StartedComponent> component) {
auto it = start_requests_.find(koid);
if (it == start_requests_.end()) {
return zx::error(ZX_ERR_NOT_FOUND);
}
auto callback = std::move(it->second);
start_requests_.erase(koid);
callback(std::move(component));
return zx::ok();
}
} // namespace driver_manager