/* * 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 #include #include #include #include #include #include #include #include #include #include "common/libs/utils/flag_parser.h" #include "common/libs/utils/subprocess.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; }; static Result 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; Flag y_flag = Flag() .Alias({FlagAliasMode::kFlagExact, "-y"}) .Alias({FlagAliasMode::kFlagExact, "--yes"}) .Setter([&is_confirmed_by_flag](const FlagMatch&) { is_confirmed_by_flag = true; return true; }); Flag help_flag = Flag() .Alias({FlagAliasMode::kFlagExact, "-h"}) .Alias({FlagAliasMode::kFlagExact, "--help"}) .Setter([&is_help](const FlagMatch&) { is_help = true; return true; }); std::vector flags{ GflagsCompatFlag("device-by-cvd-only", device_by_cvd_only), y_flag, GflagsCompatFlag("clean-runtime-dir", clean_runtime_dir), help_flag, UnexpectedArgumentGuard()}; CF_EXPECT(ParseFlags(flags, subcmd_args)); 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}; } 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 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 HandleReset(CvdClient& client, const cvd_common::Args& subcmd_args) { auto options = CF_EXPECT(ParseResetFlags(subcmd_args)); 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