328 lines
13 KiB
C++
328 lines
13 KiB
C++
//
|
|
// Copyright (C) 2019 The Android Open Source Project
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
#include "flag_forwarder.h"
|
|
|
|
#include <cstring>
|
|
|
|
#include <sstream>
|
|
#include <map>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include <gflags/gflags.h>
|
|
#include <android-base/logging.h>
|
|
#include <libxml/tree.h>
|
|
|
|
#include "common/libs/fs/shared_buf.h"
|
|
#include "common/libs/fs/shared_fd.h"
|
|
#include "common/libs/utils/subprocess.h"
|
|
|
|
/**
|
|
* Superclass for a flag loaded from another process.
|
|
*
|
|
* An instance of this class defines a flag available either in this subprocess
|
|
* or another flag. If a flag needs to be registered in the current process, see
|
|
* the DynamicFlag subclass. If multiple subprocesses declare a flag with the
|
|
* same name, they all should receive that flag, but the DynamicFlag should only
|
|
* be created zero or one times. Zero times if the parent process defines it as
|
|
* well, one time if the parent does not define it.
|
|
*
|
|
* Notably, gflags itself defines some flags that are present in every binary.
|
|
*/
|
|
class SubprocessFlag {
|
|
std::string subprocess_;
|
|
std::string name_;
|
|
public:
|
|
SubprocessFlag(const std::string& subprocess, const std::string& name)
|
|
: subprocess_(subprocess), name_(name) {
|
|
}
|
|
virtual ~SubprocessFlag() = default;
|
|
SubprocessFlag(const SubprocessFlag&) = delete;
|
|
SubprocessFlag& operator=(const SubprocessFlag&) = delete;
|
|
SubprocessFlag(SubprocessFlag&&) = delete;
|
|
SubprocessFlag& operator=(SubprocessFlag&&) = delete;
|
|
|
|
const std::string& Subprocess() const { return subprocess_; }
|
|
const std::string& Name() const { return name_; }
|
|
};
|
|
|
|
/*
|
|
* A dynamic gflags flag. Creating an instance of this class is equivalent to
|
|
* registering a flag with DEFINE_<type>. Instances of this class should not
|
|
* be deleted while flags are still in use (likely through the end of main).
|
|
*
|
|
* This is implemented as a wrapper around gflags::FlagRegisterer. This class
|
|
* serves a dual purpose of holding the memory for gflags::FlagRegisterer as
|
|
* that normally expects memory to be held statically. The other reason is to
|
|
* subclass class SubprocessFlag to fit into the flag-forwarding scheme.
|
|
*/
|
|
template<typename T>
|
|
class DynamicFlag : public SubprocessFlag {
|
|
std::string help_;
|
|
std::string filename_;
|
|
T current_storage_;
|
|
T defvalue_storage_;
|
|
gflags::FlagRegisterer registerer_;
|
|
public:
|
|
DynamicFlag(const std::string& subprocess, const std::string& name,
|
|
const std::string& help, const std::string& filename,
|
|
const T& current, const T& defvalue)
|
|
: SubprocessFlag(subprocess, name), help_(help), filename_(filename),
|
|
current_storage_(current), defvalue_storage_(defvalue),
|
|
registerer_(Name().c_str(), help_.c_str(), filename_.c_str(),
|
|
¤t_storage_, &defvalue_storage_) {
|
|
}
|
|
};
|
|
|
|
namespace {
|
|
|
|
/**
|
|
* Returns a mapping between flag name and "gflags type" as strings for flags
|
|
* defined in the binary.
|
|
*/
|
|
std::map<std::string, std::string> CurrentFlagsToTypes() {
|
|
std::map<std::string, std::string> name_to_type;
|
|
std::vector<gflags::CommandLineFlagInfo> self_flags;
|
|
gflags::GetAllFlags(&self_flags);
|
|
for (auto& flag : self_flags) {
|
|
name_to_type[flag.name] = flag.type;
|
|
}
|
|
return name_to_type;
|
|
}
|
|
|
|
/**
|
|
* Returns a pointer to the child of `node` with name `name`.
|
|
*
|
|
* For example, invoking `xmlChildWithName(<foo><bar>abc</bar></foo>, "foo")`
|
|
* will return <bar>abc</bar>.
|
|
*/
|
|
xmlNodePtr xmlChildWithName(xmlNodePtr node, const std::string& name) {
|
|
for (xmlNodePtr child = node->children; child != nullptr; child = child->next) {
|
|
if (child->type != XML_ELEMENT_NODE) {
|
|
continue;
|
|
}
|
|
if (std::strcmp((const char*) child->name, name.c_str()) == 0) {
|
|
return child;
|
|
}
|
|
}
|
|
LOG(WARNING) << "no child with name " << name;
|
|
return nullptr;
|
|
}
|
|
|
|
/**
|
|
* Returns a string with the content of an xml node.
|
|
*
|
|
* For example, calling `xmlContent(<bar>abc</bar>)` will return "abc".
|
|
*/
|
|
std::string xmlContent(xmlNodePtr node) {
|
|
if (node == nullptr || node->children == NULL
|
|
|| node->children->type != xmlElementType::XML_TEXT_NODE) {
|
|
return "";
|
|
}
|
|
return std::string((char*) node->children->content);
|
|
}
|
|
|
|
template<typename T>
|
|
T FromString(const std::string& str) {
|
|
std::stringstream stream(str);
|
|
T output;
|
|
stream >> output;
|
|
return output;
|
|
}
|
|
|
|
/**
|
|
* Creates a dynamic flag
|
|
*/
|
|
std::unique_ptr<SubprocessFlag> MakeDynamicFlag(
|
|
const std::string& subprocess,
|
|
const gflags::CommandLineFlagInfo& flag_info) {
|
|
std::unique_ptr<SubprocessFlag> ptr;
|
|
if (flag_info.type == "bool") {
|
|
ptr.reset(new DynamicFlag<bool>(subprocess, flag_info.name,
|
|
flag_info.description,
|
|
flag_info.filename,
|
|
FromString<bool>(flag_info.default_value),
|
|
FromString<bool>(flag_info.current_value)));
|
|
} else if (flag_info.type == "int32") {
|
|
ptr.reset(new DynamicFlag<int32_t>(subprocess, flag_info.name,
|
|
flag_info.description,
|
|
flag_info.filename,
|
|
FromString<int32_t>(flag_info.default_value),
|
|
FromString<int32_t>(flag_info.current_value)));
|
|
} else if (flag_info.type == "uint32") {
|
|
ptr.reset(new DynamicFlag<uint32_t>(subprocess, flag_info.name,
|
|
flag_info.description,
|
|
flag_info.filename,
|
|
FromString<uint32_t>(flag_info.default_value),
|
|
FromString<uint32_t>(flag_info.current_value)));
|
|
} else if (flag_info.type == "int64") {
|
|
ptr.reset(new DynamicFlag<int64_t>(subprocess, flag_info.name,
|
|
flag_info.description,
|
|
flag_info.filename,
|
|
FromString<int64_t>(flag_info.default_value),
|
|
FromString<int64_t>(flag_info.current_value)));
|
|
} else if (flag_info.type == "uint64") {
|
|
ptr.reset(new DynamicFlag<uint64_t>(subprocess, flag_info.name,
|
|
flag_info.description,
|
|
flag_info.filename,
|
|
FromString<uint64_t>(flag_info.default_value),
|
|
FromString<uint64_t>(flag_info.current_value)));
|
|
} else if (flag_info.type == "double") {
|
|
ptr.reset(new DynamicFlag<double>(subprocess, flag_info.name,
|
|
flag_info.description,
|
|
flag_info.filename,
|
|
FromString<double>(flag_info.default_value),
|
|
FromString<double>(flag_info.current_value)));
|
|
} else if (flag_info.type == "string") {
|
|
ptr.reset(new DynamicFlag<std::string>(subprocess, flag_info.name,
|
|
flag_info.description,
|
|
flag_info.filename,
|
|
flag_info.default_value,
|
|
flag_info.current_value));
|
|
} else {
|
|
LOG(FATAL) << "Unknown type \"" << flag_info.type << "\" for flag " << flag_info.name;
|
|
}
|
|
return ptr;
|
|
}
|
|
|
|
std::vector<gflags::CommandLineFlagInfo> FlagsForSubprocess(std::string helpxml_output) {
|
|
// Hack to try to filter out log messages that come before the xml
|
|
helpxml_output = helpxml_output.substr(helpxml_output.find("<?xml"));
|
|
|
|
xmlDocPtr doc = xmlReadMemory(helpxml_output.c_str(), helpxml_output.size(),
|
|
NULL, NULL, 0);
|
|
if (doc == NULL) {
|
|
LOG(FATAL) << "Could not parse xml of subprocess `--helpxml`";
|
|
}
|
|
xmlNodePtr root_element = xmlDocGetRootElement(doc);
|
|
std::vector<gflags::CommandLineFlagInfo> flags;
|
|
for (xmlNodePtr flag = root_element->children; flag != nullptr; flag = flag->next) {
|
|
if (std::strcmp((const char*) flag->name, "flag") != 0) {
|
|
continue;
|
|
}
|
|
gflags::CommandLineFlagInfo flag_info;
|
|
flag_info.name = xmlContent(xmlChildWithName(flag, "name"));
|
|
flag_info.type = xmlContent(xmlChildWithName(flag, "type"));
|
|
flag_info.filename = xmlContent(xmlChildWithName(flag, "file"));
|
|
flag_info.description = xmlContent(xmlChildWithName(flag, "meaning"));
|
|
flag_info.current_value = xmlContent(xmlChildWithName(flag, "current"));
|
|
flag_info.default_value = xmlContent(xmlChildWithName(flag, "default"));
|
|
flags.emplace_back(std::move(flag_info));
|
|
}
|
|
xmlFree(doc);
|
|
xmlCleanupParser();
|
|
return flags;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
FlagForwarder::FlagForwarder(std::set<std::string> subprocesses)
|
|
: subprocesses_(std::move(subprocesses)) {
|
|
std::map<std::string, std::string> flag_to_type = CurrentFlagsToTypes();
|
|
|
|
for (const auto& subprocess : subprocesses_) {
|
|
cuttlefish::Command cmd(subprocess);
|
|
cmd.AddParameter("--helpxml");
|
|
std::string helpxml_input, helpxml_output, helpxml_error;
|
|
cuttlefish::SubprocessOptions options;
|
|
options.Verbose(false);
|
|
int helpxml_ret = cuttlefish::RunWithManagedStdio(std::move(cmd), &helpxml_input,
|
|
&helpxml_output, &helpxml_error,
|
|
options);
|
|
if (helpxml_ret != 1) {
|
|
LOG(FATAL) << subprocess << " --helpxml returned unexpected response "
|
|
<< helpxml_ret << ". Stderr was " << helpxml_error;
|
|
return;
|
|
}
|
|
|
|
auto subprocess_flags = FlagsForSubprocess(helpxml_output);
|
|
for (const auto& flag : subprocess_flags) {
|
|
if (flag_to_type.count(flag.name)) {
|
|
if (flag_to_type[flag.name] == flag.type) {
|
|
flags_.emplace(std::make_unique<SubprocessFlag>(subprocess, flag.name));
|
|
} else {
|
|
LOG(FATAL) << flag.name << "defined as " << flag_to_type[flag.name]
|
|
<< " and " << flag.type;
|
|
return;
|
|
}
|
|
} else {
|
|
flag_to_type[flag.name] = flag.type;
|
|
flags_.emplace(MakeDynamicFlag(subprocess, flag));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Destructor must be defined in an implementation file.
|
|
// https://stackoverflow.com/questions/6012157
|
|
FlagForwarder::~FlagForwarder() = default;
|
|
|
|
void FlagForwarder::UpdateFlagDefaults() const {
|
|
|
|
for (const auto& subprocess : subprocesses_) {
|
|
cuttlefish::Command cmd(subprocess);
|
|
std::vector<std::string> invocation = {subprocess};
|
|
for (const auto& flag : ArgvForSubprocess(subprocess)) {
|
|
cmd.AddParameter(flag);
|
|
}
|
|
// Disable flags that could cause the subprocess to exit before helpxml.
|
|
// See gflags_reporting.cc.
|
|
cmd.AddParameter("--nohelp");
|
|
cmd.AddParameter("--nohelpfull");
|
|
cmd.AddParameter("--nohelpshort");
|
|
cmd.AddParameter("--helpon=");
|
|
cmd.AddParameter("--helpmatch=");
|
|
cmd.AddParameter("--nohelppackage=");
|
|
cmd.AddParameter("--noversion");
|
|
// Ensure this is set on by putting it at the end.
|
|
cmd.AddParameter("--helpxml");
|
|
std::string helpxml_input, helpxml_output, helpxml_error;
|
|
auto options = cuttlefish::SubprocessOptions().Verbose(false);
|
|
int helpxml_ret = cuttlefish::RunWithManagedStdio(std::move(cmd), &helpxml_input,
|
|
&helpxml_output, &helpxml_error,
|
|
options);
|
|
if (helpxml_ret != 1) {
|
|
LOG(FATAL) << subprocess << " --helpxml returned unexpected response "
|
|
<< helpxml_ret << ". Stderr was " << helpxml_error;
|
|
return;
|
|
}
|
|
|
|
auto subprocess_flags = FlagsForSubprocess(helpxml_output);
|
|
for (const auto& flag : subprocess_flags) {
|
|
gflags::SetCommandLineOptionWithMode(
|
|
flag.name.c_str(),
|
|
flag.default_value.c_str(),
|
|
gflags::FlagSettingMode::SET_FLAGS_DEFAULT);
|
|
}
|
|
}
|
|
}
|
|
|
|
std::vector<std::string> FlagForwarder::ArgvForSubprocess(
|
|
const std::string& subprocess) const {
|
|
std::vector<std::string> subprocess_argv;
|
|
for (const auto& flag : flags_) {
|
|
if (flag->Subprocess() == subprocess) {
|
|
gflags::CommandLineFlagInfo flag_info =
|
|
gflags::GetCommandLineFlagInfoOrDie(flag->Name().c_str());
|
|
if (!flag_info.is_default) {
|
|
subprocess_argv.push_back("--" + flag->Name() + "=" + flag_info.current_value);
|
|
}
|
|
}
|
|
}
|
|
return subprocess_argv;
|
|
}
|
|
|