1 /*
2 * Copyright (C) 2023 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "host/commands/cvd/handle_reset.h"
18
19 #include <errno.h>
20 #include <semaphore.h>
21 #include <sys/mman.h>
22 #include <sys/time.h>
23 #include <sys/wait.h>
24 #include <unistd.h>
25
26 #include <chrono>
27 #include <iostream>
28 #include <string>
29 #include <thread>
30
31 #include "common/libs/utils/flag_parser.h"
32 #include "common/libs/utils/subprocess.h"
33 #include "host/commands/cvd/reset_client_utils.h"
34
35 namespace cuttlefish {
36
37 struct ParsedFlags {
38 bool is_help;
39 bool clean_runtime_dir;
40 bool device_by_cvd_only;
41 bool is_confirmed_by_flag;
42 };
43
ParseResetFlags(cvd_common::Args subcmd_args)44 static Result<ParsedFlags> ParseResetFlags(cvd_common::Args subcmd_args) {
45 if (subcmd_args.size() > 2 && subcmd_args.at(2) == "help") {
46 // unfortunately, {FlagAliasMode::kFlagExact, "help"} is not allowed
47 subcmd_args[2] = "--help";
48 }
49
50 bool is_help = false;
51 bool clean_runtime_dir = true;
52 bool device_by_cvd_only = false;
53 bool is_confirmed_by_flag = false;
54 Flag y_flag = Flag()
55 .Alias({FlagAliasMode::kFlagExact, "-y"})
56 .Alias({FlagAliasMode::kFlagExact, "--yes"})
57 .Setter([&is_confirmed_by_flag](const FlagMatch&) {
58 is_confirmed_by_flag = true;
59 return true;
60 });
61 Flag help_flag = Flag()
62 .Alias({FlagAliasMode::kFlagExact, "-h"})
63 .Alias({FlagAliasMode::kFlagExact, "--help"})
64 .Setter([&is_help](const FlagMatch&) {
65 is_help = true;
66 return true;
67 });
68 std::vector<Flag> flags{
69 GflagsCompatFlag("device-by-cvd-only", device_by_cvd_only), y_flag,
70 GflagsCompatFlag("clean-runtime-dir", clean_runtime_dir), help_flag,
71 UnexpectedArgumentGuard()};
72 CF_EXPECT(ParseFlags(flags, subcmd_args));
73
74 return ParsedFlags{.is_help = is_help,
75 .clean_runtime_dir = clean_runtime_dir,
76 .device_by_cvd_only = device_by_cvd_only,
77 .is_confirmed_by_flag = is_confirmed_by_flag};
78 }
79
GetUserConfirm()80 static bool GetUserConfirm() {
81 std::cout << "Are you sure to reset all the devices, runtime files, "
82 << "and the cvd server if any [y/n]? ";
83 std::string user_confirm;
84 std::getline(std::cin, user_confirm);
85 std::transform(user_confirm.begin(), user_confirm.end(), user_confirm.begin(),
86 ::tolower);
87 return (user_confirm == "y" || user_confirm == "yes");
88 }
89
90 /*
91 * Try client.StopCvdServer(), and wait for a while.
92 *
93 * There should be two threads or processes. One is to call
94 * "StopCvdServer()," which could hang forever. The other is waiting
95 * for the thread/process, and should kill it after timeout.
96 *
97 * In that sense, a process is easy to kill in the middle (kill -9).
98 *
99 */
TimedKillCvdServer(CvdClient & client,const int timeout)100 static Result<void> TimedKillCvdServer(CvdClient& client, const int timeout) {
101 sem_t* binary_sem = (sem_t*)mmap(NULL, sizeof(sem_t), PROT_READ | PROT_WRITE,
102 MAP_ANONYMOUS | MAP_SHARED, 0, 0);
103 CF_EXPECT(binary_sem != nullptr,
104 "Failed to allocated shm for inter-process semaphore."
105 << "(errno: " << errno << ")");
106 CF_EXPECT_EQ(sem_init(binary_sem, 1, 0), 0,
107 "Failed to initialized inter-process semaphore"
108 << "(errno: " << errno << ")");
109 pid_t pid = fork();
110 CF_EXPECT(pid >= 0, "fork() failed in TimedKillCvdServer");
111 if (pid == 0) {
112 LOG(ERROR) << "Stopping the cvd server...";
113 constexpr bool clear_running_devices_first = true;
114 auto stop_server_result = client.StopCvdServer(clear_running_devices_first);
115 if (!stop_server_result.ok()) {
116 LOG(ERROR) << "cvd kill-server returned error"
117 << stop_server_result.error().Trace();
118 LOG(ERROR) << "However, cvd reset will continue cleaning up.";
119 }
120 sem_post(binary_sem);
121 // exit 0. This is a short-living worker process
122 exit(0);
123 }
124
125 Subprocess worker_process(pid);
126 struct timespec waiting_time;
127 if (clock_gettime(CLOCK_MONOTONIC, &waiting_time) == -1) {
128 // cannot set up an alarm clock. Not sure how long it should wait
129 // for the worker process. Thus, we wait for a certain amount of time,
130 // and send SIGKILL to the cvd server process and the worker process.
131 LOG(ERROR) << "Could not get the CLOCK_REALTIME.";
132 LOG(ERROR) << "Sleeping " << timeout << " seconds, and "
133 << "will send sigkill to the server.";
134 using namespace std::chrono_literals;
135 std::this_thread::sleep_for(operator""s((unsigned long long)timeout));
136 auto result_kill = KillCvdServerProcess();
137 worker_process.Stop();
138 // TODO(kwstephenkim): Compose error messages, and propagate
139 CF_EXPECT(result_kill.ok(), "KillCvdServerProcess() failed.");
140 return {};
141 }
142
143 // timed wait for the binary semaphore
144 waiting_time.tv_sec += timeout;
145 auto ret_code = sem_timedwait(binary_sem, &waiting_time);
146
147 // ret_code == 0 means sem_wait succeeded before timeout.
148 if (ret_code == 0) {
149 worker_process.Wait();
150 CF_EXPECT(KillCvdServerProcess());
151 return {};
152 }
153
154 // worker process is still running.
155 worker_process.Stop();
156 CF_EXPECT(KillCvdServerProcess());
157 return {};
158 }
159
HandleReset(CvdClient & client,const cvd_common::Args & subcmd_args)160 Result<void> HandleReset(CvdClient& client,
161 const cvd_common::Args& subcmd_args) {
162 auto options = CF_EXPECT(ParseResetFlags(subcmd_args));
163 if (options.is_help) {
164 std::cout << kHelpMessage << std::endl;
165 return {};
166 }
167
168 // cvd reset. Give one more opportunity
169 if (!options.is_confirmed_by_flag && !GetUserConfirm()) {
170 std::cout << "For more details: "
171 << " cvd reset --help" << std::endl;
172 return {};
173 }
174
175 auto result = TimedKillCvdServer(client, 50);
176 if (!result.ok()) {
177 LOG(ERROR) << result.error().Trace();
178 LOG(ERROR) << "Cvd reset continues cleaning up devices.";
179 }
180 // cvd reset handler placeholder. identical to cvd kill-server for now.
181 CF_EXPECT(KillAllCuttlefishInstances(
182 {.cvd_server_children_only = options.device_by_cvd_only,
183 .clear_instance_dirs = options.clean_runtime_dir}));
184 return {};
185 }
186
187 } // namespace cuttlefish
188