blob: 07d45dff597e7a2b0af0720920f390e7565f4dad [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/storage/volume_image/fvm/fvm_sparse_image.h"
#include <lib/fpromise/result.h>
#include <string.h>
#include <bitset>
#include <cstdint>
#include <future>
#include <iostream>
#include <memory>
#include <optional>
#include <string>
#include <fbl/algorithm.h>
#include <safemath/safe_conversions.h>
#include "src/storage/fvm/format.h"
#include "src/storage/fvm/fvm_sparse.h"
#include "src/storage/fvm/metadata.h"
#include "src/storage/volume_image/address_descriptor.h"
#include "src/storage/volume_image/fvm/options.h"
#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"
namespace storage::volume_image {
namespace {
// Dedicated memory for reading to and from the underlying media.
constexpr uint64_t kReadBufferSize = 4096;
// Returns a byte view of a fixed size struct.
// Currently we are not endian safe, so we are no worst than before. If this matter,
// this should be updated.
template <typename T>
cpp20::span<const uint8_t> FixedSizeStructToSpan(const T& typed_content) {
return cpp20::span<const uint8_t>(reinterpret_cast<const uint8_t*>(&typed_content), sizeof(T));
}
template <typename T>
cpp20::span<uint8_t> FixedSizeStructToSpan(T& typed_content) {
return cpp20::span<uint8_t>(reinterpret_cast<uint8_t*>(&typed_content), sizeof(T));
}
class NoopCompressor final : public Compressor {
public:
fpromise::result<void, std::string> Prepare(Handler handler) final {
handler_ = std::move(handler);
return fpromise::ok();
}
fpromise::result<void, std::string> Compress(cpp20::span<const uint8_t> uncompressed_data) final {
handler_(uncompressed_data);
return fpromise::ok();
}
fpromise::result<void, std::string> Finalize() final { return fpromise::ok(); }
private:
Handler handler_ = nullptr;
};
fpromise::result<uint64_t, std::string> FvmSparseWriteImageInternal(const FvmDescriptor& descriptor,
Writer* writer,
Compressor* compressor) {
uint64_t current_offset = 0;
// Write the header.
fvm::SparseImage header = fvm_sparse_internal::GenerateHeader(descriptor);
bool default_fill_extents = (header.flags & fvm::kSparseFlagZeroFillNotRequired) != 0;
auto result = writer->Write(current_offset, FixedSizeStructToSpan(header));
if (result.is_error()) {
return result.take_error_result();
}
current_offset += sizeof(fvm::SparseImage);
for (const auto& partition : descriptor.partitions()) {
auto partition_entry_result = fvm_sparse_internal::GeneratePartitionEntry(
descriptor.options().slice_size, partition, default_fill_extents);
if (partition_entry_result.is_error()) {
return partition_entry_result.take_error_result();
}
fvm_sparse_internal::PartitionEntry entry = partition_entry_result.take_value();
auto partition_result = writer->Write(current_offset, FixedSizeStructToSpan(entry.descriptor));
if (partition_result.is_error()) {
return partition_result.take_error_result();
}
current_offset += sizeof(fvm::PartitionDescriptor);
for (const auto& extent : entry.extents) {
auto extent_result = writer->Write(current_offset, FixedSizeStructToSpan(extent));
if (extent_result.is_error()) {
return extent_result.take_error_result();
}
current_offset += sizeof(fvm::ExtentDescriptor);
}
}
if (current_offset != header.header_length) {
return fpromise::error("fvm::SparseImage data does not start at header_length.");
}
std::vector<uint8_t> data(kReadBufferSize, 0);
compressor->Prepare(
[&current_offset, writer](auto compressed_data) -> fpromise::result<void, std::string> {
auto extent_data_write_result = writer->Write(current_offset, compressed_data);
if (extent_data_write_result.is_error()) {
return extent_data_write_result.take_error_result();
}
current_offset += compressed_data.size();
return fpromise::ok();
});
for (const auto& partition : descriptor.partitions()) {
const auto* reader = partition.reader();
for (const auto& mapping : partition.address().mappings) {
uint64_t remaining_bytes = mapping.count;
uint64_t default_fill_remaining_bytes = 0;
auto default_fill_value_it = mapping.options.find(EnumAsString(AddressMapOption::kFill));
std::optional<uint8_t> default_fill_value;
if (default_fill_extents && default_fill_value_it != mapping.options.end()) {
uint64_t size = std::max(mapping.size.value_or(0), mapping.count);
uint64_t slice_count = GetBlockCount(mapping.target, size, descriptor.options().slice_size);
// Need to fill all the way up the slice boundary.
default_fill_remaining_bytes =
slice_count * descriptor.options().slice_size - mapping.count;
default_fill_value = static_cast<uint8_t>(default_fill_value_it->second);
}
memset(data.data(), default_fill_value.value_or(0), data.size());
uint64_t read_offset = mapping.source;
while (remaining_bytes > 0) {
uint64_t bytes_to_read = std::min(kReadBufferSize, remaining_bytes);
remaining_bytes -= bytes_to_read;
auto buffer_view = cpp20::span(data.data(), bytes_to_read);
auto extent_data_read_result = reader->Read(read_offset, buffer_view);
if (extent_data_read_result.is_error()) {
return extent_data_read_result.take_error_result();
}
read_offset += bytes_to_read;
auto compress_result = compressor->Compress(buffer_view);
if (compress_result.is_error()) {
return fpromise::error(compress_result.take_error());
}
}
memset(data.data(), default_fill_value.value_or(0), data.size());
while (default_fill_remaining_bytes > 0) {
uint64_t bytes_to_write = std::min(kReadBufferSize, default_fill_remaining_bytes);
default_fill_remaining_bytes -= bytes_to_write;
auto buffer_view = cpp20::span(data.data(), bytes_to_write);
auto compress_result = compressor->Compress(buffer_view);
if (compress_result.is_error()) {
return fpromise::error(compress_result.take_error());
}
}
}
}
auto finalize_result = compressor->Finalize();
if (finalize_result.is_error()) {
return finalize_result.take_error_result();
}
// |current_offset| now contains the total written bytes.
return fpromise::ok(current_offset);
}
bool AddRange(std::map<uint64_t, uint64_t>& existing_ranges, uint64_t start, uint64_t length) {
auto end = start + length;
for (auto [cur_start, cur_end] : existing_ranges) {
// disjoint sets dont overlap.
if (cur_end > start && cur_start < end) {
return false;
}
}
existing_ranges[start] = start + length;
return true;
}
// Reader implementation that shares ownership of a reader with other instances.
class SharedReader final : public Reader {
public:
SharedReader(uint64_t offset, uint64_t length, std::shared_ptr<Reader> image_reader)
: offset_(offset), length_(length), image_reader_(std::move(image_reader)) {}
uint64_t length() const final { return length_; }
fpromise::result<void, std::string> Read(uint64_t offset,
cpp20::span<uint8_t> buffer) const final {
if (offset + buffer.size() > length_) {
return fpromise::error("SharedReader::Read out of bounds. Offset: " + std::to_string(offset) +
" Length: " + std::to_string(buffer.size()) +
" Max Length: " + std::to_string(length_) + ".");
}
return image_reader_->Read(offset_ + offset, buffer);
}
private:
uint64_t offset_ = 0;
uint64_t length_ = 0;
std::shared_ptr<Reader> image_reader_;
};
} // namespace
namespace fvm_sparse_internal {
uint32_t GetImageFlags(const FvmOptions& options) {
uint32_t flags = 0;
switch (options.compression.schema) {
case CompressionSchema::kLz4:
flags |= fvm::kSparseFlagLz4;
flags |= fvm::kSparseFlagZeroFillNotRequired;
break;
case CompressionSchema::kNone:
flags &= ~fvm::kSparseFlagLz4;
flags &= ~fvm::kSparseFlagZeroFillNotRequired;
break;
default:
break;
}
return flags;
}
uint32_t GetPartitionFlags(const Partition& partition) {
uint32_t flags = 0;
// TODO(jfsulliv): Propagate all kSparseFlags.
switch (partition.volume().encryption) {
case EncryptionType::kZxcrypt:
flags |= fvm::kSparseFlagZxcrypt;
break;
case EncryptionType::kNone:
flags &= ~fvm::kSparseFlagZxcrypt;
break;
default:
break;
}
flags |= fvm::kSparseFlagZeroFillNotRequired;
for (const auto& mapping : partition.address().mappings) {
if (mapping.options.find(EnumAsString(AddressMapOption::kFill)) != mapping.options.end()) {
flags &= ~fvm::kSparseFlagZeroFillNotRequired;
}
}
return flags;
}
fvm::SparseImage GenerateHeader(const FvmDescriptor& descriptor) {
fvm::SparseImage sparse_image_header = {};
sparse_image_header.magic = fvm::kSparseFormatMagic;
sparse_image_header.version = fvm::kSparseFormatVersion;
sparse_image_header.slice_size = descriptor.options().slice_size;
sparse_image_header.partition_count = descriptor.partitions().size();
sparse_image_header.maximum_disk_size = descriptor.options().max_volume_size.value_or(0);
sparse_image_header.flags = fvm_sparse_internal::GetImageFlags(descriptor.options());
unsigned int extent_count = 0;
for (const auto& partition : descriptor.partitions()) {
extent_count += partition.address().mappings.size();
}
sparse_image_header.header_length =
sizeof(fvm::PartitionDescriptor) * descriptor.partitions().size() +
sizeof(fvm::ExtentDescriptor) * extent_count + sizeof(fvm::SparseImage);
return sparse_image_header;
}
fpromise::result<PartitionEntry, std::string> GeneratePartitionEntry(uint64_t slice_size,
const Partition& partition,
bool extents_are_filled) {
PartitionEntry partition_entry = {};
partition_entry.descriptor.magic = fvm::kPartitionDescriptorMagic;
memcpy(partition_entry.descriptor.name, partition.volume().name.data(),
partition.volume().name.size());
memcpy(partition_entry.descriptor.type, partition.volume().type.data(),
partition.volume().type.size());
// TODO(gevalentino): Propagate instance guid, needs support from the sparse format.
partition_entry.descriptor.extent_count =
safemath::checked_cast<uint32_t>(partition.address().mappings.size());
partition_entry.descriptor.flags = fvm_sparse_internal::GetPartitionFlags(partition);
for (const auto& mapping : partition.address().mappings) {
uint64_t size = std::max(mapping.count, mapping.size.value_or(0));
uint64_t slice_count = GetBlockCount(mapping.target, size, slice_size);
uint64_t slice_offset = GetBlockFromBytes(mapping.target, slice_size);
if (!IsOffsetBlockAligned(mapping.target, slice_size)) {
return fpromise::error("Partition " + partition.volume().name +
" contains unaligned mapping " + std::to_string(mapping.target) +
". FVM Sparse Image requires slice aligned extent |vslice_start|.");
}
fvm::ExtentDescriptor extent_entry = {};
extent_entry.magic = fvm::kExtentDescriptorMagic;
extent_entry.slice_start = slice_offset;
extent_entry.slice_count = slice_count;
extent_entry.extent_length = mapping.count;
if (extents_are_filled &&
(mapping.options.find(EnumAsString(AddressMapOption::kFill)) != mapping.options.end())) {
extent_entry.extent_length = slice_count * slice_size;
}
partition_entry.extents.push_back(extent_entry);
}
return fpromise::ok(partition_entry);
}
uint64_t CalculateUncompressedImageSize(const FvmDescriptor& descriptor) {
uint64_t image_size = sizeof(fvm::SparseImage);
for (const auto& partition : descriptor.partitions()) {
image_size += sizeof(fvm::PartitionDescriptor);
for (const auto& mapping : partition.address().mappings) {
// Account for extent size, in the current format trailing zeroes are omitted,
// and later filled as the difference between extent_length and slice_count * slice_size.
image_size += mapping.count;
// Extent descriptor size.
image_size += sizeof(fvm::ExtentDescriptor);
}
}
return image_size;
}
fpromise::result<fvm::SparseImage, std::string> GetHeader(uint64_t offset, const Reader& reader) {
fvm::SparseImage header = {};
auto header_buffer = FixedSizeStructToSpan(header);
auto header_read_result = reader.Read(offset, header_buffer);
if (header_read_result.is_error()) {
return header_read_result.take_error_result();
}
if (header.magic != fvm::kSparseFormatMagic) {
return fpromise::error("Fvm Sparse Image header |magic| is incorrect. Expected " +
std::to_string(fvm::kSparseFormatMagic) + ", but found " +
std::to_string(header.magic) + ".");
}
if (header.version != fvm::kSparseFormatVersion) {
return fpromise::error("Fvm Sparse Image header |version| is incorrect. Expected " +
std::to_string(fvm::kSparseFormatVersion) + ", but found " +
std::to_string(header.version) + ".");
}
if ((header.flags & ~fvm::kSparseFlagAllValid) != 0) {
return fpromise::error(
"Fvm Sparse Image header |flags| contains invalid values. Found " +
std::bitset<sizeof(fvm::SparseImage::flags)>(header.flags).to_string() + " valid flags " +
std::bitset<sizeof(fvm::SparseImage::flags)>(fvm::kSparseFlagAllValid).to_string());
}
if (header.header_length < sizeof(fvm::SparseImage)) {
return fpromise::error("Fvm Sparse Image header |header_length| must be at least " +
std::to_string(sizeof(fvm::SparseImage)) + ", but was " +
std::to_string(header.header_length) + ".");
}
if (header.slice_size == 0) {
return fpromise::error("Fvm Sparse Image header |slice_size| must be non zero.");
}
return fpromise::ok(header);
}
fpromise::result<std::vector<PartitionEntry>, std::string> GetPartitions(
uint64_t offset, const Reader& reader, const fvm::SparseImage& header) {
std::vector<PartitionEntry> partitions(header.partition_count);
uint64_t current_offset = offset;
// Check partitions and extents.
for (uint64_t i = 0; i < header.partition_count; ++i) {
PartitionEntry& partition = partitions[i];
auto partition_read_result =
reader.Read(current_offset, FixedSizeStructToSpan(partition.descriptor));
if (partition_read_result.is_error()) {
return partition_read_result.take_error_result();
}
if (partition.descriptor.magic != fvm::kPartitionDescriptorMagic) {
return fpromise::error(
"Fvm Sparse Image Partition descriptor contains incorrect magic. Expected " +
std::to_string(fvm::kPartitionDescriptorMagic) + ", but found " +
std::to_string(partition.descriptor.magic) + ".");
}
if ((partition.descriptor.flags & ~fvm::kSparseFlagAllValid) != 0) {
return fpromise::error("Fvm Sparse Image Partition descriptor contains unknown flags.");
}
current_offset += sizeof(fvm::PartitionDescriptor);
std::map<uint64_t, uint64_t> allocated_ranges;
for (uint32_t j = 0; j < partition.descriptor.extent_count; ++j) {
fvm::ExtentDescriptor extent = {};
auto extent_read_result = reader.Read(current_offset, FixedSizeStructToSpan(extent));
if (extent_read_result.is_error()) {
return extent_read_result.take_error_result();
}
if (extent.magic != fvm::kExtentDescriptorMagic) {
return fpromise::error("Fvm Sparse Image Partition " + std::to_string(i) +
" extent descriptor " + std::to_string(j) +
" contains invalid magic. Expected " +
std::to_string(fvm::kExtentDescriptorMagic) + ", but found " +
std::to_string(extent.magic) + ".");
}
if (extent.extent_length > extent.slice_count * header.slice_size) {
return fpromise::error("Fvm Sparse Image Partition " + std::to_string(i) +
" extent descriptor " + std::to_string(j) + " extent length(" +
std::to_string(extent.extent_length) +
") exceeds the allocated slice range(" +
std::to_string(extent.slice_count * header.slice_size) + "), " +
std::to_string(extent.slice_count) + " allocated slices of size " +
std::to_string(header.slice_size) + ".");
}
if (!AddRange(allocated_ranges, extent.slice_start, extent.slice_count)) {
return fpromise::error("Fvm Sparse Image Partition " + std::to_string(i) +
" extent descriptor " + std::to_string(j) +
" contains overlapping slice ranges.");
}
current_offset += sizeof(fvm::ExtentDescriptor);
partition.extents.push_back(extent);
}
}
return fpromise::ok(partitions);
}
CompressionOptions GetCompressionOptions(const fvm::SparseImage& header) {
CompressionOptions options;
options.schema = CompressionSchema::kNone;
if ((header.flags & fvm::kSparseFlagLz4) != 0) {
options.schema = CompressionSchema::kLz4;
}
return options;
}
fpromise::result<fvm::Header, std::string> ConvertToFvmHeader(
const fvm::SparseImage& sparse_header, uint64_t slice_count,
const std::optional<FvmOptions>& options) {
// Generate the appropiate FVM header.
std::optional<uint64_t> max_volume_size;
std::optional<uint64_t> target_volume_size;
if (sparse_header.maximum_disk_size > 0) {
max_volume_size = sparse_header.maximum_disk_size;
}
if (options.has_value()) {
if (options->max_volume_size.has_value()) {
max_volume_size = options->max_volume_size;
}
if (options->target_volume_size.has_value()) {
target_volume_size = options->target_volume_size;
}
}
fvm::Header header =
fvm::Header::FromSliceCount(fvm::kMaxUsablePartitions, slice_count, sparse_header.slice_size);
// Fit to the provided slices.
if (!target_volume_size.has_value() && !max_volume_size.has_value()) {
return fpromise::ok(header);
}
if (max_volume_size.has_value() && max_volume_size.value() > 0) {
if (max_volume_size.value() < header.fvm_partition_size) {
return fpromise::error("|max_volume_size|(" + std::to_string(max_volume_size.value()) +
") is smaller than the required space(" +
std::to_string(header.fvm_partition_size) + ") for " +
std::to_string(slice_count) + " slices of size(" +
std::to_string(sparse_header.slice_size) + ").");
}
header = fvm::Header::FromGrowableDiskSize(
fvm::kMaxUsablePartitions, target_volume_size.value_or(header.fvm_partition_size),
max_volume_size.value(), sparse_header.slice_size);
// When the metadata is big enough, there wont be space for the slices, this will update the
// minimum partition size to match that of a minimum number of slices, when there is no targeted
// volume size.
if (header.pslice_count == 0 && !target_volume_size.has_value()) {
header.SetSliceCount(slice_count);
}
} else {
header = fvm::Header::FromDiskSize(fvm::kMaxUsablePartitions,
target_volume_size.value_or(header.fvm_partition_size),
sparse_header.slice_size);
}
if (slice_count > header.GetAllocationTableUsedEntryCount()) {
return fpromise::error(
"Fvm Sparse Image Reader found " + std::to_string(slice_count) +
" slices, but |max_volume_size|(" + std::to_string(max_volume_size.value_or(0)) +
") with expected volume size(" + std::to_string(header.fvm_partition_size) + ") allows " +
std::to_string(header.GetAllocationTableUsedEntryCount()) + " slices");
}
return fpromise::ok(header);
}
fpromise::result<fvm::Metadata, std::string> ConvertToFvmMetadata(
const fvm::Header& header, cpp20::span<const PartitionEntry> partition_entries) {
std::vector<fvm::VPartitionEntry> vpartition_entries;
std::vector<fvm::SliceEntry> slice_entries;
vpartition_entries.reserve(partition_entries.size());
uint64_t current_vpartition = 0;
for (const auto& partition_entry : partition_entries) {
uint64_t slice_count = 0;
for (const auto& extent_entry : partition_entry.extents) {
for (uint64_t i = 0; i < extent_entry.slice_count; ++i) {
fvm::SliceEntry entry = {};
entry.Set(current_vpartition + 1, extent_entry.slice_start + i);
slice_entries.push_back(entry);
}
slice_count += extent_entry.slice_count;
}
fvm::VPartitionEntry vpartition_entry = {};
memcpy(vpartition_entry.unsafe_name, partition_entry.descriptor.name,
sizeof(fvm::VPartitionEntry::unsafe_name));
memcpy(vpartition_entry.type, partition_entry.descriptor.type,
sizeof(fvm::VPartitionEntry::type));
memcpy(vpartition_entry.guid, fvm::kPlaceHolderInstanceGuid.data(),
sizeof(fvm::VPartitionEntry::type));
// Currently non of the sparse partition flags propagate anything to VPartition::flags.
// TODO(gevalentino): hide this behind an API, so we can have a single point of translation.
vpartition_entry.flags = 0;
vpartition_entry.slices = safemath::checked_cast<uint32_t>(slice_count);
vpartition_entries.push_back(vpartition_entry);
current_vpartition++;
}
auto metadata_or =
fvm::Metadata::Synthesize(header, vpartition_entries.data(), vpartition_entries.size(),
slice_entries.data(), slice_entries.size());
if (metadata_or.is_error()) {
return fpromise::error("Failed to synthesize metadata. Returned code : " +
std::to_string(metadata_or.error_value()));
}
return fpromise::ok(std::move(metadata_or.value()));
}
} // namespace fvm_sparse_internal
fpromise::result<uint64_t, std::string> FvmSparseWriteImage(const FvmDescriptor& descriptor,
Writer* writer,
Compressor* compressor) {
if (compressor == nullptr) {
NoopCompressor noop_compressor;
return FvmSparseWriteImageInternal(descriptor, writer, &noop_compressor);
}
return FvmSparseWriteImageInternal(descriptor, writer, compressor);
}
fpromise::result<bool, std::string> FvmSparseDecompressImage(uint64_t offset, const Reader& reader,
Writer& writer) {
auto header_or = fvm_sparse_internal::GetHeader(offset, reader);
if (header_or.is_error()) {
return header_or.take_error_result();
}
// Check that everything looks good metadata wise, that is that partition and extent descriptors
// are well formed, so we can abort early on any error. The entries themselves are unimportant for
// decompressing the image.
auto partition_entries_or =
fvm_sparse_internal::GetPartitions(sizeof(fvm::SparseImage), reader, header_or.value());
if (partition_entries_or.is_error()) {
return partition_entries_or.take_error_result();
}
auto compression_options = fvm_sparse_internal::GetCompressionOptions(header_or.value());
if (compression_options.schema == CompressionSchema::kNone) {
return fpromise::ok(false);
}
uint64_t accumulated_offset = 0;
// Copy the header and partition info first.
std::vector<uint8_t> metadata_buffer;
metadata_buffer.resize(header_or.value().header_length, 0);
auto metadata_read_result = reader.Read(0, metadata_buffer);
if (metadata_read_result.is_error()) {
return metadata_read_result.take_error_result();
}
// Remove the compression flag.
header_or.value().flags ^= fvm::kSparseFlagLz4;
memcpy(metadata_buffer.data(), &header_or.value(), sizeof(fvm::SparseImage));
auto metadata_write_result = writer.Write(0, metadata_buffer);
if (metadata_write_result.is_error()) {
return metadata_write_result.take_error_result();
}
accumulated_offset += header_or.value().header_length;
auto decompressor_or = Lz4Decompressor::Create(compression_options);
if (decompressor_or.is_error()) {
return decompressor_or.take_error_result();
}
auto decompressor = decompressor_or.take_value();
auto write_decompressed =
[&accumulated_offset, &writer](
cpp20::span<const uint8_t> decompressed_data) -> fpromise::result<void, std::string> {
auto write_result = writer.Write(accumulated_offset, decompressed_data);
if (write_result.is_error()) {
return write_result;
}
accumulated_offset += decompressed_data.size();
return fpromise::ok();
};
auto prepare_or = decompressor.Prepare(write_decompressed);
if (prepare_or.is_error()) {
return prepare_or.take_error_result();
}
std::vector<uint8_t> compressed_data;
constexpr uint64_t kMaxBufferSize = (64 << 10);
compressed_data.resize(std::min(kMaxBufferSize, reader.length()), 0);
uint64_t read_offset = header_or.value().header_length;
uint64_t last_hint = reader.length();
while (read_offset < reader.length()) {
auto compressed_view = cpp20::span<uint8_t>(compressed_data);
if (compressed_view.size() > reader.length() - read_offset) {
compressed_view = compressed_view.subspan(0, reader.length() - read_offset);
}
if (last_hint < compressed_view.size()) {
compressed_view = compressed_view.subspan(0, last_hint);
}
decompressor.ProvideSizeHint(compressed_view.size());
auto read_or = reader.Read(read_offset, compressed_view);
if (read_or.is_error()) {
return read_or.take_error_result();
}
auto decompressed_or = decompressor.Decompress(compressed_view);
if (decompressed_or.is_error()) {
return decompressed_or.take_error_result();
}
auto [hint, read_bytes] = decompressed_or.value();
// Decompression finished.
if (hint == 0) {
auto finalize_result = decompressor.Finalize();
if (finalize_result.is_error()) {
return finalize_result.take_error_result();
}
break;
}
read_offset += read_bytes;
if (hint > compressed_data.size()) {
compressed_data.resize(hint, 0);
}
last_hint = hint;
}
return fpromise::ok(true);
}
fpromise::result<FvmDescriptor, std::string> FvmSparseReadImage(uint64_t offset,
std::unique_ptr<Reader> reader) {
if (!reader) {
return fpromise::error("Invalid |reader| for reading sparse image.");
}
std::shared_ptr<Reader> image_reader(reader.release());
auto header_or = fvm_sparse_internal::GetHeader(offset, *image_reader);
if (header_or.is_error()) {
return header_or.take_error_result();
}
auto header = header_or.take_value();
// Get the partition entries.
auto partition_entries_or =
fvm_sparse_internal::GetPartitions(sizeof(fvm::SparseImage), *image_reader, header);
if (partition_entries_or.is_error()) {
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) {
options.max_volume_size = header.maximum_disk_size;
}
FvmDescriptor::Builder builder;
builder.SetOptions(options);
// Generate the address map for each partition entry.
uint64_t image_extent_offset = header.header_length;
for (auto& partition_entry : partition_entries_or.value()) {
VolumeDescriptor volume_descriptor;
AddressDescriptor address_descriptor;
volume_descriptor.encryption = (partition_entry.descriptor.flags & fvm::kSparseFlagZxcrypt) != 0
? EncryptionType::kZxcrypt
: EncryptionType::kNone;
std::string_view name(reinterpret_cast<const char*>(partition_entry.descriptor.name),
sizeof(fvm::PartitionDescriptor::name));
name = name.substr(0, name.find('\0'));
volume_descriptor.name = name;
memcpy(volume_descriptor.instance.data(), fvm::kPlaceHolderInstanceGuid.data(),
sizeof(VolumeDescriptor::type));
memcpy(volume_descriptor.type.data(), partition_entry.descriptor.type,
sizeof(VolumeDescriptor::type));
if ((partition_entry.descriptor.flags & fvm::kSparseFlagCorrupted) != 0) {
volume_descriptor.options.insert(Option::kEmpty);
}
uint64_t accumulated_extent_offset = 0;
for (const auto& extent : partition_entry.extents) {
AddressMap mapping;
mapping.count = extent.extent_length;
mapping.source = accumulated_extent_offset;
mapping.target = extent.slice_start * options.slice_size;
mapping.size = extent.slice_count * options.slice_size;
if ((header.flags & fvm::kSparseFlagZeroFillNotRequired) == 0) {
mapping.options[EnumAsString(AddressMapOption::kFill)] = 0;
}
address_descriptor.mappings.push_back(mapping);
accumulated_extent_offset += extent.extent_length;
}
// 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)));
}
return builder.Build();
}
} // namespace storage::volume_image