| // 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__) |