blob: 73b653b4f3501ac92164ad49593a10ca29e4bca3 [file] [log] [blame]
/*
* 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/handle_reset.h"
#include <errno.h>
#include <semaphore.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <unistd.h>
#include <chrono>
#include <iostream>
#include <optional>
#include <string>
#include <thread>
#include <android-base/logging.h>
#include "common/libs/utils/flag_parser.h"
#include "common/libs/utils/subprocess.h"
#include "host/commands/cvd/common_utils.h"
#include "host/commands/cvd/reset_client_utils.h"
namespace cuttlefish {
struct ParsedFlags {
bool is_help;
bool clean_runtime_dir;
bool device_by_cvd_only;
bool is_confirmed_by_flag;
std::optional<android::base::LogSeverity> log_level;
};
static Result<ParsedFlags> ParseResetFlags(cvd_common::Args subcmd_args) {
if (subcmd_args.size() > 2 && subcmd_args.at(2) == "help") {
// unfortunately, {FlagAliasMode::kFlagExact, "help"} is not allowed
subcmd_args[2] = "--help";
}
bool is_help = false;
bool clean_runtime_dir = true;
bool device_by_cvd_only = false;
bool is_confirmed_by_flag = false;
std::string verbosity_flag_value;
Flag y_flag =
Flag()
.Alias({FlagAliasMode::kFlagExact, "-y"})
.Alias({FlagAliasMode::kFlagExact, "--yes"})
.Setter([&is_confirmed_by_flag](const FlagMatch&) -> Result<void> {
is_confirmed_by_flag = true;
return {};
});
Flag help_flag = Flag()
.Alias({FlagAliasMode::kFlagExact, "-h"})
.Alias({FlagAliasMode::kFlagExact, "--help"})
.Setter([&is_help](const FlagMatch&) -> Result<void> {
is_help = true;
return {};
});
std::vector<Flag> flags{
GflagsCompatFlag("device-by-cvd-only", device_by_cvd_only),
y_flag,
GflagsCompatFlag("clean-runtime-dir", clean_runtime_dir),
help_flag,
GflagsCompatFlag("verbosity", verbosity_flag_value),
UnexpectedArgumentGuard()};
CF_EXPECT(ParseFlags(flags, subcmd_args));
std::optional<android::base::LogSeverity> verbosity;
if (!verbosity_flag_value.empty()) {
verbosity = CF_EXPECT(EncodeVerbosity(verbosity_flag_value),
"invalid verbosity level");
}
return ParsedFlags{.is_help = is_help,
.clean_runtime_dir = clean_runtime_dir,
.device_by_cvd_only = device_by_cvd_only,
.is_confirmed_by_flag = is_confirmed_by_flag,
.log_level = verbosity};
}
static bool GetUserConfirm() {
std::cout << "Are you sure to reset all the devices, runtime files, "
<< "and the cvd server if any [y/n]? ";
std::string user_confirm;
std::getline(std::cin, user_confirm);
std::transform(user_confirm.begin(), user_confirm.end(), user_confirm.begin(),
::tolower);
return (user_confirm == "y" || user_confirm == "yes");
}
/*
* Try client.StopCvdServer(), and wait for a while.
*
* There should be two threads or processes. One is to call
* "StopCvdServer()," which could hang forever. The other is waiting
* for the thread/process, and should kill it after timeout.
*
* In that sense, a process is easy to kill in the middle (kill -9).
*
*/
static Result<void> TimedKillCvdServer(CvdClient& client, const int timeout) {
sem_t* binary_sem = (sem_t*)mmap(NULL, sizeof(sem_t), PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_SHARED, 0, 0);
CF_EXPECT(binary_sem != nullptr,
"Failed to allocated shm for inter-process semaphore."
<< "(errno: " << errno << ")");
CF_EXPECT_EQ(sem_init(binary_sem, 1, 0), 0,
"Failed to initialized inter-process semaphore"
<< "(errno: " << errno << ")");
pid_t pid = fork();
CF_EXPECT(pid >= 0, "fork() failed in TimedKillCvdServer");
if (pid == 0) {
LOG(ERROR) << "Stopping the cvd server...";
constexpr bool clear_running_devices_first = true;
auto stop_server_result = client.StopCvdServer(clear_running_devices_first);
if (!stop_server_result.ok()) {
LOG(ERROR) << "cvd kill-server returned error"
<< stop_server_result.error().Trace();
LOG(ERROR) << "However, cvd reset will continue cleaning up.";
}
sem_post(binary_sem);
// exit 0. This is a short-living worker process
exit(0);
}
Subprocess worker_process(pid);
struct timespec waiting_time;
if (clock_gettime(CLOCK_MONOTONIC, &waiting_time) == -1) {
// cannot set up an alarm clock. Not sure how long it should wait
// for the worker process. Thus, we wait for a certain amount of time,
// and send SIGKILL to the cvd server process and the worker process.
LOG(ERROR) << "Could not get the CLOCK_REALTIME.";
LOG(ERROR) << "Sleeping " << timeout << " seconds, and "
<< "will send sigkill to the server.";
using namespace std::chrono_literals;
std::this_thread::sleep_for(operator""s((unsigned long long)timeout));
auto result_kill = KillCvdServerProcess();
worker_process.Stop();
// TODO(kwstephenkim): Compose error messages, and propagate
CF_EXPECT(result_kill.ok(), "KillCvdServerProcess() failed.");
return {};
}
// timed wait for the binary semaphore
waiting_time.tv_sec += timeout;
auto ret_code = sem_timedwait(binary_sem, &waiting_time);
// ret_code == 0 means sem_wait succeeded before timeout.
if (ret_code == 0) {
worker_process.Wait();
CF_EXPECT(KillCvdServerProcess());
return {};
}
// worker process is still running.
worker_process.Stop();
CF_EXPECT(KillCvdServerProcess());
return {};
}
Result<void> HandleReset(CvdClient& client,
const cvd_common::Args& subcmd_args) {
auto options = CF_EXPECT(ParseResetFlags(subcmd_args));
if (options.log_level) {
SetMinimumVerbosity(options.log_level.value());
}
if (options.is_help) {
std::cout << kHelpMessage << std::endl;
return {};
}
// cvd reset. Give one more opportunity
if (!options.is_confirmed_by_flag && !GetUserConfirm()) {
std::cout << "For more details: "
<< " cvd reset --help" << std::endl;
return {};
}
auto result = TimedKillCvdServer(client, 50);
if (!result.ok()) {
LOG(ERROR) << result.error().Trace();
LOG(ERROR) << "Cvd reset continues cleaning up devices.";
}
// cvd reset handler placeholder. identical to cvd kill-server for now.
CF_EXPECT(KillAllCuttlefishInstances(
{.cvd_server_children_only = options.device_by_cvd_only,
.clear_instance_dirs = options.clean_runtime_dir}));
return {};
}
} // namespace cuttlefish