Forward launch_cvd flags to assemble_cvd and run_cvd.
This adds generic flag forwarding for subprocesses. It runs
subprocesses with "--helpxml" to dynamically find the flag definitions,
then registers these as if they were locally defined flags. It then
forwards the relevant flags to subprocesses when they are invoked.
Test: "launch_cvd --help", "launch_cvd --daemon"
Bug: 269465138
Change-Id: Icbfe366b2e6d4cf75ad84b1563a26eecd8ec5981
diff --git a/host/commands/assemble_cvd/flags.cc b/host/commands/assemble_cvd/flags.cc
index 5bca5cf..edda7dd 100644
--- a/host/commands/assemble_cvd/flags.cc
+++ b/host/commands/assemble_cvd/flags.cc
@@ -570,11 +570,6 @@
}
bool ParseCommandLineFlags(int* argc, char*** argv) {
- // The config_file is created by the launcher, so the launcher is the only
- // host process that doesn't use the flag.
- // Set the default to empty.
- google::SetCommandLineOptionWithMode("config_file", "",
- gflags::SET_FLAGS_DEFAULT);
google::ParseCommandLineNonHelpFlags(argc, argv, true);
bool invalid_manager = false;
if (FLAGS_vm_manager == vm_manager::QemuManager::name()) {
diff --git a/host/commands/launch/Android.bp b/host/commands/launch/Android.bp
index a233622..f20d9f4 100644
--- a/host/commands/launch/Android.bp
+++ b/host/commands/launch/Android.bp
@@ -16,6 +16,7 @@
cc_binary_host {
name: "launch_cvd",
srcs: [
+ "flag_forwarder.cc",
"launch_cvd.cc",
],
header_libs: [
diff --git a/host/commands/launch/flag_forwarder.cc b/host/commands/launch/flag_forwarder.cc
new file mode 100644
index 0000000..d310833
--- /dev/null
+++ b/host/commands/launch/flag_forwarder.cc
@@ -0,0 +1,351 @@
+//
+// 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 <glog/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;
+}
+
+std::string CaptureSubprocessStdout(cvd::Command cmd) {
+ // TODO(b/141889929): Unify this with execute_capture_output.
+ cmd.SetVerbose(false);
+ auto dev_null = cvd::SharedFD::Open("/dev/null", O_RDONLY);
+ if (!dev_null->IsOpen()) {
+ LOG(ERROR) << "Failed to open /dev/null: " << dev_null->StrError();
+ return "";
+ }
+ cmd.RedirectStdIO(cvd::Subprocess::StdIOChannel::kStdIn, dev_null);
+ cvd::SharedFD pipe_read, pipe_write;
+ cvd::SharedFD::Pipe(&pipe_read, &pipe_write);
+ cmd.RedirectStdIO(cvd::Subprocess::StdIOChannel::kStdOut, pipe_write);
+ auto subprocess = cmd.Start();
+ if (!subprocess.Started()) {
+ LOG(ERROR) << "Could not start subprocess";
+ return "";
+ }
+ {
+ pipe_write->Close();
+ // Force the destructor to run by moving it into a smaller scope.
+ // This is necessary to close the write end of the pipe.
+ cvd::Command forceDelete = std::move(cmd);
+ }
+ std::string output;
+ int read = cvd::ReadAll(pipe_read, &output);
+ if (read < 0) {
+ LOG(ERROR) << "Could not read from pipe in CaptureSubprocessStdout";
+ return "";
+ }
+ int status;
+ subprocess.Wait(&status, 0);
+ if (!WIFEXITED(status)) {
+ LOG(ERROR) << "Subprocess exited with abnormal conditions.";
+ return "";
+ }
+ if (WEXITSTATUS(status) != 1) {
+ LOG(ERROR) << "Subprocess exited with status " << WEXITSTATUS(status)
+ << ", expected 1";
+ return "";
+ }
+ 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_) {
+ cvd::Command cmd(subprocess);
+ cmd.AddParameter("--helpxml");
+ std::string helpxml_output = CaptureSubprocessStdout(std::move(cmd));
+
+ 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_) {
+ cvd::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_output = CaptureSubprocessStdout(std::move(cmd));
+
+ 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;
+}
+
diff --git a/host/commands/launch/flag_forwarder.h b/host/commands/launch/flag_forwarder.h
new file mode 100644
index 0000000..b0f5cfe
--- /dev/null
+++ b/host/commands/launch/flag_forwarder.h
@@ -0,0 +1,39 @@
+//
+// 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.
+
+#pragma once
+
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+class SubprocessFlag;
+
+class FlagForwarder {
+ std::set<std::string> subprocesses_;
+ std::set<std::unique_ptr<SubprocessFlag>> flags_;
+
+public:
+ FlagForwarder(std::set<std::string> subprocesses);
+ ~FlagForwarder();
+ FlagForwarder(FlagForwarder&&) = default;
+ FlagForwarder(const FlagForwarder&) = delete;
+ FlagForwarder& operator=(FlagForwarder&&) = default;
+ FlagForwarder& operator=(const FlagForwarder&) = delete;
+
+ void UpdateFlagDefaults() const;
+ std::vector<std::string> ArgvForSubprocess(const std::string& subprocess) const;
+};
diff --git a/host/commands/launch/launch_cvd.cc b/host/commands/launch/launch_cvd.cc
index 297e5ae..857b471 100644
--- a/host/commands/launch/launch_cvd.cc
+++ b/host/commands/launch/launch_cvd.cc
@@ -13,25 +13,36 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+#include <gflags/gflags.h>
#include <glog/logging.h>
#include "common/libs/fs/shared_fd.h"
#include "common/libs/utils/subprocess.h"
#include "host/libs/config/cuttlefish_config.h"
+#include "flag_forwarder.h"
+
namespace {
-cvd::Subprocess StartAssembler(int argc, char** argv, cvd::SharedFD assembler_stdout) {
- cvd::Command assemble_cmd(vsoc::DefaultHostArtifactsPath("bin/assemble_cvd"));
- for (int i = 1; i < argc; i++) {
- assemble_cmd.AddParameter(argv[i]);
+std::string kAssemblerBin = vsoc::DefaultHostArtifactsPath("bin/assemble_cvd");
+std::string kRunnerBin = vsoc::DefaultHostArtifactsPath("bin/run_cvd");
+
+cvd::Subprocess StartAssembler(cvd::SharedFD assembler_stdout,
+ const std::vector<std::string>& argv) {
+ cvd::Command assemble_cmd(kAssemblerBin);
+ for (const auto& arg : argv) {
+ assemble_cmd.AddParameter(arg);
}
assemble_cmd.RedirectStdIO(cvd::Subprocess::StdIOChannel::kStdOut, assembler_stdout);
return assemble_cmd.Start();
}
-cvd::Subprocess StartRunner(cvd::SharedFD runner_stdin) {
- cvd::Command run_cmd(vsoc::DefaultHostArtifactsPath("bin/run_cvd"));
+cvd::Subprocess StartRunner(cvd::SharedFD runner_stdin,
+ const std::vector<std::string>& argv) {
+ cvd::Command run_cmd(kRunnerBin);
+ for (const auto& arg : argv) {
+ run_cmd.AddParameter(arg);
+ }
run_cmd.RedirectStdIO(cvd::Subprocess::StdIOChannel::kStdIn, runner_stdin);
return run_cmd.Start();
}
@@ -41,13 +52,23 @@
int main(int argc, char** argv) {
::android::base::InitLogging(argv, android::base::StderrLogger);
+ FlagForwarder forwarder({kAssemblerBin, kRunnerBin});
+
+ gflags::ParseCommandLineNonHelpFlags(&argc, &argv, false);
+
+ forwarder.UpdateFlagDefaults();
+
+ gflags::HandleCommandLineHelpFlags();
+
cvd::SharedFD assembler_stdout, runner_stdin;
cvd::SharedFD::Pipe(&runner_stdin, &assembler_stdout);
// SharedFDs are std::move-d in to avoid dangling references.
// Removing the std::move will probably make run_cvd hang as its stdin never closes.
- auto assemble_proc = StartAssembler(argc, argv, std::move(assembler_stdout));
- auto run_proc = StartRunner(std::move(runner_stdin));
+ auto assemble_proc = StartAssembler(std::move(assembler_stdout),
+ forwarder.ArgvForSubprocess(kAssemblerBin));
+ auto run_proc = StartRunner(std::move(runner_stdin),
+ forwarder.ArgvForSubprocess(kRunnerBin));
auto assemble_ret = assemble_proc.Wait();
if (assemble_ret != 0) {
diff --git a/host/commands/run_cvd/main.cc b/host/commands/run_cvd/main.cc
index 1aa42d2..948e8d5 100644
--- a/host/commands/run_cvd/main.cc
+++ b/host/commands/run_cvd/main.cc
@@ -320,8 +320,9 @@
} // namespace
-int main(int, char** argv) {
+int main(int argc, char** argv) {
::android::base::InitLogging(argv, android::base::StderrLogger);
+ google::ParseCommandLineFlags(&argc, &argv, false);
std::string input_files_str;
{