blob: 24efa28b6516031e29dc4bd437eb33b06641b6ba [file] [log] [blame]
// 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 <functional>
#include <iostream>
#include <unordered_map>
#include <utility>
#include <rapidjson/document.h>
namespace guest_config {
namespace {
using fuchsia::virtualization::GuestConfig;
zx_status_t parse(const OpenAt& open_at, 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 == "file") {
if (!out->format.has_invalid_tag()) {
return ZX_ERR_INVALID_ARGS;
}
fidl::InterfaceHandle<fuchsia::io::File> file;
if (zx_status_t status = open_at(path, file.NewRequest()); status != ZX_OK) {
return status;
}
out->format.set_file(std::move(file));
} else if (token == "qcow") {
if (!out->format.has_invalid_tag()) {
return ZX_ERR_INVALID_ARGS;
}
fidl::InterfaceHandle<fuchsia::io::File> file;
if (zx_status_t status = open_at(path, file.NewRequest()); status != ZX_OK) {
return status;
}
out->format.set_qcow(file.TakeChannel());
} else if (token == "block") {
if (!out->format.has_invalid_tag()) {
return ZX_ERR_INVALID_ARGS;
}
fidl::InterfaceHandle<fuchsia::hardware::block::Block> block;
if (zx_status_t status = open_at(
path, fidl::InterfaceRequest<fuchsia::io::File>(block.NewRequest().TakeChannel()));
status != ZX_OK) {
return status;
}
out->format.set_block(std::move(block));
} 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;
}
if (out->format.has_invalid_tag()) {
fidl::InterfaceHandle<fuchsia::io::File> file;
if (zx_status_t status = open_at(path, file.NewRequest()); status != ZX_OK) {
return status;
}
out->format.set_file(std::move(file));
}
return ZX_OK;
}
template <typename T>
zx_status_t parse(const rapidjson::Value& value, T* result) {
*result = value.Get<T>();
return ZX_OK;
}
template <>
zx_status_t parse<uint8_t>(const rapidjson::Value& value, uint8_t* result) {
unsigned int v = value.GetUint();
if (v > std::numeric_limits<uint8_t>::max()) {
FX_LOGS(ERROR) << "Value too large: " << v;
return ZX_ERR_INVALID_ARGS;
}
*result = static_cast<uint8_t>(value.GetUint());
return ZX_OK;
}
zx_status_t parse_mem(const rapidjson::Value& value, uint64_t* result) {
char modifier = 'b';
uint64_t size;
if (!value.IsString()) {
FX_LOGS(ERROR) << "Value is not a string";
return ZX_ERR_INVALID_ARGS;
}
int ret = sscanf(value.GetString(), "%" SCNu64 "%c", &size, &modifier);
if (ret < 1) {
FX_LOGS(ERROR) << "Value is not a size string: " << value.GetString();
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;
}
*result = size;
return ZX_OK;
}
class OptionHandler {
public:
OptionHandler() = default;
virtual zx_status_t Set(GuestConfig* cfg, const std::string& name,
const rapidjson::Value& value) = 0;
virtual ~OptionHandler() = default;
};
template <typename T>
class SimpleOptionHandler : public OptionHandler {
public:
SimpleOptionHandler(std::function<T*(GuestConfig*)> mutable_field)
: OptionHandler(), mutable_field_{std::move(mutable_field)} {}
protected:
void FillMutableField(const T& value, GuestConfig* cfg) { *(mutable_field_(cfg)) = value; }
zx_status_t Set(GuestConfig* cfg, const std::string& name,
const rapidjson::Value& value) override {
T result;
zx_status_t status = parse(value, &result);
if (status == ZX_OK) {
FillMutableField(result, cfg);
}
return status;
}
private:
std::function<T*(GuestConfig*)> mutable_field_;
};
class MemorySizeOptionHandler : public SimpleOptionHandler<uint64_t> {
public:
MemorySizeOptionHandler(std::function<uint64_t*(GuestConfig*)> mutable_field)
: SimpleOptionHandler<uint64_t>(std::move(mutable_field)) {}
zx_status_t Set(GuestConfig* cfg, const std::string& name,
const rapidjson::Value& value) override {
uint64_t result;
auto status = parse_mem(value, &result);
if (status == ZX_OK) {
FillMutableField(result, cfg);
}
return status;
}
};
using NumCpusOptionHandler = SimpleOptionHandler<uint8_t>;
using BoolOptionHandler = SimpleOptionHandler<bool>;
using StringOptionHandler = SimpleOptionHandler<std::string>;
class FileOptionHandler : public OptionHandler {
public:
FileOptionHandler(OpenAt open_at,
std::function<fuchsia::io::FileHandle*(GuestConfig*)> mutable_field)
: OptionHandler(), open_at_{std::move(open_at)}, mutable_field_{std::move(mutable_field)} {}
protected:
zx_status_t Set(GuestConfig* cfg, const std::string& name,
const rapidjson::Value& value) override {
if (value.GetStringLength() == 0) {
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.GetString(), 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<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(mutable_field)},
mutable_type_fn_{std::move(mutable_type_fn)},
type_{type} {}
private:
zx_status_t Set(GuestConfig* cfg, const std::string& name,
const rapidjson::Value& value) override {
auto status = FileOptionHandler::Set(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 Set(GuestConfig* cfg, const std::string& name,
const rapidjson::Value& value) override {
if (value.GetStringLength() == 0) {
FX_LOGS(ERROR) << "Option: '" << name << "' expects a value (--" << name << "=<value>)";
return ZX_ERR_INVALID_ARGS;
}
T result{};
auto status = parse(value, &result);
if (status == ZX_OK) {
mutable_field_(cfg)->emplace_back(result);
}
return status;
}
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 Set(GuestConfig* cfg, const std::string& name,
const rapidjson::Value& value) override {
if (value.GetStringLength() == 0) {
FX_LOGS(ERROR) << "Option: '" << name << "' expects a value (--" << name << "=<value>)";
return ZX_ERR_INVALID_ARGS;
}
fuchsia::virtualization::BlockSpec result;
auto status = parse(open_at_, value.GetString(), &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>> GetAllOptionHandlers(
OpenAt open_at) {
std::unordered_map<std::string, std::unique_ptr<OptionHandler>> handlers;
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::mutable_cmdline));
handlers.emplace("dtb-overlay", std::make_unique<FileOptionHandler>(
open_at.share(), &GuestConfig::mutable_dtb_overlay));
handlers.emplace(
"linux", std::make_unique<KernelOptionHandler>(open_at.share(), &GuestConfig::mutable_kernel,
&GuestConfig::mutable_kernel_type,
fuchsia::virtualization::KernelType::LINUX));
handlers.emplace("ramdisk", std::make_unique<FileOptionHandler>(open_at.share(),
&GuestConfig::mutable_ramdisk));
handlers.emplace(
"zircon", std::make_unique<KernelOptionHandler>(open_at.share(), &GuestConfig::mutable_kernel,
&GuestConfig::mutable_kernel_type,
fuchsia::virtualization::KernelType::ZIRCON));
handlers.emplace("cmdline-add", std::make_unique<RepeatedOptionHandler<std::string>>(
&GuestConfig::mutable_cmdline_add));
handlers.emplace("memory",
std::make_unique<MemorySizeOptionHandler>(&GuestConfig::mutable_guest_memory));
handlers.emplace("cpus", std::make_unique<NumCpusOptionHandler>(&GuestConfig::mutable_cpus));
handlers.emplace("default-net",
std::make_unique<BoolOptionHandler>(&GuestConfig::mutable_default_net));
handlers.emplace("virtio-balloon",
std::make_unique<BoolOptionHandler>(&GuestConfig::mutable_virtio_balloon));
handlers.emplace("virtio-console",
std::make_unique<BoolOptionHandler>(&GuestConfig::mutable_virtio_console));
handlers.emplace("virtio-gpu",
std::make_unique<BoolOptionHandler>(&GuestConfig::mutable_virtio_gpu));
handlers.emplace("virtio-rng",
std::make_unique<BoolOptionHandler>(&GuestConfig::mutable_virtio_rng));
handlers.emplace("virtio-sound",
std::make_unique<BoolOptionHandler>(&GuestConfig::mutable_virtio_sound));
handlers.emplace("virtio-sound-input",
std::make_unique<BoolOptionHandler>(&GuestConfig::mutable_virtio_sound_input));
handlers.emplace("virtio-vsock",
std::make_unique<BoolOptionHandler>(&GuestConfig::mutable_virtio_vsock));
handlers.emplace("virtio-mem",
std::make_unique<BoolOptionHandler>(&GuestConfig::mutable_virtio_mem));
handlers.emplace("virtio-mem-block-size", std::make_unique<MemorySizeOptionHandler>(
&GuestConfig::mutable_virtio_mem_block_size));
handlers.emplace("virtio-mem-region-size", std::make_unique<MemorySizeOptionHandler>(
&GuestConfig::mutable_virtio_mem_region_size));
handlers.emplace(
"virtio-mem-region-alignment",
std::make_unique<MemorySizeOptionHandler>(&GuestConfig::mutable_virtio_mem_region_alignment));
return handlers;
}
} // namespace
zx::result<GuestConfig> ParseConfig(const std::string& data, OpenAt open_at) {
rapidjson::Document document;
document.Parse(data);
if (!document.IsObject()) {
return zx::error(ZX_ERR_INVALID_ARGS);
}
GuestConfig cfg;
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::error(ZX_ERR_INVALID_ARGS);
}
zx_status_t status = ZX_OK;
if (member.value.IsArray()) {
// for array members, invoke the handler on each value in the array.
for (auto& array_member : member.value.GetArray()) {
status = entry->second->Set(&cfg, member.name.GetString(), array_member);
}
} else {
// For atomic members, invoke the handler directly on the value.
status = entry->second->Set(&cfg, member.name.GetString(), member.value);
}
if (status != ZX_OK) {
return zx::error(ZX_ERR_INVALID_ARGS);
}
}
return zx::ok(std::move(cfg));
}
fuchsia::virtualization::GuestConfig MergeConfigs(fuchsia::virtualization::GuestConfig base,
fuchsia::virtualization::GuestConfig overrides) {
#define COPY_GUEST_CONFIG_FIELD(field_name) \
do { \
if (overrides.has_##field_name()) { \
base.set_##field_name(std::move(*overrides.mutable_##field_name())); \
} \
} while (0)
#define APPEND_GUEST_CONFIG_FIELD(field_name) \
do { \
if (overrides.has_##field_name()) { \
base.mutable_##field_name()->insert( \
base.mutable_##field_name()->end(), \
std::make_move_iterator(overrides.mutable_##field_name()->begin()), \
std::make_move_iterator(overrides.mutable_##field_name()->end())); \
} \
} while (0)
COPY_GUEST_CONFIG_FIELD(kernel_type);
COPY_GUEST_CONFIG_FIELD(kernel);
COPY_GUEST_CONFIG_FIELD(ramdisk);
COPY_GUEST_CONFIG_FIELD(dtb_overlay);
COPY_GUEST_CONFIG_FIELD(cmdline);
APPEND_GUEST_CONFIG_FIELD(cmdline_add);
COPY_GUEST_CONFIG_FIELD(cpus);
COPY_GUEST_CONFIG_FIELD(guest_memory);
APPEND_GUEST_CONFIG_FIELD(block_devices);
APPEND_GUEST_CONFIG_FIELD(net_devices);
COPY_GUEST_CONFIG_FIELD(wayland_device);
COPY_GUEST_CONFIG_FIELD(magma_device);
COPY_GUEST_CONFIG_FIELD(default_net);
COPY_GUEST_CONFIG_FIELD(virtio_mem);
COPY_GUEST_CONFIG_FIELD(virtio_mem_block_size);
COPY_GUEST_CONFIG_FIELD(virtio_mem_region_size);
COPY_GUEST_CONFIG_FIELD(virtio_mem_region_alignment);
COPY_GUEST_CONFIG_FIELD(virtio_balloon);
COPY_GUEST_CONFIG_FIELD(virtio_console);
COPY_GUEST_CONFIG_FIELD(virtio_gpu);
COPY_GUEST_CONFIG_FIELD(virtio_rng);
COPY_GUEST_CONFIG_FIELD(virtio_vsock);
COPY_GUEST_CONFIG_FIELD(virtio_sound);
COPY_GUEST_CONFIG_FIELD(virtio_sound_input);
APPEND_GUEST_CONFIG_FIELD(vsock_listeners);
#undef COPY_GUEST_CONFIG_FIELD
#undef APPEND_GUEST_CONFIG_FIELD
return base;
}
} // namespace guest_config