// // 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 #include #include #include #include #include #include #include #include #include "common/libs/fs/shared_buf.h" #include "common/libs/fs/shared_fd.h" #include "common/libs/utils/contains.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_. 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 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 CurrentFlagsToTypes() { std::map name_to_type; std::vector 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(abc, "foo")` * will return abc. */ 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(abc)` 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 T FromString(const std::string& str) { std::stringstream stream(str); T output; stream >> output; return output; } /** * Creates a dynamic flag */ std::unique_ptr MakeDynamicFlag( const std::string& subprocess, const gflags::CommandLineFlagInfo& flag_info) { std::unique_ptr ptr; if (flag_info.type == "bool") { ptr.reset(new DynamicFlag(subprocess, flag_info.name, flag_info.description, flag_info.filename, FromString(flag_info.default_value), FromString(flag_info.current_value))); } else if (flag_info.type == "int32") { ptr.reset(new DynamicFlag(subprocess, flag_info.name, flag_info.description, flag_info.filename, FromString(flag_info.default_value), FromString(flag_info.current_value))); } else if (flag_info.type == "uint32") { ptr.reset(new DynamicFlag(subprocess, flag_info.name, flag_info.description, flag_info.filename, FromString(flag_info.default_value), FromString(flag_info.current_value))); } else if (flag_info.type == "int64") { ptr.reset(new DynamicFlag(subprocess, flag_info.name, flag_info.description, flag_info.filename, FromString(flag_info.default_value), FromString(flag_info.current_value))); } else if (flag_info.type == "uint64") { ptr.reset(new DynamicFlag(subprocess, flag_info.name, flag_info.description, flag_info.filename, FromString(flag_info.default_value), FromString(flag_info.current_value))); } else if (flag_info.type == "double") { ptr.reset(new DynamicFlag(subprocess, flag_info.name, flag_info.description, flag_info.filename, FromString(flag_info.default_value), FromString(flag_info.current_value))); } else if (flag_info.type == "string") { ptr.reset(new DynamicFlag(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 FlagsForSubprocess(std::string helpxml_output) { auto xml_begin = helpxml_output.find(" 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 subprocesses, const std::vector>& args) : subprocesses_(std::move(subprocesses)) { std::map flag_to_type = CurrentFlagsToTypes(); int subprocess_index = 0; for (const auto& subprocess : subprocesses_) { cuttlefish::Command cmd(subprocess); cmd.AddParameter("--helpxml"); if (subprocess_index < args.size()) { for (auto arg : args[subprocess_index]) { cmd.AddParameter(arg); } } subprocess_index++; 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, std::move(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(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 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, std::move(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); } } } // Hash table for repeatable flags (able to have repeated flag inputs) static std::unordered_set kRepeatableFlags = { "custom_action_config", "custom_actions", "display", "touchpad"}; std::vector FlagForwarder::ArgvForSubprocess( const std::string& subprocess, const std::vector& args) const { std::vector subprocess_argv; std::map> name_to_value; if (!args.empty()) { for (int index = 0; index < args.size(); index++) { std::string_view argument = args[index]; if (!android::base::ConsumePrefix(&argument, "-")) { continue; } android::base::ConsumePrefix(&argument, "-"); std::size_t qual_pos = argument.find('='); if (qual_pos == std::string::npos) { // to handle error cases: --flag value and -flag value // but it only apply to repeatable flag case if (cuttlefish::Contains(kRepeatableFlags, argument)) { // matched LOG(FATAL) << subprocess << " has wrong flag input: " << args[index]; } continue; } const std::string name(argument.substr(0, qual_pos)); const std::string value( argument.substr(qual_pos + 1, argument.length() - qual_pos - 1)); if (cuttlefish::Contains(kRepeatableFlags, name)) { // matched if (!cuttlefish::Contains(name_to_value, name)) { // this flag is new std::vector values; name_to_value[name] = values; } name_to_value[name].push_back(value); } } } for (const auto& flag : flags_) { if (flag->Subprocess() == subprocess) { if (cuttlefish::Contains(kRepeatableFlags, flag->Name()) && cuttlefish::Contains(name_to_value, flag->Name())) { // this is a repeatable flag with input values for (const auto& value : name_to_value[flag->Name()]) { subprocess_argv.push_back("--" + flag->Name() + "=" + value); } } else { // normal case 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; }