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