• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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