From 7b36ab7fd41c12fb889c301fb1fd5ff3de8b4d2a Mon Sep 17 00:00:00 2001 From: "A. Cody Schuffelen" Date: Thu, 15 Jan 2026 15:29:32 -0800 Subject: [PATCH] New flag and behavior `--experimental_build_super_image` When this flag is passed to cvd_internal_start/assemble_cvd, it uses information mined from the `AndroidBuild` hierarchy to create a new `super.img` file that is represented as composite disk with indirections to logical partitions stored in separate files. Part of the implementation of `liblp` was copied into this project, because the public API did not provide the functionality we needed in order to generate the proper super image header in isolation, without the logical partition contents. The implementation is similar to the composite GPT disk generator, which uses indirections to provide data for the GPT physical partitions. Bug: b/476228593 --- .../android_build/physical_partitions.cc | 4 +- .../assemble_cvd/android_build/super_image.cc | 6 +- .../commands/assemble_cvd/disk/BUILD.bazel | 2 + .../disk/android_composite_disk_config.cc | 33 +- .../commands/assemble_cvd/flags/BUILD.bazel | 14 + .../assemble_cvd/flags/build_super_image.cc | 49 +++ .../assemble_cvd/flags/build_super_image.h | 35 ++ .../cuttlefish/host/commands/start/main.cc | 1 + .../host/libs/image_aggregator/BUILD.bazel | 21 ++ .../libs/image_aggregator/super_builder.cc | 336 ++++++++++++++++++ .../libs/image_aggregator/super_builder.h | 55 +++ 11 files changed, 550 insertions(+), 6 deletions(-) create mode 100644 base/cvd/cuttlefish/host/commands/assemble_cvd/flags/build_super_image.cc create mode 100644 base/cvd/cuttlefish/host/commands/assemble_cvd/flags/build_super_image.h create mode 100644 base/cvd/cuttlefish/host/libs/image_aggregator/super_builder.cc create mode 100644 base/cvd/cuttlefish/host/libs/image_aggregator/super_builder.h diff --git a/base/cvd/cuttlefish/host/commands/assemble_cvd/android_build/physical_partitions.cc b/base/cvd/cuttlefish/host/commands/assemble_cvd/android_build/physical_partitions.cc index 30c1c69e22b..7475048ba29 100644 --- a/base/cvd/cuttlefish/host/commands/assemble_cvd/android_build/physical_partitions.cc +++ b/base/cvd/cuttlefish/host/commands/assemble_cvd/android_build/physical_partitions.cc @@ -36,8 +36,8 @@ class PhysicalPartitionsImpl : public AndroidBuild { std::string Name() const override { return "PhysicalPartitions"; } PrettyStruct Pretty() override { - return PrettyStruct(Name()) - .Member("PhysicalPartitions()", PhysicalPartitions()); + return PrettyStruct(Name()).Member("PhysicalPartitions()", + PhysicalPartitions()); } Result>> PhysicalPartitions() override { diff --git a/base/cvd/cuttlefish/host/commands/assemble_cvd/android_build/super_image.cc b/base/cvd/cuttlefish/host/commands/assemble_cvd/android_build/super_image.cc index bc38b482690..265c4c49e17 100644 --- a/base/cvd/cuttlefish/host/commands/assemble_cvd/android_build/super_image.cc +++ b/base/cvd/cuttlefish/host/commands/assemble_cvd/android_build/super_image.cc @@ -62,7 +62,9 @@ Result> PartitionExtents( const android::fs_mgr::LpMetadata& metadata, std::string_view name) { for (const LpMetadataPartition& partition : metadata.partitions) { std::string partition_name = android::fs_mgr::GetPartitionName(partition); - if (name != WithoutSlotSuffix(partition_name)) { + bool exact_match = name == partition_name; + bool without_suffix_match = name == WithoutSlotSuffix(partition_name); + if (!(exact_match || without_suffix_match)) { continue; } std::vector extents; @@ -145,7 +147,7 @@ class SuperImageAsBuildImpl : public AndroidBuild { std::string extract_path = absl::StrCat(extract_dir_, "/", name, ".img"); unlink(extract_path.c_str()); // Ignore errors SharedFD extract_fd = - SharedFD::Open(extract_path, O_RDWR | O_CREAT | O_EXCL); + SharedFD::Open(extract_path, O_RDWR | O_CREAT | O_EXCL, 0644); CF_EXPECTF(extract_fd->IsOpen(), "Failed to open '{}': ", extract_path, extract_fd->StrError()); diff --git a/base/cvd/cuttlefish/host/commands/assemble_cvd/disk/BUILD.bazel b/base/cvd/cuttlefish/host/commands/assemble_cvd/disk/BUILD.bazel index d8365858a61..ca3cea3b7fc 100644 --- a/base/cvd/cuttlefish/host/commands/assemble_cvd/disk/BUILD.bazel +++ b/base/cvd/cuttlefish/host/commands/assemble_cvd/disk/BUILD.bazel @@ -25,9 +25,11 @@ cf_cc_library( "//cuttlefish/common/libs/utils:files", "//cuttlefish/host/commands/assemble_cvd/android_build", "//cuttlefish/host/commands/assemble_cvd/disk:image_file", + "//cuttlefish/host/commands/assemble_cvd/flags:build_super_image", "//cuttlefish/host/commands/assemble_cvd/flags:system_image_dir", "//cuttlefish/host/libs/config:cuttlefish_config", "//cuttlefish/host/libs/image_aggregator", + "//cuttlefish/host/libs/image_aggregator:super_builder", "//cuttlefish/result", "//libbase", "@abseil-cpp//absl/strings", diff --git a/base/cvd/cuttlefish/host/commands/assemble_cvd/disk/android_composite_disk_config.cc b/base/cvd/cuttlefish/host/commands/assemble_cvd/disk/android_composite_disk_config.cc index 3b3923564ae..2442dbae8e4 100644 --- a/base/cvd/cuttlefish/host/commands/assemble_cvd/disk/android_composite_disk_config.cc +++ b/base/cvd/cuttlefish/host/commands/assemble_cvd/disk/android_composite_disk_config.cc @@ -30,9 +30,11 @@ #include "cuttlefish/common/libs/utils/files.h" #include "cuttlefish/host/commands/assemble_cvd/android_build/android_build.h" +#include "cuttlefish/host/commands/assemble_cvd/flags/build_super_image.h" #include "cuttlefish/host/commands/assemble_cvd/flags/system_image_dir.h" #include "cuttlefish/host/libs/config/cuttlefish_config.h" #include "cuttlefish/host/libs/image_aggregator/image_aggregator.h" +#include "cuttlefish/host/libs/image_aggregator/super_builder.h" #include "cuttlefish/result/result.h" namespace cuttlefish { @@ -96,7 +98,6 @@ Result> AndroidCompositeDiskConfig( {std::string(kPartitions.init_boot), instance.init_boot_image()}, {std::string(kPartitions.metadata), ""}, {std::string(kPartitions.misc), ""}, - {std::string(kPartitions.super), instance.new_super_image()}, {std::string(kPartitions.userdata), instance.new_data_image()}, {std::string(kPartitions.vbmeta), instance.new_vbmeta_image()}, {std::string(kPartitions.vbmeta_system), instance.vbmeta_system_image()}, @@ -106,6 +107,7 @@ Result> AndroidCompositeDiskConfig( instance.new_vbmeta_vendor_dlkm_image()}, {std::string(kPartitions.vendor_boot), instance.new_vendor_boot_image()}, {std::string(kPartitions.vvmtruststore), instance.vvmtruststore_path()}, + {std::string(kPartitions.super), instance.new_super_image()}, }; for (std::string partition : CF_EXPECT(android_build.PhysicalPartitions())) { @@ -128,9 +130,36 @@ Result> AndroidCompositeDiskConfig( CF_EXPECTF(!!inserted, "Duplicate images for '{}'", image_file->Name()); } + const BuildSuperImageFlag build_super_image = + CF_EXPECT(BuildSuperImageFlag::FromGlobalGflags()); + for (const auto& [partition, path] : primary_paths) { std::string path_used; - if (auto it = dynamic_paths.find(partition); it != dynamic_paths.end()) { + if (partition == "super" && build_super_image.ForIndex(instance.index())) { + CompositeSuperImageBuilder super_builder; + + const std::string super_from_build = + CF_EXPECT(android_build.ImageFile("super")); + super_builder.BlockDeviceSize(FileSize(super_from_build)); + + const std::set> system_partitions = + CF_EXPECT(android_build.SystemPartitions()); + const std::set> vendor_partitions = + CF_EXPECT(android_build.VendorPartitions()); + for (const std::string& logical : + CF_EXPECT(android_build.LogicalPartitions())) { + const std::string path = CF_EXPECT(android_build.ImageFile(logical)); + if (system_partitions.count(logical)) { + super_builder.SystemPartition(logical, path); + } else { + super_builder.VendorPartition(logical, path); + } + } + // TODO: b/480197663: system_other should be written as system_b + path_used = CF_EXPECT(super_builder.WriteToDirectory( + instance.instance_dir(), "super_composite.img", "super_header.img")); + } else if (auto it = dynamic_paths.find(partition); + it != dynamic_paths.end()) { path_used = it->second; } else if (FileExists(path)) { path_used = path; diff --git a/base/cvd/cuttlefish/host/commands/assemble_cvd/flags/BUILD.bazel b/base/cvd/cuttlefish/host/commands/assemble_cvd/flags/BUILD.bazel index f71ba865e36..775e40ec025 100644 --- a/base/cvd/cuttlefish/host/commands/assemble_cvd/flags/BUILD.bazel +++ b/base/cvd/cuttlefish/host/commands/assemble_cvd/flags/BUILD.bazel @@ -83,6 +83,20 @@ cf_cc_library( ], ) +cf_cc_library( + name = "build_super_image", + srcs = ["build_super_image.cc"], + hdrs = ["build_super_image.h"], + deps = [ + "//cuttlefish/host/commands/assemble_cvd:flags_defaults", + "//cuttlefish/host/commands/assemble_cvd/flags:flag_base", + "//cuttlefish/host/commands/assemble_cvd/flags:from_gflags", + "//cuttlefish/result", + "//libbase", + "@gflags", + ], +) + cf_cc_library( name = "cpus", srcs = ["cpus.cc"], diff --git a/base/cvd/cuttlefish/host/commands/assemble_cvd/flags/build_super_image.cc b/base/cvd/cuttlefish/host/commands/assemble_cvd/flags/build_super_image.cc new file mode 100644 index 00000000000..9c2a98507a0 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/assemble_cvd/flags/build_super_image.cc @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2025 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 "cuttlefish/host/commands/assemble_cvd/flags/build_super_image.h" + +#include +#include + +#include + +#include "cuttlefish/host/commands/assemble_cvd/flags/flag_base.h" +#include "cuttlefish/host/commands/assemble_cvd/flags/from_gflags.h" +#include "cuttlefish/result/result.h" + +DEFINE_string( + experimental_build_super_image, "false", + "Build the super image at runtime. This is probably not what you want."); + +namespace cuttlefish { +namespace { + +constexpr char kFlagName[] = "experimental_build_super_image"; + +} // namespace + +Result BuildSuperImageFlag::FromGlobalGflags() { + const auto flag_info = gflags::GetCommandLineFlagInfoOrDie(kFlagName); + std::vector flag_values = + CF_EXPECT(BoolFromGlobalGflags(flag_info, kFlagName)); + return BuildSuperImageFlag(std::move(flag_values)); +} + +BuildSuperImageFlag::BuildSuperImageFlag(std::vector flag_values) + : FlagBase(std::move(flag_values)) {} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/assemble_cvd/flags/build_super_image.h b/base/cvd/cuttlefish/host/commands/assemble_cvd/flags/build_super_image.h new file mode 100644 index 00000000000..409ba359c17 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/assemble_cvd/flags/build_super_image.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2026 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. + */ + +#pragma once + +#include + +#include "cuttlefish/host/commands/assemble_cvd/flags/flag_base.h" +#include "cuttlefish/result/result.h" + +namespace cuttlefish { + +class BuildSuperImageFlag : public FlagBase { + public: + static Result FromGlobalGflags(); + ~BuildSuperImageFlag() override = default; + + private: + BuildSuperImageFlag(std::vector); +}; + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/start/main.cc b/base/cvd/cuttlefish/host/commands/start/main.cc index 5dd9a091db0..b8add0c276d 100644 --- a/base/cvd/cuttlefish/host/commands/start/main.cc +++ b/base/cvd/cuttlefish/host/commands/start/main.cc @@ -154,6 +154,7 @@ const std::unordered_set& BoolFlags() { "enable_sandbox", "enable_usb", "enable_virtiofs", + "experimental_build_super_image", "fail_fast", "guest_enforce_security", "kgdb", diff --git a/base/cvd/cuttlefish/host/libs/image_aggregator/BUILD.bazel b/base/cvd/cuttlefish/host/libs/image_aggregator/BUILD.bazel index 131cee24c78..68f36b43fab 100644 --- a/base/cvd/cuttlefish/host/libs/image_aggregator/BUILD.bazel +++ b/base/cvd/cuttlefish/host/libs/image_aggregator/BUILD.bazel @@ -133,6 +133,27 @@ cf_cc_library( ], ) +cf_cc_library( + name = "super_builder", + srcs = ["super_builder.cc"], + hdrs = ["super_builder.h"], + deps = [ + "//cuttlefish/common/libs/fs", + "//cuttlefish/host/libs/config:log_string_to_dir", + "//cuttlefish/host/libs/image_aggregator:cdisk_spec_cc_proto", + "//cuttlefish/host/libs/image_aggregator:composite_disk", + "//cuttlefish/host/libs/image_aggregator:disk_image", + "//cuttlefish/host/libs/image_aggregator:image_from_file", + "//cuttlefish/pretty:unique_ptr", + "//cuttlefish/pretty/liblp", + "//cuttlefish/result", + "@abseil-cpp//absl/log", + "@abseil-cpp//absl/strings", + "@android_system_core//:liblp", + "@boringssl//:crypto", + ], +) + cf_cc_library( name = "qcow2", srcs = ["qcow2.cc"], diff --git a/base/cvd/cuttlefish/host/libs/image_aggregator/super_builder.cc b/base/cvd/cuttlefish/host/libs/image_aggregator/super_builder.cc new file mode 100644 index 00000000000..1c1115d238c --- /dev/null +++ b/base/cvd/cuttlefish/host/libs/image_aggregator/super_builder.cc @@ -0,0 +1,336 @@ +/* + * Copyright (C) 2026 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 "cuttlefish/host/libs/image_aggregator/super_builder.h" + +#include + +#include + +#include +#include + +#include "absl/strings/str_cat.h" +#include "liblp/builder.h" +#include "liblp/metadata_format.h" +#include "liblp/partition_opener.h" + +#include "cuttlefish/common/libs/fs/shared_buf.h" +#include "cuttlefish/common/libs/fs/shared_fd.h" +#include "cuttlefish/host/libs/image_aggregator/cdisk_spec.pb.h" +#include "cuttlefish/host/libs/image_aggregator/composite_disk.h" +#include "cuttlefish/host/libs/image_aggregator/disk_image.h" +#include "cuttlefish/host/libs/image_aggregator/image_from_file.h" +#include "cuttlefish/result/result.h" + +#include "cuttlefish/host/libs/config/log_string_to_dir.h" +#include "cuttlefish/pretty/liblp/liblp.h" // IWYU pragma: keep: overloads +#include "cuttlefish/pretty/unique_ptr.h" + +namespace cuttlefish { +namespace { + +using android::fs_mgr::BlockDeviceInfo; +using android::fs_mgr::Interval; +using android::fs_mgr::LinearExtent; +using android::fs_mgr::LpMetadata; +using android::fs_mgr::MetadataBuilder; +using android::fs_mgr::Partition; + +// `SHA256, ``SerializeGeometry` and `SerializeMetadata` are copied from the +// liblp implementation. +// +// liblp only exposes two methods of producing the header, in different +// overloads of `WriteToImageFile`. One overload produces the `super_empty.img` +// file with a single copy of the geometry and metadata tables. The other +// overload produces the `super.img` file with two copies of each table, and the +// contents of all the logical partitions. +// +// Technically we lose a call to `CheckExtentOrdering`, but we create the +// extents in ascending order. +// +// We want a mixture of the behavior of both overloads: two copies of each +// table, but without the logical partition contents. We are instead providing +// the logical partition contents through the composite disk indirection. +// Therefore, we copy these internal methods to serialize the tables so we can +// construct the `super.img` version of the header by itself. + +void SHA256(const void* data, const size_t length, uint8_t out[32]) { + SHA256_CTX c; + SHA256_Init(&c); + SHA256_Update(&c, data, length); + SHA256_Final(out, &c); +} + +std::string SerializeGeometry(LpMetadataGeometry& geometry) { + memset(geometry.checksum, 0, sizeof(geometry.checksum)); + SHA256(&geometry, sizeof(geometry), geometry.checksum); + + std::string blob(reinterpret_cast(&geometry), sizeof(geometry)); + blob.resize(LP_METADATA_GEOMETRY_SIZE); + return blob; +} + +std::string SerializeMetadata(LpMetadata& metadata) { + LpMetadataHeader& header = metadata.header; + + // Serialize individual tables. + const std::string partitions( + reinterpret_cast(metadata.partitions.data()), + metadata.partitions.size() * sizeof(LpMetadataPartition)); + const std::string extents( + reinterpret_cast(metadata.extents.data()), + metadata.extents.size() * sizeof(LpMetadataExtent)); + const std::string groups( + reinterpret_cast(metadata.groups.data()), + metadata.groups.size() * sizeof(LpMetadataPartitionGroup)); + const std::string block_devices( + reinterpret_cast(metadata.block_devices.data()), + metadata.block_devices.size() * sizeof(LpMetadataBlockDevice)); + + // Compute positions of tables. + header.partitions.offset = 0; + header.extents.offset = header.partitions.offset + partitions.size(); + header.groups.offset = header.extents.offset + extents.size(); + header.block_devices.offset = header.groups.offset + groups.size(); + header.tables_size = header.block_devices.offset + block_devices.size(); + + // Compute payload checksum. + const std::string tables = + absl::StrCat(partitions, extents, groups, block_devices); + SHA256(tables.data(), tables.size(), header.tables_checksum); + + // Compute header checksum. + memset(header.header_checksum, 0, sizeof(header.header_checksum)); + SHA256(&header, header.header_size, header.header_checksum); + + const std::string_view header_blob(reinterpret_cast(&header), + header.header_size); + + std::string ret = absl::StrCat(header_blob, tables); + ret.resize(metadata.geometry.metadata_max_size); + return ret; +} + +BlockDeviceInfo DefaultBlockDeviceInfo(const uint64_t size) { + static constexpr uint32_t kAlignment = 4096; + static constexpr uint32_t kAlignmentOffset = 0; + static constexpr uint32_t kLogicalBlockSize = 4096; + + return BlockDeviceInfo(LP_METADATA_DEFAULT_PARTITION_NAME, size, kAlignment, + kAlignmentOffset, kLogicalBlockSize); +} + +Result> CreateMetadataBuilder(uint64_t size) { + static constexpr uint32_t kMetadataMaxSize = 256 * 1024; + static constexpr uint32_t kMetadataSlotCount = 2; + std::unique_ptr builder = MetadataBuilder::New( + {DefaultBlockDeviceInfo(size)}, LP_METADATA_DEFAULT_PARTITION_NAME, + kMetadataMaxSize, kMetadataSlotCount); + CF_EXPECT(builder.get()); + return builder; +} + +Result AddPartition(const std::string_view name, + const std::string_view host_path, + const std::string_view group_name, + const uint32_t attributes, + MetadataBuilder& metadata_builder) { + std::unique_ptr disk_image = + CF_EXPECT(ImageFromFile(std::string(host_path))); + const uint64_t partition_size = CF_EXPECT(disk_image->VirtualSizeBytes()); + + CF_EXPECT_EQ(partition_size % LP_SECTOR_SIZE, 0); + const uint64_t num_sectors = partition_size / LP_SECTOR_SIZE; + + std::optional chosen_interval; + for (const Interval& interval : metadata_builder.GetFreeRegions()) { + if (interval.length() > num_sectors) { + chosen_interval = interval; + break; + } + } + CF_EXPECT(chosen_interval.has_value()); + + const LinearExtent extent(num_sectors, chosen_interval->device_index, + chosen_interval->start); + + Partition* partition_a = CF_EXPECT(metadata_builder.AddPartition( + absl::StrCat(name, "_a"), absl::StrCat(group_name, "_a"), attributes)); + partition_a->AddExtent(std::make_unique(extent)); + partition_a->set_attributes(LP_PARTITION_ATTR_READONLY); + + Partition* partition_b = CF_EXPECT(metadata_builder.AddPartition( + absl::StrCat(name, "_b"), absl::StrCat(group_name, "_b"), attributes)); + partition_b->set_attributes(LP_PARTITION_ATTR_READONLY); + + ComponentDisk component; + component.set_file_path(host_path); + component.set_offset(chosen_interval->start * LP_SECTOR_SIZE); + component.set_read_write_capability(ReadWriteCapability::READ_WRITE); + + return component; +} + +Result ExpandedStorageSize(const std::string& file_path) { + std::unique_ptr disk = CF_EXPECT(ImageFromFile(file_path)); + CF_EXPECT(disk.get()); + return CF_EXPECT(disk->VirtualSizeBytes()); +} + +} // namespace + +CompositeSuperImageBuilder& CompositeSuperImageBuilder::BlockDeviceSize( + const uint64_t size) & { + size_ = size; + return *this; +} + +CompositeSuperImageBuilder CompositeSuperImageBuilder::BlockDeviceSize( + const uint64_t size) && { + BlockDeviceSize(size); + return *this; +} + +CompositeSuperImageBuilder& CompositeSuperImageBuilder::SystemPartition( + const std::string_view name, const std::string_view host_path) & { + system_partitions_.emplace(name, host_path); + return *this; +} + +CompositeSuperImageBuilder CompositeSuperImageBuilder::SystemPartition( + const std::string_view name, const std::string_view host_path) && { + SystemPartition(name, host_path); + return *this; +} + +CompositeSuperImageBuilder& CompositeSuperImageBuilder::VendorPartition( + const std::string_view name, const std::string_view host_path) & { + vendor_partitions_.emplace(name, host_path); + return *this; +} + +CompositeSuperImageBuilder CompositeSuperImageBuilder::VendorPartition( + const std::string_view name, const std::string_view host_path) && { + VendorPartition(name, host_path); + return *this; +} + +Result CompositeSuperImageBuilder::WriteToDirectory( + const std::string_view output_dir, const std::string_view file_name, + const std::string_view header_name) { + std::unique_ptr metadata_builder = + CF_EXPECT(CreateMetadataBuilder(size_)); + + static constexpr std::string_view kSystemGroup = + "google_system_dynamic_partitions"; + static constexpr std::string_view kVendorGroup = + "google_vendor_dynamic_partitions"; + + CF_EXPECT(metadata_builder->AddGroup(absl::StrCat(kSystemGroup, "_a"), 0)); + CF_EXPECT(metadata_builder->AddGroup(absl::StrCat(kSystemGroup, "_b"), 0)); + + CF_EXPECT(metadata_builder->AddGroup(absl::StrCat(kVendorGroup, "_a"), 0)); + CF_EXPECT(metadata_builder->AddGroup(absl::StrCat(kVendorGroup, "_b"), 0)); + + std::vector components; + + // TODO: attributes + for (const auto& [name, path] : system_partitions_) { + components.emplace_back(CF_EXPECT( + AddPartition(name, path, kSystemGroup, 0, *metadata_builder))); + } + for (const auto& [name, path] : vendor_partitions_) { + components.emplace_back(CF_EXPECT( + AddPartition(name, path, kVendorGroup, 0, *metadata_builder))); + } + + const std::unique_ptr metadata = metadata_builder->Export(); + CF_EXPECT(metadata.get()); + + const std::string header_path = absl::StrCat(output_dir, "/", header_name); + unlink(header_path.c_str()); // Ignore errors + + SharedFD header_fd = + SharedFD::Open(header_path, O_RDWR | O_CREAT | O_EXCL, 0644); + CF_EXPECT(header_fd->IsOpen(), header_fd->StrError()); + CF_EXPECT_EQ(header_fd->LSeek(LP_PARTITION_RESERVED_BYTES, SEEK_SET), + LP_PARTITION_RESERVED_BYTES, header_fd->StrError()); + + const std::string geometry_str = SerializeGeometry(metadata->geometry); + const std::string metadata_str = SerializeMetadata(*metadata); + + CF_EXPECT(LogStringToDir(CuttlefishConfig::Get()->Instances()[0], + "generated_super.log", Pretty(metadata))); + + // We always use 2 slots, so 2 copies of metadata_str + const std::string super_header = + absl::StrCat(geometry_str, geometry_str, metadata_str, metadata_str); + CF_EXPECT_EQ(WriteAll(header_fd, super_header), super_header.size(), + header_fd->StrError()); + + ComponentDisk& header_component = *components.emplace(components.begin()); + header_component.set_file_path(header_path); + header_component.set_offset(0); + header_component.set_read_write_capability(ReadWriteCapability::READ_WRITE); + + // TODO: schuffelen: fill the gaps using a single disk. the header is + // relatively short and ends up being mapped thousands of times to fill the + // gap. There needs to be something mapped to avoid producing disk errors on + // access, and the dead space in the super image is being used to augment + // userdata. + for (auto it = components.begin(); it != components.end();) { + const auto next = it + 1; + const uint64_t part_size = CF_EXPECT(ExpandedStorageSize(it->file_path())); + const uint64_t end = it->offset() + part_size; + const uint64_t next_offset = + next == components.end() ? size_ : next->offset(); + if (end < next_offset) { + it = components.emplace(next); + ComponentDisk& fill_hole = *it; + fill_hole.set_file_path(header_path); + fill_hole.set_offset(end); + fill_hole.set_read_write_capability(ReadWriteCapability::READ_WRITE); + } else { + it++; + } + } + + const std::string file_path = absl::StrCat(output_dir, "/", file_name); + unlink(file_path.c_str()); // Ignore errors + + CompositeDisk composite; + composite.set_version(2); + composite.set_length(size_); + for (const ComponentDisk& component : components) { + *composite.add_component_disks() = component; + } + + SharedFD composite_fd = + SharedFD::Open(file_path, O_RDWR | O_CREAT | O_EXCL, 0644); + CF_EXPECT(composite_fd->IsOpen(), composite_fd->StrError()); + + const std::string magic = CompositeDiskImage::MagicString(); + CF_EXPECT_EQ(WriteAll(composite_fd, magic), magic.size(), + composite_fd->StrError()); + + const std::string composite_string = composite.SerializeAsString(); + CF_EXPECT_EQ(WriteAll(composite_fd, composite_string), + composite_string.size(), composite_fd->StrError()); + + return file_path; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/libs/image_aggregator/super_builder.h b/base/cvd/cuttlefish/host/libs/image_aggregator/super_builder.h new file mode 100644 index 00000000000..f9750337807 --- /dev/null +++ b/base/cvd/cuttlefish/host/libs/image_aggregator/super_builder.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2026 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. + */ +#pragma once + +#include + +#include +#include +#include +#include + +#include "cuttlefish/result/result.h" + +namespace cuttlefish { + +class CompositeSuperImageBuilder { + public: + CompositeSuperImageBuilder& BlockDeviceSize(uint64_t) &; + CompositeSuperImageBuilder BlockDeviceSize(uint64_t) &&; + + CompositeSuperImageBuilder& SystemPartition(std::string_view name, + std::string_view host_path) &; + CompositeSuperImageBuilder SystemPartition(std::string_view name, + std::string_view host_path) &&; + + CompositeSuperImageBuilder& VendorPartition(std::string_view name, + std::string_view host_path) &; + CompositeSuperImageBuilder VendorPartition(std::string_view name, + std::string_view host_path) &&; + + Result WriteToDirectory(std::string_view output_dir, + std::string_view file_name, + std::string_view header_name); + + private: + size_t size_ = 0; + // Map is from partition name to filename + std::map> system_partitions_; + std::map> vendor_partitions_; +}; + +} // namespace cuttlefish