blob: 62480a1a5d90443567e85270bd259f9ff33f682e [file] [log] [blame]
// Copyright 2018 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 <fuchsia/boot/c/fidl.h>
#include <fuchsia/boot/llcpp/fidl.h>
#include <fuchsia/exception/llcpp/fidl.h>
#include <fuchsia/kernel/c/fidl.h>
#include <fuchsia/power/manager/llcpp/fidl.h>
#include <fuchsia/scheduler/c/fidl.h>
#include <fuchsia/sys2/llcpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/async/cpp/wait.h>
#include <lib/async/dispatcher.h>
#include <lib/devmgr-integration-test/fixture.h>
#include <lib/devmgr-launcher/launch.h>
#include <lib/fdio/cpp/caller.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/fdio.h>
#include <lib/fidl-async/bind.h>
#include <lib/fidl-async/cpp/bind.h>
#include <lib/fidl/llcpp/connect_service.h>
#include <lib/service/llcpp/service.h>
#include <lib/vfs/cpp/remote_dir.h>
#include <lib/zx/exception.h>
#include <stdint.h>
#include <zircon/processargs.h>
#include <zircon/status.h>
#include <zircon/syscalls/exception.h>
#include <map>
#include <thread>
#include <utility>
#include <fbl/algorithm.h>
#include <fbl/ref_ptr.h>
#include <fbl/string_printf.h>
#include <mock-boot-arguments/server.h>
#include "src/lib/storage/vfs/cpp/pseudo_dir.h"
#include "src/lib/storage/vfs/cpp/service.h"
#include "src/lib/storage/vfs/cpp/synchronous_vfs.h"
#include "src/lib/storage/vfs/cpp/vfs_types.h"
namespace {
using GetBootItemFunction = devmgr_launcher::GetBootItemFunction;
// TODO(http://fxbug.dev/33183): Replace this with a test component_manager.
class FakeRealm : public fidl::WireInterface<fuchsia_sys2::Realm> {
public:
void BindChild(fuchsia_sys2::wire::ChildRef child,
fidl::ServerEnd<fuchsia_io::Directory> exposed_dir,
BindChildCompleter::Sync& completer) override {
exposed_dir_ = std::move(exposed_dir);
completer.ReplySuccess();
}
void CreateChild(fuchsia_sys2::wire::CollectionRef collection, fuchsia_sys2::wire::ChildDecl decl,
CreateChildCompleter::Sync& completer) override {
completer.ReplySuccess();
}
void DestroyChild(fuchsia_sys2::wire::ChildRef child,
DestroyChildCompleter::Sync& completer) override {}
void ListChildren(fuchsia_sys2::wire::CollectionRef collection,
fidl::ServerEnd<fuchsia_sys2::ChildIterator> iter,
ListChildrenCompleter::Sync& completer) override {}
private:
fidl::ServerEnd<fuchsia_io::Directory> exposed_dir_;
};
class FakePowerRegistration
: public fidl::WireInterface<fuchsia_power_manager::DriverManagerRegistration> {
public:
void Register(fidl::ClientEnd<fuchsia_device_manager::SystemStateTransition> transition,
fidl::ClientEnd<fuchsia_io::Directory> dir,
RegisterCompleter::Sync& completer) override {
// Store these so the other side doesn't see the channels close.
transition_ = std::move(transition);
dir_ = std::move(dir);
completer.ReplySuccess();
}
private:
fidl::ClientEnd<fuchsia_device_manager::SystemStateTransition> transition_;
fidl::ClientEnd<fuchsia_io::Directory> dir_;
};
zx_status_t ItemsGet(void* ctx, uint32_t type, uint32_t extra, fidl_txn_t* txn) {
const auto& get_boot_item = *static_cast<GetBootItemFunction*>(ctx);
zx::vmo vmo;
uint32_t length = 0;
if (get_boot_item) {
zx_status_t status = get_boot_item(type, extra, &vmo, &length);
if (status != ZX_OK) {
return status;
}
}
return fuchsia_boot_ItemsGet_reply(txn, vmo.release(), length);
}
constexpr fuchsia_boot_Items_ops kItemsOps = {
.Get = ItemsGet,
};
zx_status_t RootJobGet(void* ctx, fidl_txn_t* txn) {
const auto& root_job = *static_cast<zx::unowned_job*>(ctx);
zx::job job;
zx_status_t status = root_job->duplicate(ZX_RIGHT_SAME_RIGHTS, &job);
if (status != ZX_OK) {
return status;
}
return fuchsia_kernel_RootJobGet_reply(txn, job.release());
}
constexpr fuchsia_kernel_RootJob_ops kRootJobOps = {
.Get = RootJobGet,
};
template <class Protocol>
void CreateFakeCppService(fbl::RefPtr<fs::PseudoDir> root, async_dispatcher_t* dispatcher,
std::unique_ptr<typename fidl::WireInterface<Protocol>> server) {
auto node = fbl::MakeRefCounted<fs::Service>(
[dispatcher, server{std::move(server)}](fidl::ServerEnd<Protocol> channel) {
return fidl::BindSingleInFlightOnly(dispatcher, std::move(channel), server.get());
});
root->AddEntry(fidl::DiscoverableProtocolName<Protocol>, node);
}
void CreateFakeService(fbl::RefPtr<fs::PseudoDir> root, const char* name,
async_dispatcher_t* dispatcher, fidl_dispatch_t* dispatch, void* ctx,
const void* ops) {
auto node =
fbl::MakeRefCounted<fs::Service>([dispatcher, dispatch, ctx, ops](zx::channel channel) {
return fidl_bind(dispatcher, channel.release(), dispatch, ctx, ops);
});
root->AddEntry(name, node);
}
void ForwardService(fbl::RefPtr<fs::PseudoDir> root, const char* name,
fidl::ClientEnd<fuchsia_io::Directory> svc_client) {
root->AddEntry(name, fbl::MakeRefCounted<fs::Service>(
[name, svc_client = std::move(svc_client)](zx::channel request) {
return fdio_service_connect_at(svc_client.channel().get(), name,
request.release());
}));
}
fidl::ClientEnd<fuchsia_io::Directory> CloneDirectory(
fidl::UnownedClientEnd<fuchsia_io::Directory> end) {
return service::MaybeClone(end);
}
} // namespace
namespace devmgr_integration_test {
// We keep this structure opaque so that we don't grow a bunch of public dependencies for the
// implementation of this loop
struct IsolatedDevmgr::SvcLoopState {
~SvcLoopState() {
// We must shut down the loop before we operate on vfs and bootsvc_wait in order to prevent
// concurrent access to them
loop.Shutdown();
}
GetBootItemFunction get_boot_item;
async::Loop loop{&kAsyncLoopConfigNoAttachToCurrentThread};
fbl::RefPtr<fs::PseudoDir> root{fbl::MakeRefCounted<fs::PseudoDir>()};
fs::SynchronousVfs vfs{loop.dispatcher()};
async::Wait bootsvc_wait;
};
struct IsolatedDevmgr::ExceptionLoopState {
ExceptionLoopState(async_dispatcher_t* dispatcher, zx::channel exception_channel)
: dispatcher_(dispatcher),
exception_channel_(std::move(exception_channel)),
watcher_(this, exception_channel_.get(), ZX_CHANNEL_READABLE) {
if (dispatcher_ == nullptr) {
loop_.emplace(&kAsyncLoopConfigNoAttachToCurrentThread);
dispatcher_ = loop_->dispatcher();
}
watcher_.Begin(dispatcher_);
}
~ExceptionLoopState() {
// We must shut down the loop before we operate on watcher_ in order to prevent
// concurrent access to them. If dispatcher is passed in, this should be done beforehand.
if (loop_) {
loop_->Shutdown();
}
}
void HandleException() {
printf("Handling devmgr exception\n");
zx_exception_info_t info;
zx::exception exception;
zx_status_t status = exception_channel_.read(0, &info, exception.reset_and_get_address(),
sizeof(info), 1, nullptr, nullptr);
if (status != ZX_OK) {
return;
}
// Send exceptions to the ambient fuchsia.exception.Handler.
auto local = service::Connect<fuchsia_exception::Handler>();
if (!local.is_ok()) {
return;
}
fidl::WireSyncClient<fuchsia_exception::Handler> handler(std::move(*local));
fuchsia_exception::wire::ExceptionInfo einfo;
einfo.process_koid = info.pid;
einfo.thread_koid = info.tid;
einfo.type = static_cast<fuchsia_exception::wire::ExceptionType>(info.type);
handler.OnException(std::move(exception), einfo);
}
void DevmgrException(async_dispatcher_t* dispatcher, async::WaitBase* wait, zx_status_t status,
const zx_packet_signal_t* signal) {
if (status == ZX_ERR_CANCELED) {
return;
}
crashed_ = true;
HandleException();
if (exception_callback_) {
exception_callback_();
}
}
std::optional<async::Loop> loop_;
async_dispatcher_t* dispatcher_ = nullptr;
zx::channel exception_channel_;
std::atomic<bool> crashed_ = false;
fit::closure exception_callback_;
async::WaitMethod<ExceptionLoopState, &ExceptionLoopState::DevmgrException> watcher_;
};
zx_status_t IsolatedDevmgr::SetupExceptionLoop(async_dispatcher_t* dispatcher,
zx::channel exception_channel) {
exception_loop_state_ =
std::make_unique<ExceptionLoopState>(dispatcher, std::move(exception_channel));
if (dispatcher == nullptr) {
return exception_loop_state_->loop_->StartThread("isolated-devmgr-exceptionloop");
} else {
return ZX_OK;
}
}
// Create and host a /svc directory for the devcoordinator process we're creating.
// TODO(fxbug.dev/35991): IsolatedDevmgr and devmgr_launcher should be rewritten to make use of
// Components v2/Test Framework concepts as soon as those are ready enough. For now this has to be
// manually kept in sync with devcoordinator's manifest in //src/sys/root/devcoordinator.cml
// (although it already seems to be incomplete).
zx_status_t IsolatedDevmgr::SetupSvcLoop(
fidl::ServerEnd<fuchsia_io::Directory> bootsvc_server,
fidl::ClientEnd<fuchsia_io::Directory> fshost_outgoing_client,
GetBootItemFunction get_boot_item, std::map<std::string, std::string>&& boot_args) {
svc_loop_state_ = std::make_unique<SvcLoopState>();
svc_loop_state_->get_boot_item = std::move(get_boot_item);
// Quit the loop when the channel is closed.
svc_loop_state_->bootsvc_wait.set_object(bootsvc_server.channel().get());
svc_loop_state_->bootsvc_wait.set_trigger(ZX_CHANNEL_PEER_CLOSED);
svc_loop_state_->bootsvc_wait.set_handler([loop = &svc_loop_state_->loop](...) { loop->Quit(); });
svc_loop_state_->bootsvc_wait.Begin(svc_loop_state_->loop.dispatcher());
// Connect to /svc in the current namespace.
auto svc_client = *service::OpenServiceRoot();
// Connect to /svc in fshost's outgoing directory
auto fshost_endpoints = fidl::CreateEndpoints<fuchsia_io::Directory>();
if (!fshost_endpoints.is_ok()) {
return fshost_endpoints.status_value();
}
auto [fshost_svc_client, fshost_svc_server] = *std::move(fshost_endpoints);
zx_status_t status =
fdio_open_at(fshost_outgoing_client.channel().get(), "svc",
ZX_FS_RIGHT_READABLE | ZX_FS_RIGHT_WRITABLE | ZX_FS_FLAG_DIRECTORY,
fshost_svc_server.TakeChannel().release());
if (status != ZX_OK) {
return status;
}
// Forward required services from the current namespace.
ForwardService(svc_loop_state_->root, "fuchsia.process.Launcher", CloneDirectory(svc_client));
ForwardService(svc_loop_state_->root, "fuchsia.logger.LogSink", CloneDirectory(svc_client));
ForwardService(svc_loop_state_->root, "fuchsia.boot.RootResource", std::move(svc_client));
ForwardService(svc_loop_state_->root, "fuchsia.fshost.Loader", std::move(fshost_svc_client));
boot_args.try_emplace("virtcon.disable", "true");
// Host fake instances of some services normally provided by bootsvc and routed to devcoordinator
// by component_manager. The difference between these fakes and the optional services above is
// that these 1) are fakeable (unlike fuchsia.process.Launcher) and 2) seem to be required
// services for devcoordinator.
auto items_dispatch = reinterpret_cast<fidl_dispatch_t*>(fuchsia_boot_Items_dispatch);
CreateFakeService(svc_loop_state_->root, fuchsia_boot_Items_Name,
svc_loop_state_->loop.dispatcher(), items_dispatch,
&svc_loop_state_->get_boot_item, &kItemsOps);
auto root_job_dispatch = reinterpret_cast<fidl_dispatch_t*>(fuchsia_kernel_RootJob_dispatch);
CreateFakeService(svc_loop_state_->root, fuchsia_kernel_RootJob_Name,
svc_loop_state_->loop.dispatcher(), root_job_dispatch, &job_, &kRootJobOps);
// Create fake Boot Arguments.
CreateFakeCppService<fuchsia_boot::Arguments>(
svc_loop_state_->root, svc_loop_state_->loop.dispatcher(),
std::make_unique<mock_boot_arguments::Server>(std::move(boot_args)));
// Create fake Power Registration.
CreateFakeCppService<fuchsia_power_manager::DriverManagerRegistration>(
svc_loop_state_->root, svc_loop_state_->loop.dispatcher(),
std::make_unique<FakePowerRegistration>());
CreateFakeCppService<fuchsia_sys2::Realm>(
svc_loop_state_->root, svc_loop_state_->loop.dispatcher(), std::make_unique<FakeRealm>());
// Serve VFS on channel.
svc_loop_state_->vfs.ServeDirectory(svc_loop_state_->root, std::move(bootsvc_server),
fs::Rights::ReadWrite());
return svc_loop_state_->loop.StartThread("isolated-devmgr-svcloop");
}
__EXPORT
zx_status_t IsolatedDevmgr::AddDevfsToOutgoingDir(vfs::PseudoDir* outgoing_root_dir) {
zx::channel client, server;
auto status = zx::channel::create(0, &client, &server);
if (status != ZX_OK) {
return status;
}
fdio_cpp::UnownedFdioCaller fd(devfs_root_.get());
fdio_service_clone_to(fd.borrow_channel(), server.release());
// Add devfs to out directory.
auto devfs_out = std::make_unique<vfs::RemoteDir>(std::move(client));
outgoing_root_dir->AddEntry("dev", std::move(devfs_out));
return ZX_OK;
}
__EXPORT
devmgr_launcher::Args IsolatedDevmgr::DefaultArgs() {
devmgr_launcher::Args args;
args.sys_device_driver = kSysdevDriver;
args.load_drivers.push_back("/boot/driver/test.so");
args.driver_search_paths.push_back("/boot/driver/test");
return args;
}
__EXPORT
IsolatedDevmgr::IsolatedDevmgr() = default;
__EXPORT
IsolatedDevmgr::~IsolatedDevmgr() { Terminate(); }
__EXPORT
IsolatedDevmgr::IsolatedDevmgr(IsolatedDevmgr&& other)
: job_(std::move(other.job_)),
svc_root_dir_(std::move(other.svc_root_dir_)),
fshost_outgoing_dir_(std::move(other.fshost_outgoing_dir_)),
devfs_root_(std::move(other.devfs_root_)),
component_lifecycle_client_(std::move(other.component_lifecycle_client_)),
svc_loop_state_(std::move(other.svc_loop_state_)),
exception_loop_state_(std::move(other.exception_loop_state_)) {}
__EXPORT
IsolatedDevmgr& IsolatedDevmgr::operator=(IsolatedDevmgr&& other) {
Terminate();
job_ = std::move(other.job_);
component_lifecycle_client_ = std::move(other.component_lifecycle_client_);
devfs_root_ = std::move(other.devfs_root_);
svc_root_dir_ = std::move(other.svc_root_dir_);
fshost_outgoing_dir_ = std::move(other.fshost_outgoing_dir_);
svc_loop_state_ = std::move(other.svc_loop_state_);
exception_loop_state_ = std::move(other.exception_loop_state_);
return *this;
}
__EXPORT
void IsolatedDevmgr::Terminate() {
if (job_.is_valid()) {
job_.kill();
// Best-effort; ignores error.
zx_signals_t observed = 0;
job_.wait_one(ZX_TASK_TERMINATED, zx::time::infinite(), &observed);
}
job_.reset();
}
__EXPORT
zx_status_t IsolatedDevmgr::Create(devmgr_launcher::Args args, IsolatedDevmgr* out) {
return Create(std::move(args), nullptr, out);
}
__EXPORT
zx_status_t IsolatedDevmgr::Create(devmgr_launcher::Args args, async_dispatcher_t* dispatcher,
IsolatedDevmgr* out) {
auto svc_endpoints = fidl::CreateEndpoints<fuchsia_io::Directory>();
if (!svc_endpoints.is_ok()) {
return svc_endpoints.status_value();
}
auto [svc_client, svc_server] = *std::move(svc_endpoints);
auto fshost_endpoints = fidl::CreateEndpoints<fuchsia_io::Directory>();
if (!fshost_endpoints.is_ok()) {
return fshost_endpoints.status_value();
}
auto [fshost_outgoing_client, fshost_outgoing_server] = *std::move(fshost_endpoints);
GetBootItemFunction get_boot_item = std::move(args.get_boot_item);
auto component_lifecycle = fidl::CreateEndpoints<fuchsia_process_lifecycle::Lifecycle>();
if (!component_lifecycle.is_ok()) {
return component_lifecycle.status_value();
}
IsolatedDevmgr devmgr;
zx::channel devfs;
fidl::ClientEnd<fuchsia_io::Directory> outgoing_svc_root;
std::map<std::string, std::string> boot_args = std::move(args.boot_args);
zx_status_t status = devmgr_launcher::Launch(std::move(args), svc_client.TakeChannel(),
fshost_outgoing_server.TakeChannel(),
component_lifecycle->server.TakeChannel(),
&devmgr.job_, &devfs, &outgoing_svc_root.channel());
if (status != ZX_OK) {
return status;
}
zx::channel exception_channel;
devmgr.containing_job().create_exception_channel(0, &exception_channel);
status = devmgr.SetupExceptionLoop(dispatcher, std::move(exception_channel));
if (status != ZX_OK) {
return status;
}
status = devmgr.SetupSvcLoop(std::move(svc_server), CloneDirectory(fshost_outgoing_client),
std::move(get_boot_item), std::move(boot_args));
if (status != ZX_OK) {
return status;
}
int fd;
status = fdio_fd_create(devfs.release(), &fd);
if (status != ZX_OK) {
return status;
}
devmgr.devfs_root_.reset(fd);
devmgr.component_lifecycle_client_ = std::move(component_lifecycle->client);
devmgr.svc_root_dir_ = std::move(outgoing_svc_root);
devmgr.fshost_outgoing_dir_ = std::move(fshost_outgoing_client);
*out = std::move(devmgr);
return ZX_OK;
}
__EXPORT void IsolatedDevmgr::SetExceptionCallback(fit::closure exception_callback) {
exception_loop_state_->exception_callback_ = std::move(exception_callback);
}
__EXPORT bool IsolatedDevmgr::crashed() const { return exception_loop_state_->crashed_; }
} // namespace devmgr_integration_test