| // 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 <zircon/device/block.h> |
| |
| #include "lib/fxl/command_line.h" |
| #include "lib/fxl/logging.h" |
| #include "lib/fxl/strings/string_number_conversions.h" |
| #include "rapidjson/document.h" |
| |
| // 32 hex characters + 4 hyphens. |
| constexpr size_t kGuidStringLen = 36; |
| |
| 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--zircon=[kernel.bin] Load a Zircon kernel from 'kernel.bin'\n"; |
| std::cerr << "\t--linux=[kernel.bin] Load a Linux kernel from 'kernel.bin'\n"; |
| std::cerr << "\t--ramdisk=[ramdisk.bin] Use file 'ramdisk.bin' as an initial RAM disk\n"; |
| std::cerr << "\t--cmdline=[cmdline] Use string 'cmdline' as the kernel command line.\n"; |
| std::cerr << "\t This will overwrite any existing command line\n"; |
| std::cerr << "\t created using --cmdline or --cmdline-append\n"; |
| std::cerr << "\t--cmdline-append=[cmdline] Appends string 'cmdline' to the existing kernel\n"; |
| std::cerr << "\t command line\n"; |
| std::cerr << "\t--dtb_overlay=[overlay.dtb] Load a DTB overlay for a Linux kernel\n"; |
| std::cerr << "\t--block=[block_spec] Adds a block device with the given parameters\n"; |
| std::cerr << "\t--block-wait Wait for block devices (specified by GUID) to\n"; |
| std::cerr << "\t become available instead of failing\n"; |
| std::cerr << "\t--cpus=[cpus] Number of virtual CPUs available to the guest\n"; |
| std::cerr << "\t--memory=[bytes] Allocate 'bytes' of physical memory for the guest.\n"; |
| std::cerr << "\t The suffixes 'k', 'M', and 'G' are accepted\n"; |
| std::cerr << "\t--balloon-demand-page Demand-page balloon deflate requests\n"; |
| std::cerr << "\t--display={scenic, Specify the display backend to use for the guest.\n"; |
| std::cerr << "\t none} 'scenic' (default) will render to a scenic view.\n"; |
| std::cerr << "\t 'none' disables graphical output\n"; |
| std::cerr << "\t--wayland-memory=[bytes] Reserve 'bytes' of device memory for Wayland buffers.\n"; |
| std::cerr << "\t The suffixes 'k', 'M', and 'G' are accepted\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 << " Or by GPT Partition GUID:\n"; |
| std::cerr << " guid:14db42cf-beb7-46a2-9ef8-89b13bb80528,rw\n"; |
| std::cerr << " Or by GPT Partition Type GUID:\n"; |
| std::cerr << " type-guid:4F68BCE3-E8CD-4DB1-96E7-FBCAF984B709,rw\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 save_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 OptionTransform = |
| std::function<zx_status_t(const std::string& arg, T* out)>; |
| |
| // Handles and option by transforming the value and appending it to the |
| // given vector. |
| template <typename T> |
| static GuestConfigParser::OptionHandler append_option( |
| std::vector<T>* out, OptionTransform<T> transform) { |
| return [out, transform](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 = transform(value, &t); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "Failed to parse option string '" << value << "'"; |
| return status; |
| } |
| out->push_back(t); |
| return ZX_OK; |
| }; |
| } |
| |
| static GuestConfigParser::OptionHandler append_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; |
| }; |
| } |
| |
| constexpr size_t kMinMemorySize = 1 << 20; |
| |
| static GuestConfigParser::OptionHandler parse_mem_size(size_t* 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; |
| } |
| 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; |
| } |
| |
| if (size < kMinMemorySize) { |
| FXL_LOG(ERROR) << "Requested memory " << size |
| << " is less than the minimum supported size " |
| << kMinMemorySize; |
| return ZX_ERR_INVALID_ARGS; |
| } |
| *out = size; |
| return ZX_OK; |
| }; |
| } |
| |
| template <typename NumberType> |
| static GuestConfigParser::OptionHandler parse_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; |
| } |
| if (!fxl::StringToNumberWithError(value, out)) { |
| FXL_LOG(ERROR) << "Unable to convert '" << value << "' into a number"; |
| return ZX_ERR_INVALID_ARGS; |
| } |
| return ZX_OK; |
| }; |
| } |
| |
| // 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_guid(const std::string& guid_str, Guid& guid) { |
| if (!guid.empty()) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| if (guid_str.size() != kGuidStringLen) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| int ret = sscanf( |
| guid_str.c_str(), |
| "%2hhx%2hhx%2hhx%2hhx-%2hhx%2hhx-%2hhx%2hhx-%2hhx%2hhx-%2hhx%2hhx%2hhx%" |
| "2hhx%2hhx%2hhx", |
| &guid.bytes[3], &guid.bytes[2], &guid.bytes[1], &guid.bytes[0], |
| &guid.bytes[5], &guid.bytes[4], &guid.bytes[7], &guid.bytes[6], |
| &guid.bytes[8], &guid.bytes[9], &guid.bytes[10], &guid.bytes[11], |
| &guid.bytes[12], &guid.bytes[13], &guid.bytes[14], &guid.bytes[15]); |
| return (ret == 16) ? ZX_OK : ZX_ERR_INVALID_ARGS; |
| } |
| |
| static zx_status_t parse_block_spec(const std::string& spec, BlockSpec* out) { |
| std::string token; |
| std::istringstream tokenStream(spec); |
| while (std::getline(tokenStream, token, ',')) { |
| if (token == "fdio") { |
| out->format = fuchsia::guest::device::BlockFormat::RAW; |
| } else if (token == "qcow") { |
| out->format = fuchsia::guest::device::BlockFormat::QCOW; |
| } else if (token == "rw") { |
| out->mode = fuchsia::guest::device::BlockMode::READ_WRITE; |
| } else if (token == "ro") { |
| out->mode = fuchsia::guest::device::BlockMode::READ_ONLY; |
| } else if (token == "volatile") { |
| out->mode = fuchsia::guest::device::BlockMode::VOLATILE_WRITE; |
| } else if (token.size() > 0 && token[0] == '/') { |
| out->path = std::move(token); |
| } else if (token.compare(0, 5, "guid:") == 0) { |
| std::string guid_str = token.substr(5); |
| zx_status_t status = parse_guid(guid_str, out->guid); |
| if (status != ZX_OK) { |
| return status; |
| } |
| out->guid.type = Guid::Type::GPT_PARTITION; |
| } else if (token.compare(0, 10, "type-guid:") == 0) { |
| std::string guid_str = token.substr(10); |
| zx_status_t status = parse_guid(guid_str, out->guid); |
| if (status != ZX_OK) { |
| return status; |
| } |
| out->guid.type = Guid::Type::GPT_PARTITION_TYPE; |
| } |
| } |
| |
| // Path and GUID are mutually exclusive, but one must be provided. |
| if (out->path.empty() == out->guid.empty()) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| return ZX_OK; |
| } |
| |
| static GuestConfigParser::OptionHandler save_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 = save_option(out)(key, value); |
| if (status == ZX_OK) { |
| *kernel = set_kernel; |
| } |
| return status; |
| }; |
| } |
| |
| static GuestConfigParser::OptionHandler parse_display(GuestDisplay* 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; |
| } |
| if (value == "scenic") { |
| *out = GuestDisplay::SCENIC; |
| } else if (value == "none") { |
| *out = GuestDisplay::NONE; |
| } else { |
| FXL_LOG(ERROR) << "Invalid display value: " << value; |
| return ZX_ERR_INVALID_ARGS; |
| } |
| return ZX_OK; |
| }; |
| } |
| |
| GuestConfigParser::GuestConfigParser(GuestConfig* cfg) |
| : cfg_(cfg), |
| opts_{ |
| {"zircon", |
| save_kernel(&cfg_->kernel_path_, &cfg_->kernel_, Kernel::ZIRCON)}, |
| {"linux", |
| save_kernel(&cfg_->kernel_path_, &cfg_->kernel_, Kernel::LINUX)}, |
| {"ramdisk", save_option(&cfg_->ramdisk_path_)}, |
| {"cmdline", save_option(&cfg_->cmdline_)}, |
| {"cmdline-append", append_string(&cfg_->cmdline_, " ")}, |
| {"dtb_overlay", save_option(&cfg_->dtb_overlay_path_)}, |
| {"block", |
| append_option<BlockSpec>(&cfg_->block_specs_, parse_block_spec)}, |
| {"cpus", parse_number(&cfg_->num_cpus_)}, |
| {"memory", parse_mem_size(&cfg_->memory_)}, |
| {"balloon-demand-page", set_flag(&cfg_->balloon_demand_page_, true)}, |
| {"display", parse_display(&cfg_->display_)}, |
| {"network", set_flag(&cfg_->network_, true)}, |
| {"block-wait", set_flag(&cfg_->block_wait_, true)}, |
| {"wayland-memory", parse_mem_size(&cfg_->wayland_memory_)}, |
| } {} |
| |
| GuestConfigParser::~GuestConfigParser() = default; |
| |
| 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; |
| } |