blob: b365e70dba4d67c31a4dd081c7e2d5f5927191bb [file] [log] [blame]
// Copyright 2018 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 "vm_tools/concierge/plugin_vm.h"
#include <signal.h>
#include <utility>
#include <vector>
#include <base/files/file_util.h>
#include <base/files/scoped_file.h>
#include <base/logging.h>
#include <base/memory/ptr_util.h>
#include <base/strings/stringprintf.h>
#include <base/time/time.h>
#include "vm_tools/concierge/tap_device_builder.h"
using std::string;
namespace vm_tools {
namespace concierge {
namespace {
// Path to the crosvm binary.
constexpr char kCrosvmBin[] = "/usr/bin/crosvm";
// Path to the plugin binaries and other assets.
constexpr char kPluginBinDir[] = "/opt/pita/";
// Name of the plugin VM binary.
constexpr char kPluginBinName[] = "pvm";
// Name of the control socket used for controlling crosvm.
constexpr char kCrosvmSocket[] = "crosvm.sock";
// How long to wait before timing out on child process exits.
constexpr base::TimeDelta kChildExitTimeout = base::TimeDelta::FromSeconds(10);
// Sets the pgid of the current process to its pid. This is needed because
// crosvm assumes that only it and its children are in the same process group
// and indiscriminately sends a SIGKILL if it needs to shut them down.
bool SetPgid() {
if (setpgid(0, 0) != 0) {
PLOG(ERROR) << "Failed to change process group id";
return false;
}
return true;
}
} // namespace
// static
std::unique_ptr<PluginVm> PluginVm::Create(
uint32_t cpus,
std::vector<string> params,
arc_networkd::MacAddress mac_addr,
std::unique_ptr<arc_networkd::SubnetAddress> ipv4_addr,
uint32_t ipv4_netmask,
uint32_t ipv4_gateway,
base::FilePath stateful_dir,
base::FilePath runtime_dir,
base::FilePath cicerone_token_dir) {
auto vm = base::WrapUnique(
new PluginVm(std::move(mac_addr), std::move(ipv4_addr), ipv4_netmask,
ipv4_gateway, std::move(runtime_dir)));
if (!vm->Start(cpus, std::move(params), std::move(stateful_dir),
std::move(cicerone_token_dir))) {
vm.reset();
}
return vm;
}
PluginVm::~PluginVm() {
Shutdown();
}
bool PluginVm::Shutdown() {
// Do a sanity check here to make sure the process is still around. It may
// have crashed and we don't want to be waiting around for an RPC response
// that's never going to come. kill with a signal value of 0 is explicitly
// documented as a way to check for the existence of a process.
if (process_.pid() == 0 || (kill(process_.pid(), 0) < 0 && errno == ESRCH)) {
// The process is already gone.
process_.Release();
return true;
}
// Kill the process with SIGTERM.
if (process_.Kill(SIGTERM, kChildExitTimeout.InSeconds())) {
return true;
}
LOG(WARNING) << "Failed to kill plugin VM with SIGTERM";
// Kill it with fire.
if (process_.Kill(SIGKILL, kChildExitTimeout.InSeconds())) {
return true;
}
LOG(ERROR) << "Failed to kill plugin VM with SIGKILL";
return false;
}
VmInterface::Info PluginVm::GetInfo() {
VmInterface::Info info = {
.ipv4_address = ipv4_addr_->Address(),
.pid = process_.pid(),
.cid = 0,
.seneschal_server_handle = 0,
.status = VmInterface::Status::RUNNING,
};
return info;
}
bool PluginVm::AttachUsbDevice(uint8_t bus,
uint8_t addr,
uint16_t vid,
uint16_t pid,
int fd,
UsbControlResponse* response) {
return false;
}
bool PluginVm::DetachUsbDevice(uint8_t port, UsbControlResponse* response) {
return false;
}
bool PluginVm::ListUsbDevice(std::vector<UsbDevice>* device) {
return false;
}
PluginVm::PluginVm(arc_networkd::MacAddress mac_addr,
std::unique_ptr<arc_networkd::SubnetAddress> ipv4_addr,
uint32_t ipv4_netmask,
uint32_t ipv4_gateway,
base::FilePath runtime_dir)
: mac_addr_(std::move(mac_addr)),
ipv4_addr_(std::move(ipv4_addr)),
netmask_(ipv4_netmask),
gateway_(ipv4_gateway) {
CHECK(ipv4_addr_);
CHECK(base::DirectoryExists(runtime_dir));
// Take ownership of the runtime directory.
CHECK(runtime_dir_.Set(runtime_dir));
}
bool PluginVm::Start(uint32_t cpus,
std::vector<string> params,
base::FilePath stateful_dir,
base::FilePath cicerone_token_dir) {
// Set up the tap device.
base::ScopedFD tap_fd = BuildTapDevice(mac_addr_, gateway_, netmask_);
if (!tap_fd.is_valid()) {
LOG(ERROR) << "Unable to build and configure TAP device";
return false;
}
// Build up the process arguments.
// clang-format off
std::vector<string> args = {
kCrosvmBin, "run",
"--cpus", std::to_string(cpus),
"--mem", "2048", // TODO(b:80150167): memory allocation policy
"--tap-fd", std::to_string(tap_fd.get()),
"--socket", runtime_dir_.GetPath().Append(kCrosvmSocket).value(),
"--plugin", base::FilePath(kPluginBinDir)
.Append(kPluginBinName)
.value(),
"--plugin-mount", base::StringPrintf("%s:%s:false",
kPluginBinDir, kPluginBinDir),
"--plugin-mount", base::StringPrintf("%s:/pvm:true",
stateful_dir.value().c_str()),
// This is the directory where the cicerone host socket lives. The plugin VM
// also creates the guest socket for cicerone in this same directory using
// the following <token>.sock as the name.
"--plugin-mount",
base::StringPrintf("/run/vm_cicerone/client:/cicerone_socket:true"),
// This is the directory where the identity token for the plugin VM is
// stored.
"--plugin-mount", base::StringPrintf("%s:/cicerone_token:false",
cicerone_token_dir.value().c_str()),
};
// clang-format on
for (auto& param : params) {
args.emplace_back("--params");
args.emplace_back(std::move(param));
}
// Put everything into the brillo::ProcessImpl.
for (string& arg : args) {
process_.AddArg(std::move(arg));
}
// Change the process group before exec so that crosvm sending SIGKILL to the
// whole process group doesn't kill us as well.
process_.SetPreExecCallback(base::Bind(&SetPgid));
if (!process_.Start()) {
LOG(ERROR) << "Failed to start VM process";
return false;
}
return true;
}
} // namespace concierge
} // namespace vm_tools