/* * 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 "client.h" #include #include #include #include #include #include "common/libs/fs/shared_buf.h" #include "common/libs/fs/shared_fd.h" #include "common/libs/utils/environment.h" #include "common/libs/utils/subprocess.h" #include "host/commands/cvd/common_utils.h" #include "host/commands/cvd/server_constants.h" #include "host/libs/config/host_tools_version.h" namespace cuttlefish { namespace { Result ConnectToServer() { auto connection = SharedFD::SocketLocalClient(cvd::kServerSocketPath, /*is_abstract=*/true, SOCK_SEQPACKET); if (!connection->IsOpen()) { auto connection = SharedFD::SocketLocalClient(cvd::kServerSocketPath, /*is_abstract=*/true, SOCK_STREAM); } if (!connection->IsOpen()) { return CF_ERR("Failed to connect to server" << connection->StrError()); } return connection; } [[noreturn]] void CallPythonAcloud(std::vector& args) { auto android_top = StringFromEnv("ANDROID_BUILD_TOP", ""); if (android_top == "") { LOG(FATAL) << "Could not find android environment. Please run " << "\"source build/envsetup.sh\"."; abort(); } // TODO(b/206893146): Detect what the platform actually is. auto py_acloud_path = android_top + "/prebuilts/asuite/acloud/linux-x86/acloud"; std::unique_ptr new_argv(new char*[args.size() + 1]); for (size_t i = 0; i < args.size(); i++) { new_argv[i] = args[i].data(); } new_argv[args.size()] = nullptr; execv(py_acloud_path.data(), new_argv.get()); PLOG(FATAL) << "execv(" << py_acloud_path << ", ...) failed"; abort(); } } // end of namespace cvd::Version CvdClient::GetClientVersion() { cvd::Version client_version; client_version.set_major(cvd::kVersionMajor); client_version.set_minor(cvd::kVersionMinor); client_version.set_build(android::build::GetBuildNumber()); client_version.set_crc32(FileCrc(kServerExecPath)); return client_version; } Result CvdClient::GetServerVersion() { cvd::Request request; request.mutable_version_request(); auto response = SendRequest(request); // If cvd_server is not running, start and wait before checking its version. if (!response.ok()) { CF_EXPECT(StartCvdServer()); response = CF_EXPECT(SendRequest(request)); } CF_EXPECT(CheckStatus(response->status(), "GetVersion")); CF_EXPECT(response->has_version_response(), "GetVersion call missing VersionResponse."); return response->version_response().version(); } Result CvdClient::ValidateServerVersion(const int num_retries) { auto server_version = CF_EXPECT(GetServerVersion()); if (server_version.major() != cvd::kVersionMajor) { return CF_ERR("Major version difference: cvd(" << cvd::kVersionMajor << "." << cvd::kVersionMinor << ") != cvd_server(" << server_version.major() << "." << server_version.minor() << "). Try `cvd kill-server` or `pkill cvd_server`."); } if (server_version.minor() < cvd::kVersionMinor) { std::cerr << "Minor version of cvd_server is older than latest. " << "Attempting to restart..." << std::endl; CF_EXPECT(StopCvdServer(/*clear=*/false)); CF_EXPECT(StartCvdServer()); if (num_retries > 0) { CF_EXPECT(ValidateServerVersion(num_retries - 1)); return {}; } else { return CF_ERR("Unable to start the cvd_server with version " << cvd::kVersionMajor << "." << cvd::kVersionMinor); } } if (server_version.build() != android::build::GetBuildNumber()) { LOG(VERBOSE) << "cvd_server client version (" << android::build::GetBuildNumber() << ") does not match server version (" << server_version.build() << std::endl; } auto self_crc32 = FileCrc(kServerExecPath); if (server_version.crc32() != self_crc32) { LOG(VERBOSE) << "cvd_server client checksum (" << self_crc32 << ") doesn't match server checksum (" << server_version.crc32() << std::endl; } return {}; } Result CvdClient::StopCvdServer(bool clear) { if (!server_) { // server_ may not represent a valid connection even while the server is // running, if we haven't tried to connect. This establishes first whether // the server is running. auto connection_attempt = ConnectToServer(); if (!connection_attempt.ok()) { return {}; } } cvd::Request request; auto shutdown_request = request.mutable_shutdown_request(); if (clear) { shutdown_request->set_clear(true); } // Send the server a pipe with the Shutdown request that it // will close when it fully exits. SharedFD read_pipe, write_pipe; CF_EXPECT(cuttlefish::SharedFD::Pipe(&read_pipe, &write_pipe), "Unable to create shutdown pipe: " << strerror(errno)); auto response = SendRequest(request, OverrideFd{/* override none of 0, 1, 2 */}, /*extra_fd=*/write_pipe); // If the server is already not running then SendRequest will fail. // We treat this as success. if (!response.ok()) { server_.reset(); return {}; } CF_EXPECT(CheckStatus(response->status(), "Shutdown")); CF_EXPECT(response->has_shutdown_response(), "Shutdown call missing ShutdownResponse."); // Clear out the server_ socket. server_.reset(); // Close the write end of the pipe in this process. Now the only // process that may have the write end still open is the cvd_server. write_pipe->Close(); // Wait for the pipe to close by attempting to read from the pipe. char buf[1]; // Any size >0 should work for read attempt. CF_EXPECT(read_pipe->Read(buf, sizeof(buf)) <= 0, "Unexpected read value from cvd_server shutdown pipe."); return {}; } Result CvdClient::HandleCommand( const std::vector& cvd_process_args, const std::unordered_map& env, const std::vector& selector_args, const OverrideFd& new_control_fd) { std::optional exe_fd; // actual commandline arguments are packed in selector_args if (selector_args.size() > 2 && android::base::Basename(selector_args[0]) == "cvd" && selector_args[1] == "restart-server" && selector_args[2] == "match-client") { exe_fd = SharedFD::Open(kServerExecPath, O_RDONLY); CF_EXPECT((*exe_fd)->IsOpen(), "Failed to open \"" << kServerExecPath << "\": \"" << (*exe_fd)->StrError() << "\""); } cvd::Request request = MakeRequest({.cmd_args = cvd_process_args, .env = env, .selector_args = selector_args}, cvd::WAIT_BEHAVIOR_COMPLETE); auto response = CF_EXPECT(SendRequest(request, new_control_fd, exe_fd)); CF_EXPECT(CheckStatus(response.status(), "HandleCommand")); CF_EXPECT(response.has_command_response(), "HandleCommand call missing CommandResponse."); return {response}; } Result CvdClient::SetServer(const SharedFD& server) { CF_EXPECT(!server_, "Already have a server"); CF_EXPECT(server->IsOpen(), server->StrError()); server_ = UnixMessageSocket(server); CF_EXPECT(server_->EnableCredentials(true).ok(), "Unable to enable UnixMessageSocket credentials."); return {}; } Result CvdClient::SendRequest(const cvd::Request& request, const OverrideFd& new_control_fds, std::optional extra_fd) { if (!server_) { CF_EXPECT(SetServer(CF_EXPECT(ConnectToServer()))); } // Serialize and send the request. std::string serialized; CF_EXPECT(request.SerializeToString(&serialized), "Unable to serialize request proto."); UnixSocketMessage request_message; std::vector control_fds = { (new_control_fds.stdin_override_fd ? *new_control_fds.stdin_override_fd : SharedFD::Dup(0)), (new_control_fds.stdout_override_fd ? *new_control_fds.stdout_override_fd : SharedFD::Dup(1)), (new_control_fds.stderr_override_fd ? *new_control_fds.stderr_override_fd : SharedFD::Dup(2))}; if (extra_fd) { control_fds.push_back(*extra_fd); } auto control = CF_EXPECT(ControlMessage::FromFileDescriptors(control_fds)); request_message.control.emplace_back(std::move(control)); request_message.data = std::vector(serialized.begin(), serialized.end()); CF_EXPECT(server_->WriteMessage(request_message)); // Read and parse the response. auto read_result = CF_EXPECT(server_->ReadMessage()); serialized = std::string(read_result.data.begin(), read_result.data.end()); cvd::Response response; CF_EXPECT(response.ParseFromString(serialized), "Unable to parse serialized response proto."); return response; } Result CvdClient::StartCvdServer() { SharedFD server_fd = SharedFD::SocketLocalServer(cvd::kServerSocketPath, /*is_abstract=*/true, SOCK_SEQPACKET, 0666); CF_EXPECT(server_fd->IsOpen(), server_fd->StrError()); Command command(kServerExecPath); command.AddParameter("-INTERNAL_server_fd=", server_fd); SubprocessOptions options; options.ExitWithParent(false); command.Start(options); // Connect to the server_fd, which waits for startup. CF_EXPECT(SetServer(SharedFD::SocketLocalClient(cvd::kServerSocketPath, /*is_abstract=*/true, SOCK_SEQPACKET))); return {}; } Result CvdClient::CheckStatus(const cvd::Status& status, const std::string& rpc) { if (status.code() == cvd::Status::OK) { return {}; } return CF_ERR("Received error response for \"" << rpc << "\":\n" << status.message() << "\nIn client"); } Result CvdClient::HandleAcloud( const std::vector& args, const std::unordered_map& env) { auto server_running = ValidateServerVersion(); std::vector args_copy{args}; // TODO(b/206893146): Make this decision inside the server. if (!server_running.ok()) { CallPythonAcloud(args_copy); // no return } args_copy[0] = "try-acloud"; auto attempt = HandleCommand(args_copy, env, {}); if (!attempt.ok()) { CallPythonAcloud(args_copy); // no return } args_copy[0] = "acloud"; CF_EXPECT(HandleCommand(args_copy, env, {})); return {}; } Result CvdClient::HandleVersion() { using google::protobuf::TextFormat; std::stringstream result; std::string output; auto server_version = CF_EXPECT(GetServerVersion()); CF_EXPECT(TextFormat::PrintToString(server_version, &output), "converting server_version to string failed"); result << "Server version:" << std::endl << std::endl << output << std::endl; CF_EXPECT(TextFormat::PrintToString(CvdClient::GetClientVersion(), &output), "converting client version to string failed"); result << "Client version:" << std::endl << std::endl << output << std::endl; return {result.str()}; } Result CvdClient::ListSubcommands(const cvd_common::Envs& envs) { cvd_common::Args args{"cvd", "cmd-list"}; SharedFD read_pipe, write_pipe; CF_EXPECT(cuttlefish::SharedFD::Pipe(&read_pipe, &write_pipe), "Unable to create shutdown pipe: " << strerror(errno)); OverrideFd new_control_fd{.stdout_override_fd = write_pipe}; CF_EXPECT( HandleCommand(args, envs, std::vector{}, new_control_fd)); write_pipe->Close(); const int kChunkSize = 512; char buf[kChunkSize + 1] = {0}; std::stringstream ss; do { auto n_read = ReadExact(read_pipe, buf, kChunkSize); CF_EXPECT(n_read >= 0 && (n_read <= kChunkSize)); if (n_read == 0) { break; } buf[n_read] = 0; // null-terminate the C-style string ss << buf; if (n_read < sizeof(buf) - 1) { break; } } while (true); auto json_output = CF_EXPECT(ParseJson(ss.str())); return json_output; } Result CvdClient::ValidSubcmdsList( const cvd_common::Envs& envs) { auto valid_subcmd_json = CF_EXPECT(ListSubcommands(envs)); CF_EXPECT(valid_subcmd_json.isMember("subcmd"), "Server returned the list of subcommands in Json but it is missing " << " \"subcmd\" field"); std::string valid_subcmd_string = valid_subcmd_json["subcmd"].asString(); auto valid_subcmds = android::base::Tokenize(valid_subcmd_string, ","); return valid_subcmds; } } // end of namespace cuttlefish