1 /*
2  * Copyright (C) 2018 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 <sys/types.h>
18 
19 #include <cinttypes>
20 #include <csignal>
21 #include <cstdio>
22 #include <cstdlib>
23 #include <future>
24 #include <iostream>
25 #include <string>
26 #include <thread>
27 #include <vector>
28 
29 #include <android-base/strings.h>
30 #include <android-base/logging.h>
31 
32 #include "common/libs/fs/shared_fd.h"
33 #include "common/libs/utils/files.h"
34 #include "common/libs/utils/flag_parser.h"
35 #include "common/libs/utils/result.h"
36 #include "host/libs/allocd/request.h"
37 #include "host/libs/allocd/utils.h"
38 #include "host/libs/command_util/runner/defs.h"
39 #include "host/libs/command_util/util.h"
40 #include "host/libs/config/cuttlefish_config.h"
41 #include "host/libs/metrics/metrics_receiver.h"
42 
43 namespace cuttlefish {
44 namespace {
45 
FallbackDirs()46 std::set<std::string> FallbackDirs() {
47   std::set<std::string> paths;
48   std::string parent_path = StringFromEnv("HOME", ".");
49   paths.insert(parent_path + "/cuttlefish_assembly");
50 
51   std::unique_ptr<DIR, int(*)(DIR*)> dir(opendir(parent_path.c_str()), closedir);
52   for (auto entity = readdir(dir.get()); entity != nullptr; entity = readdir(dir.get())) {
53     std::string subdir(entity->d_name);
54     if (!android::base::StartsWith(subdir, "cuttlefish_runtime.")) {
55       continue;
56     }
57     paths.insert(parent_path + "/" + subdir);
58   }
59   return paths;
60 }
61 
DirsForInstance(const CuttlefishConfig & config,const CuttlefishConfig::InstanceSpecific instance)62 std::set<std::string> DirsForInstance(
63     const CuttlefishConfig& config,
64     const CuttlefishConfig::InstanceSpecific instance) {
65   return {
66       config.assembly_dir(),
67       instance.instance_dir(),
68       instance.instance_uds_dir(),
69   };
70 }
71 
72 // Gets a set of the possible process groups of a previous launch
GetCandidateProcessGroups(const std::set<std::string> & dirs)73 std::set<pid_t> GetCandidateProcessGroups(const std::set<std::string>& dirs) {
74   std::stringstream cmd;
75   cmd << "lsof -t 2>/dev/null";
76   for (const auto& dir : dirs) {
77     cmd << " +D " << dir;
78   }
79   std::string cmd_str = cmd.str();
80   std::shared_ptr<FILE> cmd_out(popen(cmd_str.c_str(), "r"), pclose);
81   if (!cmd_out) {
82     LOG(ERROR) << "Unable to execute '" << cmd_str << "': " << strerror(errno);
83     return {};
84   }
85   int64_t pid;
86   std::set<pid_t> ret{};
87   while(fscanf(cmd_out.get(), "%" PRId64, &pid) != EOF) {
88     pid_t pgid = getpgid(static_cast<pid_t>(pid));
89     if (pgid < 0) {
90       LOG(ERROR) << "Unable to get process group of " << pid << ": "
91                  << strerror(errno);
92       continue;
93     }
94     ret.insert(pgid);
95   }
96   // The process group of stop_cvd should not be killed
97   ret.erase(getpgrp());
98   return ret;
99 }
100 
FallBackStop(const std::set<std::string> & dirs)101 int FallBackStop(const std::set<std::string>& dirs) {
102   auto exit_code = 1; // Having to fallback is an error
103 
104   auto process_groups = GetCandidateProcessGroups(dirs);
105   for (auto pgid: process_groups) {
106     LOG(INFO) << "Sending SIGKILL to process group " << pgid;
107     auto retval = killpg(pgid, SIGKILL);
108     if (retval < 0) {
109       LOG(ERROR) << "Failed to kill process group " << pgid << ": "
110                  << strerror(errno);
111       exit_code |= 4;
112     }
113   }
114 
115   return exit_code;
116 }
117 
CleanStopInstance(const CuttlefishConfig::InstanceSpecific & instance_config,const std::int32_t wait_for_launcher)118 Result<void> CleanStopInstance(
119     const CuttlefishConfig::InstanceSpecific& instance_config,
120     const std::int32_t wait_for_launcher) {
121   SharedFD monitor_socket = CF_EXPECT(
122       GetLauncherMonitorFromInstance(instance_config, wait_for_launcher));
123 
124   LOG(INFO) << "Requesting stop";
125   CF_EXPECT(RunLauncherAction(monitor_socket, LauncherAction::kStop,
126                               wait_for_launcher));
127 
128   LOG(INFO) << "Successfully stopped device " << instance_config.instance_name()
129             << ": " << instance_config.adb_ip_and_port();
130   return {};
131 }
132 
StopInstance(const CuttlefishConfig & config,const CuttlefishConfig::InstanceSpecific & instance,const std::int32_t wait_for_launcher)133 int StopInstance(const CuttlefishConfig& config,
134                  const CuttlefishConfig::InstanceSpecific& instance,
135                  const std::int32_t wait_for_launcher) {
136   auto result = CleanStopInstance(instance, wait_for_launcher);
137   if (!result.ok()) {
138     LOG(ERROR) << "Clean stop failed: " << result.error().FormatForEnv();
139     return FallBackStop(DirsForInstance(config, instance));
140   }
141 
142   return 0;
143 }
144 
145 /// Send a StopSession request to allocd
ReleaseAllocdResources(SharedFD allocd_sock,uint32_t session_id)146 void ReleaseAllocdResources(SharedFD allocd_sock, uint32_t session_id) {
147   if (!allocd_sock->IsOpen() || session_id == -1) {
148     return;
149   }
150   Json::Value config;
151   Json::Value request_list;
152   Json::Value req;
153   req["request_type"] =
154       ReqTyToStr(RequestType::StopSession);
155   req["session_id"] = session_id;
156   request_list.append(req);
157   config["config_request"]["request_list"] = request_list;
158   SendJsonMsg(allocd_sock, config);
159   auto resp_opt = RecvJsonMsg(allocd_sock);
160   if (!resp_opt.has_value()) {
161     LOG(ERROR) << "Bad response from allocd";
162     return;
163   }
164   auto resp = resp_opt.value();
165   LOG(INFO) << "Stop Session operation: " << resp["config_status"];
166 }
167 
168 struct FlagVaules {
169   std::int32_t wait_for_launcher;
170   bool clear_instance_dirs;
171   bool helpxml;
172 };
173 
GetFlagValues(int argc,char ** argv)174 FlagVaules GetFlagValues(int argc, char** argv) {
175   std::int32_t wait_for_launcher = 5;
176   bool clear_instance_dirs = false;
177   std::vector<Flag> flags;
178   flags.emplace_back(
179       GflagsCompatFlag("wait_for_launcher", wait_for_launcher)
180           .Help("How many seconds to wait for the launcher to respond to the "
181                 "status command. A value of zero means wait indefinitely"));
182   flags.emplace_back(
183       GflagsCompatFlag("clear_instance_dirs", clear_instance_dirs)
184           .Help("If provided, deletes the instance dir after attempting to "
185                 "stop each instance."));
186   flags.emplace_back(HelpFlag(flags));
187   bool helpxml = false;
188   flags.emplace_back(HelpXmlFlag(flags, std::cout, helpxml));
189   flags.emplace_back(UnexpectedArgumentGuard());
190   std::vector<std::string> args =
191       ArgsToVec(argc - 1, argv + 1);  // Skip argv[0]
192   auto parse_res = ConsumeFlags(flags, args);
193   CHECK(parse_res.ok() || helpxml) << "Could not process command line flags.";
194 
195   return {wait_for_launcher, clear_instance_dirs, helpxml};
196 }
197 
StopCvdMain(const std::int32_t wait_for_launcher,const bool clear_instance_dirs)198 int StopCvdMain(const std::int32_t wait_for_launcher,
199                 const bool clear_instance_dirs) {
200   auto config = CuttlefishConfig::Get();
201   if (!config) {
202     LOG(ERROR) << "Failed to obtain config object";
203     return FallBackStop(FallbackDirs());
204   }
205 
206   int exit_code = 0;
207   auto instances = config->Instances();
208   std::vector<std::future<int>> exit_state_futures;
209   exit_state_futures.reserve(instances.size());
210   for (const auto& instance : instances) {
211     std::future<int> exit_code_from_thread = std::async(
212         std::launch::async,
213         [&instance, &config, &wait_for_launcher,
214          &clear_instance_dirs]() -> int {
215           int exit_status = StopInstance(*config, instance, wait_for_launcher);
216           {
217             auto session_id = instance.session_id();
218             if (exit_status == 0 && instance.use_allocd()) {
219               // only release session resources if the instance was stopped
220               SharedFD allocd_sock = SharedFD::SocketLocalClient(
221                   kDefaultLocation, false, SOCK_STREAM);
222               if (!allocd_sock->IsOpen()) {
223                 LOG(ERROR) << "Unable to connect to allocd on "
224                            << kDefaultLocation << ": "
225                            << allocd_sock->StrError();
226               }
227               ReleaseAllocdResources(allocd_sock, session_id);
228             }
229           }
230 
231           if (clear_instance_dirs && DirectoryExists(instance.instance_dir())) {
232             LOG(INFO) << "Deleting instance dir " << instance.instance_dir();
233             if (!RecursivelyRemoveDirectory(instance.instance_dir()).ok()) {
234               LOG(ERROR) << "Unable to rmdir " << instance.instance_dir();
235             }
236           }
237           return exit_status;
238         });
239     exit_state_futures.push_back(std::move(exit_code_from_thread));
240   }
241   for (auto& exit_status : exit_state_futures) {
242     exit_code |= exit_status.get();
243   }
244   return exit_code;
245 }
246 
247 } // namespace
248 } // namespace cuttlefish
249 
main(int argc,char ** argv)250 int main(int argc, char** argv) {
251   ::android::base::InitLogging(argv, android::base::StderrLogger);
252 
253   const auto [wait_for_launcher, clear_instance_dirs, helpxml] =
254       cuttlefish::GetFlagValues(argc, argv);
255 
256   if (helpxml) {
257     /*
258      * b/269925398
259      *
260      * CHECK(false) should not be executed if --helpxml is given.
261      * The return code does not have to be the same. It is good if
262      * CHECK(false) and --helpxml return the same return code.
263      */
264     return 134;
265   }
266 
267   if (cuttlefish::CuttlefishConfig::Get()->enable_metrics() ==
268       cuttlefish::CuttlefishConfig::Answer::kYes) {
269     cuttlefish::MetricsReceiver::LogMetricsVMStop();
270   }
271 
272   return cuttlefish::StopCvdMain(wait_for_launcher, clear_instance_dirs);
273 }
274