Home | History | Annotate | Download | only in vm_manager
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 #include "host/libs/vm_manager/libvirt_manager.h"
     18 
     19 #include <stdio.h>
     20 #include <cstdlib>
     21 #include <iomanip>
     22 #include <sstream>
     23 
     24 #include <gflags/gflags.h>
     25 #include <glog/logging.h>
     26 #include <libxml/tree.h>
     27 
     28 DEFINE_string(hypervisor_uri, "qemu:///system", "Hypervisor cannonical uri.");
     29 DEFINE_bool(log_xml, false, "Log the XML machine configuration");
     30 
     31 // A lot of useful information about the document created here can be found on
     32 // these websites:
     33 // - https://libvirt.org/formatdomain.html
     34 // - https://wiki.libvirt.org/page/Virtio
     35 namespace vm_manager {
     36 
     37 namespace {
     38 // This trivial no-op helper function serves purpose of making libxml2 happy.
     39 // Apparently, *most* (not all!) string literals in libxml2 have to be of
     40 // unsigned char* (aka xmlChar*) type.
     41 inline const xmlChar* xc(const char* str) {
     42   return reinterpret_cast<const xmlChar*>(str);
     43 }
     44 
     45 // Helper functions that allow us to combine any set of arguments to a single
     46 // string.
     47 // Example:
     48 //   concat("Answer", ' ', "is: ", 42);
     49 // will produce string "Answer is: 42"
     50 template <typename Arg>
     51 inline std::ostream& concat_helper(std::ostream& o, Arg a) {
     52   o << a;
     53   return o;
     54 }
     55 
     56 template <typename Arg, typename... Args>
     57 inline std::ostream& concat_helper(std::ostream& o, Arg a, Args... args) {
     58   o << a;
     59   concat_helper(o, args...);
     60   return o;
     61 }
     62 
     63 template <typename... Args>
     64 inline std::string concat(Args... args) {
     65   std::ostringstream str;
     66   concat_helper(str, args...);
     67   return str.str();
     68 }
     69 
     70 enum class DeviceSourceType {
     71   kFile,
     72   kUnixSocketClient,
     73   kUnixSocketServer,
     74 };
     75 
     76 // Basic VM configuration.
     77 // This section configures name, basic resource allocation and response to
     78 // events.
     79 void ConfigureVM(xmlNode* root, const std::string& instance_name, int cpus,
     80                  int mem_mb, const std::string& uuid) {
     81   xmlNewChild(root, nullptr, xc("name"), xc(instance_name.c_str()));
     82 
     83   // TODO(ender): should this all be 'restart'?
     84   xmlNewChild(root, nullptr, xc("on_poweroff"), xc("destroy"));
     85   xmlNewChild(root, nullptr, xc("on_reboot"), xc("restart"));
     86   xmlNewChild(root, nullptr, xc("on_crash"), xc("restart"));
     87   xmlNewChild(root, nullptr, xc("vcpu"), xc(concat(cpus).c_str()));
     88   xmlNewChild(root, nullptr, xc("memory"), xc(concat(mem_mb << 10).c_str()));
     89   if (uuid.size()) {
     90     xmlNewChild(root, nullptr, xc("uuid"), xc(uuid.c_str()));
     91   }
     92 }
     93 
     94 // Configure VM features.
     95 // This section takes care of the <features> section of the target XML file.
     96 void ConfigureVMFeatures(xmlNode* root,
     97                          const std::initializer_list<std::string>& features) {
     98   auto ch = xmlNewChild(root, nullptr, xc("features"), nullptr);
     99   for (const auto& str : features) {
    100     xmlNewChild(ch, nullptr, xc(str.c_str()), nullptr);
    101   }
    102 }
    103 
    104 // Configure VM OS.
    105 // This section configures target os (<os>).
    106 void ConfigureOperatingSystem(xmlNode* root, const std::string& kernel,
    107                               const std::string& initrd,
    108                               const std::string& args, const std::string& dtb) {
    109   auto os = xmlNewChild(root, nullptr, xc("os"), nullptr);
    110 
    111   auto type = xmlNewChild(os, nullptr, xc("type"), xc("hvm"));
    112   xmlNewProp(type, xc("arch"), xc("x86_64"));
    113   xmlNewProp(type, xc("machine"), xc("pc"));
    114 
    115   xmlNewChild(os, nullptr, xc("kernel"), xc(kernel.c_str()));
    116   xmlNewChild(os, nullptr, xc("initrd"), xc(initrd.c_str()));
    117   xmlNewChild(os, nullptr, xc("cmdline"), xc(args.c_str()));
    118   xmlNewChild(os, nullptr, xc("dtb"), xc(dtb.c_str()));
    119 }
    120 
    121 // Configure QEmu specific arguments.
    122 // This section adds the <qemu:commandline> node.
    123 void ConfigureQEmuSpecificOptions(
    124     xmlNode* root, std::initializer_list<std::string> qemu_args) {
    125   xmlNs* qemu_ns{xmlNewNs(
    126       root, xc("http://libvirt.org/schemas/domain/qemu/1.0"), xc("qemu"))};
    127 
    128   auto cmd = xmlNewChild(root, qemu_ns, xc("commandline"), nullptr);
    129   for (const auto& str : qemu_args) {
    130     auto arg = xmlNewChild(cmd, qemu_ns, xc("arg"), nullptr);
    131     xmlNewProp(arg, xc("value"), xc(str.c_str()));
    132   }
    133 }
    134 
    135 void ConfigureDeviceSource(xmlNode* device, DeviceSourceType type,
    136                            const std::string& path) {
    137   auto source = xmlNewChild(device, nullptr, xc("source"), nullptr);
    138   xmlNewProp(source, xc("path"), xc(path.c_str()));
    139 
    140   switch (type) {
    141     case DeviceSourceType::kFile:
    142       xmlNewProp(device, xc("type"), xc("file"));
    143       break;
    144 
    145     case DeviceSourceType::kUnixSocketClient:
    146       xmlNewProp(device, xc("type"), xc("unix"));
    147       xmlNewProp(source, xc("mode"), xc("connect"));
    148       break;
    149 
    150     case DeviceSourceType::kUnixSocketServer:
    151       xmlNewProp(device, xc("type"), xc("unix"));
    152       xmlNewProp(source, xc("mode"), xc("bind"));
    153       break;
    154   }
    155 }
    156 
    157 // Configure serial port.
    158 // This section adds <serial> elements to <device> node.
    159 void ConfigureSerialPort(xmlNode* devices, int port, DeviceSourceType type,
    160                          const std::string& path) {
    161   auto tty = xmlNewChild(devices, nullptr, xc("serial"), nullptr);
    162   ConfigureDeviceSource(tty, type, path);
    163 
    164   if (type == DeviceSourceType::kFile) {
    165     LOG(INFO) << "Non-interactive serial port will send output to " << path;
    166   } else {
    167     LOG(INFO) << "Interactive serial port set up. To access the console run:";
    168     LOG(INFO) << "$ sudo socat file:$(tty),raw,echo=0 " << path;
    169   }
    170   auto tgt = xmlNewChild(tty, nullptr, xc("target"), nullptr);
    171   xmlNewProp(tgt, xc("port"), xc(concat(port).c_str()));
    172 }
    173 
    174 // Configure disk partition.
    175 // This section adds <disk> elements to <devices> node.
    176 void ConfigureDisk(xmlNode* devices, const std::string& name,
    177                    const std::string& path) {
    178   auto ch = xmlNewChild(devices, nullptr, xc("disk"), nullptr);
    179   xmlNewProp(ch, xc("type"), xc("file"));
    180 
    181   auto dr = xmlNewChild(ch, nullptr, xc("driver"), nullptr);
    182   xmlNewProp(dr, xc("name"), xc("qemu"));
    183   xmlNewProp(dr, xc("type"), xc("raw"));
    184   xmlNewProp(dr, xc("io"), xc("threads"));
    185 
    186   auto tg = xmlNewChild(ch, nullptr, xc("target"), nullptr);
    187   xmlNewProp(tg, xc("dev"), xc(name.c_str()));
    188   xmlNewProp(tg, xc("bus"), xc("virtio"));
    189 
    190   auto sr = xmlNewChild(ch, nullptr, xc("source"), nullptr);
    191   xmlNewProp(sr, xc("file"), xc(path.c_str()));
    192 }
    193 
    194 // Configure virtio channel.
    195 // This section adds <channel> elements to <devices> node.
    196 void ConfigureVirtioChannel(xmlNode* devices, int port, const std::string& name,
    197                             DeviceSourceType type, const std::string& path) {
    198   if (path.empty()) {
    199     return;
    200   }
    201   auto vch = xmlNewChild(devices, nullptr, xc("channel"), nullptr);
    202   ConfigureDeviceSource(vch, type, path);
    203 
    204   auto tgt = xmlNewChild(vch, nullptr, xc("target"), nullptr);
    205   xmlNewProp(tgt, xc("type"), xc("virtio"));
    206   xmlNewProp(tgt, xc("name"), xc(name.c_str()));
    207 
    208   auto adr = xmlNewChild(vch, nullptr, xc("address"), nullptr);
    209   xmlNewProp(adr, xc("type"), xc("virtio-serial"));
    210   xmlNewProp(adr, xc("controller"), xc("0"));
    211   xmlNewProp(adr, xc("bus"), xc("0"));
    212   xmlNewProp(adr, xc("port"), xc(concat(port).c_str()));
    213 }
    214 
    215 // Configure network interface.
    216 // This section adds <interface> elements to <devices> node.
    217 void ConfigureNIC(xmlNode* devices, const std::string& name,
    218                   const std::string& bridge, int guest_id, int nic_id) {
    219   auto nic = xmlNewChild(devices, nullptr, xc("interface"), nullptr);
    220   xmlNewProp(nic, xc("type"), xc("bridge"));
    221 
    222   auto brg = xmlNewChild(nic, nullptr, xc("source"), nullptr);
    223   xmlNewProp(brg, xc("bridge"), xc(bridge.c_str()));
    224 
    225   auto mac = xmlNewChild(nic, nullptr, xc("mac"), nullptr);
    226   xmlNewProp(mac, xc("address"),
    227              xc(concat("00:43:56:44:", std::setfill('0'), std::hex,
    228                        std::setw(2), guest_id, ':', std::setw(2), nic_id)
    229                     .c_str()));
    230 
    231   auto mdl = xmlNewChild(nic, nullptr, xc("model"), nullptr);
    232   xmlNewProp(mdl, xc("type"), xc("virtio"));
    233 
    234   auto tgt = xmlNewChild(nic, nullptr, xc("target"), nullptr);
    235   xmlNewProp(tgt, xc("dev"), xc(name.c_str()));
    236 }
    237 
    238 // Configure Harwdare Random Number Generator.
    239 // This section adds <rng> element to <devices> node.
    240 void ConfigureHWRNG(xmlNode* devices, const std::string& entsrc) {
    241   auto rng = xmlNewChild(devices, nullptr, xc("rng"), nullptr);
    242   xmlNewProp(rng, xc("model"), xc("virtio"));
    243 
    244   auto rate = xmlNewChild(rng, nullptr, xc("rate"), nullptr);
    245   xmlNewProp(rate, xc("period"), xc("2000"));
    246   xmlNewProp(rate, xc("bytes"), xc("1024"));
    247 
    248   auto bend = xmlNewChild(rng, nullptr, xc("backend"), xc(entsrc.c_str()));
    249   xmlNewProp(bend, xc("model"), xc("random"));
    250 }
    251 
    252 std::string GetLibvirtCommand() {
    253   std::string cmd = "virsh";
    254   if (!FLAGS_hypervisor_uri.empty()) {
    255     cmd += " -c " + FLAGS_hypervisor_uri;
    256   }
    257   return cmd;
    258 }
    259 
    260 }  // namespace
    261 
    262 std::string LibvirtManager::BuildXmlConfig() const {
    263   auto config = vsoc::CuttlefishConfig::Get();
    264   std::string instance_name = config->instance_name();
    265 
    266   std::unique_ptr<xmlDoc, void (*)(xmlDocPtr)> xml{xmlNewDoc(xc("1.0")),
    267                                                    xmlFreeDoc};
    268   auto root{xmlNewNode(nullptr, xc("domain"))};
    269   xmlDocSetRootElement(xml.get(), root);
    270   xmlNewProp(root, xc("type"), xc("kvm"));
    271 
    272   ConfigureVM(root, instance_name, config->cpus(), config->memory_mb(),
    273               config->uuid());
    274   ConfigureVMFeatures(root, {"acpi", "apic", "hap"});
    275   ConfigureOperatingSystem(root, config->kernel_image_path(),
    276                            config->ramdisk_image_path(), config->kernel_args(),
    277                            config->dtb_path());
    278   ConfigureQEmuSpecificOptions(
    279       root, {"-chardev",
    280              concat("socket,path=", config->ivshmem_qemu_socket_path(),
    281                     ",id=ivsocket"),
    282              "-device",
    283              concat("ivshmem-doorbell,chardev=ivsocket,vectors=",
    284                     config->ivshmem_vector_count()),
    285              "-cpu", "host"});
    286 
    287   if (config->disable_app_armor_security()) {
    288     auto seclabel = xmlNewChild(root, nullptr, xc("seclabel"), nullptr);
    289     xmlNewProp(seclabel, xc("type"), xc("none"));
    290     xmlNewProp(seclabel, xc("model"), xc("apparmor"));
    291   }
    292   if (config->disable_dac_security()) {
    293     auto seclabel = xmlNewChild(root, nullptr, xc("seclabel"), nullptr);
    294     xmlNewProp(seclabel, xc("type"), xc("none"));
    295     xmlNewProp(seclabel, xc("model"), xc("dac"));
    296   }
    297 
    298   auto devices = xmlNewChild(root, nullptr, xc("devices"), nullptr);
    299 
    300   ConfigureSerialPort(devices, 0, DeviceSourceType::kUnixSocketClient,
    301                       config->kernel_log_socket_name());
    302   ConfigureSerialPort(devices, 1, DeviceSourceType::kUnixSocketServer,
    303                       config->console_path());
    304   ConfigureVirtioChannel(devices, 1, "cf-logcat", DeviceSourceType::kFile,
    305                          config->logcat_path());
    306   ConfigureVirtioChannel(devices, 2, "cf-gadget-usb-v1",
    307                          DeviceSourceType::kUnixSocketClient,
    308                          config->usb_v1_socket_name());
    309 
    310   ConfigureDisk(devices, "vda", config->system_image_path());
    311   ConfigureDisk(devices, "vdb", config->data_image_path());
    312   ConfigureDisk(devices, "vdc", config->cache_image_path());
    313   ConfigureDisk(devices, "vdd", config->vendor_image_path());
    314 
    315   ConfigureNIC(devices, config->mobile_tap_name(), config->mobile_bridge_name(),
    316                vsoc::GetInstance(), 1);
    317   ConfigureHWRNG(devices, config->entropy_source());
    318 
    319   xmlChar* tgt;
    320   int tgt_len;
    321 
    322   xmlDocDumpFormatMemoryEnc(xml.get(), &tgt, &tgt_len, "utf-8", true);
    323   std::string out((const char*)(tgt), tgt_len);
    324   xmlFree(tgt);
    325   return out;
    326 }
    327 
    328 bool LibvirtManager::Start() const {
    329   std::string start_command = GetLibvirtCommand();
    330   start_command += " create /dev/fd/0";
    331 
    332   std::string xml = BuildXmlConfig();
    333   if (FLAGS_log_xml) {
    334     LOG(INFO) << "Using XML:\n" << xml;
    335   }
    336 
    337   FILE* launch = popen(start_command.c_str(), "w");
    338   if (!launch) {
    339     LOG(FATAL) << "Unable to execute " << start_command;
    340     return false;
    341   }
    342   int rval = fputs(xml.c_str(), launch);
    343   if (rval == EOF) {
    344     LOG(FATAL) << "Launch command exited while accepting XML";
    345     return false;
    346   }
    347   int exit_code = pclose(launch);
    348   if (exit_code != 0) {
    349     LOG(FATAL) << "Launch command exited with status " << exit_code;
    350     return false;
    351   }
    352   return true;
    353 }
    354 
    355 bool LibvirtManager::Stop() const {
    356   auto config = vsoc::CuttlefishConfig::Get();
    357   auto stop_command = GetLibvirtCommand();
    358   stop_command += " destroy " + config->instance_name();
    359   return std::system(stop_command.c_str()) == 0;
    360 }
    361 
    362 }  // namespace vm_manager
    363