[volume_image]: FvmSparseReadImage support for lz4

Allows directly opening a sparse fvm image compressed with lz4, by using
FvmSpaseReadImage. Increased the default buffer size, to speed things up
a bit to 2 MB per buffer.

Test: storage-volume-image-tests
Change-Id: Icc3795930663a5f492594a053c2fc522ed1bad5d
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/514656
Commit-Queue: Gianfranco Valentino <gevalentino@google.com>
Reviewed-by: James Sullivan <jfsulliv@google.com>
diff --git a/src/storage/volume_image/adapter/adapter_integration_test.cc b/src/storage/volume_image/adapter/adapter_integration_test.cc
index 3e367e8..ba3ddb1 100644
--- a/src/storage/volume_image/adapter/adapter_integration_test.cc
+++ b/src/storage/volume_image/adapter/adapter_integration_test.cc
@@ -427,5 +427,24 @@
   ASSERT_NO_FATAL_FAILURE(CheckPartitionsInRamdisk(fvm_descriptor));
 }
 
+TEST(AdapterTest, CompressedSparseImageWithoutExplicitDecompressionToFvmImagePassesFsck) {
+  auto compressed_sparse_reader_or = FdReader::Create(kFvmSparseImagePath);
+  ASSERT_TRUE(compressed_sparse_reader_or.is_ok()) << compressed_sparse_reader_or.error();
+  FdReader compressed_sparse_reader = compressed_sparse_reader_or.take_value();
+
+  // Read the decompressed image.
+  auto fvm_descriptor_or =
+      FvmSparseReadImage(0, std::make_unique<FdReader>(std::move(compressed_sparse_reader)));
+  ASSERT_TRUE(fvm_descriptor_or.is_ok()) << fvm_descriptor_or.error();
+  auto fvm_descriptor = fvm_descriptor_or.take_value();
+
+  auto write_result = WriteFvmImage(fvm_descriptor);
+  ASSERT_TRUE(write_result.is_ok()) << write_result.error();
+  auto [fvm_vmo, fvm_writer] = write_result.take_value();
+
+  auto ramdisk_handle = LaunchFvm(fvm_vmo);
+  ASSERT_NO_FATAL_FAILURE(CheckPartitionsInRamdisk(fvm_descriptor));
+}
+
 }  // namespace
 }  // namespace storage::volume_image
diff --git a/src/storage/volume_image/fvm/BUILD.gn b/src/storage/volume_image/fvm/BUILD.gn
index 76b0773..95dfedb 100644
--- a/src/storage/volume_image/fvm/BUILD.gn
+++ b/src/storage/volume_image/fvm/BUILD.gn
@@ -51,6 +51,7 @@
   deps = [
     "//src/storage/volume_image/utils:block-utils",
     "//src/storage/volume_image/utils:lz4-compression",
+    "//src/storage/volume_image/utils:lz4-decompress-reader",
   ]
 }
 
diff --git a/src/storage/volume_image/fvm/fvm_sparse_image.cc b/src/storage/volume_image/fvm/fvm_sparse_image.cc
index fc81df3..723c013 100644
--- a/src/storage/volume_image/fvm/fvm_sparse_image.cc
+++ b/src/storage/volume_image/fvm/fvm_sparse_image.cc
@@ -25,6 +25,7 @@
 #include "src/storage/volume_image/options.h"
 #include "src/storage/volume_image/utils/block_utils.h"
 #include "src/storage/volume_image/utils/compressor.h"
+#include "src/storage/volume_image/utils/lz4_decompress_reader.h"
 #include "src/storage/volume_image/utils/lz4_decompressor.h"
 #include "src/storage/volume_image/volume_descriptor.h"
 
@@ -691,12 +692,6 @@
   }
   auto header = header_or.take_value();
 
-  if (fvm_sparse_internal::GetCompressionOptions(header).schema != CompressionSchema::kNone) {
-    return fit::error(
-        "FvmSparseReadImage only supports uncompressed images. Use FvmSparseDecompressImage "
-        "first.");
-  }
-
   // Get the partition entries.
   auto partition_entries_or =
       fvm_sparse_internal::GetPartitions(sizeof(fvm::SparseImage), *image_reader, header);
@@ -704,9 +699,16 @@
     return partition_entries_or.take_error_result();
   }
 
+  // This is the maximum offset allowed for the sparse image.
+  uint64_t total_image_size = header.header_length;
+  for (const auto& partition_entry : partition_entries_or.value()) {
+    for (const auto& extent : partition_entry.extents) {
+      total_image_size += extent.extent_length;
+    }
+  }
+
   // Get the matching options.
   FvmOptions options;
-
   options.slice_size = header.slice_size;
 
   if (header.maximum_disk_size != 0) {
@@ -717,7 +719,7 @@
   builder.SetOptions(options);
 
   // Generate the address map for each partition entry.
-  uint64_t imaged_extent_offset = header.header_length;
+  uint64_t image_extent_offset = header.header_length;
   for (auto& partition_entry : partition_entries_or.value()) {
     VolumeDescriptor volume_descriptor;
     AddressDescriptor address_descriptor;
@@ -753,10 +755,22 @@
       address_descriptor.mappings.push_back(mapping);
       accumulated_extent_offset += extent.extent_length;
     }
-    std::unique_ptr<SharedReader> partition_reader = std::make_unique<SharedReader>(
-        imaged_extent_offset, accumulated_extent_offset, image_reader);
 
-    imaged_extent_offset += accumulated_extent_offset;
+    // If the image is compressed wrap it with a Lz4DecompressReader.
+    std::shared_ptr<Reader> base_reader = image_reader;
+    if (fvm_sparse_internal::GetCompressionOptions(header).schema == CompressionSchema::kLz4) {
+      auto decompress_reader = std::make_shared<Lz4DecompressReader>(
+          header.header_length, total_image_size, image_reader);
+      if (auto result = decompress_reader->Initialize(); result.is_error()) {
+        return result.take_error_result();
+      }
+      base_reader = decompress_reader;
+    }
+
+    std::unique_ptr<SharedReader> partition_reader =
+        std::make_unique<SharedReader>(image_extent_offset, accumulated_extent_offset, base_reader);
+
+    image_extent_offset += accumulated_extent_offset;
 
     builder.AddPartition(
         Partition(volume_descriptor, address_descriptor, std::move(partition_reader)));
diff --git a/src/storage/volume_image/fvm/fvm_sparse_image_test.cc b/src/storage/volume_image/fvm/fvm_sparse_image_test.cc
index 4e6d410..3cae533 100644
--- a/src/storage/volume_image/fvm/fvm_sparse_image_test.cc
+++ b/src/storage/volume_image/fvm/fvm_sparse_image_test.cc
@@ -1697,45 +1697,18 @@
   ASSERT_TRUE(FvmSparseReadImage(0, nullptr).is_error());
 }
 
-TEST(FvmSparseReadImageTest, CompressedImageIsError) {
-  SerializedImageContainer compressed_container;
-  auto descriptor = MakeFvmDescriptorWithOptions(MakeOptions(8192, CompressionSchema::kLz4));
+void CheckGeneratedDescriptor(const FvmDescriptor& actual_descriptor,
+                              const FvmDescriptor& original_descriptor) {
+  EXPECT_EQ(actual_descriptor.options().slice_size, original_descriptor.options().slice_size);
+  EXPECT_EQ(actual_descriptor.options().max_volume_size,
+            original_descriptor.options().max_volume_size);
+  EXPECT_EQ(actual_descriptor.options().target_volume_size, std::nullopt);
+  EXPECT_EQ(actual_descriptor.options().compression.schema, CompressionSchema::kNone);
 
-  Lz4Compressor compressor = Lz4Compressor::Create(descriptor.options().compression).take_value();
-
-  // Write the compressed data that we will decompress later.
-  auto write_result = FvmSparseWriteImage(descriptor, &compressed_container.writer(), &compressor);
-  ASSERT_TRUE(write_result.is_ok()) << write_result.error();
-
-  EXPECT_TRUE(FvmSparseReadImage(
-                  0, std::make_unique<BufferReader>(0, &compressed_container.serialized_image(),
-                                                    sizeof(SerializedSparseImage)))
-                  .is_error());
-}
-
-TEST(FvmSparseReadImageTest, ReturnsFvmDescriptorAndIsCorrect) {
-  SerializedImageContainer compressed_container;
-  auto descriptor = MakeFvmDescriptorWithOptions(MakeOptions(8192, CompressionSchema::kNone));
-
-  // Write the compressed data that we will decompress later.
-  auto write_result = FvmSparseWriteImage(descriptor, &compressed_container.writer());
-  ASSERT_TRUE(write_result.is_ok()) << write_result.error();
-
-  auto read_descriptor_or = FvmSparseReadImage(
-      0, std::make_unique<BufferReader>(0, &compressed_container.serialized_image(),
-                                        sizeof(SerializedSparseImage)));
-  EXPECT_TRUE(read_descriptor_or.is_ok());
-  auto read_descriptor = read_descriptor_or.take_value();
-
-  EXPECT_EQ(read_descriptor.options().slice_size, descriptor.options().slice_size);
-  EXPECT_EQ(read_descriptor.options().max_volume_size, descriptor.options().max_volume_size);
-  EXPECT_EQ(read_descriptor.options().target_volume_size, std::nullopt);
-  EXPECT_EQ(read_descriptor.options().compression.schema, CompressionSchema::kNone);
-
-  ASSERT_EQ(read_descriptor.partitions().size(), descriptor.partitions().size());
-  auto read_partition_it = read_descriptor.partitions().begin();
-  auto expected_partition_it = descriptor.partitions().begin();
-  for (size_t i = 0; i < read_descriptor.partitions().size(); ++i) {
+  ASSERT_EQ(actual_descriptor.partitions().size(), original_descriptor.partitions().size());
+  auto read_partition_it = actual_descriptor.partitions().begin();
+  auto expected_partition_it = original_descriptor.partitions().begin();
+  for (size_t i = 0; i < actual_descriptor.partitions().size(); ++i) {
     const auto& actual_partition = *(read_partition_it);
     const auto& expected_partition = *(expected_partition_it);
 
@@ -1772,9 +1745,13 @@
       EXPECT_EQ(actual_mapping.size, expected_size);
 
       // Non compressed images will require zero filling.
-      ASSERT_EQ(actual_mapping.options.size(), 1u);
-      EXPECT_NE(actual_mapping.options.find(EnumAsString(AddressMapOption::kFill)),
-                actual_mapping.options.end());
+      if (original_descriptor.options().compression.schema == CompressionSchema::kNone) {
+        ASSERT_EQ(actual_mapping.options.size(), 1u);
+        EXPECT_NE(actual_mapping.options.find(EnumAsString(AddressMapOption::kFill)),
+                  actual_mapping.options.end());
+      } else {
+        ASSERT_EQ(actual_mapping.options.size(), 0u);
+      }
 
       actual_data.resize(actual_mapping.count, 0);
       expected_data.resize(expected_mapping.count, 0);
@@ -1794,5 +1771,41 @@
   }
 }
 
+TEST(FvmSparseReadImageTest, CompressedImageIsOk) {
+  SerializedImageContainer compressed_container;
+  auto descriptor = MakeFvmDescriptorWithOptions(MakeOptions(8192, CompressionSchema::kLz4));
+
+  Lz4Compressor compressor = Lz4Compressor::Create(descriptor.options().compression).take_value();
+
+  // Write the compressed data that we will decompress later.
+  auto write_result = FvmSparseWriteImage(descriptor, &compressed_container.writer(), &compressor);
+  ASSERT_TRUE(write_result.is_ok()) << write_result.error();
+
+  auto read_descriptor_or = FvmSparseReadImage(
+      0, std::make_unique<BufferReader>(0, &compressed_container.serialized_image(),
+                                        sizeof(SerializedSparseImage)));
+  EXPECT_TRUE(read_descriptor_or.is_ok()) << read_descriptor_or.error();
+  auto read_descriptor = read_descriptor_or.take_value();
+
+  ASSERT_NO_FATAL_FAILURE(CheckGeneratedDescriptor(read_descriptor, descriptor));
+}
+
+TEST(FvmSparseReadImageTest, ReturnsFvmDescriptorAndIsCorrect) {
+  SerializedImageContainer compressed_container;
+  auto descriptor = MakeFvmDescriptorWithOptions(MakeOptions(8192, CompressionSchema::kNone));
+
+  // Write the compressed data that we will decompress later.
+  auto write_result = FvmSparseWriteImage(descriptor, &compressed_container.writer());
+  ASSERT_TRUE(write_result.is_ok()) << write_result.error();
+
+  auto read_descriptor_or = FvmSparseReadImage(
+      0, std::make_unique<BufferReader>(0, &compressed_container.serialized_image(),
+                                        sizeof(SerializedSparseImage)));
+  EXPECT_TRUE(read_descriptor_or.is_ok());
+  auto read_descriptor = read_descriptor_or.take_value();
+
+  ASSERT_NO_FATAL_FAILURE(CheckGeneratedDescriptor(read_descriptor, descriptor));
+}
+
 }  // namespace
 }  // namespace storage::volume_image
diff --git a/src/storage/volume_image/utils/fd_reader.cc b/src/storage/volume_image/utils/fd_reader.cc
index de0607e..3eed878d 100644
--- a/src/storage/volume_image/utils/fd_reader.cc
+++ b/src/storage/volume_image/utils/fd_reader.cc
@@ -10,6 +10,7 @@
 
 #include <cstdio>
 #include <cstdlib>
+#include <iostream>
 
 #include <fbl/span.h>
 #include <fbl/unique_fd.h>
diff --git a/src/storage/volume_image/utils/lz4_decompress_reader.h b/src/storage/volume_image/utils/lz4_decompress_reader.h
index 8380f09..346a7e1 100644
--- a/src/storage/volume_image/utils/lz4_decompress_reader.h
+++ b/src/storage/volume_image/utils/lz4_decompress_reader.h
@@ -21,7 +21,7 @@
 class Lz4DecompressReader final : public Reader {
  public:
   // Default size for |StreamContext| buffers.
-  static constexpr uint64_t kMaxBufferSize = 20 * (1 << 10);
+  static constexpr uint64_t kMaxBufferSize = 2 * (1 << 20);
 
   // Lz4DecompressReader will decompress data starting at |offset|. That is the compressed data is
   // embedded in |compressed_reader| and the first compressed byte is at |offset|.