blob: 771beceafa5dcbe928d8e85dd0b0987d29c7d9c5 [file] [log] [blame] [edit]
// Copyright 2020 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <sys/socket.h>
#include <sys/types.h>
#include <memory>
#include <string>
#include <base/check.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/uuid.h>
#include "vm_tools/concierge/metrics/duration_recorder.h"
#include "vm_tools/concierge/network/plugin_vm_network.h"
#include "vm_tools/concierge/plugin_vm.h"
#include "vm_tools/concierge/plugin_vm_helper.h"
#include "vm_tools/concierge/service.h"
#include "vm_tools/concierge/service_common.h"
#include "vm_tools/concierge/service_start_vm_helper.h"
#include "vm_tools/concierge/vm_base_impl.h"
#include "vm_tools/concierge/vmplugin_dispatcher_interface.h"
namespace vm_tools::concierge {
namespace {
bool GetPluginStatefulDirectory(const VmId& vm_id,
bool create,
base::FilePath* path_out) {
return GetPluginDirectory(base::FilePath(kCryptohomeRoot)
.Append(kPluginVmDir)
.Append(vm_id.owner_id()),
"pvm", vm_id.name(), create, path_out);
}
bool GetPluginRuntimeDirectory(const std::string& vm_id,
base::ScopedTempDir* runtime_dir_out) {
base::FilePath path;
if (GetPluginDirectory(base::FilePath("/run/pvm"), "", vm_id,
true /*create */, &path)) {
// Take ownership of directory
CHECK(runtime_dir_out->Set(path));
return true;
}
return false;
}
bool GetPluginRootDirectory(const std::string& vm_id,
base::ScopedTempDir* root_dir_out) {
base::FilePath path;
if (!base::CreateTemporaryDirInDir(base::FilePath(kRuntimeDir), "vm.",
&path)) {
PLOG(ERROR) << "Unable to create root directory for VM";
return false;
}
// Take ownership of directory
CHECK(root_dir_out->Set(path));
return true;
}
bool CreatePluginRootHierarchy(const base::FilePath& root_path) {
base::FilePath etc_dir(root_path.Append("etc"));
base::File::Error dir_error;
if (!CreateDirectoryAndGetError(etc_dir, &dir_error)) {
LOG(ERROR) << "Unable to create /etc in root directory for VM "
<< base::File::ErrorToString(dir_error);
return false;
}
// Note that this will be dangling (or rather point to concierge's timezone
// instance) until crosvm bind mounts /var/lib/timezone and
// /usr/share/zoneinfo into plugin jail.
if (!base::CreateSymbolicLink(base::FilePath("/var/lib/timezone/localtime"),
etc_dir.Append("localtime"))) {
PLOG(ERROR) << "Unable to create /etc/localtime symlink";
return false;
}
return true;
}
bool GetPlugin9PSocketPath(const std::string& vm_id, base::FilePath* path_out) {
base::FilePath runtime_dir;
if (!GetPluginDirectory(base::FilePath("/run/pvm"), "", vm_id,
true /* create */, &runtime_dir)) {
LOG(ERROR) << "Unable to get runtime directory for 9P socket";
return false;
}
*path_out = runtime_dir.Append("9p.sock");
return true;
}
} // namespace
void Service::StartPluginVm(
std::unique_ptr<brillo::dbus_utils::DBusMethodResponse<
vm_tools::concierge::StartVmResponse>> response_cb,
const vm_tools::concierge::StartPluginVmRequest& request) {
ASYNC_SERVICE_METHOD();
StartVmResponse response;
// We change to a success status later if necessary.
response.set_status(VM_STATUS_FAILURE);
if (!CheckStartVmPreconditions(request, &response)) {
response_cb->Return(response);
return;
}
StartPluginVmInternal(request, response);
response_cb->Return(response);
}
StartVmResponse Service::StartPluginVmInternal(StartPluginVmRequest request,
StartVmResponse& response) {
LOG(INFO) << "Received StartPluginVm request";
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
VmId vm_id(request.owner_id(), request.name());
// Log how long it takes to start the VM.
metrics::DurationRecorder duration_recorder(
raw_ref<MetricsLibraryInterface>::from_ptr(metrics_.get()),
apps::VmType::PLUGIN_VM, metrics::DurationRecorder::Event::kVmStart);
// Get the stateful directory.
base::FilePath stateful_dir;
if (!GetPluginStatefulDirectory(vm_id, true /* create */, &stateful_dir)) {
LOG(ERROR) << "Unable to create stateful directory for VM";
response.set_failure_reason("Unable to create stateful directory");
return response;
}
// Get the directory for ISO images.
base::FilePath iso_dir;
if (!GetPluginIsoDirectory(vm_id, true /* create */, &iso_dir)) {
LOG(ERROR) << "Unable to create directory holding ISOs for VM";
response.set_failure_reason("Unable to create ISO directory");
return response;
}
// Create the runtime directory.
base::ScopedTempDir runtime_dir;
if (!GetPluginRuntimeDirectory(request.name(), &runtime_dir)) {
LOG(ERROR) << "Unable to create runtime directory for VM";
response.set_failure_reason("Unable to create runtime directory");
return response;
}
// Create the root directory.
base::ScopedTempDir root_dir;
if (!GetPluginRootDirectory(request.name(), &root_dir)) {
LOG(ERROR) << "Unable to create runtime directory for VM";
response.set_failure_reason("Unable to create runtime directory");
return response;
}
if (!CreatePluginRootHierarchy(root_dir.GetPath())) {
response.set_failure_reason("Unable to create plugin root hierarchy");
return response;
}
if (!PluginVm::WriteResolvConf(root_dir.GetPath().Append("etc"), nameservers_,
search_domains_)) {
LOG(ERROR) << "Unable to seed resolv.conf for the Plugin VM";
response.set_failure_reason("Unable to seed resolv.conf");
return response;
}
// Generate the token used by cicerone to identify the VM and write it to
// a VM specific directory that gets mounted into the VM.
std::string vm_token = base::Uuid::GenerateRandomV4().AsLowercaseString();
if (base::WriteFile(runtime_dir.GetPath().Append("cicerone.token"),
vm_token.c_str(),
vm_token.length()) != vm_token.length()) {
PLOG(ERROR) << "Failure writing out cicerone token to file";
response.set_failure_reason("Unable to set cicerone token");
return response;
}
base::FilePath p9_socket_path;
if (!GetPlugin9PSocketPath(request.name(), &p9_socket_path)) {
response.set_failure_reason("Internal error: unable to get 9P directory");
return response;
}
base::ScopedFD p9_socket =
PluginVm::CreateUnixSocket(p9_socket_path, SOCK_STREAM);
if (!p9_socket.is_valid()) {
LOG(ERROR) << "Failed creating 9P socket for file sharing";
response.set_failure_reason("Internal error: unable to create 9P socket");
return response;
}
std::unique_ptr<PluginVmNetwork> network = PluginVmNetwork::Create(
bus_, vm_id, static_cast<int>(request.subnet_index()));
if (!network) {
LOG(ERROR) << "Failed to allocate network resources for pluginvm";
response.set_failure_reason(
"Failed to allocate network resources for pluginvm");
return response;
}
std::unique_ptr<SeneschalServerProxy> seneschal_server_proxy =
SeneschalServerProxy::CreateFdProxy(bus_, seneschal_service_proxy_,
p9_socket);
if (!seneschal_server_proxy) {
LOG(ERROR) << "Unable to start shared directory server";
response.set_failure_reason("Unable to start shared directory server");
return response;
}
// Build the plugin params.
std::vector<std::string> params(
std::make_move_iterator(request.mutable_params()->begin()),
std::make_move_iterator(request.mutable_params()->end()));
// Now start the VM.
SendVmStartingUpSignal(vm_id, apps::VmType::PLUGIN_VM, 0 /* cid */);
VmBuilder vm_builder;
vm_builder.SetCpus(request.cpus());
vm_builder.AppendCustomParam("--vcpu-cgroup-path",
base::FilePath(kPluginVmVcpuCpuCgroup).value());
for (auto& param : params) {
// Because additional parameters may start with a '--', we should use
// --params=<Param> instead of --params <Param> to make explicit <Param>
// is a parameter for the plugin rather than just another parameter to
// the crosvm process.
vm_builder.AppendCustomParam(std::string("--params=") + param, "");
}
auto vm = PluginVm::Create(PluginVm::Config{
.id = vm_id,
.stateful_dir = std::move(stateful_dir),
.iso_dir = std::move(iso_dir),
.root_dir = root_dir.Take(),
.runtime_dir = runtime_dir.Take(),
.enable_vnet_hdr = request.net_options().enable_vnet_hdr(),
.bus = bus_,
.network = std::move(network),
.seneschal_server_proxy = std::move(seneschal_server_proxy),
.vm_permission_service_proxy = vm_permission_service_proxy_,
.vmplugin_service_proxy = vmplugin_service_proxy_,
.vm_builder = std::move(vm_builder)});
if (!vm) {
LOG(ERROR) << "Unable to start VM";
response.set_failure_reason("Unable to start VM");
return response;
}
NotifyCiceroneOfVmStarted(vm_id, 0 /* cid */, vm->pid(), std::move(vm_token),
apps::VmType::PLUGIN_VM);
vms_[vm_id] = std::move(vm);
HandleControlSocketReady(vm_id);
response.set_success(true);
response.set_status(VM_STATUS_RUNNING);
*response.mutable_vm_info() = ToVmInfo(vms_[vm_id]->GetInfo(), true);
return response;
}
bool Service::RenamePluginVm(const VmId& old_id,
const VmId& new_id,
std::string* failure_reason) {
base::FilePath old_dir;
if (!GetPluginStatefulDirectory(old_id, false /* create */, &old_dir)) {
*failure_reason = "unable to determine current VM directory";
return false;
}
base::FilePath old_iso_dir;
if (!GetPluginIsoDirectory(old_id, false /* create */, &old_iso_dir)) {
*failure_reason = "unable to determine current VM ISO directory";
return false;
}
base::FilePath new_dir;
if (!GetPluginStatefulDirectory(new_id, false /* create */, &new_dir)) {
*failure_reason = "unable to determine new VM directory";
return false;
}
base::FilePath new_iso_dir;
if (!GetPluginIsoDirectory(new_id, false /* create */, &new_iso_dir)) {
*failure_reason = "unable to determine new VM ISO directory";
return false;
}
bool registered;
if (!pvm::dispatcher::IsVmRegistered(bus_, vmplugin_service_proxy_, old_id,
&registered)) {
*failure_reason = "failed to check Plugin VM registration status";
return false;
}
// This is unexpected: the VM is not registered. Better leave it alone.
if (!registered) {
*failure_reason = "the VM is not registered";
return false;
}
bool is_shut_down;
if (!pvm::dispatcher::IsVmShutDown(bus_, vmplugin_service_proxy_, old_id,
&is_shut_down)) {
*failure_reason = "failed to check Plugin VM state";
return false;
}
if (!is_shut_down) {
*failure_reason = "VM is not shut down";
return false;
}
base::File::Error move_error;
if (base::PathExists(old_iso_dir) &&
!base::ReplaceFile(old_iso_dir, new_iso_dir, &move_error)) {
*failure_reason = std::string("failed to rename VM ISO directory: ") +
base::File::ErrorToString(move_error);
return false;
}
if (!pvm::dispatcher::UnregisterVm(bus_, vmplugin_service_proxy_, old_id)) {
*failure_reason = "failed to temporarily unregister VM";
return false;
}
if (!base::ReplaceFile(old_dir, new_dir, &move_error)) {
*failure_reason = std::string("failed to rename VM directory: ") +
base::File::ErrorToString(move_error);
return false;
}
if (!pvm::dispatcher::RegisterVm(bus_, vmplugin_service_proxy_, new_id,
new_dir)) {
*failure_reason = "Failed to re-register renamed VM";
return false;
}
return true;
}
} // namespace vm_tools::concierge