blob: 545580ea22cb88926c7eee365d346ec73ddce42e [file] [log] [blame]
// Copyright 2019 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/arc_vm.h"
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
// Needs to be included after sys/socket.h
#include <linux/vm_sockets.h>
#include <utility>
#include <base/files/file.h>
#include <base/files/file_path.h>
#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/string_number_conversions.h>
#include <base/strings/stringprintf.h>
#include <base/strings/string_util.h>
#include <base/strings/string_split.h>
#include <base/system/sys_info.h>
#include <base/time/time.h>
#include <chromeos/constants/vm_tools.h>
#include "vm_tools/concierge/tap_device_builder.h"
#include "vm_tools/concierge/vm_util.h"
using std::string;
namespace vm_tools {
namespace concierge {
namespace {
// Name of the control socket used for controlling crosvm.
constexpr char kCrosvmSocket[] = "arcvm.sock";
// Path to the wayland socket.
constexpr char kWaylandSocket[] = "/run/chrome/wayland-0";
// How long to wait before timing out on child process exits.
constexpr base::TimeDelta kChildExitTimeout = base::TimeDelta::FromSeconds(10);
// The CPU cgroup where all the ARCVM's crosvm processes should belong to.
constexpr char kArcvmCpuCgroup[] = "/sys/fs/cgroup/cpu/vms/arc";
// Port for arc-powerctl running on the guest side.
constexpr unsigned int kVSockPort = 4242;
// Path to the custom parameter file.
constexpr char kCustomParameterFilePath[] = "/etc/arcvm_dev.conf";
// Custom parameter key to override the kernel path
constexpr char kKeyToOverrideKernelPath[] = "KERNEL_PATH";
// Shared directories and their tags
constexpr char kOemEtcSharedDir[] = "/run/arcvm/host_generated/oem/etc";
constexpr char kOemEtcSharedDirTag[] = "oem_etc";
// Uid and gid mappings for the android data directory. This is a
// comma-separated list of 3 values: <start of range inside the user namespace>
// <start of range outside the user namespace> <count>. The values are taken
// from platform2/arc/container-bundles/rvc/config.json.
constexpr char kAndroidUidMap[] =
"0 655360 5000,5000 600 50,5050 660410 1994950";
constexpr char kAndroidGidMap[] =
"0 655360 1065,1065 20119 1,1066 656426 3934,5000 600 50,5050 660410 "
"1994950";
// For |kOemEtcSharedDir|, map host's chronos to guest's root, also arc-camera
// (603) to vendor_arc_camera (5003).
constexpr char kOemEtcUgidMap[] = "0 1000 1, 5000 600 50";
base::ScopedFD ConnectVSock(int cid) {
DLOG(INFO) << "Creating VSOCK...";
struct sockaddr_vm sa = {};
sa.svm_family = AF_VSOCK;
sa.svm_cid = cid;
sa.svm_port = kVSockPort;
base::ScopedFD fd(
socket(AF_VSOCK, SOCK_STREAM | SOCK_CLOEXEC, 0 /* protocol */));
if (!fd.is_valid()) {
PLOG(ERROR) << "Failed to create VSOCK";
return {};
}
DLOG(INFO) << "Connecting VSOCK";
if (HANDLE_EINTR(connect(fd.get(),
reinterpret_cast<const struct sockaddr*>(&sa),
sizeof(sa))) == -1) {
fd.reset();
PLOG(ERROR) << "Failed to connect.";
return {};
}
DLOG(INFO) << "VSOCK connected.";
return fd;
}
bool ShutdownArcVm(int cid) {
base::ScopedFD vsock(ConnectVSock(cid));
if (!vsock.is_valid())
return false;
const std::string command("poweroff");
if (HANDLE_EINTR(write(vsock.get(), command.c_str(), command.size())) !=
command.size()) {
PLOG(WARNING) << "Failed to write to ARCVM VSOCK";
return false;
}
DLOG(INFO) << "Started shutting down ARCVM";
return true;
}
std::string CreateSharedDataParam(const base::FilePath& data_dir,
const std::string& tag,
bool enable_caches) {
return base::StringPrintf(
"%s:%s:type=fs:cache=%s:uidmap=%s:gidmap=%s:timeout=3600:rewrite-"
"security-xattrs=true",
data_dir.value().c_str(), tag.c_str(), enable_caches ? "always" : "never",
kAndroidUidMap, kAndroidGidMap);
}
} // namespace
ArcVm::ArcVm(int32_t vsock_cid,
std::unique_ptr<patchpanel::Client> network_client,
std::unique_ptr<SeneschalServerProxy> seneschal_server_proxy,
base::FilePath runtime_dir,
ArcVmFeatures features)
: VmBaseImpl(std::move(network_client)),
vsock_cid_(vsock_cid),
seneschal_server_proxy_(std::move(seneschal_server_proxy)),
features_(features) {
CHECK(base::DirectoryExists(runtime_dir));
// Take ownership of the runtime directory.
CHECK(runtime_dir_.Set(runtime_dir));
}
ArcVm::~ArcVm() {
Shutdown();
}
std::unique_ptr<ArcVm> ArcVm::Create(
base::FilePath kernel,
base::FilePath rootfs,
base::FilePath fstab,
uint32_t cpus,
base::FilePath pstore_path,
uint32_t pstore_size,
std::vector<ArcVm::Disk> disks,
uint32_t vsock_cid,
base::FilePath data_dir,
std::unique_ptr<patchpanel::Client> network_client,
std::unique_ptr<SeneschalServerProxy> seneschal_server_proxy,
base::FilePath runtime_dir,
ArcVmFeatures features,
std::vector<string> params) {
auto vm = base::WrapUnique(new ArcVm(vsock_cid, std::move(network_client),
std::move(seneschal_server_proxy),
std::move(runtime_dir), features));
if (!vm->Start(std::move(kernel), std::move(rootfs), std::move(fstab), cpus,
std::move(pstore_path), pstore_size, std::move(disks),
std::move(data_dir), std::move(params))) {
vm.reset();
}
return vm;
}
std::string ArcVm::GetVmSocketPath() const {
return runtime_dir_.GetPath().Append(kCrosvmSocket).value();
}
bool ArcVm::Start(base::FilePath kernel,
base::FilePath rootfs,
base::FilePath fstab,
uint32_t cpus,
base::FilePath pstore_path,
uint32_t pstore_size,
std::vector<ArcVm::Disk> disks,
base::FilePath data_dir,
std::vector<string> params) {
// Get the available network interfaces.
network_devices_ = network_client_->NotifyArcVmStartup(vsock_cid_);
if (network_devices_.empty()) {
LOG(ERROR) << "No network devices available";
return false;
}
// Open the tap device(s).
std::vector<base::ScopedFD> tap_fds;
for (const auto& dev : network_devices_) {
auto fd = OpenTapDevice(dev.ifname(), true /*vnet_hdr*/,
nullptr /*ifname_out*/);
if (!fd.is_valid()) {
LOG(ERROR) << "Unable to open and configure TAP device " << dev.ifname();
} else {
tap_fds.emplace_back(std::move(fd));
}
}
if (tap_fds.empty()) {
LOG(ERROR) << "No TAP devices available";
return false;
}
const std::string rootfs_flag = rootfs_writable() ? "--rwdisk" : "--disk";
std::string oem_etc_shared_dir = base::StringPrintf(
"%s:%s:type=fs:cache=always:uidmap=%s:gidmap=%s:timeout=3600:rewrite-"
"security-xattrs=true",
kOemEtcSharedDir, kOemEtcSharedDirTag, kOemEtcUgidMap, kOemEtcUgidMap);
std::string shared_data = CreateSharedDataParam(data_dir, "data", true);
std::string shared_data_media =
CreateSharedDataParam(data_dir.Append("media"), "data_media", false);
// Build up the process arguments.
// clang-format off
base::StringPairs args = {
{ kCrosvmBin, "run" },
{ "--cpus", std::to_string(cpus) },
{ "--mem", GetVmMemoryMiB() },
{ rootfs_flag, rootfs.value() },
{ "--cid", std::to_string(vsock_cid_) },
{ "--socket", GetVmSocketPath() },
{ "--gpu", "" },
{ "--video-decoder", ""},
{ "--wayland-sock", kWaylandSocket },
{ "--wayland-sock", "/run/arcvm/mojo/mojo-proxy.sock,name=mojo" },
{ "--serial", "type=syslog,hardware=serial,num=1,earlycon=true" },
{ "--serial", "type=syslog,hardware=virtio-console,num=1,"
"console=true" },
{ "--syslog-tag", base::StringPrintf("ARCVM(%u)", vsock_cid_) },
{ "--ac97", "backend=cras,capture=true" },
{ "--ac97", "backend=cras,capture=true,capture_effects=aec" },
{ "--android-fstab", fstab.value() },
{ "--pstore", base::StringPrintf("path=%s,size=%d",
pstore_path.value().c_str(), pstore_size) },
{ "--shared-dir", std::move(oem_etc_shared_dir) },
{ "--shared-dir", std::move(shared_data) },
{ "--shared-dir", std::move(shared_data_media) },
{ "--params", base::JoinString(params, " ") },
};
// clang-format on
if (USE_CROSVM_WL_DMABUF)
args.emplace_back("--wayland-dmabuf", "");
for (const auto& fd : tap_fds) {
args.emplace_back("--tap-fd", std::to_string(fd.get()));
}
// Add any extra disks.
for (const auto& disk : disks) {
if (disk.writable) {
args.emplace_back("--rwdisk", disk.path.value());
} else {
args.emplace_back("--disk", disk.path.value());
}
}
// Add any custom parameters from file.
base::FilePath file_path(kCustomParameterFilePath);
std::string data;
if (base::ReadFileToString(file_path, &data))
LoadCustomParameters(data, &args);
// Finally list the path to the kernel.
const std::string kernel_path =
RemoveParametersWithKey(kKeyToOverrideKernelPath, kernel.value(), &args);
args.emplace_back(kernel_path, "");
// Put everything into the brillo::ProcessImpl.
for (std::pair<std::string, std::string>& arg : args) {
process_.AddArg(std::move(arg.first));
if (!arg.second.empty())
process_.AddArg(std::move(arg.second));
}
// Change the process group before exec so that crosvm sending SIGKILL to the
// whole process group doesn't kill us as well. The function also changes the
// cpu cgroup for ARCVM's crosvm processes.
process_.SetPreExecCallback(base::Bind(
&SetUpCrosvmProcess, base::FilePath(kArcvmCpuCgroup).Append("tasks")));
if (!process_.Start()) {
LOG(ERROR) << "Failed to start VM process";
// Release any network resources.
network_client_->NotifyArcVmShutdown(vsock_cid_);
return false;
}
return true;
}
bool ArcVm::Shutdown() {
// Notify arc-patchpanel that ARCVM is down.
// This should run before the process existence check below since we still
// want to release the network resources on crash.
if (!network_client_->NotifyArcVmShutdown(vsock_cid_)) {
LOG(WARNING) << "Unable to notify networking services";
}
// 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 (!CheckProcessExists(process_.pid())) {
LOG(INFO) << "ARCVM process is already gone. Do nothing";
process_.Release();
return true;
}
LOG(INFO) << "Shutting down ARCVM";
// Ask arc-powerctl running on the guest to power off the VM.
// TODO(yusukes): We should call ShutdownArcVm() only after the guest side
// service is fully started. b/143711798
if (ShutdownArcVm(vsock_cid_) &&
WaitForChild(process_.pid(), kChildExitTimeout)) {
LOG(INFO) << "ARCVM is shut down";
process_.Release();
return true;
}
LOG(WARNING) << "Failed to shut down ARCVM gracefully. Trying to turn it "
<< "down via the crosvm socket.";
RunCrosvmCommand("stop", GetVmSocketPath());
// We can't actually trust the exit codes that crosvm gives us so just see if
// it exited.
if (WaitForChild(process_.pid(), kChildExitTimeout)) {
process_.Release();
return true;
}
LOG(WARNING) << "Failed to stop VM " << vsock_cid_ << " via crosvm socket";
// Kill the process with SIGTERM.
if (process_.Kill(SIGTERM, kChildExitTimeout.InSeconds())) {
process_.Release();
return true;
}
LOG(WARNING) << "Failed to kill VM " << vsock_cid_ << " with SIGTERM";
// Kill it with fire.
if (process_.Kill(SIGKILL, kChildExitTimeout.InSeconds())) {
process_.Release();
return true;
}
LOG(ERROR) << "Failed to kill VM " << vsock_cid_ << " with SIGKILL";
return false;
}
bool ArcVm::AttachUsbDevice(uint8_t bus,
uint8_t addr,
uint16_t vid,
uint16_t pid,
int fd,
UsbControlResponse* response) {
return vm_tools::concierge::AttachUsbDevice(GetVmSocketPath(), bus, addr, vid,
pid, fd, response);
}
bool ArcVm::DetachUsbDevice(uint8_t port, UsbControlResponse* response) {
return vm_tools::concierge::DetachUsbDevice(GetVmSocketPath(), port,
response);
}
bool ArcVm::ListUsbDevice(std::vector<UsbDevice>* devices) {
return vm_tools::concierge::ListUsbDevice(GetVmSocketPath(), devices);
}
void ArcVm::HandleSuspendImminent() {
RunCrosvmCommand("suspend", GetVmSocketPath());
}
void ArcVm::HandleSuspendDone() {
RunCrosvmCommand("resume", GetVmSocketPath());
}
// static
bool ArcVm::SetVmCpuRestriction(CpuRestrictionState cpu_restriction_state) {
return VmBaseImpl::SetVmCpuRestriction(cpu_restriction_state,
kArcvmCpuCgroup);
}
uint32_t ArcVm::IPv4Address() const {
for (const auto& dev : network_devices_) {
if (dev.ifname() == "arc0")
return dev.ipv4_addr();
}
return 0;
}
VmInterface::Info ArcVm::GetInfo() {
VmInterface::Info info = {
.ipv4_address = IPv4Address(),
.pid = pid(),
.cid = cid(),
.seneschal_server_handle = seneschal_server_handle(),
.status = VmInterface::Status::RUNNING,
};
return info;
}
bool ArcVm::GetVmEnterpriseReportingInfo(
GetVmEnterpriseReportingInfoResponse* response) {
response->set_success(false);
response->set_failure_reason("Not implemented");
return false;
}
vm_tools::concierge::DiskImageStatus ArcVm::ResizeDisk(
uint64_t new_size, std::string* failure_reason) {
*failure_reason = "Not implemented";
return DiskImageStatus::DISK_STATUS_FAILED;
}
vm_tools::concierge::DiskImageStatus ArcVm::GetDiskResizeStatus(
std::string* failure_reason) {
*failure_reason = "Not implemented";
return DiskImageStatus::DISK_STATUS_FAILED;
}
} // namespace concierge
} // namespace vm_tools