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, "<", "<", 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 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