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/start.h"
18
19 #include <sys/types.h>
20
21 #include <array>
22 #include <atomic>
23 #include <cstdint>
24 #include <cstdlib>
25 #include <fstream>
26 #include <iostream>
27 #include <map>
28 #include <mutex>
29 #include <regex>
30 #include <sstream>
31 #include <string>
32
33 #include <android-base/parseint.h>
34 #include <android-base/strings.h>
35
36 #include "common/libs/fs/shared_buf.h"
37 #include "common/libs/fs/shared_fd.h"
38 #include "common/libs/utils/contains.h"
39 #include "common/libs/utils/files.h"
40 #include "common/libs/utils/flag_parser.h"
41 #include "common/libs/utils/result.h"
42 #include "cvd_server.pb.h"
43 #include "host/commands/cvd/command_sequence.h"
44 #include "host/commands/cvd/common_utils.h"
45 #include "host/commands/cvd/selector/selector_constants.h"
46 #include "host/commands/cvd/server_command/generic.h"
47 #include "host/commands/cvd/server_command/server_handler.h"
48 #include "host/commands/cvd/server_command/start_impl.h"
49 #include "host/commands/cvd/server_command/subprocess_waiter.h"
50 #include "host/commands/cvd/server_command/utils.h"
51 #include "host/commands/cvd/types.h"
52 #include "host/libs/config/cuttlefish_config.h"
53
54 namespace cuttlefish {
55
56 class CvdStartCommandHandler : public CvdServerHandler {
57 public:
INJECT(CvdStartCommandHandler (InstanceManager & instance_manager,HostToolTargetManager & host_tool_target_manager))58 INJECT(
59 CvdStartCommandHandler(InstanceManager& instance_manager,
60 HostToolTargetManager& host_tool_target_manager))
61 : instance_manager_(instance_manager),
62 host_tool_target_manager_(host_tool_target_manager),
63 acloud_action_ended_(false) {}
64
65 Result<bool> CanHandle(const RequestWithStdio& request) const;
66 Result<cvd::Response> Handle(const RequestWithStdio& request) override;
67 Result<void> Interrupt() override;
68 std::vector<std::string> CmdList() const override;
69
70 private:
71 Result<void> UpdateInstanceDatabase(
72 const uid_t uid, const selector::GroupCreationInfo& group_creation_info);
73 Result<void> FireCommand(Command&& command, const bool wait);
74 bool HasHelpOpts(const cvd_common::Args& args) const;
75
76 Result<Command> ConstructCvdNonHelpCommand(
77 const std::string& bin_file,
78 const selector::GroupCreationInfo& group_info,
79 const RequestWithStdio& request);
80
81 // call this only if !is_help
82 Result<selector::GroupCreationInfo> GetGroupCreationInfo(
83 const std::string& start_bin, const std::string& subcmd,
84 const cvd_common::Args& subcmd_args, const cvd_common::Envs& envs,
85 const RequestWithStdio& request);
86
87 Result<cvd::Response> FillOutNewInstanceInfo(
88 cvd::Response&& response,
89 const selector::GroupCreationInfo& group_creation_info);
90
91 struct UpdatedArgsAndEnvs {
92 cvd_common::Args args;
93 cvd_common::Envs envs;
94 };
95 Result<UpdatedArgsAndEnvs> UpdateInstanceArgsAndEnvs(
96 cvd_common::Args&& args, cvd_common::Envs&& envs,
97 const std::vector<selector::PerInstanceInfo>& instances,
98 const std::string& artifacts_path, const std::string& start_bin);
99
100 static Result<std::vector<std::string>> UpdateWebrtcDeviceId(
101 std::vector<std::string>&& args, const std::string& group_name,
102 const std::vector<selector::PerInstanceInfo>& per_instance_info);
103
104 Result<selector::GroupCreationInfo> UpdateArgsAndEnvs(
105 selector::GroupCreationInfo&& old_group_info,
106 const std::string& start_bin);
107
108 Result<std::string> FindStartBin(const std::string& android_host_out);
109
110 Result<void> SetBuildId(const uid_t uid, const std::string& group_name,
111 const std::string& home);
112
113 static void MarkLockfiles(selector::GroupCreationInfo& group_info,
114 const InUseState state);
MarkLockfilesInUse(selector::GroupCreationInfo & group_info)115 static void MarkLockfilesInUse(selector::GroupCreationInfo& group_info) {
116 MarkLockfiles(group_info, InUseState::kInUse);
117 }
118
119 Result<void> HandleNoDaemonWorker(
120 const selector::GroupCreationInfo& group_creation_info,
121 std::atomic<bool>* interrupted, const uid_t uid);
122
123 Result<cvd::Response> HandleNoDaemon(
124 const std::optional<selector::GroupCreationInfo>& group_creation_info,
125 const uid_t uid);
126 Result<cvd::Response> HandleDaemon(
127 std::optional<selector::GroupCreationInfo>& group_creation_info,
128 const uid_t uid);
129 Result<void> AcloudCompatActions(
130 const selector::GroupCreationInfo& group_creation_info,
131 const RequestWithStdio& request);
132
133 InstanceManager& instance_manager_;
134 SubprocessWaiter subprocess_waiter_;
135 HostToolTargetManager& host_tool_target_manager_;
136 CommandSequenceExecutor command_executor_;
137 std::mutex interruptible_;
138 bool interrupted_ = false;
139 /*
140 * Used by Interrupt() not to call command_executor_.Interrupt()
141 *
142 * If true, it is guaranteed that the command_executor_ ended the execution.
143 * If false, it may or may not be after the command_executor_.Execute()
144 */
145 std::atomic<bool> acloud_action_ended_;
146 static const std::array<std::string, 2> supported_commands_;
147 };
148
GenericNestedHandlerComponent(InstanceManager * instance_manager,HostToolTargetManager * host_tool_target_manager,SubprocessWaiter * subprocess_waiter_for_nested_handler)149 fruit::Component<> GenericNestedHandlerComponent(
150 InstanceManager* instance_manager,
151 HostToolTargetManager* host_tool_target_manager,
152 SubprocessWaiter* subprocess_waiter_for_nested_handler) {
153 return fruit::createComponent()
154 .bindInstance(*instance_manager)
155 .bindInstance(*host_tool_target_manager)
156 .bindInstance(*subprocess_waiter_for_nested_handler)
157 .install(cvdGenericCommandComponent);
158 }
159
AcloudCompatActions(const selector::GroupCreationInfo & group_creation_info,const RequestWithStdio & request)160 Result<void> CvdStartCommandHandler::AcloudCompatActions(
161 const selector::GroupCreationInfo& group_creation_info,
162 const RequestWithStdio& request) {
163 std::unique_lock interrupt_lock(interruptible_);
164 CF_EXPECT(!interrupted_, "Interrupted");
165 // rm -fr "TempDir()/acloud_cvd_temp/local-instance-<i>"
166 std::string acloud_compat_home_prefix =
167 TempDir() + "/acloud_cvd_temp/local-instance-";
168 std::vector<std::string> acloud_compat_homes;
169 acloud_compat_homes.reserve(group_creation_info.instances.size());
170 for (const auto instance : group_creation_info.instances) {
171 acloud_compat_homes.push_back(
172 ConcatToString(acloud_compat_home_prefix, instance.instance_id_));
173 }
174 for (const auto acloud_compat_home : acloud_compat_homes) {
175 bool result_deleted = true;
176 std::stringstream acloud_compat_home_stream;
177 if (!FileExists(acloud_compat_home)) {
178 continue;
179 }
180 if (!Contains(group_creation_info.envs, kLaunchedByAcloud) ||
181 group_creation_info.envs.at(kLaunchedByAcloud) != "true") {
182 if (!DirectoryExists(acloud_compat_home, /*follow_symlinks=*/false)) {
183 // cvd created a symbolic link
184 result_deleted = RemoveFile(acloud_compat_home);
185 } else {
186 // acloud created a directory
187 // rm -fr isn't supporetd by TreeHugger, so if we fork-and-exec to
188 // literally run "rm -fr", the presubmit testing may fail if ever this
189 // code is tested in the future.
190 result_deleted = RecursivelyRemoveDirectory(acloud_compat_home);
191 }
192 }
193 if (!result_deleted) {
194 LOG(ERROR) << "Removing " << acloud_compat_home << " failed.";
195 continue;
196 }
197 }
198
199 // ln -f -s [target] [symlink]
200 // 1. mkdir -p home
201 // 2. ln -f -s android_host_out home/host_bins
202 // 3. for each i in ids,
203 // ln -f -s home /tmp/acloud_cvd_temp/local-instance-<i>
204 std::vector<MakeRequestForm> request_forms;
205 const cvd_common::Envs& common_envs = group_creation_info.envs;
206
207 const std::string& home_dir = group_creation_info.home;
208 const std::string client_pwd =
209 request.Message().command_request().working_directory();
210 request_forms.push_back(
211 {.working_dir = client_pwd,
212 .cmd_args = cvd_common::Args{"mkdir", "-p", home_dir},
213 .env = common_envs,
214 .selector_args = cvd_common::Args{}});
215 const std::string& android_host_out = group_creation_info.host_artifacts_path;
216 request_forms.push_back(
217 {.working_dir = client_pwd,
218 .cmd_args = cvd_common::Args{"ln", "-T", "-f", "-s", android_host_out,
219 home_dir + "/host_bins"},
220 .env = common_envs,
221 .selector_args = cvd_common::Args{}});
222 /* TODO(weihsu@): cvd acloud delete/list must handle multi-tenancy gracefully
223 *
224 * acloud delete just calls, for all instances in a group,
225 * /tmp/acloud_cvd_temp/local-instance-<i>/host_bins/stop_cvd
226 *
227 * That isn't necessary. Not desirable. Cvd acloud should read the instance
228 * manager's in-memory data structure, and call stop_cvd once for the entire
229 * group.
230 *
231 * Likewise, acloud list simply shows all instances in a flattened way. The
232 * user has no clue about an instance group. Cvd acloud should show the
233 * hierarchy.
234 *
235 * For now, we create the symbolic links so that it is compatible with acloud
236 * in Python.
237 */
238 for (const auto& acloud_compat_home : acloud_compat_homes) {
239 if (acloud_compat_home == home_dir) {
240 LOG(ERROR) << "The \"HOME\" directory is acloud workspace, which will "
241 << "be deleted by next cvd start or acloud command with the"
242 << " same directory being \"HOME\"";
243 continue;
244 }
245 request_forms.push_back({
246 .working_dir = client_pwd,
247 .cmd_args = cvd_common::Args{"ln", "-T", "-f", "-s", home_dir,
248 acloud_compat_home},
249 .env = common_envs,
250 .selector_args = cvd_common::Args{},
251 });
252 }
253 std::vector<cvd::Request> request_protos;
254 for (const auto& request_form : request_forms) {
255 request_protos.emplace_back(MakeRequest(request_form));
256 }
257 std::vector<RequestWithStdio> new_requests;
258 auto dev_null = SharedFD::Open("/dev/null", O_RDWR);
259 CF_EXPECT(dev_null->IsOpen(), dev_null->StrError());
260 std::vector<SharedFD> dev_null_fds = {dev_null, dev_null, dev_null};
261 for (auto& request_proto : request_protos) {
262 new_requests.emplace_back(request.Client(), request_proto, dev_null_fds,
263 request.Credentials());
264 }
265 SubprocessWaiter subprocess_waiter;
266 // injector only with the GenericCommandHandler for ln and mkdir
267 fruit::Injector<> injector(GenericNestedHandlerComponent,
268 std::addressof(this->instance_manager_),
269 std::addressof(this->host_tool_target_manager_),
270 std::addressof(subprocess_waiter));
271 CF_EXPECT(command_executor_.LateInject(injector),
272 "Creating local CommandSequenceExecutor in cvd start failed.");
273 interrupt_lock.unlock();
274 CF_EXPECT(command_executor_.Execute(new_requests, dev_null));
275 return {};
276 }
277
MarkLockfiles(selector::GroupCreationInfo & group_info,const InUseState state)278 void CvdStartCommandHandler::MarkLockfiles(
279 selector::GroupCreationInfo& group_info, const InUseState state) {
280 auto& instances = group_info.instances;
281 for (auto& instance : instances) {
282 if (!instance.instance_file_lock_) {
283 continue;
284 }
285 auto result = instance.instance_file_lock_->Status(state);
286 if (!result.ok()) {
287 LOG(ERROR) << result.error().Message();
288 }
289 }
290 }
291
CanHandle(const RequestWithStdio & request) const292 Result<bool> CvdStartCommandHandler::CanHandle(
293 const RequestWithStdio& request) const {
294 auto invocation = ParseInvocation(request.Message());
295 return Contains(supported_commands_, invocation.command);
296 }
297
298 Result<CvdStartCommandHandler::UpdatedArgsAndEnvs>
UpdateInstanceArgsAndEnvs(cvd_common::Args && args,cvd_common::Envs && envs,const std::vector<selector::PerInstanceInfo> & instances,const std::string & artifacts_path,const std::string & start_bin)299 CvdStartCommandHandler::UpdateInstanceArgsAndEnvs(
300 cvd_common::Args&& args, cvd_common::Envs&& envs,
301 const std::vector<selector::PerInstanceInfo>& instances,
302 const std::string& artifacts_path, const std::string& start_bin) {
303 std::vector<unsigned> ids;
304 ids.reserve(instances.size());
305 for (const auto& instance : instances) {
306 ids.emplace_back(instance.instance_id_);
307 }
308
309 cvd_common::Args new_args{std::move(args)};
310 std::string old_instance_nums;
311 std::string old_num_instances;
312 std::string old_base_instance_num;
313
314 std::vector<Flag> instance_id_flags{
315 GflagsCompatFlag("instance_nums", old_instance_nums),
316 GflagsCompatFlag("num_instances", old_num_instances),
317 GflagsCompatFlag("base_instance_num", old_base_instance_num)};
318 // discard old ones
319 ParseFlags(instance_id_flags, new_args);
320
321 auto check_flag = [artifacts_path, start_bin,
322 this](const std::string& flag_name) -> Result<void> {
323 CF_EXPECT(
324 host_tool_target_manager_.ReadFlag({.artifacts_path = artifacts_path,
325 .op = "start",
326 .flag_name = flag_name}));
327 return {};
328 };
329 auto max = *(std::max_element(ids.cbegin(), ids.cend()));
330 auto min = *(std::min_element(ids.cbegin(), ids.cend()));
331
332 const bool is_consecutive = ((max - min) == (ids.size() - 1));
333 const bool is_sorted = std::is_sorted(ids.begin(), ids.end());
334
335 if (!is_consecutive || !is_sorted) {
336 std::string flag_value = android::base::Join(ids, ",");
337 CF_EXPECT(check_flag("instance_nums"));
338 new_args.emplace_back("--instance_nums=" + flag_value);
339 return UpdatedArgsAndEnvs{.args = std::move(new_args),
340 .envs = std::move(envs)};
341 }
342
343 // sorted and consecutive, so let's use old flags
344 // like --num_instances and --base_instance_num
345 if (ids.size() > 1) {
346 CF_EXPECT(check_flag("num_instances"),
347 "--num_instances is not supported but multi-tenancy requested.");
348 new_args.emplace_back("--num_instances=" + std::to_string(ids.size()));
349 }
350 cvd_common::Envs new_envs{std::move(envs)};
351 if (check_flag("base_instance_num").ok()) {
352 new_args.emplace_back("--base_instance_num=" + std::to_string(min));
353 }
354 new_envs[kCuttlefishInstanceEnvVarName] = std::to_string(min);
355 return UpdatedArgsAndEnvs{.args = std::move(new_args),
356 .envs = std::move(new_envs)};
357 }
358
359 /*
360 * Adds --webrtc_device_id when necessary to cmd_args_
361 */
UpdateWebrtcDeviceId(std::vector<std::string> && args,const std::string & group_name,const std::vector<selector::PerInstanceInfo> & per_instance_info)362 Result<std::vector<std::string>> CvdStartCommandHandler::UpdateWebrtcDeviceId(
363 std::vector<std::string>&& args, const std::string& group_name,
364 const std::vector<selector::PerInstanceInfo>& per_instance_info) {
365 std::vector<std::string> new_args{std::move(args)};
366 // consume webrtc_device_id
367 // it was verified by start_selector_parser
368 std::string flag_value;
369 std::vector<Flag> webrtc_device_id_flag{
370 GflagsCompatFlag("webrtc_device_id", flag_value)};
371 CF_EXPECT(ParseFlags(webrtc_device_id_flag, new_args));
372
373 CF_EXPECT(!group_name.empty());
374 std::vector<std::string> device_name_list;
375 device_name_list.reserve(per_instance_info.size());
376 for (const auto& instance : per_instance_info) {
377 const auto& per_instance_name = instance.per_instance_name_;
378 std::string device_name{group_name};
379 device_name.append("-").append(per_instance_name);
380 device_name_list.emplace_back(device_name);
381 }
382 // take --webrtc_device_id flag away
383 new_args.emplace_back("--webrtc_device_id=" +
384 android::base::Join(device_name_list, ","));
385 return new_args;
386 }
387
ConstructCvdNonHelpCommand(const std::string & bin_file,const selector::GroupCreationInfo & group_info,const RequestWithStdio & request)388 Result<Command> CvdStartCommandHandler::ConstructCvdNonHelpCommand(
389 const std::string& bin_file, const selector::GroupCreationInfo& group_info,
390 const RequestWithStdio& request) {
391 auto bin_path = group_info.host_artifacts_path;
392 bin_path.append("/bin/").append(bin_file);
393 CF_EXPECT(!group_info.home.empty());
394 ConstructCommandParam construct_cmd_param{
395 .bin_path = bin_path,
396 .home = group_info.home,
397 .args = group_info.args,
398 .envs = group_info.envs,
399 .working_dir = request.Message().command_request().working_directory(),
400 .command_name = bin_file,
401 .in = request.In(),
402 .out = request.Out(),
403 .err = request.Err()};
404 Command non_help_command = CF_EXPECT(ConstructCommand(construct_cmd_param));
405 return non_help_command;
406 }
407
408 // call this only if !is_help
409 Result<selector::GroupCreationInfo>
GetGroupCreationInfo(const std::string & start_bin,const std::string & subcmd,const std::vector<std::string> & subcmd_args,const cvd_common::Envs & envs,const RequestWithStdio & request)410 CvdStartCommandHandler::GetGroupCreationInfo(
411 const std::string& start_bin, const std::string& subcmd,
412 const std::vector<std::string>& subcmd_args, const cvd_common::Envs& envs,
413 const RequestWithStdio& request) {
414 using CreationAnalyzerParam =
415 selector::CreationAnalyzer::CreationAnalyzerParam;
416 const auto& selector_opts =
417 request.Message().command_request().selector_opts();
418 const auto selector_args = cvd_common::ConvertToArgs(selector_opts.args());
419 CreationAnalyzerParam analyzer_param{
420 .cmd_args = subcmd_args, .envs = envs, .selector_args = selector_args};
421 auto cred = CF_EXPECT(request.Credentials());
422 auto group_creation_info =
423 CF_EXPECT(instance_manager_.Analyze(subcmd, analyzer_param, cred));
424 auto final_group_creation_info =
425 CF_EXPECT(UpdateArgsAndEnvs(std::move(group_creation_info), start_bin));
426 return final_group_creation_info;
427 }
428
UpdateArgsAndEnvs(selector::GroupCreationInfo && old_group_info,const std::string & start_bin)429 Result<selector::GroupCreationInfo> CvdStartCommandHandler::UpdateArgsAndEnvs(
430 selector::GroupCreationInfo&& old_group_info,
431 const std::string& start_bin) {
432 selector::GroupCreationInfo group_creation_info = std::move(old_group_info);
433 // update instance related-flags, envs
434 const auto& instances = group_creation_info.instances;
435 const auto& host_artifacts_path = group_creation_info.host_artifacts_path;
436 auto [new_args, new_envs] = CF_EXPECT(UpdateInstanceArgsAndEnvs(
437 std::move(group_creation_info.args), std::move(group_creation_info.envs),
438 instances, host_artifacts_path, start_bin));
439 group_creation_info.args = std::move(new_args);
440 group_creation_info.envs = std::move(new_envs);
441
442 auto webrtc_device_id_flag = host_tool_target_manager_.ReadFlag(
443 {.artifacts_path = group_creation_info.host_artifacts_path,
444 .op = "start",
445 .flag_name = "webrtc_device_id"});
446 if (webrtc_device_id_flag.ok()) {
447 group_creation_info.args = CF_EXPECT(UpdateWebrtcDeviceId(
448 std::move(group_creation_info.args), group_creation_info.group_name,
449 group_creation_info.instances));
450 }
451
452 group_creation_info.envs["HOME"] = group_creation_info.home;
453 group_creation_info.envs[kAndroidHostOut] =
454 group_creation_info.host_artifacts_path;
455 group_creation_info.envs[kAndroidProductOut] =
456 group_creation_info.product_out_path;
457 /* b/253644566
458 *
459 * Old branches used kAndroidSoongHostOut instead of kAndroidHostOut
460 */
461 group_creation_info.envs[kAndroidSoongHostOut] =
462 group_creation_info.host_artifacts_path;
463 group_creation_info.envs[kCvdMarkEnv] = "true";
464 return group_creation_info;
465 }
466
operator <<(std::ostream & out,const cvd_common::Args & v)467 static std::ostream& operator<<(std::ostream& out, const cvd_common::Args& v) {
468 if (v.empty()) {
469 return out;
470 }
471 for (int i = 0; i < v.size() - 1; i++) {
472 out << v.at(i) << " ";
473 }
474 out << v.back();
475 return out;
476 }
477
ShowLaunchCommand(const std::string & bin,const cvd_common::Args & args,const cvd_common::Envs & envs)478 static void ShowLaunchCommand(const std::string& bin,
479 const cvd_common::Args& args,
480 const cvd_common::Envs& envs) {
481 std::stringstream ss;
482 std::vector<std::string> interesting_env_names{"HOME",
483 kAndroidHostOut,
484 kAndroidSoongHostOut,
485 "ANDROID_PRODUCT_OUT",
486 kCuttlefishInstanceEnvVarName,
487 kCuttlefishConfigEnvVarName};
488 for (const auto& interesting_env_name : interesting_env_names) {
489 if (Contains(envs, interesting_env_name)) {
490 ss << interesting_env_name << "=\"" << envs.at(interesting_env_name)
491 << "\" ";
492 }
493 }
494 ss << " " << bin << " " << args;
495 LOG(ERROR) << "launcher command: " << ss.str();
496 }
497
ShowLaunchCommand(const std::string & bin,selector::GroupCreationInfo & group_info)498 static void ShowLaunchCommand(const std::string& bin,
499 selector::GroupCreationInfo& group_info) {
500 ShowLaunchCommand(bin, group_info.args, group_info.envs);
501 }
502
FindStartBin(const std::string & android_host_out)503 Result<std::string> CvdStartCommandHandler::FindStartBin(
504 const std::string& android_host_out) {
505 auto start_bin = CF_EXPECT(host_tool_target_manager_.ExecBaseName({
506 .artifacts_path = android_host_out,
507 .op = "start",
508 }));
509 return start_bin;
510 }
511
512 // std::string -> bool
513 enum class BoolValueType : std::uint8_t { kTrue = 0, kFalse, kUnknown };
IsDaemonModeFlag(const cvd_common::Args & args)514 static Result<bool> IsDaemonModeFlag(const cvd_common::Args& args) {
515 /*
516 * --daemon could be either bool or string flags.
517 */
518 bool is_daemon = false;
519 auto initial_size = args.size();
520 Flag daemon_bool = GflagsCompatFlag("daemon", is_daemon);
521 std::vector<Flag> as_bool_flags{daemon_bool};
522 cvd_common::Args copied_args{args};
523 if (ParseFlags(as_bool_flags, copied_args)) {
524 if (initial_size != copied_args.size()) {
525 return is_daemon;
526 }
527 }
528 std::string daemon_values;
529 Flag daemon_string = GflagsCompatFlag("daemon", daemon_values);
530 cvd_common::Args copied_args2{args};
531 std::vector<Flag> as_string_flags{daemon_string};
532 if (!ParseFlags(as_string_flags, copied_args2)) {
533 return false;
534 }
535 if (initial_size == copied_args2.size()) {
536 return false; // not consumed
537 }
538 // --daemon should have been handled above
539 CF_EXPECT(!daemon_values.empty());
540 std::unordered_set<std::string> true_strings = {"y", "yes", "true"};
541 std::unordered_set<std::string> false_strings = {"n", "no", "false"};
542 auto tokens = android::base::Tokenize(daemon_values, ",");
543 std::unordered_set<BoolValueType> value_set;
544 for (const auto& token : tokens) {
545 std::string daemon_value(token);
546 /*
547 * https://en.cppreference.com/w/cpp/string/byte/tolower
548 *
549 * char should be converted to unsigned char first.
550 */
551 std::transform(daemon_value.begin(), daemon_value.end(),
552 daemon_value.begin(),
553 [](unsigned char c) { return std::tolower(c); });
554 if (Contains(true_strings, daemon_value)) {
555 value_set.insert(BoolValueType::kTrue);
556 continue;
557 }
558 if (Contains(false_strings, daemon_value)) {
559 value_set.insert(BoolValueType::kFalse);
560 } else {
561 value_set.insert(BoolValueType::kUnknown);
562 }
563 }
564 CF_EXPECT_LE(value_set.size(), 1,
565 "Vectorized flags for --daemon is not supported by cvd");
566 const auto only_element = *(value_set.begin());
567 // We want to, basically, launch with daemon mode, and want to know
568 // when we must not do so
569 if (only_element == BoolValueType::kFalse) {
570 return false;
571 }
572 // if kUnknown, the launcher will fail. Which mode doesn't matter
573 // for the launcher. But it matters for cvd in how cvd handles the
574 // failure.
575 return true;
576 }
577
Handle(const RequestWithStdio & request)578 Result<cvd::Response> CvdStartCommandHandler::Handle(
579 const RequestWithStdio& request) {
580 std::unique_lock interrupt_lock(interruptible_);
581 if (interrupted_) {
582 return CF_ERR("Interrupted");
583 }
584 CF_EXPECT(CanHandle(request));
585
586 cvd::Response response;
587 response.mutable_command_response();
588
589 auto precondition_verified = VerifyPrecondition(request);
590 if (!precondition_verified.ok()) {
591 response.mutable_status()->set_code(cvd::Status::FAILED_PRECONDITION);
592 response.mutable_status()->set_message(
593 precondition_verified.error().Message());
594 return response;
595 }
596
597 const uid_t uid = request.Credentials()->uid;
598 cvd_common::Envs envs =
599 cvd_common::ConvertToEnvs(request.Message().command_request().env());
600 if (Contains(envs, "HOME")) {
601 if (envs.at("HOME").empty()) {
602 envs.erase("HOME");
603 } else {
604 // As the end-user may override HOME, this could be a relative path
605 // to client's pwd, or may include "~" which is the client's actual
606 // home directory.
607 auto client_pwd = request.Message().command_request().working_directory();
608 const auto given_home_dir = envs.at("HOME");
609 /*
610 * Imagine this scenario:
611 * client$ export HOME=/tmp/new/dir
612 * client$ HOME="~/subdir" cvd start
613 *
614 * The value of ~ isn't sent to the server. The server can't figure that
615 * out as it might be overridden before the cvd start command.
616 */
617 CF_EXPECT(!android::base::StartsWith(given_home_dir, "~") &&
618 !android::base::StartsWith(given_home_dir, "~/"),
619 "The HOME directory should not start with ~");
620 envs["HOME"] = CF_EXPECT(
621 EmulateAbsolutePath({.current_working_dir = client_pwd,
622 .home_dir = CF_EXPECT(SystemWideUserHome(uid)),
623 .path_to_convert = given_home_dir,
624 .follow_symlink = false}));
625 }
626 }
627 CF_EXPECT(Contains(envs, kAndroidHostOut));
628 const auto bin = CF_EXPECT(FindStartBin(envs.at(kAndroidHostOut)));
629
630 // update DB if not help
631 // collect group creation infos
632 auto [subcmd, subcmd_args] = ParseInvocation(request.Message());
633 CF_EXPECT(Contains(supported_commands_, subcmd),
634 "subcmd should be start but is " << subcmd);
635 const bool is_help = HasHelpOpts(subcmd_args);
636 const bool is_daemon = CF_EXPECT(IsDaemonModeFlag(subcmd_args));
637
638 std::optional<selector::GroupCreationInfo> group_creation_info;
639 if (!is_help) {
640 group_creation_info = CF_EXPECT(
641 GetGroupCreationInfo(bin, subcmd, subcmd_args, envs, request));
642 CF_EXPECT(UpdateInstanceDatabase(uid, *group_creation_info));
643 response = CF_EXPECT(
644 FillOutNewInstanceInfo(std::move(response), *group_creation_info));
645 }
646
647 Command command =
648 is_help
649 ? CF_EXPECT(ConstructCvdHelpCommand(bin, envs, subcmd_args, request))
650 : CF_EXPECT(
651 ConstructCvdNonHelpCommand(bin, *group_creation_info, request));
652
653 if (!is_help) {
654 CF_EXPECT(
655 group_creation_info != std::nullopt,
656 "group_creation_info should be nullopt only when --help is given.");
657 }
658
659 if (is_help) {
660 ShowLaunchCommand(command.Executable(), subcmd_args, envs);
661 } else {
662 ShowLaunchCommand(command.Executable(), *group_creation_info);
663 CF_EXPECT(request.Message().command_request().wait_behavior() !=
664 cvd::WAIT_BEHAVIOR_START);
665 }
666
667 FireCommand(std::move(command), /*should_wait*/ true);
668 interrupt_lock.unlock();
669
670 if (is_help) {
671 auto infop = CF_EXPECT(subprocess_waiter_.Wait());
672 return ResponseFromSiginfo(infop);
673 }
674
675 // make acquire interrupt_lock inside.
676 auto acloud_compat_action_result =
677 AcloudCompatActions(*group_creation_info, request);
678 acloud_action_ended_ = true;
679 if (!acloud_compat_action_result.ok()) {
680 LOG(ERROR) << acloud_compat_action_result.error().Trace();
681 LOG(ERROR) << "AcloudCompatActions() failed"
682 << " but continue as they are minor errors.";
683 }
684 return is_daemon ? HandleDaemon(group_creation_info, uid)
685 : HandleNoDaemon(group_creation_info, uid);
686 }
687
HandleNoDaemonWorker(const selector::GroupCreationInfo & group_creation_info,std::atomic<bool> * interrupted,const uid_t uid)688 Result<void> CvdStartCommandHandler::HandleNoDaemonWorker(
689 const selector::GroupCreationInfo& group_creation_info,
690 std::atomic<bool>* interrupted, const uid_t uid) {
691 const std::string home_dir = group_creation_info.home;
692 const std::string group_name = group_creation_info.group_name;
693 std::string kernel_log_path =
694 ConcatToString(home_dir, "/cuttlefish_runtime/kernel.log");
695 std::regex finger_pattern(
696 "\\[\\s*[0-9]*\\.[0-9]+\\]\\s*GUEST_BUILD_FINGERPRINT:");
697 std::regex boot_pattern("VIRTUAL_DEVICE_BOOT_COMPLETED");
698 std::streampos last_pos;
699 bool first_iteration = true;
700 while (*interrupted == false) {
701 if (!FileExists(kernel_log_path)) {
702 LOG(ERROR) << kernel_log_path << " does not yet exist, so wait for 5s";
703 using namespace std::chrono_literals;
704 std::this_thread::sleep_for(5s);
705 continue;
706 }
707 std::ifstream kernel_log_file(kernel_log_path);
708 CF_EXPECT(kernel_log_file.is_open(),
709 "The kernel log file exists but it cannot be open.");
710 if (!first_iteration) {
711 kernel_log_file.seekg(last_pos);
712 } else {
713 first_iteration = false;
714 last_pos = kernel_log_file.tellg();
715 }
716 for (std::string line; std::getline(kernel_log_file, line);) {
717 last_pos = kernel_log_file.tellg();
718 // if the line broke before a newline, this will end up reading the
719 // previous line one more time but only with '\n'. That's okay
720 last_pos -= line.size();
721 if (last_pos != std::ios_base::beg) {
722 last_pos -= std::string("\n").size();
723 }
724 std::smatch matched;
725 if (std::regex_search(line, matched, finger_pattern)) {
726 std::string build_id = matched.suffix().str();
727 CF_EXPECT(instance_manager_.SetBuildId(uid, group_name, build_id));
728 continue;
729 }
730 if (std::regex_search(line, matched, boot_pattern)) {
731 return {};
732 }
733 }
734 using namespace std::chrono_literals;
735 std::this_thread::sleep_for(2s);
736 }
737 return CF_ERR("Cvd start kernel monitor interrupted.");
738 }
739
HandleNoDaemon(const std::optional<selector::GroupCreationInfo> & group_creation_info,const uid_t uid)740 Result<cvd::Response> CvdStartCommandHandler::HandleNoDaemon(
741 const std::optional<selector::GroupCreationInfo>& group_creation_info,
742 const uid_t uid) {
743 std::atomic<bool> interrupted;
744 std::atomic<bool> worker_success;
745 interrupted = false;
746 worker_success = false;
747 const auto* group_info = std::addressof(*group_creation_info);
748 auto* interrupted_ptr = std::addressof(interrupted);
749 auto* worker_success_ptr = std::addressof(worker_success);
750 std::thread worker = std::thread(
751 [this, group_info, interrupted_ptr, worker_success_ptr, uid]() {
752 LOG(ERROR) << "worker thread started.";
753 auto result = HandleNoDaemonWorker(*group_info, interrupted_ptr, uid);
754 *worker_success_ptr = result.ok();
755 if (*worker_success_ptr == false) {
756 LOG(ERROR) << result.error().Trace();
757 }
758 });
759 auto infop = CF_EXPECT(subprocess_waiter_.Wait());
760 if (infop.si_code != CLD_EXITED || infop.si_status != EXIT_SUCCESS) {
761 // perhaps failed in launch
762 instance_manager_.RemoveInstanceGroup(uid, group_creation_info->home);
763 interrupted = true;
764 }
765 worker.join();
766 auto final_response = ResponseFromSiginfo(infop);
767 if (!final_response.has_status() ||
768 final_response.status().code() != cvd::Status::OK) {
769 return final_response;
770 }
771 // group_creation_info is nullopt only if is_help is false
772 return FillOutNewInstanceInfo(std::move(final_response),
773 *group_creation_info);
774 }
775
HandleDaemon(std::optional<selector::GroupCreationInfo> & group_creation_info,const uid_t uid)776 Result<cvd::Response> CvdStartCommandHandler::HandleDaemon(
777 std::optional<selector::GroupCreationInfo>& group_creation_info,
778 const uid_t uid) {
779 auto infop = CF_EXPECT(subprocess_waiter_.Wait());
780 if (infop.si_code != CLD_EXITED || infop.si_status != EXIT_SUCCESS) {
781 instance_manager_.RemoveInstanceGroup(uid, group_creation_info->home);
782 }
783
784 auto final_response = ResponseFromSiginfo(infop);
785 if (!final_response.has_status() ||
786 final_response.status().code() != cvd::Status::OK) {
787 return final_response;
788 }
789 MarkLockfilesInUse(*group_creation_info);
790
791 auto set_build_id_result = SetBuildId(uid, group_creation_info->group_name,
792 group_creation_info->home);
793 if (!set_build_id_result.ok()) {
794 LOG(ERROR) << "Failed to set a build Id for "
795 << group_creation_info->group_name << " but will continue.";
796 LOG(ERROR) << "The error message was : "
797 << set_build_id_result.error().Trace();
798 }
799
800 // group_creation_info is nullopt only if is_help is false
801 return FillOutNewInstanceInfo(std::move(final_response),
802 *group_creation_info);
803 }
804
SetBuildId(const uid_t uid,const std::string & group_name,const std::string & home)805 Result<void> CvdStartCommandHandler::SetBuildId(const uid_t uid,
806 const std::string& group_name,
807 const std::string& home) {
808 // build id can't be found before this point
809 const auto build_id = CF_EXPECT(cvd_start_impl::ExtractBuildId(home));
810 CF_EXPECT(instance_manager_.SetBuildId(uid, group_name, build_id));
811 return {};
812 }
813
Interrupt()814 Result<void> CvdStartCommandHandler::Interrupt() {
815 std::scoped_lock interrupt_lock(interruptible_);
816 interrupted_ = true;
817 if (!acloud_action_ended_) {
818 auto result = command_executor_.Interrupt();
819 if (!result.ok()) {
820 LOG(ERROR) << "Failed to interrupt CommandExecutor"
821 << result.error().Message();
822 }
823 }
824 CF_EXPECT(subprocess_waiter_.Interrupt());
825 return {};
826 }
827
FillOutNewInstanceInfo(cvd::Response && response,const selector::GroupCreationInfo & group_creation_info)828 Result<cvd::Response> CvdStartCommandHandler::FillOutNewInstanceInfo(
829 cvd::Response&& response,
830 const selector::GroupCreationInfo& group_creation_info) {
831 auto new_response = std::move(response);
832 auto& command_response = *(new_response.mutable_command_response());
833 auto& instance_group_info =
834 *(CF_EXPECT(command_response.mutable_instance_group_info()));
835 instance_group_info.set_group_name(group_creation_info.group_name);
836 instance_group_info.add_home_directories(group_creation_info.home);
837 for (const auto& per_instance_info : group_creation_info.instances) {
838 auto* new_entry = CF_EXPECT(instance_group_info.add_instances());
839 new_entry->set_name(per_instance_info.per_instance_name_);
840 new_entry->set_instance_id(per_instance_info.instance_id_);
841 }
842 return new_response;
843 }
844
UpdateInstanceDatabase(const uid_t uid,const selector::GroupCreationInfo & group_creation_info)845 Result<void> CvdStartCommandHandler::UpdateInstanceDatabase(
846 const uid_t uid, const selector::GroupCreationInfo& group_creation_info) {
847 CF_EXPECT(instance_manager_.SetInstanceGroup(uid, group_creation_info),
848 group_creation_info.home
849 << " is already taken so can't create new instance.");
850 return {};
851 }
852
FireCommand(Command && command,const bool wait)853 Result<void> CvdStartCommandHandler::FireCommand(Command&& command,
854 const bool wait) {
855 SubprocessOptions options;
856 if (!wait) {
857 options.ExitWithParent(false);
858 }
859 CF_EXPECT(subprocess_waiter_.Setup(command.Start(options)));
860 return {};
861 }
862
HasHelpOpts(const std::vector<std::string> & args) const863 bool CvdStartCommandHandler::HasHelpOpts(
864 const std::vector<std::string>& args) const {
865 return IsHelpSubcmd(args);
866 }
867
CmdList() const868 std::vector<std::string> CvdStartCommandHandler::CmdList() const {
869 std::vector<std::string> subcmd_list;
870 subcmd_list.reserve(supported_commands_.size());
871 for (const auto& cmd : supported_commands_) {
872 subcmd_list.emplace_back(cmd);
873 }
874 return subcmd_list;
875 }
876
877 const std::array<std::string, 2> CvdStartCommandHandler::supported_commands_{
878 "start", "launch_cvd"};
879
880 fruit::Component<fruit::Required<InstanceManager, HostToolTargetManager>>
CvdStartCommandComponent()881 CvdStartCommandComponent() {
882 return fruit::createComponent()
883 .addMultibinding<CvdServerHandler, CvdStartCommandHandler>();
884 }
885
886 } // namespace cuttlefish
887