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