blob: 69c276329d616b2cde001ed569f94263bf0f87d7 [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 "zstd-compressed-block-collection.h"
#include <lib/fzl/vmo-mapper.h>
#include <lib/trace/event.h>
#include <stddef.h>
#include <stdint.h>
#include <zircon/device/block.h>
#include <zircon/status.h>
#include <zircon/types.h>
#include <cstring>
#include <limits>
#include <memory>
#include <blobfs/node-finder.h>
#include <fs/trace.h>
#include <storage/buffer/owned_vmoid.h>
#include "allocator/allocator.h"
#include "iterator/allocated-extent-iterator.h"
#include "iterator/block-iterator.h"
#include "zstd-seekable-block-cache.h"
#include "zstd-seekable.h"
namespace blobfs {
ZSTDCompressedBlockCollectionImpl::ZSTDCompressedBlockCollectionImpl(
fzl::VmoMapper* vmo_mapper, storage::OwnedVmoid* vmoid, uint32_t num_vmo_blocks,
SpaceManager* space_manager, fs::LegacyTransactionHandler* txn_handler, NodeFinder* node_finder,
uint32_t node_index, uint32_t num_merkle_blocks)
: vmo_mapper_(vmo_mapper),
vmoid_(vmoid),
num_vmo_blocks_(num_vmo_blocks),
space_manager_(space_manager),
txn_handler_(txn_handler),
node_finder_(node_finder),
node_index_(node_index),
num_merkle_blocks_(num_merkle_blocks) {
InodePtr inode = node_finder->GetNode(node_index);
ZX_DEBUG_ASSERT(inode != nullptr);
ZX_DEBUG_ASSERT(inode->block_count >= num_merkle_blocks);
const uint32_t num_data_blocks = inode->block_count - num_merkle_blocks;
if (num_data_blocks > 0) {
cache_ = std::make_unique<ZSTDSeekableDefaultBlockCache>(num_data_blocks);
}
}
zx_status_t ZSTDCompressedBlockCollectionImpl::Read(uint32_t data_block_offset,
uint32_t num_blocks) {
TRACE_DURATION("blobfs", "ZSTDCompressedBlockCollectionImpl::Read", "node index", node_index_,
"data block offset", data_block_offset, "number of blocks", num_blocks);
fs::ReadTxn txn(txn_handler_);
uint64_t blob_block_offset64 =
static_cast<uint64_t>(num_merkle_blocks_) + static_cast<uint64_t>(data_block_offset);
if (blob_block_offset64 > std::numeric_limits<uint32_t>::max()) {
FS_TRACE_ERROR("[blobfs][zstd] Block offset overflow\n");
return ZX_ERR_OUT_OF_RANGE;
}
uint32_t blob_block_offset = static_cast<uint32_t>(blob_block_offset64);
// Consult cache on one-block reads. Early return on cache hit.
bool first_block_cached = false;
if (cache_ != nullptr) {
TRACE_DURATION("blobfs", "ZSTDCompressedBlockCollectionImpl::Read::ReadCache");
zx_status_t status =
cache_->ReadBlock(static_cast<uint8_t*>(vmo_mapper_->start()), data_block_offset);
if (status == ZX_OK) {
if (num_blocks == 1) {
return ZX_OK;
}
first_block_cached = true;
}
}
// Iterate to blocks and enqueue reads into VMO which backs |vmoid_|.
{
TRACE_DURATION("blobfs", "ZSTDCompressedBlockCollectionImpl::Read::Iterate", "blocks",
data_block_offset + num_blocks);
BlockIterator iter(std::make_unique<AllocatedExtentIterator>(node_finder_, node_index_));
zx_status_t status = IterateToBlock(&iter, blob_block_offset);
if (status != ZX_OK) {
FS_TRACE_ERROR("[blobfs][zstd] Failed to iterate to block at offset %u: %s\n",
blob_block_offset, zx_status_get_string(status));
}
// Lookup offset to BlobFS on block device; device offsets in |StreamBlocks| are relative to
// this offset, but |txn| needs absolute block device offsets.
uint64_t dev_data_start = DataStartBlock(space_manager_->Info());
status = StreamBlocks(
&iter, num_blocks,
[&](uint64_t current_blob_block_offset, uint64_t dev_block_offset, uint32_t n_blocks) {
// Sanity check offsets. Note that this does not catch attempting to read past the end of
// the blob. This code assumes that |StreamBlocks| will return non-|ZX_OK| in that case.
if (current_blob_block_offset < blob_block_offset ||
current_blob_block_offset - blob_block_offset > num_blocks ||
current_blob_block_offset - blob_block_offset + n_blocks > num_vmo_blocks_) {
FS_TRACE_ERROR("[blobfs][zstd] Attempt to enqueue read at out-of-bounds VMO offset\n");
return ZX_ERR_OUT_OF_RANGE;
}
uint64_t actual_vmo_block_offset = current_blob_block_offset - blob_block_offset;
uint64_t actual_dev_block_offset = dev_data_start + dev_block_offset;
// Adjust offsets and number of blocks when skipping the first block (because it was
// cached).
if (first_block_cached && actual_vmo_block_offset == 0) {
// Should have early exit if only reading one block.
ZX_DEBUG_ASSERT(n_blocks > 1);
// Skip first block that would have been read (because it was cached).
txn.Enqueue(vmoid_->get(), actual_vmo_block_offset + 1, actual_dev_block_offset + 1,
n_blocks - 1);
} else {
txn.Enqueue(vmoid_->get(), actual_vmo_block_offset, actual_dev_block_offset, n_blocks);
}
return ZX_OK;
});
if (status != ZX_OK) {
return status;
}
}
// Read blocks into VMO which backs |vmoid_|.
{
TRACE_DURATION("blobfs", "ZSTDCompressedBlockCollectionImpl::Read::Transact", "blocks",
num_blocks);
txn.Transact();
}
if (cache_ != nullptr) {
TRACE_DURATION("blobfs", "ZSTDCompressedBlockCollectionImpl::Read::WriteCache");
// TODO(markdittmer): This is a tight coupling between the collection and cache. It would be
// better to delegate this logic to some kind of caching strategy object.
// Corner case: More than one block starting at first block: Include first block in cache.
if (data_block_offset == 0 && num_blocks > 1) {
cache_->WriteBlock(static_cast<uint8_t*>(vmo_mapper_->start()), 0);
}
// Usual case: Cache last block read.
cache_->WriteBlock(
static_cast<uint8_t*>(vmo_mapper_->start()) + ((num_blocks - 1) * kBlobfsBlockSize),
data_block_offset + num_blocks - 1);
}
return ZX_OK;
}
} // namespace blobfs