| // 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 "src/virtualization/lib/guest_config/guest_config.h" |
| |
| #include <lib/syslog/cpp/macros.h> |
| #include <libgen.h> |
| #include <unistd.h> |
| #include <zircon/device/block.h> |
| |
| #include <functional> |
| #include <iostream> |
| #include <unordered_map> |
| #include <utility> |
| |
| #include <rapidjson/document.h> |
| |
| #include "src/lib/fxl/command_line.h" |
| #include "src/lib/fxl/strings/string_number_conversions.h" |
| |
| namespace guest_config { |
| |
| namespace { |
| |
| using fuchsia::virtualization::GuestConfig; |
| |
| constexpr uint64_t kDefaultMemory = 1ul << 30; |
| |
| // This is a locally administered MAC address (first byte 0x02) mixed with the |
| // Google Organizationally Unique Identifier (00:1a:11). The host gets ff:ff:ff |
| // and the guest gets 00:00:00 for the last three octets. |
| constexpr fuchsia::hardware::ethernet::MacAddress kGuestMacAddress = { |
| .octets = {0x02, 0x1a, 0x11, 0x00, 0x01, 0x00}, |
| }; |
| |
| zx_status_t parse(const std::string& name, const std::string& value, bool* result) { |
| if (value.empty() || value == "true") { |
| *result = true; |
| } else if (value == "false") { |
| *result = false; |
| } else { |
| FX_LOGS(ERROR) << "Option '" << name << "' expects either 'true' or 'false'; received '" |
| << value << "'"; |
| return ZX_ERR_INVALID_ARGS; |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t parse(const std::string& name, const std::string& value, std::string* result) { |
| *result = value; |
| return ZX_OK; |
| } |
| |
| template <typename NumberType, fxl::Base base = fxl::Base::k10, |
| typename = std::enable_if<std::is_integral<NumberType>::value>> |
| zx_status_t parse(const std::string& name, const std::string& value, NumberType* out) { |
| if (!fxl::StringToNumberWithError(value, out, base)) { |
| FX_LOGS(ERROR) << "Option '" << name << "': Unable to convert '" << value << "' into a number"; |
| return ZX_ERR_INVALID_ARGS; |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t parse(const OpenAt& open_at, const std::string& name, const std::string& value, |
| fuchsia::virtualization::BlockSpec* out) { |
| std::string path; |
| std::istringstream token_stream(value); |
| std::string token; |
| while (std::getline(token_stream, token, ',')) { |
| if (token == "rw") { |
| out->mode = fuchsia::virtualization::BlockMode::READ_WRITE; |
| } else if (token == "ro") { |
| out->mode = fuchsia::virtualization::BlockMode::READ_ONLY; |
| } else if (token == "volatile") { |
| out->mode = fuchsia::virtualization::BlockMode::VOLATILE_WRITE; |
| } else if (token == "fdio") { |
| out->format = fuchsia::virtualization::BlockFormat::RAW; |
| } else if (token == "qcow") { |
| out->format = fuchsia::virtualization::BlockFormat::QCOW; |
| } else { |
| // Set the last MAX_BLOCK_DEVICE_ID characters of token as the ID. |
| size_t pos = token.size() > fuchsia::virtualization::MAX_BLOCK_DEVICE_ID |
| ? token.size() - fuchsia::virtualization::MAX_BLOCK_DEVICE_ID |
| : 0; |
| out->id = token.substr(pos, fuchsia::virtualization::MAX_BLOCK_DEVICE_ID); |
| path = std::move(token); |
| } |
| } |
| if (path.empty()) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| return open_at(path, out->file.NewRequest()); |
| } |
| |
| 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; |
| } |
| |
| 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) { |
| FX_LOGS(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: |
| FX_LOGS(ERROR) << "Invalid size modifier " << modifier; |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| *out = size; |
| return ZX_OK; |
| } |
| |
| zx_status_t parse(const std::string& name, const std::string& value, |
| fuchsia::virtualization::MemorySpec* out) { |
| out->base = 0; |
| out->policy = fuchsia::virtualization::MemoryPolicy::GUEST_CACHED; |
| std::vector<std::string> tokens = split(value, ','); |
| 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<uint64_t, fxl::Base::k16>(name, tokens[0], &out->base); |
| 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 = fuchsia::virtualization::MemoryPolicy::HOST_CACHED; |
| } else if (tokens[2] == "device") { |
| out->policy = fuchsia::virtualization::MemoryPolicy::HOST_DEVICE; |
| } else { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| return ZX_OK; |
| } |
| |
| class OptionHandler { |
| public: |
| OptionHandler() : has_field_{[](const GuestConfig&) { return false; }} {} |
| OptionHandler(std::function<bool(const GuestConfig&)> has_field) : has_field_{has_field} {} |
| |
| zx_status_t Set(GuestConfig* cfg, const std::string& name, const std::string& value, |
| bool override) { |
| if (override || !has_field_(*cfg)) { |
| return SetInternal(cfg, name, value); |
| } |
| return ZX_OK; |
| } |
| |
| void MaybeSetDefault(GuestConfig* cfg) { |
| if (!has_field_(*cfg)) { |
| SetDefault(cfg); |
| } |
| } |
| |
| virtual ~OptionHandler() = default; |
| |
| protected: |
| virtual zx_status_t SetInternal(GuestConfig* cfg, const std::string& name, |
| const std::string& value) = 0; |
| virtual void SetDefault(GuestConfig* cfg) {} |
| |
| std::function<bool(const GuestConfig&)> has_field_; |
| }; |
| |
| template <typename T, bool require_value = true> |
| class OptionHandlerWithoutDefaultValue : public OptionHandler { |
| public: |
| OptionHandlerWithoutDefaultValue(std::function<bool(const GuestConfig&)> has_field, |
| std::function<T*(GuestConfig*)> mutable_field) |
| : OptionHandler{has_field}, mutable_field_{std::move(mutable_field)} {} |
| |
| protected: |
| void FillMutableField(const T& value, GuestConfig* cfg) { *(mutable_field_(cfg)) = value; } |
| |
| zx_status_t SetInternal(GuestConfig* cfg, const std::string& name, |
| const std::string& value) override { |
| if (require_value && value.empty()) { |
| FX_LOGS(ERROR) << "Option: '" << name << "' expects a value (--" << name << "=<value>)"; |
| return ZX_ERR_INVALID_ARGS; |
| } |
| T result; |
| auto status = parse(name, value, &result); |
| if (status == ZX_OK) { |
| FillMutableField(result, cfg); |
| } |
| return status; |
| } |
| |
| private: |
| std::function<T*(GuestConfig*)> mutable_field_; |
| }; |
| |
| template <typename T, bool require_value = true> |
| class OptionHandlerWithDefaultValue : public OptionHandlerWithoutDefaultValue<T, require_value> { |
| public: |
| OptionHandlerWithDefaultValue(std::function<bool(const GuestConfig&)> has_field, |
| std::function<T*(GuestConfig*)> mutable_field, |
| const T& default_value) |
| : OptionHandlerWithoutDefaultValue<T, require_value>{std::move(has_field), |
| std::move(mutable_field)}, |
| default_value_{default_value} {} |
| |
| protected: |
| void SetDefault(GuestConfig* cfg) override { |
| OptionHandlerWithoutDefaultValue<T, require_value>::FillMutableField(default_value_, cfg); |
| } |
| |
| const T default_value_; |
| }; |
| |
| using NumCpusOptionHandler = OptionHandlerWithDefaultValue<uint8_t>; |
| using BoolOptionHandler = OptionHandlerWithDefaultValue<bool, /* require_value */ false>; |
| using StringOptionHandler = OptionHandlerWithoutDefaultValue<std::string>; |
| |
| class FileOptionHandler : public OptionHandler { |
| public: |
| FileOptionHandler(OpenAt open_at, std::function<bool(const GuestConfig&)> has_field, |
| std::function<fuchsia::io::FileHandle*(GuestConfig*)> mutable_field) |
| : OptionHandler{has_field}, |
| open_at_{std::move(open_at)}, |
| mutable_field_{std::move(mutable_field)} {} |
| |
| protected: |
| zx_status_t SetInternal(GuestConfig* cfg, const std::string& name, |
| const std::string& value) override { |
| if (value.empty()) { |
| FX_LOGS(ERROR) << "Option: '" << name << "' expects a value (--" << name << "=<value>)"; |
| return ZX_ERR_INVALID_ARGS; |
| } |
| fuchsia::io::FileHandle result; |
| zx_status_t status = open_at_(value, result.NewRequest()); |
| if (status == ZX_OK) { |
| *(mutable_field_(cfg)) = std::move(result); |
| } |
| return status; |
| } |
| |
| private: |
| OpenAt open_at_; |
| std::function<fuchsia::io::FileHandle*(GuestConfig*)> mutable_field_; |
| }; |
| |
| class KernelOptionHandler : public FileOptionHandler { |
| public: |
| KernelOptionHandler( |
| OpenAt open_at, std::function<bool(const GuestConfig&)> has_field, |
| std::function<fuchsia::io::FileHandle*(GuestConfig*)> mutable_field, |
| std::function<fuchsia::virtualization::KernelType*(GuestConfig*)> mutable_type_fn, |
| fuchsia::virtualization::KernelType type) |
| : FileOptionHandler{std::move(open_at), std::move(has_field), std::move(mutable_field)}, |
| mutable_type_fn_{std::move(mutable_type_fn)}, |
| type_{type} {} |
| |
| private: |
| zx_status_t SetInternal(GuestConfig* cfg, const std::string& name, |
| const std::string& value) override { |
| auto status = FileOptionHandler::SetInternal(cfg, name, value); |
| if (status == ZX_OK) { |
| *(mutable_type_fn_(cfg)) = type_; |
| } |
| return status; |
| } |
| |
| std::function<fuchsia::virtualization::KernelType*(GuestConfig*)> mutable_type_fn_; |
| fuchsia::virtualization::KernelType type_; |
| }; |
| |
| template <typename T> |
| class RepeatedOptionHandler : public OptionHandler { |
| public: |
| RepeatedOptionHandler(std::function<std::vector<T>*(GuestConfig*)> mutable_field) |
| : mutable_field_{std::move(mutable_field)} {} |
| |
| protected: |
| zx_status_t SetInternal(GuestConfig* cfg, const std::string& name, |
| const std::string& value) override { |
| if (value.empty()) { |
| FX_LOGS(ERROR) << "Option: '" << name << "' expects a value (--" << name << "=<value>)"; |
| return ZX_ERR_INVALID_ARGS; |
| } |
| T result{}; |
| auto status = parse(name, value, &result); |
| if (status == ZX_OK) { |
| mutable_field_(cfg)->emplace_back(result); |
| } |
| return status; |
| } |
| |
| void SetDefault(GuestConfig* cfg) override { |
| mutable_field_(cfg); // Fills it with an empty vector. |
| } |
| |
| private: |
| std::function<std::vector<T>*(GuestConfig*)> mutable_field_; |
| }; |
| |
| template <> |
| class RepeatedOptionHandler<fuchsia::virtualization::BlockSpec> : public OptionHandler { |
| public: |
| RepeatedOptionHandler( |
| OpenAt open_at, |
| std::function<std::vector<fuchsia::virtualization::BlockSpec>*(GuestConfig*)> mutable_field) |
| : open_at_(std::move(open_at)), mutable_field_{std::move(mutable_field)} {} |
| |
| protected: |
| zx_status_t SetInternal(GuestConfig* cfg, const std::string& name, |
| const std::string& value) override { |
| if (value.empty()) { |
| FX_LOGS(ERROR) << "Option: '" << name << "' expects a value (--" << name << "=<value>)"; |
| return ZX_ERR_INVALID_ARGS; |
| } |
| fuchsia::virtualization::BlockSpec result; |
| auto status = parse(open_at_, name, value, &result); |
| if (status == ZX_OK) { |
| mutable_field_(cfg)->emplace_back(std::move(result)); |
| } |
| return status; |
| } |
| |
| private: |
| OpenAt open_at_; |
| std::function<std::vector<fuchsia::virtualization::BlockSpec>*(GuestConfig*)> mutable_field_; |
| }; |
| |
| std::unordered_map<std::string, std::unique_ptr<OptionHandler>> GetCmdlineOptionHanders() { |
| std::unordered_map<std::string, std::unique_ptr<OptionHandler>> handlers; |
| handlers.emplace("cmdline-add", std::make_unique<RepeatedOptionHandler<std::string>>( |
| &GuestConfig::mutable_cmdline_add)); |
| handlers.emplace( |
| "cpus", std::make_unique<NumCpusOptionHandler>( |
| &GuestConfig::has_cpus, &GuestConfig::mutable_cpus, zx_system_get_num_cpus())); |
| handlers.emplace("interrupt", std::make_unique<RepeatedOptionHandler<uint32_t>>( |
| &GuestConfig::mutable_interrupts)); |
| handlers.emplace("memory", |
| std::make_unique<RepeatedOptionHandler<fuchsia::virtualization::MemorySpec>>( |
| &GuestConfig::mutable_memory)); |
| handlers.emplace("default-net", |
| std::make_unique<BoolOptionHandler>(&GuestConfig::has_default_net, |
| &GuestConfig::mutable_default_net, true)); |
| handlers.emplace("virtio-balloon", |
| std::make_unique<BoolOptionHandler>(&GuestConfig::has_virtio_balloon, |
| &GuestConfig::mutable_virtio_balloon, true)); |
| handlers.emplace("virtio-console", |
| std::make_unique<BoolOptionHandler>(&GuestConfig::has_virtio_console, |
| &GuestConfig::mutable_virtio_console, true)); |
| handlers.emplace("virtio-gpu", |
| std::make_unique<BoolOptionHandler>(&GuestConfig::has_virtio_gpu, |
| &GuestConfig::mutable_virtio_gpu, true)); |
| handlers.emplace("virtio-rng", |
| std::make_unique<BoolOptionHandler>(&GuestConfig::has_virtio_rng, |
| &GuestConfig::mutable_virtio_rng, true)); |
| handlers.emplace("virtio-vsock", |
| std::make_unique<BoolOptionHandler>(&GuestConfig::has_virtio_vsock, |
| &GuestConfig::mutable_virtio_vsock, true)); |
| return handlers; |
| } |
| |
| std::unordered_map<std::string, std::unique_ptr<OptionHandler>> GetAllOptionHandlers( |
| OpenAt open_at) { |
| auto handlers = GetCmdlineOptionHanders(); |
| handlers.emplace("block", |
| std::make_unique<RepeatedOptionHandler<fuchsia::virtualization::BlockSpec>>( |
| open_at.share(), &GuestConfig::mutable_block_devices)); |
| handlers.emplace("cmdline", std::make_unique<StringOptionHandler>(&GuestConfig::has_cmdline, |
| &GuestConfig::mutable_cmdline)); |
| handlers.emplace("dtb-overlay", std::make_unique<FileOptionHandler>( |
| open_at.share(), &GuestConfig::has_dtb_overlay, |
| &GuestConfig::mutable_dtb_overlay)); |
| handlers.emplace( |
| "linux", std::make_unique<KernelOptionHandler>( |
| open_at.share(), &GuestConfig::has_kernel, &GuestConfig::mutable_kernel, |
| &GuestConfig::mutable_kernel_type, fuchsia::virtualization::KernelType::LINUX)); |
| handlers.emplace("ramdisk", |
| std::make_unique<FileOptionHandler>(open_at.share(), &GuestConfig::has_ramdisk, |
| &GuestConfig::mutable_ramdisk)); |
| handlers.emplace("zircon", std::make_unique<KernelOptionHandler>( |
| open_at.share(), &GuestConfig::has_kernel, |
| &GuestConfig::mutable_kernel, &GuestConfig::mutable_kernel_type, |
| fuchsia::virtualization::KernelType::ZIRCON)); |
| return handlers; |
| } |
| |
| } // namespace |
| |
| void PrintCommandLineUsage(const char* program_name) { |
| // clang-format off |
| std::cerr << "usage: " << program_name << " [OPTIONS]\n"; |
| std::cerr << "\n"; |
| std::cerr << "OPTIONS:\n"; |
| std::cerr << "\t--cmdline-add=[string] Adds 'string' to the existing kernel command line.\n"; |
| std::cerr << "\t--cpus=[number] Number of virtual CPUs available to the guest\n"; |
| std::cerr << "\t--default-net Enable a default net device (defaults to true)\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 (default " << kDefaultMemory << " bytes)\n"; |
| std::cerr << "\t--interrupt=[spec] Adds a hardware interrupt mapping to the guest\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-rng Enable virtio-rng (default)\n"; |
| std::cerr << "\t--virtio-vsock Enable virtio-vsock (default)\n"; |
| std::cerr << "\n"; |
| std::cerr << "NET SPEC\n"; |
| std::cerr << "\n"; |
| std::cerr << " Net devices can be specified by MAC address. Each --net argument specifies an\n"; |
| std::cerr << " additional device.\n"; |
| std::cerr << "\n"; |
| std::cerr << " Ex:\n"; |
| std::cerr << " --net=02:1a:11:00:00:00\n"; |
| std::cerr << "\n"; |
| std::cerr << " By default the guest is configured with one net device with the MAC address in\n"; |
| std::cerr << " the example above. To remove the default device pass --default-net=false.\n"; |
| std::cerr << "\n"; |
| // clang-format on |
| } |
| |
| void SetDefaults(GuestConfig* cfg) { |
| if (!cfg->has_memory()) { |
| cfg->mutable_memory()->push_back({.size = kDefaultMemory}); |
| } |
| |
| for (const auto& [name, handler] : GetCmdlineOptionHanders()) { |
| handler->MaybeSetDefault(cfg); |
| } |
| |
| if (cfg->default_net()) { |
| cfg->mutable_net_devices()->push_back({ |
| .mac_address = kGuestMacAddress, |
| // TODO(https://fxbug.dev/67566): Enable once bridging is fixed. |
| .enable_bridge = false, |
| }); |
| } |
| } |
| |
| zx_status_t ParseArguments(int argc, const char** argv, fuchsia::virtualization::GuestConfig* cfg) { |
| fxl::CommandLine cl = fxl::CommandLineFromArgcArgv(argc, argv); |
| |
| if (cl.positional_args().size() > 0) { |
| FX_LOGS(ERROR) << "Unknown positional option: " << cl.positional_args()[0]; |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| auto handlers = GetCmdlineOptionHanders(); |
| for (const fxl::CommandLine::Option& option : cl.options()) { |
| auto entry = handlers.find(option.name); |
| if (entry == handlers.end()) { |
| FX_LOGS(ERROR) << "Unknown option --" << option.name; |
| return ZX_ERR_INVALID_ARGS; |
| } |
| auto status = entry->second->Set(cfg, option.name, option.value, /* override= */ true); |
| if (status != ZX_OK) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t ParseConfig(const std::string& data, OpenAt open_at, GuestConfig* cfg) { |
| rapidjson::Document document; |
| document.Parse(data); |
| if (!document.IsObject()) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| auto opts = GetAllOptionHandlers(std::move(open_at)); |
| for (auto& member : document.GetObject()) { |
| auto entry = opts.find(member.name.GetString()); |
| if (entry == opts.end()) { |
| FX_LOGS(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->Set(cfg, member.name.GetString(), |
| member.value.GetString(), /* override= */ false); |
| 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()) { |
| FX_LOGS(ERROR) << "Array entry has incorect type, expected string: " |
| << member.name.GetString(); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| zx_status_t status = entry->second->Set(cfg, member.name.GetString(), |
| array_member.GetString(), /* override= */ true); |
| if (status != ZX_OK) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| } |
| continue; |
| } |
| FX_LOGS(ERROR) << "Field has incorrect type, expected string or array: " |
| << member.name.GetString(); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| return ZX_OK; |
| } |
| |
| } // namespace guest_config |