/* * 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 "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/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/image_aggregator/image_aggregator.h" #include "host/libs/vm_manager/crosvm_manager.h" // Taken from external/avb/libavb/avb_slot_verify.c; this define is not in the headers #define VBMETA_MAX_SIZE 65536ul DEFINE_string(system_image_dir, cuttlefish::DefaultGuestImagePath(""), "Location of the system partition images."); 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(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(esp, "", "Path to ESP partition image (FAT formatted)"); 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(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::CrosvmManager; bool ResolveInstanceFiles() { if (FLAGS_system_image_dir.empty()) { LOG(ERROR) << "--system_image_dir must be specified."; return false; } // 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_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_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 true; } std::vector os_composite_disk_config( const CuttlefishConfig::InstanceSpecific& instance) { std::vector partitions; partitions.push_back(ImagePartition { .label = "misc", .image_file_path = FLAGS_misc_image, }); if (!FLAGS_esp.empty()) { partitions.push_back(ImagePartition { .label = "esp", .image_file_path = FLAGS_esp, .type = kEfiSystemPartition, }); } partitions.push_back(ImagePartition { .label = "boot_a", .image_file_path = FLAGS_boot_image, }); partitions.push_back(ImagePartition { .label = "boot_b", .image_file_path = FLAGS_boot_image, }); // Boot image repacking is not supported on protected VMs. Repacking requires // resigning the image and keys on android hosts aren't trusted. if (!FLAGS_protected_vm) { partitions.push_back(ImagePartition{ .label = "vendor_boot_a", .image_file_path = instance.vendor_boot_image_path(), }); partitions.push_back(ImagePartition{ .label = "vendor_boot_b", .image_file_path = instance.vendor_boot_image_path(), }); } else { partitions.push_back(ImagePartition{ .label = "vendor_boot_a", .image_file_path = FLAGS_vendor_boot_image, }); partitions.push_back(ImagePartition{ .label = "vendor_boot_b", .image_file_path = FLAGS_vendor_boot_image, }); } partitions.push_back(ImagePartition { .label = "vbmeta_a", .image_file_path = FLAGS_vbmeta_image, }); partitions.push_back(ImagePartition { .label = "vbmeta_b", .image_file_path = FLAGS_vbmeta_image, }); partitions.push_back(ImagePartition { .label = "vbmeta_system_a", .image_file_path = FLAGS_vbmeta_system_image, }); partitions.push_back(ImagePartition { .label = "vbmeta_system_b", .image_file_path = FLAGS_vbmeta_system_image, }); partitions.push_back(ImagePartition { .label = "super", .image_file_path = FLAGS_super_image, }); partitions.push_back(ImagePartition { .label = "userdata", .image_file_path = FLAGS_data_image, }); partitions.push_back(ImagePartition { .label = "metadata", .image_file_path = FLAGS_metadata_image, }); return partitions; } std::vector persistent_composite_disk_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 = instance.uboot_env_image_path(), }); if (!FLAGS_protected_vm) { partitions.push_back(ImagePartition{ .label = "frp", .image_file_path = instance.factory_reset_protected_path(), }); } partitions.push_back(ImagePartition{ .label = "bootconfig", .image_file_path = instance.persistent_bootconfig_path(), }); return partitions; } static std::chrono::system_clock::time_point LastUpdatedInputDisk( const std::vector& partitions) { std::chrono::system_clock::time_point ret; for (auto& partition : partitions) { if (partition.label == "frp") { continue; } auto partition_mod_time = FileModificationTime(partition.image_file_path); if (partition_mod_time > ret) { ret = partition_mod_time; } } return ret; } bool ShouldCreateAllCompositeDisks(const CuttlefishConfig& config) { std::chrono::system_clock::time_point youngest_disk_img; for (auto& partition : os_composite_disk_config(config.ForDefaultInstance())) { auto partition_mod_time = FileModificationTime(partition.image_file_path); if (partition_mod_time > youngest_disk_img) { youngest_disk_img = partition_mod_time; } } // If the youngest partition img is younger than any composite disk, this fact implies that // the composite disks are all out of date and need to be reinitialized. for (auto& instance : config.Instances()) { if (!FileExists(instance.os_composite_disk_path())) { continue; } if (youngest_disk_img > FileModificationTime(instance.os_composite_disk_path())) { return true; } } return false; } bool DoesCompositeMatchCurrentDiskConfig( const std::string& prior_disk_config_path, const std::vector& partitions) { std::string current_disk_config_path = prior_disk_config_path + ".tmp"; std::ostringstream disk_conf; for (auto& partition : partitions) { disk_conf << partition.image_file_path << "\n"; } { // This file acts as a descriptor of the cuttlefish disk contents in a VMM agnostic way (VMMs // used are QEMU and CrosVM at the time of writing). This file is used to determine if the // disk config for the pending boot matches the disk from the past boot. std::ofstream file_out(current_disk_config_path.c_str(), std::ios::binary); file_out << disk_conf.str(); CHECK(file_out.good()) << "Disk config verification failed."; } if (!FileExists(prior_disk_config_path) || ReadFile(prior_disk_config_path) != ReadFile(current_disk_config_path)) { CHECK(cuttlefish::RenameFile(current_disk_config_path, prior_disk_config_path)) << "Unable to delete the old disk config descriptor"; LOG(DEBUG) << "Disk Config has changed since last boot. Regenerating composite disk."; return false; } else { RemoveFile(current_disk_config_path); return true; } } bool ShouldCreateCompositeDisk(const std::string& composite_disk_path, const std::vector& partitions) { if (!FileExists(composite_disk_path)) { return true; } auto composite_age = FileModificationTime(composite_disk_path); return composite_age < LastUpdatedInputDisk(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; } bool CreateCompositeDisk(const CuttlefishConfig& config, const CuttlefishConfig::InstanceSpecific& instance) { if (!SharedFD::Open(instance.os_composite_disk_path().c_str(), O_WRONLY | O_CREAT, 0644) ->IsOpen()) { LOG(ERROR) << "Could not ensure " << instance.os_composite_disk_path() << " exists"; return false; } if (config.vm_manager() == CrosvmManager::name()) { // Check if filling in the sparse image would run out of disk space. auto existing_sizes = SparseFileSizes(FLAGS_data_image); if (existing_sizes.sparse_size == 0 && existing_sizes.disk_size == 0) { LOG(ERROR) << "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 LOG(ERROR) << "Not enough space remaining in fs containing " << FLAGS_data_image; LOG(ERROR) << "Wanted " << (existing_sizes.sparse_size - existing_sizes.disk_size); LOG(ERROR) << "Got " << available_space; return false; } 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; } std::string header_path = instance.PerInstancePath("os_composite_gpt_header.img"); std::string footer_path = instance.PerInstancePath("os_composite_gpt_footer.img"); CreateCompositeDisk(os_composite_disk_config(instance), header_path, footer_path, instance.os_composite_disk_path()); } else { // If this doesn't fit into the disk, it will fail while aggregating. The // aggregator doesn't maintain any sparse attributes. AggregateImage(os_composite_disk_config(instance), instance.os_composite_disk_path()); } return true; } bool CreatePersistentCompositeDisk( const CuttlefishConfig& config, const CuttlefishConfig::InstanceSpecific& instance) { if (!SharedFD::Open(instance.persistent_composite_disk_path().c_str(), O_WRONLY | O_CREAT, 0644) ->IsOpen()) { LOG(ERROR) << "Could not ensure " << instance.persistent_composite_disk_path() << " exists"; return false; } if (config.vm_manager() == CrosvmManager::name()) { std::string header_path = instance.PerInstancePath("persistent_composite_gpt_header.img"); std::string footer_path = instance.PerInstancePath("persistent_composite_gpt_footer.img"); CreateCompositeDisk(persistent_composite_disk_config(instance), header_path, footer_path, instance.persistent_composite_disk_path()); } else { AggregateImage(persistent_composite_disk_config(instance), instance.persistent_composite_disk_path()); } return true; } static void RepackAllBootImages(const CuttlefishConfig* config) { CHECK(FileHasContent(FLAGS_boot_image)) << "File not found: " << FLAGS_boot_image; CHECK(FileHasContent(FLAGS_vendor_boot_image)) << "File not found: " << FLAGS_vendor_boot_image; if (FLAGS_kernel_path.size()) { 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()); CHECK(success) << "Failed to regenerate the boot image with the new kernel"; SetCommandLineOptionWithMode("boot_image", new_boot_image_path.c_str(), google::FlagSettingMode::SET_FLAGS_DEFAULT); } for (auto instance : config->Instances()) { const std::string new_vendor_boot_image_path = instance.vendor_boot_image_path(); const std::vector boot_config_vector = BootconfigArgsFromConfig(*config, instance); if (FLAGS_kernel_path.size() || FLAGS_initramfs_path.size()) { // 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(), instance.instance_dir(), boot_config_vector, config->bootconfig_supported()); CHECK(success) << "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(), instance.instance_dir(), boot_config_vector, config->bootconfig_supported()); CHECK(success) << "Failed to regenerate the vendor boot image without a ramdisk"; } } else { // Repack the vendor boot image to add the instance specific bootconfig // parameters bool success = RepackVendorBootImage( std::string(), FLAGS_vendor_boot_image, new_vendor_boot_image_path, config->assembly_dir(), instance.instance_dir(), boot_config_vector, config->bootconfig_supported()); CHECK(success) << "Failed to regenerate the vendor boot image"; } } } void CreateDynamicDiskFiles(const FetcherConfig& fetcher_config, const CuttlefishConfig* config) { // Create misc if necessary CHECK(InitializeMiscImage(FLAGS_misc_image)) << "Failed to create misc image"; // Create data if necessary DataImageResult dataImageResult = ApplyDataImagePolicy(*config, FLAGS_data_image); CHECK(dataImageResult != DataImageResult::Error) << "Failed to set up userdata"; if (!FileExists(FLAGS_metadata_image)) { CreateBlankImage(FLAGS_metadata_image, FLAGS_blank_metadata_image_mb, "none"); } // If we are booting a protected VM, for now, assume we want a super minimal // environment with no userdata encryption, limited debug, no FRP emulation, a // static env for the bootloader, no SD-Card and no resume-on-reboot HAL // support. We can also assume that image repacking isn't trusted. Repacking // requires resigning the image and keys from an android host aren't trusted. if (!FLAGS_protected_vm) { RepackAllBootImages(config); for (const auto& instance : config->Instances()) { if (!FileExists(instance.access_kregistry_path())) { CreateBlankImage(instance.access_kregistry_path(), 2 /* mb */, "none"); } if (!FileExists(instance.pstore_path())) { CreateBlankImage(instance.pstore_path(), 2 /* mb */, "none"); } if (FLAGS_use_sdcard && !FileExists(instance.sdcard_path())) { CreateBlankImage(instance.sdcard_path(), FLAGS_blank_sdcard_image_mb, "sdcard"); } CHECK(InitBootloaderEnvPartition(*config, instance)) << "Failed to create bootloader environment partition"; const auto frp = instance.factory_reset_protected_path(); if (!FileExists(frp)) { CreateBlankImage(frp, 1 /* mb */, "none"); } const auto bootconfig_path = instance.persistent_bootconfig_path(); if (!FileExists(bootconfig_path)) { CreateBlankImage(bootconfig_path, 1 /* mb */, "none"); } auto bootconfig_fd = SharedFD::Open(bootconfig_path, O_RDWR); CHECK(bootconfig_fd->IsOpen()) << "Unable to open bootconfig file: " << bootconfig_fd->StrError(); const std::string bootconfig = android::base::Join(BootconfigArgsFromConfig(*config, instance), "\n") + "\n"; ssize_t bytesWritten = WriteAll(bootconfig_fd, bootconfig); CHECK(bytesWritten == bootconfig.size()); LOG(DEBUG) << "Bootconfig parameters from vendor boot image and config are " << ReadFile(bootconfig_path); const off_t bootconfig_size_bytes = AlignToPowerOf2(bootconfig.size(), PARTITION_SIZE_SHIFT); CHECK(bootconfig_fd->Truncate(bootconfig_size_bytes) == 0) << "`truncate --size=" << bootconfig_size_bytes << " bytes " << bootconfig_path << "` failed:" << bootconfig_fd->StrError(); } } for (const auto& instance : config->Instances()) { bool compositeMatchesDiskConfig = DoesCompositeMatchCurrentDiskConfig( instance.PerInstancePath("persistent_composite_disk_config.txt"), persistent_composite_disk_config(instance)); bool oldCompositeDisk = ShouldCreateCompositeDisk(instance.persistent_composite_disk_path(), persistent_composite_disk_config(instance)); if (!compositeMatchesDiskConfig || oldCompositeDisk) { CHECK(CreatePersistentCompositeDisk(*config, instance)) << "Failed to create persistent composite disk"; } } // 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); CHECK(fd->Truncate(VBMETA_MAX_SIZE) == 0) << "`truncate --size=" << VBMETA_MAX_SIZE << " " << vbmeta_image << "` " << "failed: " << fd->StrError(); } } CHECK(FileHasContent(FLAGS_bootloader)) << "File not found: " << FLAGS_bootloader; if (!FLAGS_esp.empty()) { CHECK(FileHasContent(FLAGS_esp)) << "File not found: " << FLAGS_esp; } if (SuperImageNeedsRebuilding(fetcher_config, *config)) { bool success = RebuildSuperImage(fetcher_config, *config, FLAGS_super_image); CHECK(success) << "Super image rebuilding requested but could not be completed."; } bool newDataImage = dataImageResult == DataImageResult::FileUpdated; for (auto instance : config->Instances()) { bool compositeMatchesDiskConfig = DoesCompositeMatchCurrentDiskConfig( instance.PerInstancePath("os_composite_disk_config.txt"), os_composite_disk_config(instance)); bool oldCompositeDisk = ShouldCreateCompositeDisk( instance.os_composite_disk_path(), os_composite_disk_config(instance)); if (!compositeMatchesDiskConfig || oldCompositeDisk || !FLAGS_resume || newDataImage) { if (FLAGS_resume) { LOG(INFO) << "Requested to continue an existing session, (the default) " << "but the disk files have become out of date. Wiping the " << "old session files and starting a new session for device " << instance.serial_number(); } CHECK(CreateCompositeDisk(*config, instance)) << "Failed to create composite disk"; if (FileExists(instance.access_kregistry_path())) { CreateBlankImage(instance.access_kregistry_path(), 2 /* mb */, "none"); } if (FileExists(instance.pstore_path())) { CreateBlankImage(instance.pstore_path(), 2 /* mb */, "none"); } } } if (!FLAGS_protected_vm) { for (auto instance : config->Instances()) { auto overlay_path = instance.PerInstancePath("overlay.img"); bool missingOverlay = !FileExists(overlay_path); bool newOverlay = FileModificationTime(overlay_path) < FileModificationTime(instance.os_composite_disk_path()); if (missingOverlay || !FLAGS_resume || newOverlay) { CreateQcowOverlay(config->crosvm_binary(), instance.os_composite_disk_path(), overlay_path); } } } for (auto instance : config->Instances()) { // Check that the files exist for (const auto& file : instance.virtual_disk_paths()) { if (!file.empty()) { CHECK(FileHasContent(file)) << "File not found: " << file; } } } } } // namespace cuttlefish