1 //
2 // Copyright (C) 2019 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 #include <iostream>
17
18 #include <android-base/logging.h>
19 #include <android-base/parsebool.h>
20 #include <android-base/parseint.h>
21 #include <android-base/strings.h>
22 #include <gflags/gflags.h>
23
24 #include "common/libs/fs/shared_buf.h"
25 #include "common/libs/fs/shared_fd.h"
26 #include "common/libs/utils/environment.h"
27 #include "common/libs/utils/files.h"
28 #include "common/libs/utils/flag_parser.h"
29 #include "common/libs/utils/tee_logging.h"
30 #include "host/commands/assemble_cvd/clean.h"
31 #include "host/commands/assemble_cvd/disk_flags.h"
32 #include "host/commands/assemble_cvd/flag_feature.h"
33 #include "host/commands/assemble_cvd/flags.h"
34 #include "host/commands/assemble_cvd/flags_defaults.h"
35 #include "host/libs/config/adb/adb.h"
36 #include "host/libs/config/config_flag.h"
37 #include "host/libs/config/custom_actions.h"
38 #include "host/libs/config/fastboot/fastboot.h"
39 #include "host/libs/config/fetcher_config.h"
40 #include "host/libs/config/inject.h"
41
42 using cuttlefish::StringFromEnv;
43
44 DEFINE_string(assembly_dir, CF_DEFAULTS_ASSEMBLY_DIR,
45 "A directory to put generated files common between instances");
46 DEFINE_string(instance_dir, CF_DEFAULTS_INSTANCE_DIR,
47 "This is a directory that will hold the cuttlefish generated"
48 "files, including both instance-specific and common files");
49 DEFINE_bool(resume, CF_DEFAULTS_RESUME,
50 "Resume using the disk from the last session, if "
51 "possible. i.e., if --noresume is passed, the disk "
52 "will be reset to the state it was initially launched "
53 "in. This flag is ignored if the underlying partition "
54 "images have been updated since the first launch.");
55
56 DECLARE_bool(use_overlay);
57
58 namespace cuttlefish {
59 namespace {
60
61 std::string kFetcherConfigFile = "fetcher_config.json";
62
63 struct LocatedFetcherConfig {
64 FetcherConfig fetcher_config;
65 std::optional<std::string> working_dir;
66 };
67
FindFetcherConfig(const std::vector<std::string> & files)68 LocatedFetcherConfig FindFetcherConfig(const std::vector<std::string>& files) {
69 LocatedFetcherConfig located_fetcher_config;
70 for (const auto& file : files) {
71 if (android::base::EndsWith(file, kFetcherConfigFile)) {
72 std::string home_directory = StringFromEnv("HOME", CurrentDirectory());
73 std::string fetcher_file = file;
74 if (!FileExists(file) &&
75 FileExists(home_directory + "/" + fetcher_file)) {
76 LOG(INFO) << "Found " << fetcher_file << " in HOME directory ('"
77 << home_directory << "') and not current working directory";
78
79 located_fetcher_config.working_dir = home_directory;
80 fetcher_file = home_directory + "/" + fetcher_file;
81 }
82
83 if (located_fetcher_config.fetcher_config.LoadFromFile(fetcher_file)) {
84 return located_fetcher_config;
85 }
86 LOG(ERROR) << "Could not load fetcher config file.";
87 }
88 }
89 return located_fetcher_config;
90 }
91
GetLegacyConfigFilePath(const CuttlefishConfig & config)92 std::string GetLegacyConfigFilePath(const CuttlefishConfig& config) {
93 return config.ForDefaultInstance().PerInstancePath("cuttlefish_config.json");
94 }
95
SaveConfig(const CuttlefishConfig & tmp_config_obj)96 Result<void> SaveConfig(const CuttlefishConfig& tmp_config_obj) {
97 auto config_file = GetConfigFilePath(tmp_config_obj);
98 auto config_link = GetGlobalConfigFileLink();
99 // Save the config object before starting any host process
100 CF_EXPECT(tmp_config_obj.SaveToFile(config_file),
101 "Failed to save to \"" << config_file << "\"");
102 auto legacy_config_file = GetLegacyConfigFilePath(tmp_config_obj);
103 CF_EXPECT(tmp_config_obj.SaveToFile(legacy_config_file),
104 "Failed to save to \"" << legacy_config_file << "\"");
105
106 setenv(kCuttlefishConfigEnvVarName, config_file.c_str(), true);
107 if (symlink(config_file.c_str(), config_link.c_str()) != 0) {
108 return CF_ERRNO("symlink(\"" << config_file << "\", \"" << config_link
109 << ") failed");
110 }
111
112 return {};
113 }
114
115 #ifndef O_TMPFILE
116 # define O_TMPFILE (020000000 | O_DIRECTORY)
117 #endif
118
CreateLegacySymlinks(const CuttlefishConfig::InstanceSpecific & instance)119 Result<void> CreateLegacySymlinks(
120 const CuttlefishConfig::InstanceSpecific& instance) {
121 std::string log_files[] = {"kernel.log",
122 "launcher.log",
123 "logcat",
124 "metrics.log",
125 "modem_simulator.log",
126 "crosvm_openwrt.log",
127 "crosvm_openwrt_boot.log"};
128 for (const auto& log_file : log_files) {
129 auto symlink_location = instance.PerInstancePath(log_file.c_str());
130 auto log_target = "logs/" + log_file; // Relative path
131 if (symlink(log_target.c_str(), symlink_location.c_str()) != 0) {
132 return CF_ERRNO("symlink(\"" << log_target << ", " << symlink_location
133 << ") failed");
134 }
135 }
136
137 std::stringstream legacy_instance_path_stream;
138 legacy_instance_path_stream << FLAGS_instance_dir;
139 if (gflags::GetCommandLineFlagInfoOrDie("instance_dir").is_default) {
140 legacy_instance_path_stream << "_runtime";
141 }
142 legacy_instance_path_stream << "." << instance.id();
143 auto legacy_instance_path = legacy_instance_path_stream.str();
144
145 if (DirectoryExists(legacy_instance_path, /* follow_symlinks */ false)) {
146 CF_EXPECT(RecursivelyRemoveDirectory(legacy_instance_path),
147 "Failed to remove legacy directory " << legacy_instance_path);
148 } else if (FileExists(legacy_instance_path, /* follow_symlinks */ false)) {
149 CF_EXPECT(RemoveFile(legacy_instance_path),
150 "Failed to remove instance_dir symlink " << legacy_instance_path);
151 }
152 if (symlink(instance.instance_dir().c_str(), legacy_instance_path.c_str())) {
153 return CF_ERRNO("symlink(\"" << instance.instance_dir() << "\", \""
154 << legacy_instance_path << "\") failed");
155 }
156
157 const auto mac80211_uds_name = "vhost_user_mac80211";
158
159 const auto mac80211_uds_path =
160 instance.PerInstanceInternalUdsPath(mac80211_uds_name);
161 const auto legacy_mac80211_uds_path =
162 instance.PerInstanceInternalPath(mac80211_uds_name);
163
164 if (symlink(mac80211_uds_path.c_str(), legacy_mac80211_uds_path.c_str())) {
165 return CF_ERRNO("symlink(\"" << mac80211_uds_path << "\", \""
166 << legacy_mac80211_uds_path << "\") failed");
167 }
168
169 return {};
170 }
171
InitFilesystemAndCreateConfig(FetcherConfig fetcher_config,const std::vector<GuestConfig> & guest_configs,fruit::Injector<> & injector)172 Result<const CuttlefishConfig*> InitFilesystemAndCreateConfig(
173 FetcherConfig fetcher_config, const std::vector<GuestConfig>& guest_configs,
174 fruit::Injector<>& injector) {
175 std::string runtime_dir_parent = AbsolutePath(FLAGS_instance_dir);
176 while (runtime_dir_parent[runtime_dir_parent.size() - 1] == '/') {
177 runtime_dir_parent =
178 runtime_dir_parent.substr(0, FLAGS_instance_dir.rfind('/'));
179 }
180 runtime_dir_parent =
181 runtime_dir_parent.substr(0, FLAGS_instance_dir.rfind('/'));
182 auto log = SharedFD::Open(runtime_dir_parent, O_WRONLY | O_TMPFILE,
183 S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
184 if (!log->IsOpen()) {
185 LOG(ERROR) << "Could not open O_TMPFILE precursor to assemble_cvd.log: "
186 << log->StrError();
187 } else {
188 android::base::SetLogger(TeeLogger({
189 {ConsoleSeverity(), SharedFD::Dup(2), MetadataLevel::ONLY_MESSAGE},
190 {LogFileSeverity(), log, MetadataLevel::FULL},
191 }));
192 }
193
194 {
195 // The config object is created here, but only exists in memory until the
196 // SaveConfig line below. Don't launch cuttlefish subprocesses between these
197 // two operations, as those will assume they can read the config object from
198 // disk.
199 auto config = CF_EXPECT(
200 InitializeCuttlefishConfiguration(FLAGS_instance_dir, guest_configs,
201 injector, fetcher_config),
202 "cuttlefish configuration initialization failed");
203
204 // take the max value of modem_simulator_instance_number in each instance
205 // which is used for preserving/deleting iccprofile_for_simX.xml files
206 int modem_simulator_count = 0;
207
208 std::set<std::string> preserving;
209 bool creating_os_disk = false;
210 // if any device needs to rebuild its composite disk,
211 // then don't preserve any files and delete everything.
212 for (const auto& instance : config.Instances()) {
213 auto os_builder = OsCompositeDiskBuilder(config, instance);
214 creating_os_disk |= CF_EXPECT(os_builder.WillRebuildCompositeDisk());
215 if (instance.ap_boot_flow() != CuttlefishConfig::InstanceSpecific::APBootFlow::None) {
216 auto ap_builder = ApCompositeDiskBuilder(config, instance);
217 creating_os_disk |= CF_EXPECT(ap_builder.WillRebuildCompositeDisk());
218 }
219 if (instance.modem_simulator_instance_number() > modem_simulator_count) {
220 modem_simulator_count = instance.modem_simulator_instance_number();
221 }
222 }
223 // TODO(schuffelen): Add smarter decision for when to delete runtime files.
224 // Files like NVChip are tightly bound to Android keymint and should be
225 // deleted when userdata is reset. However if the user has ever run without
226 // the overlay, then we want to keep this until userdata.img was externally
227 // replaced.
228 creating_os_disk &= FLAGS_use_overlay;
229 if (FLAGS_resume && creating_os_disk) {
230 LOG(INFO) << "Requested resuming a previous session (the default behavior) "
231 << "but the base images have changed under the overlay, making the "
232 << "overlay incompatible. Wiping the overlay files.";
233 } else if (FLAGS_resume && !creating_os_disk) {
234 preserving.insert("overlay.img");
235 preserving.insert("ap_overlay.img");
236 preserving.insert("os_composite_disk_config.txt");
237 preserving.insert("os_composite_gpt_header.img");
238 preserving.insert("os_composite_gpt_footer.img");
239 preserving.insert("os_composite.img");
240 preserving.insert("sdcard.img");
241 preserving.insert("boot_repacked.img");
242 preserving.insert("vendor_dlkm_repacked.img");
243 preserving.insert("vendor_boot_repacked.img");
244 preserving.insert("access-kregistry");
245 preserving.insert("hwcomposer-pmem");
246 preserving.insert("NVChip");
247 preserving.insert("gatekeeper_secure");
248 preserving.insert("gatekeeper_insecure");
249 preserving.insert("modem_nvram.json");
250 preserving.insert("recording");
251 preserving.insert("persistent_composite_disk_config.txt");
252 preserving.insert("persistent_composite_gpt_header.img");
253 preserving.insert("persistent_composite_gpt_footer.img");
254 preserving.insert("persistent_composite.img");
255 preserving.insert("uboot_env.img");
256 preserving.insert("factory_reset_protected.img");
257 std::stringstream ss;
258 for (int i = 0; i < modem_simulator_count; i++) {
259 ss.clear();
260 ss << "iccprofile_for_sim" << i << ".xml";
261 preserving.insert(ss.str());
262 ss.str("");
263 }
264 }
265 CF_EXPECT(CleanPriorFiles(preserving, config.assembly_dir(),
266 config.instance_dirs()),
267 "Failed to clean prior files");
268
269 auto defaultGroup = "cvdnetwork";
270 const mode_t defaultMode = S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH;
271
272 CF_EXPECT(EnsureDirectoryExists(config.root_dir()));
273 CF_EXPECT(EnsureDirectoryExists(config.assembly_dir()));
274 CF_EXPECT(EnsureDirectoryExists(config.instances_dir()));
275 CF_EXPECT(EnsureDirectoryExists(config.instances_uds_dir(), defaultMode,
276 defaultGroup));
277
278 LOG(INFO) << "Path for instance UDS: " << config.instances_uds_dir();
279
280 if (log->LinkAtCwd(config.AssemblyPath("assemble_cvd.log"))) {
281 LOG(ERROR) << "Unable to persist assemble_cvd log at "
282 << config.AssemblyPath("assemble_cvd.log")
283 << ": " << log->StrError();
284 }
285 for (const auto& instance : config.Instances()) {
286 // Create instance directory if it doesn't exist.
287 CF_EXPECT(EnsureDirectoryExists(instance.instance_dir()));
288 auto internal_dir = instance.instance_dir() + "/" + kInternalDirName;
289 CF_EXPECT(EnsureDirectoryExists(internal_dir));
290 auto shared_dir = instance.instance_dir() + "/" + kSharedDirName;
291 CF_EXPECT(EnsureDirectoryExists(shared_dir));
292 auto recording_dir = instance.instance_dir() + "/recording";
293 CF_EXPECT(EnsureDirectoryExists(recording_dir));
294 CF_EXPECT(EnsureDirectoryExists(instance.PerInstanceLogPath("")));
295
296 CF_EXPECT(EnsureDirectoryExists(instance.instance_uds_dir(), defaultMode,
297 defaultGroup));
298 CF_EXPECT(EnsureDirectoryExists(instance.instance_internal_uds_dir(),
299 defaultMode, defaultGroup));
300 CF_EXPECT(EnsureDirectoryExists(instance.PerInstanceGrpcSocketPath(""),
301 defaultMode, defaultGroup));
302
303 // TODO(schuffelen): Move this code somewhere better
304 CF_EXPECT(CreateLegacySymlinks(instance));
305 }
306 CF_EXPECT(SaveConfig(config), "Failed to initialize configuration");
307 }
308
309 // Do this early so that the config object is ready for anything that needs
310 // it
311 auto config = CuttlefishConfig::Get();
312 CF_EXPECT(config != nullptr, "Failed to obtain config singleton");
313
314 if (DirectoryExists(FLAGS_assembly_dir, /* follow_symlinks */ false)) {
315 CF_EXPECT(RecursivelyRemoveDirectory(FLAGS_assembly_dir),
316 "Failed to remove directory " << FLAGS_assembly_dir);
317 } else if (FileExists(FLAGS_assembly_dir, /* follow_symlinks */ false)) {
318 CF_EXPECT(RemoveFile(FLAGS_assembly_dir),
319 "Failed to remove file" << FLAGS_assembly_dir);
320 }
321 if (symlink(config->assembly_dir().c_str(),
322 FLAGS_assembly_dir.c_str())) {
323 return CF_ERRNO("symlink(\"" << config->assembly_dir() << "\", \""
324 << FLAGS_assembly_dir << "\") failed");
325 }
326
327 std::string first_instance = config->Instances()[0].instance_dir();
328 std::string double_legacy_instance_dir = FLAGS_instance_dir + "_runtime";
329 if (FileExists(double_legacy_instance_dir, /* follow_symlinks */ false)) {
330 CF_EXPECT(RemoveFile(double_legacy_instance_dir),
331 "Failed to remove symlink " << double_legacy_instance_dir);
332 }
333 if (symlink(first_instance.c_str(), double_legacy_instance_dir.c_str())) {
334 return CF_ERRNO("symlink(\"" << first_instance << "\", \""
335 << double_legacy_instance_dir
336 << "\") failed");
337 }
338
339 CF_EXPECT(CreateDynamicDiskFiles(fetcher_config, *config));
340
341 return config;
342 }
343
344 const std::string kKernelDefaultPath = "kernel";
345 const std::string kInitramfsImg = "initramfs.img";
ExtractKernelParamsFromFetcherConfig(const FetcherConfig & fetcher_config)346 static void ExtractKernelParamsFromFetcherConfig(
347 const FetcherConfig& fetcher_config) {
348 std::string discovered_kernel =
349 fetcher_config.FindCvdFileWithSuffix(kKernelDefaultPath);
350 std::string discovered_ramdisk =
351 fetcher_config.FindCvdFileWithSuffix(kInitramfsImg);
352
353 SetCommandLineOptionWithMode("kernel_path", discovered_kernel.c_str(),
354 google::FlagSettingMode::SET_FLAGS_DEFAULT);
355
356 SetCommandLineOptionWithMode("initramfs_path", discovered_ramdisk.c_str(),
357 google::FlagSettingMode::SET_FLAGS_DEFAULT);
358 }
359
FlagsComponent()360 fruit::Component<> FlagsComponent() {
361 return fruit::createComponent()
362 .install(AdbConfigComponent)
363 .install(AdbConfigFlagComponent)
364 .install(AdbConfigFragmentComponent)
365 .install(FastbootConfigComponent)
366 .install(FastbootConfigFlagComponent)
367 .install(FastbootConfigFragmentComponent)
368 .install(GflagsComponent)
369 .install(ConfigFlagComponent)
370 .install(CustomActionsComponent);
371 }
372
373 } // namespace
374
AssembleCvdMain(int argc,char ** argv)375 Result<int> AssembleCvdMain(int argc, char** argv) {
376 setenv("ANDROID_LOG_TAGS", "*:v", /* overwrite */ 0);
377 ::android::base::InitLogging(argv, android::base::StderrLogger);
378
379 int tty = isatty(0);
380 int error_num = errno;
381 CF_EXPECT(tty == 0,
382 "stdin was a tty, expected to be passed the output of a "
383 "previous stage. Did you mean to run launch_cvd?");
384 CF_EXPECT(error_num != EBADF,
385 "stdin was not a valid file descriptor, expected to be "
386 "passed the output of launch_cvd. Did you mean to run launch_cvd?");
387
388 std::string input_files_str;
389 {
390 auto input_fd = SharedFD::Dup(0);
391 auto bytes_read = ReadAll(input_fd, &input_files_str);
392 CF_EXPECT(bytes_read >= 0, "Failed to read input files. Error was \""
393 << input_fd->StrError() << "\"");
394 }
395 std::vector<std::string> input_files = android::base::Split(input_files_str, "\n");
396
397 LocatedFetcherConfig located_fetcher_config = FindFetcherConfig(input_files);
398 if (located_fetcher_config.working_dir) {
399 LOG(INFO) << "Changing current working dircetory to '"
400 << *located_fetcher_config.working_dir << "'";
401 CF_EXPECT(chdir((*located_fetcher_config.working_dir).c_str()) == 0,
402 "Unable to change working dir to '"
403 << *located_fetcher_config.working_dir
404 << "': " << strerror(errno));
405 }
406
407 // set gflags defaults to point to kernel/RD from fetcher config
408 ExtractKernelParamsFromFetcherConfig(located_fetcher_config.fetcher_config);
409
410 auto args = ArgsToVec(argc - 1, argv + 1);
411
412 bool help = false;
413 std::string help_str;
414 bool helpxml = false;
415
416 std::vector<Flag> help_flags = {
417 GflagsCompatFlag("help", help),
418 GflagsCompatFlag("helpfull", help),
419 GflagsCompatFlag("helpshort", help),
420 GflagsCompatFlag("helpmatch", help_str),
421 GflagsCompatFlag("helpon", help_str),
422 GflagsCompatFlag("helppackage", help_str),
423 GflagsCompatFlag("helpxml", helpxml),
424 };
425 for (const auto& help_flag : help_flags) {
426 if (!help_flag.Parse(args)) {
427 LOG(ERROR) << "Failed to process help flag.";
428 return 1;
429 }
430 }
431
432 fruit::Injector<> injector(FlagsComponent);
433
434 for (auto& late_injected : injector.getMultibindings<LateInjected>()) {
435 CF_EXPECT(late_injected->LateInject(injector));
436 }
437
438 auto flag_features = injector.getMultibindings<FlagFeature>();
439 CF_EXPECT(FlagFeature::ProcessFlags(flag_features, args),
440 "Failed to parse flags.");
441
442 if (help || help_str != "") {
443 LOG(WARNING) << "TODO(schuffelen): Implement `--help` for assemble_cvd.";
444 LOG(WARNING) << "In the meantime, call `launch_cvd --help`";
445 return 1;
446 } else if (helpxml) {
447 if (!FlagFeature::WriteGflagsHelpXml(flag_features, std::cout)) {
448 LOG(ERROR) << "Failure in writing gflags helpxml output";
449 }
450 std::exit(1); // For parity with gflags
451 }
452 // TODO(schuffelen): Put in "unknown flag" guards after gflags is removed.
453 // gflags either consumes all arguments that start with - or leaves all of
454 // them in place, and either errors out on unknown flags or accepts any flags.
455
456 auto guest_configs =
457 CF_EXPECT(GetGuestConfigAndSetDefaults(), "Failed to parse arguments");
458
459 auto config = CF_EXPECT(InitFilesystemAndCreateConfig(
460 std::move(located_fetcher_config.fetcher_config),
461 guest_configs, injector),
462 "Failed to create config");
463
464 std::cout << GetConfigFilePath(*config) << "\n";
465 std::cout << std::flush;
466
467 return 0;
468 }
469
470 } // namespace cuttlefish
471
main(int argc,char ** argv)472 int main(int argc, char** argv) {
473 auto res = cuttlefish::AssembleCvdMain(argc, argv);
474 if (res.ok()) {
475 return *res;
476 }
477 LOG(ERROR) << "assemble_cvd failed: \n" << res.error().Message();
478 LOG(DEBUG) << "assemble_cvd failed: \n" << res.error().Trace();
479 abort();
480 }
481