[zircon][lib][fvm] Utility method for resize.

On the next patch this methods will be used to verify that the
conditions are met for growing the fvm. Essentially introduces
invariants based on the metadata allocated size, in terms of the
maximum addressable partition, slices, and allocatable slices.

Some of these methods takes the current disk size as parameter,
which is useful when growing in multiple steps.

TEST=format-info-test

Change-Id: Id68e77f1b83a7df4b603c2cdb337fb8609b7e1c4
diff --git a/zircon/system/ulib/fvm/fvm.cpp b/zircon/system/ulib/fvm/fvm.cpp
index a99c972..4f28c3d 100644
--- a/zircon/system/ulib/fvm/fvm.cpp
+++ b/zircon/system/ulib/fvm/fvm.cpp
@@ -77,25 +77,26 @@
 #ifdef __cplusplus
 
 fvm::FormatInfo fvm::FormatInfo::FromSuperBlock(const Header& superblock) {
-    fvm::FormatInfo summary;
-    summary.metadata_allocated_size_ = superblock.allocation_table_size + kAllocTableOffset;
-    summary.metadata_size_ =
+    fvm::FormatInfo format_info;
+    format_info.metadata_allocated_size_ = superblock.allocation_table_size + kAllocTableOffset;
+    format_info.metadata_size_ =
         MetadataSizeOrZero(superblock.fvm_partition_size, superblock.slice_size);
-    summary.slice_size_ = superblock.slice_size;
-    summary.slice_count_ = UsableSlicesCountOrZero(
-        superblock.fvm_partition_size, summary.metadata_allocated_size(), summary.slice_size());
-    return summary;
+    format_info.slice_size_ = superblock.slice_size;
+    format_info.slice_count_ =
+        UsableSlicesCountOrZero(superblock.fvm_partition_size,
+                                format_info.metadata_allocated_size(), format_info.slice_size());
+    return format_info;
 }
 
 fvm::FormatInfo fvm::FormatInfo::FromPreallocatedSize(size_t initial_size, size_t max_size,
                                                       size_t slice_size) {
-    fvm::FormatInfo summary;
-    summary.metadata_allocated_size_ = MetadataSizeOrZero(max_size, slice_size);
-    summary.metadata_size_ = MetadataSizeOrZero(initial_size, slice_size);
-    summary.slice_size_ = slice_size;
-    summary.slice_count_ = UsableSlicesCountOrZero(initial_size, summary.metadata_allocated_size(),
-                                                   summary.slice_size());
-    return summary;
+    fvm::FormatInfo format_info;
+    format_info.metadata_allocated_size_ = MetadataSizeOrZero(max_size, slice_size);
+    format_info.metadata_size_ = MetadataSizeOrZero(initial_size, slice_size);
+    format_info.slice_size_ = slice_size;
+    format_info.slice_count_ = UsableSlicesCountOrZero(
+        initial_size, format_info.metadata_allocated_size(), format_info.slice_size());
+    return format_info;
 }
 
 fvm::FormatInfo fvm::FormatInfo::FromDiskSize(size_t disk_size, size_t slice_size) {
diff --git a/zircon/system/ulib/fvm/include/fvm/format.h b/zircon/system/ulib/fvm/include/fvm/format.h
index 792d3df..d5c5ec3 100644
--- a/zircon/system/ulib/fvm/include/fvm/format.h
+++ b/zircon/system/ulib/fvm/include/fvm/format.h
@@ -332,6 +332,33 @@
         return 2 * metadata_allocated_size_ + (pslice - 1) * slice_size_;
     }
 
+    // Returns the maximum number of slices that can be addressed from the maximum possible size
+    // of the metatadata.
+    size_t GetMaxAllocatableSlices() const {
+        return (metadata_allocated_size() - kAllocTableOffset) / sizeof(SliceEntry);
+    }
+
+    // Returns the maximum number of slices that the allocated metadata can address for a given
+    // |disk_size|.
+    size_t GetMaxAddressableSlices(uint64_t disk_size) const {
+        size_t slice_count =
+            std::min(GetMaxAllocatableSlices(), UsableSlicesCount(disk_size, slice_size_));
+        // Because the allocation thable is 1-indexed and pslices are 0 indexed on disk,
+        // if the number of slices fit perfectly in the metadata, the allocated buffer won't be big
+        // enough to address them all. This only happens when the rounded up block value happens to
+        // match the disk size.
+        // TODO(gevalentino): Fix underlying cause and remove workaround.
+        if ((GetSliceStart(1) + slice_count * slice_size()) == metadata_allocated_size()) {
+            slice_count--;
+        }
+        return slice_count;
+    }
+
+    // Returns the maximum partition size the current metadata can grow to.
+    size_t GetMaxPartitionSize() const {
+        return GetSliceStart(1) + GetMaxAllocatableSlices() * slice_size_;
+    }
+
 private:
     // Size in bytes of addressable metadata.
     size_t metadata_size_ = 0;
diff --git a/zircon/system/ulib/fvm/test/format-info-test.cpp b/zircon/system/ulib/fvm/test/format-info-test.cpp
index f90b1e3..ffffa3d 100644
--- a/zircon/system/ulib/fvm/test/format-info-test.cpp
+++ b/zircon/system/ulib/fvm/test/format-info-test.cpp
@@ -9,22 +9,19 @@
 namespace fvm {
 namespace {
 
-// 8 KB
-constexpr size_t kFvmBlockSize = 1 << 13;
-
 // 256 KB
-constexpr size_t kFvmSlizeSize = 1 << 18;
+constexpr size_t kFvmSlizeSize = 8 * kBlockSize;
 
-constexpr size_t kInitialDiskSize = 256 * kFvmBlockSize;
+constexpr size_t kInitialDiskSize = 256 * kBlockSize;
 
-constexpr size_t kMaxDiskSize = 1024 * kFvmBlockSize;
+constexpr size_t kMaxDiskSize = 1024 * kBlockSize;
 
 constexpr size_t kAllocTableSize = fvm::AllocTableLength(kMaxDiskSize, kFvmSlizeSize);
 
 constexpr size_t kPartitionTableSize = fvm::kVPartTableLength;
 
-fvm_t MakeSuperBlock(size_t part_size, size_t part_table_size, size_t alloc_table_size) {
-    fvm_t superblock;
+Header MakeSuperBlock(size_t part_size, size_t part_table_size, size_t alloc_table_size) {
+    Header superblock;
     superblock.fvm_partition_size = part_size;
     superblock.vpartition_table_size = part_table_size;
     superblock.allocation_table_size = alloc_table_size;
@@ -32,7 +29,7 @@
     superblock.version = fvm::kMagic;
     superblock.magic = fvm::kVersion;
     superblock.generation = 1;
-    fvm_update_hash(&superblock, sizeof(fvm_t));
+    fvm_update_hash(&superblock, sizeof(Header));
     return superblock;
 }
 
@@ -43,7 +40,7 @@
 }
 
 TEST(FvmInfoTest, FromSuperblockNoGaps) {
-    fvm_t superblock = MakeSuperBlock(kMaxDiskSize, kPartitionTableSize, kAllocTableSize);
+    Header superblock = MakeSuperBlock(kMaxDiskSize, kPartitionTableSize, kAllocTableSize);
     FormatInfo format_info = FormatInfo::FromSuperBlock(superblock);
 
     // When there is no gap allocated and metadata size should match.
@@ -58,10 +55,20 @@
               format_info.GetSuperblockOffset(SuperblockType::kSecondary));
     EXPECT_EQ(CalculateSliceStart(kMaxDiskSize, kPartitionTableSize, kAllocTableSize),
               format_info.GetSliceStart(1));
+    EXPECT_EQ((fvm::MetadataSize(kMaxDiskSize, kFvmSlizeSize) - AllocationTable::kOffset) /
+                  sizeof(SliceEntry),
+              format_info.GetMaxAllocatableSlices());
+    EXPECT_EQ((fvm::UsableSlicesCount(kMaxDiskSize, kFvmSlizeSize)),
+              format_info.GetMaxAddressableSlices(kMaxDiskSize));
+    EXPECT_EQ(format_info.GetSliceStart(1) +
+                  kFvmSlizeSize *
+                      ((fvm::MetadataSize(kMaxDiskSize, kFvmSlizeSize) - kAllocTableOffset) /
+                       sizeof(SliceEntry)),
+              format_info.GetMaxPartitionSize());
 }
 
 TEST(FvmInfoTest, FromSuperblockWithGaps) {
-    fvm_t superblock = MakeSuperBlock(kInitialDiskSize, kPartitionTableSize, kAllocTableSize);
+    Header superblock = MakeSuperBlock(kInitialDiskSize, kPartitionTableSize, kAllocTableSize);
     FormatInfo format_info = FormatInfo::FromSuperBlock(superblock);
 
     EXPECT_EQ(fvm::MetadataSize(kInitialDiskSize, kFvmSlizeSize), format_info.metadata_size());
@@ -75,6 +82,17 @@
               format_info.GetSuperblockOffset(SuperblockType::kSecondary));
     EXPECT_EQ(CalculateSliceStart(kMaxDiskSize, kPartitionTableSize, kAllocTableSize),
               format_info.GetSliceStart(1));
+
+    EXPECT_EQ((fvm::MetadataSize(kMaxDiskSize, kFvmSlizeSize) - AllocationTable::kOffset) /
+                  sizeof(SliceEntry),
+              format_info.GetMaxAllocatableSlices());
+    EXPECT_EQ((fvm::UsableSlicesCount(kMaxDiskSize, kFvmSlizeSize)),
+              format_info.GetMaxAddressableSlices(kMaxDiskSize));
+    EXPECT_EQ(format_info.GetSliceStart(1) +
+                  kFvmSlizeSize *
+                      ((fvm::MetadataSize(kMaxDiskSize, kFvmSlizeSize) - kAllocTableOffset) /
+                       sizeof(SliceEntry)),
+              format_info.GetMaxPartitionSize());
 }
 
 TEST(FvmInfoTest, FromDiskSize) {
@@ -92,6 +110,17 @@
               format_info.GetSuperblockOffset(SuperblockType::kSecondary));
     EXPECT_EQ(CalculateSliceStart(kMaxDiskSize, kPartitionTableSize, kAllocTableSize),
               format_info.GetSliceStart(1));
+
+    EXPECT_EQ((fvm::MetadataSize(kMaxDiskSize, kFvmSlizeSize) - AllocationTable::kOffset) /
+                  sizeof(SliceEntry),
+              format_info.GetMaxAllocatableSlices());
+    EXPECT_EQ((fvm::UsableSlicesCount(kMaxDiskSize, kFvmSlizeSize)),
+              format_info.GetMaxAddressableSlices(kMaxDiskSize));
+    EXPECT_EQ(format_info.GetSliceStart(1) +
+                  kFvmSlizeSize *
+                      ((fvm::MetadataSize(kMaxDiskSize, kFvmSlizeSize) - kAllocTableOffset) /
+                       sizeof(SliceEntry)),
+              format_info.GetMaxPartitionSize());
 }
 
 TEST(FvmInfoTest, FromPreallocatedSizeWithGaps) {
@@ -109,6 +138,17 @@
               format_info.GetSuperblockOffset(SuperblockType::kSecondary));
     EXPECT_EQ(CalculateSliceStart(kMaxDiskSize, kPartitionTableSize, kAllocTableSize),
               format_info.GetSliceStart(1));
+
+    EXPECT_EQ((fvm::MetadataSize(kMaxDiskSize, kFvmSlizeSize) - AllocationTable::kOffset) /
+                  sizeof(SliceEntry),
+              format_info.GetMaxAllocatableSlices());
+    EXPECT_EQ((fvm::UsableSlicesCount(kMaxDiskSize, kFvmSlizeSize)),
+              format_info.GetMaxAddressableSlices(kMaxDiskSize));
+    EXPECT_EQ(format_info.GetSliceStart(1) +
+                  kFvmSlizeSize *
+                      ((fvm::MetadataSize(kMaxDiskSize, kFvmSlizeSize) - kAllocTableOffset) /
+                       sizeof(SliceEntry)),
+              format_info.GetMaxPartitionSize());
 }
 
 } // namespace