blob: 692e4539cc578326588c668050744627d9224bcc [file] [log] [blame]
//
// 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(),
&current_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;
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) {
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;
}