1 /*
2 * Copyright (C) 2021 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "common/libs/utils/flag_parser.h"
18
19 #include <algorithm>
20 #include <cerrno>
21 #include <cstdlib>
22 #include <cstring>
23 #include <iostream>
24 #include <string_view>
25 #include <type_traits>
26 #include <unordered_map>
27 #include <unordered_set>
28 #include <utility>
29
30 #include <android-base/logging.h>
31 #include <android-base/strings.h>
32
33 namespace cuttlefish {
34
operator <<(std::ostream & out,const FlagAlias & alias)35 std::ostream& operator<<(std::ostream& out, const FlagAlias& alias) {
36 switch (alias.mode) {
37 case FlagAliasMode::kFlagExact:
38 return out << alias.name;
39 case FlagAliasMode::kFlagPrefix:
40 return out << alias.name << "*";
41 case FlagAliasMode::kFlagConsumesFollowing:
42 return out << alias.name << " *";
43 default:
44 LOG(FATAL) << "Unexpected flag alias mode " << (int)alias.mode;
45 }
46 return out;
47 }
48
UnvalidatedAlias(const FlagAlias & alias)49 Flag& Flag::UnvalidatedAlias(const FlagAlias& alias) & {
50 aliases_.push_back(alias);
51 return *this;
52 }
UnvalidatedAlias(const FlagAlias & alias)53 Flag Flag::UnvalidatedAlias(const FlagAlias& alias) && {
54 aliases_.push_back(alias);
55 return *this;
56 }
57
ValidateAlias(const FlagAlias & alias)58 void Flag::ValidateAlias(const FlagAlias& alias) {
59 using android::base::EndsWith;
60 using android::base::StartsWith;
61
62 CHECK(StartsWith(alias.name, "-")) << "Flags should start with \"-\"";
63 if (alias.mode == FlagAliasMode::kFlagPrefix) {
64 CHECK(EndsWith(alias.name, "=")) << "Prefix flags shold end with \"=\"";
65 }
66
67 CHECK(!HasAlias(alias)) << "Duplicate flag alias: " << alias.name;
68 if (alias.mode == FlagAliasMode::kFlagConsumesFollowing) {
69 CHECK(!HasAlias({FlagAliasMode::kFlagExact, alias.name}))
70 << "Overlapping flag aliases for " << alias.name;
71 CHECK(!HasAlias({FlagAliasMode::kFlagConsumesArbitrary, alias.name}))
72 << "Overlapping flag aliases for " << alias.name;
73 } else if (alias.mode == FlagAliasMode::kFlagExact) {
74 CHECK(!HasAlias({FlagAliasMode::kFlagConsumesFollowing, alias.name}))
75 << "Overlapping flag aliases for " << alias.name;
76 CHECK(!HasAlias({FlagAliasMode::kFlagConsumesArbitrary, alias.name}))
77 << "Overlapping flag aliases for " << alias.name;
78 } else if (alias.mode == FlagAliasMode::kFlagConsumesArbitrary) {
79 CHECK(!HasAlias({FlagAliasMode::kFlagExact, alias.name}))
80 << "Overlapping flag aliases for " << alias.name;
81 CHECK(!HasAlias({FlagAliasMode::kFlagConsumesFollowing, alias.name}))
82 << "Overlapping flag aliases for " << alias.name;
83 }
84 }
85
Alias(const FlagAlias & alias)86 Flag& Flag::Alias(const FlagAlias& alias) & {
87 ValidateAlias(alias);
88 aliases_.push_back(alias);
89 return *this;
90 }
Alias(const FlagAlias & alias)91 Flag Flag::Alias(const FlagAlias& alias) && {
92 ValidateAlias(alias);
93 aliases_.push_back(alias);
94 return *this;
95 }
96
Help(const std::string & help)97 Flag& Flag::Help(const std::string& help) & {
98 help_ = help;
99 return *this;
100 }
Help(const std::string & help)101 Flag Flag::Help(const std::string& help) && {
102 help_ = help;
103 return *this;
104 }
105
Getter(std::function<std::string ()> fn)106 Flag& Flag::Getter(std::function<std::string()> fn) & {
107 getter_ = std::move(fn);
108 return *this;
109 }
Getter(std::function<std::string ()> fn)110 Flag Flag::Getter(std::function<std::string()> fn) && {
111 getter_ = std::move(fn);
112 return *this;
113 }
114
Setter(std::function<bool (const FlagMatch &)> fn)115 Flag& Flag::Setter(std::function<bool(const FlagMatch&)> fn) & {
116 setter_ = std::move(fn);
117 return *this;
118 }
Setter(std::function<bool (const FlagMatch &)> fn)119 Flag Flag::Setter(std::function<bool(const FlagMatch&)> fn) && {
120 setter_ = std::move(fn);
121 return *this;
122 }
123
LikelyFlag(const std::string & next_arg)124 static bool LikelyFlag(const std::string& next_arg) {
125 return android::base::StartsWith(next_arg, "-");
126 }
127
BoolToString(bool val)128 std::string BoolToString(bool val) {
129 return val ? "true" : "false";
130 }
131
Process(const std::string & arg,const std::optional<std::string> & next_arg) const132 Flag::FlagProcessResult Flag::Process(
133 const std::string& arg, const std::optional<std::string>& next_arg) const {
134 if (!setter_ && aliases_.size() > 0) {
135 LOG(ERROR) << "No setter for flag with alias " << aliases_[0].name;
136 return FlagProcessResult::kFlagError;
137 }
138 for (auto& alias : aliases_) {
139 switch (alias.mode) {
140 case FlagAliasMode::kFlagConsumesArbitrary:
141 if (arg != alias.name) {
142 continue;
143 }
144 if (!next_arg || LikelyFlag(*next_arg)) {
145 if (!(*setter_)({arg, ""})) {
146 LOG(ERROR) << "Processing \"" << arg << "\" failed";
147 return FlagProcessResult::kFlagError;
148 }
149 return FlagProcessResult::kFlagConsumed;
150 }
151 if (!(*setter_)({arg, *next_arg})) {
152 LOG(ERROR) << "Processing \"" << arg << "\" \"" << *next_arg
153 << "\" failed";
154 return FlagProcessResult::kFlagError;
155 }
156 return FlagProcessResult::kFlagConsumedOnlyFollowing;
157 case FlagAliasMode::kFlagConsumesFollowing:
158 if (arg != alias.name) {
159 continue;
160 }
161 if (!next_arg) {
162 LOG(ERROR) << "Expected an argument after \"" << arg << "\"";
163 return FlagProcessResult::kFlagError;
164 }
165 if (!(*setter_)({arg, *next_arg})) {
166 LOG(ERROR) << "Processing \"" << arg << "\" \"" << *next_arg
167 << "\" failed";
168 return FlagProcessResult::kFlagError;
169 }
170 return FlagProcessResult::kFlagConsumedWithFollowing;
171 case FlagAliasMode::kFlagExact:
172 if (arg != alias.name) {
173 continue;
174 }
175 if (!(*setter_)({arg, arg})) {
176 LOG(ERROR) << "Processing \"" << arg << "\" failed";
177 return FlagProcessResult::kFlagError;
178 }
179 return FlagProcessResult::kFlagConsumed;
180 case FlagAliasMode::kFlagPrefix:
181 if (!android::base::StartsWith(arg, alias.name)) {
182 continue;
183 }
184 if (!(*setter_)({alias.name, arg.substr(alias.name.size())})) {
185 LOG(ERROR) << "Processing \"" << arg << "\" failed";
186 return FlagProcessResult::kFlagError;
187 }
188 return FlagProcessResult::kFlagConsumed;
189 default:
190 LOG(ERROR) << "Unknown flag alias mode: " << (int)alias.mode;
191 return FlagProcessResult::kFlagError;
192 }
193 }
194 return FlagProcessResult::kFlagSkip;
195 }
196
Parse(std::vector<std::string> & arguments) const197 bool Flag::Parse(std::vector<std::string>& arguments) const {
198 for (int i = 0; i < arguments.size();) {
199 std::string arg = arguments[i];
200 std::optional<std::string> next_arg;
201 if (i < arguments.size() - 1) {
202 next_arg = arguments[i + 1];
203 }
204 auto result = Process(arg, next_arg);
205 if (result == FlagProcessResult::kFlagError) {
206 return false;
207 } else if (result == FlagProcessResult::kFlagConsumed) {
208 arguments.erase(arguments.begin() + i);
209 } else if (result == FlagProcessResult::kFlagConsumedWithFollowing) {
210 arguments.erase(arguments.begin() + i, arguments.begin() + i + 2);
211 } else if (result == FlagProcessResult::kFlagConsumedOnlyFollowing) {
212 arguments.erase(arguments.begin() + i + 1, arguments.begin() + i + 2);
213 } else if (result == FlagProcessResult::kFlagSkip) {
214 i++;
215 continue;
216 } else {
217 LOG(ERROR) << "Unknown FlagProcessResult: " << (int)result;
218 return false;
219 }
220 }
221 return true;
222 }
Parse(std::vector<std::string> && arguments) const223 bool Flag::Parse(std::vector<std::string>&& arguments) const {
224 return Parse(static_cast<std::vector<std::string>&>(arguments));
225 }
226
HasAlias(const FlagAlias & test) const227 bool Flag::HasAlias(const FlagAlias& test) const {
228 for (const auto& alias : aliases_) {
229 if (alias.mode == test.mode && alias.name == test.name) {
230 return true;
231 }
232 }
233 return false;
234 }
235
XmlEscape(const std::string & s)236 static std::string XmlEscape(const std::string& s) {
237 using android::base::StringReplace;
238 return StringReplace(StringReplace(s, "<", "<", true), ">", ">", true);
239 }
240
WriteGflagsCompatXml(std::ostream & out) const241 bool Flag::WriteGflagsCompatXml(std::ostream& out) const {
242 std::unordered_set<std::string> name_guesses;
243 for (const auto& alias : aliases_) {
244 std::string_view name = alias.name;
245 if (!android::base::ConsumePrefix(&name, "-")) {
246 continue;
247 }
248 android::base::ConsumePrefix(&name, "-");
249 if (alias.mode == FlagAliasMode::kFlagExact) {
250 android::base::ConsumePrefix(&name, "no");
251 name_guesses.insert(std::string{name});
252 } else if (alias.mode == FlagAliasMode::kFlagConsumesFollowing) {
253 name_guesses.insert(std::string{name});
254 } else if (alias.mode == FlagAliasMode::kFlagPrefix) {
255 if (!android::base::ConsumeSuffix(&name, "=")) {
256 continue;
257 }
258 name_guesses.insert(std::string{name});
259 }
260 }
261 bool found_alias = false;
262 for (const auto& name : name_guesses) {
263 bool has_bool_aliases =
264 HasAlias({FlagAliasMode::kFlagPrefix, "-" + name + "="}) &&
265 HasAlias({FlagAliasMode::kFlagPrefix, "--" + name + "="}) &&
266 HasAlias({FlagAliasMode::kFlagExact, "-" + name}) &&
267 HasAlias({FlagAliasMode::kFlagExact, "--" + name}) &&
268 HasAlias({FlagAliasMode::kFlagExact, "-no" + name}) &&
269 HasAlias({FlagAliasMode::kFlagExact, "--no" + name});
270 bool has_other_aliases =
271 HasAlias({FlagAliasMode::kFlagPrefix, "-" + name + "="}) &&
272 HasAlias({FlagAliasMode::kFlagPrefix, "--" + name + "="}) &&
273 HasAlias({FlagAliasMode::kFlagConsumesFollowing, "-" + name}) &&
274 HasAlias({FlagAliasMode::kFlagConsumesFollowing, "--" + name});
275 bool has_help_aliases = HasAlias({FlagAliasMode::kFlagExact, "-help"}) &&
276 HasAlias({FlagAliasMode::kFlagExact, "--help"});
277 std::vector<bool> has_aliases = {has_bool_aliases, has_other_aliases,
278 has_help_aliases};
279 const auto true_count =
280 std::count(has_aliases.cbegin(), has_aliases.cend(), true);
281 if (true_count > 1) {
282 LOG(ERROR) << "Expected exactly one of has_bool_aliases, "
283 << "has_other_aliases, and has_help_aliases, got "
284 << true_count << " for \"" << name << "\".";
285 return false;
286 }
287 if (true_count == 0) {
288 continue;
289 }
290 found_alias = true;
291 std::string type_str =
292 (has_bool_aliases || has_help_aliases) ? "bool" : "string";
293 // Lifted from external/gflags/src/gflags_reporting.cc:DescribeOneFlagInXML
294 out << "<flag>\n";
295 out << " <file>file.cc</file>\n";
296 out << " <name>" << XmlEscape(name) << "</name>\n";
297 auto help = help_ ? XmlEscape(*help_) : std::string{""};
298 out << " <meaning>" << help << "</meaning>\n";
299 auto value = getter_ ? XmlEscape((*getter_)()) : std::string{""};
300 out << " <default>" << value << "</default>\n";
301 out << " <current>" << value << "</current>\n";
302 out << " <type>" << type_str << "</type>\n";
303 out << "</flag>\n";
304 }
305 return found_alias;
306 }
307
operator <<(std::ostream & out,const Flag & flag)308 std::ostream& operator<<(std::ostream& out, const Flag& flag) {
309 out << "[";
310 for (auto it = flag.aliases_.begin(); it != flag.aliases_.end(); it++) {
311 if (it != flag.aliases_.begin()) {
312 out << ", ";
313 }
314 out << *it;
315 }
316 out << "]\n";
317 if (flag.help_) {
318 out << "(" << *flag.help_ << ")\n";
319 }
320 if (flag.getter_) {
321 out << "(Current value: \"" << (*flag.getter_)() << "\")\n";
322 }
323 return out;
324 }
325
ArgsToVec(int argc,char ** argv)326 std::vector<std::string> ArgsToVec(int argc, char** argv) {
327 std::vector<std::string> args;
328 for (int i = 0; i < argc; i++) {
329 args.push_back(argv[i]);
330 }
331 return args;
332 }
333
ParseFlags(const std::vector<Flag> & flags,std::vector<std::string> & args)334 bool ParseFlags(const std::vector<Flag>& flags,
335 std::vector<std::string>& args) {
336 for (const auto& flag : flags) {
337 if (!flag.Parse(args)) {
338 return false;
339 }
340 }
341 return true;
342 }
343
ParseFlags(const std::vector<Flag> & flags,std::vector<std::string> && args)344 bool ParseFlags(const std::vector<Flag>& flags,
345 std::vector<std::string>&& args) {
346 for (const auto& flag : flags) {
347 if (!flag.Parse(args)) {
348 return false;
349 }
350 }
351 return true;
352 }
353
WriteGflagsCompatXml(const std::vector<Flag> & flags,std::ostream & out)354 bool WriteGflagsCompatXml(const std::vector<Flag>& flags, std::ostream& out) {
355 for (const auto& flag : flags) {
356 if (!flag.WriteGflagsCompatXml(out)) {
357 return false;
358 }
359 }
360 return true;
361 }
362
HelpFlag(const std::vector<Flag> & flags,const std::string & text)363 Flag HelpFlag(const std::vector<Flag>& flags, const std::string& text) {
364 auto setter = [&](FlagMatch) {
365 if (text.size() > 0) {
366 LOG(INFO) << text;
367 }
368 for (const auto& flag : flags) {
369 LOG(INFO) << flag;
370 }
371 return false;
372 };
373 return Flag()
374 .Alias({FlagAliasMode::kFlagExact, "-help"})
375 .Alias({FlagAliasMode::kFlagExact, "--help"})
376 .Setter(setter);
377 }
378
GflagsCompatBoolFlagSetter(const std::string & name,bool & value,const FlagMatch & match)379 static bool GflagsCompatBoolFlagSetter(const std::string& name, bool& value,
380 const FlagMatch& match) {
381 const auto& key = match.key;
382 if (key == "-" + name || key == "--" + name) {
383 value = true;
384 return true;
385 } else if (key == "-no" + name || key == "--no" + name) {
386 value = false;
387 return true;
388 } else if (key == "-" + name + "=" || key == "--" + name + "=") {
389 if (match.value == "true") {
390 value = true;
391 return true;
392 } else if (match.value == "false") {
393 value = false;
394 return true;
395 } else {
396 LOG(ERROR) << "Unexpected boolean value \"" << match.value << "\""
397 << " for \"" << name << "\"";
398 return false;
399 }
400 }
401 LOG(ERROR) << "Unexpected key \"" << match.key << "\""
402 << " for \"" << name << "\"";
403 return false;
404 }
405
GflagsCompatBoolFlagBase(const std::string & name)406 static Flag GflagsCompatBoolFlagBase(const std::string& name) {
407 return Flag()
408 .Alias({FlagAliasMode::kFlagPrefix, "-" + name + "="})
409 .Alias({FlagAliasMode::kFlagPrefix, "--" + name + "="})
410 .Alias({FlagAliasMode::kFlagExact, "-" + name})
411 .Alias({FlagAliasMode::kFlagExact, "--" + name})
412 .Alias({FlagAliasMode::kFlagExact, "-no" + name})
413 .Alias({FlagAliasMode::kFlagExact, "--no" + name});
414 }
415
HelpXmlFlag(const std::vector<Flag> & flags,std::ostream & out,bool & value,const std::string & text)416 Flag HelpXmlFlag(const std::vector<Flag>& flags, std::ostream& out, bool& value,
417 const std::string& text) {
418 const std::string name = "helpxml";
419 auto setter = [name, &out, &value, &text, &flags](const FlagMatch& match) {
420 bool print_xml = false;
421 auto parse_success = GflagsCompatBoolFlagSetter(name, print_xml, match);
422 if (!parse_success) {
423 return false;
424 }
425 if (!print_xml) {
426 return true;
427 }
428 if (!text.empty()) {
429 out << text << std::endl;
430 }
431 value = print_xml;
432 out << "<?xml version=\"1.0\"?>" << std::endl << "<AllFlags>" << std::endl;
433 WriteGflagsCompatXml(flags, out);
434 out << "</AllFlags>" << std::flush;
435 return false;
436 };
437 return GflagsCompatBoolFlagBase(name).Setter(setter);
438 }
439
InvalidFlagGuard()440 Flag InvalidFlagGuard() {
441 return Flag()
442 .UnvalidatedAlias({FlagAliasMode::kFlagPrefix, "-"})
443 .Help(
444 "This executable only supports the flags in `-help`. Positional "
445 "arguments may be supported.")
446 .Setter([](const FlagMatch& match) {
447 LOG(ERROR) << "Unknown flag " << match.value;
448 return false;
449 });
450 }
451
UnexpectedArgumentGuard()452 Flag UnexpectedArgumentGuard() {
453 return Flag()
454 .UnvalidatedAlias({FlagAliasMode::kFlagPrefix, ""})
455 .Help(
456 "This executable only supports the flags in `-help`. Positional "
457 "arguments are not supported.")
458 .Setter([](const FlagMatch& match) {
459 LOG(ERROR) << "Unexpected argument \"" << match.value << "\"";
460 return false;
461 });
462 }
463
GflagsCompatFlag(const std::string & name)464 Flag GflagsCompatFlag(const std::string& name) {
465 return Flag()
466 .Alias({FlagAliasMode::kFlagPrefix, "-" + name + "="})
467 .Alias({FlagAliasMode::kFlagPrefix, "--" + name + "="})
468 .Alias({FlagAliasMode::kFlagConsumesFollowing, "-" + name})
469 .Alias({FlagAliasMode::kFlagConsumesFollowing, "--" + name});
470 };
471
GflagsCompatFlag(const std::string & name,std::string & value)472 Flag GflagsCompatFlag(const std::string& name, std::string& value) {
473 return GflagsCompatFlag(name)
474 .Getter([&value]() { return value; })
475 .Setter([&value](const FlagMatch& match) {
476 value = match.value;
477 return true;
478 });
479 }
480
481 template <typename T>
ParseInteger(const std::string & value)482 std::optional<T> ParseInteger(const std::string& value) {
483 if (value.size() == 0) {
484 return {};
485 }
486 const char* base = value.c_str();
487 char* end = nullptr;
488 errno = 0;
489 auto r = strtoll(base, &end, /* auto-detect */ 0);
490 if (errno != 0 || end != base + value.size()) {
491 return {};
492 }
493 if (static_cast<T>(r) != r) {
494 return {};
495 }
496 return r;
497 }
498
499 template <typename T>
GflagsCompatNumericFlagGeneric(const std::string & name,T & value)500 static Flag GflagsCompatNumericFlagGeneric(const std::string& name, T& value) {
501 return GflagsCompatFlag(name)
502 .Getter([&value]() { return std::to_string(value); })
503 .Setter([&value](const FlagMatch& match) {
504 auto parsed = ParseInteger<T>(match.value);
505 if (parsed) {
506 value = *parsed;
507 return true;
508 } else {
509 LOG(ERROR) << "Failed to parse \"" << match.value
510 << "\" as an integer";
511 return false;
512 }
513 });
514 }
515
GflagsCompatFlag(const std::string & name,int32_t & value)516 Flag GflagsCompatFlag(const std::string& name, int32_t& value) {
517 return GflagsCompatNumericFlagGeneric(name, value);
518 }
519
GflagsCompatFlag(const std::string & name,bool & value)520 Flag GflagsCompatFlag(const std::string& name, bool& value) {
521 return GflagsCompatBoolFlagBase(name)
522 .Getter([&value]() { return value ? "true" : "false"; })
523 .Setter([name, &value](const FlagMatch& match) {
524 return GflagsCompatBoolFlagSetter(name, value, match);
525 });
526 };
527
528 } // namespace cuttlefish
529