blob: 5744b4e8ff9fcf41d73a0baf50ffa65f9ceb5d87 [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 "sysmem.h"
#include <crashsvc/crashsvc.h>
#include <fbl/algorithm.h>
#include <fbl/string_printf.h>
#include <fs/remote-dir.h>
#include <fuchsia/boot/c/fidl.h>
#include <fuchsia/device/manager/c/fidl.h>
#include <fuchsia/fshost/c/fidl.h>
#include <fuchsia/paver/c/fidl.h>
#include <fuchsia/virtualconsole/c/fidl.h>
#include <fuchsia/net/c/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/fdio.h>
#include <lib/fdio/directory.h>
#include <lib/logger/provider.h>
#include <lib/process-launcher/launcher.h>
#include <lib/profile/profile.h>
#include <lib/svc/outgoing.h>
#include <lib/kernel-debug/kernel-debug.h>
#include <lib/zx/job.h>
#include <zircon/process.h>
#include <zircon/processargs.h>
#include <zircon/status.h>
// An instance of a zx_service_provider_t.
//
// Includes the |ctx| pointer for the zx_service_provider_t.
typedef struct zx_service_provider_instance {
// The service provider for which this structure is an instance.
const zx_service_provider_t* provider;
// The |ctx| pointer returned by the provider's |init| function, if any.
void* ctx;
} zx_service_provider_instance_t;
static zx_status_t provider_init(zx_service_provider_instance_t* instance) {
if (instance->provider->ops->init) {
zx_status_t status = instance->provider->ops->init(&instance->ctx);
if (status != ZX_OK)
return status;
}
return ZX_OK;
}
static zx_status_t provider_publish(zx_service_provider_instance_t* instance,
async_dispatcher_t* dispatcher, const fbl::RefPtr<fs::PseudoDir>& dir) {
const zx_service_provider_t* provider = instance->provider;
if (!provider->services || !provider->ops->connect)
return ZX_ERR_INVALID_ARGS;
for (size_t i = 0; provider->services[i]; ++i) {
const char* service_name = provider->services[i];
zx_status_t status = dir->AddEntry(
service_name,
fbl::MakeRefCounted<fs::Service>([instance, dispatcher, service_name](zx::channel request) {
return instance->provider->ops->connect(instance->ctx, dispatcher, service_name, request.release());
}));
if (status != ZX_OK) {
for (size_t j = 0; j < i; ++j)
dir->RemoveEntry(provider->services[j]);
return status;
}
}
return ZX_OK;
}
static void provider_release(zx_service_provider_instance_t* instance) {
if (instance->provider->ops->release)
instance->provider->ops->release(instance->ctx);
instance->ctx = nullptr;
}
static zx_status_t provider_load(zx_service_provider_instance_t* instance,
async_dispatcher_t* dispatcher, const fbl::RefPtr<fs::PseudoDir>& dir) {
if (instance->provider->version != SERVICE_PROVIDER_VERSION) {
return ZX_ERR_INVALID_ARGS;
}
zx_status_t status = provider_init(instance);
if (status != ZX_OK) {
return status;
}
status = provider_publish(instance, dispatcher, dir);
if (status != ZX_OK) {
provider_release(instance);
return status;
}
return ZX_OK;
}
static zx_handle_t appmgr_svc;
static zx_handle_t root_job;
static zx_handle_t root_resource;
// We should host the tracelink service ourselves instead of routing the request
// to appmgr.
zx_status_t publish_tracelink(const fbl::RefPtr<fs::PseudoDir>& dir) {
const char* service_name = "fuchsia.tracelink.Registry";
return dir->AddEntry(
service_name,
fbl::MakeRefCounted<fs::Service>([service_name](zx::channel request) {
return fdio_service_connect_at(appmgr_svc, service_name, request.release());
}));
}
// We shouldn't need to access these non-Zircon services from svchost, but
// currently some tests assume they can reach these services from the test
// environment. Instead, we should make the test environment hermetic and
// remove the dependencies on these services.
static constexpr const char* deprecated_services[] = {
"fuchsia.amber.Control",
"fuchsia.cobalt.LoggerFactory",
"fuchsia.devicesettings.DeviceSettingsManager",
"fuchsia.logger.Log",
"fuchsia.logger.LogSink",
// Interface to resolve shell commands.
"fuchsia.process.Resolver",
fuchsia_net_SocketProvider_Name,
// Legacy interface for netstack, defined in //garnet
"fuchsia.netstack.Netstack",
// New interface for netstack (WIP), defined in //zircon
"fuchsia.net.stack.Stack",
"fuchsia.sys.Environment",
"fuchsia.sys.Launcher",
"fuchsia.wlan.service.Wlan",
// TODO(PT-88): This entry is temporary, until PT-88 is resolved.
"fuchsia.tracing.controller.Controller",
// For amberctl over serial shell.
"fuchsia.pkg.PackageResolver",
"fuchsia.pkg.RepositoryManager",
"fuchsia.pkg.rewrite.Engine",
nullptr,
// DO NOT ADD MORE ENTRIES TO THIS LIST.
// Tests should not be accessing services from the environment. Instead,
// they should run in containers that have their own service instances.
};
// List of services which are re-routed to the fshost service provider handle.
static constexpr const char* fshost_services[] = {
fuchsia_fshost_Filesystems_Name,
fuchsia_fshost_Registry_Name,
nullptr,
};
// Forward these Zircon services to miscsvc.
static constexpr const char* miscsvc_services[] = {
fuchsia_paver_Paver_Name,
nullptr,
};
// List of services which are re-routed to the bootsvc service provider.
static constexpr const char* bootsvc_services[] = {
fuchsia_boot_Items_Name,
nullptr,
};
// The ServiceProxy is a Vnode which, if opened, connects to a service.
// However, if treated like a directory, the service proxy will attempt to
// relay the underlying request to the connected service channel.
class ServiceProxy : public fs::Service {
public:
ServiceProxy(zx::unowned_channel svc, fbl::StringPiece svc_name)
: Service([this](zx::channel request) {
return fdio_service_connect_at(svc_->get(), svc_name_.data(), request.release());
}), svc_(std::move(svc)), svc_name_(svc_name) {}
// This proxy may be a directory. Attempt to connect to the requested object,
// and return a RemoteDir representing the connection.
//
// If the underlying service does not speak the directory protocol, then attempting
// to connect to the service will close the connection. This is expected.
zx_status_t Lookup(fbl::RefPtr<Vnode>* out, fbl::StringPiece name) final {
fbl::String path(fbl::StringPrintf("%s/%.*s", svc_name_.data(),
static_cast<int>(name.length()), name.data()));
zx::channel client, server;
zx_status_t status = zx::channel::create(0, &client, &server);
if (status != ZX_OK) {
return status;
}
status = fdio_service_connect_at(svc_->get(), path.data(), server.release());
if (status != ZX_OK) {
return status;
}
*out = fbl::MakeRefCounted<fs::RemoteDir>(std::move(client));
return ZX_OK;
}
private:
zx::unowned_channel svc_;
fbl::StringPiece svc_name_;
};
void publish_service(const fbl::RefPtr<fs::PseudoDir>& dir,
const char* name, zx::unowned_channel svc) {
dir->AddEntry(
name,
fbl::MakeRefCounted<ServiceProxy>(std::move(svc), name));
}
void publish_services(const fbl::RefPtr<fs::PseudoDir>& dir,
const char* const* names, zx::unowned_channel svc) {
for (size_t i = 0; names[i] != nullptr; ++i) {
const char* service_name = names[i];
publish_service(dir, service_name, zx::unowned_channel(svc->get()));
}
}
void publish_remote_service(const fbl::RefPtr<fs::PseudoDir>& dir,
const char* name, zx::unowned_channel forwarding_channel) {
fbl::String path = fbl::StringPrintf("public/%s", name);
dir->AddEntry(name, fbl::MakeRefCounted<fs::Service>(
[path, forwarding_channel = std::move(forwarding_channel)](zx::channel request) {
return fdio_service_connect_at(forwarding_channel->get(), path.c_str(), request.release());
}));
}
//TODO(edcoyne): remove this and make virtcon talk virtual filesystems too.
void publish_proxy_service(const fbl::RefPtr<fs::PseudoDir>& dir,
const char* name, zx::unowned_channel forwarding_channel) {
dir->AddEntry(name, fbl::MakeRefCounted<fs::Service>(
[name, forwarding_channel = std::move(forwarding_channel)](zx::channel request) {
const auto request_handle = request.release();
return forwarding_channel->write(0, name, static_cast<uint32_t>(strlen(name)),
&request_handle, 1);
}));
}
int main(int argc, char** argv) {
bool require_system = false;
if (argc > 1) {
require_system = strcmp(argv[1], "--require-system") == 0 ? true: false;
}
async::Loop loop(&kAsyncLoopConfigNoAttachToThread);
async_dispatcher_t* dispatcher = loop.dispatcher();
svc::Outgoing outgoing(dispatcher);
appmgr_svc = zx_take_startup_handle(PA_HND(PA_USER0, 0));
root_job = zx_take_startup_handle(PA_HND(PA_USER0, 1));
root_resource = zx_take_startup_handle(PA_HND(PA_USER0, 2));
zx::channel devmgr_proxy_channel = zx::channel(zx_take_startup_handle(PA_HND(PA_USER0, 3)));
zx::channel fshost_svc = zx::channel(zx_take_startup_handle(PA_HND(PA_USER0, 4)));
zx::channel virtcon_proxy_channel = zx::channel(zx_take_startup_handle(PA_HND(PA_USER0, 5)));
zx::channel miscsvc_svc = zx::channel(zx_take_startup_handle(PA_HND(PA_USER0, 6)));
zx::channel bootsvc_svc = zx::channel(zx_take_startup_handle(PA_HND(PA_USER0, 7)));
zx_status_t status = outgoing.ServeFromStartupInfo();
if (status != ZX_OK) {
fprintf(stderr, "svchost: error: Failed to serve outgoing directory: %d (%s).\n",
status, zx_status_get_string(status));
return 1;
}
zx_handle_t profile_root_job_copy;
status = zx_handle_duplicate(root_job, ZX_RIGHT_SAME_RIGHTS, &profile_root_job_copy);
if (status != ZX_OK) {
fprintf(stderr, "svchost: failed to duplicate root job: %d (%s).\n", status,
zx_status_get_string(status));
return 1;
}
zx_service_provider_instance_t service_providers[] = {
{.provider = launcher_get_service_provider(), .ctx = nullptr},
{.provider = sysmem2_get_service_provider(), .ctx = nullptr},
{.provider = kernel_debug_get_service_provider(),
.ctx = reinterpret_cast<void*>(static_cast<uintptr_t>(root_resource))},
{.provider = profile_get_service_provider(),
.ctx = reinterpret_cast<void*>(static_cast<uintptr_t>(profile_root_job_copy))},
};
for (size_t i = 0; i < fbl::count_of(service_providers); ++i) {
status = provider_load(&service_providers[i], dispatcher, outgoing.public_dir());
if (status != ZX_OK) {
fprintf(stderr, "svchost: error: Failed to load service provider %zu: %d (%s).\n",
i, status, zx_status_get_string(status));
return 1;
}
}
// if full system is not required drop simple logger service.
zx_service_provider_instance_t logger_service{.provider = logger_get_service_provider(),
.ctx = nullptr};
if(!require_system) {
status = provider_load(&logger_service, dispatcher, outgoing.public_dir());
if (status != ZX_OK) {
fprintf(stderr, "svchost: error: Failed to publish logger: %d (%s).\n",
status, zx_status_get_string(status));
return 1;
}
}
status = publish_tracelink(outgoing.public_dir());
if (status != ZX_OK) {
fprintf(stderr, "svchost: error: Failed to publish tracelink: %d (%s).\n",
status, zx_status_get_string(status));
return 1;
}
publish_services(outgoing.public_dir(), deprecated_services, zx::unowned_channel(appmgr_svc));
publish_services(outgoing.public_dir(), fshost_services, zx::unowned_channel(fshost_svc));
publish_services(outgoing.public_dir(), miscsvc_services, zx::unowned_channel(miscsvc_svc));
publish_services(outgoing.public_dir(), bootsvc_services, zx::unowned_channel(bootsvc_svc));
publish_remote_service(outgoing.public_dir(),
fuchsia_device_manager_DebugDumper_Name,
zx::unowned_channel(devmgr_proxy_channel));
publish_remote_service(outgoing.public_dir(),
fuchsia_device_manager_Administrator_Name,
zx::unowned_channel(devmgr_proxy_channel));
if (virtcon_proxy_channel.is_valid()) {
publish_proxy_service(outgoing.public_dir(),
fuchsia_virtualconsole_SessionManager_Name,
zx::unowned_channel(virtcon_proxy_channel));
}
thrd_t thread;
status = start_crashsvc(zx::job(root_job), require_system ? appmgr_svc : ZX_HANDLE_INVALID,
&thread);
if (status != ZX_OK) {
// The system can still function without crashsvc, log the error but
// keep going.
fprintf(stderr, "svchost: error: Failed to start crashsvc: %d (%s).\n",
status, zx_status_get_string(status));
} else {
thrd_detach(thread);
}
status = loop.Run();
for (size_t i = 0; i < fbl::count_of(service_providers); ++i) {
provider_release(&service_providers[i]);
}
return status;
}