blob: 75d70afd6d568b2244abb0ba46640efd94378d02 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include <ctype.h>
#include <inttypes.h>
#include <lib/boot-options/boot-options.h>
#include <lib/boot-options/word-view.h>
#include <zircon/compiler.h>
namespace {
#include "enum.h"
}
BootOptions* gBootOptions = nullptr;
using namespace std::string_view_literals;
// This avoids libc++ functions the kernel can't use, and avoids strtoul so as
// not to require NUL termination.
//
// TODO(fxbug.dev/62052): Reconsider the overflow policy below.
std::optional<int64_t> BootOptions::ParseInt(std::string_view value) {
int64_t neg = 1;
if (value.substr(0, 1) == "-") {
neg = -1;
value.remove_prefix(1);
} else if (value.substr(0, 1) == "+") {
value.remove_prefix(1);
}
std::optional<int64_t> result;
auto from_chars = [&](std::string_view prefix, int base, bool trim = false) {
if (value.substr(0, prefix.size()) == prefix) {
if (trim) {
value.remove_prefix(prefix.size());
}
if (value.empty()) {
return false;
}
int64_t result_value = 0;
for (char c : value) {
mul_overflow(result_value, base, &result_value);
switch (c) {
case '0' ... '9':
if (c - '0' >= base) {
return false;
}
add_overflow(result_value, c - '0', &result_value);
break;
case 'a' ... 'f':
if (base != 16) {
return false;
}
add_overflow(result_value, c - 'a' + 10, &result_value);
break;
default:
return false;
}
}
mul_overflow(result_value, neg, &result_value);
result = result_value; // Finally set the result.
return true;
}
return false;
};
from_chars("0x", 16, true) || from_chars("0", 8) || from_chars("", 10);
return result;
}
namespace {
constexpr char kComplainPrefix[] = "kernel";
// This names an arbitrary index for each member. This explicit indirection
// avoids having a constexpr table of string_view constants, which doesn't fly
// for pure PIC.
enum class Index {
#define DEFINE_OPTION(name, type, member, init, doc) member,
#include <lib/boot-options/options.inc>
#undef DEFINE_OPTION
};
// Map Index::member to the name for BootOptions::member. The compiler may
// optimize this back into a table lookup with an array of string_view
// constants, but that optimization is disabled when pure PIC is required.
constexpr std::string_view OptionName(Index idx) {
switch (idx) {
#define DEFINE_OPTION(name, type, member, init, doc) \
case Index::member: \
return name##sv;
#include <lib/boot-options/options.inc>
#undef DEFINE_OPTION
}
return {};
}
// This overload lets the generic lambda below work for both cases.
constexpr std::string_view OptionName(std::string_view name) { return name; }
// Compare option names, using either string_view or Index.
constexpr auto OptionLessThan = [](auto&& a, auto&& b) { return OptionName(a) < OptionName(b); };
constexpr auto CheckSortedNames = [](const auto& names) {
return std::is_sorted(names.begin(), names.end(), OptionLessThan);
};
#if _LIBCPP_STD_VER > 17
#define CONSTEXPR_STD_SORT constexpr
#else
#define CONSTEXPR_STD_SORT const
#endif
// kSortedNames lists Index values in ascending lexicographic order of name.
CONSTEXPR_STD_SORT auto kSortedNames = []() {
std::array names{
#define DEFINE_OPTION(name, type, member, init, doc) Index::member,
#include <lib/boot-options/options.inc>
#undef DEFINE_OPTION
};
// TODO(mcgrathr): C++20 has constexpr std::sort but libc++ doesn't implement
// it yet. Should be:
// std::sort(names.begin(), names.end(), OptionLessThan);
for ([[maybe_unused]] auto& i : names) {
for (auto& j : names) {
if (&j < &names[names.size() - 1] && OptionLessThan((&j)[1], j)) {
std::swap(j, (&j)[1]);
}
}
}
#if _LIBCPP_STD_VER == 17
ZX_ASSERT(CheckSortedNames(names));
#endif
return names;
}();
#if _LIBCPP_STD_VER > 17
static_assert(CheckSortedNames(kSortedNames));
#endif
// Map option name to Index using binary search.
std::optional<Index> FindOption(std::string_view name) {
if (auto it = std::lower_bound(kSortedNames.begin(), kSortedNames.end(), name, OptionLessThan);
it != kSortedNames.end() && name == OptionName(*it)) {
return *it;
}
return std::nullopt;
}
// The length of the longest option name.
CONSTEXPR_STD_SORT size_t kMaxNameLen =
OptionName(*std::max_element(kSortedNames.begin(), kSortedNames.end(), [](Index a, Index b) {
return OptionName(a).size() < OptionName(b).size();
})).size();
template <Index member, typename T>
void ShowOption(const T& value, bool defaults, FILE* out);
#define DEFINE_OPTION(name, type, member, init, doc) \
template <> \
void ShowOption<Index::member, type>(const type& value, bool defaults, FILE* out) { \
const type default_value init; \
BootOptions::Print(OptionName(Index::member), value, out); \
if (defaults) { \
fprintf(out, " (default "); \
BootOptions::Print(OptionName(Index::member), default_value, out); \
fprintf(out, ")\n"); \
} else { \
fprintf(out, "\n"); \
} \
}
#include <lib/boot-options/options.inc>
#undef DEFINE_OPTION
} // namespace
BootOptions::WordResult BootOptions::ParseWord(std::string_view word) {
std::string_view key, value;
if (auto eq = word.find('='); eq == std::string_view::npos) {
// No '=' means the whole word is the key, with an empty value.
key = word;
} else {
key = word.substr(0, eq);
value = word.substr(eq + 1);
}
// Match the key against the known option names.
// Note this leaves the member with its current/default value but still
// returns true when the key was known but the value was unparsable.
if (auto option = FindOption(key)) {
switch (*option) {
#define DEFINE_OPTION(name, type, member, init, doc) \
case Index::member: \
Parse(value, &BootOptions::member); \
break;
#include <lib/boot-options/options.inc>
#undef DEFINE_OPTION
}
return {key, true};
}
return {key, false};
}
void BootOptions::SetMany(std::string_view cmdline, FILE* complain) {
bool verbose = complain != nullptr;
if (!complain) {
complain = stdout;
}
for (auto word : WordView(cmdline)) {
if (auto result = ParseWord(word);
!result.known &&
(verbose ||
result.key.substr(0, std::string_view(kComplainPrefix).size()) == kComplainPrefix)) {
if (result.key.size() > kMaxNameLen) {
fprintf(complain, "NOTE: Unrecognized kernel option %zu characters long (max %zu)\n",
result.key.size(), kMaxNameLen);
} else {
char name[kMaxNameLen + 1];
name[SanitizeString(name, kMaxNameLen, result.key)] = '\0';
fprintf(complain, "WARN: Kernel ignored unrecognized option '%s'\n", name);
}
}
}
}
int BootOptions::Show(std::string_view key, bool defaults, FILE* out) {
if (auto option = FindOption(key)) {
switch (*option) {
#define DEFINE_OPTION(name, type, member, init, doc) \
case Index::member: \
ShowOption<Index::member, type>(member, defaults, out); \
break;
#include <lib/boot-options/options.inc>
#undef DEFINE_OPTION
}
return 0;
}
return -1;
}
void BootOptions::Show(bool defaults, FILE* out) {
#define DEFINE_OPTION(name, type, member, init, doc) \
ShowOption<Index::member, type>(member, defaults, out);
#include <lib/boot-options/options.inc>
#undef DEFINE_OPTION
}
// Helpers for BootOptions::Parse overloads below.
namespace {
template <typename T>
void ParseIntValue(std::string_view value, T& result) {
if (auto parsed = BootOptions::ParseInt(value)) {
result = static_cast<T>(*parsed);
}
}
template <typename... Driver>
struct UartParser {
uart::all::Driver& result_;
template <typename OneDriver>
bool MaybeCreate(std::string_view value) {
if (auto driver = OneDriver::MaybeCreate(value)) {
result_ = std::move(*driver);
return true;
}
return false;
}
bool Parse(std::string_view value) { return (MaybeCreate<Driver>(value) || ... || false); }
};
} // namespace
// Overloads for various types.
void BootOptions::Parse(std::string_view value, bool BootOptions::*member) {
// Any other value, even an empty value, means true.
this->*member = value != "false" && value != "0" && value != "off";
}
void BootOptions::PrintValue(const bool& value, FILE* out) {
fprintf(out, "%s", value ? "true" : "false");
}
void BootOptions::Parse(std::string_view value, uint64_t BootOptions::*member) {
ParseIntValue(value, this->*member);
}
void BootOptions::PrintValue(const uint64_t& value, FILE* out) { fprintf(out, "%#" PRIx64, value); }
void BootOptions::Parse(std::string_view value, uint32_t BootOptions::*member) {
ParseIntValue(value, this->*member);
}
void BootOptions::PrintValue(const uint32_t& value, FILE* out) { fprintf(out, "%#" PRIx32, value); }
void BootOptions::Parse(std::string_view value, uint8_t BootOptions::*member) {
ParseIntValue(value, this->*member);
}
void BootOptions::PrintValue(const uint8_t& value, FILE* out) { fprintf(out, "%#" PRIx8, value); }
void BootOptions::Parse(std::string_view value, SmallString BootOptions::*member) {
SmallString& result = this->*member;
size_t wrote = value.copy(&result[0], result.size());
// In the event of a value of size greater or equal to SmallString's capacity,
// truncate to keep invariant that the string is NUL-terminated.
result[std::min(wrote, result.size() - 1)] = '\0';
}
void BootOptions::PrintValue(const SmallString& value, FILE* out) {
ZX_ASSERT(value.back() == '\0');
fprintf(out, "%s", &value[0]);
}
void BootOptions::Parse(std::string_view value, RedactedHex BootOptions::*member) {
RedactedHex& result = this->*member;
if (std::all_of(value.begin(), value.end(), isxdigit)) {
result.len = value.copy(&result.hex[0], result.hex.size());
Redact(value);
}
}
void BootOptions::PrintValue(const RedactedHex& value, FILE* out) {
if (value.len > 0) {
fprintf(out, "<redacted.%zu.hex.chars>", value.len);
}
}
void BootOptions::Parse(std::string_view value, uart::all::Driver BootOptions::*member) {
if (!uart::all::WithAllDrivers<UartParser>{this->*member}.Parse(value)) {
// Probably has nowhere to go, but anyway.
printf("WARN: Unrecognized serial console setting '%.*s' ignored\n",
static_cast<int>(value.size()), value.data());
}
}
void BootOptions::PrintValue(const uart::all::Driver& value, FILE* out) {
std::visit([out](const auto& uart) { uart.Unparse(out); }, value);
}
void BootOptions::Parse(std::string_view value, OomBehavior BootOptions::*member) {
Enum<OomBehavior>(EnumParser{value, &(this->*member)});
}
void BootOptions::PrintValue(const OomBehavior& value, FILE* out) {
Enum<OomBehavior>(EnumPrinter{value, out});
}
#if BOOT_OPTIONS_TESTONLY_OPTIONS
void BootOptions::Parse(std::string_view value, TestEnum BootOptions::*member) {
Enum<TestEnum>(EnumParser{value, &(this->*member)});
}
void BootOptions::PrintValue(const TestEnum& value, FILE* out) {
Enum<TestEnum>(EnumPrinter{value, out});
}
void BootOptions::Parse(std::string_view value, TestStruct BootOptions::*member) {
if (value == "test") {
(this->*member).present = true;
} else {
printf("WARN: Ignored unknown value '%.*s' for test option\n", static_cast<int>(value.size()),
value.data());
}
}
void BootOptions::PrintValue(const TestStruct& value, FILE* out) { fprintf(out, "test"); }
#endif // BOOT_OPTIONS_TESTONLY_OPTIONS
#if BOOT_OPTIONS_GENERATOR || defined(__x86_64__)
void BootOptions::Parse(std::string_view value, IntelHwpPolicy BootOptions::*member) {
Enum<IntelHwpPolicy>(EnumParser{value, &(this->*member)});
}
void BootOptions::PrintValue(const IntelHwpPolicy& value, FILE* out) {
Enum<IntelHwpPolicy>(EnumPrinter{value, out});
}
#endif // BOOT_OPTIONS_GENERATOR || defined(__x86_64__)