blob: 5ffc2b51b97156ca86f45f2aee4cd45451f1866a [file] [log] [blame] [edit]
/*
* 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_command/generic.h"
#include <sys/types.h>
#include <functional>
#include <mutex>
#include <android-base/file.h>
#include <android-base/scopeguard.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/environment.h"
#include "common/libs/utils/files.h"
#include "common/libs/utils/result.h"
#include "common/libs/utils/subprocess.h"
#include "cvd_server.pb.h"
#include "host/commands/cvd/command_sequence.h"
#include "host/commands/cvd/common_utils.h"
#include "host/commands/cvd/instance_manager.h"
#include "host/commands/cvd/selector/selector_constants.h"
#include "host/commands/cvd/server_command/host_tool_target_manager.h"
#include "host/commands/cvd/server_command/server_handler.h"
#include "host/commands/cvd/server_command/subprocess_waiter.h"
#include "host/commands/cvd/server_command/utils.h"
#include "host/commands/cvd/types.h"
#include "host/libs/config/instance_nums.h"
namespace cuttlefish {
class CvdGenericCommandHandler : public CvdServerHandler {
public:
CvdGenericCommandHandler(InstanceManager& instance_manager,
SubprocessWaiter& subprocess_waiter,
HostToolTargetManager& host_tool_target_manager);
Result<bool> CanHandle(const RequestWithStdio& request) const;
Result<cvd::Response> Handle(const RequestWithStdio& request) override;
Result<void> Interrupt() override;
cvd_common::Args CmdList() const override;
private:
struct CommandInvocationInfo {
std::string command;
std::string bin;
std::string bin_path;
std::string home;
std::string host_artifacts_path;
uid_t uid;
std::vector<std::string> args;
cvd_common::Envs envs;
};
struct ExtractedInfo {
CommandInvocationInfo invocation_info;
std::optional<selector::LocalInstanceGroup> group;
bool is_non_help_cvd;
};
Result<ExtractedInfo> ExtractInfo(const RequestWithStdio& request) const;
Result<std::string> GetBin(const std::string& subcmd) const;
Result<std::string> GetBin(const std::string& subcmd,
const std::string& host_artifacts_path) const;
bool IsStopCommand(const std::string& subcmd) const {
return subcmd == "stop" || subcmd == "stop_cvd";
}
// whether the "bin" is cvd bins like stop_cvd or not (e.g. ln, ls, mkdir)
// The information to fire the command might be different. This information
// is about what the executable binary is and how to find it.
struct BinPathInfo {
std::string bin_;
std::string bin_path_;
std::string host_artifacts_path_;
};
Result<BinPathInfo> NonCvdBinPath(const std::string& subcmd,
const cvd_common::Envs& envs) const;
Result<BinPathInfo> CvdHelpBinPath(const std::string& subcmd,
const cvd_common::Envs& envs) const;
InstanceManager& instance_manager_;
SubprocessWaiter& subprocess_waiter_;
HostToolTargetManager& host_tool_target_manager_;
std::mutex interruptible_;
bool interrupted_ = false;
using BinGeneratorType = std::function<Result<std::string>(
const std::string& host_artifacts_path)>;
std::map<std::string, std::string> command_to_binary_map_;
static constexpr char kHostBugreportBin[] = "cvd_internal_host_bugreport";
static constexpr char kLnBin[] = "ln";
static constexpr char kMkdirBin[] = "mkdir";
static constexpr char kClearBin[] =
"clear_placeholder"; // Unused, runs CvdClear()
// Only indicates that host_tool_target_manager_ should generate at runtime
static constexpr char kBinGeneratedAtRuntime[] =
"host_tool_manager_generates_at_runtime_placeholder";
};
CvdGenericCommandHandler::CvdGenericCommandHandler(
InstanceManager& instance_manager, SubprocessWaiter& subprocess_waiter,
HostToolTargetManager& host_tool_target_manager)
: instance_manager_(instance_manager),
subprocess_waiter_(subprocess_waiter),
host_tool_target_manager_(host_tool_target_manager),
command_to_binary_map_{{"host_bugreport", kHostBugreportBin},
{"cvd_host_bugreport", kHostBugreportBin},
{"stop", kBinGeneratedAtRuntime},
{"stop_cvd", kBinGeneratedAtRuntime},
{"clear", kClearBin},
{"mkdir", kMkdirBin},
{"ln", kLnBin}} {}
Result<bool> CvdGenericCommandHandler::CanHandle(
const RequestWithStdio& request) const {
auto invocation = ParseInvocation(request.Message());
return Contains(command_to_binary_map_, invocation.command);
}
Result<void> CvdGenericCommandHandler::Interrupt() {
std::scoped_lock interrupt_lock(interruptible_);
interrupted_ = true;
CF_EXPECT(subprocess_waiter_.Interrupt());
return {};
}
Result<cvd::Response> CvdGenericCommandHandler::Handle(
const RequestWithStdio& request) {
std::unique_lock interrupt_lock(interruptible_);
if (interrupted_) {
return CF_ERR("Interrupted");
}
CF_EXPECT(CanHandle(request));
CF_EXPECT(request.Credentials() != std::nullopt);
const uid_t uid = request.Credentials()->uid;
cvd::Response response;
response.mutable_command_response();
auto precondition_verified = VerifyPrecondition(request);
if (!precondition_verified.ok()) {
response.mutable_status()->set_code(cvd::Status::FAILED_PRECONDITION);
response.mutable_status()->set_message(
precondition_verified.error().Message());
return response;
}
auto [invocation_info, group_opt, is_non_help_cvd] =
CF_EXPECT(ExtractInfo(request));
if (invocation_info.bin == kClearBin) {
*response.mutable_status() =
instance_manager_.CvdClear(request.Out(), request.Err());
return response;
}
if (is_non_help_cvd && !group_opt) {
return CF_EXPECT(NoGroupResponse(request));
}
ConstructCommandParam construct_cmd_param{
.bin_path = invocation_info.bin_path,
.home = invocation_info.home,
.args = invocation_info.args,
.envs = invocation_info.envs,
.working_dir = request.Message().command_request().working_directory(),
.command_name = invocation_info.bin,
.in = request.In(),
.out = request.Out(),
.err = request.Err()};
Command command = CF_EXPECT(ConstructCommand(construct_cmd_param));
SubprocessOptions options;
if (request.Message().command_request().wait_behavior() ==
cvd::WAIT_BEHAVIOR_START) {
options.ExitWithParent(false);
}
CF_EXPECT(subprocess_waiter_.Setup(command.Start(options)));
bool is_stop = IsStopCommand(invocation_info.command);
// captured structured bindings are a C++20 extension
// so we need [group_ptr] instead of [&group_opt]
auto* group_ptr = (group_opt ? std::addressof(*group_opt) : nullptr);
android::base::ScopeGuard exit_action([this, is_stop, group_ptr]() {
if (!is_stop) {
return;
}
if (!group_ptr) {
return;
}
for (const auto& instance : group_ptr->Instances()) {
auto lock = instance_manager_.TryAcquireLock(instance->InstanceId());
if (lock.ok() && (*lock)) {
(*lock)->Status(InUseState::kNotInUse);
continue;
}
LOG(ERROR) << "InstanceLockFileManager failed to acquire lock for #"
<< instance->InstanceId();
}
});
if (request.Message().command_request().wait_behavior() ==
cvd::WAIT_BEHAVIOR_START) {
response.mutable_status()->set_code(cvd::Status::OK);
return response;
}
interrupt_lock.unlock();
auto infop = CF_EXPECT(subprocess_waiter_.Wait());
if (infop.si_code == CLD_EXITED && IsStopCommand(invocation_info.command)) {
instance_manager_.RemoveInstanceGroup(uid, invocation_info.home);
}
return ResponseFromSiginfo(infop);
}
std::vector<std::string> CvdGenericCommandHandler::CmdList() const {
std::vector<std::string> subcmd_list;
subcmd_list.reserve(command_to_binary_map_.size());
for (const auto& [cmd, _] : command_to_binary_map_) {
subcmd_list.emplace_back(cmd);
}
return subcmd_list;
}
Result<CvdGenericCommandHandler::BinPathInfo>
CvdGenericCommandHandler::NonCvdBinPath(const std::string& subcmd,
const cvd_common::Envs& envs) const {
auto bin_path_base = CF_EXPECT(GetBin(subcmd));
// no need of executable directory. Will look up by PATH
// bin_path_base is like ln, mkdir, etc.
return BinPathInfo{.bin_ = bin_path_base,
.bin_path_ = bin_path_base,
.host_artifacts_path_ = envs.at(kAndroidHostOut)};
}
Result<CvdGenericCommandHandler::BinPathInfo>
CvdGenericCommandHandler::CvdHelpBinPath(const std::string& subcmd,
const cvd_common::Envs& envs) const {
auto tool_dir_path = envs.at(kAndroidHostOut);
if (!DirectoryExists(tool_dir_path + "/bin")) {
tool_dir_path =
android::base::Dirname(android::base::GetExecutableDirectory());
}
auto bin_path_base = CF_EXPECT(GetBin(subcmd, tool_dir_path));
// no need of executable directory. Will look up by PATH
// bin_path_base is like ln, mkdir, etc.
return BinPathInfo{
.bin_ = bin_path_base,
.bin_path_ = tool_dir_path.append("/bin/").append(bin_path_base),
.host_artifacts_path_ = envs.at(kAndroidHostOut)};
}
/*
* commands like ln, mkdir, clear
* -> bin, bin, system_wide_home, N/A, cmd_args, envs
*
* help command
* -> android_out/bin, bin, system_wide_home, android_out, cmd_args, envs
*
* non-help command
* -> group->a/o/bin, bin, group->home, group->android_out, cmd_args, envs
*
*/
Result<CvdGenericCommandHandler::ExtractedInfo>
CvdGenericCommandHandler::ExtractInfo(const RequestWithStdio& request) const {
auto result_opt = request.Credentials();
CF_EXPECT(result_opt != std::nullopt);
const uid_t uid = result_opt->uid;
auto [subcmd, cmd_args] = ParseInvocation(request.Message());
CF_EXPECT(Contains(command_to_binary_map_, subcmd));
cvd_common::Envs envs =
cvd_common::ConvertToEnvs(request.Message().command_request().env());
const auto& selector_opts =
request.Message().command_request().selector_opts();
const auto selector_args = cvd_common::ConvertToArgs(selector_opts.args());
CF_EXPECT(Contains(envs, kAndroidHostOut) &&
DirectoryExists(envs.at(kAndroidHostOut)));
std::unordered_set<std::string> non_cvd_op{"clear", "mkdir", "ln"};
if (Contains(non_cvd_op, subcmd) || CF_EXPECT(IsHelpSubcmd(cmd_args))) {
const auto [bin, bin_path, host_artifacts_path] =
Contains(non_cvd_op, subcmd) ? CF_EXPECT(NonCvdBinPath(subcmd, envs))
: CF_EXPECT(CvdHelpBinPath(subcmd, envs));
return ExtractedInfo{
.invocation_info =
CommandInvocationInfo{
.command = subcmd,
.bin = bin,
.bin_path = bin_path,
.home = CF_EXPECT(SystemWideUserHome(uid)),
.host_artifacts_path = envs.at(kAndroidHostOut),
.uid = uid,
.args = cmd_args,
.envs = envs},
.group = std::nullopt,
.is_non_help_cvd = false};
}
auto instance_group_result =
instance_manager_.SelectGroup(selector_args, envs, uid);
ExtractedInfo extracted_info;
extracted_info.is_non_help_cvd = true;
if (!instance_group_result.ok()) {
CF_EXPECT(!instance_manager_.HasInstanceGroups(uid),
instance_group_result.error().FormatForEnv());
return extracted_info;
}
auto& instance_group = *instance_group_result;
auto android_host_out = instance_group.HostArtifactsPath();
auto home = instance_group.HomeDir();
auto bin = CF_EXPECT(GetBin(subcmd, android_host_out));
auto bin_path = ConcatToString(android_host_out, "/bin/", bin);
CommandInvocationInfo result = {.command = subcmd,
.bin = bin,
.bin_path = bin_path,
.home = home,
.host_artifacts_path = android_host_out,
.uid = uid,
.args = cmd_args,
.envs = envs};
result.envs["HOME"] = home;
extracted_info.invocation_info = result;
extracted_info.group = instance_group;
return extracted_info;
}
Result<std::string> CvdGenericCommandHandler::GetBin(
const std::string& subcmd) const {
CF_EXPECT(Contains(command_to_binary_map_, subcmd));
const auto& bin_name = command_to_binary_map_.at(subcmd);
CF_EXPECT(bin_name != kBinGeneratedAtRuntime);
return bin_name;
}
Result<std::string> CvdGenericCommandHandler::GetBin(
const std::string& subcmd, const std::string& host_artifacts_path) const {
CF_EXPECT(Contains(command_to_binary_map_, subcmd));
const auto& bin_name = command_to_binary_map_.at(subcmd);
if (bin_name != kBinGeneratedAtRuntime) {
return GetBin(subcmd);
}
std::string calculated_bin_name =
CF_EXPECT(host_tool_target_manager_.ExecBaseName(
{.artifacts_path = host_artifacts_path, .op = subcmd}));
return calculated_bin_name;
}
std::unique_ptr<CvdServerHandler> NewCvdGenericCommandHandler(
InstanceManager& instance_manager, SubprocessWaiter& subprocess_waiter,
HostToolTargetManager& host_tool_target_manager) {
return std::unique_ptr<CvdServerHandler>(new CvdGenericCommandHandler(
instance_manager, subprocess_waiter, host_tool_target_manager));
}
} // namespace cuttlefish