blob: c82cfdd83bdb07671508653524e8b57cc2d3beca [file] [log] [blame]
/*
* 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 {
/*
* TODO(weihsu@): change the code accordingly per verbosity level control.
*
* Now, the server can start with a verbosity level. Change the code
* accordingly.
*/
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