liblp: Allow the super partition to span multiple block devices.

This patch allows the block device table in LpMetadataHeader to contain
additional partitions. MetadataBuilder can now resize partitions such
that are allocated across block devices as needed, however, it attempts
to minimize this by grouping free regions by device.

Bug: 116802789
Test: liblp_test gtests
      device with super partition flashes and boots

Change-Id: I9cf74c8925faf154703eeba2a26546a152efcaa2
diff --git a/fs_mgr/fs_mgr_dm_linear.cpp b/fs_mgr/fs_mgr_dm_linear.cpp
index 6ddd5a8..4dacebf 100644
--- a/fs_mgr/fs_mgr_dm_linear.cpp
+++ b/fs_mgr/fs_mgr_dm_linear.cpp
@@ -75,14 +75,9 @@
                 target = std::make_unique<DmTargetZero>(sector, extent.num_sectors);
                 break;
             case LP_TARGET_TYPE_LINEAR: {
-                auto block_device = GetMetadataSuperBlockDevice(metadata);
-                if (!block_device) {
-                    LOG(ERROR) << "Could not identify the super block device";
-                    return false;
-                }
-
+                const auto& block_device = metadata.block_devices[extent.target_source];
                 std::string path;
-                if (!GetPhysicalPartitionDevicePath(*block_device, &path)) {
+                if (!GetPhysicalPartitionDevicePath(block_device, &path)) {
                     LOG(ERROR) << "Unable to complete device-mapper table, unknown block device";
                     return false;
                 }
diff --git a/fs_mgr/liblp/builder.cpp b/fs_mgr/liblp/builder.cpp
index 1b8ed57..3cd33b1 100644
--- a/fs_mgr/liblp/builder.cpp
+++ b/fs_mgr/liblp/builder.cpp
@@ -29,12 +29,19 @@
 namespace android {
 namespace fs_mgr {
 
-void LinearExtent::AddTo(LpMetadata* out) const {
-    out->extents.push_back(LpMetadataExtent{num_sectors_, LP_TARGET_TYPE_LINEAR, physical_sector_});
+bool LinearExtent::AddTo(LpMetadata* out) const {
+    if (device_index_ >= out->block_devices.size()) {
+        LERROR << "Extent references unknown block device.";
+        return false;
+    }
+    out->extents.emplace_back(
+            LpMetadataExtent{num_sectors_, LP_TARGET_TYPE_LINEAR, physical_sector_, device_index_});
+    return true;
 }
 
-void ZeroExtent::AddTo(LpMetadata* out) const {
-    out->extents.push_back(LpMetadataExtent{num_sectors_, LP_TARGET_TYPE_ZERO, 0});
+bool ZeroExtent::AddTo(LpMetadata* out) const {
+    out->extents.emplace_back(LpMetadataExtent{num_sectors_, LP_TARGET_TYPE_ZERO, 0, 0});
+    return true;
 }
 
 Partition::Partition(const std::string& name, const std::string& group_name, uint32_t attributes)
@@ -44,15 +51,17 @@
     size_ += extent->num_sectors() * LP_SECTOR_SIZE;
 
     if (LinearExtent* new_extent = extent->AsLinearExtent()) {
-        if (!extents_.empty() && extents_.back()->AsLinearExtent() &&
-            extents_.back()->AsLinearExtent()->end_sector() == new_extent->physical_sector()) {
-            // If the previous extent can be merged into this new one, do so
-            // to avoid creating unnecessary extents.
+        if (!extents_.empty() && extents_.back()->AsLinearExtent()) {
             LinearExtent* prev_extent = extents_.back()->AsLinearExtent();
-            extent = std::make_unique<LinearExtent>(
-                    prev_extent->num_sectors() + new_extent->num_sectors(),
-                    prev_extent->physical_sector());
-            extents_.pop_back();
+            if (prev_extent->end_sector() == new_extent->physical_sector() &&
+                prev_extent->device_index() == new_extent->device_index()) {
+                // If the previous extent can be merged into this new one, do so
+                // to avoid creating unnecessary extents.
+                extent = std::make_unique<LinearExtent>(
+                        prev_extent->num_sectors() + new_extent->num_sectors(),
+                        prev_extent->device_index(), prev_extent->physical_sector());
+                extents_.pop_back();
+            }
         }
     }
     extents_.push_back(std::move(extent));
@@ -108,9 +117,12 @@
     if (!builder) {
         return nullptr;
     }
-    BlockDeviceInfo device_info;
-    if (opener.GetInfo(super_partition, &device_info)) {
-        builder->UpdateBlockDeviceInfo(device_info);
+    for (size_t i = 0; i < builder->block_devices_.size(); i++) {
+        std::string partition_name = GetBlockDevicePartitionName(builder->block_devices_[i]);
+        BlockDeviceInfo device_info;
+        if (opener.GetInfo(partition_name, &device_info)) {
+            builder->UpdateBlockDeviceInfo(i, device_info);
+        }
     }
     return builder;
 }
@@ -120,11 +132,11 @@
     return New(PartitionOpener(), super_partition, slot_number);
 }
 
-std::unique_ptr<MetadataBuilder> MetadataBuilder::New(const BlockDeviceInfo& device_info,
-                                                      uint32_t metadata_max_size,
-                                                      uint32_t metadata_slot_count) {
+std::unique_ptr<MetadataBuilder> MetadataBuilder::New(
+        const std::vector<BlockDeviceInfo>& block_devices, const std::string& super_partition,
+        uint32_t metadata_max_size, uint32_t metadata_slot_count) {
     std::unique_ptr<MetadataBuilder> builder(new MetadataBuilder());
-    if (!builder->Init(device_info, metadata_max_size, metadata_slot_count)) {
+    if (!builder->Init(block_devices, super_partition, metadata_max_size, metadata_slot_count)) {
         return nullptr;
     }
     return builder;
@@ -156,6 +168,7 @@
 
 bool MetadataBuilder::Init(const LpMetadata& metadata) {
     geometry_ = metadata.geometry;
+    block_devices_ = metadata.block_devices;
 
     for (const auto& group : metadata.groups) {
         std::string group_name = GetPartitionGroupName(group);
@@ -164,10 +177,6 @@
         }
     }
 
-    for (const auto& block_device : metadata.block_devices) {
-        block_devices_.push_back(block_device);
-    }
-
     for (const auto& partition : metadata.partitions) {
         std::string group_name = GetPartitionGroupName(metadata.groups[partition.group_index]);
         Partition* builder =
@@ -179,7 +188,8 @@
         for (size_t i = 0; i < partition.num_extents; i++) {
             const LpMetadataExtent& extent = metadata.extents[partition.first_extent_index + i];
             if (extent.target_type == LP_TARGET_TYPE_LINEAR) {
-                auto copy = std::make_unique<LinearExtent>(extent.num_sectors, extent.target_data);
+                auto copy = std::make_unique<LinearExtent>(extent.num_sectors, extent.target_source,
+                                                           extent.target_data);
                 builder->AddExtent(std::move(copy));
             } else if (extent.target_type == LP_TARGET_TYPE_ZERO) {
                 auto copy = std::make_unique<ZeroExtent>(extent.num_sectors);
@@ -190,7 +200,37 @@
     return true;
 }
 
-bool MetadataBuilder::Init(const BlockDeviceInfo& device_info, uint32_t metadata_max_size,
+static bool VerifyDeviceProperties(const BlockDeviceInfo& device_info) {
+    if (device_info.logical_block_size % LP_SECTOR_SIZE != 0) {
+        LERROR << "Block device " << device_info.partition_name
+               << " logical block size must be a multiple of 512.";
+        return false;
+    }
+    if (device_info.size % device_info.logical_block_size != 0) {
+        LERROR << "Block device " << device_info.partition_name
+               << " size must be a multiple of its block size.";
+        return false;
+    }
+    if (device_info.alignment_offset % LP_SECTOR_SIZE != 0) {
+        LERROR << "Block device " << device_info.partition_name
+               << " alignment offset is not sector-aligned.";
+        return false;
+    }
+    if (device_info.alignment % LP_SECTOR_SIZE != 0) {
+        LERROR << "Block device " << device_info.partition_name
+               << " partition alignment is not sector-aligned.";
+        return false;
+    }
+    if (device_info.alignment_offset > device_info.alignment) {
+        LERROR << "Block device " << device_info.partition_name
+               << " partition alignment offset is greater than its alignment.";
+        return false;
+    }
+    return true;
+}
+
+bool MetadataBuilder::Init(const std::vector<BlockDeviceInfo>& block_devices,
+                           const std::string& super_partition, uint32_t metadata_max_size,
                            uint32_t metadata_slot_count) {
     if (metadata_max_size < sizeof(LpMetadataHeader)) {
         LERROR << "Invalid metadata maximum size.";
@@ -200,70 +240,102 @@
         LERROR << "Invalid metadata slot count.";
         return false;
     }
+    if (block_devices.empty()) {
+        LERROR << "No block devices were specified.";
+        return false;
+    }
 
     // Align the metadata size up to the nearest sector.
     metadata_max_size = AlignTo(metadata_max_size, LP_SECTOR_SIZE);
 
-    // Check that device properties are sane.
-    if (device_info.size % LP_SECTOR_SIZE != 0) {
-        LERROR << "Block device size must be a multiple of 512.";
+    // Validate and build the block device list.
+    uint32_t logical_block_size = 0;
+    for (const auto& device_info : block_devices) {
+        if (!VerifyDeviceProperties(device_info)) {
+            return false;
+        }
+
+        if (!logical_block_size) {
+            logical_block_size = device_info.logical_block_size;
+        }
+        if (logical_block_size != device_info.logical_block_size) {
+            LERROR << "All partitions must have the same logical block size.";
+            return false;
+        }
+
+        LpMetadataBlockDevice out = {};
+        out.alignment = device_info.alignment;
+        out.alignment_offset = device_info.alignment_offset;
+        out.size = device_info.size;
+        if (device_info.partition_name.size() >= sizeof(out.partition_name)) {
+            LERROR << "Partition name " << device_info.partition_name << " exceeds maximum length.";
+            return false;
+        }
+        strncpy(out.partition_name, device_info.partition_name.c_str(), sizeof(out.partition_name));
+
+        // In the case of the super partition, this field will be adjusted
+        // later. For all partitions, the first 512 bytes are considered
+        // untouched to be compatible code that looks for an MBR. Thus we
+        // start counting free sectors at sector 1, not 0.
+        uint64_t free_area_start = LP_SECTOR_SIZE;
+        if (out.alignment || out.alignment_offset) {
+            free_area_start = AlignTo(free_area_start, out.alignment, out.alignment_offset);
+        } else {
+            free_area_start = AlignTo(free_area_start, logical_block_size);
+        }
+        out.first_logical_sector = free_area_start / LP_SECTOR_SIZE;
+
+        // There must be one logical block of space available.
+        uint64_t minimum_size = out.first_logical_sector * LP_SECTOR_SIZE + logical_block_size;
+        if (device_info.size < minimum_size) {
+            LERROR << "Block device " << device_info.partition_name
+                   << " is too small to hold any logical partitions.";
+            return false;
+        }
+
+        // The "root" of the super partition is always listed first.
+        if (device_info.partition_name == super_partition) {
+            block_devices_.emplace(block_devices_.begin(), out);
+        } else {
+            block_devices_.emplace_back(out);
+        }
+    }
+    if (GetBlockDevicePartitionName(block_devices_[0]) != super_partition) {
+        LERROR << "No super partition was specified.";
         return false;
     }
-    if (device_info.logical_block_size % LP_SECTOR_SIZE != 0) {
-        LERROR << "Logical block size must be a multiple of 512.";
-        return false;
-    }
-    if (device_info.alignment_offset % LP_SECTOR_SIZE != 0) {
-        LERROR << "Alignment offset is not sector-aligned.";
-        return false;
-    }
-    if (device_info.alignment % LP_SECTOR_SIZE != 0) {
-        LERROR << "Partition alignment is not sector-aligned.";
-        return false;
-    }
-    if (device_info.alignment_offset > device_info.alignment) {
-        LERROR << "Partition alignment offset is greater than its alignment.";
-        return false;
-    }
+
+    LpMetadataBlockDevice& super = block_devices_[0];
 
     // We reserve a geometry block (4KB) plus space for each copy of the
     // maximum size of a metadata blob. Then, we double that space since
     // we store a backup copy of everything.
     uint64_t total_reserved = GetTotalMetadataSize(metadata_max_size, metadata_slot_count);
-    if (device_info.size < total_reserved) {
+    if (super.size < total_reserved) {
         LERROR << "Attempting to create metadata on a block device that is too small.";
         return false;
     }
 
     // Compute the first free sector, factoring in alignment.
     uint64_t free_area_start = total_reserved;
-    if (device_info.alignment || device_info.alignment_offset) {
-        free_area_start =
-                AlignTo(free_area_start, device_info.alignment, device_info.alignment_offset);
+    if (super.alignment || super.alignment_offset) {
+        free_area_start = AlignTo(free_area_start, super.alignment, super.alignment_offset);
     } else {
-        free_area_start = AlignTo(free_area_start, device_info.logical_block_size);
+        free_area_start = AlignTo(free_area_start, logical_block_size);
     }
-    uint64_t first_sector = free_area_start / LP_SECTOR_SIZE;
+    super.first_logical_sector = free_area_start / LP_SECTOR_SIZE;
 
     // There must be one logical block of free space remaining (enough for one partition).
-    uint64_t minimum_disk_size = (first_sector * LP_SECTOR_SIZE) + device_info.logical_block_size;
-    if (device_info.size < minimum_disk_size) {
+    uint64_t minimum_disk_size = (super.first_logical_sector * LP_SECTOR_SIZE) + logical_block_size;
+    if (super.size < minimum_disk_size) {
         LERROR << "Device must be at least " << minimum_disk_size << " bytes, only has "
-               << device_info.size;
+               << super.size;
         return false;
     }
 
-    block_devices_.push_back(LpMetadataBlockDevice{
-            first_sector,
-            device_info.alignment,
-            device_info.alignment_offset,
-            device_info.size,
-            "super",
-    });
-
     geometry_.metadata_max_size = metadata_max_size;
     geometry_.metadata_slot_count = metadata_slot_count;
-    geometry_.logical_block_size = device_info.logical_block_size;
+    geometry_.logical_block_size = logical_block_size;
 
     if (!AddGroup("default", 0)) {
         return false;
@@ -347,8 +419,9 @@
     for (size_t i = 1; i < extents.size(); i++) {
         const Interval& previous = extents[i - 1];
         const Interval& current = extents[i];
+        DCHECK(previous.device_index == current.device_index);
 
-        uint64_t aligned = AlignSector(previous.end);
+        uint64_t aligned = AlignSector(block_devices_[current.device_index], previous.end);
         if (aligned >= current.start) {
             // There is no gap between these two extents, try the next one.
             // Note that we check with >= instead of >, since alignment may
@@ -358,37 +431,43 @@
 
         // The new interval represents the free space starting at the end of
         // the previous interval, and ending at the start of the next interval.
-        free_regions->emplace_back(aligned, current.start);
+        free_regions->emplace_back(current.device_index, aligned, current.start);
     }
 }
 
 auto MetadataBuilder::GetFreeRegions() const -> std::vector<Interval> {
     std::vector<Interval> free_regions;
 
-    // Collect all extents in the partition table, then sort them by starting
-    // sector.
-    std::vector<Interval> extents;
+    // Collect all extents in the partition table, per-device, then sort them
+    // by starting sector.
+    std::vector<std::vector<Interval>> device_extents(block_devices_.size());
     for (const auto& partition : partitions_) {
         for (const auto& extent : partition->extents()) {
             LinearExtent* linear = extent->AsLinearExtent();
             if (!linear) {
                 continue;
             }
-            extents.emplace_back(linear->physical_sector(),
+            CHECK(linear->device_index() < device_extents.size());
+            auto& extents = device_extents[linear->device_index()];
+            extents.emplace_back(linear->device_index(), linear->physical_sector(),
                                  linear->physical_sector() + extent->num_sectors());
         }
     }
 
     // Add 0-length intervals for the first and last sectors. This will cause
     // ExtentToFreeList() to treat the space in between as available.
-    uint64_t first_sector = super_device().first_logical_sector;
-    uint64_t last_sector = super_device().size / LP_SECTOR_SIZE;
-    extents.emplace_back(first_sector, first_sector);
-    extents.emplace_back(last_sector, last_sector);
+    for (size_t i = 0; i < device_extents.size(); i++) {
+        auto& extents = device_extents[i];
+        const auto& block_device = block_devices_[i];
 
-    std::sort(extents.begin(), extents.end());
+        uint64_t first_sector = block_device.first_logical_sector;
+        uint64_t last_sector = block_device.size / LP_SECTOR_SIZE;
+        extents.emplace_back(i, first_sector, first_sector);
+        extents.emplace_back(i, last_sector, last_sector);
 
-    ExtentsToFreeList(extents, &free_regions);
+        std::sort(extents.begin(), extents.end());
+        ExtentsToFreeList(extents, &free_regions);
+    }
     return free_regions;
 }
 
@@ -443,7 +522,7 @@
         uint64_t sectors = std::min(sectors_needed, region.length());
         CHECK(sectors % sectors_per_block == 0);
 
-        auto extent = std::make_unique<LinearExtent>(sectors, region.start);
+        auto extent = std::make_unique<LinearExtent>(sectors, region.device_index, region.start);
         new_extents.push_back(std::move(extent));
         sectors_needed -= sectors;
         if (!sectors_needed) {
@@ -471,6 +550,9 @@
     metadata->header = header_;
     metadata->geometry = geometry_;
 
+    // Assign this early so the extent table can read it.
+    metadata->block_devices = block_devices_;
+
     std::map<std::string, size_t> group_indices;
     for (const auto& group : groups_) {
         LpMetadataPartitionGroup out = {};
@@ -515,13 +597,13 @@
         part.group_index = iter->second;
 
         for (const auto& extent : partition->extents()) {
-            extent->AddTo(metadata.get());
+            if (!extent->AddTo(metadata.get())) {
+                return nullptr;
+            }
         }
         metadata->partitions.push_back(part);
     }
 
-    metadata->block_devices = block_devices_;
-
     metadata->header.partitions.num_entries = static_cast<uint32_t>(metadata->partitions.size());
     metadata->header.extents.num_entries = static_cast<uint32_t>(metadata->extents.size());
     metadata->header.groups.num_entries = static_cast<uint32_t>(metadata->groups.size());
@@ -531,7 +613,11 @@
 }
 
 uint64_t MetadataBuilder::AllocatableSpace() const {
-    return super_device().size - (super_device().first_logical_sector * LP_SECTOR_SIZE);
+    uint64_t total_size = 0;
+    for (const auto& block_device : block_devices_) {
+        total_size += block_device.size - (block_device.first_logical_sector * LP_SECTOR_SIZE);
+    }
+    return total_size;
 }
 
 uint64_t MetadataBuilder::UsedSpace() const {
@@ -542,26 +628,58 @@
     return size;
 }
 
-uint64_t MetadataBuilder::AlignSector(uint64_t sector) const {
+uint64_t MetadataBuilder::AlignSector(const LpMetadataBlockDevice& block_device,
+                                      uint64_t sector) const {
     // Note: when reading alignment info from the Kernel, we don't assume it
     // is aligned to the sector size, so we round up to the nearest sector.
     uint64_t lba = sector * LP_SECTOR_SIZE;
-    uint64_t aligned = AlignTo(lba, super_device().alignment, super_device().alignment_offset);
+    uint64_t aligned = AlignTo(lba, block_device.alignment, block_device.alignment_offset);
     return AlignTo(aligned, LP_SECTOR_SIZE) / LP_SECTOR_SIZE;
 }
 
-bool MetadataBuilder::GetBlockDeviceInfo(BlockDeviceInfo* info) const {
-    info->size = super_device().size;
-    info->alignment = super_device().alignment;
-    info->alignment_offset = super_device().alignment_offset;
+bool MetadataBuilder::FindBlockDeviceByName(const std::string& partition_name,
+                                            uint32_t* index) const {
+    for (size_t i = 0; i < block_devices_.size(); i++) {
+        if (GetBlockDevicePartitionName(block_devices_[i]) == partition_name) {
+            *index = i;
+            return true;
+        }
+    }
+    return false;
+}
+
+bool MetadataBuilder::GetBlockDeviceInfo(const std::string& partition_name,
+                                         BlockDeviceInfo* info) const {
+    uint32_t index;
+    if (!FindBlockDeviceByName(partition_name, &index)) {
+        LERROR << "No device named " << partition_name;
+        return false;
+    }
+    info->size = block_devices_[index].size;
+    info->alignment = block_devices_[index].alignment;
+    info->alignment_offset = block_devices_[index].alignment_offset;
     info->logical_block_size = geometry_.logical_block_size;
+    info->partition_name = partition_name;
     return true;
 }
 
-bool MetadataBuilder::UpdateBlockDeviceInfo(const BlockDeviceInfo& device_info) {
-    if (device_info.size != super_device().size) {
+bool MetadataBuilder::UpdateBlockDeviceInfo(const std::string& partition_name,
+                                            const BlockDeviceInfo& device_info) {
+    uint32_t index;
+    if (!FindBlockDeviceByName(partition_name, &index)) {
+        LERROR << "No device named " << partition_name;
+        return false;
+    }
+    return UpdateBlockDeviceInfo(index, device_info);
+}
+
+bool MetadataBuilder::UpdateBlockDeviceInfo(size_t index, const BlockDeviceInfo& device_info) {
+    CHECK(index < block_devices_.size());
+
+    LpMetadataBlockDevice& block_device = block_devices_[index];
+    if (device_info.size != block_device.size) {
         LERROR << "Device size does not match (got " << device_info.size << ", expected "
-               << super_device().size << ")";
+               << block_device.size << ")";
         return false;
     }
     if (device_info.logical_block_size != geometry_.logical_block_size) {
@@ -573,10 +691,10 @@
     // The kernel does not guarantee these values are present, so we only
     // replace existing values if the new values are non-zero.
     if (device_info.alignment) {
-        super_device().alignment = device_info.alignment;
+        block_device.alignment = device_info.alignment;
     }
     if (device_info.alignment_offset) {
-        super_device().alignment_offset = device_info.alignment_offset;
+        block_device.alignment_offset = device_info.alignment_offset;
     }
     return true;
 }
diff --git a/fs_mgr/liblp/builder_test.cpp b/fs_mgr/liblp/builder_test.cpp
index c02242a..c27e300 100644
--- a/fs_mgr/liblp/builder_test.cpp
+++ b/fs_mgr/liblp/builder_test.cpp
@@ -27,6 +27,7 @@
 
 TEST(liblp, BuildBasic) {
     unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(1024 * 1024, 1024, 2);
+    ASSERT_NE(builder, nullptr);
 
     Partition* partition = builder->AddPartition("system", LP_PARTITION_ATTR_READONLY);
     ASSERT_NE(partition, nullptr);
@@ -41,6 +42,7 @@
 
 TEST(liblp, ResizePartition) {
     unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(1024 * 1024, 1024, 2);
+    ASSERT_NE(builder, nullptr);
 
     Partition* system = builder->AddPartition("system", LP_PARTITION_ATTR_READONLY);
     ASSERT_NE(system, nullptr);
@@ -94,6 +96,7 @@
 
 TEST(liblp, PartitionAlignment) {
     unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(1024 * 1024, 1024, 2);
+    ASSERT_NE(builder, nullptr);
 
     // Test that we align up to one sector.
     Partition* system = builder->AddPartition("system", LP_PARTITION_ATTR_READONLY);
@@ -120,6 +123,7 @@
 TEST(liblp, MetadataAlignment) {
     // Make sure metadata sizes get aligned up.
     unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(1024 * 1024, 1000, 2);
+    ASSERT_NE(builder, nullptr);
     unique_ptr<LpMetadata> exported = builder->Export();
     ASSERT_NE(exported, nullptr);
     EXPECT_EQ(exported->geometry.metadata_max_size, 1024);
@@ -127,7 +131,7 @@
 
 TEST(liblp, InternalAlignment) {
     // Test the metadata fitting within alignment.
-    BlockDeviceInfo device_info(1024 * 1024, 768 * 1024, 0, 4096);
+    BlockDeviceInfo device_info("super", 1024 * 1024, 768 * 1024, 0, 4096);
     unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(device_info, 1024, 2);
     ASSERT_NE(builder, nullptr);
     unique_ptr<LpMetadata> exported = builder->Export();
@@ -174,7 +178,7 @@
 }
 
 TEST(liblp, InternalPartitionAlignment) {
-    BlockDeviceInfo device_info(512 * 1024 * 1024, 768 * 1024, 753664, 4096);
+    BlockDeviceInfo device_info("super", 512 * 1024 * 1024, 768 * 1024, 753664, 4096);
     unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(device_info, 32 * 1024, 2);
 
     Partition* a = builder->AddPartition("a", 0);
@@ -394,7 +398,7 @@
     static const size_t kMetadataSize = 64 * 1024;
 
     // No space to store metadata + geometry.
-    BlockDeviceInfo device_info(kDiskSize, 0, 0, 4096);
+    BlockDeviceInfo device_info("super", kDiskSize, 0, 0, 4096);
     unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(device_info, kMetadataSize, 1);
     EXPECT_EQ(builder, nullptr);
 
@@ -441,12 +445,12 @@
 }
 
 TEST(liblp, UpdateBlockDeviceInfo) {
-    BlockDeviceInfo device_info(1024 * 1024, 4096, 1024, 4096);
+    BlockDeviceInfo device_info("super", 1024 * 1024, 4096, 1024, 4096);
     unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(device_info, 1024, 1);
     ASSERT_NE(builder, nullptr);
 
     BlockDeviceInfo new_info;
-    ASSERT_TRUE(builder->GetBlockDeviceInfo(&new_info));
+    ASSERT_TRUE(builder->GetBlockDeviceInfo("super", &new_info));
 
     EXPECT_EQ(new_info.size, device_info.size);
     EXPECT_EQ(new_info.alignment, device_info.alignment);
@@ -455,37 +459,37 @@
 
     device_info.alignment = 0;
     device_info.alignment_offset = 2048;
-    ASSERT_TRUE(builder->UpdateBlockDeviceInfo(device_info));
-    ASSERT_TRUE(builder->GetBlockDeviceInfo(&new_info));
+    ASSERT_TRUE(builder->UpdateBlockDeviceInfo("super", device_info));
+    ASSERT_TRUE(builder->GetBlockDeviceInfo("super", &new_info));
     EXPECT_EQ(new_info.alignment, 4096);
     EXPECT_EQ(new_info.alignment_offset, device_info.alignment_offset);
 
     device_info.alignment = 8192;
     device_info.alignment_offset = 0;
-    ASSERT_TRUE(builder->UpdateBlockDeviceInfo(device_info));
-    ASSERT_TRUE(builder->GetBlockDeviceInfo(&new_info));
+    ASSERT_TRUE(builder->UpdateBlockDeviceInfo("super", device_info));
+    ASSERT_TRUE(builder->GetBlockDeviceInfo("super", &new_info));
     EXPECT_EQ(new_info.alignment, 8192);
     EXPECT_EQ(new_info.alignment_offset, 2048);
 
     new_info.size += 4096;
-    ASSERT_FALSE(builder->UpdateBlockDeviceInfo(new_info));
-    ASSERT_TRUE(builder->GetBlockDeviceInfo(&new_info));
+    ASSERT_FALSE(builder->UpdateBlockDeviceInfo("super", new_info));
+    ASSERT_TRUE(builder->GetBlockDeviceInfo("super", &new_info));
     EXPECT_EQ(new_info.size, 1024 * 1024);
 
     new_info.logical_block_size = 512;
-    ASSERT_FALSE(builder->UpdateBlockDeviceInfo(new_info));
-    ASSERT_TRUE(builder->GetBlockDeviceInfo(&new_info));
+    ASSERT_FALSE(builder->UpdateBlockDeviceInfo("super", new_info));
+    ASSERT_TRUE(builder->GetBlockDeviceInfo("super", &new_info));
     EXPECT_EQ(new_info.logical_block_size, 4096);
 }
 
 TEST(liblp, InvalidBlockSize) {
-    BlockDeviceInfo device_info(1024 * 1024, 0, 0, 513);
+    BlockDeviceInfo device_info("super", 1024 * 1024, 0, 0, 513);
     unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(device_info, 1024, 1);
     EXPECT_EQ(builder, nullptr);
 }
 
 TEST(liblp, AlignedExtentSize) {
-    BlockDeviceInfo device_info(1024 * 1024, 0, 0, 4096);
+    BlockDeviceInfo device_info("super", 1024 * 1024, 0, 0, 4096);
     unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(device_info, 1024, 1);
     ASSERT_NE(builder, nullptr);
 
@@ -497,13 +501,13 @@
 
 TEST(liblp, AlignedFreeSpace) {
     // Only one sector free - at least one block is required.
-    BlockDeviceInfo device_info(10240, 0, 0, 4096);
+    BlockDeviceInfo device_info("super", 10240, 0, 0, 4096);
     unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(device_info, 512, 1);
     ASSERT_EQ(builder, nullptr);
 }
 
 TEST(liblp, HasDefaultGroup) {
-    BlockDeviceInfo device_info(1024 * 1024, 0, 0, 4096);
+    BlockDeviceInfo device_info("super", 1024 * 1024, 0, 0, 4096);
     unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(device_info, 1024, 1);
     ASSERT_NE(builder, nullptr);
 
@@ -511,7 +515,7 @@
 }
 
 TEST(liblp, GroupSizeLimits) {
-    BlockDeviceInfo device_info(1024 * 1024, 0, 0, 4096);
+    BlockDeviceInfo device_info("super", 1024 * 1024, 0, 0, 4096);
     unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(device_info, 1024, 1);
     ASSERT_NE(builder, nullptr);
 
@@ -530,6 +534,9 @@
 constexpr unsigned long long operator"" _GiB(unsigned long long x) {  // NOLINT
     return x << 30;
 }
+constexpr unsigned long long operator"" _MiB(unsigned long long x) {  // NOLINT
+    return x << 20;
+}
 
 TEST(liblp, RemoveAndAddFirstPartition) {
     auto builder = MetadataBuilder::New(10_GiB, 65536, 2);
@@ -555,7 +562,7 @@
 }
 
 TEST(liblp, ListGroups) {
-    BlockDeviceInfo device_info(1024 * 1024, 0, 0, 4096);
+    BlockDeviceInfo device_info("super", 1024 * 1024, 0, 0, 4096);
     unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(device_info, 1024, 1);
     ASSERT_NE(builder, nullptr);
     ASSERT_TRUE(builder->AddGroup("example", 0));
@@ -565,7 +572,7 @@
 }
 
 TEST(liblp, RemoveGroupAndPartitions) {
-    BlockDeviceInfo device_info(1024 * 1024, 0, 0, 4096);
+    BlockDeviceInfo device_info("super", 1024 * 1024, 0, 0, 4096);
     unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(device_info, 1024, 1);
     ASSERT_NE(builder, nullptr);
     ASSERT_TRUE(builder->AddGroup("example", 0));
@@ -580,3 +587,48 @@
     builder->RemoveGroupAndPartitions("default");
     ASSERT_NE(builder->FindPartition("system"), nullptr);
 }
+
+TEST(liblp, MultipleBlockDevices) {
+    std::vector<BlockDeviceInfo> partitions = {
+            BlockDeviceInfo("system_a", 256_MiB, 786432, 229376, 4096),
+            BlockDeviceInfo("vendor_a", 128_MiB, 786432, 753664, 4096),
+            BlockDeviceInfo("product_a", 64_MiB, 786432, 753664, 4096),
+    };
+    unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(partitions, "system_a", 65536, 2);
+    ASSERT_NE(builder, nullptr);
+    EXPECT_EQ(builder->AllocatableSpace(), 467238912);
+
+    // Create a partition that spans 3 devices.
+    Partition* p = builder->AddPartition("system_a", 0);
+    ASSERT_NE(p, nullptr);
+    ASSERT_TRUE(builder->ResizePartition(p, 466976768));
+
+    unique_ptr<LpMetadata> metadata = builder->Export();
+    ASSERT_NE(metadata, nullptr);
+    ASSERT_EQ(metadata->block_devices.size(), 3);
+    EXPECT_EQ(GetBlockDevicePartitionName(metadata->block_devices[0]), "system_a");
+    EXPECT_EQ(metadata->block_devices[0].size, 256_MiB);
+    EXPECT_EQ(metadata->block_devices[0].alignment, 786432);
+    EXPECT_EQ(metadata->block_devices[0].alignment_offset, 229376);
+    EXPECT_EQ(GetBlockDevicePartitionName(metadata->block_devices[1]), "vendor_a");
+    EXPECT_EQ(metadata->block_devices[1].size, 128_MiB);
+    EXPECT_EQ(metadata->block_devices[1].alignment, 786432);
+    EXPECT_EQ(metadata->block_devices[1].alignment_offset, 753664);
+    EXPECT_EQ(GetBlockDevicePartitionName(metadata->block_devices[2]), "product_a");
+    EXPECT_EQ(metadata->block_devices[2].size, 64_MiB);
+    EXPECT_EQ(metadata->block_devices[2].alignment, 786432);
+    EXPECT_EQ(metadata->block_devices[2].alignment_offset, 753664);
+    ASSERT_EQ(metadata->extents.size(), 3);
+    EXPECT_EQ(metadata->extents[0].num_sectors, 522304);
+    EXPECT_EQ(metadata->extents[0].target_type, LP_TARGET_TYPE_LINEAR);
+    EXPECT_EQ(metadata->extents[0].target_data, 1984);
+    EXPECT_EQ(metadata->extents[0].target_source, 0);
+    EXPECT_EQ(metadata->extents[1].num_sectors, 260672);
+    EXPECT_EQ(metadata->extents[1].target_type, LP_TARGET_TYPE_LINEAR);
+    EXPECT_EQ(metadata->extents[1].target_data, 1472);
+    EXPECT_EQ(metadata->extents[1].target_source, 1);
+    EXPECT_EQ(metadata->extents[2].num_sectors, 129088);
+    EXPECT_EQ(metadata->extents[2].target_type, LP_TARGET_TYPE_LINEAR);
+    EXPECT_EQ(metadata->extents[2].target_data, 1472);
+    EXPECT_EQ(metadata->extents[2].target_source, 2);
+}
diff --git a/fs_mgr/liblp/include/liblp/builder.h b/fs_mgr/liblp/include/liblp/builder.h
index a090889..f9de106 100644
--- a/fs_mgr/liblp/include/liblp/builder.h
+++ b/fs_mgr/liblp/include/liblp/builder.h
@@ -41,7 +41,7 @@
     explicit Extent(uint64_t num_sectors) : num_sectors_(num_sectors) {}
     virtual ~Extent() {}
 
-    virtual void AddTo(LpMetadata* out) const = 0;
+    virtual bool AddTo(LpMetadata* out) const = 0;
     virtual LinearExtent* AsLinearExtent() { return nullptr; }
 
     uint64_t num_sectors() const { return num_sectors_; }
@@ -54,16 +54,18 @@
 // This corresponds to a dm-linear target.
 class LinearExtent final : public Extent {
   public:
-    LinearExtent(uint64_t num_sectors, uint64_t physical_sector)
-        : Extent(num_sectors), physical_sector_(physical_sector) {}
+    LinearExtent(uint64_t num_sectors, uint32_t device_index, uint64_t physical_sector)
+        : Extent(num_sectors), device_index_(device_index), physical_sector_(physical_sector) {}
 
-    void AddTo(LpMetadata* metadata) const override;
+    bool AddTo(LpMetadata* metadata) const override;
     LinearExtent* AsLinearExtent() override { return this; }
 
     uint64_t physical_sector() const { return physical_sector_; }
     uint64_t end_sector() const { return physical_sector_ + num_sectors_; }
+    uint32_t device_index() const { return device_index_; }
 
   private:
+    uint32_t device_index_;
     uint64_t physical_sector_;
 };
 
@@ -72,7 +74,7 @@
   public:
     explicit ZeroExtent(uint64_t num_sectors) : Extent(num_sectors) {}
 
-    void AddTo(LpMetadata* out) const override;
+    bool AddTo(LpMetadata* out) const override;
 };
 
 class PartitionGroup final {
@@ -122,15 +124,17 @@
 
 class MetadataBuilder {
   public:
-    // Construct an empty logical partition table builder. The block device size
-    // and maximum metadata size must be specified, as this will determine which
-    // areas of the physical partition can be flashed for metadata vs for logical
-    // partitions.
+    // Construct an empty logical partition table builder given the specified
+    // map of partitions that are available for storing logical partitions.
+    //
+    // At least one partition in the list must be the "super" device, where
+    // metadata will be stored.
     //
     // If the parameters would yield invalid metadata, nullptr is returned. This
-    // could happen if the block device size is too small to store the metadata
-    // and backup copies.
-    static std::unique_ptr<MetadataBuilder> New(const BlockDeviceInfo& device_info,
+    // could happen if the super device is too small to store all required
+    // metadata.
+    static std::unique_ptr<MetadataBuilder> New(const std::vector<BlockDeviceInfo>& block_devices,
+                                                const std::string& super_partition,
                                                 uint32_t metadata_max_size,
                                                 uint32_t metadata_slot_count);
 
@@ -150,11 +154,20 @@
     // This method is for testing or changing off-line tables.
     static std::unique_ptr<MetadataBuilder> New(const LpMetadata& metadata);
 
+    // Helper function for a single super partition, for tests.
+    static std::unique_ptr<MetadataBuilder> New(const BlockDeviceInfo& device_info,
+                                                uint32_t metadata_max_size,
+                                                uint32_t metadata_slot_count) {
+        return New({device_info}, device_info.partition_name, metadata_max_size,
+                   metadata_slot_count);
+    }
+
     // Wrapper around New() with a BlockDeviceInfo that only specifies a device
     // size. This is a convenience method for tests.
     static std::unique_ptr<MetadataBuilder> New(uint64_t blockdev_size, uint32_t metadata_max_size,
                                                 uint32_t metadata_slot_count) {
-        BlockDeviceInfo device_info(blockdev_size, 0, 0, kDefaultBlockSize);
+        BlockDeviceInfo device_info(LP_METADATA_DEFAULT_PARTITION_NAME, blockdev_size, 0, 0,
+                                    kDefaultBlockSize);
         return New(device_info, metadata_max_size, metadata_slot_count);
     }
 
@@ -209,8 +222,8 @@
     // Remove all partitions belonging to a group, then remove the group.
     void RemoveGroupAndPartitions(const std::string& group_name);
 
-    bool GetBlockDeviceInfo(BlockDeviceInfo* info) const;
-    bool UpdateBlockDeviceInfo(const BlockDeviceInfo& info);
+    bool GetBlockDeviceInfo(const std::string& partition_name, BlockDeviceInfo* info) const;
+    bool UpdateBlockDeviceInfo(const std::string& partition_name, const BlockDeviceInfo& info);
 
   private:
     MetadataBuilder();
@@ -218,19 +231,27 @@
     MetadataBuilder(MetadataBuilder&&) = delete;
     MetadataBuilder& operator=(const MetadataBuilder&) = delete;
     MetadataBuilder& operator=(MetadataBuilder&&) = delete;
-    bool Init(const BlockDeviceInfo& info, uint32_t metadata_max_size, uint32_t metadata_slot_count);
+    bool Init(const std::vector<BlockDeviceInfo>& block_devices, const std::string& super_partition,
+              uint32_t metadata_max_size, uint32_t metadata_slot_count);
     bool Init(const LpMetadata& metadata);
     bool GrowPartition(Partition* partition, uint64_t aligned_size);
     void ShrinkPartition(Partition* partition, uint64_t aligned_size);
-    uint64_t AlignSector(uint64_t sector) const;
+    uint64_t AlignSector(const LpMetadataBlockDevice& device, uint64_t sector) const;
     uint64_t TotalSizeOfGroup(PartitionGroup* group) const;
+    bool UpdateBlockDeviceInfo(size_t index, const BlockDeviceInfo& info);
+    bool FindBlockDeviceByName(const std::string& partition_name, uint32_t* index) const;
 
     struct Interval {
+        uint32_t device_index;
         uint64_t start;
         uint64_t end;
 
-        Interval(uint64_t start, uint64_t end) : start(start), end(end) {}
+        Interval(uint32_t device_index, uint64_t start, uint64_t end)
+            : device_index(device_index), start(start), end(end) {}
         uint64_t length() const { return end - start; }
+
+        // Note: the device index is not included in sorting (intervals are
+        // sorted in per-device lists).
         bool operator<(const Interval& other) const {
             return (start == other.start) ? end < other.end : start < other.start;
         }
@@ -239,9 +260,6 @@
     void ExtentsToFreeList(const std::vector<Interval>& extents,
                            std::vector<Interval>* free_regions) const;
 
-    const LpMetadataBlockDevice& super_device() const { return block_devices_[0]; }
-    LpMetadataBlockDevice& super_device() { return block_devices_[0]; }
-
     LpMetadataGeometry geometry_;
     LpMetadataHeader header_;
     std::vector<std::unique_ptr<Partition>> partitions_;
diff --git a/fs_mgr/liblp/include/liblp/metadata_format.h b/fs_mgr/liblp/include/liblp/metadata_format.h
index 8a309be..1e40df3 100644
--- a/fs_mgr/liblp/include/liblp/metadata_format.h
+++ b/fs_mgr/liblp/include/liblp/metadata_format.h
@@ -38,7 +38,7 @@
 #define LP_METADATA_HEADER_MAGIC 0x414C5030
 
 /* Current metadata version. */
-#define LP_METADATA_MAJOR_VERSION 7
+#define LP_METADATA_MAJOR_VERSION 8
 #define LP_METADATA_MINOR_VERSION 0
 
 /* Attributes for the LpMetadataPartition::attributes field.
@@ -240,6 +240,13 @@
      * ZERO: This field must be 0.
      */
     uint64_t target_data;
+
+    /* 20: Contents depends on target_type.
+     *
+     * LINEAR: Must be an index into the block devices table.
+     * ZERO: This field must be 0.
+     */
+    uint32_t target_source;
 } __attribute__((packed)) LpMetadataExtent;
 
 /* This struct defines an entry in the groups table. Each group has a maximum
@@ -255,8 +262,9 @@
     uint64_t maximum_size;
 } LpMetadataPartitionGroup;
 
-/* This struct defines an entry in the block_devices table. There must be
- * exactly one device, corresponding to the super partition.
+/* This struct defines an entry in the block_devices table. There must be at
+ * least one device, and the first device must represent the partition holding
+ * the super metadata.
  */
 typedef struct LpMetadataBlockDevice {
     /* 0: First usable sector for allocating logical partitions. this will be
diff --git a/fs_mgr/liblp/include/liblp/partition_opener.h b/fs_mgr/liblp/include/liblp/partition_opener.h
index fe61b9c..e506bd5 100644
--- a/fs_mgr/liblp/include/liblp/partition_opener.h
+++ b/fs_mgr/liblp/include/liblp/partition_opener.h
@@ -27,12 +27,13 @@
 
 struct BlockDeviceInfo {
     BlockDeviceInfo() : size(0), alignment(0), alignment_offset(0), logical_block_size(0) {}
-    BlockDeviceInfo(uint64_t size, uint32_t alignment, uint32_t alignment_offset,
-                    uint32_t logical_block_size)
+    BlockDeviceInfo(const std::string& partition_name, uint64_t size, uint32_t alignment,
+                    uint32_t alignment_offset, uint32_t logical_block_size)
         : size(size),
           alignment(alignment),
           alignment_offset(alignment_offset),
-          logical_block_size(logical_block_size) {}
+          logical_block_size(logical_block_size),
+          partition_name(partition_name) {}
     // Size of the block device, in bytes.
     uint64_t size;
     // Optimal target alignment, in bytes. Partition extents will be aligned to
@@ -44,6 +45,9 @@
     uint32_t alignment_offset;
     // Block size, for aligning extent sizes and partition sizes.
     uint32_t logical_block_size;
+    // The physical partition name for this block device, as it would appear in
+    // the GPT or under /dev/block/by-name.
+    std::string partition_name;
 };
 
 // Test-friendly interface for interacting with partitions.
diff --git a/fs_mgr/liblp/io_test.cpp b/fs_mgr/liblp/io_test.cpp
index 9c675fe..603e5c0 100644
--- a/fs_mgr/liblp/io_test.cpp
+++ b/fs_mgr/liblp/io_test.cpp
@@ -128,7 +128,7 @@
 // Flashing metadata should not work if the metadata was created for a larger
 // disk than the destination disk.
 TEST(liblp, ExportDiskTooSmall) {
-    unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(kDiskSize + 1024, 512, 2);
+    unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(kDiskSize + 4096, 512, 2);
     ASSERT_NE(builder, nullptr);
     unique_ptr<LpMetadata> exported = builder->Export();
     ASSERT_NE(exported, nullptr);
@@ -581,7 +581,7 @@
     unique_fd fd = CreateFakeDisk();
     ASSERT_GE(fd, 0);
 
-    BlockDeviceInfo device_info(kDiskSize, 0, 0, 512);
+    BlockDeviceInfo device_info("super", kDiskSize, 0, 0, 512);
     unique_ptr<MetadataBuilder> builder =
             MetadataBuilder::New(device_info, kMetadataSize, kMetadataSlots);
     ASSERT_NE(builder, nullptr);
diff --git a/fs_mgr/liblp/partition_opener.cpp b/fs_mgr/liblp/partition_opener.cpp
index 7381eed..77b0e62 100644
--- a/fs_mgr/liblp/partition_opener.cpp
+++ b/fs_mgr/liblp/partition_opener.cpp
@@ -24,6 +24,8 @@
 #include <sys/types.h>
 #include <unistd.h>
 
+#include <android-base/file.h>
+
 #include "utility.h"
 
 namespace android {
@@ -68,6 +70,7 @@
 
     device_info->alignment_offset = static_cast<uint32_t>(alignment_offset);
     device_info->logical_block_size = static_cast<uint32_t>(logical_block_size);
+    device_info->partition_name = android::base::Basename(block_device);
     return true;
 #else
     (void)block_device;
diff --git a/fs_mgr/liblp/reader.cpp b/fs_mgr/liblp/reader.cpp
index 070573c..a02e746 100644
--- a/fs_mgr/liblp/reader.cpp
+++ b/fs_mgr/liblp/reader.cpp
@@ -274,6 +274,12 @@
         memcpy(&extent, cursor, sizeof(extent));
         cursor += header.extents.entry_size;
 
+        if (extent.target_type == LP_TARGET_TYPE_LINEAR &&
+            extent.target_source >= header.block_devices.num_entries) {
+            LERROR << "Logical partition extent has invalid block device.";
+            return nullptr;
+        }
+
         metadata->extents.push_back(extent);
     }