/* * Copyright (C) 2022 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 #include #include #include #include #include #include #include #include #include "common/libs/utils/contains.h" #include "common/libs/utils/result.h" #include "host/libs/config/cuttlefish_config.h" using grpc::InsecureChannelCredentials; namespace cuttlefish { namespace { constexpr char kCvdEnvHelpMessage[] = "cvd env: cuttlefish environment controller\n" "Basic usage: cvd [selector options] env [sub_command] [args] [options]\n" "Sub commands:\n" " ls: list services and methods for given arguments\n" " Usage: cvd [selector options] env ls [service] [method] [-l]\n" " service(optional) : gRPC service name\n" " method(optional) : method name for given service\n" " -l(optional) : Use a long listing format\n" " type: get detailed information for given request/reply type\n" " Usage: cvd [selector options] env type [service] [method] [type]\n" " service : gRPC service name\n" " method : method name in given service\n" " type : Protocol buffer type name in given method\n" " call: request a rpc with given method\n" " Usage: cvd [selector options] env call [service] [method] [request]\n" " service : gRPC service name\n" " method : method name in given service\n" " request : Protobuffer with text format\n\n" "* \"cvd [selector_options] env\" can be replaced with:\n" " \"cvd_internal_env [internal device name]\"\n"; bool PrintStream(std::stringstream* ss, const grpc::string& output) { (*ss) << output; return true; } class InsecureCliCredentials final : public grpc::testing::CliCredentials { public: std::shared_ptr GetChannelCredentials() const override { return InsecureChannelCredentials(); } const grpc::string GetCredentialUsage() const override { return ""; } }; std::vector ConvertToCharVec(const std::vector& str_vec) { std::vector char_vec; char_vec.reserve(str_vec.size()); for (const auto& str : str_vec) { char_vec.push_back(const_cast(str.c_str())); } return char_vec; } void RunGrpcCommand(const std::vector& arguments, std::stringstream& output_stream) { int grpc_cli_argc = arguments.size(); auto new_arguments = ConvertToCharVec(arguments); char** grpc_cli_argv = new_arguments.data(); grpc::testing::InitTest(&grpc_cli_argc, &grpc_cli_argv, true); grpc::testing::GrpcToolMainLib( grpc_cli_argc, (const char**)grpc_cli_argv, InsecureCliCredentials(), std::bind(PrintStream, &output_stream, std::placeholders::_1)); } std::string RunGrpcCommand(const std::vector& arguments) { std::stringstream output_stream; RunGrpcCommand(arguments, output_stream); return output_stream.str(); } std::vector GetServiceList(const std::string& server_address) { std::vector service_list; std::stringstream output_stream; std::vector arguments{"grpc_cli", "ls", server_address}; RunGrpcCommand(arguments, output_stream); std::string service_name; while (std::getline(output_stream, service_name)) { if (service_name.compare("grpc.reflection.v1alpha.ServerReflection") == 0) { continue; } service_list.emplace_back(service_name); } return service_list; } Result GetServerAddress( const std::vector& server_address_list, const std::string& service_name) { std::vector candidates; for (const auto& server_address : server_address_list) { for (auto& full_service_name : GetServiceList(server_address)) { if (android::base::EndsWith(full_service_name, service_name)) { candidates.emplace_back(server_address); break; } } } CF_EXPECT(candidates.size() > 0, service_name + " is not found."); CF_EXPECT(candidates.size() < 2, service_name + " is ambiguous."); return candidates[0]; } Result GetFullServiceName(const std::string& server_address, const std::string& service_name) { std::vector candidates; for (auto& full_service_name : GetServiceList(server_address)) { if (android::base::EndsWith(full_service_name, service_name)) { candidates.emplace_back(full_service_name); } } CF_EXPECT(candidates.size() > 0, service_name + " is not found."); CF_EXPECT(candidates.size() < 2, service_name + " is ambiguous."); return candidates[0]; } Result GetFullMethodName(const std::string& server_address, const std::string& service_name, const std::string& method_name) { const auto& full_service_name = CF_EXPECT(GetFullServiceName(server_address, service_name)); return full_service_name + "/" + method_name; } Result GetFullTypeName(const std::string& server_address, const std::string& service_name, const std::string& method_name, const std::string& type_name) { // Run grpc_cli ls -l for given method to extract full type name. // Example output: // rpc OpenwrtIpaddr(google.protobuf.Empty) returns // (openwrtcontrolserver.OpenwrtIpaddrReply) {} const auto& full_method_name = CF_EXPECT(GetFullMethodName(server_address, service_name, method_name)); std::vector grpc_arguments{"grpc_cli", "ls", "-l", server_address, full_method_name}; auto grpc_result = RunGrpcCommand(grpc_arguments); std::vector candidates; for (auto& full_type_name : android::base::Split(grpc_result, "()")) { if (android::base::EndsWith(full_type_name, type_name)) { candidates.emplace_back(full_type_name); } } CF_EXPECT(candidates.size() > 0, service_name + " is not found."); CF_EXPECT(candidates.size() < 2, service_name + " is ambiguous."); return candidates[0]; } Result HandleLsCmd(const std::vector& server_address_list, const std::vector& args, const std::vector& options) { CF_EXPECT(args.size() < 3, "too many arguments"); if (args.size() > 0) { std::vector grpc_arguments{"grpc_cli", "ls"}; const auto& service_name = args[0]; const auto& server_address = CF_EXPECT(GetServerAddress(server_address_list, service_name)); grpc_arguments.push_back(server_address); if (args.size() > 1) { // ls subcommand with 2 arguments; service_name and method_name const auto& method_name = args[1]; const auto& full_method_name = CF_EXPECT( GetFullMethodName(server_address, service_name, method_name)); grpc_arguments.push_back(full_method_name); } else { // ls subcommand with 1 argument; service_name const auto& full_service_name = CF_EXPECT(GetFullServiceName(server_address, service_name)); grpc_arguments.push_back(full_service_name); } grpc_arguments.insert(grpc_arguments.end(), options.begin(), options.end()); std::cout << RunGrpcCommand(grpc_arguments); } else { // ls subcommand with no arguments for (const auto& server_address : server_address_list) { std::vector grpc_arguments{"grpc_cli", "ls", server_address}; grpc_arguments.insert(grpc_arguments.end(), options.begin(), options.end()); std::cout << RunGrpcCommand(grpc_arguments); } } return {}; } Result HandleTypeCmd(const std::vector& server_address_list, const std::vector& args, const std::vector& options) { CF_EXPECT(args.size() > 2, "need to specify a service name, a method name, and type_name"); CF_EXPECT(args.size() < 4, "too many arguments"); std::vector grpc_arguments{"grpc_cli", "type"}; const auto& service_name = args[0]; const auto& method_name = args[1]; const auto& type_name = args[2]; const auto& server_address = CF_EXPECT(GetServerAddress(server_address_list, service_name)); grpc_arguments.push_back(server_address); const auto& full_type_name = CF_EXPECT( GetFullTypeName(server_address, service_name, method_name, type_name)); grpc_arguments.push_back(full_type_name); grpc_arguments.insert(grpc_arguments.end(), options.begin(), options.end()); std::cout << RunGrpcCommand(grpc_arguments); return {}; } Result HandleCallCmd(const std::vector& server_address_list, const std::vector& args, const std::vector& options) { CF_EXPECT(args.size() > 2, "need to specify a service name, a method name, and text-formatted " "proto"); CF_EXPECT(args.size() < 4, "too many arguments"); std::vector grpc_arguments{"grpc_cli", "call"}; // TODO(b/265384449): support the case without text-formatted proto. const auto& service_name = args[0]; const auto& method_name = args[1]; const auto& proto_text_format = args[2]; const auto& server_address = CF_EXPECT(GetServerAddress(server_address_list, service_name)); grpc_arguments.push_back(server_address); const auto& full_method_name = CF_EXPECT(GetFullMethodName(server_address, service_name, method_name)); grpc_arguments.push_back(full_method_name); grpc_arguments.push_back(proto_text_format); grpc_arguments.insert(grpc_arguments.end(), options.begin(), options.end()); std::cout << RunGrpcCommand(grpc_arguments); return {}; } bool ContainHelpOption(int argc, char** argv) { for (int i = 0; i < argc; i++) { if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-help") == 0) { return true; } } return false; } Result CvdEnvMain(int argc, char** argv) { ::android::base::InitLogging(argv, android::base::StderrLogger); if (ContainHelpOption(argc, argv)) { std::cout << kCvdEnvHelpMessage; return {}; } CF_EXPECT(argc >= 3, " need to specify a receiver and a command"); const auto& receiver = argv[1]; const auto& cmd = argv[2]; std::vector options; std::vector args; for (int i = 3; i < argc; i++) { if (android::base::StartsWith(argv[i], '-')) { options.push_back(argv[i]); } else { args.push_back(argv[i]); } } const auto* config = CuttlefishConfig::Get(); CF_EXPECT(config != nullptr, "Unable to find the config"); std::vector server_address_list; const auto& instances = config->Instances(); auto receiver_instance = std::find_if( begin(instances), end(instances), [&receiver](const auto& instance) { return receiver == instance.instance_name(); }); CF_EXPECT(receiver_instance != std::end(instances), "there is no instance of which name is " << receiver << ". please check instance name by cvd fleet"); for (const auto& entry : std::filesystem::directory_iterator( receiver_instance->grpc_socket_path())) { LOG(DEBUG) << "loading " << entry.path(); server_address_list.emplace_back("unix:" + entry.path().string()); } auto command_map = std::unordered_map( const std::vector&, const std::vector&, const std::vector&)>>{{ {"call", HandleCallCmd}, {"ls", HandleLsCmd}, {"type", HandleTypeCmd}, }}; CF_EXPECT(Contains(command_map, cmd), cmd << " isn't supported"); CF_EXPECT(command_map[cmd](server_address_list, args, options)); return {}; } } // namespace } // namespace cuttlefish int main(int argc, char** argv) { const auto& ret = cuttlefish::CvdEnvMain(argc, argv); CHECK(ret.ok()) << ret.error().Message(); return 0; }