| /* |
| * 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", ramdisk_partition_path_); |
| ConfigureDisk(devices, "vdb", system_partition_path_); |
| ConfigureDisk(devices, "vdc", data_partition_path_); |
| ConfigureDisk(devices, "vdd", 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 |