/* * 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/libs/config/data_image.h" #include #include #include "blkid.h" #include "common/libs/fs/shared_buf.h" #include "common/libs/utils/files.h" #include "common/libs/utils/network.h" #include "common/libs/utils/result.h" #include "common/libs/utils/subprocess.h" #include "host/libs/config/esp.h" #include "host/libs/config/mbr.h" #include "host/libs/config/openwrt_args.h" #include "host/libs/vm_manager/gem5_manager.h" namespace cuttlefish { namespace { const std::string kDataPolicyUseExisting = "use_existing"; const std::string kDataPolicyCreateIfMissing = "create_if_missing"; const std::string kDataPolicyAlwaysCreate = "always_create"; const std::string kDataPolicyResizeUpTo= "resize_up_to"; const int FSCK_ERROR_CORRECTED = 1; const int FSCK_ERROR_CORRECTED_REQUIRES_REBOOT = 2; bool ForceFsckImage(const std::string& data_image, const CuttlefishConfig::InstanceSpecific& instance) { std::string fsck_path; if (instance.userdata_format() == "f2fs") { fsck_path = HostBinaryPath("fsck.f2fs"); } else if (instance.userdata_format() == "ext4") { fsck_path = "/sbin/e2fsck"; } int fsck_status = execute({fsck_path, "-y", "-f", data_image}); if (fsck_status & ~(FSCK_ERROR_CORRECTED|FSCK_ERROR_CORRECTED_REQUIRES_REBOOT)) { LOG(ERROR) << "`" << fsck_path << " -y -f " << data_image << "` failed with code " << fsck_status; return false; } return true; } bool ResizeImage(const std::string& data_image, int data_image_mb, const CuttlefishConfig::InstanceSpecific& instance) { auto file_mb = FileSize(data_image) >> 20; if (file_mb > data_image_mb) { LOG(ERROR) << data_image << " is already " << file_mb << " MB, will not " << "resize down."; return false; } else if (file_mb == data_image_mb) { LOG(INFO) << data_image << " is already the right size"; return true; } else { off_t raw_target = static_cast(data_image_mb) << 20; auto fd = SharedFD::Open(data_image, O_RDWR); if (fd->Truncate(raw_target) != 0) { LOG(ERROR) << "`truncate --size=" << data_image_mb << "M " << data_image << "` failed:" << fd->StrError(); return false; } bool fsck_success = ForceFsckImage(data_image, instance); if (!fsck_success) { return false; } std::string resize_path; if (instance.userdata_format() == "f2fs") { resize_path = HostBinaryPath("resize.f2fs"); } else if (instance.userdata_format() == "ext4") { resize_path = "/sbin/resize2fs"; } int resize_status = execute({resize_path, data_image}); if (resize_status != 0) { LOG(ERROR) << "`" << resize_path << " " << data_image << "` failed with code " << resize_status; return false; } fsck_success = ForceFsckImage(data_image, instance); if (!fsck_success) { return false; } } return true; } } // namespace bool CreateBlankImage( const std::string& image, int num_mb, const std::string& image_fmt) { LOG(DEBUG) << "Creating " << image; off_t image_size_bytes = static_cast(num_mb) << 20; // The newfs_msdos tool with the mandatory -C option will do the same // as below to zero the image file, so we don't need to do it here if (image_fmt != "sdcard") { auto fd = SharedFD::Open(image, O_CREAT | O_TRUNC | O_RDWR, 0666); if (fd->Truncate(image_size_bytes) != 0) { LOG(ERROR) << "`truncate --size=" << num_mb << "M " << image << "` failed:" << fd->StrError(); return false; } } if (image_fmt == "ext4") { if (execute({"/sbin/mkfs.ext4", image}) != 0) { return false; } } else if (image_fmt == "f2fs") { auto make_f2fs_path = HostBinaryPath("make_f2fs"); if (execute({make_f2fs_path, "-l", "data", image, "-C", "utf8", "-O", "compression,extra_attr,project_quota,casefold", "-g", "android"}) != 0) { return false; } } else if (image_fmt == "sdcard") { // Reserve 1MB in the image for the MBR and padding, to simulate what // other OSes do by default when partitioning a drive off_t offset_size_bytes = 1 << 20; image_size_bytes -= offset_size_bytes; if (!NewfsMsdos(image, num_mb, 1)) { LOG(ERROR) << "Failed to create SD-Card filesystem"; return false; } // Write the MBR after the filesystem is formatted, as the formatting tools // don't consistently preserve the image contents MasterBootRecord mbr = { .partitions = {{ .partition_type = 0xC, .first_lba = (std::uint32_t) offset_size_bytes / SECTOR_SIZE, .num_sectors = (std::uint32_t) image_size_bytes / SECTOR_SIZE, }}, .boot_signature = {0x55, 0xAA}, }; auto fd = SharedFD::Open(image, O_RDWR); if (WriteAllBinary(fd, &mbr) != sizeof(MasterBootRecord)) { LOG(ERROR) << "Writing MBR to " << image << " failed:" << fd->StrError(); return false; } } else if (image_fmt != "none") { LOG(WARNING) << "Unknown image format '" << image_fmt << "' for " << image << ", treating as 'none'."; } return true; } std::string GetFsType(const std::string& path) { std::string fs_type; blkid_cache cache; if (blkid_get_cache(&cache, NULL) < 0) { LOG(INFO) << "blkid_get_cache failed"; return fs_type; } blkid_dev dev = blkid_get_dev(cache, path.c_str(), BLKID_DEV_NORMAL); if (!dev) { LOG(INFO) << "blkid_get_dev failed"; blkid_put_cache(cache); return fs_type; } const char *type, *value; blkid_tag_iterate iter = blkid_tag_iterate_begin(dev); while (blkid_tag_next(iter, &type, &value) == 0) { if (!strcmp(type, "TYPE")) { fs_type = value; } } blkid_tag_iterate_end(iter); blkid_put_cache(cache); return fs_type; } class InitializeDataImageImpl : public InitializeDataImage { public: INJECT(InitializeDataImageImpl( const CuttlefishConfig::InstanceSpecific& instance)) : instance_(instance) {} // SetupFeature std::string Name() const override { return "InitializeDataImageImpl"; } bool Enabled() const override { return true; } private: std::unordered_set Dependencies() const override { return {}; } bool Setup() override { auto action = ChooseAction(); if (!action.ok()) { LOG(ERROR) << "Failed to select a userdata processing action: " << action.error().Message(); LOG(DEBUG) << "Failed to select a userdata processing action: " << action.error().Trace(); return false; } auto result = EvaluateAction(*action); if (!result.ok()) { LOG(ERROR) << "Failed to evaluate userdata action: " << result.error().Message(); LOG(DEBUG) << "Failed to evaluate userdata action: " << result.error().Trace(); return false; } return true; } private: enum class DataImageAction { kNoAction, kCreateImage, kResizeImage }; Result ChooseAction() { if (instance_.data_policy() == kDataPolicyAlwaysCreate) { return DataImageAction::kCreateImage; } if (!FileHasContent(instance_.data_image())) { if (instance_.data_policy() == kDataPolicyUseExisting) { return CF_ERR("A data image must exist to use -data_policy=" << kDataPolicyUseExisting); } else if (instance_.data_policy() == kDataPolicyResizeUpTo) { return CF_ERR(instance_.data_image() << " does not exist, but resizing was requested"); } return DataImageAction::kCreateImage; } if (instance_.data_policy() == kDataPolicyUseExisting) { return DataImageAction::kNoAction; } auto current_fs_type = GetFsType(instance_.data_image()); if (current_fs_type != instance_.userdata_format()) { CF_EXPECT(instance_.data_policy() != kDataPolicyResizeUpTo, "Changing the fs format is incompatible with -data_policy=" << kDataPolicyResizeUpTo << " (\"" << current_fs_type << "\" != \"" << instance_.userdata_format() << "\")"); return DataImageAction::kCreateImage; } if (instance_.data_policy() == kDataPolicyResizeUpTo) { return DataImageAction::kResizeImage; } return DataImageAction::kNoAction; } Result EvaluateAction(DataImageAction action) { switch (action) { case DataImageAction::kNoAction: LOG(DEBUG) << instance_.data_image() << " exists. Not creating it."; return {}; case DataImageAction::kCreateImage: { RemoveFile(instance_.data_image()); CF_EXPECT(instance_.blank_data_image_mb() != 0, "Expected `-blank_data_image_mb` to be set for " "image creation."); CF_EXPECT(CreateBlankImage(instance_.data_image(), instance_.blank_data_image_mb(), instance_.userdata_format()), "Failed to create a blank image at \"" << instance_.data_image() << "\" with size " << instance_.blank_data_image_mb() << " and format \"" << instance_.userdata_format() << "\""); return {}; } case DataImageAction::kResizeImage: { CF_EXPECT(instance_.blank_data_image_mb() != 0, "Expected `-blank_data_image_mb` to be set for " "image resizing."); CF_EXPECT(ResizeImage(instance_.data_image(), instance_.blank_data_image_mb(), instance_), "Failed to resize \"" << instance_.data_image() << "\" to " << instance_.blank_data_image_mb() << " MB"); return {}; } } } const CuttlefishConfig::InstanceSpecific& instance_; }; fruit::Component, InitializeDataImage> InitializeDataImageComponent() { return fruit::createComponent() .addMultibinding() .bind(); } class InitializeMiscImageImpl : public InitializeMiscImage { public: INJECT(InitializeMiscImageImpl( const CuttlefishConfig::InstanceSpecific& instance)) : instance_(instance) {} // SetupFeature std::string Name() const override { return "InitializeMiscImageImpl"; } bool Enabled() const override { return true; } private: std::unordered_set Dependencies() const override { return {}; } bool Setup() override { bool misc_exists = FileHasContent(instance_.misc_image()); if (misc_exists) { LOG(DEBUG) << "misc partition image: use existing at \"" << instance_.misc_image() << "\""; return true; } LOG(DEBUG) << "misc partition image: creating empty at \"" << instance_.misc_image() << "\""; if (!CreateBlankImage(instance_.new_misc_image(), 1 /* mb */, "none")) { LOG(ERROR) << "Failed to create misc image"; return false; } return true; } private: const CuttlefishConfig::InstanceSpecific& instance_; }; fruit::Component, InitializeMiscImage> InitializeMiscImageComponent() { return fruit::createComponent() .addMultibinding() .bind(); } class InitializeEspImageImpl : public InitializeEspImage { public: INJECT(InitializeEspImageImpl( const CuttlefishConfig& config, const CuttlefishConfig::InstanceSpecific& instance)) : config_(config), instance_(instance) {} // SetupFeature std::string Name() const override { return "InitializeEspImageImpl"; } std::unordered_set Dependencies() const override { return {}; } bool Enabled() const override { return EspRequiredForBootFlow() || EspRequiredForAPBootFlow(); } protected: bool Setup() override { if (EspRequiredForAPBootFlow()) { LOG(DEBUG) << "creating esp_image: " << instance_.ap_esp_image_path(); if (!BuildAPImage()) { return false; } } const auto is_not_gem5 = config_.vm_manager() != vm_manager::Gem5Manager::name(); const auto esp_required_for_boot_flow = EspRequiredForBootFlow(); if (is_not_gem5 && esp_required_for_boot_flow) { LOG(DEBUG) << "creating esp_image: " << instance_.otheros_esp_image_path(); if (!BuildOSImage()) { return false; } } return true; } private: bool EspRequiredForBootFlow() const { const auto flow = instance_.boot_flow(); return flow == CuttlefishConfig::InstanceSpecific::BootFlow::Linux || flow == CuttlefishConfig::InstanceSpecific::BootFlow::Fuchsia; } bool EspRequiredForAPBootFlow() const { return instance_.ap_boot_flow() == CuttlefishConfig::InstanceSpecific::APBootFlow::Grub; } bool BuildAPImage() { auto linux = LinuxEspBuilder(instance_.ap_esp_image_path()); InitLinuxArgs(linux); auto openwrt_args = OpenwrtArgsFromConfig(instance_); for (auto& openwrt_arg : openwrt_args) { linux.Argument(openwrt_arg.first, openwrt_arg.second); } linux.Root("/dev/vda2") .Architecture(instance_.target_arch()) .Kernel(config_.ap_kernel_image()); return linux.Build(); } bool BuildOSImage() { switch (instance_.boot_flow()) { case CuttlefishConfig::InstanceSpecific::BootFlow::Linux: { auto linux = LinuxEspBuilder(instance_.otheros_esp_image_path()); InitLinuxArgs(linux); linux.Root("/dev/vda2") .Architecture(instance_.target_arch()) .Kernel(instance_.linux_kernel_path()); if (!instance_.linux_initramfs_path().empty()) { linux.Initrd(instance_.linux_initramfs_path()); } return linux.Build(); } case CuttlefishConfig::InstanceSpecific::BootFlow::Fuchsia: { auto fuchsia = FuchsiaEspBuilder(instance_.otheros_esp_image_path()); return fuchsia.Architecture(instance_.target_arch()) .Zedboot(instance_.fuchsia_zedboot_path()) .MultibootBinary(instance_.fuchsia_multiboot_bin_path()) .Build(); } default: break; } return true; } void InitLinuxArgs(LinuxEspBuilder& linux) { linux.Root("/dev/vda2"); linux.Argument("console", "hvc0") .Argument("panic", "-1") .Argument("noefi"); switch (instance_.target_arch()) { case Arch::Arm: case Arch::Arm64: linux.Argument("console", "ttyAMA0"); break; case Arch::RiscV64: linux.Argument("console", "ttyS0"); break; case Arch::X86: case Arch::X86_64: linux.Argument("console", "ttyS0") .Argument("pnpacpi", "off") .Argument("acpi", "noirq") .Argument("reboot", "k") .Argument("noexec", "off"); break; } } const CuttlefishConfig& config_; const CuttlefishConfig::InstanceSpecific& instance_; }; fruit::Component, InitializeEspImage> InitializeEspImageComponent() { return fruit::createComponent() .addMultibinding() .bind(); } } // namespace cuttlefish