• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 <cerrno>
20 #include <cstdint>
21 #include <cstdlib>
22 #include <cstring>
23 #include <functional>
24 #include <optional>
25 #include <ostream>
26 #include <string>
27 #include <string_view>
28 #include <type_traits>
29 #include <unordered_map>
30 #include <unordered_set>
31 #include <utility>
32 #include <vector>
33 
34 #include <android-base/logging.h>
35 #include <android-base/strings.h>
36 
37 namespace cuttlefish {
38 
operator <<(std::ostream & out,const FlagAlias & alias)39 std::ostream& operator<<(std::ostream& out, const FlagAlias& alias) {
40   switch (alias.mode) {
41     case FlagAliasMode::kFlagExact:
42       return out << alias.name;
43     case FlagAliasMode::kFlagPrefix:
44       return out << alias.name << "*";
45     case FlagAliasMode::kFlagConsumesFollowing:
46       return out << alias.name << " *";
47     default:
48       LOG(FATAL) << "Unexpected flag alias mode " << (int)alias.mode;
49   }
50   return out;
51 }
52 
UnvalidatedAlias(const FlagAlias & alias)53 Flag& Flag::UnvalidatedAlias(const FlagAlias& alias) & {
54   aliases_.push_back(alias);
55   return *this;
56 }
UnvalidatedAlias(const FlagAlias & alias)57 Flag Flag::UnvalidatedAlias(const FlagAlias& alias) && {
58   aliases_.push_back(alias);
59   return *this;
60 }
61 
ValidateAlias(const FlagAlias & alias)62 void Flag::ValidateAlias(const FlagAlias& alias) {
63   using android::base::EndsWith;
64   using android::base::StartsWith;
65 
66   CHECK(StartsWith(alias.name, "-")) << "Flags should start with \"-\"";
67   if (alias.mode == FlagAliasMode::kFlagPrefix) {
68     CHECK(EndsWith(alias.name, "=")) << "Prefix flags shold end with \"=\"";
69   }
70 
71   CHECK(!HasAlias(alias)) << "Duplicate flag alias: " << alias.name;
72   if (alias.mode == FlagAliasMode::kFlagConsumesFollowing) {
73     CHECK(!HasAlias({FlagAliasMode::kFlagExact, alias.name}))
74         << "Overlapping flag aliases for " << alias.name;
75     CHECK(!HasAlias({FlagAliasMode::kFlagConsumesArbitrary, alias.name}))
76         << "Overlapping flag aliases for " << alias.name;
77   } else if (alias.mode == FlagAliasMode::kFlagExact) {
78     CHECK(!HasAlias({FlagAliasMode::kFlagConsumesFollowing, alias.name}))
79         << "Overlapping flag aliases for " << alias.name;
80     CHECK(!HasAlias({FlagAliasMode::kFlagConsumesArbitrary, alias.name}))
81         << "Overlapping flag aliases for " << alias.name;
82   } else if (alias.mode == FlagAliasMode::kFlagConsumesArbitrary) {
83     CHECK(!HasAlias({FlagAliasMode::kFlagExact, alias.name}))
84         << "Overlapping flag aliases for " << alias.name;
85     CHECK(!HasAlias({FlagAliasMode::kFlagConsumesFollowing, alias.name}))
86         << "Overlapping flag aliases for " << alias.name;
87   }
88 }
89 
Alias(const FlagAlias & alias)90 Flag& Flag::Alias(const FlagAlias& alias) & {
91   ValidateAlias(alias);
92   aliases_.push_back(alias);
93   return *this;
94 }
Alias(const FlagAlias & alias)95 Flag Flag::Alias(const FlagAlias& alias) && {
96   ValidateAlias(alias);
97   aliases_.push_back(alias);
98   return *this;
99 }
100 
Help(const std::string & help)101 Flag& Flag::Help(const std::string& help) & {
102   help_ = help;
103   return *this;
104 }
Help(const std::string & help)105 Flag Flag::Help(const std::string& help) && {
106   help_ = help;
107   return *this;
108 }
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 }
Getter(std::function<std::string ()> fn)114 Flag Flag::Getter(std::function<std::string()> fn) && {
115   getter_ = std::move(fn);
116   return *this;
117 }
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 }
Setter(std::function<bool (const FlagMatch &)> fn)123 Flag Flag::Setter(std::function<bool(const FlagMatch&)> fn) && {
124   setter_ = std::move(fn);
125   return *this;
126 }
127 
LikelyFlag(const std::string & next_arg)128 static bool LikelyFlag(const std::string& next_arg) {
129   return android::base::StartsWith(next_arg, "-");
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, "<", "&lt;", true), ">", "&gt;", 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     if (has_bool_aliases && has_other_aliases) {
276       LOG(ERROR) << "Expected exactly one of has_bool_aliases and "
277                  << "has_other_aliases, got both for \"" << name << "\".";
278       return false;
279     } else if (!has_bool_aliases && !has_other_aliases) {
280       continue;
281     }
282     found_alias = true;
283     // Lifted from external/gflags/src/gflags_reporting.cc:DescribeOneFlagInXML
284     out << "<flag>\n";
285     out << "  <file>file.cc</file>\n";
286     out << "  <name>" << XmlEscape(name) << "</name>\n";
287     auto help = help_ ? XmlEscape(*help_) : std::string{""};
288     out << "  <meaning>" << help << "</meaning>\n";
289     auto value = getter_ ? XmlEscape((*getter_)()) : std::string{""};
290     out << "  <default>" << value << "</default>\n";
291     out << "  <current>" << value << "</current>\n";
292     out << "  <type>" << (has_bool_aliases ? "bool" : "string") << "</type>\n";
293     out << "</flag>\n";
294   }
295   return found_alias;
296 }
297 
operator <<(std::ostream & out,const Flag & flag)298 std::ostream& operator<<(std::ostream& out, const Flag& flag) {
299   out << "[";
300   for (auto it = flag.aliases_.begin(); it != flag.aliases_.end(); it++) {
301     if (it != flag.aliases_.begin()) {
302       out << ", ";
303     }
304     out << *it;
305   }
306   out << "]\n";
307   if (flag.help_) {
308     out << "(" << *flag.help_ << ")\n";
309   }
310   if (flag.getter_) {
311     out << "(Current value: \"" << (*flag.getter_)() << "\")\n";
312   }
313   return out;
314 }
315 
ArgsToVec(int argc,char ** argv)316 std::vector<std::string> ArgsToVec(int argc, char** argv) {
317   std::vector<std::string> args;
318   for (int i = 0; i < argc; i++) {
319     args.push_back(argv[i]);
320   }
321   return args;
322 }
323 
ParseFlags(const std::vector<Flag> & flags,std::vector<std::string> & args)324 bool ParseFlags(const std::vector<Flag>& flags,
325                 std::vector<std::string>& args) {
326   for (const auto& flag : flags) {
327     if (!flag.Parse(args)) {
328       return false;
329     }
330   }
331   return true;
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 
WriteGflagsCompatXml(const std::vector<Flag> & flags,std::ostream & out)344 bool WriteGflagsCompatXml(const std::vector<Flag>& flags, std::ostream& out) {
345   for (const auto& flag : flags) {
346     if (!flag.WriteGflagsCompatXml(out)) {
347       return false;
348     }
349   }
350   return true;
351 }
352 
HelpFlag(const std::vector<Flag> & flags,const std::string & text)353 Flag HelpFlag(const std::vector<Flag>& flags, const std::string& text) {
354   auto setter = [&](FlagMatch) {
355     if (text.size() > 0) {
356       LOG(INFO) << text;
357     }
358     for (const auto& flag : flags) {
359       LOG(INFO) << flag;
360     }
361     return false;
362   };
363   return Flag()
364       .Alias({FlagAliasMode::kFlagExact, "-help"})
365       .Alias({FlagAliasMode::kFlagExact, "--help"})
366       .Setter(setter);
367 }
368 
InvalidFlagGuard()369 Flag InvalidFlagGuard() {
370   return Flag()
371       .UnvalidatedAlias({FlagAliasMode::kFlagPrefix, "-"})
372       .Help(
373           "This executable only supports the flags in `-help`. Positional "
374           "arguments may be supported.")
375       .Setter([](const FlagMatch& match) {
376         LOG(ERROR) << "Unknown flag " << match.value;
377         return false;
378       });
379 }
380 
UnexpectedArgumentGuard()381 Flag UnexpectedArgumentGuard() {
382   return Flag()
383       .UnvalidatedAlias({FlagAliasMode::kFlagPrefix, ""})
384       .Help(
385           "This executable only supports the flags in `-help`. Positional "
386           "arguments are not supported.")
387       .Setter([](const FlagMatch& match) {
388         LOG(ERROR) << "Unexpected argument \"" << match.value << "\"";
389         return false;
390       });
391 }
392 
GflagsCompatFlag(const std::string & name)393 Flag GflagsCompatFlag(const std::string& name) {
394   return Flag()
395       .Alias({FlagAliasMode::kFlagPrefix, "-" + name + "="})
396       .Alias({FlagAliasMode::kFlagPrefix, "--" + name + "="})
397       .Alias({FlagAliasMode::kFlagConsumesFollowing, "-" + name})
398       .Alias({FlagAliasMode::kFlagConsumesFollowing, "--" + name});
399 };
400 
GflagsCompatFlag(const std::string & name,std::string & value)401 Flag GflagsCompatFlag(const std::string& name, std::string& value) {
402   return GflagsCompatFlag(name)
403       .Getter([&value]() { return value; })
404       .Setter([&value](const FlagMatch& match) {
405         value = match.value;
406         return true;
407       });
408 }
409 
410 template <typename T>
ParseInteger(const std::string & value)411 std::optional<T> ParseInteger(const std::string& value) {
412   if (value.size() == 0) {
413     return {};
414   }
415   const char* base = value.c_str();
416   char* end = nullptr;
417   errno = 0;
418   auto r = strtoll(base, &end, /* auto-detect */ 0);
419   if (errno != 0 || end != base + value.size()) {
420     return {};
421   }
422   if (static_cast<T>(r) != r) {
423     return {};
424   }
425   return r;
426 }
427 
428 template <typename T>
GflagsCompatNumericFlagGeneric(const std::string & name,T & value)429 static Flag GflagsCompatNumericFlagGeneric(const std::string& name, T& value) {
430   return GflagsCompatFlag(name)
431       .Getter([&value]() { return std::to_string(value); })
432       .Setter([&value](const FlagMatch& match) {
433         auto parsed = ParseInteger<T>(match.value);
434         if (parsed) {
435           value = *parsed;
436           return true;
437         } else {
438           LOG(ERROR) << "Failed to parse \"" << match.value
439                      << "\" as an integer";
440           return false;
441         }
442       });
443 }
444 
GflagsCompatFlag(const std::string & name,int32_t & value)445 Flag GflagsCompatFlag(const std::string& name, int32_t& value) {
446   return GflagsCompatNumericFlagGeneric(name, value);
447 }
448 
GflagsCompatFlag(const std::string & name,bool & value)449 Flag GflagsCompatFlag(const std::string& name, bool& value) {
450   return Flag()
451       .Alias({FlagAliasMode::kFlagPrefix, "-" + name + "="})
452       .Alias({FlagAliasMode::kFlagPrefix, "--" + name + "="})
453       .Alias({FlagAliasMode::kFlagExact, "-" + name})
454       .Alias({FlagAliasMode::kFlagExact, "--" + name})
455       .Alias({FlagAliasMode::kFlagExact, "-no" + name})
456       .Alias({FlagAliasMode::kFlagExact, "--no" + name})
457       .Getter([&value]() { return value ? "true" : "false"; })
458       .Setter([name, &value](const FlagMatch& match) {
459         const auto& key = match.key;
460         if (key == "-" + name || key == "--" + name) {
461           value = true;
462           return true;
463         } else if (key == "-no" + name || key == "--no" + name) {
464           value = false;
465           return true;
466         } else if (key == "-" + name + "=" || key == "--" + name + "=") {
467           if (match.value == "true") {
468             value = true;
469             return true;
470           } else if (match.value == "false") {
471             value = false;
472             return true;
473           } else {
474             LOG(ERROR) << "Unexpected boolean value \"" << match.value << "\""
475                        << " for \"" << name << "\"";
476             return false;
477           }
478         }
479         LOG(ERROR) << "Unexpected key \"" << match.key << "\""
480                    << " for \"" << name << "\"";
481         return false;
482       });
483 };
484 
485 }  // namespace cuttlefish
486