| // Copyright 2018 The Fuchsia 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 "garnet/bin/guest/vmm/guest_config.h" |
| |
| #include <libgen.h> |
| #include <unistd.h> |
| #include <iostream> |
| |
| #include <lib/fxl/command_line.h> |
| #include <lib/fxl/logging.h> |
| #include <lib/fxl/strings/string_number_conversions.h> |
| #include <rapidjson/document.h> |
| #include <zircon/device/block.h> |
| |
| static void print_usage(fxl::CommandLine& cl) { |
| // clang-format off |
| std::cerr << "usage: " << cl.argv0() << " [OPTIONS]\n"; |
| std::cerr << "\n"; |
| std::cerr << "OPTIONS:\n"; |
| std::cerr << "\t--block=[spec] Adds a block device with the given parameters\n"; |
| std::cerr << "\t--cmdline-add=[string] Adds 'string' to the existing kernel command line.\n"; |
| std::cerr << "\t This will overwrite any existing command line created\n"; |
| std::cerr << "\t using --cmdline or --cmdline-add\n"; |
| std::cerr << "\t--cmdline=[string] Use 'string' as the kernel command line\n"; |
| std::cerr << "\t--cpus=[number] Number of virtual CPUs available to the guest\n"; |
| std::cerr << "\t--dtb-overlay=[path] Load a DTB overlay for a Linux kernel\n"; |
| std::cerr << "\t--host-memory Directly map host memory into the guest\n"; |
| std::cerr << "\t--linux=[path] Load a Linux kernel from 'path'\n"; |
| std::cerr << "\t--legacy-net Enable legacy virtio-net (uses host interface)\n"; |
| std::cerr << "\t--memory=[bytes] Allocate 'bytes' of memory for the guest.\n"; |
| std::cerr << "\t The suffixes 'k', 'M', and 'G' are accepted\n"; |
| std::cerr << "\t--interrupt=[spec] Adds a hardware interrupt mapping to the guest\n"; |
| std::cerr << "\t--ramdisk=[path] Load 'path' as an initial RAM disk\n"; |
| std::cerr << "\t--virtio-balloon Enable virtio-balloon (default)\n"; |
| std::cerr << "\t--virtio-console Enable virtio-console (default)\n"; |
| std::cerr << "\t--virtio-gpu Enable virtio-gpu and virtio-input (default)\n"; |
| std::cerr << "\t--virtio-net Enable virtio-net (default)\n"; |
| std::cerr << "\t--virtio-rng Enable virtio-rng (default)\n"; |
| std::cerr << "\t--virtio-vsock Enable virtio-vsock (default)\n"; |
| std::cerr << "\t--zircon=[path] Load a Zircon kernel from 'path'\n"; |
| std::cerr << "\n"; |
| std::cerr << "BLOCK SPEC\n"; |
| std::cerr << "\n"; |
| std::cerr << " Block devices can be specified by path:\n"; |
| std::cerr << " /pkg/data/disk.img\n"; |
| std::cerr << "\n"; |
| std::cerr << " Additional Options:\n"; |
| std::cerr << " rw/ro: Create a read/write or read-only device.\n"; |
| std::cerr << " fdio: Use the FDIO back-end for the block device.\n"; |
| std::cerr << "\n"; |
| std::cerr << " Ex:\n"; |
| std::cerr << "\n"; |
| std::cerr << " To open a filesystem resource packaged with the guest application\n"; |
| std::cerr << " (read-only is important here as the /pkg/data namespace provides\n"; |
| std::cerr << " read-only view into the package resources):\n"; |
| std::cerr << "\n"; |
| std::cerr << " /pkg/data/system.img,fdio,ro\n"; |
| std::cerr << "\n"; |
| std::cerr << " To specify a block device with a given path and read-write\n"; |
| std::cerr << " permissions\n"; |
| std::cerr << "\n"; |
| std::cerr << " /dev/class/block/000,fdio,rw\n"; |
| std::cerr << "\n"; |
| // clang-format on |
| } |
| |
| static GuestConfigParser::OptionHandler set_option(std::string* out) { |
| return [out](const std::string& key, const std::string& value) { |
| if (value.empty()) { |
| FXL_LOG(ERROR) << "Option: '" << key << "' expects a value (--" << key |
| << "=<value>)"; |
| return ZX_ERR_INVALID_ARGS; |
| } |
| *out = value; |
| return ZX_OK; |
| }; |
| } |
| |
| // A function that converts a string option into a custom type. |
| template <typename T> |
| using OptionParser = std::function<zx_status_t(const std::string& arg, T* out)>; |
| |
| // Handles an option by parsing the value and adding it to a container. |
| template <typename T, typename C> |
| static GuestConfigParser::OptionHandler add_option(C* out, |
| OptionParser<T> parse) { |
| return [out, parse](const std::string& key, const std::string& value) { |
| if (value.empty()) { |
| FXL_LOG(ERROR) << "Option: '" << key << "' expects a value (--" << key |
| << "=<value>)"; |
| return ZX_ERR_INVALID_ARGS; |
| } |
| T t; |
| zx_status_t status = parse(value, &t); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "Failed to parse option string '" << value << "'"; |
| return status; |
| } |
| out->insert(out->end(), t); |
| return ZX_OK; |
| }; |
| } |
| |
| static GuestConfigParser::OptionHandler add_string(std::string* out, |
| const char* delim) { |
| return [out, delim](const std::string& key, const std::string& value) { |
| if (value.empty()) { |
| FXL_LOG(ERROR) << "Option: '" << key << "' expects a value (--" << key |
| << "=<value>)"; |
| return ZX_ERR_INVALID_ARGS; |
| } |
| out->append(delim); |
| out->append(value); |
| return ZX_OK; |
| }; |
| } |
| |
| template <typename NumberType> |
| static zx_status_t parse_number(const std::string& value, NumberType* out, |
| fxl::Base base) { |
| if (!fxl::StringToNumberWithError(value, out, base)) { |
| FXL_LOG(ERROR) << "Unable to convert '" << value << "' into a number"; |
| return ZX_ERR_INVALID_ARGS; |
| } |
| return ZX_OK; |
| } |
| |
| template <typename NumberType> |
| static GuestConfigParser::OptionHandler set_number(NumberType* out) { |
| return [out](const std::string& key, const std::string& value) { |
| if (value.empty()) { |
| FXL_LOG(ERROR) << "Option: '" << key << "' expects a value (--" << key |
| << "=<value>)"; |
| return ZX_ERR_INVALID_ARGS; |
| } |
| return parse_number(value, out, fxl::Base::k10); |
| }; |
| } |
| |
| // Create an |OptionHandler| that sets |out| to a boolean flag. This can be |
| // specified not only as '--foo=true' or '--foo=false', but also as '--foo', in |
| // which case |out| will take the value of |default_flag_value|. |
| static GuestConfigParser::OptionHandler set_flag(bool* out, |
| bool default_flag_value) { |
| return [out, default_flag_value](const std::string& key, |
| const std::string& option_value) { |
| bool flag_value = default_flag_value; |
| if (!option_value.empty()) { |
| if (option_value == "true") { |
| flag_value = default_flag_value; |
| } else if (option_value == "false") { |
| flag_value = !default_flag_value; |
| } else { |
| FXL_LOG(ERROR) << "Option: '" << key |
| << "' expects either 'true' or 'false'; received '" |
| << option_value << "'"; |
| |
| return ZX_ERR_INVALID_ARGS; |
| } |
| } |
| *out = flag_value; |
| return ZX_OK; |
| }; |
| } |
| |
| static zx_status_t parse_block_spec(const std::string& spec, BlockSpec* out) { |
| std::istringstream token_stream(spec); |
| std::string token; |
| while (std::getline(token_stream, token, ',')) { |
| if (token == "fdio") { |
| out->format = fuchsia::guest::BlockFormat::RAW; |
| } else if (token == "qcow") { |
| out->format = fuchsia::guest::BlockFormat::QCOW; |
| } else if (token == "rw") { |
| out->mode = fuchsia::guest::BlockMode::READ_WRITE; |
| } else if (token == "ro") { |
| out->mode = fuchsia::guest::BlockMode::READ_ONLY; |
| } else if (token == "volatile") { |
| out->mode = fuchsia::guest::BlockMode::VOLATILE_WRITE; |
| } else if (token.size() > 0 && token[0] == '/') { |
| out->path = std::move(token); |
| } |
| } |
| if (out->path.empty()) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| return ZX_OK; |
| } |
| |
| static std::vector<std::string> split(const std::string& spec, char delim) { |
| std::istringstream token_stream(spec); |
| std::string token; |
| std::vector<std::string> tokens; |
| while (std::getline(token_stream, token, delim)) { |
| tokens.push_back(token); |
| } |
| return tokens; |
| } |
| |
| static zx_status_t parse_interrupt_spec(const std::string& spec, |
| InterruptSpec* out) { |
| std::vector<std::string> tokens = split(spec, ','); |
| if (tokens.size() != 2) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| zx_status_t status = parse_number(tokens[0], &out->vector, fxl::Base::k10); |
| if (status != ZX_OK) { |
| return status; |
| } |
| return parse_number(tokens[1], &out->options, fxl::Base::k10); |
| } |
| |
| static zx_status_t parse_memory(const std::string& value, size_t* out) { |
| char modifier = 'b'; |
| size_t size; |
| int ret = sscanf(value.c_str(), "%zd%c", &size, &modifier); |
| if (ret < 1) { |
| FXL_LOG(ERROR) << "Value is not a size string: " << value; |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| switch (modifier) { |
| case 'b': |
| break; |
| case 'k': |
| size *= (1 << 10); |
| break; |
| case 'M': |
| size *= (1 << 20); |
| break; |
| case 'G': |
| size *= (1 << 30); |
| break; |
| default: |
| FXL_LOG(ERROR) << "Invalid size modifier " << modifier; |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| *out = size; |
| return ZX_OK; |
| } |
| |
| static zx_status_t parse_memory_spec(const std::string& spec, MemorySpec* out) { |
| out->base = 0; |
| out->policy = MemoryPolicy::GUEST_CACHED; |
| std::vector<std::string> tokens = split(spec, ','); |
| if (tokens.size() == 1) { |
| return parse_memory(tokens[0], &out->size); |
| } |
| if (tokens.size() > 3) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| zx_status_t status = parse_number(tokens[0], &out->base, fxl::Base::k16); |
| if (status != ZX_OK) { |
| return status; |
| } |
| status = parse_memory(tokens[1], &out->size); |
| if (status != ZX_OK) { |
| return status; |
| } |
| if (tokens.size() != 3) { |
| return ZX_OK; |
| } else if (tokens[2] == "cached") { |
| out->policy = MemoryPolicy::HOST_CACHED; |
| } else if (tokens[2] == "device") { |
| out->policy = MemoryPolicy::HOST_DEVICE; |
| } else { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| return ZX_OK; |
| } |
| |
| static GuestConfigParser::OptionHandler set_kernel(std::string* out, |
| Kernel* kernel, |
| Kernel set_kernel) { |
| return [out, kernel, set_kernel](const std::string& key, |
| const std::string& value) { |
| zx_status_t status = set_option(out)(key, value); |
| if (status == ZX_OK) { |
| *kernel = set_kernel; |
| } |
| return status; |
| }; |
| } |
| |
| GuestConfigParser::GuestConfigParser(GuestConfig* cfg) |
| : cfg_(cfg), |
| opts_{ |
| {"block", |
| add_option<BlockSpec>(&cfg_->block_devices_, parse_block_spec)}, |
| {"cmdline-add", add_string(&cfg_->cmdline_, " ")}, |
| {"cmdline", set_option(&cfg_->cmdline_)}, |
| {"cpus", set_number(&cfg_->cpus_)}, |
| {"dtb-overlay", set_option(&cfg_->dtb_overlay_path_)}, |
| {"interrupt", |
| add_option<InterruptSpec>(&cfg_->interrupts_, parse_interrupt_spec)}, |
| {"legacy-net", set_flag(&cfg_->legacy_net_, true)}, |
| {"linux", |
| set_kernel(&cfg_->kernel_path_, &cfg_->kernel_, Kernel::LINUX)}, |
| {"memory", add_option<MemorySpec>(&cfg_->memory_, parse_memory_spec)}, |
| {"ramdisk", set_option(&cfg_->ramdisk_path_)}, |
| {"virtio-balloon", set_flag(&cfg_->virtio_balloon_, true)}, |
| {"virtio-console", set_flag(&cfg_->virtio_console_, true)}, |
| {"virtio-gpu", set_flag(&cfg_->virtio_gpu_, true)}, |
| {"virtio-net", set_flag(&cfg_->virtio_net_, true)}, |
| {"virtio-rng", set_flag(&cfg_->virtio_rng_, true)}, |
| {"virtio-vsock", set_flag(&cfg_->virtio_vsock_, true)}, |
| {"zircon", |
| set_kernel(&cfg_->kernel_path_, &cfg_->kernel_, Kernel::ZIRCON)}, |
| } {} |
| |
| void GuestConfigParser::SetDefaults() { |
| if (cfg_->memory_.empty()) { |
| cfg_->memory_.push_back({.size = 1ul << 30}); |
| } |
| } |
| |
| zx_status_t GuestConfigParser::ParseArgcArgv(int argc, char** argv) { |
| fxl::CommandLine cl = fxl::CommandLineFromArgcArgv(argc, argv); |
| |
| if (cl.positional_args().size() > 0) { |
| FXL_LOG(ERROR) << "Unknown positional option: " << cl.positional_args()[0]; |
| print_usage(cl); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| for (const fxl::CommandLine::Option& option : cl.options()) { |
| auto entry = opts_.find(option.name); |
| if (entry == opts_.end()) { |
| FXL_LOG(ERROR) << "Unknown option --" << option.name; |
| print_usage(cl); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| zx_status_t status = entry->second(option.name, option.value); |
| if (status != ZX_OK) { |
| print_usage(cl); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t GuestConfigParser::ParseConfig(const std::string& data) { |
| rapidjson::Document document; |
| document.Parse(data); |
| if (!document.IsObject()) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| for (auto& member : document.GetObject()) { |
| auto entry = opts_.find(member.name.GetString()); |
| if (entry == opts_.end()) { |
| FXL_LOG(ERROR) << "Unknown field in configuration object: " |
| << member.name.GetString(); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| // For string members, invoke the handler directly on the value. |
| if (member.value.IsString()) { |
| zx_status_t status = |
| entry->second(member.name.GetString(), member.value.GetString()); |
| if (status != ZX_OK) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| continue; |
| } |
| |
| // For array members, invoke the handler on each value in the array. |
| if (member.value.IsArray()) { |
| for (auto& array_member : member.value.GetArray()) { |
| if (!array_member.IsString()) { |
| FXL_LOG(ERROR) << "Array entry has incorect type, expected string: " |
| << member.name.GetString(); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| zx_status_t status = |
| entry->second(member.name.GetString(), array_member.GetString()); |
| if (status != ZX_OK) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| } |
| continue; |
| } |
| FXL_LOG(ERROR) << "Field has incorrect type, expected string or array: " |
| << member.name.GetString(); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| return ZX_OK; |
| } |