/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #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 CanHandle(const RequestWithStdio& request) const; Result Handle(const RequestWithStdio& request) override; Result Interrupt() override; std::vector CmdList() const override; private: Result UpdateInstanceDatabase( const uid_t uid, const selector::GroupCreationInfo& group_creation_info); Result FireCommand(Command&& command, const bool wait); bool HasHelpOpts(const cvd_common::Args& args) const; Result ConstructCvdNonHelpCommand( const std::string& bin_file, const selector::GroupCreationInfo& group_info, const RequestWithStdio& request); // call this only if !is_help Result 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 FillOutNewInstanceInfo( cvd::Response&& response, const selector::GroupCreationInfo& group_creation_info); struct UpdatedArgsAndEnvs { cvd_common::Args args; cvd_common::Envs envs; }; Result UpdateInstanceArgsAndEnvs( cvd_common::Args&& args, cvd_common::Envs&& envs, const std::vector& instances, const std::string& artifacts_path, const std::string& start_bin); static Result> UpdateWebrtcDeviceId( std::vector&& args, const std::string& group_name, const std::vector& per_instance_info); Result UpdateArgsAndEnvs( selector::GroupCreationInfo&& old_group_info, const std::string& start_bin); Result FindStartBin(const std::string& android_host_out); Result 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 HandleNoDaemonWorker( const selector::GroupCreationInfo& group_creation_info, std::atomic* interrupted, const uid_t uid); Result HandleNoDaemon( const std::optional& group_creation_info, const uid_t uid); Result HandleDaemon( std::optional& group_creation_info, const uid_t uid); Result 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 acloud_action_ended_; static const std::array 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 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-" std::string acloud_compat_home_prefix = TempDir() + "/acloud_cvd_temp/local-instance-"; std::vector 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- std::vector 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", "-T", "-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-/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", "-T", "-f", "-s", home_dir, acloud_compat_home}, .env = common_envs, .selector_args = cvd_common::Args{}, }); } std::vector request_protos; for (const auto& request_form : request_forms) { request_protos.emplace_back(MakeRequest(request_form)); } std::vector new_requests; auto dev_null = SharedFD::Open("/dev/null", O_RDWR); CF_EXPECT(dev_null->IsOpen(), dev_null->StrError()); std::vector 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 CvdStartCommandHandler::CanHandle( const RequestWithStdio& request) const { auto invocation = ParseInvocation(request.Message()); return Contains(supported_commands_, invocation.command); } Result CvdStartCommandHandler::UpdateInstanceArgsAndEnvs( cvd_common::Args&& args, cvd_common::Envs&& envs, const std::vector& instances, const std::string& artifacts_path, const std::string& start_bin) { std::vector 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 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 { 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> CvdStartCommandHandler::UpdateWebrtcDeviceId( std::vector&& args, const std::string& group_name, const std::vector& per_instance_info) { std::vector new_args{std::move(args)}; // consume webrtc_device_id // it was verified by start_selector_parser std::string flag_value; std::vector 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 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 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 CvdStartCommandHandler::GetGroupCreationInfo( const std::string& start_bin, const std::string& subcmd, const std::vector& 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 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 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 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 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 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 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 true_strings = {"y", "yes", "true"}; std::unordered_set false_strings = {"n", "no", "false"}; auto tokens = android::base::Tokenize(daemon_values, ","); std::unordered_set 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 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 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 CvdStartCommandHandler::HandleNoDaemonWorker( const selector::GroupCreationInfo& group_creation_info, std::atomic* 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 CvdStartCommandHandler::HandleNoDaemon( const std::optional& group_creation_info, const uid_t uid) { std::atomic interrupted; std::atomic 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 CvdStartCommandHandler::HandleDaemon( std::optional& 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 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 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 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 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 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& args) const { return IsHelpSubcmd(args); } std::vector CvdStartCommandHandler::CmdList() const { std::vector 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 CvdStartCommandHandler::supported_commands_{ "start", "launch_cvd"}; fruit::Component> CvdStartCommandComponent() { return fruit::createComponent() .addMultibinding(); } } // namespace cuttlefish