• 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_command/generic.h"
18 
19 #include <sys/types.h>
20 
21 #include <functional>
22 #include <mutex>
23 #include <variant>
24 
25 #include <android-base/file.h>
26 
27 #include "common/libs/fs/shared_fd.h"
28 #include "common/libs/utils/contains.h"
29 #include "common/libs/utils/environment.h"
30 #include "common/libs/utils/files.h"
31 #include "common/libs/utils/result.h"
32 #include "common/libs/utils/scope_guard.h"
33 #include "common/libs/utils/subprocess.h"
34 #include "cvd_server.pb.h"
35 #include "host/commands/cvd/command_sequence.h"
36 #include "host/commands/cvd/common_utils.h"
37 #include "host/commands/cvd/instance_manager.h"
38 #include "host/commands/cvd/selector/selector_constants.h"
39 #include "host/commands/cvd/server_command/host_tool_target_manager.h"
40 #include "host/commands/cvd/server_command/server_handler.h"
41 #include "host/commands/cvd/server_command/subprocess_waiter.h"
42 #include "host/commands/cvd/server_command/utils.h"
43 #include "host/commands/cvd/types.h"
44 #include "host/libs/config/cuttlefish_config.h"
45 #include "host/libs/config/instance_nums.h"
46 
47 namespace cuttlefish {
48 
49 class CvdGenericCommandHandler : public CvdServerHandler {
50  public:
51   INJECT(CvdGenericCommandHandler(
52       InstanceManager& instance_manager, SubprocessWaiter& subprocess_waiter,
53       HostToolTargetManager& host_tool_target_manager));
54 
55   Result<bool> CanHandle(const RequestWithStdio& request) const;
56   Result<cvd::Response> Handle(const RequestWithStdio& request) override;
57   Result<void> Interrupt() override;
58   cvd_common::Args CmdList() const override;
59 
60  private:
61   struct CommandInvocationInfo {
62     std::string command;
63     std::string bin;
64     std::string bin_path;
65     std::string home;
66     std::string host_artifacts_path;
67     uid_t uid;
68     std::vector<std::string> args;
69     cvd_common::Envs envs;
70   };
71   struct ExtractedInfo {
72     CommandInvocationInfo invocation_info;
73     std::optional<selector::LocalInstanceGroup> group;
74   };
75   Result<ExtractedInfo> ExtractInfo(const RequestWithStdio& request) const;
76   Result<std::string> GetBin(const std::string& subcmd) const;
77   Result<std::string> GetBin(const std::string& subcmd,
78                              const std::string& host_artifacts_path) const;
IsStopCommand(const std::string & subcmd) const79   bool IsStopCommand(const std::string& subcmd) const {
80     return subcmd == "stop" || subcmd == "stop_cvd";
81   }
82   // whether the "bin" is cvd bins like stop_cvd or not (e.g. ln, ls, mkdir)
83   // The information to fire the command might be different. This information
84   // is about what the executable binary is and how to find it.
85   struct BinPathInfo {
86     std::string bin_;
87     std::string bin_path_;
88     std::string host_artifacts_path_;
89   };
90   Result<BinPathInfo> NonCvdBinPath(const std::string& subcmd,
91                                     const cvd_common::Envs& envs) const;
92   Result<BinPathInfo> CvdHelpBinPath(const std::string& subcmd,
93                                      const cvd_common::Envs& envs) const;
94   Result<BinPathInfo> CvdBinPath(const std::string& subcmd,
95                                  const cvd_common::Envs& envs,
96                                  const std::string& home,
97                                  const uid_t uid) const;
98 
99   InstanceManager& instance_manager_;
100   SubprocessWaiter& subprocess_waiter_;
101   HostToolTargetManager& host_tool_target_manager_;
102   std::mutex interruptible_;
103   bool interrupted_ = false;
104   using BinGeneratorType = std::function<Result<std::string>(
105       const std::string& host_artifacts_path)>;
106   using BinType = std::variant<std::string, BinGeneratorType>;
107   std::map<std::string, BinType> command_to_binary_map_;
108 
109   static constexpr char kHostBugreportBin[] = "cvd_internal_host_bugreport";
110   static constexpr char kLnBin[] = "ln";
111   static constexpr char kMkdirBin[] = "mkdir";
112   static constexpr char kClearBin[] =
113       "clear_placeholder";  // Unused, runs CvdClear()
114 };
115 
CvdGenericCommandHandler(InstanceManager & instance_manager,SubprocessWaiter & subprocess_waiter,HostToolTargetManager & host_tool_target_manager)116 CvdGenericCommandHandler::CvdGenericCommandHandler(
117     InstanceManager& instance_manager, SubprocessWaiter& subprocess_waiter,
118     HostToolTargetManager& host_tool_target_manager)
119     : instance_manager_(instance_manager),
120       subprocess_waiter_(subprocess_waiter),
121       host_tool_target_manager_(host_tool_target_manager),
122       command_to_binary_map_{
123           {"host_bugreport", kHostBugreportBin},
124           {"cvd_host_bugreport", kHostBugreportBin},
125           {"status",
126            [this](
127                const std::string& host_artifacts_path) -> Result<std::string> {
128              auto stat_bin = CF_EXPECT(host_tool_target_manager_.ExecBaseName({
129                  .artifacts_path = host_artifacts_path,
130                  .op = "status",
131              }));
132              return stat_bin;
133            }},
134           {"cvd_status",
135            [this](
136                const std::string& host_artifacts_path) -> Result<std::string> {
137              auto stat_bin = CF_EXPECT(host_tool_target_manager_.ExecBaseName({
138                  .artifacts_path = host_artifacts_path,
139                  .op = "status",
140              }));
141              return stat_bin;
142            }},
143           {"stop",
144            [this](
145                const std::string& host_artifacts_path) -> Result<std::string> {
146              auto stop_bin = CF_EXPECT(host_tool_target_manager_.ExecBaseName({
147                  .artifacts_path = host_artifacts_path,
148                  .op = "stop",
149              }));
150              return stop_bin;
151            }},
152           {"stop_cvd",
153            [this](
154                const std::string& host_artifacts_path) -> Result<std::string> {
155              auto stop_bin = CF_EXPECT(host_tool_target_manager_.ExecBaseName({
156                  .artifacts_path = host_artifacts_path,
157                  .op = "stop",
158              }));
159              return stop_bin;
160            }},
161           {"clear", kClearBin},
162           {"mkdir", kMkdirBin},
163           {"ln", kLnBin},
164       } {}
165 
CanHandle(const RequestWithStdio & request) const166 Result<bool> CvdGenericCommandHandler::CanHandle(
167     const RequestWithStdio& request) const {
168   auto invocation = ParseInvocation(request.Message());
169   return Contains(command_to_binary_map_, invocation.command);
170 }
171 
Interrupt()172 Result<void> CvdGenericCommandHandler::Interrupt() {
173   std::scoped_lock interrupt_lock(interruptible_);
174   interrupted_ = true;
175   CF_EXPECT(subprocess_waiter_.Interrupt());
176   return {};
177 }
178 
Handle(const RequestWithStdio & request)179 Result<cvd::Response> CvdGenericCommandHandler::Handle(
180     const RequestWithStdio& request) {
181   std::unique_lock interrupt_lock(interruptible_);
182   if (interrupted_) {
183     return CF_ERR("Interrupted");
184   }
185   CF_EXPECT(CanHandle(request));
186   CF_EXPECT(request.Credentials() != std::nullopt);
187   const uid_t uid = request.Credentials()->uid;
188 
189   cvd::Response response;
190   response.mutable_command_response();
191 
192   auto precondition_verified = VerifyPrecondition(request);
193   if (!precondition_verified.ok()) {
194     response.mutable_status()->set_code(cvd::Status::FAILED_PRECONDITION);
195     response.mutable_status()->set_message(
196         precondition_verified.error().Message());
197     return response;
198   }
199   auto [invocation_info, group_opt] = CF_EXPECT(ExtractInfo(request));
200   if (invocation_info.bin == kClearBin) {
201     *response.mutable_status() =
202         instance_manager_.CvdClear(request.Out(), request.Err());
203     return response;
204   }
205 
206   ConstructCommandParam construct_cmd_param{
207       .bin_path = invocation_info.bin_path,
208       .home = invocation_info.home,
209       .args = invocation_info.args,
210       .envs = invocation_info.envs,
211       .working_dir = request.Message().command_request().working_directory(),
212       .command_name = invocation_info.bin,
213       .in = request.In(),
214       .out = request.Out(),
215       .err = request.Err()};
216   Command command = CF_EXPECT(ConstructCommand(construct_cmd_param));
217 
218   SubprocessOptions options;
219   if (request.Message().command_request().wait_behavior() ==
220       cvd::WAIT_BEHAVIOR_START) {
221     options.ExitWithParent(false);
222   }
223   CF_EXPECT(subprocess_waiter_.Setup(command.Start(options)));
224 
225   bool is_stop = IsStopCommand(invocation_info.command);
226 
227   // captured structured bindings are a C++20 extension
228   // so we need [group_ptr] instead of [&group_opt]
229   auto* group_ptr = (group_opt ? std::addressof(*group_opt) : nullptr);
230   ScopeGuard exit_action([this, is_stop, group_ptr]() {
231     if (!is_stop) {
232       return;
233     }
234     if (!group_ptr) {
235       return;
236     }
237     for (const auto& instance : group_ptr->Instances()) {
238       auto lock = instance_manager_.TryAcquireLock(instance->InstanceId());
239       if (lock.ok() && (*lock)) {
240         (*lock)->Status(InUseState::kNotInUse);
241         continue;
242       }
243       LOG(ERROR) << "InstanceLockFileManager failed to acquire lock for #"
244                  << instance->InstanceId();
245     }
246   });
247 
248   if (request.Message().command_request().wait_behavior() ==
249       cvd::WAIT_BEHAVIOR_START) {
250     response.mutable_status()->set_code(cvd::Status::OK);
251     return response;
252   }
253 
254   interrupt_lock.unlock();
255 
256   auto infop = CF_EXPECT(subprocess_waiter_.Wait());
257 
258   if (infop.si_code == CLD_EXITED && IsStopCommand(invocation_info.command)) {
259     instance_manager_.RemoveInstanceGroup(uid, invocation_info.home);
260   }
261 
262   return ResponseFromSiginfo(infop);
263 }
264 
CmdList() const265 std::vector<std::string> CvdGenericCommandHandler::CmdList() const {
266   std::vector<std::string> subcmd_list;
267   subcmd_list.reserve(command_to_binary_map_.size());
268   for (const auto& [cmd, _] : command_to_binary_map_) {
269     subcmd_list.emplace_back(cmd);
270   }
271   return subcmd_list;
272 }
273 
274 Result<CvdGenericCommandHandler::BinPathInfo>
NonCvdBinPath(const std::string & subcmd,const cvd_common::Envs & envs) const275 CvdGenericCommandHandler::NonCvdBinPath(const std::string& subcmd,
276                                         const cvd_common::Envs& envs) const {
277   auto bin_path_base = CF_EXPECT(GetBin(subcmd));
278   // no need of executable directory. Will look up by PATH
279   // bin_path_base is like ln, mkdir, etc.
280   return BinPathInfo{.bin_ = bin_path_base,
281                      .bin_path_ = bin_path_base,
282                      .host_artifacts_path_ = envs.at(kAndroidHostOut)};
283 }
284 
285 Result<CvdGenericCommandHandler::BinPathInfo>
CvdHelpBinPath(const std::string & subcmd,const cvd_common::Envs & envs) const286 CvdGenericCommandHandler::CvdHelpBinPath(const std::string& subcmd,
287                                          const cvd_common::Envs& envs) const {
288   auto tool_dir_path = envs.at(kAndroidHostOut);
289   if (!DirectoryExists(tool_dir_path + "/bin")) {
290     tool_dir_path =
291         android::base::Dirname(android::base::GetExecutableDirectory());
292   }
293   auto bin_path_base = CF_EXPECT(GetBin(subcmd, tool_dir_path));
294   // no need of executable directory. Will look up by PATH
295   // bin_path_base is like ln, mkdir, etc.
296   return BinPathInfo{
297       .bin_ = bin_path_base,
298       .bin_path_ = tool_dir_path.append("/bin/").append(bin_path_base),
299       .host_artifacts_path_ = envs.at(kAndroidHostOut)};
300 }
301 
302 Result<CvdGenericCommandHandler::BinPathInfo>
CvdBinPath(const std::string & subcmd,const cvd_common::Envs & envs,const std::string & home,const uid_t uid) const303 CvdGenericCommandHandler::CvdBinPath(const std::string& subcmd,
304                                      const cvd_common::Envs& envs,
305                                      const std::string& home,
306                                      const uid_t uid) const {
307   std::string host_artifacts_path;
308   auto instance_group_result = instance_manager_.FindGroup(
309       uid, InstanceManager::Query{selector::kHomeField, home});
310 
311   // the dir that "bin/<this subcmd bin file>" belongs to
312   std::string tool_dir_path;
313   if (instance_group_result.ok()) {
314     host_artifacts_path = instance_group_result->HostArtifactsPath();
315     tool_dir_path = host_artifacts_path;
316   } else {
317     // if the group does not exist (e.g. cvd status --help)
318     // falls back here
319     host_artifacts_path = envs.at(kAndroidHostOut);
320     tool_dir_path = host_artifacts_path;
321     if (!DirectoryExists(tool_dir_path + "/bin")) {
322       tool_dir_path =
323           android::base::Dirname(android::base::GetExecutableDirectory());
324     }
325   }
326   const std::string bin = CF_EXPECT(GetBin(subcmd, tool_dir_path));
327   const std::string bin_path = tool_dir_path.append("/bin/").append(bin);
328   CF_EXPECT(FileExists(bin_path));
329   return BinPathInfo{.bin_ = bin,
330                      .bin_path_ = bin_path,
331                      .host_artifacts_path_ = host_artifacts_path};
332 }
333 
334 /*
335  * commands like ln, mkdir, clear
336  *  -> bin, bin, system_wide_home, N/A, cmd_args, envs
337  *
338  * help command
339  *  -> android_out/bin, bin, system_wide_home, android_out, cmd_args, envs
340  *
341  * non-help command
342  *  -> group->a/o/bin, bin, group->home, group->android_out, cmd_args, envs
343  *
344  */
345 Result<CvdGenericCommandHandler::ExtractedInfo>
ExtractInfo(const RequestWithStdio & request) const346 CvdGenericCommandHandler::ExtractInfo(const RequestWithStdio& request) const {
347   auto result_opt = request.Credentials();
348   CF_EXPECT(result_opt != std::nullopt);
349   const uid_t uid = result_opt->uid;
350 
351   auto [subcmd, cmd_args] = ParseInvocation(request.Message());
352   CF_EXPECT(Contains(command_to_binary_map_, subcmd));
353 
354   cvd_common::Envs envs =
355       cvd_common::ConvertToEnvs(request.Message().command_request().env());
356   const auto& selector_opts =
357       request.Message().command_request().selector_opts();
358   const auto selector_args = cvd_common::ConvertToArgs(selector_opts.args());
359   CF_EXPECT(Contains(envs, kAndroidHostOut) &&
360             DirectoryExists(envs.at(kAndroidHostOut)));
361 
362   std::unordered_set<std::string> non_cvd_op{"clear", "mkdir", "ln"};
363   if (Contains(non_cvd_op, subcmd) || IsHelpSubcmd(cmd_args)) {
364     const auto [bin, bin_path, host_artifacts_path] =
365         Contains(non_cvd_op, subcmd) ? CF_EXPECT(NonCvdBinPath(subcmd, envs))
366                                      : CF_EXPECT(CvdHelpBinPath(subcmd, envs));
367     return ExtractedInfo{
368         .invocation_info =
369             CommandInvocationInfo{
370                 .command = subcmd,
371                 .bin = bin,
372                 .bin_path = bin_path,
373                 .home = CF_EXPECT(SystemWideUserHome(uid)),
374                 .host_artifacts_path = envs.at(kAndroidHostOut),
375                 .uid = uid,
376                 .args = cmd_args,
377                 .envs = envs},
378         .group = std::nullopt};
379   }
380 
381   auto instance_group =
382       CF_EXPECT(instance_manager_.SelectGroup(selector_args, envs, uid));
383   auto android_host_out = instance_group.HostArtifactsPath();
384   auto home = instance_group.HomeDir();
385   auto bin = CF_EXPECT(GetBin(subcmd, android_host_out));
386   auto bin_path = ConcatToString(android_host_out, "/bin/", bin);
387   CommandInvocationInfo result = {.command = subcmd,
388                                   .bin = bin,
389                                   .bin_path = bin_path,
390                                   .home = home,
391                                   .host_artifacts_path = android_host_out,
392                                   .uid = uid,
393                                   .args = cmd_args,
394                                   .envs = envs};
395   result.envs["HOME"] = home;
396   return ExtractedInfo{.invocation_info = result, .group = instance_group};
397 }
398 
GetBin(const std::string & subcmd) const399 Result<std::string> CvdGenericCommandHandler::GetBin(
400     const std::string& subcmd) const {
401   const auto& bin_type_entry = command_to_binary_map_.at(subcmd);
402   const std::string* ptr_if_string =
403       std::get_if<std::string>(std::addressof(bin_type_entry));
404   CF_EXPECT(ptr_if_string != nullptr,
405             "To figure out bin for " << subcmd << ", we need ANDROID_HOST_OUT");
406   return *ptr_if_string;
407 }
408 
GetBin(const std::string & subcmd,const std::string & host_artifacts_path) const409 Result<std::string> CvdGenericCommandHandler::GetBin(
410     const std::string& subcmd, const std::string& host_artifacts_path) const {
411   auto bin_getter = Overload{
412       [](const std::string& str) -> Result<std::string> { return str; },
413       [&host_artifacts_path](
414           const BinGeneratorType& bin_generator) -> Result<std::string> {
415         const auto bin = CF_EXPECT(bin_generator(host_artifacts_path));
416         return bin;
417       },
418       [](auto) -> Result<std::string> {
419         return CF_ERR("Unsupported parameter type for GetBin()");
420       }};
421   auto bin =
422       CF_EXPECT(std::visit(bin_getter, command_to_binary_map_.at(subcmd)));
423   return bin;
424 }
425 
426 fruit::Component<
427     fruit::Required<InstanceManager, SubprocessWaiter, HostToolTargetManager>>
cvdGenericCommandComponent()428 cvdGenericCommandComponent() {
429   return fruit::createComponent()
430       .addMultibinding<CvdServerHandler, CvdGenericCommandHandler>();
431 }
432 
433 }  // namespace cuttlefish
434