/* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "host/commands/assemble_cvd/disk_flags.h" #include #include #include #include #include #include #include "common/libs/fs/shared_buf.h" #include "common/libs/utils/environment.h" #include "common/libs/utils/files.h" #include "common/libs/utils/size_utils.h" #include "common/libs/utils/subprocess.h" #include "host/commands/assemble_cvd/boot_config.h" #include "host/commands/assemble_cvd/boot_image_utils.h" #include "host/commands/assemble_cvd/disk_builder.h" #include "host/commands/assemble_cvd/super_image_mixer.h" #include "host/libs/config/bootconfig_args.h" #include "host/libs/config/cuttlefish_config.h" #include "host/libs/config/data_image.h" #include "host/libs/vm_manager/crosvm_manager.h" #include "host/libs/vm_manager/gem5_manager.h" // Taken from external/avb/libavb/avb_slot_verify.c; this define is not in the headers #define VBMETA_MAX_SIZE 65536ul // Taken from external/avb/avbtool.py; this define is not in the headers #define MAX_AVB_METADATA_SIZE 69632ul DECLARE_string(system_image_dir); DEFINE_string(boot_image, "", "Location of cuttlefish boot image. If empty it is assumed to be " "boot.img in the directory specified by -system_image_dir."); DEFINE_string( init_boot_image, "", "Location of cuttlefish init boot image. If empty it is assumed to " "be init_boot.img in the directory specified by -system_image_dir."); DEFINE_string(data_image, "", "Location of the data partition image."); DEFINE_string(super_image, "", "Location of the super partition image."); DEFINE_string(misc_image, "", "Location of the misc partition image. If the image does not " "exist, a blank new misc partition image is created."); DEFINE_string(metadata_image, "", "Location of the metadata partition image " "to be generated."); DEFINE_string(vendor_boot_image, "", "Location of cuttlefish vendor boot image. If empty it is assumed to " "be vendor_boot.img in the directory specified by -system_image_dir."); DEFINE_string(vbmeta_image, "", "Location of cuttlefish vbmeta image. If empty it is assumed to " "be vbmeta.img in the directory specified by -system_image_dir."); DEFINE_string(vbmeta_system_image, "", "Location of cuttlefish vbmeta_system image. If empty it is assumed to " "be vbmeta_system.img in the directory specified by -system_image_dir."); DEFINE_string(otheros_esp_image, "", "Location of cuttlefish esp image. If the image does not exist, " "and --otheros_root_image is specified, an esp partition image " "is created with default bootloaders."); DEFINE_string(otheros_kernel_path, "", "Location of cuttlefish otheros kernel."); DEFINE_string(otheros_initramfs_path, "", "Location of cuttlefish otheros initramfs.img."); DEFINE_string(otheros_root_image, "", "Location of cuttlefish otheros root filesystem image."); DEFINE_int32(blank_metadata_image_mb, 16, "The size of the blank metadata image to generate, MB."); DEFINE_int32(blank_sdcard_image_mb, 2048, "If enabled, the size of the blank sdcard image to generate, MB."); DECLARE_string(ap_rootfs_image); DECLARE_string(bootloader); DECLARE_bool(use_sdcard); DECLARE_string(initramfs_path); DECLARE_string(kernel_path); DECLARE_bool(resume); DECLARE_bool(protected_vm); namespace cuttlefish { using vm_manager::Gem5Manager; Result ResolveInstanceFiles() { CF_EXPECT(!FLAGS_system_image_dir.empty(), "--system_image_dir must be specified."); // If user did not specify location of either of these files, expect them to // be placed in --system_image_dir location. std::string default_boot_image = FLAGS_system_image_dir + "/boot.img"; SetCommandLineOptionWithMode("boot_image", default_boot_image.c_str(), google::FlagSettingMode::SET_FLAGS_DEFAULT); std::string default_init_boot_image = FLAGS_system_image_dir + "/init_boot.img"; SetCommandLineOptionWithMode("init_boot_image", default_init_boot_image.c_str(), google::FlagSettingMode::SET_FLAGS_DEFAULT); std::string default_data_image = FLAGS_system_image_dir + "/userdata.img"; SetCommandLineOptionWithMode("data_image", default_data_image.c_str(), google::FlagSettingMode::SET_FLAGS_DEFAULT); std::string default_metadata_image = FLAGS_system_image_dir + "/metadata.img"; SetCommandLineOptionWithMode("metadata_image", default_metadata_image.c_str(), google::FlagSettingMode::SET_FLAGS_DEFAULT); std::string default_super_image = FLAGS_system_image_dir + "/super.img"; SetCommandLineOptionWithMode("super_image", default_super_image.c_str(), google::FlagSettingMode::SET_FLAGS_DEFAULT); std::string default_misc_image = FLAGS_system_image_dir + "/misc.img"; SetCommandLineOptionWithMode("misc_image", default_misc_image.c_str(), google::FlagSettingMode::SET_FLAGS_DEFAULT); std::string default_esp_image = FLAGS_system_image_dir + "/esp.img"; SetCommandLineOptionWithMode("otheros_esp_image", default_esp_image.c_str(), google::FlagSettingMode::SET_FLAGS_DEFAULT); std::string default_vendor_boot_image = FLAGS_system_image_dir + "/vendor_boot.img"; SetCommandLineOptionWithMode("vendor_boot_image", default_vendor_boot_image.c_str(), google::FlagSettingMode::SET_FLAGS_DEFAULT); std::string default_vbmeta_image = FLAGS_system_image_dir + "/vbmeta.img"; SetCommandLineOptionWithMode("vbmeta_image", default_vbmeta_image.c_str(), google::FlagSettingMode::SET_FLAGS_DEFAULT); std::string default_vbmeta_system_image = FLAGS_system_image_dir + "/vbmeta_system.img"; SetCommandLineOptionWithMode("vbmeta_system_image", default_vbmeta_system_image.c_str(), google::FlagSettingMode::SET_FLAGS_DEFAULT); return {}; } std::vector GetOsCompositeDiskConfig() { std::vector partitions; partitions.push_back(ImagePartition{ .label = "misc", .image_file_path = AbsolutePath(FLAGS_misc_image), .read_only = true, }); partitions.push_back(ImagePartition{ .label = "boot_a", .image_file_path = AbsolutePath(FLAGS_boot_image), .read_only = true, }); partitions.push_back(ImagePartition{ .label = "boot_b", .image_file_path = AbsolutePath(FLAGS_boot_image), .read_only = true, }); partitions.push_back(ImagePartition{ .label = "init_boot_a", .image_file_path = AbsolutePath(FLAGS_init_boot_image), .read_only = true, }); partitions.push_back(ImagePartition{ .label = "init_boot_b", .image_file_path = AbsolutePath(FLAGS_init_boot_image), .read_only = true, }); partitions.push_back(ImagePartition{ .label = "vendor_boot_a", .image_file_path = AbsolutePath(FLAGS_vendor_boot_image), .read_only = true, }); partitions.push_back(ImagePartition{ .label = "vendor_boot_b", .image_file_path = AbsolutePath(FLAGS_vendor_boot_image), .read_only = true, }); partitions.push_back(ImagePartition{ .label = "vbmeta_a", .image_file_path = AbsolutePath(FLAGS_vbmeta_image), .read_only = true, }); partitions.push_back(ImagePartition{ .label = "vbmeta_b", .image_file_path = AbsolutePath(FLAGS_vbmeta_image), .read_only = true, }); partitions.push_back(ImagePartition{ .label = "vbmeta_system_a", .image_file_path = AbsolutePath(FLAGS_vbmeta_system_image), .read_only = true, }); partitions.push_back(ImagePartition{ .label = "vbmeta_system_b", .image_file_path = AbsolutePath(FLAGS_vbmeta_system_image), .read_only = true, }); partitions.push_back(ImagePartition{ .label = "super", .image_file_path = AbsolutePath(FLAGS_super_image), .read_only = true, }); partitions.push_back(ImagePartition{ .label = "userdata", .image_file_path = AbsolutePath(FLAGS_data_image), .read_only = true, }); partitions.push_back(ImagePartition{ .label = "metadata", .image_file_path = AbsolutePath(FLAGS_metadata_image), .read_only = true, }); if (!FLAGS_otheros_root_image.empty()) { partitions.push_back(ImagePartition{ .label = "otheros_esp", .image_file_path = AbsolutePath(FLAGS_otheros_esp_image), .type = kEfiSystemPartition, .read_only = true, }); partitions.push_back(ImagePartition{ .label = "otheros_root", .image_file_path = AbsolutePath(FLAGS_otheros_root_image), .read_only = true, }); } if (!FLAGS_ap_rootfs_image.empty()) { partitions.push_back(ImagePartition{ .label = "ap_rootfs", .image_file_path = AbsolutePath(FLAGS_ap_rootfs_image), .read_only = true, }); } return partitions; } DiskBuilder OsCompositeDiskBuilder(const CuttlefishConfig& config) { return DiskBuilder() .Partitions(GetOsCompositeDiskConfig()) .VmManager(config.vm_manager()) .CrosvmPath(config.crosvm_binary()) .ConfigPath(config.AssemblyPath("os_composite_disk_config.txt")) .HeaderPath(config.AssemblyPath("os_composite_gpt_header.img")) .FooterPath(config.AssemblyPath("os_composite_gpt_footer.img")) .CompositeDiskPath(config.os_composite_disk_path()) .ResumeIfPossible(FLAGS_resume); } std::vector persistent_composite_disk_config( const CuttlefishConfig& config, const CuttlefishConfig::InstanceSpecific& instance) { std::vector partitions; // Note that if the position of uboot_env changes, the environment for // u-boot must be updated as well (see boot_config.cc and // cuttlefish.fragment in external/u-boot). partitions.push_back(ImagePartition{ .label = "uboot_env", .image_file_path = AbsolutePath(instance.uboot_env_image_path()), }); partitions.push_back(ImagePartition{ .label = "vbmeta", .image_file_path = AbsolutePath(instance.vbmeta_path()), }); if (!FLAGS_protected_vm) { partitions.push_back(ImagePartition{ .label = "frp", .image_file_path = AbsolutePath(instance.factory_reset_protected_path()), }); } if (config.bootconfig_supported()) { partitions.push_back(ImagePartition{ .label = "bootconfig", .image_file_path = AbsolutePath(instance.persistent_bootconfig_path()), }); } return partitions; } static uint64_t AvailableSpaceAtPath(const std::string& path) { struct statvfs vfs; if (statvfs(path.c_str(), &vfs) != 0) { int error_num = errno; LOG(ERROR) << "Could not find space available at " << path << ", error was " << strerror(error_num); return 0; } // f_frsize (block size) * f_bavail (free blocks) for unprivileged users. return static_cast(vfs.f_frsize) * vfs.f_bavail; } class BootImageRepacker : public SetupFeature { public: INJECT(BootImageRepacker(const CuttlefishConfig& config)) : config_(config) {} // SetupFeature std::string Name() const override { return "BootImageRepacker"; } std::unordered_set Dependencies() const override { return {}; } bool Enabled() const override { // If we are booting a protected VM, for now, assume that image repacking // isn't trusted. Repacking requires resigning the image and keys from an // android host aren't trusted. return !config_.protected_vm(); } protected: bool Setup() override { if (!FileHasContent(FLAGS_boot_image)) { LOG(ERROR) << "File not found: " << FLAGS_boot_image; return false; } // The init_boot partition is be optional for testing boot.img // with the ramdisk inside. if (!FileHasContent(FLAGS_init_boot_image)) { LOG(WARNING) << "File not found: " << FLAGS_init_boot_image; } if (!FileHasContent(FLAGS_vendor_boot_image)) { LOG(ERROR) << "File not found: " << FLAGS_vendor_boot_image; return false; } // Repacking a boot.img doesn't work with Gem5 because the user must always // specify a vmlinux instead of an arm64 Image, and that file can be too // large to be repacked. Skip repack of boot.img on Gem5, as we need to be // able to extract the ramdisk.img in a later stage and so this step must // not fail (..and the repacked kernel wouldn't be used anyway). if (FLAGS_kernel_path.size() && config_.vm_manager() != Gem5Manager::name()) { const std::string new_boot_image_path = config_.AssemblyPath("boot_repacked.img"); bool success = RepackBootImage(FLAGS_kernel_path, FLAGS_boot_image, new_boot_image_path, config_.assembly_dir()); if (!success) { LOG(ERROR) << "Failed to regenerate the boot image with the new kernel"; return false; } SetCommandLineOptionWithMode("boot_image", new_boot_image_path.c_str(), google::FlagSettingMode::SET_FLAGS_DEFAULT); } if (FLAGS_kernel_path.size() || FLAGS_initramfs_path.size()) { const std::string new_vendor_boot_image_path = config_.AssemblyPath("vendor_boot_repacked.img"); // Repack the vendor boot images if kernels and/or ramdisks are passed in. if (FLAGS_initramfs_path.size()) { bool success = RepackVendorBootImage( FLAGS_initramfs_path, FLAGS_vendor_boot_image, new_vendor_boot_image_path, config_.assembly_dir(), config_.bootconfig_supported()); if (!success) { LOG(ERROR) << "Failed to regenerate the vendor boot image with the " "new ramdisk"; } else { // This control flow implies a kernel with all configs built in. // If it's just the kernel, repack the vendor boot image without a // ramdisk. bool success = RepackVendorBootImageWithEmptyRamdisk( FLAGS_vendor_boot_image, new_vendor_boot_image_path, config_.assembly_dir(), config_.bootconfig_supported()); if (!success) { LOG(ERROR) << "Failed to regenerate the vendor boot image without " "a ramdisk"; return false; } } SetCommandLineOptionWithMode( "vendor_boot_image", new_vendor_boot_image_path.c_str(), google::FlagSettingMode::SET_FLAGS_DEFAULT); } } return true; } private: const CuttlefishConfig& config_; }; class Gem5ImageUnpacker : public SetupFeature { public: INJECT(Gem5ImageUnpacker( const CuttlefishConfig& config, BootImageRepacker& bir)) : config_(config), bir_(bir) {} // SetupFeature std::string Name() const override { return "Gem5ImageUnpacker"; } std::unordered_set Dependencies() const override { return { static_cast(&bir_), }; } bool Enabled() const override { // Everything has a bootloader except gem5, so only run this for gem5 return config_.vm_manager() == Gem5Manager::name(); } protected: bool Setup() override { /* Unpack the original or repacked boot and vendor boot ramdisks, so that * we have access to the baked bootconfig and raw compressed ramdisks. * This allows us to emulate what a bootloader would normally do, which * Gem5 can't support itself. This code also copies the kernel again * (because Gem5 only supports raw vmlinux) and handles the bootloader * binaries specially. This code is just part of the solution; it only * does the parts which are instance agnostic. */ if (!FileHasContent(FLAGS_boot_image)) { LOG(ERROR) << "File not found: " << FLAGS_boot_image; return false; } // The init_boot partition is be optional for testing boot.img // with the ramdisk inside. if (!FileHasContent(FLAGS_init_boot_image)) { LOG(WARNING) << "File not found: " << FLAGS_init_boot_image; } if (!FileHasContent(FLAGS_vendor_boot_image)) { LOG(ERROR) << "File not found: " << FLAGS_vendor_boot_image; return false; } const std::string unpack_dir = config_.assembly_dir(); bool success = UnpackBootImage(FLAGS_init_boot_image, unpack_dir); if (!success) { LOG(ERROR) << "Failed to extract the init boot image"; return false; } success = UnpackVendorBootImageIfNotUnpacked(FLAGS_vendor_boot_image, unpack_dir); if (!success) { LOG(ERROR) << "Failed to extract the vendor boot image"; return false; } // Assume the user specified a kernel manually which is a vmlinux std::ofstream kernel(unpack_dir + "/kernel", std::ios_base::binary | std::ios_base::trunc); std::ifstream vmlinux(FLAGS_kernel_path, std::ios_base::binary); kernel << vmlinux.rdbuf(); kernel.close(); // Gem5 needs the bootloader binary to be a specific directory structure // to find it. Create a 'binaries' directory and copy it into there const std::string binaries_dir = unpack_dir + "/binaries"; if (mkdir(binaries_dir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) < 0 && errno != EEXIST) { PLOG(ERROR) << "Failed to create dir: \"" << binaries_dir << "\" "; return false; } std::ofstream bootloader(binaries_dir + "/" + cpp_basename(FLAGS_bootloader), std::ios_base::binary | std::ios_base::trunc); std::ifstream src_bootloader(FLAGS_bootloader, std::ios_base::binary); bootloader << src_bootloader.rdbuf(); bootloader.close(); // Gem5 also needs the ARM version of the bootloader, even though it // doesn't use it. It'll even open it to check it's a valid ELF file. // Work around this by copying such a named file from the same directory std::ofstream boot_arm(binaries_dir + "/boot.arm", std::ios_base::binary | std::ios_base::trunc); std::ifstream src_boot_arm(cpp_dirname(FLAGS_bootloader) + "/boot.arm", std::ios_base::binary); boot_arm << src_boot_arm.rdbuf(); boot_arm.close(); return true; } private: const CuttlefishConfig& config_; BootImageRepacker& bir_; }; class GeneratePersistentBootconfig : public SetupFeature { public: INJECT(GeneratePersistentBootconfig( const CuttlefishConfig& config, const CuttlefishConfig::InstanceSpecific& instance)) : config_(config), instance_(instance) {} // SetupFeature std::string Name() const override { return "GeneratePersistentBootconfig"; } bool Enabled() const override { return (!config_.protected_vm()); } private: std::unordered_set Dependencies() const override { return {}; } bool Setup() override { // Cuttlefish for the time being won't be able to support OTA from a // non-bootconfig kernel to a bootconfig-kernel (or vice versa) IF the // device is stopped (via stop_cvd). This is rarely an issue since OTA // testing run on cuttlefish is done within one launch cycle of the device. // If this ever becomes an issue, this code will have to be rewritten. if(!config_.bootconfig_supported()) { return true; } const auto bootconfig_path = instance_.persistent_bootconfig_path(); if (!FileExists(bootconfig_path)) { if (!CreateBlankImage(bootconfig_path, 1 /* mb */, "none")) { LOG(ERROR) << "Failed to create image at " << bootconfig_path; return false; } } auto bootconfig_fd = SharedFD::Open(bootconfig_path, O_RDWR); if (!bootconfig_fd->IsOpen()) { LOG(ERROR) << "Unable to open bootconfig file: " << bootconfig_fd->StrError(); return false; } const std::string bootconfig = android::base::Join(BootconfigArgsFromConfig(config_, instance_), "\n") + "\n"; ssize_t bytesWritten = WriteAll(bootconfig_fd, bootconfig); LOG(DEBUG) << "bootconfig size is " << bytesWritten; if (bytesWritten != bootconfig.size()) { LOG(ERROR) << "Failed to write contents of bootconfig to \"" << bootconfig_path << "\""; return false; } LOG(DEBUG) << "Bootconfig parameters from vendor boot image and config are " << ReadFile(bootconfig_path); if (bootconfig_fd->Truncate(bytesWritten) != 0) { LOG(ERROR) << "`truncate --size=" << bytesWritten << " bytes " << bootconfig_path << "` failed:" << bootconfig_fd->StrError(); return false; } if (config_.vm_manager() != Gem5Manager::name()) { bootconfig_fd->Close(); const off_t bootconfig_size_bytes = AlignToPowerOf2( MAX_AVB_METADATA_SIZE + bytesWritten, PARTITION_SIZE_SHIFT); auto avbtool_path = HostBinaryPath("avbtool"); Command bootconfig_hash_footer_cmd(avbtool_path); bootconfig_hash_footer_cmd.AddParameter("add_hash_footer"); bootconfig_hash_footer_cmd.AddParameter("--image"); bootconfig_hash_footer_cmd.AddParameter(bootconfig_path); bootconfig_hash_footer_cmd.AddParameter("--partition_size"); bootconfig_hash_footer_cmd.AddParameter(bootconfig_size_bytes); bootconfig_hash_footer_cmd.AddParameter("--partition_name"); bootconfig_hash_footer_cmd.AddParameter("bootconfig"); bootconfig_hash_footer_cmd.AddParameter("--key"); bootconfig_hash_footer_cmd.AddParameter( DefaultHostArtifactsPath("etc/cvd_avb_testkey.pem")); bootconfig_hash_footer_cmd.AddParameter("--algorithm"); bootconfig_hash_footer_cmd.AddParameter("SHA256_RSA4096"); int success = bootconfig_hash_footer_cmd.Start().Wait(); if (success != 0) { LOG(ERROR) << "Unable to run append hash footer. Exited with status " << success; return false; } } else { const off_t bootconfig_size_bytes_gem5 = AlignToPowerOf2( bytesWritten, PARTITION_SIZE_SHIFT); bootconfig_fd->Truncate(bootconfig_size_bytes_gem5); bootconfig_fd->Close(); } return true; } const CuttlefishConfig& config_; const CuttlefishConfig::InstanceSpecific& instance_; }; class GeneratePersistentVbmeta : public SetupFeature { public: INJECT(GeneratePersistentVbmeta( const CuttlefishConfig& config, const CuttlefishConfig::InstanceSpecific& instance, InitBootloaderEnvPartition& bootloader_env, GeneratePersistentBootconfig& bootconfig)) : config_(config), instance_(instance), bootloader_env_(bootloader_env), bootconfig_(bootconfig) {} // SetupFeature std::string Name() const override { return "GeneratePersistentVbmeta"; } bool Enabled() const override { return (!config_.protected_vm()); } private: std::unordered_set Dependencies() const override { return { static_cast(&bootloader_env_), static_cast(&bootconfig_), }; } bool Setup() override { auto avbtool_path = HostBinaryPath("avbtool"); Command vbmeta_cmd(avbtool_path); vbmeta_cmd.AddParameter("make_vbmeta_image"); vbmeta_cmd.AddParameter("--output"); vbmeta_cmd.AddParameter(instance_.vbmeta_path()); vbmeta_cmd.AddParameter("--algorithm"); vbmeta_cmd.AddParameter("SHA256_RSA4096"); vbmeta_cmd.AddParameter("--key"); vbmeta_cmd.AddParameter( DefaultHostArtifactsPath("etc/cvd_avb_testkey.pem")); vbmeta_cmd.AddParameter("--chain_partition"); vbmeta_cmd.AddParameter("uboot_env:1:" + DefaultHostArtifactsPath("etc/cvd.avbpubkey")); if (config_.bootconfig_supported()) { vbmeta_cmd.AddParameter("--chain_partition"); vbmeta_cmd.AddParameter("bootconfig:2:" + DefaultHostArtifactsPath("etc/cvd.avbpubkey")); } bool success = vbmeta_cmd.Start().Wait(); if (success != 0) { LOG(ERROR) << "Unable to create persistent vbmeta. Exited with status " << success; return false; } if (FileSize(instance_.vbmeta_path()) > VBMETA_MAX_SIZE) { LOG(ERROR) << "Generated vbmeta - " << instance_.vbmeta_path() << " is larger than the expected " << VBMETA_MAX_SIZE << ". Stopping."; return false; } if (FileSize(instance_.vbmeta_path()) != VBMETA_MAX_SIZE) { auto fd = SharedFD::Open(instance_.vbmeta_path(), O_RDWR); if (!fd->IsOpen() || fd->Truncate(VBMETA_MAX_SIZE) != 0) { LOG(ERROR) << "`truncate --size=" << VBMETA_MAX_SIZE << " " << instance_.vbmeta_path() << "` " << "failed: " << fd->StrError(); return false; } } return true; } const CuttlefishConfig& config_; const CuttlefishConfig::InstanceSpecific& instance_; InitBootloaderEnvPartition& bootloader_env_; GeneratePersistentBootconfig& bootconfig_; }; class InitializeMetadataImage : public SetupFeature { public: INJECT(InitializeMetadataImage()) {} // SetupFeature std::string Name() const override { return "InitializeMetadataImage"; } bool Enabled() const override { return true; } private: std::unordered_set Dependencies() const override { return {}; } Result ResultSetup() override { if (FileExists(FLAGS_metadata_image)) { return {}; } CF_EXPECT(CreateBlankImage(FLAGS_metadata_image, FLAGS_blank_metadata_image_mb, "none"), "Failed to create \"" << FLAGS_metadata_image << "\" with size " << FLAGS_blank_metadata_image_mb); return {}; } }; class InitializeAccessKregistryImage : public SetupFeature { public: INJECT(InitializeAccessKregistryImage( const CuttlefishConfig& config, const CuttlefishConfig::InstanceSpecific& instance)) : config_(config), instance_(instance) {} // SetupFeature std::string Name() const override { return "InitializeAccessKregistryImage"; } bool Enabled() const override { return !config_.protected_vm(); } private: std::unordered_set Dependencies() const override { return {}; } Result ResultSetup() override { auto access_kregistry = instance_.access_kregistry_path(); if (FileExists(access_kregistry)) { return {}; } CF_EXPECT(CreateBlankImage(access_kregistry, 2 /* mb */, "none"), "Failed to create \"" << access_kregistry << "\""); return {}; } const CuttlefishConfig& config_; const CuttlefishConfig::InstanceSpecific& instance_; }; class InitializeHwcomposerPmemImage : public SetupFeature { public: INJECT(InitializeHwcomposerPmemImage( const CuttlefishConfig& config, const CuttlefishConfig::InstanceSpecific& instance)) : config_(config), instance_(instance) {} // SetupFeature std::string Name() const override { return "InitializeHwcomposerPmemImage"; } bool Enabled() const override { return !config_.protected_vm(); } private: std::unordered_set Dependencies() const override { return {}; } Result ResultSetup() override { if (FileExists(instance_.hwcomposer_pmem_path())) { return {}; } CF_EXPECT( CreateBlankImage(instance_.hwcomposer_pmem_path(), 2 /* mb */, "none"), "Failed creating \"" << instance_.hwcomposer_pmem_path() << "\""); return {}; } const CuttlefishConfig& config_; const CuttlefishConfig::InstanceSpecific& instance_; }; class InitializePstore : public SetupFeature { public: INJECT(InitializePstore(const CuttlefishConfig& config, const CuttlefishConfig::InstanceSpecific& instance)) : config_(config), instance_(instance) {} // SetupFeature std::string Name() const override { return "InitializePstore"; } bool Enabled() const override { return !config_.protected_vm(); } private: std::unordered_set Dependencies() const override { return {}; } Result ResultSetup() override { if (FileExists(instance_.pstore_path())) { return {}; } CF_EXPECT(CreateBlankImage(instance_.pstore_path(), 2 /* mb */, "none"), "Failed to create \"" << instance_.pstore_path() << "\""); return {}; } const CuttlefishConfig& config_; const CuttlefishConfig::InstanceSpecific& instance_; }; class InitializeSdCard : public SetupFeature { public: INJECT(InitializeSdCard(const CuttlefishConfig& config, const CuttlefishConfig::InstanceSpecific& instance)) : config_(config), instance_(instance) {} // SetupFeature std::string Name() const override { return "InitializeSdCard"; } bool Enabled() const override { return FLAGS_use_sdcard && !config_.protected_vm(); } private: std::unordered_set Dependencies() const override { return {}; } Result ResultSetup() override { if (FileExists(instance_.sdcard_path())) { return {}; } CF_EXPECT(CreateBlankImage(instance_.sdcard_path(), FLAGS_blank_sdcard_image_mb, "sdcard"), "Failed to create \"" << instance_.sdcard_path() << "\""); return {}; } const CuttlefishConfig& config_; const CuttlefishConfig::InstanceSpecific& instance_; }; class InitializeFactoryResetProtected : public SetupFeature { public: INJECT(InitializeFactoryResetProtected( const CuttlefishConfig& config, const CuttlefishConfig::InstanceSpecific& instance)) : config_(config), instance_(instance) {} // SetupFeature std::string Name() const override { return "InitializeSdCard"; } bool Enabled() const override { return !config_.protected_vm(); } private: std::unordered_set Dependencies() const override { return {}; } Result ResultSetup() override { auto frp = instance_.factory_reset_protected_path(); if (FileExists(frp)) { return {}; } CF_EXPECT(CreateBlankImage(frp, 1 /* mb */, "none"), "Failed to create \"" << frp << "\""); return {}; } const CuttlefishConfig& config_; const CuttlefishConfig::InstanceSpecific& instance_; }; class InitializeInstanceCompositeDisk : public SetupFeature { public: INJECT(InitializeInstanceCompositeDisk( const CuttlefishConfig& config, const CuttlefishConfig::InstanceSpecific& instance, InitializeFactoryResetProtected& frp, GeneratePersistentVbmeta& vbmeta)) : config_(config), instance_(instance), frp_(frp), vbmeta_(vbmeta) {} std::string Name() const override { return "InitializeInstanceCompositeDisk"; } bool Enabled() const override { return true; } private: std::unordered_set Dependencies() const override { return { static_cast(&frp_), static_cast(&vbmeta_), }; } Result ResultSetup() override { auto ipath = [this](const std::string& path) -> std::string { return instance_.PerInstancePath(path.c_str()); }; auto persistent_disk_builder = DiskBuilder() .Partitions(persistent_composite_disk_config(config_, instance_)) .VmManager(config_.vm_manager()) .CrosvmPath(config_.crosvm_binary()) .ConfigPath(ipath("persistent_composite_disk_config.txt")) .HeaderPath(ipath("persistent_composite_gpt_header.img")) .FooterPath(ipath("persistent_composite_gpt_footer.img")) .CompositeDiskPath(instance_.persistent_composite_disk_path()) .ResumeIfPossible(FLAGS_resume); CF_EXPECT(persistent_disk_builder.BuildCompositeDiskIfNecessary()); return {}; } const CuttlefishConfig& config_; const CuttlefishConfig::InstanceSpecific& instance_; InitializeFactoryResetProtected& frp_; GeneratePersistentVbmeta& vbmeta_; }; class VbmetaEnforceMinimumSize : public SetupFeature { public: INJECT(VbmetaEnforceMinimumSize()) {} std::string Name() const override { return "VbmetaEnforceMinimumSize"; } bool Enabled() const override { return true; } private: std::unordered_set Dependencies() const override { return {}; } Result ResultSetup() override { // libavb expects to be able to read the maximum vbmeta size, so we must // provide a partition which matches this or the read will fail for (const auto& vbmeta_image : {FLAGS_vbmeta_image, FLAGS_vbmeta_system_image}) { if (FileSize(vbmeta_image) != VBMETA_MAX_SIZE) { auto fd = SharedFD::Open(vbmeta_image, O_RDWR); CF_EXPECT(fd->IsOpen(), "Could not open \"" << vbmeta_image << "\": " << fd->StrError()); CF_EXPECT(fd->Truncate(VBMETA_MAX_SIZE) == 0, "`truncate --size=" << VBMETA_MAX_SIZE << " " << vbmeta_image << "` failed: " << fd->StrError()); } } return {}; } }; class BootloaderPresentCheck : public SetupFeature { public: INJECT(BootloaderPresentCheck()) {} std::string Name() const override { return "BootloaderPresentCheck"; } bool Enabled() const override { return true; } private: std::unordered_set Dependencies() const override { return {}; } Result ResultSetup() override { CF_EXPECT(FileHasContent(FLAGS_bootloader), "File not found: " << FLAGS_bootloader); return {}; } }; static fruit::Component<> DiskChangesComponent(const FetcherConfig* fetcher, const CuttlefishConfig* config) { return fruit::createComponent() .bindInstance(*fetcher) .bindInstance(*config) .addMultibinding() .addMultibinding() .addMultibinding() .addMultibinding() .addMultibinding() .install(FixedMiscImagePathComponent, &FLAGS_misc_image) .install(InitializeMiscImageComponent) .install(FixedDataImagePathComponent, &FLAGS_data_image) .install(InitializeDataImageComponent) // Create esp if necessary .install(InitializeEspImageComponent, &FLAGS_otheros_esp_image, &FLAGS_otheros_kernel_path, &FLAGS_otheros_initramfs_path, &FLAGS_otheros_root_image, config) .install(SuperImageRebuilderComponent, &FLAGS_super_image); } static fruit::Component<> DiskChangesPerInstanceComponent( const FetcherConfig* fetcher, const CuttlefishConfig* config, const CuttlefishConfig::InstanceSpecific* instance) { return fruit::createComponent() .bindInstance(*fetcher) .bindInstance(*config) .bindInstance(*instance) .addMultibinding() .addMultibinding() .addMultibinding() .addMultibinding() .addMultibinding() .addMultibinding() .addMultibinding() .addMultibinding() .install(InitBootloaderEnvPartitionComponent); } Result CreateDynamicDiskFiles(const FetcherConfig& fetcher_config, const CuttlefishConfig& config) { // TODO(schuffelen): Unify this with the other injector created in // assemble_cvd.cpp fruit::Injector<> injector(DiskChangesComponent, &fetcher_config, &config); const auto& features = injector.getMultibindings(); CF_EXPECT(SetupFeature::RunSetup(features)); for (const auto& instance : config.Instances()) { fruit::Injector<> instance_injector(DiskChangesPerInstanceComponent, &fetcher_config, &config, &instance); const auto& instance_features = instance_injector.getMultibindings(); CF_EXPECT(SetupFeature::RunSetup(instance_features), "instance = \"" << instance.instance_name() << "\""); } // Check if filling in the sparse image would run out of disk space. auto existing_sizes = SparseFileSizes(FLAGS_data_image); CF_EXPECT(existing_sizes.sparse_size > 0 || existing_sizes.disk_size > 0, "Unable to determine size of \"" << FLAGS_data_image << "\". Does this file exist?"); auto available_space = AvailableSpaceAtPath(FLAGS_data_image); if (available_space < existing_sizes.sparse_size - existing_sizes.disk_size) { // TODO(schuffelen): Duplicate this check in run_cvd when it can run on a // separate machine return CF_ERR("Not enough space remaining in fs containing \"" << FLAGS_data_image << "\", wanted " << (existing_sizes.sparse_size - existing_sizes.disk_size) << ", got " << available_space); } else { LOG(DEBUG) << "Available space: " << available_space; LOG(DEBUG) << "Sparse size of \"" << FLAGS_data_image << "\": " << existing_sizes.sparse_size; LOG(DEBUG) << "Disk size of \"" << FLAGS_data_image << "\": " << existing_sizes.disk_size; } auto os_disk_builder = OsCompositeDiskBuilder(config); auto built_composite = CF_EXPECT(os_disk_builder.BuildCompositeDiskIfNecessary()); if (built_composite) { for (auto instance : config.Instances()) { if (FileExists(instance.access_kregistry_path())) { CF_EXPECT(CreateBlankImage(instance.access_kregistry_path(), 2 /* mb */, "none"), "Failed for \"" << instance.access_kregistry_path() << "\""); } if (FileExists(instance.hwcomposer_pmem_path())) { CF_EXPECT(CreateBlankImage(instance.hwcomposer_pmem_path(), 2 /* mb */, "none"), "Failed for \"" << instance.hwcomposer_pmem_path() << "\""); } if (FileExists(instance.pstore_path())) { CF_EXPECT(CreateBlankImage(instance.pstore_path(), 2 /* mb */, "none"), "Failed for\"" << instance.pstore_path() << "\""); } } } if (!FLAGS_protected_vm) { for (auto instance : config.Instances()) { os_disk_builder.OverlayPath(instance.PerInstancePath("overlay.img")); CF_EXPECT(os_disk_builder.BuildOverlayIfNecessary()); if (instance.start_ap()) { os_disk_builder.OverlayPath(instance.PerInstancePath("ap_overlay.img")); CF_EXPECT(os_disk_builder.BuildOverlayIfNecessary()); } } } for (auto instance : config.Instances()) { // Check that the files exist for (const auto& file : instance.virtual_disk_paths()) { if (!file.empty()) { CF_EXPECT(FileHasContent(file), "File not found: \"" << file << "\""); } } // Gem5 Simulate per-instance what the bootloader would usually do // Since on other devices this runs every time, just do it here every time if (config.vm_manager() == Gem5Manager::name()) { RepackGem5BootImage( instance.PerInstancePath("initrd.img"), instance.persistent_bootconfig_path(), config.assembly_dir()); } } return {}; } } // namespace cuttlefish