blob: c9858b34daf735b05d15870a116082377c06b25e [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_command/start.h"
#include <sys/types.h>
#include <array>
#include <atomic>
#include <cstdint>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <map>
#include <mutex>
#include <optional>
#include <regex>
#include <sstream>
#include <string>
#include <thread>
#include <android-base/parseint.h>
#include <android-base/strings.h>
#include "common/libs/fs/shared_fd.h"
#include "common/libs/utils/contains.h"
#include "common/libs/utils/files.h"
#include "common/libs/utils/flag_parser.h"
#include "common/libs/utils/result.h"
#include "cvd_server.pb.h"
#include "host/commands/cvd/command_sequence.h"
#include "host/commands/cvd/common_utils.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/config_constants.h"
namespace cuttlefish {
namespace {
std::optional<std::string> GetConfigPath(cvd_common::Args& args) {
int initial_size = args.size();
std::string config_file;
std::vector<Flag> config_flags = {
GflagsCompatFlag("config_file", config_file)};
auto result = ParseFlags(config_flags, args);
if (!result.ok() || initial_size == args.size()) {
return std::nullopt;
}
return config_file;
}
RequestWithStdio CreateLoadCommand(const RequestWithStdio& request,
cvd_common::Args& args,
const std::string& config_file) {
cvd::Request request_proto;
auto& load_command = *request_proto.mutable_command_request();
*load_command.mutable_env() = request.Message().command_request().env();
load_command.set_working_directory(
request.Message().command_request().working_directory());
load_command.add_args("cvd");
load_command.add_args("load");
for (const auto& arg : args) {
load_command.add_args(arg);
}
load_command.add_args(config_file);
return RequestWithStdio(request.Client(), request_proto,
request.FileDescriptors(), request.Credentials());
}
} // namespace
class CvdStartCommandHandler : public CvdServerHandler {
public:
CvdStartCommandHandler(InstanceManager& instance_manager,
HostToolTargetManager& host_tool_target_manager,
CommandSequenceExecutor& command_executor)
: instance_manager_(instance_manager),
host_tool_target_manager_(host_tool_target_manager),
// TODO: b/300476262 - Migrate to using local instances rather than
// constructor-injected ones
command_executor_(command_executor),
sub_action_ended_(false) {}
Result<bool> CanHandle(const RequestWithStdio& request) const;
Result<cvd::Response> Handle(const RequestWithStdio& request) override;
Result<void> Interrupt() override;
std::vector<std::string> CmdList() const override;
private:
Result<void> UpdateInstanceDatabase(
const uid_t uid, const selector::GroupCreationInfo& group_creation_info);
Result<void> FireCommand(Command&& command, const bool wait);
bool HasHelpOpts(const cvd_common::Args& args) const;
Result<Command> ConstructCvdNonHelpCommand(
const std::string& bin_file,
const selector::GroupCreationInfo& group_info,
const RequestWithStdio& request);
// call this only if !is_help
Result<selector::GroupCreationInfo> GetGroupCreationInfo(
const std::string& start_bin, const std::string& subcmd,
const cvd_common::Args& subcmd_args, const cvd_common::Envs& envs,
const RequestWithStdio& request);
Result<cvd::Response> FillOutNewInstanceInfo(
cvd::Response&& response,
const selector::GroupCreationInfo& group_creation_info);
struct UpdatedArgsAndEnvs {
cvd_common::Args args;
cvd_common::Envs envs;
};
Result<UpdatedArgsAndEnvs> UpdateInstanceArgsAndEnvs(
cvd_common::Args&& args, cvd_common::Envs&& envs,
const std::vector<selector::PerInstanceInfo>& instances,
const std::string& artifacts_path, const std::string& start_bin);
Result<selector::GroupCreationInfo> UpdateArgsAndEnvs(
selector::GroupCreationInfo&& old_group_info,
const std::string& start_bin);
Result<std::string> FindStartBin(const std::string& android_host_out);
static void MarkLockfiles(selector::GroupCreationInfo& group_info,
const InUseState state);
static void MarkLockfilesInUse(selector::GroupCreationInfo& group_info) {
MarkLockfiles(group_info, InUseState::kInUse);
}
/*
* wait, remove the instance group if start failed, filling out the
* response.
*/
Result<cvd::Response> PostStartExecutionActions(
std::optional<selector::GroupCreationInfo>& group_creation_info,
const uid_t uid, const bool is_daemonized);
Result<void> AcloudCompatActions(
const selector::GroupCreationInfo& group_creation_info,
const RequestWithStdio& request);
Result<void> CreateSymlinks(
const selector::GroupCreationInfo& group_creation_info);
InstanceManager& instance_manager_;
SubprocessWaiter subprocess_waiter_;
HostToolTargetManager& host_tool_target_manager_;
CommandSequenceExecutor& command_executor_;
std::mutex interruptible_;
bool interrupted_ = false;
/*
* Used by Interrupt() not to call command_executor_.Interrupt()
*
* If true, it is guaranteed that the command_executor_ ended the execution.
* If false, it may or may not be after the command_executor_.Execute()
*/
std::atomic<bool> sub_action_ended_;
static const std::array<std::string, 2> supported_commands_;
};
Result<void> CvdStartCommandHandler::AcloudCompatActions(
const selector::GroupCreationInfo& group_creation_info,
const RequestWithStdio& request) {
std::unique_lock interrupt_lock(interruptible_);
CF_EXPECT(!interrupted_, "Interrupted");
// rm -fr "TempDir()/acloud_cvd_temp/local-instance-<i>"
std::string acloud_compat_home_prefix =
TempDir() + "/acloud_cvd_temp/local-instance-";
std::vector<std::string> acloud_compat_homes;
acloud_compat_homes.reserve(group_creation_info.instances.size());
for (const auto& instance : group_creation_info.instances) {
acloud_compat_homes.push_back(
ConcatToString(acloud_compat_home_prefix, instance.instance_id_));
}
for (const auto& acloud_compat_home : acloud_compat_homes) {
bool result_deleted = true;
std::stringstream acloud_compat_home_stream;
if (!FileExists(acloud_compat_home)) {
continue;
}
if (!Contains(group_creation_info.envs, kLaunchedByAcloud) ||
group_creation_info.envs.at(kLaunchedByAcloud) != "true") {
if (!DirectoryExists(acloud_compat_home, /*follow_symlinks=*/false)) {
// cvd created a symbolic link
result_deleted = RemoveFile(acloud_compat_home);
} else {
// acloud created a directory
// rm -fr isn't supporetd by TreeHugger, so if we fork-and-exec to
// literally run "rm -fr", the presubmit testing may fail if ever this
// code is tested in the future.
result_deleted = RecursivelyRemoveDirectory(acloud_compat_home);
}
}
if (!result_deleted) {
LOG(ERROR) << "Removing " << acloud_compat_home << " failed.";
continue;
}
}
// ln -f -s [target] [symlink]
// 1. mkdir -p home
// 2. ln -f -s android_host_out home/host_bins
// 3. for each i in ids,
// ln -f -s home /tmp/acloud_cvd_temp/local-instance-<i>
std::vector<MakeRequestForm> request_forms;
const cvd_common::Envs& common_envs = group_creation_info.envs;
const std::string& home_dir = group_creation_info.home;
const std::string client_pwd =
request.Message().command_request().working_directory();
request_forms.push_back(
{.cmd_args = cvd_common::Args{"mkdir", "-p", home_dir},
.env = common_envs,
.selector_args = cvd_common::Args{},
.working_dir = client_pwd});
const std::string& android_host_out = group_creation_info.host_artifacts_path;
request_forms.push_back(
{.cmd_args = cvd_common::Args{"ln", "-T", "-f", "-s", android_host_out,
home_dir + "/host_bins"},
.env = common_envs,
.selector_args = cvd_common::Args{},
.working_dir = client_pwd});
/* TODO(weihsu@): cvd acloud delete/list must handle multi-tenancy gracefully
*
* acloud delete just calls, for all instances in a group,
* /tmp/acloud_cvd_temp/local-instance-<i>/host_bins/stop_cvd
*
* That isn't necessary. Not desirable. Cvd acloud should read the instance
* manager's in-memory data structure, and call stop_cvd once for the entire
* group.
*
* Likewise, acloud list simply shows all instances in a flattened way. The
* user has no clue about an instance group. Cvd acloud should show the
* hierarchy.
*
* For now, we create the symbolic links so that it is compatible with acloud
* in Python.
*/
for (const auto& acloud_compat_home : acloud_compat_homes) {
if (acloud_compat_home == home_dir) {
LOG(ERROR) << "The \"HOME\" directory is acloud workspace, which will "
<< "be deleted by next cvd start or acloud command with the"
<< " same directory being \"HOME\"";
continue;
}
request_forms.push_back({
.cmd_args = cvd_common::Args{"ln", "-T", "-f", "-s", home_dir,
acloud_compat_home},
.env = common_envs,
.selector_args = cvd_common::Args{},
.working_dir = client_pwd,
});
}
std::vector<cvd::Request> request_protos;
for (const auto& request_form : request_forms) {
request_protos.emplace_back(MakeRequest(request_form));
}
std::vector<RequestWithStdio> new_requests;
auto dev_null = SharedFD::Open("/dev/null", O_RDWR);
CF_EXPECT(dev_null->IsOpen(), dev_null->StrError());
std::vector<SharedFD> dev_null_fds = {dev_null, dev_null, dev_null};
for (auto& request_proto : request_protos) {
new_requests.emplace_back(request.Client(), request_proto, dev_null_fds,
request.Credentials());
}
interrupt_lock.unlock();
CF_EXPECT(command_executor_.Execute(new_requests, dev_null));
return {};
}
void CvdStartCommandHandler::MarkLockfiles(
selector::GroupCreationInfo& group_info, const InUseState state) {
auto& instances = group_info.instances;
for (auto& instance : instances) {
if (!instance.instance_file_lock_) {
continue;
}
auto result = instance.instance_file_lock_->Status(state);
if (!result.ok()) {
LOG(ERROR) << result.error().FormatForEnv();
}
}
}
Result<bool> CvdStartCommandHandler::CanHandle(
const RequestWithStdio& request) const {
auto invocation = ParseInvocation(request.Message());
return Contains(supported_commands_, invocation.command);
}
Result<CvdStartCommandHandler::UpdatedArgsAndEnvs>
CvdStartCommandHandler::UpdateInstanceArgsAndEnvs(
cvd_common::Args&& args, cvd_common::Envs&& envs,
const std::vector<selector::PerInstanceInfo>& instances,
const std::string& artifacts_path, const std::string& start_bin) {
std::vector<unsigned> ids;
ids.reserve(instances.size());
for (const auto& instance : instances) {
ids.emplace_back(instance.instance_id_);
}
cvd_common::Args new_args{std::move(args)};
std::string old_instance_nums;
std::string old_num_instances;
std::string old_base_instance_num;
std::vector<Flag> instance_id_flags{
GflagsCompatFlag("instance_nums", old_instance_nums),
GflagsCompatFlag("num_instances", old_num_instances),
GflagsCompatFlag("base_instance_num", old_base_instance_num)};
// discard old ones
ParseFlags(instance_id_flags, new_args);
auto check_flag = [artifacts_path, start_bin,
this](const std::string& flag_name) -> Result<void> {
CF_EXPECT(
host_tool_target_manager_.ReadFlag({.artifacts_path = artifacts_path,
.op = "start",
.flag_name = flag_name}));
return {};
};
auto max = *(std::max_element(ids.cbegin(), ids.cend()));
auto min = *(std::min_element(ids.cbegin(), ids.cend()));
const bool is_consecutive = ((max - min) == (ids.size() - 1));
const bool is_sorted = std::is_sorted(ids.begin(), ids.end());
if (!is_consecutive || !is_sorted) {
std::string flag_value = android::base::Join(ids, ",");
CF_EXPECT(check_flag("instance_nums"));
new_args.emplace_back("--instance_nums=" + flag_value);
return UpdatedArgsAndEnvs{.args = std::move(new_args),
.envs = std::move(envs)};
}
// sorted and consecutive, so let's use old flags
// like --num_instances and --base_instance_num
if (ids.size() > 1) {
CF_EXPECT(check_flag("num_instances"),
"--num_instances is not supported but multi-tenancy requested.");
new_args.emplace_back("--num_instances=" + std::to_string(ids.size()));
}
cvd_common::Envs new_envs{std::move(envs)};
if (check_flag("base_instance_num").ok()) {
new_args.emplace_back("--base_instance_num=" + std::to_string(min));
}
new_envs[kCuttlefishInstanceEnvVarName] = std::to_string(min);
return UpdatedArgsAndEnvs{.args = std::move(new_args),
.envs = std::move(new_envs)};
}
Result<Command> CvdStartCommandHandler::ConstructCvdNonHelpCommand(
const std::string& bin_file, const selector::GroupCreationInfo& group_info,
const RequestWithStdio& request) {
auto bin_path = group_info.host_artifacts_path;
bin_path.append("/bin/").append(bin_file);
CF_EXPECT(!group_info.home.empty());
ConstructCommandParam construct_cmd_param{
.bin_path = bin_path,
.home = group_info.home,
.args = group_info.args,
.envs = group_info.envs,
.working_dir = request.Message().command_request().working_directory(),
.command_name = bin_file,
.in = request.In(),
.out = request.Out(),
.err = request.Err()};
Command non_help_command = CF_EXPECT(ConstructCommand(construct_cmd_param));
return non_help_command;
}
// call this only if !is_help
Result<selector::GroupCreationInfo>
CvdStartCommandHandler::GetGroupCreationInfo(
const std::string& start_bin, const std::string& subcmd,
const std::vector<std::string>& subcmd_args, const cvd_common::Envs& envs,
const RequestWithStdio& request) {
using CreationAnalyzerParam =
selector::CreationAnalyzer::CreationAnalyzerParam;
const auto& selector_opts =
request.Message().command_request().selector_opts();
const auto selector_args = cvd_common::ConvertToArgs(selector_opts.args());
CreationAnalyzerParam analyzer_param{
.cmd_args = subcmd_args, .envs = envs, .selector_args = selector_args};
auto cred = CF_EXPECT(request.Credentials());
auto group_creation_info =
CF_EXPECT(instance_manager_.Analyze(subcmd, analyzer_param, cred));
auto final_group_creation_info =
CF_EXPECT(UpdateArgsAndEnvs(std::move(group_creation_info), start_bin));
return final_group_creation_info;
}
Result<selector::GroupCreationInfo> CvdStartCommandHandler::UpdateArgsAndEnvs(
selector::GroupCreationInfo&& old_group_info,
const std::string& start_bin) {
selector::GroupCreationInfo group_creation_info = std::move(old_group_info);
// update instance related-flags, envs
const auto& instances = group_creation_info.instances;
const auto& host_artifacts_path = group_creation_info.host_artifacts_path;
auto [new_args, new_envs] = CF_EXPECT(UpdateInstanceArgsAndEnvs(
std::move(group_creation_info.args), std::move(group_creation_info.envs),
instances, host_artifacts_path, start_bin));
group_creation_info.args = std::move(new_args);
group_creation_info.envs = std::move(new_envs);
// for backward compatibility, older cvd host tools don't accept group_id
auto has_group_id_flag =
host_tool_target_manager_
.ReadFlag({.artifacts_path = group_creation_info.host_artifacts_path,
.op = "start",
.flag_name = "group_id"})
.ok();
if (has_group_id_flag) {
group_creation_info.args.emplace_back("--group_id=" +
group_creation_info.group_name);
}
group_creation_info.envs["HOME"] = group_creation_info.home;
group_creation_info.envs[kAndroidHostOut] =
group_creation_info.host_artifacts_path;
group_creation_info.envs[kAndroidProductOut] =
group_creation_info.product_out_path;
/* b/253644566
*
* Old branches used kAndroidSoongHostOut instead of kAndroidHostOut
*/
group_creation_info.envs[kAndroidSoongHostOut] =
group_creation_info.host_artifacts_path;
group_creation_info.envs[kCvdMarkEnv] = "true";
return group_creation_info;
}
static std::ostream& operator<<(std::ostream& out, const cvd_common::Args& v) {
if (v.empty()) {
return out;
}
for (int i = 0; i < v.size() - 1; i++) {
out << v.at(i) << " ";
}
out << v.back();
return out;
}
static void ShowLaunchCommand(const std::string& bin,
const cvd_common::Args& args,
const cvd_common::Envs& envs) {
std::stringstream ss;
std::vector<std::string> interesting_env_names{"HOME",
kAndroidHostOut,
kAndroidSoongHostOut,
"ANDROID_PRODUCT_OUT",
kCuttlefishInstanceEnvVarName,
kCuttlefishConfigEnvVarName};
for (const auto& interesting_env_name : interesting_env_names) {
if (Contains(envs, interesting_env_name)) {
ss << interesting_env_name << "=\"" << envs.at(interesting_env_name)
<< "\" ";
}
}
ss << " " << bin << " " << args;
LOG(ERROR) << "launcher command: " << ss.str();
}
static void ShowLaunchCommand(const std::string& bin,
selector::GroupCreationInfo& group_info) {
ShowLaunchCommand(bin, group_info.args, group_info.envs);
}
Result<std::string> CvdStartCommandHandler::FindStartBin(
const std::string& android_host_out) {
auto start_bin = CF_EXPECT(host_tool_target_manager_.ExecBaseName({
.artifacts_path = android_host_out,
.op = "start",
}));
return start_bin;
}
Result<bool> IsDaemonModeFlag(const cvd_common::Args& args) {
bool flag_set = false;
bool is_daemon = true;
Flag flag =
Flag()
.Alias({FlagAliasMode::kFlagPrefix, "-daemon="})
.Alias({FlagAliasMode::kFlagPrefix, "--daemon="})
.Alias({FlagAliasMode::kFlagExact, "-daemon"})
.Alias({FlagAliasMode::kFlagExact, "--daemon"})
.Alias({FlagAliasMode::kFlagExact, "-nodaemon"})
.Alias({FlagAliasMode::kFlagExact, "--nodaemon"})
.Setter([&is_daemon,
&flag_set](const FlagMatch& match) -> Result<void> {
flag_set = true;
if (match.key == match.value) {
is_daemon = match.key.find("no") == std::string::npos;
return {};
}
CF_EXPECTF(match.value.find(",") == std::string::npos,
"{} had a comma", match.value);
static constexpr std::string_view kFalseStrings[] = {"n", "no",
"false"};
for (const auto& falseString : kFalseStrings) {
if (android::base::EqualsIgnoreCase(falseString, match.value)) {
is_daemon = false;
}
}
// Allow `cvd_internal_start` to produce its own error for other
// invalid strings.
return {};
});
auto args_copy = args;
CF_EXPECT(ParseFlags({flag}, args_copy));
return flag_set && is_daemon;
}
// For backward compatibility, we add extra symlink in system wide home
// when HOME is NOT overridden and selector flags are NOT given.
Result<void> CvdStartCommandHandler::CreateSymlinks(
const selector::GroupCreationInfo& group_creation_info) {
std::string instance_home_dir = "";
auto system_wide_home = CF_EXPECT(SystemWideUserHome());
for (const auto& instance : group_creation_info.instances) {
std::string legacy_path = system_wide_home + "/cuttlefish_runtime.";
legacy_path = ConcatToString(legacy_path, instance.instance_id_);
instance_home_dir = group_creation_info.home;
instance_home_dir = instance_home_dir + "/cuttlefish/instances/cvd-";
instance_home_dir = ConcatToString(instance_home_dir, instance.instance_id_);
if (DirectoryExists(legacy_path, /* follow_symlinks */ true)) {
CF_EXPECT(RecursivelyRemoveDirectory(legacy_path),
"Failed to remove legacy directory " << legacy_path);
}
if (symlink(instance_home_dir.c_str(), legacy_path.c_str())) {
return CF_ERRNO("symlink(\"" << instance_home_dir << "\", \""
<< legacy_path << "\") failed");
}
legacy_path = system_wide_home + "/cuttlefish_runtime";
if (DirectoryExists(legacy_path, true)) {
CF_EXPECT(RecursivelyRemoveDirectory(legacy_path),
"Failed to remove legacy directory " << legacy_path);
}
if (symlink(instance_home_dir.c_str(), legacy_path.c_str())) {
return CF_ERRNO("symlink(\"" << instance_home_dir << "\", \""
<< legacy_path << "\") failed");
}
std::string cuttlefish_path = group_creation_info.home + "/cuttlefish/";
legacy_path = system_wide_home + "/cuttlefish";
if (DirectoryExists(legacy_path, true)) {
CF_EXPECT(RecursivelyRemoveDirectory(legacy_path),
"Failed to remove legacy directory " << legacy_path);
}
if (symlink(cuttlefish_path.c_str(), legacy_path.c_str())) {
return CF_ERRNO("symlink(\"" << cuttlefish_path << "\", \"" << legacy_path
<< "\") failed");
}
std::string cuttlefish_assembly_path = cuttlefish_path + "assembly/";
legacy_path = system_wide_home + "/cuttlefish_assembly";
if (DirectoryExists(legacy_path, true)) {
CF_EXPECT(RecursivelyRemoveDirectory(legacy_path),
"Failed to remove legacy directory " << legacy_path);
}
if (symlink(cuttlefish_assembly_path.c_str(), legacy_path.c_str())) {
return CF_ERRNO("symlink(\"" << cuttlefish_assembly_path << "\", \""
<< legacy_path << "\") failed");
}
std::string config_path =
cuttlefish_assembly_path + "cuttlefish_config.json";
legacy_path = system_wide_home + "/.cuttlefish_config.json";
if (FileExists(legacy_path, false)) {
CF_EXPECT(RemoveFile(legacy_path),
"Failed to remove instance_dir symlink " << legacy_path);
}
if (symlink(config_path.c_str(), legacy_path.c_str())) {
return CF_ERRNO("symlink(\"" << config_path << "\", \"" << legacy_path
<< "\") failed");
}
}
return {};
}
Result<cvd::Response> CvdStartCommandHandler::Handle(
const RequestWithStdio& request) {
std::unique_lock interrupt_lock(interruptible_);
if (interrupted_) {
return CF_ERR("Interrupted");
}
CF_EXPECT(CanHandle(request));
cvd::Response response;
response.mutable_command_response();
auto [subcmd, subcmd_args] = ParseInvocation(request.Message());
std::optional<std::string> config_file = GetConfigPath(subcmd_args);
if (config_file) {
auto subrequest = CreateLoadCommand(request, subcmd_args, *config_file);
interrupt_lock.unlock();
response =
CF_EXPECT(command_executor_.ExecuteOne(subrequest, request.Err()));
sub_action_ended_ = true;
return 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;
}
const uid_t uid = request.Credentials()->uid;
cvd_common::Envs envs =
cvd_common::ConvertToEnvs(request.Message().command_request().env());
if (Contains(envs, "HOME")) {
if (envs.at("HOME").empty()) {
envs.erase("HOME");
} else {
// As the end-user may override HOME, this could be a relative path
// to client's pwd, or may include "~" which is the client's actual
// home directory.
auto client_pwd = request.Message().command_request().working_directory();
const auto given_home_dir = envs.at("HOME");
/*
* Imagine this scenario:
* client$ export HOME=/tmp/new/dir
* client$ HOME="~/subdir" cvd start
*
* The value of ~ isn't sent to the server. The server can't figure that
* out as it might be overridden before the cvd start command.
*/
CF_EXPECT(!android::base::StartsWith(given_home_dir, "~") &&
!android::base::StartsWith(given_home_dir, "~/"),
"The HOME directory should not start with ~");
envs["HOME"] = CF_EXPECT(
EmulateAbsolutePath({.current_working_dir = client_pwd,
.home_dir = CF_EXPECT(SystemWideUserHome(uid)),
.path_to_convert = given_home_dir,
.follow_symlink = false}));
}
}
CF_EXPECT(Contains(envs, kAndroidHostOut));
const auto bin = CF_EXPECT(FindStartBin(envs.at(kAndroidHostOut)));
// update DB if not help
// collect group creation infos
CF_EXPECT(Contains(supported_commands_, subcmd),
"subcmd should be start but is " << subcmd);
const bool is_help = HasHelpOpts(subcmd_args);
const bool is_daemon = CF_EXPECT(IsDaemonModeFlag(subcmd_args));
std::optional<selector::GroupCreationInfo> group_creation_info;
if (!is_help) {
group_creation_info = CF_EXPECT(
GetGroupCreationInfo(bin, subcmd, subcmd_args, envs, request));
CF_EXPECT(UpdateInstanceDatabase(uid, *group_creation_info));
response = CF_EXPECT(
FillOutNewInstanceInfo(std::move(response), *group_creation_info));
}
Command command =
is_help
? CF_EXPECT(ConstructCvdHelpCommand(bin, envs, subcmd_args, request))
: CF_EXPECT(
ConstructCvdNonHelpCommand(bin, *group_creation_info, request));
if (!is_help) {
CF_EXPECT(
group_creation_info != std::nullopt,
"group_creation_info should be nullopt only when --help is given.");
}
if (is_help) {
ShowLaunchCommand(command.Executable(), subcmd_args, envs);
} else {
ShowLaunchCommand(command.Executable(), *group_creation_info);
CF_EXPECT(request.Message().command_request().wait_behavior() !=
cvd::WAIT_BEHAVIOR_START);
}
FireCommand(std::move(command), /*should_wait*/ true);
interrupt_lock.unlock();
if (is_help) {
auto infop = CF_EXPECT(subprocess_waiter_.Wait());
return ResponseFromSiginfo(infop);
}
// For backward compatibility, we add extra symlink in system wide home
// when HOME is NOT overridden and selector flags are NOT given.
if (group_creation_info->is_default_group) {
CreateSymlinks(*group_creation_info);
}
// make acquire interrupt_lock inside.
auto acloud_compat_action_result =
AcloudCompatActions(*group_creation_info, request);
sub_action_ended_ = true;
if (!acloud_compat_action_result.ok()) {
LOG(ERROR) << acloud_compat_action_result.error().FormatForEnv();
LOG(ERROR) << "AcloudCompatActions() failed"
<< " but continue as they are minor errors.";
}
return PostStartExecutionActions(group_creation_info, uid, is_daemon);
}
Result<cvd::Response> CvdStartCommandHandler::PostStartExecutionActions(
std::optional<selector::GroupCreationInfo>& group_creation_info,
const uid_t uid, const bool is_daemonized) {
auto infop = CF_EXPECT(subprocess_waiter_.Wait());
if (infop.si_code != CLD_EXITED || infop.si_status != EXIT_SUCCESS) {
// perhaps failed in launch
instance_manager_.RemoveInstanceGroup(uid, group_creation_info->home);
}
auto final_response = ResponseFromSiginfo(infop);
if (!final_response.has_status() ||
final_response.status().code() != cvd::Status::OK) {
return final_response;
}
if (is_daemonized) {
// If not daemonized, reaching here means the instance group terminated.
// Thus, it's enough to release the file lock in the destructor.
// If daemonized, reaching here means the group started successfully
// As the destructor will release the file lock, the instance lock
// files must be marked as used
MarkLockfilesInUse(*group_creation_info);
}
// group_creation_info is nullopt only if is_help is false
return FillOutNewInstanceInfo(std::move(final_response),
*group_creation_info);
}
Result<void> CvdStartCommandHandler::Interrupt() {
std::scoped_lock interrupt_lock(interruptible_);
interrupted_ = true;
if (!sub_action_ended_) {
auto result = command_executor_.Interrupt();
if (!result.ok()) {
LOG(ERROR) << "Failed to interrupt CommandExecutor"
<< result.error().FormatForEnv();
}
}
CF_EXPECT(subprocess_waiter_.Interrupt());
return {};
}
Result<cvd::Response> CvdStartCommandHandler::FillOutNewInstanceInfo(
cvd::Response&& response,
const selector::GroupCreationInfo& group_creation_info) {
auto new_response = std::move(response);
auto& command_response = *(new_response.mutable_command_response());
auto& instance_group_info =
*(CF_EXPECT(command_response.mutable_instance_group_info()));
instance_group_info.set_group_name(group_creation_info.group_name);
instance_group_info.add_home_directories(group_creation_info.home);
for (const auto& per_instance_info : group_creation_info.instances) {
auto* new_entry = CF_EXPECT(instance_group_info.add_instances());
new_entry->set_name(per_instance_info.per_instance_name_);
new_entry->set_instance_id(per_instance_info.instance_id_);
}
return new_response;
}
Result<void> CvdStartCommandHandler::UpdateInstanceDatabase(
const uid_t uid, const selector::GroupCreationInfo& group_creation_info) {
CF_EXPECT(instance_manager_.SetInstanceGroup(uid, group_creation_info),
group_creation_info.home
<< " is already taken so can't create new instance.");
return {};
}
Result<void> CvdStartCommandHandler::FireCommand(Command&& command,
const bool wait) {
SubprocessOptions options;
if (!wait) {
options.ExitWithParent(false);
}
CF_EXPECT(subprocess_waiter_.Setup(command.Start(options)));
return {};
}
bool CvdStartCommandHandler::HasHelpOpts(
const std::vector<std::string>& args) const {
return IsHelpSubcmd(args);
}
std::vector<std::string> CvdStartCommandHandler::CmdList() const {
std::vector<std::string> subcmd_list;
subcmd_list.reserve(supported_commands_.size());
for (const auto& cmd : supported_commands_) {
subcmd_list.emplace_back(cmd);
}
return subcmd_list;
}
const std::array<std::string, 2> CvdStartCommandHandler::supported_commands_{
"start", "launch_cvd"};
std::unique_ptr<CvdServerHandler> NewCvdStartCommandHandler(
InstanceManager& instance_manager,
HostToolTargetManager& host_tool_target_manager,
CommandSequenceExecutor& executor) {
return std::unique_ptr<CvdServerHandler>(new CvdStartCommandHandler(
instance_manager, host_tool_target_manager, executor));
}
} // namespace cuttlefish