blob: 66ac3fb1d4a19fce6bdbdf6fb73e2420929381fd [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <iomanip>
#include <sstream>
#include <glog/logging.h>
#include "host/config/guest_config.h"
// This class represents libvirt guest configuration.
// A lot of useful information about the document created here can be found on
// these websites:
// - https://libvirt.org/formatdomain.html
// - https://wiki.libvirt.org/page/Virtio
namespace config {
namespace {
// This trivial no-op helper function serves purpose of making libxml2 happy.
// Apparently, *most* (not all!) string literals in libxml2 have to be of
// unsigned char* (aka xmlChar*) type.
inline const xmlChar* xc(const char* str) {
return reinterpret_cast<const xmlChar*>(str);
}
// Helper functions that allow us to combine any set of arguments to a single
// string.
// Example:
// concat("Answer", ' ', "is: ", 42);
// will produce string "Answer is: 42"
template <typename Arg>
inline std::ostream& concat_helper(std::ostream& o, Arg a) {
o << a;
return o;
}
template <typename Arg, typename... Args>
inline std::ostream& concat_helper(std::ostream& o, Arg a, Args... args) {
o << a;
concat_helper(o, args...);
return o;
}
template <typename... Args>
inline std::string concat(Args... args) {
std::ostringstream str;
concat_helper(str, args...);
return str.str();
}
enum class DeviceSourceType {
kFile,
kUnixSocketClient,
kUnixSocketServer,
};
// Basic VM configuration.
// This section configures name, basic resource allocation and response to
// events.
void ConfigureVM(xmlNode* root, const std::string& instance_name, int cpus,
int mem_mb) {
xmlNewChild(root, nullptr, xc("name"), xc(instance_name.c_str()));
// TODO(ender): should this all be 'restart'?
xmlNewChild(root, nullptr, xc("on_poweroff"), xc("destroy"));
xmlNewChild(root, nullptr, xc("on_reboot"), xc("restart"));
xmlNewChild(root, nullptr, xc("on_crash"), xc("restart"));
xmlNewChild(root, nullptr, xc("vcpu"), xc(concat(cpus).c_str()));
xmlNewChild(root, nullptr, xc("memory"), xc(concat(mem_mb << 10).c_str()));
}
// Configure VM features.
// This section takes care of the <features> section of the target XML file.
void ConfigureVMFeatures(xmlNode* root,
const std::initializer_list<std::string>& features) {
auto ch = xmlNewChild(root, nullptr, xc("features"), nullptr);
for (const auto& str : features) {
xmlNewChild(ch, nullptr, xc(str.c_str()), nullptr);
}
}
// Configure VM OS.
// This section configures target os (<os>).
void ConfigureOperatingSystem(xmlNode* root, const std::string& kernel,
const std::string& initrd,
const std::string& args) {
auto os = xmlNewChild(root, nullptr, xc("os"), nullptr);
auto type = xmlNewChild(os, nullptr, xc("type"), xc("hvm"));
xmlNewProp(type, xc("arch"), xc("x86_64"));
xmlNewProp(type, xc("machine"), xc("pc"));
xmlNewChild(os, nullptr, xc("kernel"), xc(kernel.c_str()));
xmlNewChild(os, nullptr, xc("initrd"), xc(initrd.c_str()));
xmlNewChild(os, nullptr, xc("cmdline"), xc(args.c_str()));
}
// Configure QEmu specific arguments.
// This section adds the <qemu:commandline> node.
void ConfigureQEmuSpecificOptions(
xmlNode* root, std::initializer_list<std::string> qemu_args) {
xmlNs* qemu_ns{xmlNewNs(
root, xc("http://libvirt.org/schemas/domain/qemu/1.0"), xc("qemu"))};
auto cmd = xmlNewChild(root, qemu_ns, xc("commandline"), nullptr);
for (const auto& str : qemu_args) {
auto arg = xmlNewChild(cmd, qemu_ns, xc("arg"), nullptr);
xmlNewProp(arg, xc("value"), xc(str.c_str()));
}
}
void ConfigureDeviceSource(xmlNode* device, DeviceSourceType type,
const std::string& path) {
auto source = xmlNewChild(device, nullptr, xc("source"), nullptr);
xmlNewProp(source, xc("path"), xc(path.c_str()));
switch (type) {
case DeviceSourceType::kFile:
xmlNewProp(device, xc("type"), xc("file"));
break;
case DeviceSourceType::kUnixSocketClient:
xmlNewProp(device, xc("type"), xc("unix"));
xmlNewProp(source, xc("mode"), xc("connect"));
break;
case DeviceSourceType::kUnixSocketServer:
xmlNewProp(device, xc("type"), xc("unix"));
xmlNewProp(source, xc("mode"), xc("bind"));
break;
}
}
// Configure serial port.
// This section adds <serial> elements to <device> node.
void ConfigureSerialPort(xmlNode* devices, int port, DeviceSourceType type,
const std::string& path) {
auto tty = xmlNewChild(devices, nullptr, xc("serial"), nullptr);
ConfigureDeviceSource(tty, type, path);
if (type == DeviceSourceType::kFile) {
LOG(INFO) << "Non-interactive serial port will send output to " << path;
} else {
LOG(INFO) << "Interactive serial port set up. To access the console run:";
LOG(INFO) << "$ sudo socat file:$(tty),raw,echo=0 " << path;
}
auto tgt = xmlNewChild(tty, nullptr, xc("target"), nullptr);
xmlNewProp(tgt, xc("port"), xc(concat(port).c_str()));
}
// Configure disk partition.
// This section adds <disk> elements to <devices> node.
void ConfigureDisk(xmlNode* devices, const std::string& name,
const std::string& path) {
auto ch = xmlNewChild(devices, nullptr, xc("disk"), nullptr);
xmlNewProp(ch, xc("type"), xc("file"));
auto dr = xmlNewChild(ch, nullptr, xc("driver"), nullptr);
xmlNewProp(dr, xc("name"), xc("qemu"));
xmlNewProp(dr, xc("type"), xc("raw"));
xmlNewProp(dr, xc("io"), xc("threads"));
auto tg = xmlNewChild(ch, nullptr, xc("target"), nullptr);
xmlNewProp(tg, xc("dev"), xc(name.c_str()));
xmlNewProp(tg, xc("bus"), xc("virtio"));
auto sr = xmlNewChild(ch, nullptr, xc("source"), nullptr);
xmlNewProp(sr, xc("file"), xc(path.c_str()));
}
// Configure virtio channel.
// This section adds <channel> elements to <devices> node.
void ConfigureVirtioChannel(xmlNode* devices, int port, DeviceSourceType type,
const std::string& path) {
auto vch = xmlNewChild(devices, nullptr, xc("channel"), nullptr);
ConfigureDeviceSource(vch, type, path);
auto tgt = xmlNewChild(vch, nullptr, xc("target"), nullptr);
xmlNewProp(tgt, xc("type"), xc("virtio"));
xmlNewProp(tgt, xc("name"), xc(concat("vport0p", port).c_str()));
auto adr = xmlNewChild(vch, nullptr, xc("address"), nullptr);
xmlNewProp(adr, xc("type"), xc("virtio-serial"));
xmlNewProp(adr, xc("controller"), xc("0"));
xmlNewProp(adr, xc("bus"), xc("0"));
xmlNewProp(adr, xc("port"), xc(concat(port).c_str()));
}
// Configure network interface.
// This section adds <interface> elements to <devices> node.
void ConfigureNIC(xmlNode* devices, const std::string& name,
const std::string& bridge, int guest_id, int nic_id) {
auto nic = xmlNewChild(devices, nullptr, xc("interface"), nullptr);
xmlNewProp(nic, xc("type"), xc("bridge"));
auto brg = xmlNewChild(nic, nullptr, xc("source"), nullptr);
xmlNewProp(brg, xc("bridge"), xc(bridge.c_str()));
auto mac = xmlNewChild(nic, nullptr, xc("mac"), nullptr);
xmlNewProp(mac, xc("address"),
xc(concat("00:41:56:44:", std::setfill('0'), std::hex,
std::setw(2), guest_id, ':', std::setw(2), nic_id)
.c_str()));
auto mdl = xmlNewChild(nic, nullptr, xc("model"), nullptr);
xmlNewProp(mdl, xc("type"), xc("e1000"));
auto tgt = xmlNewChild(nic, nullptr, xc("target"), nullptr);
xmlNewProp(tgt, xc("dev"), xc(name.c_str()));
}
// Configure Harwdare Random Number Generator.
// This section adds <rng> element to <devices> node.
void ConfigureHWRNG(xmlNode* devices, const std::string& entsrc) {
auto rng = xmlNewChild(devices, nullptr, xc("rng"), nullptr);
xmlNewProp(rng, xc("model"), xc("virtio"));
auto rate = xmlNewChild(rng, nullptr, xc("rate"), nullptr);
xmlNewProp(rate, xc("period"), xc("2000"));
xmlNewProp(rate, xc("bytes"), xc("1024"));
auto bend = xmlNewChild(rng, nullptr, xc("backend"), xc(entsrc.c_str()));
xmlNewProp(bend, xc("model"), xc("random"));
}
} // namespace
std::string GuestConfig::GetInstanceName() const {
return concat("android-cuttlefish-", id_);
}
std::string GuestConfig::GetUSBSocketName() const {
return concat("/tmp/", GetInstanceName(), "-usb");
}
std::string GuestConfig::Build() const {
std::string instance_name = GetInstanceName();
std::unique_ptr<xmlDoc, void (*)(xmlDocPtr)> xml{xmlNewDoc(xc("1.0")),
xmlFreeDoc};
auto root{xmlNewNode(nullptr, xc("domain"))};
xmlDocSetRootElement(xml.get(), root);
xmlNewProp(root, xc("type"), xc("kvm"));
ConfigureVM(root, instance_name, vcpus_, memory_mb_);
ConfigureVMFeatures(root, {"acpi", "apic", "hap"});
ConfigureOperatingSystem(root, kernel_name_, initrd_name_, kernel_args_);
ConfigureQEmuSpecificOptions(
root,
{"-chardev", concat("socket,path=", ivshmem_socket_path_, ",id=ivsocket"),
"-device",
concat("ivshmem-doorbell,chardev=ivsocket,vectors=",
ivshmem_vector_count_),
"-cpu", "host"});
auto devices = xmlNewChild(root, nullptr, xc("devices"), nullptr);
ConfigureSerialPort(devices, 0, DeviceSourceType::kUnixSocketServer,
concat("/tmp/", instance_name, "-serial"));
ConfigureVirtioChannel(devices, 1, DeviceSourceType::kFile,
concat("/tmp/", instance_name, "-logcat"));
ConfigureVirtioChannel(devices, 2, DeviceSourceType::kUnixSocketClient,
GetUSBSocketName());
ConfigureDisk(devices, "vda", system_partition_path_);
ConfigureDisk(devices, "vdb", data_partition_path_);
ConfigureDisk(devices, "vdc", cache_partition_path_);
ConfigureNIC(devices, concat("amobile", id_), mobile_bridge_name_, id_, 1);
ConfigureHWRNG(devices, entropy_source_);
xmlNewChild(devices, nullptr, xc("emulator"), xc(emulator_.c_str()));
xmlChar* tgt;
int tgt_len;
xmlDocDumpFormatMemoryEnc(xml.get(), &tgt, &tgt_len, "utf-8", true);
std::string out((const char*)(tgt), tgt_len);
xmlFree(tgt);
return out;
}
} // namespace config