blob: b61eac88e58d364f7ac9d7b63d8f11af98c17107 [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 <regex>
#include <sstream>
#include <string>
#include <android-base/parseint.h>
#include <android-base/strings.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/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/selector/selector_constants.h"
#include "host/commands/cvd/server_command/generic.h"
#include "host/commands/cvd/server_command/server_handler.h"
#include "host/commands/cvd/server_command/start_impl.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/cuttlefish_config.h"
namespace cuttlefish {
class CvdStartCommandHandler : public CvdServerHandler {
public:
INJECT(
CvdStartCommandHandler(InstanceManager& instance_manager,
HostToolTargetManager& host_tool_target_manager))
: instance_manager_(instance_manager),
host_tool_target_manager_(host_tool_target_manager),
acloud_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);
static Result<std::vector<std::string>> UpdateWebrtcDeviceId(
std::vector<std::string>&& args, const std::string& group_name,
const std::vector<selector::PerInstanceInfo>& per_instance_info);
Result<selector::GroupCreationInfo> UpdateArgsAndEnvs(
selector::GroupCreationInfo&& old_group_info,
const std::string& start_bin);
Result<std::string> FindStartBin(const std::string& android_host_out);
Result<void> SetBuildId(const uid_t uid, const std::string& group_name,
const std::string& home);
static void MarkLockfiles(selector::GroupCreationInfo& group_info,
const InUseState state);
static void MarkLockfilesInUse(selector::GroupCreationInfo& group_info) {
MarkLockfiles(group_info, InUseState::kInUse);
}
Result<void> HandleNoDaemonWorker(
const selector::GroupCreationInfo& group_creation_info,
std::atomic<bool>* interrupted, const uid_t uid);
Result<cvd::Response> HandleNoDaemon(
const std::optional<selector::GroupCreationInfo>& group_creation_info,
const uid_t uid);
Result<cvd::Response> HandleDaemon(
std::optional<selector::GroupCreationInfo>& group_creation_info,
const uid_t uid);
Result<void> AcloudCompatActions(
const selector::GroupCreationInfo& group_creation_info,
const RequestWithStdio& request);
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> acloud_action_ended_;
static const std::array<std::string, 2> supported_commands_;
};
fruit::Component<> GenericNestedHandlerComponent(
InstanceManager* instance_manager,
HostToolTargetManager* host_tool_target_manager,
SubprocessWaiter* subprocess_waiter_for_nested_handler) {
return fruit::createComponent()
.bindInstance(*instance_manager)
.bindInstance(*host_tool_target_manager)
.bindInstance(*subprocess_waiter_for_nested_handler)
.install(cvdGenericCommandComponent);
}
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 (!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.
if (!Contains(group_creation_info.envs, kLaunchedByAcloud) ||
group_creation_info.envs.at(kLaunchedByAcloud) != "true") {
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(
{.working_dir = client_pwd,
.cmd_args = cvd_common::Args{"mkdir", "-p", home_dir},
.env = common_envs,
.selector_args = cvd_common::Args{}});
const std::string& android_host_out = group_creation_info.host_artifacts_path;
request_forms.push_back(
{.working_dir = client_pwd,
.cmd_args = cvd_common::Args{"ln", "-f", "-s", android_host_out,
home_dir + "/host_bins"},
.env = common_envs,
.selector_args = cvd_common::Args{}});
/* 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({
.working_dir = client_pwd,
.cmd_args =
cvd_common::Args{"ln", "-f", "-s", home_dir, acloud_compat_home},
.env = common_envs,
.selector_args = cvd_common::Args{},
});
}
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());
}
SubprocessWaiter subprocess_waiter;
// injector only with the GenericCommandHandler for ln and mkdir
fruit::Injector<> injector(GenericNestedHandlerComponent,
std::addressof(this->instance_manager_),
std::addressof(this->host_tool_target_manager_),
std::addressof(subprocess_waiter));
CF_EXPECT(command_executor_.LateInject(injector),
"Creating local CommandSequenceExecutor in cvd start failed.");
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().Message();
}
}
}
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)};
}
/*
* Adds --webrtc_device_id when necessary to cmd_args_
*/
Result<std::vector<std::string>> CvdStartCommandHandler::UpdateWebrtcDeviceId(
std::vector<std::string>&& args, const std::string& group_name,
const std::vector<selector::PerInstanceInfo>& per_instance_info) {
std::vector<std::string> new_args{std::move(args)};
// consume webrtc_device_id
// it was verified by start_selector_parser
std::string flag_value;
std::vector<Flag> webrtc_device_id_flag{
GflagsCompatFlag("webrtc_device_id", flag_value)};
CF_EXPECT(ParseFlags(webrtc_device_id_flag, new_args));
CF_EXPECT(!group_name.empty());
std::vector<std::string> device_name_list;
device_name_list.reserve(per_instance_info.size());
for (const auto& instance : per_instance_info) {
const auto& per_instance_name = instance.per_instance_name_;
std::string device_name{group_name};
device_name.append("-").append(per_instance_name);
device_name_list.emplace_back(device_name);
}
// take --webrtc_device_id flag away
new_args.emplace_back("--webrtc_device_id=" +
android::base::Join(device_name_list, ","));
return new_args;
}
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);
auto webrtc_device_id_flag = host_tool_target_manager_.ReadFlag(
{.artifacts_path = group_creation_info.host_artifacts_path,
.op = "start",
.flag_name = "webrtc_device_id"});
if (webrtc_device_id_flag.ok()) {
group_creation_info.args = CF_EXPECT(UpdateWebrtcDeviceId(
std::move(group_creation_info.args), group_creation_info.group_name,
group_creation_info.instances));
}
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;
}
// std::string -> bool
enum class BoolValueType : std::uint8_t { kTrue = 0, kFalse, kUnknown };
static Result<bool> IsDaemonModeFlag(const cvd_common::Args& args) {
/*
* --daemon could be either bool or string flags.
*/
bool is_daemon = false;
auto initial_size = args.size();
Flag daemon_bool = GflagsCompatFlag("daemon", is_daemon);
std::vector<Flag> as_bool_flags{daemon_bool};
cvd_common::Args copied_args{args};
if (ParseFlags(as_bool_flags, copied_args)) {
if (initial_size != copied_args.size()) {
return is_daemon;
}
}
std::string daemon_values;
Flag daemon_string = GflagsCompatFlag("daemon", daemon_values);
cvd_common::Args copied_args2{args};
std::vector<Flag> as_string_flags{daemon_string};
if (!ParseFlags(as_string_flags, copied_args2)) {
return false;
}
if (initial_size == copied_args2.size()) {
return false; // not consumed
}
// --daemon should have been handled above
CF_EXPECT(!daemon_values.empty());
std::unordered_set<std::string> true_strings = {"y", "yes", "true"};
std::unordered_set<std::string> false_strings = {"n", "no", "false"};
auto tokens = android::base::Tokenize(daemon_values, ",");
std::unordered_set<BoolValueType> value_set;
for (const auto& token : tokens) {
std::string daemon_value(token);
/*
* https://en.cppreference.com/w/cpp/string/byte/tolower
*
* char should be converted to unsigned char first.
*/
std::transform(daemon_value.begin(), daemon_value.end(),
daemon_value.begin(),
[](unsigned char c) { return std::tolower(c); });
if (Contains(true_strings, daemon_value)) {
value_set.insert(BoolValueType::kTrue);
continue;
}
if (Contains(false_strings, daemon_value)) {
value_set.insert(BoolValueType::kFalse);
} else {
value_set.insert(BoolValueType::kUnknown);
}
}
CF_EXPECT_LE(value_set.size(), 1,
"Vectorized flags for --daemon is not supported by cvd");
const auto only_element = *(value_set.begin());
// We want to, basically, launch with daemon mode, and want to know
// when we must not do so
if (only_element == BoolValueType::kFalse) {
return false;
}
// if kUnknown, the launcher will fail. Which mode doesn't matter
// for the launcher. But it matters for cvd in how cvd handles the
// failure.
return true;
}
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 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
auto [subcmd, subcmd_args] = ParseInvocation(request.Message());
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);
}
// make acquire interrupt_lock inside.
auto acloud_compat_action_result =
AcloudCompatActions(*group_creation_info, request);
acloud_action_ended_ = true;
if (!acloud_compat_action_result.ok()) {
LOG(ERROR) << acloud_compat_action_result.error().Trace();
LOG(ERROR) << "AcloudCompatActions() failed"
<< " but continue as they are minor errors.";
}
return is_daemon ? HandleDaemon(group_creation_info, uid)
: HandleNoDaemon(group_creation_info, uid);
}
Result<void> CvdStartCommandHandler::HandleNoDaemonWorker(
const selector::GroupCreationInfo& group_creation_info,
std::atomic<bool>* interrupted, const uid_t uid) {
const std::string home_dir = group_creation_info.home;
const std::string group_name = group_creation_info.group_name;
std::string kernel_log_path =
ConcatToString(home_dir, "/cuttlefish_runtime/kernel.log");
std::regex finger_pattern(
"\\[\\s*[0-9]*\\.[0-9]+\\]\\s*GUEST_BUILD_FINGERPRINT:");
std::regex boot_pattern("VIRTUAL_DEVICE_BOOT_COMPLETED");
std::streampos last_pos;
bool first_iteration = true;
while (*interrupted == false) {
if (!FileExists(kernel_log_path)) {
LOG(ERROR) << kernel_log_path << " does not yet exist, so wait for 5s";
using namespace std::chrono_literals;
std::this_thread::sleep_for(5s);
continue;
}
std::ifstream kernel_log_file(kernel_log_path);
CF_EXPECT(kernel_log_file.is_open(),
"The kernel log file exists but it cannot be open.");
if (!first_iteration) {
kernel_log_file.seekg(last_pos);
} else {
first_iteration = false;
last_pos = kernel_log_file.tellg();
}
for (std::string line; std::getline(kernel_log_file, line);) {
last_pos = kernel_log_file.tellg();
// if the line broke before a newline, this will end up reading the
// previous line one more time but only with '\n'. That's okay
last_pos -= line.size();
if (last_pos != std::ios_base::beg) {
last_pos -= std::string("\n").size();
}
std::smatch matched;
if (std::regex_search(line, matched, finger_pattern)) {
std::string build_id = matched.suffix().str();
CF_EXPECT(instance_manager_.SetBuildId(uid, group_name, build_id));
continue;
}
if (std::regex_search(line, matched, boot_pattern)) {
return {};
}
}
using namespace std::chrono_literals;
std::this_thread::sleep_for(2s);
}
return CF_ERR("Cvd start kernel monitor interrupted.");
}
Result<cvd::Response> CvdStartCommandHandler::HandleNoDaemon(
const std::optional<selector::GroupCreationInfo>& group_creation_info,
const uid_t uid) {
std::atomic<bool> interrupted;
std::atomic<bool> worker_success;
interrupted = false;
worker_success = false;
const auto* group_info = std::addressof(*group_creation_info);
auto* interrupted_ptr = std::addressof(interrupted);
auto* worker_success_ptr = std::addressof(worker_success);
std::thread worker = std::thread(
[this, group_info, interrupted_ptr, worker_success_ptr, uid]() {
LOG(ERROR) << "worker thread started.";
auto result = HandleNoDaemonWorker(*group_info, interrupted_ptr, uid);
*worker_success_ptr = result.ok();
if (*worker_success_ptr == false) {
LOG(ERROR) << result.error().Trace();
}
});
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);
interrupted = true;
}
worker.join();
auto final_response = ResponseFromSiginfo(infop);
if (!final_response.has_status() ||
final_response.status().code() != cvd::Status::OK) {
return final_response;
}
// group_creation_info is nullopt only if is_help is false
return FillOutNewInstanceInfo(std::move(final_response),
*group_creation_info);
}
Result<cvd::Response> CvdStartCommandHandler::HandleDaemon(
std::optional<selector::GroupCreationInfo>& group_creation_info,
const uid_t uid) {
auto infop = CF_EXPECT(subprocess_waiter_.Wait());
if (infop.si_code != CLD_EXITED || infop.si_status != EXIT_SUCCESS) {
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;
}
MarkLockfilesInUse(*group_creation_info);
auto set_build_id_result = SetBuildId(uid, group_creation_info->group_name,
group_creation_info->home);
if (!set_build_id_result.ok()) {
LOG(ERROR) << "Failed to set a build Id for "
<< group_creation_info->group_name << " but will continue.";
LOG(ERROR) << "The error message was : "
<< set_build_id_result.error().Trace();
}
// group_creation_info is nullopt only if is_help is false
return FillOutNewInstanceInfo(std::move(final_response),
*group_creation_info);
}
Result<void> CvdStartCommandHandler::SetBuildId(const uid_t uid,
const std::string& group_name,
const std::string& home) {
// build id can't be found before this point
const auto build_id = CF_EXPECT(cvd_start_impl::ExtractBuildId(home));
CF_EXPECT(instance_manager_.SetBuildId(uid, group_name, build_id));
return {};
}
Result<void> CvdStartCommandHandler::Interrupt() {
std::scoped_lock interrupt_lock(interruptible_);
interrupted_ = true;
if (!acloud_action_ended_) {
auto result = command_executor_.Interrupt();
if (!result.ok()) {
LOG(ERROR) << "Failed to interrupt CommandExecutor"
<< result.error().Message();
}
}
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"};
fruit::Component<fruit::Required<InstanceManager, HostToolTargetManager>>
CvdStartCommandComponent() {
return fruit::createComponent()
.addMultibinding<CvdServerHandler, CvdStartCommandHandler>();
}
} // namespace cuttlefish