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 <gflags/gflags.h>
27 #include <android-base/logging.h>
28 #include <libxml/tree.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 ¤t_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 // Hack to try to filter out log messages that come before the xml
205 helpxml_output = helpxml_output.substr(helpxml_output.find("<?xml"));
206
207 xmlDocPtr doc = xmlReadMemory(helpxml_output.c_str(), helpxml_output.size(),
208 NULL, NULL, 0);
209 if (doc == NULL) {
210 LOG(FATAL) << "Could not parse xml of subprocess `--helpxml`";
211 }
212 xmlNodePtr root_element = xmlDocGetRootElement(doc);
213 std::vector<gflags::CommandLineFlagInfo> flags;
214 for (xmlNodePtr flag = root_element->children; flag != nullptr; flag = flag->next) {
215 if (std::strcmp((const char*) flag->name, "flag") != 0) {
216 continue;
217 }
218 gflags::CommandLineFlagInfo flag_info;
219 flag_info.name = xmlContent(xmlChildWithName(flag, "name"));
220 flag_info.type = xmlContent(xmlChildWithName(flag, "type"));
221 flag_info.filename = xmlContent(xmlChildWithName(flag, "file"));
222 flag_info.description = xmlContent(xmlChildWithName(flag, "meaning"));
223 flag_info.current_value = xmlContent(xmlChildWithName(flag, "current"));
224 flag_info.default_value = xmlContent(xmlChildWithName(flag, "default"));
225 flags.emplace_back(std::move(flag_info));
226 }
227 xmlFree(doc);
228 xmlCleanupParser();
229 return flags;
230 }
231
232 } // namespace
233
FlagForwarder(std::set<std::string> subprocesses)234 FlagForwarder::FlagForwarder(std::set<std::string> subprocesses)
235 : subprocesses_(std::move(subprocesses)) {
236 std::map<std::string, std::string> flag_to_type = CurrentFlagsToTypes();
237
238 for (const auto& subprocess : subprocesses_) {
239 cuttlefish::Command cmd(subprocess);
240 cmd.AddParameter("--helpxml");
241 std::string helpxml_input, helpxml_output, helpxml_error;
242 cuttlefish::SubprocessOptions options;
243 options.Verbose(false);
244 int helpxml_ret = cuttlefish::RunWithManagedStdio(std::move(cmd), &helpxml_input,
245 &helpxml_output, &helpxml_error,
246 options);
247 if (helpxml_ret != 1) {
248 LOG(FATAL) << subprocess << " --helpxml returned unexpected response "
249 << helpxml_ret << ". Stderr was " << helpxml_error;
250 return;
251 }
252
253 auto subprocess_flags = FlagsForSubprocess(helpxml_output);
254 for (const auto& flag : subprocess_flags) {
255 if (flag_to_type.count(flag.name)) {
256 if (flag_to_type[flag.name] == flag.type) {
257 flags_.emplace(std::make_unique<SubprocessFlag>(subprocess, flag.name));
258 } else {
259 LOG(FATAL) << flag.name << "defined as " << flag_to_type[flag.name]
260 << " and " << flag.type;
261 return;
262 }
263 } else {
264 flag_to_type[flag.name] = flag.type;
265 flags_.emplace(MakeDynamicFlag(subprocess, flag));
266 }
267 }
268 }
269 }
270
271 // Destructor must be defined in an implementation file.
272 // https://stackoverflow.com/questions/6012157
273 FlagForwarder::~FlagForwarder() = default;
274
UpdateFlagDefaults() const275 void FlagForwarder::UpdateFlagDefaults() const {
276
277 for (const auto& subprocess : subprocesses_) {
278 cuttlefish::Command cmd(subprocess);
279 std::vector<std::string> invocation = {subprocess};
280 for (const auto& flag : ArgvForSubprocess(subprocess)) {
281 cmd.AddParameter(flag);
282 }
283 // Disable flags that could cause the subprocess to exit before helpxml.
284 // See gflags_reporting.cc.
285 cmd.AddParameter("--nohelp");
286 cmd.AddParameter("--nohelpfull");
287 cmd.AddParameter("--nohelpshort");
288 cmd.AddParameter("--helpon=");
289 cmd.AddParameter("--helpmatch=");
290 cmd.AddParameter("--nohelppackage=");
291 cmd.AddParameter("--noversion");
292 // Ensure this is set on by putting it at the end.
293 cmd.AddParameter("--helpxml");
294 std::string helpxml_input, helpxml_output, helpxml_error;
295 auto options = cuttlefish::SubprocessOptions().Verbose(false);
296 int helpxml_ret = cuttlefish::RunWithManagedStdio(std::move(cmd), &helpxml_input,
297 &helpxml_output, &helpxml_error,
298 options);
299 if (helpxml_ret != 1) {
300 LOG(FATAL) << subprocess << " --helpxml returned unexpected response "
301 << helpxml_ret << ". Stderr was " << helpxml_error;
302 return;
303 }
304
305 auto subprocess_flags = FlagsForSubprocess(helpxml_output);
306 for (const auto& flag : subprocess_flags) {
307 gflags::SetCommandLineOptionWithMode(
308 flag.name.c_str(),
309 flag.default_value.c_str(),
310 gflags::FlagSettingMode::SET_FLAGS_DEFAULT);
311 }
312 }
313 }
314
315 // Hash table for repeatable flags (able to have repeated flag inputs)
316 static std::unordered_set<std::string> kRepeatableFlags = {"custom_action_config",
317 "custom_actions"};
318
ArgvForSubprocess(const std::string & subprocess,const std::vector<std::string> & args) const319 std::vector<std::string> FlagForwarder::ArgvForSubprocess(
320 const std::string& subprocess, const std::vector<std::string>& args) const {
321 std::vector<std::string> subprocess_argv;
322 std::map<std::string, std::vector<std::string>> name_to_value;
323
324 if (!args.empty()) {
325 for (int index = 0; index < args.size(); index++) {
326 std::string_view argument = args[index];
327 if (!android::base::ConsumePrefix(&argument, "-")) {
328 continue;
329 }
330 android::base::ConsumePrefix(&argument, "-");
331 std::size_t qual_pos = argument.find('=');
332 if (qual_pos == std::string::npos) {
333 // to handle error cases: --flag value and -flag value
334 // but it only apply to repeatable flag case
335 if (cuttlefish::Contains(kRepeatableFlags, argument)) {
336 // matched
337 LOG(FATAL) << subprocess
338 << " has wrong flag input: " << args[index];
339 }
340 continue;
341 }
342 const std::string name(argument.substr(0, qual_pos));
343 const std::string value(
344 argument.substr(qual_pos + 1, argument.length() - qual_pos - 1));
345
346 if (cuttlefish::Contains(kRepeatableFlags, name)) {
347 // matched
348 if (!cuttlefish::Contains(name_to_value, name)) {
349 // this flag is new
350 std::vector<std::string> values;
351 name_to_value[name] = values;
352 }
353 name_to_value[name].push_back(value);
354 }
355 }
356 }
357
358 for (const auto& flag : flags_) {
359 if (flag->Subprocess() == subprocess) {
360 if (cuttlefish::Contains(kRepeatableFlags, flag->Name()) &&
361 cuttlefish::Contains(name_to_value, flag->Name())) {
362 // this is a repeatable flag with input values
363 for (const auto& value : name_to_value[flag->Name()]) {
364 subprocess_argv.push_back("--" + flag->Name() + "=" + value);
365 }
366 } else {
367 // normal case
368 gflags::CommandLineFlagInfo flag_info =
369 gflags::GetCommandLineFlagInfoOrDie(flag->Name().c_str());
370 if (!flag_info.is_default) {
371 subprocess_argv.push_back("--" + flag->Name() + "=" +
372 flag_info.current_value);
373 }
374 }
375 }
376 }
377 return subprocess_argv;
378 }
379