1 //
2 // Copyright (C) 2019 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 #include "flag_forwarder.h"
17 
18 #include <cstring>
19 
20 #include <map>
21 #include <sstream>
22 #include <string>
23 #include <unordered_set>
24 #include <vector>
25 
26 #include <android-base/logging.h>
27 #include <gflags/gflags.h>
28 #include <libxml/parser.h>
29 
30 #include "common/libs/fs/shared_buf.h"
31 #include "common/libs/fs/shared_fd.h"
32 #include "common/libs/utils/contains.h"
33 #include "common/libs/utils/subprocess.h"
34 
35 namespace cuttlefish {
36 
37 /**
38  * Superclass for a flag loaded from another process.
39  *
40  * An instance of this class defines a flag available either in this subprocess
41  * or another flag. If a flag needs to be registered in the current process, see
42  * the DynamicFlag subclass. If multiple subprocesses declare a flag with the
43  * same name, they all should receive that flag, but the DynamicFlag should only
44  * be created zero or one times. Zero times if the parent process defines it as
45  * well, one time if the parent does not define it.
46  *
47  * Notably, gflags itself defines some flags that are present in every binary.
48  */
49 class SubprocessFlag {
50   std::string subprocess_;
51   std::string name_;
52 public:
SubprocessFlag(const std::string & subprocess,const std::string & name)53   SubprocessFlag(const std::string& subprocess, const std::string& name)
54       : subprocess_(subprocess), name_(name) {
55   }
56   virtual ~SubprocessFlag() = default;
57   SubprocessFlag(const SubprocessFlag&) = delete;
58   SubprocessFlag& operator=(const SubprocessFlag&) = delete;
59   SubprocessFlag(SubprocessFlag&&) = delete;
60   SubprocessFlag& operator=(SubprocessFlag&&) = delete;
61 
Subprocess() const62   const std::string& Subprocess() const { return subprocess_; }
Name() const63   const std::string& Name() const { return name_; }
64 };
65 
66 /*
67  * A dynamic gflags flag. Creating an instance of this class is equivalent to
68  * registering a flag with DEFINE_<type>. Instances of this class should not
69  * be deleted while flags are still in use (likely through the end of main).
70  *
71  * This is implemented as a wrapper around gflags::FlagRegisterer. This class
72  * serves a dual purpose of holding the memory for gflags::FlagRegisterer as
73  * that normally expects memory to be held statically. The other reason is to
74  * subclass class SubprocessFlag to fit into the flag-forwarding scheme.
75  */
76 template<typename T>
77 class DynamicFlag : public SubprocessFlag {
78   std::string help_;
79   std::string filename_;
80   T current_storage_;
81   T defvalue_storage_;
82   gflags::FlagRegisterer registerer_;
83 public:
DynamicFlag(const std::string & subprocess,const std::string & name,const std::string & help,const std::string & filename,const T & current,const T & defvalue)84   DynamicFlag(const std::string& subprocess, const std::string& name,
85               const std::string& help, const std::string& filename,
86               const T& current, const T& defvalue)
87       : SubprocessFlag(subprocess, name), help_(help), filename_(filename),
88         current_storage_(current), defvalue_storage_(defvalue),
89         registerer_(Name().c_str(), help_.c_str(), filename_.c_str(),
90                     &current_storage_, &defvalue_storage_) {
91   }
92 };
93 
94 namespace {
95 
96 /**
97  * Returns a mapping between flag name and "gflags type" as strings for flags
98  * defined in the binary.
99  */
CurrentFlagsToTypes()100 std::map<std::string, std::string> CurrentFlagsToTypes() {
101   std::map<std::string, std::string> name_to_type;
102   std::vector<gflags::CommandLineFlagInfo> self_flags;
103   gflags::GetAllFlags(&self_flags);
104   for (auto& flag : self_flags) {
105     name_to_type[flag.name] = flag.type;
106   }
107   return name_to_type;
108 }
109 
110 /**
111  * Returns a pointer to the child of `node` with name `name`.
112  *
113  * For example, invoking `xmlChildWithName(<foo><bar>abc</bar></foo>, "foo")`
114  * will return <bar>abc</bar>.
115  */
xmlChildWithName(xmlNodePtr node,const std::string & name)116 xmlNodePtr xmlChildWithName(xmlNodePtr node, const std::string& name) {
117   for (xmlNodePtr child = node->children; child != nullptr; child = child->next) {
118     if (child->type != XML_ELEMENT_NODE) {
119       continue;
120     }
121     if (std::strcmp((const char*) child->name, name.c_str()) == 0) {
122       return child;
123     }
124   }
125   LOG(WARNING) << "no child with name " << name;
126   return nullptr;
127 }
128 
129 /**
130  * Returns a string with the content of an xml node.
131  *
132  * For example, calling `xmlContent(<bar>abc</bar>)` will return "abc".
133  */
xmlContent(xmlNodePtr node)134 std::string xmlContent(xmlNodePtr node) {
135   if (node == nullptr || node->children == NULL
136       || node->children->type != xmlElementType::XML_TEXT_NODE) {
137     return "";
138   }
139   return std::string((char*) node->children->content);
140 }
141 
142 template<typename T>
FromString(const std::string & str)143 T FromString(const std::string& str) {
144   std::stringstream stream(str);
145   T output;
146   stream >> output;
147   return output;
148 }
149 
150 /**
151  * Creates a dynamic flag
152  */
MakeDynamicFlag(const std::string & subprocess,const gflags::CommandLineFlagInfo & flag_info)153 std::unique_ptr<SubprocessFlag> MakeDynamicFlag(
154     const std::string& subprocess,
155     const gflags::CommandLineFlagInfo& flag_info) {
156   std::unique_ptr<SubprocessFlag> ptr;
157   if (flag_info.type == "bool") {
158     ptr.reset(new DynamicFlag<bool>(subprocess, flag_info.name,
159                                     flag_info.description,
160                                     flag_info.filename,
161                                     FromString<bool>(flag_info.default_value),
162                                     FromString<bool>(flag_info.current_value)));
163   } else if (flag_info.type == "int32") {
164     ptr.reset(new DynamicFlag<int32_t>(subprocess, flag_info.name,
165                                        flag_info.description,
166                                        flag_info.filename,
167                                        FromString<int32_t>(flag_info.default_value),
168                                        FromString<int32_t>(flag_info.current_value)));
169   } else if (flag_info.type == "uint32") {
170     ptr.reset(new DynamicFlag<uint32_t>(subprocess, flag_info.name,
171                                         flag_info.description,
172                                         flag_info.filename,
173                                         FromString<uint32_t>(flag_info.default_value),
174                                         FromString<uint32_t>(flag_info.current_value)));
175   } else if (flag_info.type == "int64") {
176     ptr.reset(new DynamicFlag<int64_t>(subprocess, flag_info.name,
177                                        flag_info.description,
178                                        flag_info.filename,
179                                        FromString<int64_t>(flag_info.default_value),
180                                        FromString<int64_t>(flag_info.current_value)));
181   } else if (flag_info.type == "uint64") {
182     ptr.reset(new DynamicFlag<uint64_t>(subprocess, flag_info.name,
183                                         flag_info.description,
184                                         flag_info.filename,
185                                         FromString<uint64_t>(flag_info.default_value),
186                                         FromString<uint64_t>(flag_info.current_value)));
187   } else if (flag_info.type == "double") {
188     ptr.reset(new DynamicFlag<double>(subprocess, flag_info.name,
189                                       flag_info.description,
190                                       flag_info.filename,
191                                       FromString<double>(flag_info.default_value),
192                                       FromString<double>(flag_info.current_value)));
193   } else if (flag_info.type == "string") {
194     ptr.reset(new DynamicFlag<std::string>(subprocess, flag_info.name,
195                                            flag_info.description,
196                                            flag_info.filename,
197                                            flag_info.default_value,
198                                            flag_info.current_value));
199   } else {
200     LOG(FATAL) << "Unknown type \"" << flag_info.type << "\" for flag " << flag_info.name;
201   }
202   return ptr;
203 }
204 
FlagsForSubprocess(std::string helpxml_output)205 std::vector<gflags::CommandLineFlagInfo> FlagsForSubprocess(std::string helpxml_output) {
206   auto xml_begin = helpxml_output.find("<?xml");
207   CHECK(xml_begin != std::string::npos)
208       << "No xml found in '" << helpxml_output << "'";
209   // Hack to try to filter out log messages that come before the xml
210   helpxml_output = helpxml_output.substr(xml_begin);
211 
212   xmlDocPtr doc = xmlReadMemory(helpxml_output.c_str(), helpxml_output.size(),
213                                 NULL, NULL, 0);
214   if (doc == NULL) {
215     LOG(FATAL) << "Could not parse xml of subprocess `--helpxml`";
216   }
217   xmlNodePtr root_element = xmlDocGetRootElement(doc);
218   std::vector<gflags::CommandLineFlagInfo> flags;
219   for (xmlNodePtr flag = root_element->children; flag != nullptr; flag = flag->next) {
220     if (std::strcmp((const char*) flag->name, "flag") != 0) {
221       continue;
222     }
223     gflags::CommandLineFlagInfo flag_info;
224     flag_info.name = xmlContent(xmlChildWithName(flag, "name"));
225     flag_info.type = xmlContent(xmlChildWithName(flag, "type"));
226     flag_info.filename = xmlContent(xmlChildWithName(flag, "file"));
227     flag_info.description = xmlContent(xmlChildWithName(flag, "meaning"));
228     flag_info.current_value = xmlContent(xmlChildWithName(flag, "current"));
229     flag_info.default_value = xmlContent(xmlChildWithName(flag, "default"));
230     flags.emplace_back(std::move(flag_info));
231   }
232   xmlFree(doc);
233   xmlCleanupParser();
234   return flags;
235 }
236 
237 } // namespace
238 
FlagForwarder(std::set<std::string> subprocesses,const std::vector<std::vector<std::string>> & args)239 FlagForwarder::FlagForwarder(std::set<std::string> subprocesses,
240                              const std::vector<std::vector<std::string>>& args)
241     : subprocesses_(std::move(subprocesses)) {
242   std::map<std::string, std::string> flag_to_type = CurrentFlagsToTypes();
243 
244   int subprocess_index = 0;
245   for (const auto& subprocess : subprocesses_) {
246     Command cmd(subprocess);
247     cmd.AddParameter("--helpxml");
248 
249     if (subprocess_index < args.size()) {
250       for (auto arg : args[subprocess_index]) {
251         cmd.AddParameter(arg);
252       }
253     }
254     subprocess_index++;
255 
256     std::string helpxml_input, helpxml_output, helpxml_error;
257     SubprocessOptions options;
258     options.Verbose(false);
259     int helpxml_ret =
260         RunWithManagedStdio(std::move(cmd), &helpxml_input, &helpxml_output,
261                             &helpxml_error, std::move(options));
262     if (helpxml_ret != 1) {
263       LOG(FATAL) << subprocess << " --helpxml returned unexpected response "
264                  << helpxml_ret << ". Stderr was " << helpxml_error;
265       return;
266     }
267 
268     auto subprocess_flags = FlagsForSubprocess(helpxml_output);
269     for (const auto& flag : subprocess_flags) {
270       if (flag_to_type.count(flag.name)) {
271         if (flag_to_type[flag.name] == flag.type) {
272           flags_.emplace(std::make_unique<SubprocessFlag>(subprocess, flag.name));
273         } else {
274           LOG(FATAL) << flag.name << "defined as " << flag_to_type[flag.name]
275                      << " and " << flag.type;
276           return;
277         }
278       } else {
279         flag_to_type[flag.name] = flag.type;
280         flags_.emplace(MakeDynamicFlag(subprocess, flag));
281       }
282     }
283   }
284 }
285 
286 // Destructor must be defined in an implementation file.
287 // https://stackoverflow.com/questions/6012157
288 FlagForwarder::~FlagForwarder() = default;
289 
UpdateFlagDefaults() const290 void FlagForwarder::UpdateFlagDefaults() const {
291 
292   for (const auto& subprocess : subprocesses_) {
293     Command cmd(subprocess);
294     std::vector<std::string> invocation = {subprocess};
295     for (const auto& flag : ArgvForSubprocess(subprocess)) {
296       cmd.AddParameter(flag);
297     }
298     // Disable flags that could cause the subprocess to exit before helpxml.
299     // See gflags_reporting.cc.
300     cmd.AddParameter("--nohelp");
301     cmd.AddParameter("--nohelpfull");
302     cmd.AddParameter("--nohelpshort");
303     cmd.AddParameter("--helpon=");
304     cmd.AddParameter("--helpmatch=");
305     cmd.AddParameter("--nohelppackage=");
306     cmd.AddParameter("--noversion");
307     // Ensure this is set on by putting it at the end.
308     cmd.AddParameter("--helpxml");
309     std::string helpxml_input, helpxml_output, helpxml_error;
310     auto options = SubprocessOptions().Verbose(false);
311     int helpxml_ret =
312         RunWithManagedStdio(std::move(cmd), &helpxml_input, &helpxml_output,
313                             &helpxml_error, std::move(options));
314     if (helpxml_ret != 1) {
315       LOG(FATAL) << subprocess << " --helpxml returned unexpected response "
316                  << helpxml_ret << ". Stderr was " << helpxml_error;
317       return;
318     }
319 
320     auto subprocess_flags = FlagsForSubprocess(helpxml_output);
321     for (const auto& flag : subprocess_flags) {
322       gflags::SetCommandLineOptionWithMode(
323           flag.name.c_str(),
324           flag.default_value.c_str(),
325           gflags::FlagSettingMode::SET_FLAGS_DEFAULT);
326     }
327   }
328 }
329 
330 // Hash table for repeatable flags (able to have repeated flag inputs)
331 static std::unordered_set<std::string> kRepeatableFlags = {
332     "custom_action_config", "custom_actions", "display", "touchpad"};
333 
ArgvForSubprocess(const std::string & subprocess,const std::vector<std::string> & args) const334 std::vector<std::string> FlagForwarder::ArgvForSubprocess(
335     const std::string& subprocess, const std::vector<std::string>& args) const {
336   std::vector<std::string> subprocess_argv;
337   std::map<std::string, std::vector<std::string>> name_to_value;
338 
339   if (!args.empty()) {
340     for (int index = 0; index < args.size(); index++) {
341       std::string_view argument = args[index];
342       if (!android::base::ConsumePrefix(&argument, "-")) {
343         continue;
344       }
345       android::base::ConsumePrefix(&argument, "-");
346       std::size_t qual_pos = argument.find('=');
347       if (qual_pos == std::string::npos) {
348         // to handle error cases: --flag value and -flag value
349         // but it only apply to repeatable flag case
350         if (Contains(kRepeatableFlags, argument)) {
351           // matched
352           LOG(FATAL) << subprocess
353                      << " has wrong flag input: " << args[index];
354         }
355         continue;
356       }
357       const std::string name(argument.substr(0, qual_pos));
358       const std::string value(
359           argument.substr(qual_pos + 1, argument.length() - qual_pos - 1));
360 
361       if (Contains(kRepeatableFlags, name)) {
362         // matched
363         if (!Contains(name_to_value, name)) {
364           // this flag is new
365           std::vector<std::string> values;
366           name_to_value[name] = values;
367         }
368         name_to_value[name].push_back(value);
369       }
370     }
371   }
372 
373   for (const auto& flag : flags_) {
374     if (flag->Subprocess() == subprocess) {
375       if (Contains(kRepeatableFlags, flag->Name()) &&
376           Contains(name_to_value, flag->Name())) {
377         // this is a repeatable flag with input values
378         for (const auto& value : name_to_value[flag->Name()]) {
379           subprocess_argv.push_back("--" + flag->Name() + "=" + value);
380         }
381       } else {
382         // normal case
383         gflags::CommandLineFlagInfo flag_info =
384             gflags::GetCommandLineFlagInfoOrDie(flag->Name().c_str());
385         if (!flag_info.is_default) {
386           subprocess_argv.push_back("--" + flag->Name() + "=" +
387                                     flag_info.current_value);
388         }
389       }
390     }
391   }
392   return subprocess_argv;
393 }
394 
395 }  // namespace cuttlefish
396