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(),
+                    &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;
+}
+
+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;
   {