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