blob: 3e06ca213e606de506f00ee30a8b9fc37eb2be0c [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/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/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 <stdint.h>
#include <zircon/processargs.h>
#include <zircon/status.h>
#include <thread>
#include <utility>
#include <fbl/algorithm.h>
#include <fs/pseudo_dir.h>
#include <fs/service.h>
#include <fs/synchronous_vfs.h>
#include <fs/vfs_types.h>
#include "fbl/ref_ptr.h"
namespace {
using GetBootItemFunction = devmgr_launcher::GetBootItemFunction;
using GetArgumentsFunction = devmgr_launcher::GetArgumentsFunction;
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 ArgumentsGet(void* ctx, fidl_txn_t* txn) {
const auto& get_arguments = *static_cast<GetArgumentsFunction*>(ctx);
zx::vmo vmo;
uint32_t length = 0;
if (get_arguments) {
zx_status_t status = get_arguments(&vmo, &length);
if (status != ZX_OK) {
return status;
}
}
return fuchsia_boot_ArgumentsGet_reply(txn, vmo.release(), length);
}
constexpr fuchsia_boot_Arguments_ops kArgumentsOps = {
.Get = ArgumentsGet,
};
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 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::unowned_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());
}));
}
// 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 host_svc_directory(zx::channel bootsvc_server, GetBootItemFunction get_boot_item,
GetArgumentsFunction get_arguments, zx::unowned_job root_job) {
async::Loop loop{&kAsyncLoopConfigNoAttachToCurrentThread};
// Quit the loop when the channel is closed.
async::Wait wait(bootsvc_server.get(), ZX_CHANNEL_PEER_CLOSED, 0, [&loop](...) { loop.Quit(); });
wait.Begin(loop.dispatcher());
// Setup VFS.
fs::SynchronousVfs vfs(loop.dispatcher());
auto root = fbl::MakeRefCounted<fs::PseudoDir>();
// 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;
}
}
// Forward required services from the current namespace. Currently this is just
// fuchsia.process.Launcher.
ForwardService(root, fuchsia_process_Launcher_Name, zx::unowned_channel(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(root, fuchsia_boot_Items_Name, loop.dispatcher(), items_dispatch,
&get_boot_item, &kItemsOps);
auto arguments_dispatch = reinterpret_cast<fidl_dispatch_t*>(fuchsia_boot_Arguments_dispatch);
CreateFakeService(root, fuchsia_boot_Arguments_Name, loop.dispatcher(), arguments_dispatch,
&get_arguments, &kArgumentsOps);
auto root_job_dispatch = reinterpret_cast<fidl_dispatch_t*>(fuchsia_boot_RootJob_dispatch);
CreateFakeService(root, fuchsia_boot_RootJob_Name, loop.dispatcher(), root_job_dispatch,
&root_job, &kRootJobOps);
// Serve VFS on channel.
vfs.ServeDirectory(root, std::move(bootsvc_server), fs::Rights::ReadWrite());
return loop.Run();
}
} // namespace
namespace devmgr_integration_test {
__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() {
Terminate();
}
__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;
}
GetBootItemFunction get_boot_item = std::move(args.get_boot_item);
GetArgumentsFunction get_arguments = std::move(args.get_arguments);
IsolatedDevmgr devmgr;
zx::channel devfs;
zx::channel outgoing_svc_root;
status = devmgr_launcher::Launch(std::move(args), std::move(svc_client), &devmgr.job_, &devfs,
&outgoing_svc_root);
if (status != ZX_OK) {
return status;
}
// Launch host_svc_directory thread after calling devmgr_launcher::Launch, to
// avoid a race when accessing devmgr.job_.
std::thread(host_svc_directory, std::move(svc_server), std::move(get_boot_item),
std::move(get_arguments), zx::unowned_job(devmgr.job_))
.detach();
int fd;
status = fdio_fd_create(devfs.release(), &fd);
if (status != ZX_OK) {
return status;
}
devmgr.devfs_root_.reset(fd);
devmgr.svc_root_dir_.reset(outgoing_svc_root.release());
*out = std::move(devmgr);
return ZX_OK;
}
} // namespace devmgr_integration_test