// Copyright 2014 The Chromium 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 "quiche_platform_impl/quiche_command_line_flags_impl.h" #include #include #include #include #include #include "base/command_line.h" #include "base/export_template.h" #include "base/logging.h" #include "base/strings/strcat.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "build/build_config.h" #include "net/third_party/quiche/src/quiche/quic/platform/api/quic_logging.h" namespace quiche { namespace { size_t FindLineWrapPosition(const std::string& s, size_t desired_len) { if (s.length() <= desired_len) { return std::string::npos; } size_t pos = s.find_last_of(base::kWhitespaceASCII, desired_len); if (pos != std::string::npos) { return pos; } pos = s.find_first_of(base::kWhitespaceASCII, desired_len); if (pos != std::string::npos) { return pos; } return std::string::npos; } // Pretty-print a flag description in the format: // // --flag_name Some text describing the flag that can // wrap around to the next line. void AppendFlagDescription(const std::string& name, std::string help, std::string* out) { const int kStartCol = 20; const int kEndCol = 80; const int kMinPadding = 2; static const char kDashes[] = "--"; base::StrAppend(out, {kDashes, name}); int col = strlen(kDashes) + name.length(); if (col + kMinPadding < kEndCol) { // Start help text on same line int pad_len = std::max(kMinPadding, kStartCol - col); base::StrAppend(out, {std::string(pad_len, ' ')}); col += pad_len; } else { // Start help text on next line base::StrAppend(out, {"\n", std::string(kStartCol, ' ')}); col = kStartCol; } while (!help.empty()) { size_t desired_len = kEndCol - col; size_t wrap_pos = FindLineWrapPosition(help, desired_len); if (wrap_pos == std::string::npos) { base::StrAppend(out, {help}); break; } base::StrAppend( out, {help.substr(0, wrap_pos), "\n", std::string(kStartCol, ' ')}); help = help.substr(wrap_pos + 1); col = kStartCol; } base::StrAppend(out, {"\n"}); } // Overload for platforms where base::CommandLine::StringType == std::string. [[maybe_unused]] std::vector ToQuicheStringVector( const std::vector& v) { return v; } #if defined(WCHAR_T_IS_UTF16) // Overload for platforms where base::CommandLine::StringType == std::wstring. [[maybe_unused]] std::vector ToQuicheStringVector( const std::vector& v) { std::vector qsv; for (const auto& s : v) { if (!base::IsStringASCII(s)) { QUIC_LOG(ERROR) << "Unable to convert to ASCII: " << s; continue; } qsv.push_back(base::WideToASCII(s)); } return qsv; } #endif // defined(WCHAR_T_IS_UTF16) } // namespace // static QuicheFlagRegistry& QuicheFlagRegistry::GetInstance() { static base::NoDestructor instance; return *instance; } void QuicheFlagRegistry::RegisterFlag( const char* name, std::unique_ptr helper) { flags_.emplace(std::string(name), std::move(helper)); } bool QuicheFlagRegistry::SetFlags(const base::CommandLine& command_line, std::string* error_msg) const { for (const auto& kv : flags_) { const std::string& name = kv.first; const QuicheFlagHelper* helper = kv.second.get(); if (!command_line.HasSwitch(name)) { continue; } std::string value = command_line.GetSwitchValueASCII(name); if (!helper->SetFlag(value)) { *error_msg = base::StrCat({"Invalid value \"", value, "\" for flag --", name}); return false; } QUIC_LOG(INFO) << "Set flag --" << name << " = " << value; } return true; } void QuicheFlagRegistry::ResetFlags() const { for (const auto& kv : flags_) { kv.second->ResetFlag(); QUIC_LOG(INFO) << "Reset flag --" << kv.first; } } std::string QuicheFlagRegistry::GetHelp() const { std::string help; AppendFlagDescription("help", "Print this help message.", &help); for (const auto& kv : flags_) { AppendFlagDescription(kv.first, kv.second->GetHelp(), &help); } return help; } template <> bool TypedQuicheFlagHelper::SetFlag(const std::string& s) const { static const base::NoDestructor> kTrueValues( std::initializer_list({"", "1", "t", "true", "y", "yes"})); static const base::NoDestructor> kFalseValues( std::initializer_list({"0", "f", "false", "n", "no"})); if (kTrueValues->find(base::ToLowerASCII(s)) != kTrueValues->end()) { *flag_ = true; return true; } if (kFalseValues->find(base::ToLowerASCII(s)) != kFalseValues->end()) { *flag_ = false; return true; } return false; } template <> bool TypedQuicheFlagHelper::SetFlag(const std::string& s) const { int value; if (!base::StringToInt(s, &value) || value < std::numeric_limits::min() || value > std::numeric_limits::max()) { return false; } *flag_ = static_cast(value); return true; } template <> bool TypedQuicheFlagHelper::SetFlag(const std::string& s) const { int32_t value; if (!base::StringToInt(s, &value)) { return false; } *flag_ = value; return true; } template <> bool TypedQuicheFlagHelper::SetFlag(const std::string& s) const { *flag_ = s; return true; } template class TypedQuicheFlagHelper; template class TypedQuicheFlagHelper; template class TypedQuicheFlagHelper; template class TypedQuicheFlagHelper; QuicheFlagRegistry::QuicheFlagRegistry() = default; QuicheFlagRegistry::~QuicheFlagRegistry() = default; std::vector QuicheParseCommandLineFlagsImpl( const char* usage, int argc, const char* const* argv) { base::CommandLine::Init(argc, argv); auto result = QuicheParseCommandLineFlagsHelper( usage, *base::CommandLine::ForCurrentProcess()); if (result.exit_status.has_value()) { exit(*result.exit_status); } logging::LoggingSettings settings; settings.logging_dest = logging::LOG_TO_STDERR; CHECK(logging::InitLogging(settings)); return result.non_flag_args; } QuicheParseCommandLineFlagsResult QuicheParseCommandLineFlagsHelper( const char* usage, const base::CommandLine& command_line) { QuicheParseCommandLineFlagsResult result; result.non_flag_args = ToQuicheStringVector(command_line.GetArgs()); if (command_line.HasSwitch("h") || command_line.HasSwitch("help")) { QuichePrintCommandLineFlagHelpImpl(usage); result.exit_status = 0; } else { std::string msg; if (!QuicheFlagRegistry::GetInstance().SetFlags(command_line, &msg)) { std::cerr << msg << std::endl; result.exit_status = 1; } } return result; } void QuichePrintCommandLineFlagHelpImpl(const char* usage) { std::cout << usage << std::endl << "Options:" << std::endl << QuicheFlagRegistry::GetInstance().GetHelp() << std::endl; } QuicheParseCommandLineFlagsResult::QuicheParseCommandLineFlagsResult() = default; QuicheParseCommandLineFlagsResult::QuicheParseCommandLineFlagsResult( const QuicheParseCommandLineFlagsResult&) = default; QuicheParseCommandLineFlagsResult::~QuicheParseCommandLineFlagsResult() = default; } // namespace quiche