unplugged-system/device/google/cuttlefish/host/commands/cvd/reset_client_utils.cpp

416 lines
14 KiB
C++

/*
* 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 <signal.h>
#include <algorithm>
#include <cctype>
#include <iomanip> // std::setw
#include <iostream> // std::endl
#include <sstream>
#include <unordered_set>
#include <android-base/file.h>
#include <android-base/parseint.h>
#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<std::string> 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::RunCvdProcInfo>
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<unsigned>(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<std::string> 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> 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<std::vector<RunCvdProcessManager::GroupProcInfo>>
RunCvdProcessManager::CollectInfo() {
auto run_cvd_pids = CF_EXPECT(CollectPidsByExecName("run_cvd"));
std::vector<RunCvdProcInfo> 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<std::string, GroupProcInfo> 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<pid_t>{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<GroupProcInfo> output;
output.reserve(groups.size());
for (auto& [_, group] : groups) {
output.push_back(std::move(group));
}
return output;
}
Result<void> 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<void> 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<void> RunCvdProcessManager::SendSignals(
const bool cvd_server_children_only) {
auto recollected_run_cvd_pids = CF_EXPECT(CollectPidsByExecName("run_cvd"));
std::unordered_set<pid_t> 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<void> 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<void> KillCvdServerProcess() {
std::vector<pid_t> self_exe_pids =
CF_EXPECT(CollectPidsByArgv0(kServerExecPath));
if (self_exe_pids.empty()) {
LOG(ERROR) << "cvd server is not running.";
return {};
}
std::vector<pid_t> 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