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