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