blob: d1331305a3630aaa82dfcba35fe3dc89b3259e00 [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 "src/virtualization/packages/biscotti_guest/linux_runner/guest.h"
#include <arpa/inet.h>
#include <fcntl.h>
#include <grpc++/grpc++.h>
#include <grpc++/server_posix.h>
#include <lib/async/default.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/fdio.h>
#include <lib/fidl/cpp/vector.h>
#include <lib/fzl/fdio.h>
#include <netinet/in.h>
#include <src/lib/fxl/logging.h>
#include <unistd.h>
#include <zircon/processargs.h>
#include <memory>
#include "src/lib/fxl/logging.h"
#include "src/virtualization/lib/grpc/grpc_vsock_stub.h"
#include "src/virtualization/packages/biscotti_guest/linux_runner/ports.h"
#include "src/virtualization/packages/biscotti_guest/third_party/protos/vm_guest.grpc.pb.h"
namespace linux_runner {
static constexpr const char* kLinuxEnvirionmentName = "biscotti";
static constexpr const char* kLinuxGuestPackage =
"fuchsia-pkg://fuchsia.com/biscotti_guest#meta/biscotti_guest.cmx";
static constexpr const char* kContainerName = "stretch";
static constexpr const char* kContainerImageAlias = "debian/stretch";
static constexpr const char* kContainerImageServer =
"https://storage.googleapis.com/cros-containers";
static constexpr const char* kDefaultContainerUser = "machina";
static constexpr const char* kLinuxUriScheme = "linux://";
// Minfs max file size is currently just under 4GB.
static constexpr const char* kStatefulImagePath = "/data/stateful.img";
static constexpr const char* kExtrasImagePath = "/pkg/data/extras.img";
static fidl::InterfaceHandle<fuchsia::io::File> GetOrCreateStatefulPartition(
size_t image_size) {
TRACE_DURATION("linux_runner", "GetOrCreateStatefulPartition");
int fd = open(kStatefulImagePath, O_RDWR);
if (fd < 0 && errno == ENOENT) {
fd = open(kStatefulImagePath, O_RDWR | O_CREAT);
if (fd < 0) {
FXL_LOG(ERROR) << "Failed to create stateful image: " << strerror(errno);
return nullptr;
}
if (ftruncate(fd, image_size) < 0) {
FXL_LOG(ERROR) << "Failed to truncate image: " << strerror(errno);
return nullptr;
}
}
if (fd < 0) {
FXL_LOG(ERROR) << "Failed to open image: " << strerror(errno);
return nullptr;
}
zx_handle_t handle = ZX_HANDLE_INVALID;
zx_status_t status = fdio_get_service_handle(fd, &handle);
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Failed to get service handle: " << status;
return nullptr;
}
return fidl::InterfaceHandle<fuchsia::io::File>(zx::channel(handle));
}
static fidl::InterfaceHandle<fuchsia::io::File> GetExtrasPartition() {
TRACE_DURATION("linux_runner", "GetExtrasPartition");
int fd = open(kExtrasImagePath, O_RDONLY);
if (fd < 0) {
return nullptr;
}
zx_handle_t handle = ZX_HANDLE_INVALID;
zx_status_t status = fdio_get_service_handle(fd, &handle);
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Failed to get service handle: " << status;
return nullptr;
}
return fidl::InterfaceHandle<fuchsia::io::File>(zx::channel(handle));
}
static fidl::VectorPtr<fuchsia::virtualization::BlockDevice> GetBlockDevices(
size_t stateful_image_size) {
TRACE_DURATION("linux_runner", "GetBlockDevices");
auto file_handle = GetOrCreateStatefulPartition(stateful_image_size);
FXL_CHECK(file_handle) << "Failed to open stateful file";
fidl::VectorPtr<fuchsia::virtualization::BlockDevice> devices;
devices.push_back({
"stateful",
fuchsia::virtualization::BlockMode::READ_WRITE,
fuchsia::virtualization::BlockFormat::RAW,
std::move(file_handle),
});
auto extras_handle = GetExtrasPartition();
if (extras_handle) {
devices.push_back({
"extras",
fuchsia::virtualization::BlockMode::VOLATILE_WRITE,
fuchsia::virtualization::BlockFormat::RAW,
std::move(extras_handle),
});
}
return devices;
}
// static
zx_status_t Guest::CreateAndStart(sys::ComponentContext* context,
GuestConfig config,
std::unique_ptr<Guest>* guest) {
TRACE_DURATION("linux_runner", "Guest::CreateAndStart");
fuchsia::virtualization::ManagerPtr guestmgr;
context->svc()->Connect(guestmgr.NewRequest());
fuchsia::virtualization::RealmPtr guest_env;
guestmgr->Create(kLinuxEnvirionmentName, guest_env.NewRequest());
*guest = std::make_unique<Guest>(context, config, std::move(guest_env));
return ZX_OK;
}
Guest::Guest(sys::ComponentContext* context, GuestConfig config,
fuchsia::virtualization::RealmPtr env)
: async_(async_get_default_dispatcher()),
executor_(async_),
config_(config),
guest_env_(std::move(env)),
wayland_dispatcher_(context, fit::bind_member(this, &Guest::OnNewView)) {
guest_env_->GetHostVsockEndpoint(socket_endpoint_.NewRequest());
executor_.schedule_task(Start());
}
Guest::~Guest() {
if (grpc_server_) {
grpc_server_->inner()->Shutdown();
grpc_server_->inner()->Wait();
}
}
fit::promise<> Guest::Start() {
TRACE_DURATION("linux_runner", "Guest::Start");
return StartGrpcServer()
.and_then([this](std::unique_ptr<GrpcVsockServer>& server) mutable
-> fit::result<void, zx_status_t> {
grpc_server_ = std::move(server);
StartGuest();
return fit::ok();
})
.or_else([](const zx_status_t& status) {
FXL_LOG(ERROR) << "Failed to start guest: " << status;
return fit::ok();
});
}
fit::promise<std::unique_ptr<GrpcVsockServer>, zx_status_t>
Guest::StartGrpcServer() {
TRACE_DURATION("linux_runner", "Guest::StartGrpcServer");
fuchsia::virtualization::HostVsockEndpointPtr socket_endpoint;
guest_env_->GetHostVsockEndpoint(socket_endpoint.NewRequest());
GrpcVsockServerBuilder builder(std::move(socket_endpoint));
// LogCollector
builder.AddListenPort(kLogCollectorPort);
builder.RegisterService(&log_collector_);
// StartupListener
builder.AddListenPort(kStartupListenerPort);
builder.RegisterService(
static_cast<vm_tools::StartupListener::Service*>(this));
// TremplinListener
builder.AddListenPort(kTremplinListenerPort);
builder.RegisterService(
static_cast<vm_tools::tremplin::TremplinListener::Service*>(this));
// ContainerListener
builder.AddListenPort(kGarconPort);
builder.RegisterService(
static_cast<vm_tools::container::ContainerListener::Service*>(this));
return builder.Build();
}
void Guest::StartGuest() {
TRACE_DURATION("linux_runner", "Guest::StartGuest");
FXL_CHECK(!guest_controller_)
<< "Called StartGuest with an existing instance";
FXL_LOG(INFO) << "Launching guest...";
fuchsia::virtualization::LaunchInfo launch_info;
launch_info.url = kLinuxGuestPackage;
launch_info.args.push_back("--virtio-gpu=false");
launch_info.block_devices = GetBlockDevices(config_.stateful_image_size);
launch_info.wayland_device = fuchsia::virtualization::WaylandDevice::New();
launch_info.wayland_device->dispatcher = wayland_dispatcher_.NewBinding();
auto vm_create_nonce = TRACE_NONCE();
TRACE_FLOW_BEGIN("linux_runner", "LaunchInstance", vm_create_nonce);
guest_env_->LaunchInstance(
std::move(launch_info), guest_controller_.NewRequest(),
[this, vm_create_nonce](uint32_t cid) {
TRACE_DURATION("linux_runner", "LaunchInstance Callback");
TRACE_FLOW_END("linux_runner", "LaunchInstance", vm_create_nonce);
FXL_LOG(INFO) << "Guest launched with CID " << cid;
guest_cid_ = cid;
TRACE_FLOW_BEGIN("linux_runner", "TerminaBoot", vm_ready_nonce_);
});
}
void Guest::MountExtrasPartition() {
TRACE_DURATION("linux_runner", "Guest::MountExtrasPartition");
FXL_CHECK(maitred_)
<< "Called MountExtrasPartition without a maitre'd connection";
FXL_LOG(INFO) << "Mounting Extras Partition";
grpc::ClientContext context;
vm_tools::MountRequest request;
vm_tools::MountResponse response;
request.mutable_source()->assign("/dev/vdc");
request.mutable_target()->assign("/mnt/shared");
request.mutable_fstype()->assign("ext2");
request.mutable_options()->assign("");
request.set_mountflags(0);
{
TRACE_DURATION("linux_runner", "MountRPC");
auto grpc_status = maitred_->Mount(&context, request, &response);
FXL_CHECK(grpc_status.ok())
<< "Failed to mount extras filesystem: " << grpc_status.error_message();
}
FXL_LOG(INFO) << "Mounted Filesystem: " << response.error();
}
void Guest::ConfigureNetwork() {
TRACE_DURATION("linux_runner", "Guest::ConfigureNetwork");
FXL_CHECK(maitred_)
<< "Called ConfigureNetwork without a maitre'd connection";
struct in_addr addr;
uint32_t ip_addr = 0;
FXL_LOG(INFO) << "Using ip: " << LINUX_RUNNER_IP_DEFAULT;
FXL_CHECK(inet_aton(LINUX_RUNNER_IP_DEFAULT, &addr) != 0)
<< "Failed to parse address string";
ip_addr = addr.s_addr;
uint32_t netmask = 0;
FXL_LOG(INFO) << "Using netmask: " << LINUX_RUNNER_NETMASK_DEFAULT;
FXL_CHECK(inet_aton(LINUX_RUNNER_NETMASK_DEFAULT, &addr) != 0)
<< "Failed to parse address string";
netmask = addr.s_addr;
uint32_t gateway = 0;
FXL_LOG(INFO) << "Using gateway: " << LINUX_RUNNER_GATEWAY_DEFAULT;
FXL_CHECK(inet_aton(LINUX_RUNNER_GATEWAY_DEFAULT, &addr) != 0)
<< "Failed to parse address string";
gateway = addr.s_addr;
FXL_LOG(INFO) << "Configuring Guest Network...";
grpc::ClientContext context;
vm_tools::NetworkConfigRequest request;
vm_tools::EmptyMessage response;
vm_tools::IPv4Config* config = request.mutable_ipv4_config();
config->set_address(ip_addr);
config->set_gateway(gateway);
config->set_netmask(netmask);
{
TRACE_DURATION("linux_runner", "ConfigureNetworkRPC");
auto grpc_status = maitred_->ConfigureNetwork(&context, request, &response);
FXL_CHECK(grpc_status.ok())
<< "Failed to configure guest network: " << grpc_status.error_message();
}
FXL_LOG(INFO) << "Network configured.";
}
void Guest::StartTermina() {
TRACE_DURATION("linux_runner", "Guest::StartTermina");
FXL_CHECK(maitred_) << "Called StartTermina without a maitre'd connection";
FXL_LOG(INFO) << "Starting Termina...";
grpc::ClientContext context;
vm_tools::StartTerminaRequest request;
vm_tools::StartTerminaResponse response;
std::string lxd_subnet = "100.115.92.1/24";
request.mutable_lxd_ipv4_subnet()->swap(lxd_subnet);
{
TRACE_DURATION("linux_runner", "StartTerminaRPC");
auto grpc_status = maitred_->StartTermina(&context, request, &response);
FXL_CHECK(grpc_status.ok())
<< "Failed to start Termina: " << grpc_status.error_message();
}
}
// This exposes a shell on /dev/hvc0 that can be used to interact with the
// VM.
void Guest::LaunchContainerShell() {
FXL_CHECK(maitred_) << "Called LaunchShell without a maitre'd connection";
FXL_LOG(INFO) << "Launching container shell...";
grpc::ClientContext context;
vm_tools::LaunchProcessRequest request;
vm_tools::LaunchProcessResponse response;
request.add_argv()->assign("/usr/bin/lxc");
request.add_argv()->assign("exec");
request.add_argv()->assign(kContainerName);
request.add_argv()->assign("--");
request.add_argv()->assign("/bin/login");
request.add_argv()->assign("-f");
request.add_argv()->assign(kDefaultContainerUser);
request.set_respawn(true);
request.set_use_console(true);
request.set_wait_for_exit(false);
{
auto env = request.mutable_env();
env->insert({"LXD_DIR", "/mnt/stateful/lxd"});
env->insert({"LXD_CONF", "/mnt/stateful/lxd_conf"});
env->insert({"LXD_UNPRIVILEGED_ONLY", "true"});
}
{
TRACE_DURATION("linux_runner", "LaunchProcessRPC");
auto status = maitred_->LaunchProcess(&context, request, &response);
FXL_CHECK(status.ok()) << "Failed to launch container shell: "
<< status.error_message();
}
}
void Guest::CreateContainer() {
TRACE_DURATION("linux_runner", "Guest::CreateContainer");
FXL_CHECK(tremplin_)
<< "CreateContainer called without a Tremplin connection";
FXL_LOG(INFO) << "Creating Container...";
grpc::ClientContext context;
vm_tools::tremplin::CreateContainerRequest request;
vm_tools::tremplin::CreateContainerResponse response;
request.mutable_container_name()->assign(kContainerName);
request.mutable_image_alias()->assign(kContainerImageAlias);
request.mutable_image_server()->assign(kContainerImageServer);
{
TRACE_DURATION("linux_runner", "CreateContainerRPC");
auto status = tremplin_->CreateContainer(&context, request, &response);
FXL_CHECK(status.ok()) << "Failed to create container: "
<< status.error_message();
}
switch (response.status()) {
case vm_tools::tremplin::CreateContainerResponse::CREATING:
break;
case vm_tools::tremplin::CreateContainerResponse::EXISTS:
FXL_LOG(INFO) << "Container already exists";
StartContainer();
break;
case vm_tools::tremplin::CreateContainerResponse::FAILED:
FXL_LOG(ERROR) << "Failed to create container: "
<< response.failure_reason();
break;
case vm_tools::tremplin::CreateContainerResponse::UNKNOWN:
default:
FXL_LOG(ERROR) << "Unknown status: " << response.status();
break;
}
}
void Guest::StartContainer() {
TRACE_DURATION("linux_runner", "Guest::StartContainer");
FXL_CHECK(tremplin_) << "StartContainer called without a Tremplin connection";
FXL_LOG(INFO) << "Starting Container...";
grpc::ClientContext context;
vm_tools::tremplin::StartContainerRequest request;
vm_tools::tremplin::StartContainerResponse response;
request.mutable_container_name()->assign(kContainerName);
request.mutable_host_public_key()->assign("");
request.mutable_container_private_key()->assign("");
request.mutable_token()->assign("container_token");
{
TRACE_DURATION("linux_runner", "StartContainerRPC");
auto status = tremplin_->StartContainer(&context, request, &response);
FXL_CHECK(status.ok()) << "Failed to start container: "
<< status.error_message();
}
switch (response.status()) {
case vm_tools::tremplin::StartContainerResponse::RUNNING:
case vm_tools::tremplin::StartContainerResponse::STARTED:
FXL_LOG(INFO) << "Container started";
SetupUser();
break;
case vm_tools::tremplin::StartContainerResponse::FAILED:
FXL_LOG(ERROR) << "Failed to start container: "
<< response.failure_reason();
break;
case vm_tools::tremplin::StartContainerResponse::UNKNOWN:
default:
FXL_LOG(ERROR) << "Unknown status: " << response.status();
break;
}
}
void Guest::SetupUser() {
FXL_CHECK(tremplin_) << "SetupUser called without a Tremplin connection";
FXL_LOG(INFO) << "Creating user '" << kDefaultContainerUser << "'...";
grpc::ClientContext context;
vm_tools::tremplin::SetUpUserRequest request;
vm_tools::tremplin::SetUpUserResponse response;
request.mutable_container_name()->assign(kContainerName);
request.mutable_container_username()->assign(kDefaultContainerUser);
{
TRACE_DURATION("linux_runner", "SetUpUserRPC");
auto status = tremplin_->SetUpUser(&context, request, &response);
FXL_CHECK(status.ok()) << "Failed to setup user '" << kDefaultContainerUser
<< "': " << status.error_message();
}
switch (response.status()) {
case vm_tools::tremplin::SetUpUserResponse::EXISTS:
case vm_tools::tremplin::SetUpUserResponse::SUCCESS:
FXL_LOG(INFO) << "User created.";
LaunchContainerShell();
break;
case vm_tools::tremplin::SetUpUserResponse::FAILED:
FXL_LOG(ERROR) << "Failed to create user: " << response.failure_reason();
break;
case vm_tools::tremplin::SetUpUserResponse::UNKNOWN:
default:
FXL_LOG(ERROR) << "Unknown status: " << response.status();
break;
}
}
grpc::Status Guest::VmReady(grpc::ServerContext* context,
const vm_tools::EmptyMessage* request,
vm_tools::EmptyMessage* response) {
TRACE_DURATION("linux_runner", "Guest::VmReady");
TRACE_FLOW_END("linux_runner", "TerminaBoot", vm_ready_nonce_);
FXL_LOG(INFO) << "VM Ready -- Connecting to Maitre'd...";
std::unique_ptr<vm_tools::Maitred::Stub> maitred_;
auto p =
NewGrpcVsockStub<vm_tools::Maitred>(socket_endpoint_, guest_cid_, kMaitredPort)
.then([this](fit::result<std::unique_ptr<vm_tools::Maitred::Stub>,
zx_status_t>& result) mutable {
if (result.is_ok()) {
this->maitred_ = std::move(result.value());
MountExtrasPartition();
ConfigureNetwork();
StartTermina();
} else {
FXL_CHECK(false) << "Failed to connect to Maitre'd";
}
});
executor_.schedule_task(std::move(p));
return grpc::Status::OK;
}
grpc::Status Guest::TremplinReady(
grpc::ServerContext* context,
const ::vm_tools::tremplin::TremplinStartupInfo* request,
vm_tools::tremplin::EmptyMessage* response) {
TRACE_DURATION("linux_runner", "Guest::TremplinReady");
FXL_LOG(INFO) << "Tremplin Ready.";
auto p = NewGrpcVsockStub<vm_tools::tremplin::Tremplin>(socket_endpoint_, guest_cid_, kTremplinPort)
.then([this](fit::result<
std::unique_ptr<vm_tools::tremplin::Tremplin::Stub>,
zx_status_t>& result) mutable -> fit::result<> {
if (result.is_ok()) {
tremplin_ = std::move(result.value());
CreateContainer();
} else {
FXL_LOG(ERROR) << "Failed to connect to tremplin";
}
return fit::ok();
});
executor_.schedule_task(std::move(p));
return grpc::Status::OK;
}
grpc::Status Guest::UpdateCreateStatus(
grpc::ServerContext* context,
const vm_tools::tremplin::ContainerCreationProgress* request,
vm_tools::tremplin::EmptyMessage* response) {
TRACE_DURATION("linux_runner", "Guest::UpdateCreateStatus");
switch (request->status()) {
case vm_tools::tremplin::ContainerCreationProgress::CREATED:
FXL_LOG(INFO) << "Container created: " << request->container_name();
StartContainer();
break;
case vm_tools::tremplin::ContainerCreationProgress::DOWNLOADING:
FXL_LOG(INFO) << "Downloading " << request->container_name() << ": "
<< request->download_progress() << "%";
break;
case vm_tools::tremplin::ContainerCreationProgress::DOWNLOAD_TIMED_OUT:
FXL_LOG(INFO) << "Download timed out for " << request->container_name();
break;
case vm_tools::tremplin::ContainerCreationProgress::CANCELLED:
FXL_LOG(INFO) << "Download cancelled for " << request->container_name();
break;
case vm_tools::tremplin::ContainerCreationProgress::FAILED:
FXL_LOG(INFO) << "Download failed for " << request->container_name()
<< ": " << request->failure_reason();
break;
case vm_tools::tremplin::ContainerCreationProgress::UNKNOWN:
default:
FXL_LOG(INFO) << "Unknown download status: " << request->status();
break;
}
return grpc::Status::OK;
}
grpc::Status Guest::UpdateDeletionStatus(
::grpc::ServerContext* context,
const ::vm_tools::tremplin::ContainerDeletionProgress* request,
::vm_tools::tremplin::EmptyMessage* response) {
TRACE_DURATION("linux_runner", "Guest::UpdateDeletionStatus");
FXL_LOG(INFO) << "Update Deletion Status";
return grpc::Status::OK;
}
grpc::Status Guest::UpdateStartStatus(
::grpc::ServerContext* context,
const ::vm_tools::tremplin::ContainerStartProgress* request,
::vm_tools::tremplin::EmptyMessage* response) {
TRACE_DURATION("linux_runner", "Guest::UpdateStartStatus");
FXL_LOG(INFO) << "Update Start Status";
return grpc::Status::OK;
}
grpc::Status Guest::UpdateExportStatus(
::grpc::ServerContext* context,
const ::vm_tools::tremplin::ContainerExportProgress* request,
::vm_tools::tremplin::EmptyMessage* response) {
TRACE_DURATION("linux_runner", "Guest::UpdateExportStatus");
FXL_LOG(INFO) << "Update Export Status";
return grpc::Status::OK;
}
grpc::Status Guest::UpdateImportStatus(
::grpc::ServerContext* context,
const ::vm_tools::tremplin::ContainerImportProgress* request,
::vm_tools::tremplin::EmptyMessage* response) {
TRACE_DURATION("linux_runner", "Guest::UpdateImportStatus");
FXL_LOG(INFO) << "Update Import Status";
return grpc::Status::OK;
}
grpc::Status Guest::ContainerShutdown(
::grpc::ServerContext* context,
const ::vm_tools::tremplin::ContainerShutdownInfo* request,
::vm_tools::tremplin::EmptyMessage* response) {
TRACE_DURATION("linux_runner", "Guest::ContainerShutdown");
FXL_LOG(INFO) << "Container Shutdown";
return grpc::Status::OK;
}
grpc::Status Guest::ContainerReady(
grpc::ServerContext* context,
const vm_tools::container::ContainerStartupInfo* request,
vm_tools::EmptyMessage* response) {
TRACE_DURATION("linux_runner", "Guest::ContainerReady");
// TODO(tjdetwiler): validate token.
auto garcon_port = request->garcon_port();
FXL_LOG(INFO) << "Container Ready; Garcon listening on port " << garcon_port;
auto p = NewGrpcVsockStub<vm_tools::container::Garcon>(socket_endpoint_, guest_cid_, garcon_port)
.then([this](fit::result<
std::unique_ptr<vm_tools::container::Garcon::Stub>,
zx_status_t>& result) mutable -> fit::result<> {
if (result.is_ok()) {
garcon_ = std::move(result.value());
DumpContainerDebugInfo();
for (auto it = pending_requests_.begin();
it != pending_requests_.end();
it = pending_requests_.erase(it)) {
LaunchApplication(std::move(*it));
}
} else {
FXL_LOG(ERROR) << "Failed to connect to garcon";
}
return fit::ok();
});
executor_.schedule_task(std::move(p));
return grpc::Status::OK;
}
grpc::Status Guest::ContainerShutdown(
grpc::ServerContext* context,
const vm_tools::container::ContainerShutdownInfo* request,
vm_tools::EmptyMessage* response) {
FXL_LOG(INFO) << "Container Shutdown";
return grpc::Status::OK;
}
grpc::Status Guest::UpdateApplicationList(
grpc::ServerContext* context,
const vm_tools::container::UpdateApplicationListRequest* request,
vm_tools::EmptyMessage* response) {
TRACE_DURATION("linux_runner", "Guest::UpdateApplicationList");
FXL_LOG(INFO) << "Update Application List";
for (const auto& application : request->application()) {
FXL_LOG(INFO) << "ID: " << application.desktop_file_id();
const auto& name = application.name().values().begin();
if (name != application.name().values().end()) {
FXL_LOG(INFO) << "\tname: " << name->value();
}
const auto& comment = application.comment().values().begin();
if (comment != application.comment().values().end()) {
FXL_LOG(INFO) << "\tcomment: " << comment->value();
}
FXL_LOG(INFO) << "\tno_display: " << application.no_display();
FXL_LOG(INFO) << "\tstartup_wm_class: " << application.startup_wm_class();
FXL_LOG(INFO) << "\tstartup_notify: " << application.startup_notify();
FXL_LOG(INFO) << "\tpackage_id: " << application.package_id();
}
return grpc::Status::OK;
}
grpc::Status Guest::OpenUrl(grpc::ServerContext* context,
const vm_tools::container::OpenUrlRequest* request,
vm_tools::EmptyMessage* response) {
TRACE_DURATION("linux_runner", "Guest::OpenUrl");
FXL_LOG(INFO) << "Open URL";
return grpc::Status::OK;
}
grpc::Status Guest::InstallLinuxPackageProgress(
grpc::ServerContext* context,
const vm_tools::container::InstallLinuxPackageProgressInfo* request,
vm_tools::EmptyMessage* response) {
TRACE_DURATION("linux_runner", "Guest::InstallLinuxPackageProgress");
FXL_LOG(INFO) << "Install Linux Package Progress";
return grpc::Status::OK;
}
grpc::Status Guest::UninstallPackageProgress(
grpc::ServerContext* context,
const vm_tools::container::UninstallPackageProgressInfo* request,
vm_tools::EmptyMessage* response) {
TRACE_DURATION("linux_runner", "Guest::UninstallPackageProgress");
FXL_LOG(INFO) << "Uninstall Package Progress";
return grpc::Status::OK;
}
grpc::Status Guest::OpenTerminal(
grpc::ServerContext* context,
const vm_tools::container::OpenTerminalRequest* request,
vm_tools::EmptyMessage* response) {
TRACE_DURATION("linux_runner", "Guest::OpenTerminal");
FXL_LOG(INFO) << "Open Terminal";
return grpc::Status::OK;
}
grpc::Status Guest::UpdateMimeTypes(
grpc::ServerContext* context,
const vm_tools::container::UpdateMimeTypesRequest* request,
vm_tools::EmptyMessage* response) {
TRACE_DURATION("linux_runner", "Guest::UpdateMimeTypes");
FXL_LOG(INFO) << "Update Mime Types";
size_t i = 0;
for (const auto& pair : request->mime_type_mappings()) {
FXL_LOG(INFO) << "\t" << pair.first << ": " << pair.second;
if (++i > 10) {
FXL_LOG(INFO) << "\t..." << (request->mime_type_mappings_size() - i)
<< " more.";
break;
}
}
return grpc::Status::OK;
}
void Guest::DumpContainerDebugInfo() {
FXL_CHECK(garcon_)
<< "Called DumpContainerDebugInfo without a garcon connection";
FXL_LOG(INFO) << "Dumping Container Debug Info...";
grpc::ClientContext context;
vm_tools::container::GetDebugInformationRequest request;
vm_tools::container::GetDebugInformationResponse response;
auto grpc_status = garcon_->GetDebugInformation(&context, request, &response);
if (!grpc_status.ok()) {
FXL_LOG(ERROR) << "Failed to read container debug information: "
<< grpc_status.error_message();
return;
}
FXL_LOG(INFO) << "Container debug information:";
FXL_LOG(INFO) << response.debug_information();
}
void Guest::Launch(AppLaunchRequest request) {
TRACE_DURATION("linux_runner", "Guest::Launch");
// If we have a garcon connection we can request the launch immediately.
// Otherwise we just retain the request and forward it along once the
// container is started.
if (garcon_) {
LaunchApplication(std::move(request));
return;
}
pending_requests_.push_back(std::move(request));
}
void Guest::LaunchApplication(AppLaunchRequest app) {
TRACE_DURATION("linux_runner", "Guest::LaunchApplication");
FXL_CHECK(garcon_) << "Called LaunchApplication without a garcon connection";
std::string desktop_file_id = app.application.resolved_url;
if (desktop_file_id.rfind(kLinuxUriScheme, 0) == std::string::npos) {
FXL_LOG(ERROR) << "Invalid URI: " << desktop_file_id;
return;
}
desktop_file_id.erase(0, strlen(kLinuxUriScheme));
if (desktop_file_id == "") {
// HACK: we use the empty URI to pick up a view that wasn't associated
// with an app launch request. For example, if you started a GUI
// application from the serial console, a wayland view will have been
// created without a fuchsia component to associate with it.
//
// We'll need to come up with a more proper solution, but this allows us to
// at least do some testing of these views for the time being.
auto it = background_views_.begin();
if (it == background_views_.end()) {
FXL_LOG(INFO) << "No background views available";
return;
}
CreateComponent(std::move(app), it->Bind());
background_views_.erase(it);
return;
}
FXL_LOG(INFO) << "Launching: " << desktop_file_id;
grpc::ClientContext context;
vm_tools::container::LaunchApplicationRequest request;
vm_tools::container::LaunchApplicationResponse response;
request.set_desktop_file_id(std::move(desktop_file_id));
{
TRACE_DURATION("linux_runner", "LaunchApplicationRPC");
auto grpc_status = garcon_->LaunchApplication(&context, request, &response);
if (!grpc_status.ok() || !response.success()) {
FXL_LOG(ERROR) << "Failed to launch application: "
<< grpc_status.error_message() << ", "
<< response.failure_reason();
return;
}
}
FXL_LOG(INFO) << "Application launched successfully";
pending_views_.push_back(std::move(app));
}
void Guest::OnNewView(
fidl::InterfaceHandle<fuchsia::ui::app::ViewProvider> view_provider) {
TRACE_DURATION("linux_runner", "Guest::OnNewView");
// TODO: This currently just pops a component request off the queue to
// associate with the new view. This is obviously racy but will work until
// we can pipe though a startup id to provide a more accurate correlation.
auto it = pending_views_.begin();
if (it == pending_views_.end()) {
background_views_.push_back(std::move(view_provider));
return;
}
CreateComponent(std::move(*it), std::move(view_provider));
pending_views_.erase(it);
}
void Guest::CreateComponent(
AppLaunchRequest request,
fidl::InterfaceHandle<fuchsia::ui::app::ViewProvider> view_provider) {
TRACE_DURATION("linux_runner", "Guest::CreateComponent");
auto component = LinuxComponent::Create(
fit::bind_member(this, &Guest::OnComponentTerminated),
std::move(request.application), std::move(request.startup_info),
std::move(request.controller_request), view_provider.Bind());
components_.insert({component.get(), std::move(component)});
}
void Guest::OnComponentTerminated(const LinuxComponent* component) {
components_.erase(component);
}
} // namespace linux_runner