blob: f6b4837b4de0971ba62d2a293cc5972e0b4b7cfd [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/process/c/fidl.h>
#include <fuchsia/scheduler/c/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/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 <stdint.h>
#include <zircon/processargs.h>
#include <zircon/status.h>
#include <map>
#include <thread>
#include <utility>
#include <fbl/algorithm.h>
#include <fbl/ref_ptr.h>
#include <fbl/string_printf.h>
#include <fs/pseudo_dir.h>
#include <fs/service.h>
#include <fs/synchronous_vfs.h>
#include <fs/vfs_types.h>
#include <mock-boot-arguments/server.h>
namespace {
using GetBootItemFunction = devmgr_launcher::GetBootItemFunction;
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_boot_RootJobGet_reply(txn, job.release());
}
constexpr fuchsia_boot_RootJob_ops kRootJobOps = {
.Get = RootJobGet,
};
void CreateFakeCppService(fbl::RefPtr<fs::PseudoDir> root, const char* name,
async_dispatcher_t* dispatcher,
std::unique_ptr<llcpp::fuchsia::boot::Arguments::Interface> server) {
auto node = fbl::MakeRefCounted<fs::Service>(
[dispatcher, server{std::move(server)}](zx::channel channel) {
return fidl::Bind(dispatcher, std::move(channel), server.get());
});
root->AddEntry(name, 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, zx::channel 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.get(), name, request.release());
}));
}
} // 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;
};
// Create and host a /svc directory for the devcoordinator process we're creating.
// TODO(fxb/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(zx::channel bootsvc_server,
zx::channel 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.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.
zx::channel svc_client;
{
zx::channel svc_server;
zx_status_t status = zx::channel::create(0, &svc_client, &svc_server);
if (status != ZX_OK) {
return status;
}
status = fdio_service_connect("/svc", svc_server.release());
if (status != ZX_OK) {
return status;
}
}
// Connect to /svc in fshost's outgoing directory
zx::channel fshost_svc_client;
{
zx::channel fshost_svc_server;
zx_status_t status = zx::channel::create(0, &fshost_svc_client, &fshost_svc_server);
if (status != ZX_OK) {
return status;
}
status = fdio_open_at(fshost_outgoing_client.get(), "svc",
ZX_FS_RIGHT_READABLE | ZX_FS_RIGHT_WRITABLE | ZX_FS_FLAG_DIRECTORY,
fshost_svc_server.release());
if (status != ZX_OK) {
return status;
}
}
// Forward required services from the current namespace. Currently this is just
// fuchsia.process.Launcher.
ForwardService(svc_loop_state_->root, fuchsia_process_Launcher_Name, std::move(svc_client));
ForwardService(svc_loop_state_->root, "fuchsia.logger.LogSink", std::move(svc_client));
ForwardService(svc_loop_state_->root, "fuchsia.fshost.Loader", std::move(fshost_svc_client));
// 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_boot_RootJob_dispatch);
CreateFakeService(svc_loop_state_->root, fuchsia_boot_RootJob_Name,
svc_loop_state_->loop.dispatcher(), root_job_dispatch, &job_, &kRootJobOps);
CreateFakeCppService(svc_loop_state_->root, llcpp::fuchsia::boot::Arguments::Name,
svc_loop_state_->loop.dispatcher(),
std::make_unique<mock_boot_arguments::Server>(std::move(boot_args)));
// 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
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_)),
devfs_root_(std::move(other.devfs_root_)),
svc_loop_state_(std::move(other.svc_loop_state_)) {}
__EXPORT
IsolatedDevmgr& IsolatedDevmgr::operator=(IsolatedDevmgr&& other) {
Terminate();
job_ = std::move(other.job_);
devfs_root_ = std::move(other.devfs_root_);
svc_root_dir_ = std::move(other.svc_root_dir_);
svc_loop_state_ = std::move(other.svc_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) {
zx::channel svc_client, svc_server;
zx_status_t status = zx::channel::create(0, &svc_client, &svc_server);
if (status != ZX_OK) {
return status;
}
zx::channel fshost_outgoing_client, fshost_outgoing_server;
status = zx::channel::create(0, &fshost_outgoing_client, &fshost_outgoing_server);
if (status != ZX_OK) {
return status;
}
GetBootItemFunction get_boot_item = std::move(args.get_boot_item);
zx::channel component_lifecycle_client, component_lifecycle_server;
status = zx::channel::create(0, &component_lifecycle_client, &component_lifecycle_server);
if (status != ZX_OK) {
return status;
}
IsolatedDevmgr devmgr;
zx::channel devfs;
zx::channel outgoing_svc_root;
std::map<std::string, std::string> boot_args = std::move(args.boot_args);
status = devmgr_launcher::Launch(
std::move(args), std::move(svc_client), std::move(fshost_outgoing_server),
std::move(component_lifecycle_server), &devmgr.job_, &devfs, &outgoing_svc_root);
if (status != ZX_OK) {
return status;
}
status = devmgr.SetupSvcLoop(std::move(svc_server), std::move(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_.reset(component_lifecycle_client.release());
devmgr.svc_root_dir_.reset(outgoing_svc_root.release());
*out = std::move(devmgr);
return ZX_OK;
}
} // namespace devmgr_integration_test