• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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/server.h"
18 
19 #include <set>
20 #include <string>
21 #include <vector>
22 
23 #include <android-base/file.h>
24 #include <android-base/logging.h>
25 #include <fruit/fruit.h>
26 
27 #include "cvd_server.pb.h"
28 
29 #include "common/libs/fs/shared_buf.h"
30 #include "common/libs/fs/shared_fd.h"
31 #include "common/libs/utils/environment.h"
32 #include "common/libs/utils/files.h"
33 #include "common/libs/utils/flag_parser.h"
34 #include "common/libs/utils/result.h"
35 #include "common/libs/utils/subprocess.h"
36 #include "host/commands/cvd/instance_manager.h"
37 #include "host/libs/config/cuttlefish_config.h"
38 
39 namespace cuttlefish {
40 namespace {
41 
42 constexpr char kHostBugreportBin[] = "cvd_internal_host_bugreport";
43 constexpr char kStartBin[] = "cvd_internal_start";
44 constexpr char kFetchBin[] = "fetch_cvd";
45 constexpr char kMkdirBin[] = "/bin/mkdir";
46 
47 constexpr char kClearBin[] = "clear_placeholder";  // Unused, runs CvdClear()
48 constexpr char kFleetBin[] = "fleet_placeholder";  // Unused, runs CvdFleet()
49 constexpr char kHelpBin[] = "help_placeholder";  // Unused, prints kHelpMessage.
50 constexpr char kHelpMessage[] = R"(Cuttlefish Virtual Device (CVD) CLI.
51 
52 usage: cvd <command> <args>
53 
54 Commands:
55   help                Print this message.
56   help <command>      Print help for a command.
57   start               Start a device.
58   stop                Stop a running device.
59   clear               Stop all running devices and delete all instance and assembly directories.
60   fleet               View the current fleet status.
61   kill-server         Kill the cvd_server background process.
62   status              Check and print the state of a running instance.
63   host_bugreport      Capture a host bugreport, including configs, logs, and tombstones.
64 
65 Args:
66   <command args>      Each command has its own set of args. See cvd help <command>.
67   --clean             If provided, runs cvd kill-server before the requested command.
68 )";
69 
70 const std::map<std::string, std::string> CommandToBinaryMap = {
71     {"help", kHelpBin},
72     {"host_bugreport", kHostBugreportBin},
73     {"cvd_host_bugreport", kHostBugreportBin},
74     {"start", kStartBin},
75     {"launch_cvd", kStartBin},
76     {"status", kStatusBin},
77     {"cvd_status", kStatusBin},
78     {"stop", kStopBin},
79     {"stop_cvd", kStopBin},
80     {"clear", kClearBin},
81     {"fetch", kFetchBin},
82     {"fetch_cvd", kFetchBin},
83     {"mkdir", kMkdirBin},
84     {"fleet", kFleetBin}};
85 
86 }  // namespace
87 
CvdCommandHandler(InstanceManager & instance_manager)88 CvdCommandHandler::CvdCommandHandler(InstanceManager& instance_manager)
89     : instance_manager_(instance_manager) {}
90 
CanHandle(const RequestWithStdio & request) const91 Result<bool> CvdCommandHandler::CanHandle(
92     const RequestWithStdio& request) const {
93   auto invocation = ParseInvocation(request.Message());
94   return CommandToBinaryMap.find(invocation.command) !=
95          CommandToBinaryMap.end();
96 }
97 
Handle(const RequestWithStdio & request)98 Result<cvd::Response> CvdCommandHandler::Handle(
99     const RequestWithStdio& request) {
100   std::unique_lock interrupt_lock(interruptible_);
101   if (interrupted_) {
102     return CF_ERR("Interrupted");
103   }
104   CF_EXPECT(CanHandle(request));
105   cvd::Response response;
106   response.mutable_command_response();
107 
108   auto invocation = ParseInvocation(request.Message());
109 
110   auto subcommand_bin = CommandToBinaryMap.find(invocation.command);
111   CF_EXPECT(subcommand_bin != CommandToBinaryMap.end());
112   auto bin = subcommand_bin->second;
113 
114   // HOME is used to possibly set CuttlefishConfig path env variable later. This
115   // env variable is used by subcommands when locating the config.
116   auto request_home = request.Message().command_request().env().find("HOME");
117   std::string home =
118       request_home != request.Message().command_request().env().end()
119           ? request_home->second
120           : StringFromEnv("HOME", ".");
121 
122   // Create a copy of args before parsing, to be passed to subcommands.
123   auto args = invocation.arguments;
124   auto args_copy = invocation.arguments;
125 
126   auto host_artifacts_path =
127       request.Message().command_request().env().find("ANDROID_HOST_OUT");
128   if (host_artifacts_path == request.Message().command_request().env().end()) {
129     response.mutable_status()->set_code(cvd::Status::FAILED_PRECONDITION);
130     response.mutable_status()->set_message(
131         "Missing ANDROID_HOST_OUT in client environment.");
132     return response;
133   }
134 
135   if (bin == kHelpBin) {
136     // Handle `cvd help`
137     if (args.empty()) {
138       WriteAll(request.Out(), kHelpMessage);
139       response.mutable_status()->set_code(cvd::Status::OK);
140       return response;
141     }
142 
143     // Certain commands have no detailed help text.
144     std::set<std::string> builtins = {"help", "clear", "kill-server"};
145     auto it = CommandToBinaryMap.find(args[0]);
146     if (it == CommandToBinaryMap.end() ||
147         builtins.find(args[0]) != builtins.end()) {
148       WriteAll(request.Out(), kHelpMessage);
149       response.mutable_status()->set_code(cvd::Status::OK);
150       return response;
151     }
152 
153     // Handle `cvd help <subcommand>` by calling the subcommand with --help.
154     bin = it->second;
155     args_copy.push_back("--help");
156   } else if (bin == kClearBin) {
157     *response.mutable_status() =
158         instance_manager_.CvdClear(request.Out(), request.Err());
159     return response;
160   } else if (bin == kFleetBin) {
161     auto env_config = request.Message().command_request().env().find(
162         kCuttlefishConfigEnvVarName);
163     std::string config_path;
164     if (env_config != request.Message().command_request().env().end()) {
165       config_path = env_config->second;
166     }
167     *response.mutable_status() =
168         instance_manager_.CvdFleet(request.Out(), config_path);
169     return response;
170   } else if (bin == kStartBin) {
171     auto first_instance = 1;
172     auto instance_env =
173         request.Message().command_request().env().find("CUTTLEFISH_INSTANCE");
174     if (instance_env != request.Message().command_request().env().end()) {
175       first_instance = std::stoi(instance_env->second);
176     }
177     auto ins_flag = GflagsCompatFlag("base_instance_num", first_instance);
178     auto num_instances = 1;
179     auto num_instances_flag = GflagsCompatFlag("num_instances", num_instances);
180     CF_EXPECT(ParseFlags({ins_flag, num_instances_flag}, args));
181 
182     // Track this assembly_dir in the fleet.
183     InstanceManager::InstanceGroupInfo info;
184     info.host_binaries_dir = host_artifacts_path->second + "/bin/";
185     for (int i = first_instance; i < first_instance + num_instances; i++) {
186       info.instances.insert(i);
187     }
188     instance_manager_.SetInstanceGroup(home, info);
189   }
190 
191   Command command("(replaced)");
192   if (bin == kFetchBin) {
193     command.SetExecutable(HostBinaryPath("fetch_cvd"));
194   } else if (bin == kMkdirBin) {
195     command.SetExecutable(kMkdirBin);
196   } else {
197     auto assembly_info = CF_EXPECT(instance_manager_.GetInstanceGroup(home));
198     command.SetExecutable(assembly_info.host_binaries_dir + bin);
199   }
200   for (const std::string& arg : args_copy) {
201     command.AddParameter(arg);
202   }
203 
204   // Set CuttlefishConfig path based on assembly dir,
205   // used by subcommands when locating the CuttlefishConfig.
206   if (request.Message().command_request().env().count(
207           kCuttlefishConfigEnvVarName) == 0) {
208     auto config_path = GetCuttlefishConfigPath(home);
209     if (config_path) {
210       command.AddEnvironmentVariable(kCuttlefishConfigEnvVarName, *config_path);
211     }
212   }
213   for (auto& it : request.Message().command_request().env()) {
214     command.UnsetFromEnvironment(it.first);
215     command.AddEnvironmentVariable(it.first, it.second);
216   }
217 
218   // Redirect stdin, stdout, stderr back to the cvd client
219   command.RedirectStdIO(Subprocess::StdIOChannel::kStdIn, request.In());
220   command.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, request.Out());
221   command.RedirectStdIO(Subprocess::StdIOChannel::kStdErr, request.Err());
222   SubprocessOptions options;
223 
224   if (request.Message().command_request().wait_behavior() ==
225       cvd::WAIT_BEHAVIOR_START) {
226     options.ExitWithParent(false);
227   }
228 
229   const auto& working_dir =
230       request.Message().command_request().working_directory();
231   if (!working_dir.empty()) {
232     auto fd = SharedFD::Open(working_dir, O_RDONLY | O_PATH | O_DIRECTORY);
233     CF_EXPECT(fd->IsOpen(),
234               "Couldn't open \"" << working_dir << "\": " << fd->StrError());
235     command.SetWorkingDirectory(fd);
236   }
237 
238   subprocess_ = command.Start(options);
239 
240   if (request.Message().command_request().wait_behavior() ==
241       cvd::WAIT_BEHAVIOR_START) {
242     response.mutable_status()->set_code(cvd::Status::OK);
243     return response;
244   }
245   interrupt_lock.unlock();
246 
247   siginfo_t infop{};
248 
249   // This blocks until the process exits, but doesn't reap it.
250   auto result = subprocess_->Wait(&infop, WEXITED | WNOWAIT);
251   CF_EXPECT(result != -1, "Lost track of subprocess pid");
252   interrupt_lock.lock();
253   // Perform a reaping wait on the process (which should already have exited).
254   result = subprocess_->Wait(&infop, WEXITED);
255   CF_EXPECT(result != -1, "Lost track of subprocess pid");
256   // The double wait avoids a race around the kernel reusing pids. Waiting
257   // with WNOWAIT won't cause the child process to be reaped, so the kernel
258   // won't reuse the pid until the Wait call below, and any kill signals won't
259   // reach unexpected processes.
260 
261   subprocess_ = {};
262 
263   if (infop.si_code == CLD_EXITED && bin == kStopBin) {
264     instance_manager_.RemoveInstanceGroup(home);
265   }
266 
267   if (infop.si_code == CLD_EXITED && infop.si_status == 0) {
268     response.mutable_status()->set_code(cvd::Status::OK);
269     return response;
270   }
271 
272   response.mutable_status()->set_code(cvd::Status::INTERNAL);
273   if (infop.si_code == CLD_EXITED) {
274     response.mutable_status()->set_message("Exited with code " +
275                                            std::to_string(infop.si_status));
276   } else if (infop.si_code == CLD_KILLED) {
277     response.mutable_status()->set_message("Exited with signal " +
278                                            std::to_string(infop.si_status));
279   } else {
280     response.mutable_status()->set_message("Quit with code " +
281                                            std::to_string(infop.si_status));
282   }
283   return response;
284 }
285 
Interrupt()286 Result<void> CvdCommandHandler::Interrupt() {
287   std::scoped_lock interrupt_lock(interruptible_);
288   if (subprocess_) {
289     auto stop_result = subprocess_->Stop();
290     switch (stop_result) {
291       case StopperResult::kStopFailure:
292         return CF_ERR("Failed to stop subprocess");
293       case StopperResult::kStopCrash:
294         return CF_ERR("Stopper caused process to crash");
295       case StopperResult::kStopSuccess:
296         return {};
297       default:
298         return CF_ERR("Unknown stop result: " << (uint64_t)stop_result);
299     }
300   }
301   return {};
302 }
303 
ParseInvocation(const cvd::Request & request)304 CommandInvocation ParseInvocation(const cvd::Request& request) {
305   CommandInvocation invocation;
306   if (request.contents_case() != cvd::Request::ContentsCase::kCommandRequest) {
307     return invocation;
308   }
309   if (request.command_request().args_size() == 0) {
310     return invocation;
311   }
312   for (const std::string& arg : request.command_request().args()) {
313     invocation.arguments.push_back(arg);
314   }
315   invocation.arguments[0] = cpp_basename(invocation.arguments[0]);
316   if (invocation.arguments[0] == "cvd") {
317     if (invocation.arguments.size() == 1) {
318       // Show help if user invokes `cvd` alone.
319       invocation.command = "help";
320       invocation.arguments = {};
321     } else {  // More arguments
322       invocation.command = invocation.arguments[1];
323       invocation.arguments.erase(invocation.arguments.begin());
324       invocation.arguments.erase(invocation.arguments.begin());
325     }
326   } else {
327     invocation.command = invocation.arguments[0];
328     invocation.arguments.erase(invocation.arguments.begin());
329   }
330   return invocation;
331 }
332 
cvdCommandComponent()333 fruit::Component<fruit::Required<InstanceManager>> cvdCommandComponent() {
334   return fruit::createComponent()
335       .addMultibinding<CvdServerHandler, CvdCommandHandler>();
336 }
337 
338 }  // namespace cuttlefish
339