blob: 45dbad8457d7375d03f07e2391dceb75a12a296a [file] [log] [blame] [edit]
/*
* 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 <android-base/logging.h>
#include <android-base/result.h>
#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<off_t>(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<off_t>(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<SetupFeature*> 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<DataImageAction> 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<void> 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<fruit::Required<const CuttlefishConfig::InstanceSpecific>,
InitializeDataImage>
InitializeDataImageComponent() {
return fruit::createComponent()
.addMultibinding<SetupFeature, InitializeDataImage>()
.bind<InitializeDataImage, InitializeDataImageImpl>();
}
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<SetupFeature*> 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<fruit::Required<const CuttlefishConfig::InstanceSpecific>,
InitializeMiscImage>
InitializeMiscImageComponent() {
return fruit::createComponent()
.addMultibinding<SetupFeature, InitializeMiscImage>()
.bind<InitializeMiscImage, InitializeMiscImageImpl>();
}
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<SetupFeature*> 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<fruit::Required<const CuttlefishConfig,
const CuttlefishConfig::InstanceSpecific>,
InitializeEspImage>
InitializeEspImageComponent() {
return fruit::createComponent()
.addMultibinding<SetupFeature, InitializeEspImage>()
.bind<InitializeEspImage, InitializeEspImageImpl>();
}
} // namespace cuttlefish