| // Copyright 2023 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "libbrillo/brillo/kernel_config_utils.h" |
| |
| #include <algorithm> |
| #include <optional> |
| #include <string> |
| #include <vector> |
| |
| #include <base/files/file_util.h> |
| #include <base/logging.h> |
| #include <base/strings/string_tokenizer.h> |
| #include <base/strings/string_util.h> |
| |
| namespace brillo { |
| |
| namespace { |
| constexpr char kKernelCmdline[] = "/proc/cmdline"; |
| constexpr char kDoubleQuote = '"'; |
| constexpr char kEquals = '='; |
| constexpr char kTerminator[] = "--"; |
| |
| constexpr size_t kMaxKernelConfigSize = 4096; |
| |
| enum class FindType { kValue, kFlag }; |
| |
| struct ValueBound { |
| std::string::const_iterator begin; |
| std::string::const_iterator end; |
| }; |
| |
| // Tokenization logic to search config for desired flags or values. |
| bool Find(const std::string& kernel_config, |
| const std::string& target, |
| FindType type, |
| ValueBound* value_bound) { |
| // Setup tokenizer to split up on any white space while also not breaking |
| // quoted values. |
| base::StringTokenizer tokenizer(kernel_config, base::kWhitespaceASCII); |
| tokenizer.set_quote_chars(std::string{kDoubleQuote}); |
| std::vector<std::string> target_variants; |
| |
| switch (type) { |
| case FindType::kValue: |
| // Token should start with `target=` or `"target"=`. |
| target_variants = { |
| kDoubleQuote + target + kDoubleQuote + kEquals, |
| target.ends_with(kEquals) ? target : (target + kEquals)}; |
| break; |
| case FindType::kFlag: |
| // Match `target`, `"target"`, `target=...`, or `"target"=...` |
| target_variants = {target, kDoubleQuote + target + kDoubleQuote, |
| target + kEquals, |
| kDoubleQuote + target + kDoubleQuote + kEquals}; |
| break; |
| } |
| |
| while (tokenizer.GetNext()) { |
| const auto token = tokenizer.token(); |
| if (token == kTerminator) |
| break; |
| |
| switch (type) { |
| case FindType::kValue: |
| for (const auto& target_variant : target_variants) { |
| if (!token.starts_with(target_variant)) |
| continue; |
| |
| if (value_bound) { |
| *value_bound = {tokenizer.token_begin() + target_variant.size(), |
| tokenizer.token_end()}; |
| } |
| return true; |
| } |
| break; |
| |
| case FindType::kFlag: |
| for (const auto& target_variant : target_variants) { |
| if (token == target_variant || (target_variant.ends_with(kEquals) && |
| token.starts_with(target_variant))) |
| return true; |
| } |
| break; |
| } |
| } |
| |
| return false; |
| } |
| |
| // Value find logic that is common for extracting and setting values in the |
| // kernel config. |
| bool GetValueOffset(const std::string& kernel_config, |
| const std::string& key, |
| ValueBound* value_bound) { |
| return Find(kernel_config, key, FindType::kValue, value_bound); |
| } |
| |
| } // namespace |
| |
| std::optional<std::string> GetCurrentKernelConfig() { |
| std::string cmdline; |
| if (!base::ReadFileToStringWithMaxSize(base::FilePath(kKernelCmdline), |
| &cmdline, kMaxKernelConfigSize)) { |
| PLOG(ERROR) << "Failed to read kernel command line from " << kKernelCmdline; |
| return std::nullopt; |
| } |
| return cmdline; |
| } |
| |
| std::optional<std::string> ExtractKernelArgValue( |
| const std::string& kernel_config, |
| const std::string& key, |
| const bool strip_quotes) { |
| ValueBound value_bound; |
| if (!GetValueOffset(kernel_config, key, &value_bound)) { |
| return std::nullopt; |
| } |
| auto value = std::string{value_bound.begin, value_bound.end}; |
| if (value.starts_with(kDoubleQuote)) { |
| if (value.length() == 1 || !value.ends_with(kDoubleQuote)) { |
| // Value is corrupt if there's no end quote. |
| return std::nullopt; |
| } |
| if (strip_quotes) { |
| value = value.substr(1, value.length() - 2); |
| } |
| } |
| return value; |
| } |
| |
| bool SetKernelArg(const std::string& key, |
| const std::string& value, |
| std::string& kernel_config) { |
| ValueBound value_bound; |
| if (!GetValueOffset(kernel_config, key, &value_bound)) { |
| return false; |
| } |
| std::string adjusted_value = value; |
| |
| // Check for white space and quoted values. |
| const bool value_has_white_space = |
| std::any_of(value.begin(), value.end(), |
| [](const char& c) { return base::IsAsciiWhitespace(c); }); |
| const bool quoted_value = value.size() > 1 && |
| value.starts_with(kDoubleQuote) && |
| value.ends_with(kDoubleQuote); |
| |
| // If the new value has spaces, quote it before inserting. Skip quoting if the |
| // value is already quoted. |
| if (value_has_white_space && !quoted_value) { |
| adjusted_value = kDoubleQuote + value + kDoubleQuote; |
| } |
| kernel_config.replace(value_bound.begin, value_bound.end, adjusted_value); |
| return true; |
| } |
| |
| bool FlagExists(const std::string& kernel_config, const std::string& flag) { |
| return Find(kernel_config, flag, FindType::kFlag, nullptr); |
| } |
| |
| } // namespace brillo |