1 // Copyright 2014 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "quiche_platform_impl/quiche_command_line_flags_impl.h"
6
7 #include <initializer_list>
8 #include <iostream>
9 #include <set>
10 #include <string>
11 #include <vector>
12
13 #include "base/command_line.h"
14 #include "base/export_template.h"
15 #include "base/logging.h"
16 #include "base/strings/strcat.h"
17 #include "base/strings/string_number_conversions.h"
18 #include "base/strings/string_util.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "build/build_config.h"
21 #include "net/third_party/quiche/src/quiche/quic/platform/api/quic_logging.h"
22
23 namespace quiche {
24
25 namespace {
26
FindLineWrapPosition(const std::string & s,size_t desired_len)27 size_t FindLineWrapPosition(const std::string& s, size_t desired_len) {
28 if (s.length() <= desired_len) {
29 return std::string::npos;
30 }
31 size_t pos = s.find_last_of(base::kWhitespaceASCII, desired_len);
32 if (pos != std::string::npos) {
33 return pos;
34 }
35 pos = s.find_first_of(base::kWhitespaceASCII, desired_len);
36 if (pos != std::string::npos) {
37 return pos;
38 }
39 return std::string::npos;
40 }
41
42 // Pretty-print a flag description in the format:
43 //
44 // --flag_name Some text describing the flag that can
45 // wrap around to the next line.
AppendFlagDescription(const std::string & name,std::string help,std::string * out)46 void AppendFlagDescription(const std::string& name,
47 std::string help,
48 std::string* out) {
49 const int kStartCol = 20;
50 const int kEndCol = 80;
51 const int kMinPadding = 2;
52 static const char kDashes[] = "--";
53
54 base::StrAppend(out, {kDashes, name});
55 int col = strlen(kDashes) + name.length();
56 if (col + kMinPadding < kEndCol) {
57 // Start help text on same line
58 int pad_len = std::max(kMinPadding, kStartCol - col);
59 base::StrAppend(out, {std::string(pad_len, ' ')});
60 col += pad_len;
61 } else {
62 // Start help text on next line
63 base::StrAppend(out, {"\n", std::string(kStartCol, ' ')});
64 col = kStartCol;
65 }
66
67 while (!help.empty()) {
68 size_t desired_len = kEndCol - col;
69 size_t wrap_pos = FindLineWrapPosition(help, desired_len);
70 if (wrap_pos == std::string::npos) {
71 base::StrAppend(out, {help});
72 break;
73 }
74 base::StrAppend(
75 out, {help.substr(0, wrap_pos), "\n", std::string(kStartCol, ' ')});
76 help = help.substr(wrap_pos + 1);
77 col = kStartCol;
78 }
79 base::StrAppend(out, {"\n"});
80 }
81
82 // Overload for platforms where base::CommandLine::StringType == std::string.
ToQuicheStringVector(const std::vector<std::string> & v)83 [[maybe_unused]] std::vector<std::string> ToQuicheStringVector(
84 const std::vector<std::string>& v) {
85 return v;
86 }
87
88 #if defined(WCHAR_T_IS_16_BIT)
89 // Overload for platforms where base::CommandLine::StringType == std::wstring.
ToQuicheStringVector(const std::vector<std::wstring> & v)90 [[maybe_unused]] std::vector<std::string> ToQuicheStringVector(
91 const std::vector<std::wstring>& v) {
92 std::vector<std::string> qsv;
93 for (const auto& s : v) {
94 if (!base::IsStringASCII(s)) {
95 QUIC_LOG(ERROR) << "Unable to convert to ASCII: " << s;
96 continue;
97 }
98 qsv.push_back(base::WideToASCII(s));
99 }
100 return qsv;
101 }
102 #endif // defined(WCHAR_T_IS_16_BIT)
103
104 } // namespace
105
106 // static
GetInstance()107 QuicheFlagRegistry& QuicheFlagRegistry::GetInstance() {
108 static base::NoDestructor<QuicheFlagRegistry> instance;
109 return *instance;
110 }
111
RegisterFlag(const char * name,std::unique_ptr<QuicheFlagHelper> helper)112 void QuicheFlagRegistry::RegisterFlag(
113 const char* name,
114 std::unique_ptr<QuicheFlagHelper> helper) {
115 flags_.emplace(std::string(name), std::move(helper));
116 }
117
SetFlags(const base::CommandLine & command_line,std::string * error_msg) const118 bool QuicheFlagRegistry::SetFlags(const base::CommandLine& command_line,
119 std::string* error_msg) const {
120 for (const auto& kv : flags_) {
121 const std::string& name = kv.first;
122 const QuicheFlagHelper* helper = kv.second.get();
123 if (!command_line.HasSwitch(name)) {
124 continue;
125 }
126 std::string value = command_line.GetSwitchValueASCII(name);
127 if (!helper->SetFlag(value)) {
128 *error_msg =
129 base::StrCat({"Invalid value \"", value, "\" for flag --", name});
130 return false;
131 }
132 QUIC_LOG(INFO) << "Set flag --" << name << " = " << value;
133 }
134 return true;
135 }
136
ResetFlags() const137 void QuicheFlagRegistry::ResetFlags() const {
138 for (const auto& kv : flags_) {
139 kv.second->ResetFlag();
140 QUIC_LOG(INFO) << "Reset flag --" << kv.first;
141 }
142 }
143
GetHelp() const144 std::string QuicheFlagRegistry::GetHelp() const {
145 std::string help;
146 AppendFlagDescription("help", "Print this help message.", &help);
147 for (const auto& kv : flags_) {
148 AppendFlagDescription(kv.first, kv.second->GetHelp(), &help);
149 }
150 return help;
151 }
152
153 template <>
SetFlag(const std::string & s) const154 bool TypedQuicheFlagHelper<bool>::SetFlag(const std::string& s) const {
155 static const base::NoDestructor<std::set<std::string>> kTrueValues(
156 std::initializer_list<std::string>({"", "1", "t", "true", "y", "yes"}));
157 static const base::NoDestructor<std::set<std::string>> kFalseValues(
158 std::initializer_list<std::string>({"0", "f", "false", "n", "no"}));
159 if (kTrueValues->find(base::ToLowerASCII(s)) != kTrueValues->end()) {
160 *flag_ = true;
161 return true;
162 }
163 if (kFalseValues->find(base::ToLowerASCII(s)) != kFalseValues->end()) {
164 *flag_ = false;
165 return true;
166 }
167 return false;
168 }
169
170 template <>
SetFlag(const std::string & s) const171 bool TypedQuicheFlagHelper<uint16_t>::SetFlag(const std::string& s) const {
172 int value;
173 if (!base::StringToInt(s, &value) ||
174 value < std::numeric_limits<uint16_t>::min() ||
175 value > std::numeric_limits<uint16_t>::max()) {
176 return false;
177 }
178 *flag_ = static_cast<uint16_t>(value);
179 return true;
180 }
181
182 template <>
SetFlag(const std::string & s) const183 bool TypedQuicheFlagHelper<int32_t>::SetFlag(const std::string& s) const {
184 int32_t value;
185 if (!base::StringToInt(s, &value)) {
186 return false;
187 }
188 *flag_ = value;
189 return true;
190 }
191
192 template <>
SetFlag(const std::string & s) const193 bool TypedQuicheFlagHelper<std::string>::SetFlag(const std::string& s) const {
194 *flag_ = s;
195 return true;
196 }
197
198 template class TypedQuicheFlagHelper<bool>;
199 template class TypedQuicheFlagHelper<uint16_t>;
200 template class TypedQuicheFlagHelper<int32_t>;
201 template class TypedQuicheFlagHelper<std::string>;
202
203 QuicheFlagRegistry::QuicheFlagRegistry() = default;
204 QuicheFlagRegistry::~QuicheFlagRegistry() = default;
205
QuicheParseCommandLineFlagsImpl(const char * usage,int argc,const char * const * argv)206 std::vector<std::string> QuicheParseCommandLineFlagsImpl(
207 const char* usage,
208 int argc,
209 const char* const* argv) {
210 base::CommandLine::Init(argc, argv);
211 auto result = QuicheParseCommandLineFlagsHelper(
212 usage, *base::CommandLine::ForCurrentProcess());
213 if (result.exit_status.has_value()) {
214 exit(*result.exit_status);
215 }
216
217 logging::LoggingSettings settings;
218 settings.logging_dest = logging::LOG_TO_STDERR;
219 CHECK(logging::InitLogging(settings));
220
221 return result.non_flag_args;
222 }
223
QuicheParseCommandLineFlagsHelper(const char * usage,const base::CommandLine & command_line)224 QuicheParseCommandLineFlagsResult QuicheParseCommandLineFlagsHelper(
225 const char* usage,
226 const base::CommandLine& command_line) {
227 QuicheParseCommandLineFlagsResult result;
228 result.non_flag_args = ToQuicheStringVector(command_line.GetArgs());
229 if (command_line.HasSwitch("h") || command_line.HasSwitch("help")) {
230 QuichePrintCommandLineFlagHelpImpl(usage);
231 result.exit_status = 0;
232 } else {
233 std::string msg;
234 if (!QuicheFlagRegistry::GetInstance().SetFlags(command_line, &msg)) {
235 std::cerr << msg << std::endl;
236 result.exit_status = 1;
237 }
238 }
239 return result;
240 }
241
QuichePrintCommandLineFlagHelpImpl(const char * usage)242 void QuichePrintCommandLineFlagHelpImpl(const char* usage) {
243 std::cout << usage << std::endl
244 << "Options:" << std::endl
245 << QuicheFlagRegistry::GetInstance().GetHelp() << std::endl;
246 }
247
248 QuicheParseCommandLineFlagsResult::QuicheParseCommandLineFlagsResult() =
249 default;
250 QuicheParseCommandLineFlagsResult::QuicheParseCommandLineFlagsResult(
251 const QuicheParseCommandLineFlagsResult&) = default;
252 QuicheParseCommandLineFlagsResult::~QuicheParseCommandLineFlagsResult() =
253 default;
254
255 } // namespace quiche
256