286 lines
10 KiB
C++
286 lines
10 KiB
C++
|
|
/*
|
||
|
|
* 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 "host/commands/cvd/server.h"
|
||
|
|
|
||
|
|
#include <sys/types.h>
|
||
|
|
|
||
|
|
#include <cstdio>
|
||
|
|
#include <iostream>
|
||
|
|
|
||
|
|
#include <android-base/file.h>
|
||
|
|
#include <fruit/fruit.h>
|
||
|
|
|
||
|
|
#include "common/libs/fs/shared_buf.h"
|
||
|
|
#include "common/libs/fs/shared_fd.h"
|
||
|
|
#include "common/libs/utils/contains.h"
|
||
|
|
#include "common/libs/utils/result.h"
|
||
|
|
#include "cvd_server.pb.h"
|
||
|
|
#include "host/commands/cvd/common_utils.h"
|
||
|
|
#include "host/commands/cvd/epoll_loop.h"
|
||
|
|
#include "host/commands/cvd/frontline_parser.h"
|
||
|
|
#include "host/commands/cvd/instance_manager.h"
|
||
|
|
#include "host/commands/cvd/server_command/components.h"
|
||
|
|
#include "host/commands/cvd/server_command/utils.h"
|
||
|
|
#include "host/commands/cvd/types.h"
|
||
|
|
#include "host/libs/web/build_api.h"
|
||
|
|
|
||
|
|
namespace cuttlefish {
|
||
|
|
namespace {
|
||
|
|
|
||
|
|
constexpr char kRestartServerHelpMessage[] =
|
||
|
|
R"(Cuttlefish Virtual Device (CVD) CLI.
|
||
|
|
|
||
|
|
usage: cvd restart-server <common args> <mode> <mode args>
|
||
|
|
|
||
|
|
Common Args:
|
||
|
|
--help Print out this message
|
||
|
|
--verbose Control verbose mode
|
||
|
|
|
||
|
|
Modes:
|
||
|
|
match-client Use the client executable.
|
||
|
|
latest Download the latest executable
|
||
|
|
reuse-server Use the server executable.
|
||
|
|
)";
|
||
|
|
|
||
|
|
Result<SharedFD> LatestCvdAsFd(BuildApi& build_api) {
|
||
|
|
static constexpr char kBuild[] = "aosp-master";
|
||
|
|
static constexpr char kTarget[] = "aosp_cf_x86_64_phone-userdebug";
|
||
|
|
auto latest = CF_EXPECT(build_api.LatestBuildId(kBuild, kTarget));
|
||
|
|
DeviceBuild build{latest, kTarget};
|
||
|
|
|
||
|
|
auto fd = SharedFD::MemfdCreate("cvd");
|
||
|
|
CF_EXPECT(fd->IsOpen(), "MemfdCreate failed: " << fd->StrError());
|
||
|
|
|
||
|
|
auto write = [fd](char* data, size_t size) -> bool {
|
||
|
|
if (size == 0) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
auto written = WriteAll(fd, data, size);
|
||
|
|
if (written != size) {
|
||
|
|
LOG(ERROR) << "Failed to persist data: " << fd->StrError();
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
};
|
||
|
|
CF_EXPECT(build_api.ArtifactToCallback(build, "cvd", write));
|
||
|
|
|
||
|
|
return fd;
|
||
|
|
}
|
||
|
|
|
||
|
|
class CvdRestartHandler : public CvdServerHandler {
|
||
|
|
public:
|
||
|
|
INJECT(CvdRestartHandler(BuildApi& build_api, CvdServer& server,
|
||
|
|
InstanceManager& instance_manager))
|
||
|
|
: build_api_(build_api),
|
||
|
|
supported_modes_({"match-client", "latest", "reuse-server"}),
|
||
|
|
server_(server),
|
||
|
|
instance_manager_(instance_manager) {
|
||
|
|
flags_.EnrollFlag(CvdFlag<bool>("help", false));
|
||
|
|
flags_.EnrollFlag(CvdFlag<bool>("verbose", false));
|
||
|
|
// If the fla is false, the request will fail if there's on-going requests
|
||
|
|
// If true, calls Stop()
|
||
|
|
flags_.EnrollFlag(CvdFlag<bool>("force", true));
|
||
|
|
}
|
||
|
|
|
||
|
|
Result<bool> CanHandle(const RequestWithStdio& request) const override {
|
||
|
|
auto invocation = ParseInvocation(request.Message());
|
||
|
|
return android::base::Basename(invocation.command) == kRestartServer;
|
||
|
|
}
|
||
|
|
|
||
|
|
Result<cvd::Response> Handle(const RequestWithStdio& request) override {
|
||
|
|
CF_EXPECT(CanHandle(request));
|
||
|
|
cvd::Response response;
|
||
|
|
if (request.Message().has_shutdown_request()) {
|
||
|
|
response.mutable_shutdown_response();
|
||
|
|
} else {
|
||
|
|
CF_EXPECT(
|
||
|
|
request.Message().has_command_request(),
|
||
|
|
"cvd restart request must be either command or shutdown request.");
|
||
|
|
response.mutable_command_response();
|
||
|
|
}
|
||
|
|
// all_args[0] = "cvd", all_args[1] = "restart_server"
|
||
|
|
cvd_common::Args all_args =
|
||
|
|
cvd_common::ConvertToArgs(request.Message().command_request().args());
|
||
|
|
CF_EXPECT_GE(all_args.size(), 2);
|
||
|
|
CF_EXPECT_EQ(all_args.at(0), "cvd");
|
||
|
|
CF_EXPECT_EQ(all_args.at(1), kRestartServer);
|
||
|
|
// erase the first item, "cvd"
|
||
|
|
all_args.erase(all_args.begin());
|
||
|
|
|
||
|
|
auto parsed = CF_EXPECT(Parse(all_args));
|
||
|
|
if (parsed.help) {
|
||
|
|
const std::string help_message(kRestartServerHelpMessage);
|
||
|
|
WriteAll(request.Out(), help_message);
|
||
|
|
return response;
|
||
|
|
}
|
||
|
|
|
||
|
|
// On CF_ERR, the locks will be released automatically
|
||
|
|
WriteAll(request.Out(), "Stopping the cvd_server.\n");
|
||
|
|
server_.Stop();
|
||
|
|
|
||
|
|
CF_EXPECT(request.Credentials() != std::nullopt);
|
||
|
|
const uid_t client_uid = request.Credentials()->uid;
|
||
|
|
auto json_string =
|
||
|
|
CF_EXPECT(SerializedInstanceDatabaseToString(client_uid));
|
||
|
|
std::optional<SharedFD> mem_fd;
|
||
|
|
if (instance_manager_.HasInstanceGroups(client_uid)) {
|
||
|
|
mem_fd = CF_EXPECT(CreateMemFileWithSerializedDb(json_string));
|
||
|
|
CF_EXPECT(mem_fd != std::nullopt && (*mem_fd)->IsOpen(),
|
||
|
|
"mem file not open?");
|
||
|
|
}
|
||
|
|
|
||
|
|
if (parsed.verbose && mem_fd) {
|
||
|
|
PrintFileLink(request.Err(), *mem_fd);
|
||
|
|
}
|
||
|
|
|
||
|
|
const std::string subcmd = parsed.subcmd.value_or("reuse-server");
|
||
|
|
SharedFD new_exe;
|
||
|
|
CF_EXPECT(Contains(supported_modes_, subcmd),
|
||
|
|
"unsupported subcommand :" << subcmd);
|
||
|
|
if (subcmd == "match-client") {
|
||
|
|
CF_EXPECT(request.Extra(), "match-client requires the file descriptor.");
|
||
|
|
new_exe = *request.Extra();
|
||
|
|
} else if (subcmd == "latest") {
|
||
|
|
new_exe = CF_EXPECT(LatestCvdAsFd(build_api_));
|
||
|
|
} else if (subcmd == "reuse-server") {
|
||
|
|
new_exe = CF_EXPECT(NewExecFromPath(request, kServerExecPath));
|
||
|
|
} else {
|
||
|
|
return CF_ERR("unsupported subcommand");
|
||
|
|
}
|
||
|
|
|
||
|
|
CF_EXPECT(server_.Exec({.new_exe = new_exe,
|
||
|
|
.carryover_client_fd = request.Client(),
|
||
|
|
.client_stderr_fd = request.Err(),
|
||
|
|
.in_memory_data_fd = mem_fd,
|
||
|
|
.verbose = parsed.verbose}));
|
||
|
|
return CF_ERR("Should be unreachable");
|
||
|
|
}
|
||
|
|
|
||
|
|
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:
|
||
|
|
struct Parsed {
|
||
|
|
bool help;
|
||
|
|
bool verbose;
|
||
|
|
std::optional<std::string> subcmd;
|
||
|
|
std::optional<std::string> exec_path;
|
||
|
|
};
|
||
|
|
Result<Parsed> Parse(const cvd_common::Args& args) {
|
||
|
|
// it's ugly but let's reuse the frontline parser
|
||
|
|
auto parser_with_result =
|
||
|
|
CF_EXPECT(FrontlineParser::Parse({.internal_cmds = supported_modes_,
|
||
|
|
.all_args = args,
|
||
|
|
.cvd_flags = flags_}));
|
||
|
|
CF_EXPECT(parser_with_result != nullptr,
|
||
|
|
"FrontlineParser::Parse() returned nullptr");
|
||
|
|
// If there was a subcmd, the flags for the subcmd is in SubCmdArgs().
|
||
|
|
// If not, the flags for restart-server would be in CvdArgs()
|
||
|
|
std::optional<std::string> subcmd_opt = parser_with_result->SubCmd();
|
||
|
|
cvd_common::Args subcmd_args =
|
||
|
|
(subcmd_opt ? parser_with_result->SubCmdArgs()
|
||
|
|
: parser_with_result->CvdArgs());
|
||
|
|
auto name_flag_map = CF_EXPECT(flags_.CalculateFlags(subcmd_args));
|
||
|
|
CF_EXPECT(Contains(name_flag_map, "help"));
|
||
|
|
CF_EXPECT(Contains(name_flag_map, "verbose"));
|
||
|
|
|
||
|
|
bool help =
|
||
|
|
CF_EXPECT(FlagCollection::GetValue<bool>(name_flag_map.at("help")));
|
||
|
|
bool verbose =
|
||
|
|
CF_EXPECT(FlagCollection::GetValue<bool>(name_flag_map.at("verbose")));
|
||
|
|
std::optional<std::string> exec_path;
|
||
|
|
if (Contains(name_flag_map, "exec-path")) {
|
||
|
|
exec_path = CF_EXPECT(
|
||
|
|
FlagCollection::GetValue<std::string>(name_flag_map.at("exec-path")));
|
||
|
|
}
|
||
|
|
return Parsed{.help = help,
|
||
|
|
.verbose = verbose,
|
||
|
|
.subcmd = subcmd_opt,
|
||
|
|
.exec_path = exec_path};
|
||
|
|
}
|
||
|
|
|
||
|
|
Result<SharedFD> NewExecFromPath(const RequestWithStdio& request,
|
||
|
|
const std::string& exec_path) {
|
||
|
|
std::string emulated_absolute_path;
|
||
|
|
const std::string client_pwd =
|
||
|
|
request.Message().command_request().working_directory();
|
||
|
|
// ~ that means $HOME is not supported
|
||
|
|
CF_EXPECT(!android::base::StartsWith(exec_path, "~/"),
|
||
|
|
"Path starting with ~/ is not supported.");
|
||
|
|
CF_EXPECT_NE(
|
||
|
|
exec_path, "~",
|
||
|
|
"~ is not supported as a executable path, and likely is not a file.");
|
||
|
|
emulated_absolute_path =
|
||
|
|
CF_EXPECT(EmulateAbsolutePath({.current_working_dir = client_pwd,
|
||
|
|
.path_to_convert = exec_path,
|
||
|
|
.follow_symlink = false}),
|
||
|
|
"Failed to change exec_path to an absolute path.");
|
||
|
|
auto new_exe = SharedFD::Open(emulated_absolute_path, O_RDONLY);
|
||
|
|
CF_EXPECT(new_exe->IsOpen(), "Failed to open \""
|
||
|
|
<< exec_path << " that is "
|
||
|
|
<< emulated_absolute_path
|
||
|
|
<< "\": " << new_exe->StrError());
|
||
|
|
return new_exe;
|
||
|
|
}
|
||
|
|
|
||
|
|
Result<std::string> SerializedInstanceDatabaseToString(
|
||
|
|
const uid_t client_uid) {
|
||
|
|
auto db_json = CF_EXPECT(instance_manager_.Serialize(client_uid),
|
||
|
|
"Failed to serialized instance database");
|
||
|
|
return db_json.toStyledString();
|
||
|
|
}
|
||
|
|
|
||
|
|
Result<SharedFD> CreateMemFileWithSerializedDb(
|
||
|
|
const std::string& json_string) {
|
||
|
|
const std::string mem_file_name = "cvd_server_" + std::to_string(getpid());
|
||
|
|
auto mem_fd = SharedFD::MemfdCreateWithData(mem_file_name, json_string);
|
||
|
|
CF_EXPECT(mem_fd->IsOpen(),
|
||
|
|
"MemfdCreateWithData failed: " << mem_fd->StrError());
|
||
|
|
return mem_fd;
|
||
|
|
}
|
||
|
|
|
||
|
|
void PrintFileLink(const SharedFD& fd_stream, const SharedFD& mem_fd) const {
|
||
|
|
auto link_target_result = mem_fd->ProcFdLinkTarget();
|
||
|
|
if (!link_target_result.ok()) {
|
||
|
|
WriteAll(fd_stream,
|
||
|
|
"Failed to resolve the link target for the memory file.\n");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
std::string message("The link target for the memory file is ");
|
||
|
|
message.append(*link_target_result).append("\n");
|
||
|
|
WriteAll(fd_stream, message);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
BuildApi& build_api_;
|
||
|
|
std::vector<std::string> supported_modes_;
|
||
|
|
FlagCollection flags_;
|
||
|
|
CvdServer& server_;
|
||
|
|
InstanceManager& instance_manager_;
|
||
|
|
};
|
||
|
|
|
||
|
|
} // namespace
|
||
|
|
|
||
|
|
fruit::Component<fruit::Required<BuildApi, CvdServer, InstanceManager>>
|
||
|
|
CvdRestartComponent() {
|
||
|
|
return fruit::createComponent()
|
||
|
|
.addMultibinding<CvdServerHandler, CvdRestartHandler>();
|
||
|
|
}
|
||
|
|
|
||
|
|
} // namespace cuttlefish
|