diff --git a/base/cvd/cuttlefish/host/commands/assemble_cvd/assemble_cvd_flags.cpp b/base/cvd/cuttlefish/host/commands/assemble_cvd/assemble_cvd_flags.cpp index e62f828b4d0..862cd29b0ce 100644 --- a/base/cvd/cuttlefish/host/commands/assemble_cvd/assemble_cvd_flags.cpp +++ b/base/cvd/cuttlefish/host/commands/assemble_cvd/assemble_cvd_flags.cpp @@ -497,11 +497,18 @@ DEFINE_string(fuchsia_root_image, CF_DEFAULTS_FUCHSIA_ROOT_IMAGE, DEFINE_string( custom_partition_path, CF_DEFAULTS_CUSTOM_PARTITION_PATH, - "Location of custom image that will be passed as a \"custom\" partition" - "to rootfs and can be used by /dev/block/by-name/custom. Multiple images " - "can be passed, separated by semicolons and can be used as " - "/dev/block/by-name/custom_1, /dev/block/by-name/custom_2, etc. Example: " - "--custom_partition_path=\"/path/to/custom.img;/path/to/other.img\""); + "Location of custom image that will be passed as a custom partition " + "to rootfs. Multiple entries can be passed, separated by semicolons. " + "Each entry uses key=value pairs separated by commas. " + "Supported keys: name (partition label), path (image file path), " + "ab (true/false for A/B slots). 'path' is required; 'name' defaults to " + "'custom', 'custom_1', etc.; 'ab' defaults to false. " + "Example with A/B: " + "--custom_partition_path=\"name=oem1,path=./oem1.img,ab=true;" + "name=oem2,path=./oem2.img\" " + "Example without names (legacy): " + "--custom_partition_path=\"/path/to/custom.img;/path/to/other.img\" " + "For multi-instance, separate per-instance values with '|'."); DEFINE_string( blank_sdcard_image_mb, CF_DEFAULTS_BLANK_SDCARD_IMAGE_MB, 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 2c31ea21708..f78f25a8ad0 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 @@ -26,6 +26,7 @@ #include #include +#include "absl/log/log.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_split.h" @@ -72,6 +73,63 @@ std::optional HibernationImage( return FileExists(path) ? std::optional(image) : std::nullopt; } +struct CustomPartitionSpec { + std::string label; + std::string path; + bool ab_enabled; +}; + +Result ParseCustomPartitionSpec(std::string_view spec, + int index) { + std::string label; + std::string path; + bool ab_enabled = false; + + // Key=value format: "name=oem,path=./oem.img,ab=true" + if (spec.find('=') != std::string_view::npos) { + std::vector kvpairs = absl::StrSplit(spec, ','); + for (const auto& kvpair : kvpairs) { + std::vector kv = absl::StrSplit(kvpair, '='); + CF_EXPECTF(kv.size() == 2, + "Invalid key=value pair '{}' in custom partition spec '{}'", + kvpair, spec); + std::string_view key = kv[0]; + std::string_view value = kv[1]; + if (key == "name") { + label = std::string(value); + } else if (key == "path") { + path = std::string(value); + } else if (key == "ab") { + CF_EXPECTF(value == "true" || value == "false", + "Invalid value '{}' for key 'ab' in custom partition spec " + "'{}'. Expected 'true' or 'false'", + value, spec); + ab_enabled = (value == "true"); + } else { + return CF_ERRF("Unknown key '{}' in custom partition spec '{}'. " + "Valid keys are: name, path, ab", + key, spec); + } + } + CF_EXPECTF(!path.empty(), + "Missing required 'path' key in custom partition spec '{}'", + spec); + if (label.empty()) { + label = index > 0 ? "custom_" + std::to_string(index) : "custom"; + } + } else { + // Legacy format: plain path only + label = index > 0 ? "custom_" + std::to_string(index) : "custom"; + path = std::string(spec); + } + + return CustomPartitionSpec{ + .label = std::move(label), + .path = std::move(path), + .ab_enabled = ab_enabled, + }; +} + } // namespace Result> AndroidCompositeDiskConfig( @@ -214,10 +272,26 @@ Result> AndroidCompositeDiskConfig( std::vector custom_partition_paths = absl::StrSplit(custom_partition_path, ';'); for (int i = 0; i < custom_partition_paths.size(); i++) { - partitions.push_back(ImagePartition{ - .label = i > 0 ? "custom_" + std::to_string(i) : "custom", - .image_file_path = AbsolutePath(custom_partition_paths[i]), - }); + CustomPartitionSpec spec = + CF_EXPECT(ParseCustomPartitionSpec(custom_partition_paths[i], i)); + if (spec.ab_enabled) { + VLOG(1) << "Adding custom partition: " << spec.label + "_a"; + partitions.push_back(ImagePartition{ + .label = spec.label + "_a", + .image_file_path = AbsolutePath(spec.path), + }); + VLOG(1) << "Adding custom partition: " << spec.label + "_b"; + partitions.push_back(ImagePartition{ + .label = spec.label + "_b", + .image_file_path = AbsolutePath(spec.path), + }); + } else { + VLOG(1) << "Adding custom partition: " << spec.label; + partitions.push_back(ImagePartition{ + .label = spec.label, + .image_file_path = AbsolutePath(spec.path), + }); + } } } diff --git a/base/cvd/cuttlefish/host/commands/assemble_cvd/disk_builder.cpp b/base/cvd/cuttlefish/host/commands/assemble_cvd/disk_builder.cpp index 6fdb6390210..1637fb7c850 100644 --- a/base/cvd/cuttlefish/host/commands/assemble_cvd/disk_builder.cpp +++ b/base/cvd/cuttlefish/host/commands/assemble_cvd/disk_builder.cpp @@ -155,7 +155,7 @@ Result DiskBuilder::TextConfig() { CF_EXPECT(!partitions_.empty() ^ !entire_disk_.empty(), "Specify either partitions or a whole disk"); for (auto& partition : partitions_) { - disk_conf << partition.image_file_path << "\n"; + disk_conf << partition.label << ":" << partition.image_file_path << "\n"; } if (!entire_disk_.empty()) { disk_conf << entire_disk_; diff --git a/base/cvd/cuttlefish/host/commands/assemble_cvd/disk_image_flags_vectorization.cc b/base/cvd/cuttlefish/host/commands/assemble_cvd/disk_image_flags_vectorization.cc index 8bc5ee5c2ea..2981373cc19 100644 --- a/base/cvd/cuttlefish/host/commands/assemble_cvd/disk_image_flags_vectorization.cc +++ b/base/cvd/cuttlefish/host/commands/assemble_cvd/disk_image_flags_vectorization.cc @@ -84,8 +84,10 @@ Result DiskImageFlagsVectorization( std::vector fuchsia_root_image = absl::StrSplit(FLAGS_fuchsia_root_image, ','); + // custom_partition_path uses ',' inside key=value entries, so split + // on '|' for multi-instance separation instead of ','. std::vector custom_partition_path = - absl::StrSplit(FLAGS_custom_partition_path, ','); + absl::StrSplit(FLAGS_custom_partition_path, '|'); std::vector blank_sdcard_image_mb = absl::StrSplit(FLAGS_blank_sdcard_image_mb, ',');