blob: 8bfce9c11f6c40ce3cd4cd1af4acd5525ca4c3b0 [file]
// 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/bin/termina_guest_manager/termina_guest_manager.h"
#include <lib/async/cpp/task.h>
#include <lib/syslog/cpp/macros.h>
#include <memory>
#include "src/lib/fxl/strings/string_printf.h"
#include "src/virtualization/bin/termina_guest_manager/block_devices.h"
namespace termina_guest_manager {
namespace {
using ::fuchsia::virtualization::GuestConfig;
using ::fuchsia::virtualization::GuestManagerError;
constexpr std::string_view kLinuxEnvironmentName("termina");
constexpr size_t kBytesToWipe = 1ul * 1024 * 1024; // 1 MiB
void NotifyClient(fidl::Binding<fuchsia::virtualization::LinuxManager>& binding,
GuestInfo& current_info) {
fuchsia::virtualization::LinuxGuestInfo info;
info.set_cid(current_info.cid);
info.set_container_status(current_info.container_status);
info.set_download_percent(current_info.download_percent);
info.set_failure_reason(current_info.failure_reason);
binding.events().OnGuestInfoChanged(std::string(kLinuxEnvironmentName), std::move(info));
}
std::string GuestManagerErrorToString(fuchsia::virtualization::GuestManagerError error) {
switch (error) {
case fuchsia::virtualization::GuestManagerError::BAD_CONFIG:
return "BAD_CONFIG";
case fuchsia::virtualization::GuestManagerError::ALREADY_RUNNING:
return "ALREADY_RUNNING";
case fuchsia::virtualization::GuestManagerError::NOT_RUNNING:
return "NOT_RUNNING";
case fuchsia::virtualization::GuestManagerError::START_FAILURE:
return "START_FAILURE";
case fuchsia::virtualization::GuestManagerError::NO_STORAGE:
return "NO_STORAGE";
default:
return fxl::StringPrintf("GuestManagerError(%u)", static_cast<int32_t>(error));
}
}
} // namespace
TerminaGuestManager::TerminaGuestManager(async_dispatcher_t* dispatcher,
fit::function<void()> stop_manager_callback)
: TerminaGuestManager(dispatcher, sys::ComponentContext::CreateAndServeOutgoingDirectory(),
termina_config::Config::TakeFromStartupHandle(),
std::move(stop_manager_callback)) {}
TerminaGuestManager::TerminaGuestManager(async_dispatcher_t* dispatcher,
std::unique_ptr<sys::ComponentContext> context,
termina_config::Config structured_config,
fit::function<void()> stop_manager_callback)
: GuestManager(dispatcher, context.get()),
context_(std::move(context)),
structured_config_(std::move(structured_config)),
stop_manager_callback_(std::move(stop_manager_callback)),
dispatcher_(dispatcher) {
guest_ = CreateGuest();
context_->outgoing()->AddPublicService<fuchsia::virtualization::LinuxManager>(
[this](auto request) {
manager_bindings_.AddBinding(this, std::move(request));
// If we have an initial status; notify the new connection now.
if (info_.has_value()) {
NotifyClient(*manager_bindings_.bindings().back(), *info_);
}
});
}
std::unique_ptr<Guest> TerminaGuestManager::CreateGuest() {
return std::make_unique<Guest>(
structured_config_,
[this](GuestInfo guest_info) {
// OnGuestInfoChanged must be called on the FIDL thread from which it originates since
// StartAndGetLinuxGuestInfo responses must be sent on the same thread as they were
// dispatched, while Guest can use this callback from other (e.g. grpc) threads.
async::PostTask(dispatcher_, [this, guest_info = std::move(guest_info)]() {
OnGuestInfoChanged(std::move(guest_info));
});
},
[this] { return QueryGuestNetworkState(); });
}
fit::result<GuestManagerError, GuestConfig> TerminaGuestManager::GetDefaultGuestConfig() {
TRACE_DURATION("termina_guest_manager", "TerminaGuestManager::GetDefaultGuestConfig");
auto base_config = GuestManager::GetDefaultGuestConfig();
if (base_config.is_error()) {
return base_config.take_error();
}
auto block_devices_result = GetBlockDevices(structured_config_);
if (block_devices_result.is_error()) {
FX_LOGS(ERROR) << "Failed to option block devices: " << block_devices_result.error_value();
return fit::error(GuestManagerError::NO_STORAGE);
}
// Drop /dev from our local namespace. We no longer need this capability so we go ahead and
// release it.
DropDevNamespace();
fuchsia::virtualization::GuestConfig termina_config;
termina_config.set_virtio_gpu(false);
termina_config.set_block_devices(std::move(block_devices_result.value()));
termina_config.set_magma_device(fuchsia::virtualization::MagmaDevice());
// Include a wayland device.
fuchsia::wayland::ServerPtr server_proxy;
termina_config.mutable_wayland_device();
// Add the vsock listeners for gRPC services.
*termina_config.mutable_vsock_listeners() = guest_->take_vsock_listeners();
return fit::ok(guest_config::MergeConfigs(std::move(*base_config), std::move(termina_config)));
}
void TerminaGuestManager::StartGuest() {
fuchsia::virtualization::GuestConfig cfg;
Launch(std::move(cfg), guest_controller_.NewRequest(), [this](auto res) {
if (res.is_err()) {
FX_LOGS(INFO) << "Termina Guest failed to launch: " << GuestManagerErrorToString(res.err());
OnGuestInfoChanged(GuestInfo{
.cid = fuchsia::virtualization::DEFAULT_GUEST_CID,
.container_status = fuchsia::virtualization::ContainerStatus::FAILED,
.failure_reason = fxl::StringPrintf("Failed to launch VM: %s",
GuestManagerErrorToString(res.err()).c_str()),
});
}
});
}
void TerminaGuestManager::OnGuestLaunched() {
if (!guest_controller_) {
Connect(guest_controller_.NewRequest(), [](auto res) {
// This should only fail if the guest isn't started, which should not be possible here.
FX_CHECK(res.is_response());
});
}
guest_->OnGuestLaunched(*this, *guest_controller_.get());
}
void TerminaGuestManager::OnGuestStopped() {
info_ = std::nullopt;
guest_ = CreateGuest();
if (structured_config_.stateful_partition_type() == "fvm") {
// The termina guest manager is dropping access to /dev preventing further accesses, so we
// can't restart the guest without restarting the guest manager component unless using fxfs.
// Eventually we will only be using fxfs, and this check can go away.
stop_manager_callback_();
}
}
void TerminaGuestManager::StartAndGetLinuxGuestInfo(std::string label,
StartAndGetLinuxGuestInfoCallback callback) {
TRACE_DURATION("termina_guest_manager", "TerminaGuestManager::StartAndGetLinuxGuestInfo");
// Linux runner is currently limited to a single environment name.
if (label != kLinuxEnvironmentName) {
FX_LOGS(ERROR) << "Invalid Linux environment: " << label;
callback(fpromise::error(ZX_ERR_UNAVAILABLE));
return;
}
if (!is_guest_started()) {
StartGuest();
} else if (info_ && info_->container_status == fuchsia::virtualization::ContainerStatus::FAILED) {
info_ = std::nullopt;
guest_->RetryContainerStartup();
}
if (info_.has_value()) {
fuchsia::virtualization::LinuxGuestInfo info;
info.set_cid(info_->cid);
info.set_container_status(info_->container_status);
info.set_download_percent(info_->download_percent);
info.set_failure_reason(info_->failure_reason);
fuchsia::virtualization::LinuxManager_StartAndGetLinuxGuestInfo_Response response;
response.info = std::move(info);
callback(fuchsia::virtualization::LinuxManager_StartAndGetLinuxGuestInfo_Result::WithResponse(
std::move(response)));
} else {
callbacks_.push_back(std::move(callback));
}
}
void TerminaGuestManager::WipeData(WipeDataCallback callback) {
if (is_guest_started()) {
callback(fuchsia::virtualization::LinuxManager_WipeData_Result::WithErr(ZX_ERR_BAD_STATE));
return;
}
// We zero out some bytes at the beginning of the partition to corrupt any filesystem data-
// structures stored there.
zx::result<> status = WipeStatefulPartition(kBytesToWipe);
if (status.is_error()) {
callback(fuchsia::virtualization::LinuxManager_WipeData_Result::WithErr(status.status_value()));
} else {
callback(fuchsia::virtualization::LinuxManager_WipeData_Result::WithResponse(
fuchsia::virtualization::LinuxManager_WipeData_Response()));
}
}
void TerminaGuestManager::GracefulShutdown() {
if (is_guest_started()) {
guest_->InitiateGuestShutdown();
}
}
void TerminaGuestManager::OnGuestInfoChanged(GuestInfo info) {
info_ = info;
while (!callbacks_.empty()) {
fuchsia::virtualization::LinuxGuestInfo info;
info.set_cid(info_->cid);
info.set_container_status(fuchsia::virtualization::ContainerStatus::TRANSIENT);
fuchsia::virtualization::LinuxManager_StartAndGetLinuxGuestInfo_Response response;
response.info = std::move(info);
callbacks_.front()(
fuchsia::virtualization::LinuxManager_StartAndGetLinuxGuestInfo_Result::WithResponse(
std::move(response)));
callbacks_.pop_front();
}
for (auto& binding : manager_bindings_.bindings()) {
NotifyClient(*binding, *info_);
}
}
} // namespace termina_guest_manager