Subcommand handlers should return the list of subcmds

For the sake of cvd-specific commandline arguments parser,
it should be known which operations/subcommands are valid.
Each subcommand handler is the best place to keep those,
and thus they should also return the list of their subcommands.

To test the new feature, cvd cmd-list is added but not
documented. The command will be removed in the coming CLs.

Bug: 262063179
Test: Run locally: cvd cmd-list
Change-Id: I706369f6653061f9cd5b0636560c8988cc885227
diff --git a/host/commands/cvd/acloud_command.cpp b/host/commands/cvd/acloud_command.cpp
index b8cf34e..4cdb16d 100644
--- a/host/commands/cvd/acloud_command.cpp
+++ b/host/commands/cvd/acloud_command.cpp
@@ -33,6 +33,7 @@
 #include "host/commands/cvd/instance_lock.h"
 #include "host/commands/cvd/server.h"
 #include "host/commands/cvd/server_client.h"
+#include "host/commands/cvd/types.h"
 #include "host/libs/config/cuttlefish_config.h"
 
 namespace cuttlefish {
@@ -384,6 +385,7 @@
            invocation.arguments.size() >= 1 &&
            invocation.arguments[0] == "create";
   }
+  cvd_common::Args CmdList() const override { return {}; }
   Result<cvd::Response> Handle(const RequestWithStdio& request) override {
     CF_EXPECT(converter_.Convert(request));
     return CF_ERR("Unreleased");
@@ -406,6 +408,9 @@
     return invocation.command == "acloud" && invocation.arguments.size() >= 1 &&
            invocation.arguments[0] == "create";
   }
+
+  cvd_common::Args CmdList() const override { return {}; }
+
   Result<cvd::Response> Handle(const RequestWithStdio& request) override {
     std::unique_lock interrupt_lock(interrupt_mutex_);
     if (interrupted_) {
diff --git a/host/commands/cvd/demo_multi_vd.cpp b/host/commands/cvd/demo_multi_vd.cpp
index 22b44a2..c228599 100644
--- a/host/commands/cvd/demo_multi_vd.cpp
+++ b/host/commands/cvd/demo_multi_vd.cpp
@@ -28,9 +28,10 @@
 #include "common/libs/utils/flag_parser.h"
 #include "common/libs/utils/result.h"
 #include "host/commands/cvd/command_sequence.h"
+#include "host/commands/cvd/instance_lock.h"
 #include "host/commands/cvd/server.h"
-#include "instance_lock.h"
-#include "server_client.h"
+#include "host/commands/cvd/server_client.h"
+#include "host/commands/cvd/types.h"
 
 namespace cuttlefish {
 
@@ -106,6 +107,7 @@
     response.mutable_command_response();
     return response;
   }
+
   Result<void> Interrupt() override {
     std::scoped_lock interrupt_lock(interrupt_mutex_);
     interrupted_ = true;
@@ -113,6 +115,8 @@
     return {};
   }
 
+  cvd_common::Args CmdList() const override { return {"experimental"}; }
+
   Result<DemoCommandSequence> CreateCommandSequence(
       const RequestWithStdio& request) {
     const auto& client_env = request.Message().command_request().env();
@@ -354,6 +358,7 @@
            invocation.arguments.size() >= 1 &&
            Presets().count(invocation.arguments[0]) > 0;
   }
+
   Result<cvd::Response> Handle(const RequestWithStdio& request) override {
     std::unique_lock interrupt_lock(interrupt_mutex_);
     if (interrupted_) {
@@ -391,6 +396,7 @@
     response.mutable_command_response();
     return response;
   }
+
   Result<void> Interrupt() override {
     std::scoped_lock interrupt_lock(interrupt_mutex_);
     interrupted_ = true;
@@ -398,6 +404,8 @@
     return {};
   }
 
+  cvd_common::Args CmdList() const override { return {"experimental"}; }
+
  private:
   CommandSequenceExecutor& executor_;
 
diff --git a/host/commands/cvd/help_command.cpp b/host/commands/cvd/help_command.cpp
index 36c3ab4..85e3a42 100644
--- a/host/commands/cvd/help_command.cpp
+++ b/host/commands/cvd/help_command.cpp
@@ -20,6 +20,7 @@
 
 #include "common/libs/fs/shared_buf.h"
 #include "host/commands/cvd/command_sequence.h"
+#include "host/commands/cvd/types.h"
 
 namespace cuttlefish {
 
@@ -91,6 +92,8 @@
     return {};
   }
 
+  cvd_common::Args CmdList() const override { return {"help"}; }
+
  private:
   std::mutex interruptible_;
   bool interrupted_ = false;
diff --git a/host/commands/cvd/load_configs.cpp b/host/commands/cvd/load_configs.cpp
index 37ae828..a837ee5 100644
--- a/host/commands/cvd/load_configs.cpp
+++ b/host/commands/cvd/load_configs.cpp
@@ -17,6 +17,7 @@
 
 #include <chrono>
 #include <mutex>
+#include <sstream>
 #include <string>
 
 #include <fruit/fruit.h>
@@ -28,7 +29,8 @@
 #include "host/commands/cvd/command_sequence.h"
 #include "host/commands/cvd/parser/load_configs_parser.h"
 #include "host/commands/cvd/server.h"
-#include "server_client.h"
+#include "host/commands/cvd/server_client.h"
+#include "host/commands/cvd/types.h"
 
 namespace cuttlefish {
 
@@ -46,7 +48,7 @@
 
   Result<bool> CanHandle(const RequestWithStdio& request) const override {
     auto invocation = ParseInvocation(request.Message());
-    return invocation.command == "load";
+    return invocation.command == kLoadSubCmd;
   }
 
   Result<cvd::Response> Handle(const RequestWithStdio& request) override {
@@ -73,6 +75,8 @@
     return {};
   }
 
+  cvd_common::Args CmdList() const override { return {kLoadSubCmd}; }
+
   Result<DemoCommandSequence> CreateCommandSequence(
       const RequestWithStdio& request) {
     bool help = false;
@@ -86,8 +90,10 @@
     CF_EXPECT(ParseFlags(flags, args));
 
     if (help) {
-      static constexpr char kHelp[] = "Usage: cvd load";
-      CF_EXPECT(WriteAll(request.Out(), kHelp, sizeof(kHelp)) == sizeof(kHelp));
+      std::stringstream help_msg_stream;
+      help_msg_stream << "Usage: cvd " << kLoadSubCmd;
+      const auto help_msg = help_msg_stream.str();
+      CF_EXPECT(WriteAll(request.Out(), help_msg) == help_msg.size());
       return {};
     }
 
@@ -140,6 +146,8 @@
   }
 
  private:
+  static constexpr char kLoadSubCmd[] = "load";
+
   CommandSequenceExecutor& executor_;
   InstanceLockFileManager& lock_file_manager_;
 
diff --git a/host/commands/cvd/main.cc b/host/commands/cvd/main.cc
index 5eaaf34..6a173ea 100644
--- a/host/commands/cvd/main.cc
+++ b/host/commands/cvd/main.cc
@@ -138,6 +138,11 @@
     return {};
   }
 
+  if (args.size() > 1 && android::base::Basename(args[0]) == "cvd" &&
+      args[1] == "cmd-list") {
+    return {};
+  }
+
   // TODO(schuffelen): Deduplicate when calls to setenv are removed.
   CF_EXPECT(client.HandleCommand(args, env, selector_args));
   return {};
diff --git a/host/commands/cvd/server.cc b/host/commands/cvd/server.cc
index 9989d67..c075cb8 100644
--- a/host/commands/cvd/server.cc
+++ b/host/commands/cvd/server.cc
@@ -29,6 +29,7 @@
 #include <android-base/file.h>
 #include <android-base/logging.h>
 #include <fruit/fruit.h>
+#include <json/json.h>
 
 #include "cvd_server.pb.h"
 
@@ -287,6 +288,52 @@
   return {};
 }
 
+static bool IsCmdListRequest(const RequestWithStdio& request) {
+  const auto& args = request.Message().command_request().args();
+  if (args.size() < 2) {
+    return false;
+  }
+  return (args[1] == "cmd-list");
+}
+
+/**
+ * Returns the list of valid subcommands that cvd server can handle.
+ *
+ * The server is in the best position to list all the subcommands. This list
+ * is, however, needed by the parser. For now, the parser is in the client
+ * side, and is planned to be moved to the server side eventually. Until it
+ * is done, we offer an undocumented (not present in the help message) subcmd
+ * that is "subcmd-list."  This is the implementation.
+ *
+ * TODO(kwstephenkim): move the argument separator parser from the client to
+ * the server side, and delete this function.
+ *
+ */
+static Result<cvd::Response> HandleCmdList(
+    const RequestWithStdio& request,
+    const std::vector<CvdServerHandler*>& handlers) {
+  std::unordered_set<std::string> subcmds;
+  for (const auto& handler : handlers) {
+    auto&& cmds_list = handler->CmdList();
+    for (const auto& cmd : cmds_list) {
+      subcmds.insert(cmd);
+    }
+  }
+  CF_EXPECT(!subcmds.empty());
+
+  cvd::Response response;
+  response.mutable_command_response();
+  response.mutable_status()->set_code(cvd::Status::OK);
+
+  // duplication removed
+  std::vector<std::string> subcmds_vec{subcmds.begin(), subcmds.end()};
+  const auto subcmds_str = android::base::Join(subcmds_vec, ",");
+  Json::Value subcmd_info;
+  subcmd_info["subcmd"] = subcmds_str;
+  WriteAll(request.Out(), subcmd_info.toStyledString());
+  return response;
+}
+
 // replace cvd -h or --help into cvd help
 static RequestWithStdio ReplaceHelp(RequestWithStdio&& src_request) {
   auto request = std::move(src_request);
@@ -318,6 +365,10 @@
   }
 
   auto possible_handlers = injector.getMultibindings<CvdServerHandler>();
+  if (IsCmdListRequest(request)) {
+    auto response = CF_EXPECT(HandleCmdList(request, possible_handlers));
+    return response;
+  }
 
   // Even if the interrupt callback outlives the request handler, it'll only
   // hold on to this struct which will be cleaned out when the request handler
diff --git a/host/commands/cvd/server.h b/host/commands/cvd/server.h
index 0ef40db..73daeac 100644
--- a/host/commands/cvd/server.h
+++ b/host/commands/cvd/server.h
@@ -36,6 +36,7 @@
 #include "host/commands/cvd/instance_manager.h"
 #include "host/commands/cvd/logger.h"
 #include "host/commands/cvd/server_client.h"
+#include "host/commands/cvd/types.h"
 #include "host/libs/config/inject.h"
 #include "host/libs/web/build_api.h"
 
@@ -48,6 +49,8 @@
   virtual Result<bool> CanHandle(const RequestWithStdio&) const = 0;
   virtual Result<cvd::Response> Handle(const RequestWithStdio&) = 0;
   virtual Result<void> Interrupt() = 0;
+  // returns the list of subcommand it can handle
+  virtual cvd_common::Args CmdList() const = 0;
 };
 
 class CvdServer {
diff --git a/host/commands/cvd/server_command_fetch_impl.cpp b/host/commands/cvd/server_command_fetch_impl.cpp
index 8890c6b..ec53841 100644
--- a/host/commands/cvd/server_command_fetch_impl.cpp
+++ b/host/commands/cvd/server_command_fetch_impl.cpp
@@ -18,6 +18,7 @@
 
 #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"
 
 namespace cuttlefish {
@@ -25,7 +26,7 @@
 
 Result<bool> CvdFetchHandler::CanHandle(const RequestWithStdio& request) const {
   auto invocation = ParseInvocation(request.Message());
-  return invocation.command == "fetch" || invocation.command == "fetch_cvd";
+  return Contains(fetch_cmd_list_, invocation.command);
 }
 
 Result<cvd::Response> CvdFetchHandler::Handle(const RequestWithStdio& request) {
diff --git a/host/commands/cvd/server_command_fetch_impl.h b/host/commands/cvd/server_command_fetch_impl.h
index 63f57b0..f6d7295 100644
--- a/host/commands/cvd/server_command_fetch_impl.h
+++ b/host/commands/cvd/server_command_fetch_impl.h
@@ -26,6 +26,7 @@
 #include "host/commands/cvd/server.h"
 #include "host/commands/cvd/server_command_impl.h"
 #include "host/commands/cvd/server_command_subprocess_waiter.h"
+#include "host/commands/cvd/types.h"
 
 namespace cuttlefish {
 namespace cvd_cmd_impl {
@@ -33,16 +34,19 @@
 class CvdFetchHandler : public CvdServerHandler {
  public:
   INJECT(CvdFetchHandler(SubprocessWaiter& subprocess_waiter))
-      : subprocess_waiter_(subprocess_waiter) {}
+      : subprocess_waiter_(subprocess_waiter),
+        fetch_cmd_list_{std::vector<std::string>{"fetch", "fetch_cvd"}} {}
 
   Result<bool> CanHandle(const RequestWithStdio& request) const override;
   Result<cvd::Response> Handle(const RequestWithStdio& request) override;
   Result<void> Interrupt() override;
+  cvd_common::Args CmdList() const override { return fetch_cmd_list_; }
 
  private:
   SubprocessWaiter& subprocess_waiter_;
   std::mutex interruptible_;
   bool interrupted_ = false;
+  std::vector<std::string> fetch_cmd_list_;
 };
 
 }  // namespace cvd_cmd_impl
diff --git a/host/commands/cvd/server_command_fleet_impl.cpp b/host/commands/cvd/server_command_fleet_impl.cpp
index 7fc8d66..09d8fa9 100644
--- a/host/commands/cvd/server_command_fleet_impl.cpp
+++ b/host/commands/cvd/server_command_fleet_impl.cpp
@@ -68,7 +68,7 @@
 
 Result<cvd::Status> CvdFleetCommandHandler::HandleCvdFleet(
     const uid_t uid, const SharedFD& out, const SharedFD& err,
-    const Args& cmd_args) const {
+    const cvd_common::Args& cmd_args) const {
   if (IsHelp(cmd_args)) {
     auto status = CF_EXPECT(CvdFleetHelp(out));
     return status;
@@ -77,7 +77,7 @@
   return status;
 }
 
-bool CvdFleetCommandHandler::IsHelp(const Args& args) const {
+bool CvdFleetCommandHandler::IsHelp(const cvd_common::Args& args) const {
   for (const auto& arg : args) {
     if (arg == "--help" || arg == "-help") {
       return true;
diff --git a/host/commands/cvd/server_command_fleet_impl.h b/host/commands/cvd/server_command_fleet_impl.h
index f58967a..fde2609 100644
--- a/host/commands/cvd/server_command_fleet_impl.h
+++ b/host/commands/cvd/server_command_fleet_impl.h
@@ -33,12 +33,12 @@
 #include "host/commands/cvd/instance_manager.h"
 #include "host/commands/cvd/server.h"
 #include "host/commands/cvd/server_command_subprocess_waiter.h"
+#include "host/commands/cvd/types.h"
 
 namespace cuttlefish {
 namespace cvd_cmd_impl {
 
-using Envs = std::unordered_map<std::string, std::string>;
-using Args = std::vector<std::string>;
+using Envs = cvd_common::Envs;
 
 class CvdFleetCommandHandler : public CvdServerHandler {
  public:
@@ -50,6 +50,7 @@
   Result<bool> CanHandle(const RequestWithStdio& request) const;
   Result<cvd::Response> Handle(const RequestWithStdio& request) override;
   Result<void> Interrupt() override;
+  cvd_common::Args CmdList() const override { return {kFleetSubcmd}; }
 
  private:
   InstanceManager& instance_manager_;
@@ -60,9 +61,9 @@
   static constexpr char kFleetSubcmd[] = "fleet";
   Result<cvd::Status> HandleCvdFleet(const uid_t uid, const SharedFD& out,
                                      const SharedFD& err,
-                                     const Args& cmd_args) const;
+                                     const cvd_common::Args& cmd_args) const;
   Result<cvd::Status> CvdFleetHelp(const SharedFD& out) const;
-  bool IsHelp(const Args& cmd_args) const;
+  bool IsHelp(const cvd_common::Args& cmd_args) const;
 };
 
 }  // namespace cvd_cmd_impl
diff --git a/host/commands/cvd/server_command_generic_impl.cpp b/host/commands/cvd/server_command_generic_impl.cpp
index 00996c3..b8adda1 100644
--- a/host/commands/cvd/server_command_generic_impl.cpp
+++ b/host/commands/cvd/server_command_generic_impl.cpp
@@ -121,6 +121,15 @@
   return ResponseFromSiginfo(infop);
 }
 
+std::vector<std::string> CvdCommandHandler::CmdList() const {
+  std::vector<std::string> subcmd_list;
+  subcmd_list.reserve(command_to_binary_map_.size());
+  for (const auto& [cmd, _] : command_to_binary_map_) {
+    subcmd_list.emplace_back(cmd);
+  }
+  return subcmd_list;
+}
+
 const std::map<std::string, std::string>
     CvdCommandHandler::command_to_binary_map_ = {
         {"host_bugreport", kHostBugreportBin},
diff --git a/host/commands/cvd/server_command_generic_impl.h b/host/commands/cvd/server_command_generic_impl.h
index c8af914..6d8ae5d 100644
--- a/host/commands/cvd/server_command_generic_impl.h
+++ b/host/commands/cvd/server_command_generic_impl.h
@@ -28,6 +28,7 @@
 #include "host/commands/cvd/server.h"
 #include "host/commands/cvd/server_command_impl.h"
 #include "host/commands/cvd/server_command_subprocess_waiter.h"
+#include "host/commands/cvd/types.h"
 
 namespace cuttlefish {
 namespace cvd_cmd_impl {
@@ -42,6 +43,7 @@
   Result<bool> CanHandle(const RequestWithStdio& request) const;
   Result<cvd::Response> Handle(const RequestWithStdio& request) override;
   Result<void> Interrupt() override;
+  cvd_common::Args CmdList() const override;
 
  private:
   InstanceManager& instance_manager_;
diff --git a/host/commands/cvd/server_command_start_impl.cpp b/host/commands/cvd/server_command_start_impl.cpp
index 635ffaa..353b478 100644
--- a/host/commands/cvd/server_command_start_impl.cpp
+++ b/host/commands/cvd/server_command_start_impl.cpp
@@ -192,6 +192,15 @@
   return IsHelpSubcmd(args);
 }
 
+std::vector<std::string> CvdStartCommandHandler::CmdList() const {
+  std::vector<std::string> subcmd_list;
+  subcmd_list.reserve(command_to_binary_map_.size());
+  for (const auto& [cmd, _] : command_to_binary_map_) {
+    subcmd_list.emplace_back(cmd);
+  }
+  return subcmd_list;
+}
+
 const std::map<std::string, std::string>
     CvdStartCommandHandler::command_to_binary_map_ = {
         {"start", kStartBin},
diff --git a/host/commands/cvd/server_command_start_impl.h b/host/commands/cvd/server_command_start_impl.h
index 8dfbaf5..80791c2 100644
--- a/host/commands/cvd/server_command_start_impl.h
+++ b/host/commands/cvd/server_command_start_impl.h
@@ -44,6 +44,7 @@
   Result<bool> CanHandle(const RequestWithStdio& request) const;
   Result<cvd::Response> Handle(const RequestWithStdio& request) override;
   Result<void> Interrupt() override;
+  std::vector<std::string> CmdList() const override;
 
  private:
   Result<void> UpdateInstanceDatabase(
diff --git a/host/commands/cvd/server_restart.cpp b/host/commands/cvd/server_restart.cpp
index 300ac8f..a19c07c 100644
--- a/host/commands/cvd/server_restart.cpp
+++ b/host/commands/cvd/server_restart.cpp
@@ -27,6 +27,7 @@
 #include "common/libs/fs/shared_fd.h"
 #include "common/libs/utils/result.h"
 #include "host/commands/cvd/instance_manager.h"
+#include "host/commands/cvd/types.h"
 #include "host/libs/web/build_api.h"
 
 namespace cuttlefish {
@@ -67,7 +68,7 @@
 
   Result<bool> CanHandle(const RequestWithStdio& request) const override {
     auto invocation = ParseInvocation(request.Message());
-    return android::base::Basename(invocation.command) == "restart-server";
+    return android::base::Basename(invocation.command) == kRestartServer;
   }
 
   Result<cvd::Response> Handle(const RequestWithStdio& request) override {
@@ -108,6 +109,8 @@
   }
 
   Result<void> Interrupt() override { return CF_ERR("Can't interrupt"); }
+  cvd_common::Args CmdList() const override { return {kRestartServer}; }
+  constexpr static char kRestartServer[] = "restart-server";
 
  private:
   BuildApi& build_api_;
diff --git a/host/commands/cvd/server_shutdown.cpp b/host/commands/cvd/server_shutdown.cpp
index 8560baf..213e8be 100644
--- a/host/commands/cvd/server_shutdown.cpp
+++ b/host/commands/cvd/server_shutdown.cpp
@@ -26,6 +26,7 @@
 #include "common/libs/fs/shared_fd.h"
 #include "common/libs/utils/result.h"
 #include "host/commands/cvd/instance_manager.h"
+#include "host/commands/cvd/types.h"
 
 namespace cuttlefish {
 namespace {
@@ -85,6 +86,9 @@
 
   Result<void> Interrupt() override { return CF_ERR("Can't interrupt"); }
 
+  // For now, shutdown isn't done by cvd shutdown.
+  cvd_common::Args CmdList() const override { return {}; }
+
  private:
   CvdServer& server_;
   InstanceManager& instance_manager_;
diff --git a/host/commands/cvd/server_version.cpp b/host/commands/cvd/server_version.cpp
index 2168c0f..1b5b866 100644
--- a/host/commands/cvd/server_version.cpp
+++ b/host/commands/cvd/server_version.cpp
@@ -17,12 +17,12 @@
 #include "host/commands/cvd/server.h"
 
 #include <build/version.h>
+#include <cvd_server.pb.h>
 #include <fruit/fruit.h>
 
-#include "cvd_server.pb.h"
-
 #include "common/libs/utils/result.h"
 #include "host/commands/cvd/server_constants.h"
+#include "host/commands/cvd/types.h"
 #include "host/libs/config/host_tools_version.h"
 
 namespace cuttlefish {
@@ -50,6 +50,8 @@
   }
 
   Result<void> Interrupt() override { return CF_ERR("Can't interrupt"); }
+
+  cvd_common::Args CmdList() const override { return {"version"}; }
 };
 
 }  // namespace