• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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