blob: daff3601c40f2fe77a03e674a27bff81142461a1 [file] [log] [blame]
// Copyright 2018 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/blobfs/allocator/allocator.h"
#include <inttypes.h>
#include <lib/fzl/resizeable-vmo-mapper.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/trace/event.h>
#include <lib/zx/result.h>
#include <lib/zx/vmo.h>
#include <zircon/compiler.h>
#include <zircon/errors.h>
#include <zircon/types.h>
#include <cstdint>
#include <memory>
#include <mutex>
#include <utility>
#include <vector>
#include <bitmap/raw-bitmap.h>
#include <fbl/algorithm.h>
#include <safemath/safe_conversions.h>
#include <storage/buffer/owned_vmoid.h>
#include "src/storage/blobfs/allocator/base_allocator.h"
#include "src/storage/blobfs/common.h"
#include "src/storage/blobfs/format.h"
#include "src/storage/lib/vfs/cpp/transaction/device_transaction_handler.h"
namespace blobfs {
Allocator::Allocator(SpaceManager* space_manager, RawBitmap block_map,
fzl::ResizeableVmoMapper node_map,
std::unique_ptr<id_allocator::IdAllocator> node_bitmap)
: BaseAllocator(std::move(block_map), std::move(node_bitmap)),
space_manager_(space_manager),
node_map_(std::move(node_map)) {}
zx::result<InodePtr> Allocator::GetNode(uint32_t node_index) __TA_NO_THREAD_SAFETY_ANALYSIS {
if (node_index >= node_map_.size() / kBlobfsInodeSize) {
return zx::error(ZX_ERR_INVALID_ARGS);
}
// TODO(https://fxbug.dev/42160756): Calling lock_shared from a thread that already holds the lock is
// undefined behaviour.
node_map_grow_mutex_.lock_shared();
return zx::ok(
InodePtr(&reinterpret_cast<Inode*>(node_map_.start())[node_index], InodePtrDeleter(this)));
}
zx_status_t Allocator::ResetFromStorage(fs::DeviceTransactionHandler& transaction_handler) {
ZX_DEBUG_ASSERT(ReservedBlockCount() == 0);
ZX_DEBUG_ASSERT(ReservedNodeCount() == 0);
// Ensure the block and node maps are up-to-date with changes in size that
// might have happened.
zx_status_t status;
if ((status = ResetBlockMapSize()) != ZX_OK) {
return status;
}
if ((status = ResetNodeMapSize()) != ZX_OK) {
return status;
}
storage::OwnedVmoid block_map_vmoid;
storage::OwnedVmoid node_map_vmoid;
// TODO(https://fxbug.dev/42126023): Change to use fpromise::result<OwnedVmo, zx_status_t>.
status = space_manager_->BlockAttachVmo(GetBlockMapVmo(),
&block_map_vmoid.GetReference(space_manager_));
if (status != ZX_OK) {
return status;
}
status =
space_manager_->BlockAttachVmo(GetNodeMapVmo(), &node_map_vmoid.GetReference(space_manager_));
if (status != ZX_OK) {
return status;
}
const auto info = space_manager_->Info();
std::vector<storage::BufferedOperation> operations;
operations.push_back({.vmoid = block_map_vmoid.get(),
.op = {
.type = storage::OperationType::kRead,
.vmo_offset = 0,
.dev_offset = BlockMapStartBlock(info),
.length = BlockMapBlocks(info),
}});
operations.push_back({.vmoid = node_map_vmoid.get(),
.op = {
.type = storage::OperationType::kRead,
.vmo_offset = 0,
.dev_offset = NodeMapStartBlock(info),
.length = NodeMapBlocks(info),
}});
return transaction_handler.RunRequests(operations);
}
const zx::vmo& Allocator::GetBlockMapVmo() const {
return GetBlockBitmap().StorageUnsafe()->GetVmo();
}
const zx::vmo& Allocator::GetNodeMapVmo() const { return node_map_.vmo(); }
zx::result<ReservedNode> Allocator::ReserveNode() {
TRACE_DURATION("blobfs", "ReserveNode");
return BaseAllocator::ReserveNode();
}
zx_status_t Allocator::ResetBlockMapSize() {
ZX_DEBUG_ASSERT(ReservedBlockCount() == 0);
const auto info = space_manager_->Info();
uint64_t new_size = info.data_block_count;
RawBitmap& block_bitmap = GetBlockBitmap();
if (new_size != block_bitmap.size()) {
uint64_t rounded_size = BlockMapBlocks(info) * kBlobfsBlockBits;
if (zx_status_t status = block_bitmap.Reset(rounded_size); status != ZX_OK) {
return status;
}
if (new_size < rounded_size) {
// In the event that the requested block count is not a multiple of the nearest block size,
// shrink down to the actual block count.
if (zx_status_t status = block_bitmap.Shrink(new_size); status != ZX_OK) {
return status;
}
}
}
return ZX_OK;
}
zx_status_t Allocator::ResetNodeMapSize() {
ZX_DEBUG_ASSERT(ReservedNodeCount() == 0);
const auto info = space_manager_->Info();
uint64_t nodemap_size = kBlobfsInodeSize * info.inode_count;
zx_status_t status = ZX_OK;
if (fbl::round_up(nodemap_size, kBlobfsBlockSize) != nodemap_size) {
return ZX_ERR_BAD_STATE;
}
ZX_DEBUG_ASSERT(nodemap_size / kBlobfsBlockSize == NodeMapBlocks(info));
if (nodemap_size > node_map_.size()) {
status = GrowNodeMap(nodemap_size);
} else if (nodemap_size < node_map_.size()) {
// It is safe to shrink node_map_ without a lock because the mapping won't change in that case.
status = node_map_.Shrink(nodemap_size);
}
if (status != ZX_OK) {
return status;
}
return GetNodeBitmap().Reset(info.inode_count);
}
void Allocator::LogAllocationFailure(uint64_t num_blocks) const {
const Superblock& info = space_manager_->Info();
const uint64_t requested_bytes = num_blocks * info.block_size;
const uint64_t total_bytes = info.data_block_count * info.block_size;
const uint64_t persisted_used_bytes = info.alloc_block_count * info.block_size;
const uint64_t pending_used_bytes = ReservedBlockCount() * info.block_size;
const uint64_t used_bytes = persisted_used_bytes + pending_used_bytes;
ZX_ASSERT_MSG(used_bytes <= total_bytes,
"blobfs using more bytes than available: %" PRIu64 " > %" PRIu64 "\n", used_bytes,
total_bytes);
const uint64_t free_bytes = total_bytes - used_bytes;
if (!log_allocation_failure_) {
return;
}
FX_LOGS(ERROR) << "Blobfs has run out of space on persistent storage.";
FX_LOGS(ERROR) << " Could not allocate " << requested_bytes << " bytes";
FX_LOGS(ERROR) << " Total data bytes : " << total_bytes;
FX_LOGS(ERROR) << " Used data bytes : " << persisted_used_bytes;
FX_LOGS(ERROR) << " Preallocated bytes: " << pending_used_bytes;
FX_LOGS(ERROR) << " Free data bytes : " << free_bytes;
FX_LOGS(ERROR) << " This allocation failure is the result of "
<< (requested_bytes <= free_bytes ? "fragmentation" : "over-allocation");
}
zx_status_t Allocator::GrowNodeMap(size_t size) {
std::scoped_lock lock(node_map_grow_mutex_);
return node_map_.Grow(size);
}
// TODO(https://fxbug.dev/42080556): change this to __TA_RELEASE_SHARED(node_map_grow_mutex_) after clang
// roll.
void Allocator::DropInodePtr() __TA_NO_THREAD_SAFETY_ANALYSIS {
node_map_grow_mutex_.unlock_shared();
}
zx::result<> Allocator::AddBlocks(uint64_t block_count) {
if (zx_status_t status = space_manager_->AddBlocks(block_count, &GetBlockBitmap());
status != ZX_OK) {
LogAllocationFailure(block_count);
return zx::make_result(status);
}
return zx::ok();
}
zx::result<> Allocator::AddNodes() {
zx_status_t status = space_manager_->AddInodes(this);
if (status != ZX_OK) {
return zx::make_result(status);
}
auto inode_count = space_manager_->Info().inode_count;
status = GetNodeBitmap().Grow(inode_count);
// This is an awkward situation where we could secure storage but potentially ran out of [virtual]
// memory. There is nothing much we can do. The filesystem might fail soon from other alloc
// failures. It is better to turn the fs-mount into read-only instance or panic to safe-guard
// against any damage rather than propagating these errors.
//
// One alternative considered was to reorder memory allocation first and then allocate disk.
// Reordering just delays the problem and also to reorder this layer needs to know details like
// what is fvm slice size is. We decided against that route.
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to grow bitmap for inodes";
}
return zx::make_result(status);
}
void Allocator::Decommit() {
const auto& bitmap = GetNodeBitmap();
const size_t blobs_per_page = zx_system_get_page_size() / sizeof(Inode);
size_t start = 0, count = 0;
size_t decommit_total = 0;
for (;;) {
if (start + count >= bitmap.Size() || bitmap.IsBusy(start + count)) {
if (start + count >= bitmap.Size()) {
// Round up at the end.
count += blobs_per_page - 1;
}
count -= count % blobs_per_page;
if (count >= blobs_per_page) {
const uint64_t size = sizeof(Inode) * count;
if (zx_status_t status = node_map_.vmo().op_range(ZX_VMO_OP_DECOMMIT, start * sizeof(Inode),
size, nullptr, 0);
status != ZX_OK) {
FX_PLOGS(WARNING, status) << "Failed to decommit unused node map";
return;
}
decommit_total += size;
}
start += count + blobs_per_page;
if (start >= bitmap.Size())
break;
count = 0;
} else {
++count;
}
}
if (decommit_total > 0) {
FX_LOGS(INFO) << "Decommitted " << decommit_total << " bytes from the node map";
}
}
} // namespace blobfs