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 <android-base/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 ¤t_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 cuttlefish::Command cmd(subprocess);
238 cmd.AddParameter("--helpxml");
239 std::string helpxml_input, helpxml_output, helpxml_error;
240 cuttlefish::SubprocessOptions options;
241 options.Verbose(false);
242 int helpxml_ret = cuttlefish::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 cuttlefish::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 cuttlefish::SubprocessOptions options;
294 options.Verbose(false);
295 int helpxml_ret = cuttlefish::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