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/selector/creation_analyzer.h"
18
19 #include <sys/types.h>
20
21 #include <algorithm>
22 #include <map>
23 #include <regex>
24 #include <set>
25 #include <string>
26
27 #include <android-base/parseint.h>
28 #include <android-base/strings.h>
29
30 #include "common/libs/utils/contains.h"
31 #include "common/libs/utils/flag_parser.h"
32 #include "common/libs/utils/users.h"
33 #include "host/commands/cvd/common_utils.h"
34 #include "host/commands/cvd/selector/instance_database_utils.h"
35 #include "host/commands/cvd/selector/selector_constants.h"
36 #include "host/libs/config/cuttlefish_config.h"
37
38 namespace cuttlefish {
39 namespace selector {
40
IsCvdStart(const std::string & cmd)41 static bool IsCvdStart(const std::string& cmd) {
42 if (cmd.empty()) {
43 return false;
44 }
45 return cmd == "start";
46 }
47
Analyze(const std::string & cmd,const CreationAnalyzerParam & param,const ucred & credential,const InstanceDatabase & instance_database,InstanceLockFileManager & instance_lock_file_manager)48 Result<GroupCreationInfo> CreationAnalyzer::Analyze(
49 const std::string& cmd, const CreationAnalyzerParam& param,
50 const ucred& credential, const InstanceDatabase& instance_database,
51 InstanceLockFileManager& instance_lock_file_manager) {
52 CF_EXPECT(IsCvdStart(cmd),
53 "CreationAnalyzer::Analyze() is for cvd start only.");
54 const auto client_uid = credential.uid;
55 auto selector_options_parser =
56 CF_EXPECT(StartSelectorParser::ConductSelectFlagsParser(
57 client_uid, param.selector_args, param.cmd_args, param.envs));
58 CreationAnalyzer analyzer(param, credential,
59 std::move(selector_options_parser),
60 instance_database, instance_lock_file_manager);
61 auto result = CF_EXPECT(analyzer.Analyze());
62 return result;
63 }
64
CreationAnalyzer(const CreationAnalyzerParam & param,const ucred & credential,StartSelectorParser && selector_options_parser,const InstanceDatabase & instance_database,InstanceLockFileManager & instance_file_lock_manager)65 CreationAnalyzer::CreationAnalyzer(
66 const CreationAnalyzerParam& param, const ucred& credential,
67 StartSelectorParser&& selector_options_parser,
68 const InstanceDatabase& instance_database,
69 InstanceLockFileManager& instance_file_lock_manager)
70 : cmd_args_(param.cmd_args),
71 envs_(param.envs),
72 selector_args_(param.selector_args),
73 credential_(credential),
74 selector_options_parser_{std::move(selector_options_parser)},
75 instance_database_{instance_database},
76 instance_file_lock_manager_{instance_file_lock_manager} {}
77
ConstructIdLockFileMap(std::vector<InstanceLockFile> && lock_files)78 static std::unordered_map<unsigned, InstanceLockFile> ConstructIdLockFileMap(
79 std::vector<InstanceLockFile>&& lock_files) {
80 std::unordered_map<unsigned, InstanceLockFile> mapping;
81 for (auto& lock_file : lock_files) {
82 const unsigned id = static_cast<unsigned>(lock_file.Instance());
83 mapping.insert({id, std::move(lock_file)});
84 }
85 lock_files.clear();
86 return mapping;
87 }
88
IsIdAvailable(const InstanceDatabase & instance_database,const unsigned id)89 static Result<void> IsIdAvailable(const InstanceDatabase& instance_database,
90 const unsigned id) {
91 auto subset =
92 CF_EXPECT(instance_database.FindInstances(Query{kInstanceIdField, id}));
93 CF_EXPECT(subset.empty());
94 return {};
95 }
96
97 Result<std::vector<PerInstanceInfo>>
AnalyzeInstanceIdsInternal(const std::vector<unsigned> & requested_instance_ids)98 CreationAnalyzer::AnalyzeInstanceIdsInternal(
99 const std::vector<unsigned>& requested_instance_ids) {
100 CF_EXPECT(!requested_instance_ids.empty(),
101 "Instance IDs were specified, so should be one or more.");
102 for (const auto id : requested_instance_ids) {
103 CF_EXPECT(IsIdAvailable(instance_database_, id),
104 "instance ID #" << id << " is requeested but not available.");
105 }
106
107 std::vector<std::string> per_instance_names;
108 if (selector_options_parser_.PerInstanceNames()) {
109 per_instance_names = *selector_options_parser_.PerInstanceNames();
110 CF_EXPECT_EQ(per_instance_names.size(), requested_instance_ids.size());
111 } else {
112 for (const auto id : requested_instance_ids) {
113 per_instance_names.push_back(std::to_string(id));
114 }
115 }
116
117 std::map<unsigned, std::string> id_name_pairs;
118 for (size_t i = 0; i != requested_instance_ids.size(); i++) {
119 id_name_pairs[requested_instance_ids.at(i)] = per_instance_names.at(i);
120 }
121
122 std::vector<PerInstanceInfo> instance_info;
123 bool must_acquire_file_locks = selector_options_parser_.MustAcquireFileLock();
124 if (!must_acquire_file_locks) {
125 for (const auto& [id, name] : id_name_pairs) {
126 instance_info.emplace_back(id, name);
127 }
128 return instance_info;
129 }
130 auto acquired_all_file_locks =
131 CF_EXPECT(instance_file_lock_manager_.LockAllAvailable());
132 auto id_to_lockfile_map =
133 ConstructIdLockFileMap(std::move(acquired_all_file_locks));
134 for (const auto& [id, instance_name] : id_name_pairs) {
135 CF_EXPECT(Contains(id_to_lockfile_map, id),
136 "Instance ID " << id << " lock file can't be locked.");
137 auto& lock_file = id_to_lockfile_map.at(id);
138 instance_info.emplace_back(id, instance_name, std::move(lock_file));
139 }
140 return instance_info;
141 }
142
143 /*
144 * Filters out the ids in id_pool that already exist in instance_database
145 */
CollectUnusedIds(const InstanceDatabase & instance_database,std::vector<unsigned> && id_pool)146 static Result<std::vector<unsigned>> CollectUnusedIds(
147 const InstanceDatabase& instance_database,
148 std::vector<unsigned>&& id_pool) {
149 std::vector<unsigned> collected_ids;
150 for (const auto id : id_pool) {
151 if (IsIdAvailable(instance_database, id).ok()) {
152 collected_ids.push_back(id);
153 }
154 }
155 return collected_ids;
156 }
157
158 struct NameLockFilePair {
159 std::string name;
160 InstanceLockFile lock_file;
161 };
162 Result<std::vector<PerInstanceInfo>>
AnalyzeInstanceIdsInternal()163 CreationAnalyzer::AnalyzeInstanceIdsInternal() {
164 CF_EXPECT(selector_options_parser_.MustAcquireFileLock(),
165 "For now, cvd server always acquire the file locks "
166 << "when IDs are automatically allocated.");
167
168 // As this test was done earlier, this line must not fail
169 const auto n_instances = selector_options_parser_.RequestedNumInstances();
170 auto acquired_all_file_locks =
171 CF_EXPECT(instance_file_lock_manager_.LockAllAvailable());
172 auto id_to_lockfile_map =
173 ConstructIdLockFileMap(std::move(acquired_all_file_locks));
174
175 /* generate n_instances consecutive ids. For backward compatibility,
176 * we prefer n consecutive ids for now.
177 */
178 std::vector<unsigned> id_pool;
179 id_pool.reserve(id_to_lockfile_map.size());
180 for (const auto& [id, _] : id_to_lockfile_map) {
181 id_pool.push_back(id);
182 }
183 auto unused_id_pool =
184 CF_EXPECT(CollectUnusedIds(instance_database_, std::move(id_pool)));
185 auto unique_id_allocator = std::move(IdAllocator::New(unused_id_pool));
186 CF_EXPECT(unique_id_allocator != nullptr,
187 "Memory allocation for UniqueResourceAllocator failed.");
188
189 // auto-generation means the user did not specify much: e.g. "cvd start"
190 // In this case, the user may expect the instance id to be 1+
191 using ReservationSet = UniqueResourceAllocator<unsigned>::ReservationSet;
192 std::optional<ReservationSet> allocated_ids_opt;
193 if (selector_options_parser_.IsMaybeDefaultGroup()) {
194 allocated_ids_opt = unique_id_allocator->TakeRange(1, 1 + n_instances);
195 }
196 if (!allocated_ids_opt) {
197 allocated_ids_opt =
198 unique_id_allocator->UniqueConsecutiveItems(n_instances);
199 }
200 CF_EXPECT(allocated_ids_opt != std::nullopt, "Unique ID allocation failed.");
201
202 std::vector<unsigned> allocated_ids;
203 allocated_ids.reserve(allocated_ids_opt->size());
204 for (const auto& reservation : *allocated_ids_opt) {
205 allocated_ids.push_back(reservation.Get());
206 }
207 std::sort(allocated_ids.begin(), allocated_ids.end());
208
209 const auto per_instance_names_opt =
210 selector_options_parser_.PerInstanceNames();
211 if (per_instance_names_opt) {
212 CF_EXPECT(per_instance_names_opt->size() == allocated_ids.size());
213 }
214 std::vector<PerInstanceInfo> instance_info;
215 for (size_t i = 0; i != allocated_ids.size(); i++) {
216 const auto id = allocated_ids.at(i);
217 std::string name = (per_instance_names_opt ? per_instance_names_opt->at(i)
218 : std::to_string(id));
219 instance_info.emplace_back(id, name, std::move(id_to_lockfile_map.at(id)));
220 }
221 return instance_info;
222 }
223
AnalyzeInstanceIds()224 Result<std::vector<PerInstanceInfo>> CreationAnalyzer::AnalyzeInstanceIds() {
225 auto requested_instance_ids = selector_options_parser_.InstanceIds();
226 return requested_instance_ids
227 ? CF_EXPECT(AnalyzeInstanceIdsInternal(*requested_instance_ids))
228 : CF_EXPECT(AnalyzeInstanceIdsInternal());
229 }
230
231 /*
232 * 1. Remove --num_instances, --instance_nums, --base_instance_num if any.
233 * 2. If the ids are consecutive and ordered, add:
234 * --base_instance_num=min --num_instances=ids.size()
235 * 3. If not, --instance_nums=<ids>
236 *
237 */
UpdateInstanceArgs(std::vector<std::string> && args,const std::vector<unsigned> & ids)238 static Result<std::vector<std::string>> UpdateInstanceArgs(
239 std::vector<std::string>&& args, const std::vector<unsigned>& ids) {
240 CF_EXPECT(ids.empty() == false);
241
242 std::vector<std::string> new_args{std::move(args)};
243 std::string old_instance_nums;
244 std::string old_num_instances;
245 std::string old_base_instance_num;
246
247 std::vector<Flag> instance_id_flags{
248 GflagsCompatFlag("instance_nums", old_instance_nums),
249 GflagsCompatFlag("num_instances", old_num_instances),
250 GflagsCompatFlag("base_instance_num", old_base_instance_num)};
251 // discard old ones
252 ParseFlags(instance_id_flags, new_args);
253
254 auto max = *(std::max_element(ids.cbegin(), ids.cend()));
255 auto min = *(std::min_element(ids.cbegin(), ids.cend()));
256
257 const bool is_consecutive = ((max - min) == (ids.size() - 1));
258 const bool is_sorted = std::is_sorted(ids.begin(), ids.end());
259
260 if (!is_consecutive || !is_sorted) {
261 std::string flag_value = android::base::Join(ids, ",");
262 new_args.push_back("--instance_nums=" + flag_value);
263 return new_args;
264 }
265
266 // sorted and consecutive, so let's use old flags
267 // like --num_instances and --base_instance_num
268 new_args.push_back("--num_instances=" + std::to_string(ids.size()));
269 new_args.push_back("--base_instance_num=" + std::to_string(min));
270 return new_args;
271 }
272
UpdateWebrtcDeviceId(std::vector<std::string> && args,const std::vector<PerInstanceInfo> & per_instance_info)273 Result<std::vector<std::string>> CreationAnalyzer::UpdateWebrtcDeviceId(
274 std::vector<std::string>&& args,
275 const std::vector<PerInstanceInfo>& per_instance_info) {
276 std::vector<std::string> new_args{std::move(args)};
277 std::string flag_value;
278 std::vector<Flag> webrtc_device_id_flag{
279 GflagsCompatFlag("webrtc_device_id", flag_value)};
280 std::vector<std::string> copied_args{new_args};
281 CF_EXPECT(ParseFlags(webrtc_device_id_flag, copied_args));
282
283 if (!flag_value.empty()) {
284 return new_args;
285 }
286
287 CF_EXPECT(!group_name_.empty());
288 std::vector<std::string> device_name_list;
289 for (const auto& instance : per_instance_info) {
290 const auto& per_instance_name = instance.per_instance_name_;
291 std::string device_name = group_name_ + "-" + per_instance_name;
292 device_name_list.push_back(device_name);
293 }
294 // take --webrtc_device_id flag away
295 new_args = std::move(copied_args);
296 new_args.push_back("--webrtc_device_id=" +
297 android::base::Join(device_name_list, ","));
298 return new_args;
299 }
300
Analyze()301 Result<GroupCreationInfo> CreationAnalyzer::Analyze() {
302 auto instance_info = CF_EXPECT(AnalyzeInstanceIds());
303 std::vector<unsigned> ids;
304 ids.reserve(instance_info.size());
305 for (const auto& instance : instance_info) {
306 ids.push_back(instance.instance_id_);
307 }
308 cmd_args_ = CF_EXPECT(UpdateInstanceArgs(std::move(cmd_args_), ids));
309
310 group_name_ = CF_EXPECT(AnalyzeGroupName(instance_info));
311 cmd_args_ =
312 CF_EXPECT(UpdateWebrtcDeviceId(std::move(cmd_args_), instance_info));
313
314 home_ = CF_EXPECT(AnalyzeHome());
315 envs_["HOME"] = home_;
316
317 CF_EXPECT(Contains(envs_, kAndroidHostOut));
318 std::string android_product_out_path = Contains(envs_, kAndroidProductOut)
319 ? envs_.at(kAndroidProductOut)
320 : envs_.at(kAndroidHostOut);
321 GroupCreationInfo report = {.home = home_,
322 .host_artifacts_path = envs_.at(kAndroidHostOut),
323 .product_out_path = android_product_out_path,
324 .group_name = group_name_,
325 .instances = std::move(instance_info),
326 .args = cmd_args_,
327 .envs = envs_};
328 return report;
329 }
330
AnalyzeGroupName(const std::vector<PerInstanceInfo> & per_instance_infos) const331 Result<std::string> CreationAnalyzer::AnalyzeGroupName(
332 const std::vector<PerInstanceInfo>& per_instance_infos) const {
333 if (selector_options_parser_.GroupName()) {
334 return selector_options_parser_.GroupName().value();
335 }
336 // auto-generate group name
337 std::vector<unsigned> ids;
338 ids.reserve(per_instance_infos.size());
339 for (const auto& per_instance_info : per_instance_infos) {
340 ids.push_back(per_instance_info.instance_id_);
341 }
342 std::string base_name = GenDefaultGroupName();
343 if (selector_options_parser_.IsMaybeDefaultGroup()) {
344 /*
345 * this base_name might be already taken. In that case, the user's
346 * request should fail in the InstanceDatabase
347 */
348 auto groups =
349 CF_EXPECT(instance_database_.FindGroups({kGroupNameField, base_name}));
350 CF_EXPECT(groups.empty(), "The default instance group name, \""
351 << base_name << "\" has been already taken.");
352 return base_name;
353 }
354
355 /* We cannot return simply "cvd" as we do not want duplication in the group
356 * name across the instance groups owned by the user. Note that the set of ids
357 * are expected to be unique to the user, so we use the ids. If ever the end
358 * user happened to have already used the generated name, we did our best, and
359 * cvd start will fail with a proper error message.
360 */
361 auto unique_suffix =
362 std::to_string(*std::min_element(ids.begin(), ids.end()));
363 return base_name + "_" + unique_suffix;
364 }
365
AnalyzeHome() const366 Result<std::string> CreationAnalyzer::AnalyzeHome() const {
367 auto system_wide_home = CF_EXPECT(SystemWideUserHome(credential_.uid));
368 if (Contains(envs_, "HOME") && envs_.at("HOME") != system_wide_home) {
369 return envs_.at("HOME");
370 }
371
372 if (selector_options_parser_.IsMaybeDefaultGroup()) {
373 auto groups = CF_EXPECT(
374 instance_database_.FindGroups({kHomeField, system_wide_home}));
375 if (groups.empty()) {
376 return system_wide_home;
377 }
378 }
379
380 CF_EXPECT(!group_name_.empty(),
381 "To auto-generate HOME, the group name is a must.");
382 const auto client_uid = credential_.uid;
383 const auto client_gid = credential_.gid;
384 std::string auto_generated_home =
385 CF_EXPECT(ParentOfAutogeneratedHomes(client_uid, client_gid));
386 auto_generated_home.append("/" + std::to_string(client_uid));
387 auto_generated_home.append("/" + group_name_);
388 CF_EXPECT(EnsureDirectoryExistsAllTheWay(auto_generated_home));
389 return auto_generated_home;
390 }
391
392 } // namespace selector
393 } // namespace cuttlefish
394