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/instance_manager.h"
18
19 #include <signal.h>
20
21 #include <map>
22 #include <mutex>
23 #include <sstream>
24
25 #include <android-base/file.h>
26 #include <fruit/fruit.h>
27
28 #include "common/libs/fs/shared_buf.h"
29 #include "common/libs/fs/shared_fd.h"
30 #include "common/libs/utils/contains.h"
31 #include "common/libs/utils/files.h"
32 #include "common/libs/utils/flag_parser.h"
33 #include "common/libs/utils/result.h"
34 #include "common/libs/utils/subprocess.h"
35 #include "cvd_server.pb.h"
36 #include "host/commands/cvd/common_utils.h"
37 #include "host/commands/cvd/selector/instance_database_utils.h"
38 #include "host/commands/cvd/selector/selector_constants.h"
39 #include "host/commands/cvd/server_constants.h"
40 #include "host/libs/config/cuttlefish_config.h"
41 #include "host/libs/config/known_paths.h"
42
43 namespace cuttlefish {
44 namespace {
45
46 // Returns true only if command terminated normally, and returns 0
RunCommand(Command && command)47 Result<void> RunCommand(Command&& command) {
48 auto subprocess = std::move(command.Start());
49 siginfo_t infop{};
50 // This blocks until the process exits, but doesn't reap it.
51 auto result = subprocess.Wait(&infop, WEXITED);
52 CF_EXPECT(result != -1, "Lost track of subprocess pid");
53 CF_EXPECT(infop.si_code == CLD_EXITED && infop.si_status == 0);
54 return {};
55 }
56
57 } // namespace
58
GetCuttlefishConfigPath(const std::string & home)59 Result<std::string> InstanceManager::GetCuttlefishConfigPath(
60 const std::string& home) {
61 return selector::GetCuttlefishConfigPath(home);
62 }
63
InstanceManager(InstanceLockFileManager & lock_manager,HostToolTargetManager & host_tool_target_manager)64 InstanceManager::InstanceManager(
65 InstanceLockFileManager& lock_manager,
66 HostToolTargetManager& host_tool_target_manager)
67 : lock_manager_(lock_manager),
68 host_tool_target_manager_(host_tool_target_manager) {}
69
GetInstanceDB(const uid_t uid)70 selector::InstanceDatabase& InstanceManager::GetInstanceDB(const uid_t uid) {
71 if (!Contains(instance_dbs_, uid)) {
72 instance_dbs_.try_emplace(uid);
73 }
74 return instance_dbs_[uid];
75 }
76
Serialize(const uid_t uid)77 Result<Json::Value> InstanceManager::Serialize(const uid_t uid) {
78 std::lock_guard lock(instance_db_mutex_);
79 const auto& db = GetInstanceDB(uid);
80 return db.Serialize();
81 }
82
LoadFromJson(const uid_t uid,const Json::Value & db_json)83 Result<void> InstanceManager::LoadFromJson(const uid_t uid,
84 const Json::Value& db_json) {
85 std::lock_guard lock(instance_db_mutex_);
86 CF_EXPECT(!Contains(instance_dbs_, uid));
87 auto& db = GetInstanceDB(uid);
88 CF_EXPECT(db.LoadFromJson(db_json));
89 return {};
90 }
91
Analyze(const std::string & sub_cmd,const CreationAnalyzerParam & param,const ucred & credential)92 Result<InstanceManager::GroupCreationInfo> InstanceManager::Analyze(
93 const std::string& sub_cmd, const CreationAnalyzerParam& param,
94 const ucred& credential) {
95 const uid_t uid = credential.uid;
96 std::unique_lock lock(instance_db_mutex_);
97 auto& instance_db = GetInstanceDB(uid);
98 lock.unlock();
99
100 auto group_creation_info = CF_EXPECT(CreationAnalyzer::Analyze(
101 sub_cmd, param, credential, instance_db, lock_manager_));
102 return {group_creation_info};
103 }
104
SelectGroup(const cvd_common::Args & selector_args,const cvd_common::Envs & envs,const uid_t uid)105 Result<InstanceManager::LocalInstanceGroup> InstanceManager::SelectGroup(
106 const cvd_common::Args& selector_args, const cvd_common::Envs& envs,
107 const uid_t uid) {
108 return SelectGroup(selector_args, {}, envs, uid);
109 }
110
SelectGroup(const cvd_common::Args & selector_args,const Queries & extra_queries,const cvd_common::Envs & envs,const uid_t uid)111 Result<InstanceManager::LocalInstanceGroup> InstanceManager::SelectGroup(
112 const cvd_common::Args& selector_args, const Queries& extra_queries,
113 const cvd_common::Envs& envs, const uid_t uid) {
114 std::unique_lock lock(instance_db_mutex_);
115 auto& instance_db = GetInstanceDB(uid);
116 auto group_selector = CF_EXPECT(
117 GroupSelector::GetSelector(selector_args, extra_queries, envs, uid));
118 auto group = CF_EXPECT(group_selector.FindGroup(instance_db));
119 return group;
120 }
121
SelectInstance(const cvd_common::Args & selector_args,const cvd_common::Envs & envs,const uid_t uid)122 Result<InstanceManager::LocalInstance::Copy> InstanceManager::SelectInstance(
123 const cvd_common::Args& selector_args, const cvd_common::Envs& envs,
124 const uid_t uid) {
125 return SelectInstance(selector_args, {}, envs, uid);
126 }
127
SelectInstance(const cvd_common::Args & selector_args,const Queries & extra_queries,const cvd_common::Envs & envs,const uid_t uid)128 Result<InstanceManager::LocalInstance::Copy> InstanceManager::SelectInstance(
129 const cvd_common::Args& selector_args, const Queries& extra_queries,
130 const cvd_common::Envs& envs, const uid_t uid) {
131 std::unique_lock lock(instance_db_mutex_);
132 auto& instance_db = GetInstanceDB(uid);
133 auto instance_selector = CF_EXPECT(
134 InstanceSelector::GetSelector(selector_args, extra_queries, envs, uid));
135 auto instance_copy = CF_EXPECT(instance_selector.FindInstance(instance_db));
136 return instance_copy;
137 }
138
HasInstanceGroups(const uid_t uid)139 bool InstanceManager::HasInstanceGroups(const uid_t uid) {
140 std::lock_guard lock(instance_db_mutex_);
141 auto& instance_db = GetInstanceDB(uid);
142 return !instance_db.IsEmpty();
143 }
144
SetInstanceGroup(const uid_t uid,const selector::GroupCreationInfo & group_info)145 Result<void> InstanceManager::SetInstanceGroup(
146 const uid_t uid, const selector::GroupCreationInfo& group_info) {
147 std::lock_guard assemblies_lock(instance_db_mutex_);
148 auto& instance_db = GetInstanceDB(uid);
149
150 const auto group_name = group_info.group_name;
151 const auto home_dir = group_info.home;
152 const auto host_artifacts_path = group_info.host_artifacts_path;
153 const auto product_out_path = group_info.product_out_path;
154 const auto& per_instance_info = group_info.instances;
155
156 auto new_group = CF_EXPECT(
157 instance_db.AddInstanceGroup({.group_name = group_name,
158 .home_dir = home_dir,
159 .host_artifacts_path = host_artifacts_path,
160 .product_out_path = product_out_path}));
161
162 using InstanceInfo = selector::InstanceDatabase::InstanceInfo;
163 std::vector<InstanceInfo> instances_info;
164 for (const auto& instance : per_instance_info) {
165 InstanceInfo info{.name = instance.per_instance_name_,
166 .id = instance.instance_id_};
167 instances_info.push_back(info);
168 }
169 auto result = instance_db.AddInstances(group_name, instances_info);
170 if (!result.ok()) {
171 /*
172 * The way InstanceManager uses the database is that it adds an empty
173 * group, gets an handle, and add instances to it. Thus, failing to adding
174 * an instance to the group does not always mean that the instance group
175 * addition fails. It is up to the caller. In this case, however, failing
176 * to add an instance to a new group means failing to create an instance
177 * group itself. Thus, we should remove the new instance group from the
178 * database.
179 *
180 */
181 instance_db.RemoveInstanceGroup(new_group.Get());
182 return CF_ERR(result.error().Trace());
183 }
184 return {};
185 }
186
SetBuildId(const uid_t uid,const std::string & group_name,const std::string & build_id)187 Result<void> InstanceManager::SetBuildId(const uid_t uid,
188 const std::string& group_name,
189 const std::string& build_id) {
190 std::lock_guard assemblies_lock(instance_db_mutex_);
191 auto& instance_db = GetInstanceDB(uid);
192 CF_EXPECT(instance_db.SetBuildId(group_name, build_id));
193 return {};
194 }
195
RemoveInstanceGroup(const uid_t uid,const std::string & dir)196 void InstanceManager::RemoveInstanceGroup(const uid_t uid,
197 const std::string& dir) {
198 std::lock_guard assemblies_lock(instance_db_mutex_);
199 auto& instance_db = GetInstanceDB(uid);
200 auto result = instance_db.FindGroup({selector::kHomeField, dir});
201 if (!result.ok()) return;
202 auto group = *result;
203 instance_db.RemoveInstanceGroup(group);
204 }
205
206 template <typename... Args>
GetCommand(const std::string & prog_path,Args &&...args)207 static Command GetCommand(const std::string& prog_path, Args&&... args) {
208 Command command(prog_path);
209 (command.AddParameter(args), ...);
210 return command;
211 }
212
213 struct ExecCommandResult {
214 std::string stdout_buf;
215 std::string stderr_buf;
216 };
217
ExecCommand(Command && command)218 static Result<ExecCommandResult> ExecCommand(Command&& command) {
219 ExecCommandResult command_result;
220 CF_EXPECT_EQ(RunWithManagedStdio(std::move(command), /* stdin */ nullptr,
221 std::addressof(command_result.stdout_buf),
222 std::addressof(command_result.stderr_buf)),
223 0);
224 return command_result;
225 }
226
227 Result<InstanceManager::StatusCommandOutput>
IssueStatusCommand(const selector::LocalInstanceGroup & group,const SharedFD & err)228 InstanceManager::IssueStatusCommand(const selector::LocalInstanceGroup& group,
229 const SharedFD& err) {
230 std::string not_supported_version_msg = " does not comply with cvd fleet.\n";
231 const auto host_android_out = group.HostArtifactsPath();
232 auto status_bin = CF_EXPECT(host_tool_target_manager_.ExecBaseName({
233 .artifacts_path = host_android_out,
234 .op = "status",
235 }));
236 const auto prog_path = host_android_out + "/bin/" + status_bin;
237 Command with_args = GetCommand(prog_path, "--all_instances", "--print");
238 with_args.SetEnvironment({ConcatToString("HOME=", group.HomeDir())});
239 auto command_result = ExecCommand(std::move(with_args));
240 if (command_result.ok()) {
241 StatusCommandOutput output;
242 if (command_result->stdout_buf.empty()) {
243 WriteAll(err, ConcatToString(group.GroupName(), "-*",
244 not_supported_version_msg));
245 Json::Reader().parse("{}", output.stdout_json);
246 return output;
247 }
248 output.stdout_json = CF_EXPECT(ParseJson(command_result->stdout_buf));
249 return output;
250 }
251 StatusCommandOutput output;
252 int index = 0;
253 for (const auto& instance_ref : CF_EXPECT(group.FindAllInstances())) {
254 const auto id = instance_ref.Get().InstanceId();
255 Command without_args = GetCommand(prog_path);
256 std::vector<std::string> new_envs{
257 ConcatToString("HOME=", group.HomeDir()),
258 ConcatToString(kCuttlefishInstanceEnvVarName, "=", std::to_string(id))};
259 without_args.SetEnvironment(new_envs);
260 auto second_command_result =
261 CF_EXPECT(ExecCommand(std::move(without_args)));
262 if (second_command_result.stdout_buf.empty()) {
263 WriteAll(err,
264 instance_ref.Get().DeviceName() + not_supported_version_msg);
265 second_command_result.stdout_buf.append("{}");
266 }
267 output.stdout_json[index] =
268 CF_EXPECT(ParseJson(second_command_result.stdout_buf));
269 }
270 return output;
271 }
272
CvdFleetImpl(const uid_t uid,const SharedFD & out,const SharedFD & err)273 Result<cvd::Status> InstanceManager::CvdFleetImpl(const uid_t uid,
274 const SharedFD& out,
275 const SharedFD& err) {
276 std::lock_guard assemblies_lock(instance_db_mutex_);
277 auto& instance_db = GetInstanceDB(uid);
278 const char _GroupDeviceInfoStart[] = "[\n";
279 const char _GroupDeviceInfoSeparate[] = ",\n";
280 const char _GroupDeviceInfoEnd[] = "]\n";
281 WriteAll(out, _GroupDeviceInfoStart);
282 auto&& instance_groups = instance_db.InstanceGroups();
283
284 for (const auto& group : instance_groups) {
285 CF_EXPECT(group != nullptr);
286 auto result = IssueStatusCommand(*group, err);
287 if (!result.ok()) {
288 WriteAll(err, " (unknown instance status error)");
289 } else {
290 const auto [stderr_msg, stdout_json] = *result;
291 WriteAll(err, stderr_msg);
292 // TODO(kwstephenkim): build a data structure that also includes
293 // selector-related information, etc.
294 WriteAll(out, stdout_json.toStyledString());
295 }
296 // move on
297 if (group == *instance_groups.crbegin()) {
298 continue;
299 }
300 WriteAll(out, _GroupDeviceInfoSeparate);
301 }
302 WriteAll(out, _GroupDeviceInfoEnd);
303 cvd::Status status;
304 status.set_code(cvd::Status::OK);
305 return status;
306 }
307
CvdFleet(const uid_t uid,const SharedFD & out,const SharedFD & err,const std::vector<std::string> & fleet_cmd_args)308 Result<cvd::Status> InstanceManager::CvdFleet(
309 const uid_t uid, const SharedFD& out, const SharedFD& err,
310 const std::vector<std::string>& fleet_cmd_args) {
311 bool is_help = false;
312 for (const auto& arg : fleet_cmd_args) {
313 if (arg == "--help" || arg == "-help") {
314 is_help = true;
315 break;
316 }
317 }
318 CF_EXPECT(!is_help,
319 "cvd fleet --help should be handled by fleet handler itself.");
320 const auto status = CF_EXPECT(CvdFleetImpl(uid, out, err));
321 return status;
322 }
323
StopBin(const std::string & host_android_out)324 Result<std::string> InstanceManager::StopBin(
325 const std::string& host_android_out) {
326 const auto stop_bin = CF_EXPECT(host_tool_target_manager_.ExecBaseName({
327 .artifacts_path = host_android_out,
328 .op = "stop",
329 }));
330 return stop_bin;
331 }
332
IssueStopCommand(const SharedFD & out,const SharedFD & err,const std::string & config_file_path,const selector::LocalInstanceGroup & group)333 Result<void> InstanceManager::IssueStopCommand(
334 const SharedFD& out, const SharedFD& err,
335 const std::string& config_file_path,
336 const selector::LocalInstanceGroup& group) {
337 const auto stop_bin = CF_EXPECT(StopBin(group.HostArtifactsPath()));
338 Command command(group.HostArtifactsPath() + "/bin/" + stop_bin);
339 command.AddParameter("--clear_instance_dirs");
340 command.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, out);
341 command.RedirectStdIO(Subprocess::StdIOChannel::kStdErr, err);
342 command.AddEnvironmentVariable(kCuttlefishConfigEnvVarName, config_file_path);
343 auto wait_result = RunCommand(std::move(command));
344 /**
345 * --clear_instance_dirs may not be available for old branches. This causes
346 * the stop_cvd to terminates with a non-zero exit code due to the parsing
347 * error. Then, we will try to re-run it without the flag.
348 */
349 if (!wait_result.ok()) {
350 std::stringstream error_msg;
351 error_msg << stop_bin << " was executed internally, and failed. It might "
352 << "be failing to parse the new --clear_instance_dirs. Will try "
353 << "without the flag.\n";
354 WriteAll(err, error_msg.str());
355 Command no_clear_instance_dir_command(group.HostArtifactsPath() + "/bin/" +
356 stop_bin);
357 no_clear_instance_dir_command.RedirectStdIO(
358 Subprocess::StdIOChannel::kStdOut, out);
359 no_clear_instance_dir_command.RedirectStdIO(
360 Subprocess::StdIOChannel::kStdErr, err);
361 no_clear_instance_dir_command.AddEnvironmentVariable(
362 kCuttlefishConfigEnvVarName, config_file_path);
363 wait_result = RunCommand(std::move(no_clear_instance_dir_command));
364 }
365
366 if (!wait_result.ok()) {
367 WriteAll(err,
368 "Warning: error stopping instances for dir \"" + group.HomeDir() +
369 "\".\nThis can happen if instances are already stopped.\n");
370 }
371 for (const auto& instance : group.Instances()) {
372 auto lock = lock_manager_.TryAcquireLock(instance->InstanceId());
373 if (lock.ok() && (*lock)) {
374 (*lock)->Status(InUseState::kNotInUse);
375 continue;
376 }
377 WriteAll(err, "InstanceLockFileManager failed to acquire lock");
378 }
379 return {};
380 }
381
CvdClear(const SharedFD & out,const SharedFD & err)382 cvd::Status InstanceManager::CvdClear(const SharedFD& out,
383 const SharedFD& err) {
384 std::lock_guard lock(instance_db_mutex_);
385 cvd::Status status;
386 const std::string config_json_name = cpp_basename(GetGlobalConfigFileLink());
387 for (auto& [uid, instance_db] : instance_dbs_) {
388 auto&& instance_groups = instance_db.InstanceGroups();
389 for (const auto& group : instance_groups) {
390 auto config_path = group->GetCuttlefishConfigPath();
391 if (config_path.ok()) {
392 auto stop_result = IssueStopCommand(out, err, *config_path, *group);
393 if (!stop_result.ok()) {
394 LOG(ERROR) << stop_result.error().Message();
395 }
396 }
397 RemoveFile(group->HomeDir() + "/cuttlefish_runtime");
398 RemoveFile(group->HomeDir() + config_json_name);
399 }
400 instance_db.Clear();
401 }
402 // TODO(kwstephenkim): we need a better mechanism to make sure that
403 // we clear all run_cvd processes.
404 instance_dbs_.clear();
405 WriteAll(err, "Stopped all known instances\n");
406 status.set_code(cvd::Status::OK);
407 return status;
408 }
409
TryAcquireLock(int instance_num)410 Result<std::optional<InstanceLockFile>> InstanceManager::TryAcquireLock(
411 int instance_num) {
412 std::lock_guard lock(instance_db_mutex_);
413 return CF_EXPECT(lock_manager_.TryAcquireLock(instance_num));
414 }
415
416 Result<std::vector<InstanceManager::LocalInstanceGroup>>
FindGroups(const uid_t uid,const Query & query) const417 InstanceManager::FindGroups(const uid_t uid, const Query& query) const {
418 return CF_EXPECT(FindGroups(uid, Queries{query}));
419 }
420
421 Result<std::vector<InstanceManager::LocalInstanceGroup>>
FindGroups(const uid_t uid,const Queries & queries) const422 InstanceManager::FindGroups(const uid_t uid, const Queries& queries) const {
423 std::lock_guard lock(instance_db_mutex_);
424 if (!Contains(instance_dbs_, uid)) {
425 return {};
426 }
427 const auto& db = instance_dbs_.at(uid);
428 auto groups = CF_EXPECT(db.FindGroups(queries));
429 // create a copy as we are escaping the critical section
430 std::vector<LocalInstanceGroup> output;
431 for (const auto& group_ref : groups) {
432 output.push_back(group_ref.Get());
433 }
434 return output;
435 }
436
437 Result<std::vector<InstanceManager::LocalInstance::Copy>>
FindInstances(const uid_t uid,const Query & query) const438 InstanceManager::FindInstances(const uid_t uid, const Query& query) const {
439 return CF_EXPECT(FindInstances(uid, Queries{query}));
440 }
441
442 Result<std::vector<InstanceManager::LocalInstance::Copy>>
FindInstances(const uid_t uid,const Queries & queries) const443 InstanceManager::FindInstances(const uid_t uid, const Queries& queries) const {
444 std::lock_guard lock(instance_db_mutex_);
445 if (!Contains(instance_dbs_, uid)) {
446 return {};
447 }
448 const auto& db = instance_dbs_.at(uid);
449 auto instances = CF_EXPECT(db.FindInstances(queries));
450 // create a copy as we are escaping the critical section
451 std::vector<LocalInstance::Copy> output;
452 for (const auto& instance : instances) {
453 output.push_back(instance.Get().GetCopy());
454 }
455 return output;
456 }
457
FindGroup(const uid_t uid,const Query & query) const458 Result<InstanceManager::LocalInstanceGroup> InstanceManager::FindGroup(
459 const uid_t uid, const Query& query) const {
460 return CF_EXPECT(FindGroup(uid, Queries{query}));
461 }
462
FindGroup(const uid_t uid,const Queries & queries) const463 Result<InstanceManager::LocalInstanceGroup> InstanceManager::FindGroup(
464 const uid_t uid, const Queries& queries) const {
465 std::lock_guard lock(instance_db_mutex_);
466 CF_EXPECT(Contains(instance_dbs_, uid));
467 const auto& db = instance_dbs_.at(uid);
468 auto output = CF_EXPECT(db.FindGroups(queries));
469 CF_EXPECT_EQ(output.size(), 1);
470 return *(output.begin());
471 }
472
473 } // namespace cuttlefish
474