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