381 lines
13 KiB
C++
381 lines
13 KiB
C++
/*
|
|
* Copyright (C) 2021 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 <stdlib.h>
|
|
#include <chrono>
|
|
#include <iostream>
|
|
#include <map>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <thread>
|
|
#include <vector>
|
|
|
|
#include <android-base/file.h>
|
|
#include <android-base/logging.h>
|
|
#include <android-base/result.h>
|
|
#include <build/version.h>
|
|
|
|
#include "cvd_server.pb.h"
|
|
|
|
#include "common/libs/fs/shared_fd.h"
|
|
#include "common/libs/utils/environment.h"
|
|
#include "common/libs/utils/files.h"
|
|
#include "common/libs/utils/flag_parser.h"
|
|
#include "common/libs/utils/result.h"
|
|
#include "common/libs/utils/shared_fd_flag.h"
|
|
#include "common/libs/utils/subprocess.h"
|
|
#include "common/libs/utils/unix_sockets.h"
|
|
#include "host/commands/cvd/server.h"
|
|
#include "host/commands/cvd/server_constants.h"
|
|
#include "host/libs/config/cuttlefish_config.h"
|
|
#include "host/libs/config/host_tools_version.h"
|
|
|
|
namespace cuttlefish {
|
|
namespace {
|
|
|
|
Result<SharedFD> 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;
|
|
}
|
|
|
|
class CvdClient {
|
|
public:
|
|
Result<void> EnsureCvdServerRunning(const std::string& host_tool_directory,
|
|
int num_retries = 1) {
|
|
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(host_tool_directory));
|
|
response = CF_EXPECT(SendRequest(request));
|
|
}
|
|
CF_EXPECT(CheckStatus(response->status(), "GetVersion"));
|
|
CF_EXPECT(response->has_version_response(),
|
|
"GetVersion call missing VersionResponse.");
|
|
|
|
auto server_version = response->version_response().version();
|
|
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(host_tool_directory));
|
|
if (num_retries > 0) {
|
|
CF_EXPECT(EnsureCvdServerRunning(host_tool_directory, 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("/proc/self/exe");
|
|
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<void> 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, /*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<void> HandleCommand(std::vector<std::string> args,
|
|
std::vector<std::string> env) {
|
|
cvd::Request request;
|
|
auto command_request = request.mutable_command_request();
|
|
for (const std::string& arg : args) {
|
|
command_request->add_args(arg);
|
|
}
|
|
for (const std::string& e : env) {
|
|
auto eq_pos = e.find('=');
|
|
if (eq_pos == std::string::npos) {
|
|
LOG(WARNING) << "Environment var in unknown format: " << e;
|
|
continue;
|
|
}
|
|
(*command_request->mutable_env())[e.substr(0, eq_pos)] =
|
|
e.substr(eq_pos + 1);
|
|
}
|
|
std::unique_ptr<char, void(*)(void*)> cwd(getcwd(nullptr, 0), &free);
|
|
command_request->set_working_directory(cwd.get());
|
|
command_request->set_wait_behavior(cvd::WAIT_BEHAVIOR_COMPLETE);
|
|
|
|
auto response = CF_EXPECT(SendRequest(request));
|
|
CF_EXPECT(CheckStatus(response.status(), "HandleCommand"));
|
|
CF_EXPECT(response.has_command_response(),
|
|
"HandleCommand call missing CommandResponse.");
|
|
return {};
|
|
}
|
|
|
|
private:
|
|
std::optional<UnixMessageSocket> server_;
|
|
|
|
Result<void> 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<cvd::Response> SendRequest(const cvd::Request& request,
|
|
std::optional<SharedFD> 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<SharedFD> control_fds = {
|
|
SharedFD::Dup(0),
|
|
SharedFD::Dup(1),
|
|
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<char>(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<void> StartCvdServer(const std::string& host_tool_directory) {
|
|
SharedFD server_fd =
|
|
SharedFD::SocketLocalServer(cvd::kServerSocketPath,
|
|
/*is_abstract=*/true, SOCK_SEQPACKET, 0666);
|
|
CF_EXPECT(server_fd->IsOpen(), server_fd->StrError());
|
|
|
|
// TODO(b/196114111): Investigate fully "daemonizing" the cvd_server.
|
|
CF_EXPECT(setenv("ANDROID_HOST_OUT", host_tool_directory.c_str(),
|
|
/*overwrite=*/true) == 0);
|
|
Command command("/proc/self/exe");
|
|
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<void> 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");
|
|
}
|
|
};
|
|
|
|
[[noreturn]] void CallPythonAcloud(std::vector<std::string>& 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";
|
|
char** 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);
|
|
PLOG(FATAL) << "execv(" << py_acloud_path << ", ...) failed";
|
|
abort();
|
|
}
|
|
|
|
Result<int> CvdMain(int argc, char** argv, char** envp) {
|
|
android::base::InitLogging(argv, android::base::StderrLogger);
|
|
|
|
std::vector<std::string> args = ArgsToVec(argc, argv);
|
|
std::vector<Flag> flags;
|
|
|
|
CvdClient client;
|
|
|
|
// TODO(b/206893146): Make this decision inside the server.
|
|
if (args[0] == "acloud") {
|
|
auto server_running = client.EnsureCvdServerRunning(
|
|
android::base::Dirname(android::base::GetExecutableDirectory()));
|
|
if (server_running.ok()) {
|
|
// TODO(schuffelen): Deduplicate when calls to setenv are removed.
|
|
std::vector<std::string> env;
|
|
for (char** e = envp; *e != 0; e++) {
|
|
env.emplace_back(*e);
|
|
}
|
|
args[0] = "try-acloud";
|
|
auto attempt = client.HandleCommand(args, env);
|
|
if (attempt.ok()) {
|
|
args[0] = "acloud";
|
|
CF_EXPECT(client.HandleCommand(args, env));
|
|
return 0;
|
|
} else {
|
|
CallPythonAcloud(args);
|
|
}
|
|
} else {
|
|
// Something is wrong with the server, fall back to python acloud
|
|
CallPythonAcloud(args);
|
|
}
|
|
}
|
|
bool clean = false;
|
|
flags.emplace_back(GflagsCompatFlag("clean", clean));
|
|
SharedFD internal_server_fd;
|
|
flags.emplace_back(SharedFDFlag("INTERNAL_server_fd", internal_server_fd));
|
|
|
|
CF_EXPECT(ParseFlags(flags, args));
|
|
|
|
if (internal_server_fd->IsOpen()) {
|
|
return CF_EXPECT(CvdServerMain(internal_server_fd));
|
|
} else if (argv[0] == std::string("/proc/self/exe")) {
|
|
return CF_ERR(
|
|
"Expected to be in server mode, but didn't get a server "
|
|
"fd: "
|
|
<< internal_server_fd->StrError());
|
|
}
|
|
|
|
// Special case for `cvd kill-server`, handled by directly
|
|
// stopping the cvd_server.
|
|
if (argc > 1 && strcmp("kill-server", argv[1]) == 0) {
|
|
CF_EXPECT(client.StopCvdServer(/*clear=*/true));
|
|
return 0;
|
|
}
|
|
|
|
// Special case for --clean flag, used to clear any existing state.
|
|
if (clean) {
|
|
LOG(INFO) << "cvd invoked with --clean; "
|
|
<< "stopping the cvd_server before continuing.";
|
|
CF_EXPECT(client.StopCvdServer(/*clear=*/true));
|
|
}
|
|
|
|
// Handle all remaining commands by forwarding them to the cvd_server.
|
|
CF_EXPECT(client.EnsureCvdServerRunning(android::base::Dirname(
|
|
android::base::GetExecutableDirectory())),
|
|
"Unable to ensure cvd_server is running.");
|
|
|
|
// TODO(schuffelen): Deduplicate when calls to setenv are removed.
|
|
std::vector<std::string> env;
|
|
for (char** e = envp; *e != 0; e++) {
|
|
env.emplace_back(*e);
|
|
}
|
|
CF_EXPECT(client.HandleCommand(args, env));
|
|
return 0;
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace cuttlefish
|
|
|
|
int main(int argc, char** argv, char** envp) {
|
|
auto result = cuttlefish::CvdMain(argc, argv, envp);
|
|
if (result.ok()) {
|
|
return *result;
|
|
} else {
|
|
std::cerr << result.error() << std::endl;
|
|
return -1;
|
|
}
|
|
}
|