blob: e1548df0832487b8d6f20e21b2de2b07f87b6bd4 [file] [log] [blame]
// Copyright 2020 The Chromium OS 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 <sys/socket.h>
#include <sys/types.h>
#include <string>
#include <base/files/file_util.h>
#include <base/guid.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/shared_data.h"
#include "vm_tools/concierge/vmplugin_dispatcher_interface.h"
namespace vm_tools {
namespace concierge {
namespace {
bool GetPluginStatefulDirectory(const std::string& vm_id,
const std::string& cryptohome_id,
bool create,
base::FilePath* path_out) {
return GetPluginDirectory(base::FilePath(kCryptohomeRoot)
.Append(kPluginVmDir)
.Append(cryptohome_id),
"pvm", vm_id, 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
std::unique_ptr<dbus::Response> Service::StartPluginVm(
dbus::MethodCall* method_call) {
LOG(INFO) << "Received StartPluginVm request";
std::unique_ptr<dbus::Response> dbus_response(
dbus::Response::FromMethodCall(method_call));
dbus::MessageReader reader(method_call);
dbus::MessageWriter writer(dbus_response.get());
StartPluginVmRequest request;
StartVmResponse response;
auto helper_result = StartVmHelper<StartPluginVmRequest>(
method_call, &reader, &writer, true /* allow_zero_cpus */);
if (!helper_result) {
return dbus_response;
}
std::tie(request, response) = *helper_result;
VmInfo* vm_info = response.mutable_vm_info();
vm_info->set_vm_type(VmInfo::PLUGIN_VM);
// Get the stateful directory.
base::FilePath stateful_dir;
if (!GetPluginStatefulDirectory(request.name(), request.owner_id(),
true /* create */, &stateful_dir)) {
LOG(ERROR) << "Unable to create stateful directory for VM";
response.set_failure_reason("Unable to create stateful directory");
writer.AppendProtoAsArrayOfBytes(response);
return dbus_response;
}
// Get the directory for ISO images.
base::FilePath iso_dir;
if (!GetPluginIsoDirectory(request.name(), request.owner_id(),
true /* create */, &iso_dir)) {
LOG(ERROR) << "Unable to create directory holding ISOs for VM";
response.set_failure_reason("Unable to create ISO directory");
writer.AppendProtoAsArrayOfBytes(response);
return dbus_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");
writer.AppendProtoAsArrayOfBytes(response);
return dbus_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");
writer.AppendProtoAsArrayOfBytes(response);
return dbus_response;
}
if (!CreatePluginRootHierarchy(root_dir.GetPath())) {
response.set_failure_reason("Unable to create plugin root hierarchy");
writer.AppendProtoAsArrayOfBytes(response);
return dbus_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");
writer.AppendProtoAsArrayOfBytes(response);
return dbus_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::GenerateGUID();
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");
writer.AppendProtoAsArrayOfBytes(response);
return dbus_response;
}
base::FilePath p9_socket_path;
if (!GetPlugin9PSocketPath(request.name(), &p9_socket_path)) {
response.set_failure_reason("Internal error: unable to get 9P directory");
writer.AppendProtoAsArrayOfBytes(response);
return dbus_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");
writer.AppendProtoAsArrayOfBytes(response);
return dbus_response;
}
std::unique_ptr<patchpanel::Client> network_client =
patchpanel::Client::New(bus_);
if (!network_client) {
LOG(ERROR) << "Unable to open networking service client";
response.set_failure_reason("Unable to open network service client");
writer.AppendProtoAsArrayOfBytes(response);
return dbus_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");
writer.AppendProtoAsArrayOfBytes(response);
return dbus_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.
VmId vm_id(request.owner_id(), request.name());
SendVmStartingUpSignal(vm_id, *vm_info);
VmBuilder vm_builder;
vm_builder.SetCpus(request.cpus());
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(
vm_id, std::move(stateful_dir), std::move(iso_dir), root_dir.Take(),
runtime_dir.Take(), std::move(network_client), request.subnet_index(),
request.net_options().enable_vnet_hdr(), bus_,
std::move(seneschal_server_proxy), vm_permission_service_proxy_,
vmplugin_service_proxy_, std::move(vm_builder));
if (!vm) {
LOG(ERROR) << "Unable to start VM";
response.set_failure_reason("Unable to start VM");
writer.AppendProtoAsArrayOfBytes(response);
return dbus_response;
}
VmInterface::Info info = vm->GetInfo();
vm_info->set_ipv4_address(info.ipv4_address);
vm_info->set_pid(info.pid);
vm_info->set_cid(info.cid);
vm_info->set_seneschal_server_handle(info.seneschal_server_handle);
vm_info->set_permission_token(info.permission_token);
switch (info.status) {
case VmInterface::Status::STARTING: {
response.set_status(VM_STATUS_STARTING);
break;
}
case VmInterface::Status::RUNNING: {
response.set_status(VM_STATUS_RUNNING);
break;
}
default: {
response.set_status(VM_STATUS_UNKNOWN);
break;
}
}
response.set_success(true);
writer.AppendProtoAsArrayOfBytes(response);
NotifyCiceroneOfVmStarted(vm_id, 0 /* cid */, info.pid, std::move(vm_token));
SendVmStartedSignal(vm_id, *vm_info, response.status());
vms_[vm_id] = std::move(vm);
return dbus_response;
}
bool Service::RenamePluginVm(const std::string& owner_id,
const std::string& old_name,
const std::string& new_name,
std::string* failure_reason) {
base::FilePath old_dir;
if (!GetPluginStatefulDirectory(old_name, owner_id, false /* create */,
&old_dir)) {
*failure_reason = "unable to determine current VM directory";
return false;
}
base::FilePath old_iso_dir;
if (!GetPluginIsoDirectory(old_name, owner_id, false /* create */,
&old_iso_dir)) {
*failure_reason = "unable to determine current VM ISO directory";
return false;
}
base::FilePath new_dir;
if (!GetPluginStatefulDirectory(new_name, owner_id, false /* create */,
&new_dir)) {
*failure_reason = "unable to determine new VM directory";
return false;
}
base::FilePath new_iso_dir;
if (!GetPluginIsoDirectory(new_name, owner_id, false /* create */,
&new_iso_dir)) {
*failure_reason = "unable to determine new VM ISO directory";
return false;
}
VmId old_id(owner_id, old_name);
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_,
VmId(owner_id, new_name), new_dir)) {
*failure_reason = "Failed to re-register renamed VM";
return false;
}
return true;
}
} // namespace concierge
} // namespace vm_tools