/* * Copyright (C) 2023 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/reset_client_utils.h" #include #include #include #include // std::setw #include // std::endl #include #include #include #include #include "common/libs/utils/contains.h" #include "common/libs/utils/files.h" #include "common/libs/utils/proc_file_utils.h" #include "common/libs/utils/subprocess.h" #include "host/commands/cvd/common_utils.h" #include "host/commands/cvd/reset_client_utils.h" #include "host/libs/config/cuttlefish_config.h" namespace cuttlefish { static bool IsTrue(const std::string& value) { std::unordered_set true_strings = {"y", "yes", "true"}; std::string value_in_lower_case = value; /* * https://en.cppreference.com/w/cpp/string/byte/tolower * * char should be converted to unsigned char first. */ std::transform(value_in_lower_case.begin(), value_in_lower_case.end(), value_in_lower_case.begin(), [](unsigned char c) { return std::tolower(c); }); return Contains(true_strings, value_in_lower_case); } Result RunCvdProcessManager::AnalyzeRunCvdProcess(const pid_t pid) { auto proc_info = CF_EXPECT(ExtractProcInfo(pid)); RunCvdProcInfo info; info.pid_ = proc_info.pid_; info.exec_path_ = proc_info.actual_exec_path_; info.cmd_args_ = std::move(proc_info.args_); info.envs_ = std::move(proc_info.envs_); CF_EXPECT(Contains(info.envs_, "HOME")); info.home_ = info.envs_.at("HOME"); if (Contains(info.envs_, kAndroidHostOut)) { info.android_host_out_ = info.envs_[kAndroidHostOut]; } else { if (Contains(info.envs_, kAndroidSoongHostOut)) { info.android_host_out_ = info.envs_[kAndroidSoongHostOut]; } } CF_EXPECT(Contains(info.envs_, kCuttlefishInstanceEnvVarName)); int id; CF_EXPECT(android::base::ParseInt( info.envs_.at(kCuttlefishInstanceEnvVarName), &id)); info.id_ = static_cast(id); if (!Contains(info.envs_, kAndroidHostOut) && !Contains(info.envs_, kAndroidSoongHostOut)) { const std::string server_host_out = android::base::Dirname(android::base::GetExecutableDirectory()); info.envs_[kAndroidHostOut] = server_host_out; info.envs_[kAndroidSoongHostOut] = server_host_out; } if (Contains(info.envs_, kCvdMarkEnv) && IsTrue(info.envs_.at(kCvdMarkEnv))) { info.is_cvd_server_started_ = true; } std::vector stop_bins{"cvd_internal_stop", "stop_cvd"}; if (Contains(info.envs_, kAndroidHostOut)) { for (const auto& bin : stop_bins) { std::string internal_stop_path = ConcatToString(info.envs_.at(kAndroidHostOut), "/bin/", bin); if (!FileExists(internal_stop_path)) { continue; } info.stop_cvd_path_ = std::move(internal_stop_path); return info; } } for (const auto& bin : stop_bins) { std::string stop_cvd_path = ConcatToString(info.envs_.at(kAndroidSoongHostOut), "/bin/", bin); if (!FileExists(stop_cvd_path)) { continue; } info.stop_cvd_path_ = std::move(stop_cvd_path); return info; } return CF_ERR("cvd_internal_stop or stop_cvd cannot be found for " << " pid #" << pid); } static Command CreateStopCvdCommand(const std::string& stopper_path, const cvd_common::Envs& envs, const cvd_common::Args& args) { Command command(cpp_basename(stopper_path)); command.SetExecutable(stopper_path); for (const auto& arg : args) { command.AddParameter(arg); } for (const auto& [key, value] : envs) { command.AddEnvironmentVariable(key, value); } return command; } Result RunCvdProcessManager::Get() { RunCvdProcessManager run_cvd_processes_manager; run_cvd_processes_manager.cf_groups_ = CF_EXPECT(run_cvd_processes_manager.CollectInfo()); return run_cvd_processes_manager; } Result> RunCvdProcessManager::CollectInfo() { auto run_cvd_pids = CF_EXPECT(CollectPidsByExecName("run_cvd")); std::vector run_cvd_infos; run_cvd_infos.reserve(run_cvd_pids.size()); for (const auto run_cvd_pid : run_cvd_pids) { auto run_cvd_info_result = AnalyzeRunCvdProcess(run_cvd_pid); if (!run_cvd_info_result.ok()) { LOG(ERROR) << "Failed to collect information for run_cvd at #" << run_cvd_pid << std::endl << run_cvd_info_result.error().Trace(); continue; } run_cvd_infos.push_back(*run_cvd_info_result); } // home --> group map std::unordered_map groups; for (auto& run_cvd_info : run_cvd_infos) { const auto home = run_cvd_info.home_; if (!Contains(groups, home)) { // create using a default constructor groups[home] = GroupProcInfo(); groups[home].home_ = home; groups[home].exec_path_ = run_cvd_info.exec_path_; groups[home].stop_cvd_path_ = run_cvd_info.stop_cvd_path_; groups[home].is_cvd_server_started_ = run_cvd_info.is_cvd_server_started_; groups[home].android_host_out_ = run_cvd_info.android_host_out_; } auto& id_instance_map = groups[home].instances_; if (!Contains(id_instance_map, run_cvd_info.id_)) { id_instance_map[run_cvd_info.id_] = GroupProcInfo::InstanceInfo{ .pids_ = std::set{run_cvd_info.pid_}, .envs_ = std::move(run_cvd_info.envs_), .cmd_args_ = std::move(run_cvd_info.cmd_args_), .id_ = run_cvd_info.id_}; continue; } // this is the other run_cvd process under the same instance i id_instance_map[run_cvd_info.id_].pids_.insert(run_cvd_info.pid_); } std::vector output; output.reserve(groups.size()); for (auto& [_, group] : groups) { output.push_back(std::move(group)); } return output; } Result RunCvdProcessManager::RunStopCvd(const GroupProcInfo& group_info, const bool clear_runtime_dirs) { const auto& stopper_path = group_info.stop_cvd_path_; int ret_code = 0; cvd_common::Envs stop_cvd_envs; stop_cvd_envs["HOME"] = group_info.home_; if (group_info.android_host_out_) { stop_cvd_envs[kAndroidHostOut] = group_info.android_host_out_.value(); stop_cvd_envs[kAndroidSoongHostOut] = group_info.android_host_out_.value(); } else { auto android_host_out = StringFromEnv( kAndroidHostOut, android::base::Dirname(android::base::GetExecutableDirectory())); stop_cvd_envs[kAndroidHostOut] = android_host_out; stop_cvd_envs[kAndroidSoongHostOut] = android_host_out; } if (clear_runtime_dirs) { Command first_stop_cvd = CreateStopCvdCommand( stopper_path, stop_cvd_envs, {"--clear_instance_dirs=true"}); LOG(ERROR) << "Running HOME=" << stop_cvd_envs.at("HOME") << " " << stopper_path << " --clear_instance_dirs"; std::string stdout_str; std::string stderr_str; ret_code = RunWithManagedStdio(std::move(first_stop_cvd), nullptr, std::addressof(stdout_str), std::addressof(stderr_str)); // TODO(kwstephenkim): deletes manually if `stop_cvd --clear_instance_dirs` // failed. } if (!clear_runtime_dirs || ret_code != 0) { if (clear_runtime_dirs) { LOG(ERROR) << "Failed to run " << stopper_path << " --clear_runtime_dirs=true"; LOG(ERROR) << "Perhaps --clear_instance_dirs is not taken."; LOG(ERROR) << "Trying again without it"; } Command second_stop_cvd = CreateStopCvdCommand(stopper_path, stop_cvd_envs, {}); LOG(ERROR) << "Running HOME=" << stop_cvd_envs.at("HOME") << " " << stopper_path; std::string stdout_str; std::string stderr_str; ret_code = RunWithManagedStdio(std::move(second_stop_cvd), nullptr, std::addressof(stdout_str), std::addressof(stderr_str)); } if (ret_code != 0) { std::stringstream error; error << "HOME=" << group_info.home_ << group_info.stop_cvd_path_ + " Failed."; return CF_ERR(error.str()); } LOG(ERROR) << "\"" << stopper_path << " successfully " << "\" stopped instances at HOME=" << group_info.home_; return {}; } Result RunCvdProcessManager::RunStopCvdAll( const bool cvd_server_children_only, const bool clear_instance_dirs) { for (const auto& group_info : cf_groups_) { if (cvd_server_children_only && !group_info.is_cvd_server_started_) { continue; } auto stop_cvd_result = RunStopCvd(group_info, clear_instance_dirs); if (!stop_cvd_result.ok()) { LOG(ERROR) << stop_cvd_result.error().Trace(); continue; } } return {}; } static bool IsStillRunCvd(const pid_t pid) { std::string pid_dir = ConcatToString("/proc/", pid); if (!FileExists(pid_dir)) { return false; } auto owner_result = OwnerUid(pid_dir); if (!owner_result.ok() || (getuid() != *owner_result)) { return false; } auto extract_proc_info_result = ExtractProcInfo(pid); if (!extract_proc_info_result.ok()) { return false; } return (cpp_basename(extract_proc_info_result->actual_exec_path_) == "run_cvd"); } Result RunCvdProcessManager::SendSignals( const bool cvd_server_children_only) { auto recollected_run_cvd_pids = CF_EXPECT(CollectPidsByExecName("run_cvd")); std::unordered_set failed_pids; for (const auto& group_info : cf_groups_) { if (cvd_server_children_only && !group_info.is_cvd_server_started_) { continue; } for (const auto& [_, instance] : group_info.instances_) { const auto& pids = instance.pids_; for (const auto pid : pids) { if (!Contains(recollected_run_cvd_pids, pid)) { // pid is alive but reassigned to non-run_cvd process continue; } if (!IsStillRunCvd(pid)) { // pid is now assigned to a different process continue; } auto ret_sigkill = kill(pid, SIGKILL); if (ret_sigkill == 0) { LOG(ERROR) << "SIGKILL was delivered to pid #" << pid; } else { LOG(ERROR) << "SIGKILL was not delivered to pid #" << pid; } if (!IsStillRunCvd(pid)) { continue; } LOG(ERROR) << "Will still send SIGHUP as run_cvd #" << pid << " has not been terminated by SIGKILL."; auto ret_sighup = kill(pid, SIGHUP); if (ret_sighup != 0) { LOG(ERROR) << "SIGHUP sent to process #" << pid << " but all failed."; } if (ret_sigkill != 0 && ret_sighup != 0) { failed_pids.insert(pid); } } } } std::stringstream error_msg_stream; error_msg_stream << "Some run_cvd processes were not killed: {"; for (const auto& pid : failed_pids) { error_msg_stream << pid << ","; } auto error_msg = error_msg_stream.str(); if (!failed_pids.empty()) { error_msg.pop_back(); } error_msg.append("}"); CF_EXPECT(failed_pids.empty(), error_msg); return {}; } void RunCvdProcessManager::DeleteLockFiles( const bool cvd_server_children_only) { for (const auto& group_info : cf_groups_) { if (cvd_server_children_only && !group_info.is_cvd_server_started_) { continue; } const auto& instances = group_info.instances_; std::string lock_file_prefix = "/tmp/acloud_cvd_temp/local-instance-"; for (const auto& [id, _] : instances) { std::stringstream lock_file_path_stream; lock_file_path_stream << lock_file_prefix << id << ".lock"; auto lock_file_path = lock_file_path_stream.str(); if (FileExists(lock_file_path) && !DirectoryExists(lock_file_path)) { if (RemoveFile(lock_file_path)) { LOG(ERROR) << "Reset the lock file: " << lock_file_path; } else { LOG(ERROR) << "Failed to reset lock file: " << lock_file_path; } } } } } Result KillAllCuttlefishInstances(const DeviceClearOptions& options) { RunCvdProcessManager manager = CF_EXPECT(RunCvdProcessManager::Get()); CF_EXPECT(manager.KillAllCuttlefishInstances(options.cvd_server_children_only, options.clear_instance_dirs)); return {}; } Result KillCvdServerProcess() { std::vector self_exe_pids = CF_EXPECT(CollectPidsByArgv0(kServerExecPath)); if (self_exe_pids.empty()) { LOG(ERROR) << "cvd server is not running."; return {}; } std::vector cvd_server_pids; /** * Finds processes whose executable path is kServerExecPath, and * that is owned by getuid(), and that has the "INTERNAL_server_fd" * in the arguments list. */ for (const auto pid : self_exe_pids) { auto proc_info_result = ExtractProcInfo(pid); if (!proc_info_result.ok()) { LOG(ERROR) << "Failed to extract process info for pid " << pid; continue; } auto owner_uid_result = OwnerUid(pid); if (!owner_uid_result.ok()) { LOG(ERROR) << "Failed to find the uid for pid " << pid; continue; } if (getuid() != *owner_uid_result) { continue; } for (const auto& arg : proc_info_result->args_) { if (Contains(arg, "INTERNAL_server_fd")) { cvd_server_pids.push_back(pid); break; } } } if (cvd_server_pids.empty()) { LOG(ERROR) << "Cvd server process is not found. Perhaps, it is not running."; return {}; } if (cvd_server_pids.size() > 1) { LOG(ERROR) << "There are " << cvd_server_pids.size() << " server processes " << "running while it should be up to 1."; } for (const auto pid : cvd_server_pids) { auto kill_ret = kill(pid, SIGKILL); if (kill_ret != 0) { LOG(ERROR) << "kill(" << pid << ", SIGKILL) failed."; } else { LOG(ERROR) << "Cvd server process #" << pid << " is killed."; } } return {}; } } // namespace cuttlefish