blob: 8af86100a2679f2c87ce9d608460f5108f38478d [file] [log] [blame]
// Copyright 2017 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/fvm/driver/vpartition_manager.h"
#include <fidl/fuchsia.hardware.block.volume/cpp/wire.h>
#include <fuchsia/hardware/block/driver/c/banjo.h>
#include <inttypes.h>
#include <lib/ddk/binding_driver.h>
#include <lib/ddk/debug.h>
#include <lib/ddk/device.h>
#include <lib/ddk/driver.h>
#include <lib/fit/defer.h>
#include <lib/fzl/owned-vmo-mapper.h>
#include <lib/sync/completion.h>
#include <lib/zircon-internal/thread_annotations.h>
#include <lib/zx/vmo.h>
#include <string.h>
#include <threads.h>
#include <unistd.h>
#include <zircon/compiler.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include <zircon/types.h>
#include <algorithm>
#include <atomic>
#include <cstdlib>
#include <limits>
#include <memory>
#include <new>
#include <sstream>
#include <utility>
#include <fbl/array.h>
#include <fbl/auto_lock.h>
#include <safemath/clamped_math.h>
#include "src/lib/uuid/uuid.h"
#include "src/storage/fvm/driver/slice_extent.h"
#include "src/storage/fvm/driver/vpartition.h"
#include "src/storage/fvm/format.h"
#include "src/storage/fvm/fvm.h"
#include "src/storage/fvm/metadata.h"
#include "src/storage/fvm/vmo_metadata_buffer.h"
namespace fvm {
namespace {
zx_status_t FvmLoadThread(void* arg) {
return reinterpret_cast<fvm::VPartitionManager*>(arg)->Load();
}
// Our GUIDs come from several places which all must agree on the size.
static_assert(kGuidSize == BLOCK_GUID_LEN);
// Comparison for Guids. Each pointer should be a buffer of kGuidSize length.
bool GuidsEqual(const uint8_t* a, const uint8_t* b) { return memcmp(a, b, kGuidSize) == 0; }
} // namespace
VPartitionManager::VPartitionManager(zx_device_t* parent, const block_info_t& info,
size_t block_op_size, const block_impl_protocol_t* bp)
: ManagerDeviceType(parent), info_(info), block_op_size_(block_op_size) {
memcpy(&bp_, bp, sizeof(*bp));
}
VPartitionManager::~VPartitionManager() = default;
// static
zx_status_t VPartitionManager::Bind(void* /*unused*/, zx_device_t* dev) {
block_info_t block_info;
block_impl_protocol_t bp;
size_t block_op_size = 0;
if (device_get_protocol(dev, ZX_PROTOCOL_BLOCK, &bp) != ZX_OK) {
zxlogf(ERROR, "block device: does not support block protocol");
return ZX_ERR_NOT_SUPPORTED;
}
bp.ops->query(bp.ctx, &block_info, &block_op_size);
auto vpm = std::make_unique<VPartitionManager>(dev, block_info, block_op_size, &bp);
zx_status_t status = vpm->DdkAdd(ddk::DeviceAddArgs("fvm")
.set_flags(DEVICE_ADD_NON_BINDABLE)
.set_inspect_vmo(vpm->diagnostics().DuplicateVmo()));
if (status != ZX_OK) {
zxlogf(ERROR, "block device: failed to DdkAdd: %s", zx_status_get_string(status));
return status;
}
// The VPartitionManager object is owned by the DDK, now that it has been
// added. It will be deleted when the device is released.
[[maybe_unused]] auto ptr = vpm.release();
return ZX_OK;
}
fvm::Header VPartitionManager::GetHeader() const {
fbl::AutoLock lock(&const_cast<VPartitionManager*>(this)->lock_);
return *GetHeaderLocked();
}
void VPartitionManager::DdkInit(ddk::InitTxn txn) {
init_txn_ = std::move(txn);
// Read vpartition table asynchronously.
int rc = thrd_create_with_name(&initialization_thread_, FvmLoadThread, this, "fvm-init");
if (rc < 0) {
zxlogf(ERROR, "block device: Could not load initialization thread");
sync_completion_signal(&worker_completed_);
// This will schedule the device to be unbound.
return init_txn_->Reply(ZX_ERR_NO_MEMORY);
}
// The initialization thread will reply to |init_txn_| once it is ready to make the
// device visible and able to be unbound.
}
zx_status_t VPartitionManager::AddPartition(std::unique_ptr<VPartition> vp,
sync_completion_t* on_visible) {
const std::string name =
GetAllocatedVPartEntry(vp->entry_index())->name() + "-p-" + std::to_string(vp->entry_index());
const size_t entry_index = vp->entry_index();
{
fbl::AutoLock lock(&lock_);
ZX_DEBUG_ASSERT(!device_bound_at_entry_[entry_index]);
device_bound_at_entry_[entry_index] = true;
}
if (zx_status_t status = VPartition::AddSignalVisible(std::move(vp), name, on_visible);
status != ZX_OK) {
fbl::AutoLock lock(&lock_);
device_bound_at_entry_[entry_index] = false;
return status;
}
return ZX_OK;
}
struct VpmIoCookie {
std::atomic<size_t> num_txns;
std::atomic<zx_status_t> status;
sync_completion_t signal;
};
static void IoCallback(void* cookie, zx_status_t status, block_op_t* op) {
VpmIoCookie* c = reinterpret_cast<VpmIoCookie*>(cookie);
if (status != ZX_OK) {
c->status.store(status);
}
if (c->num_txns.fetch_sub(1) - 1 == 0) {
sync_completion_signal(&c->signal);
}
}
zx_status_t VPartitionManager::DoIoLocked(zx_handle_t vmo, size_t off, size_t len,
uint8_t opcode) const {
const size_t block_size = info_.block_size;
size_t len_remaining = len / block_size;
size_t vmo_offset = 0;
size_t dev_offset = off / block_size;
// The operation may need to be chuncked according to the block device's limits. We don't check
// explicitly for fuchsia_hardware_block::wire::kMaxTransferUnbounded because the transfers are
// still limited to 32-bits and that constant is the largest 32-bit value.
const size_t max_transfer = info_.max_transfer_size / block_size;
const size_t num_data_txns = fbl::round_up(len_remaining, max_transfer) / max_transfer;
// Add a "FLUSH" operation to write requests.
const bool flushing = opcode == BLOCK_OPCODE_WRITE;
const size_t num_txns = num_data_txns + (flushing ? 1 : 0);
fbl::Array<uint8_t> buffer(new uint8_t[block_op_size_ * num_txns], block_op_size_ * num_txns);
VpmIoCookie cookie;
cookie.num_txns.store(num_txns);
cookie.status.store(ZX_OK);
sync_completion_reset(&cookie.signal);
for (size_t i = 0; i < num_data_txns; i++) {
size_t length = std::min(len_remaining, max_transfer);
len_remaining -= length;
block_op_t* bop = reinterpret_cast<block_op_t*>(buffer.data() + (block_op_size_ * i));
bop->command = {.opcode = opcode, .flags = 0};
bop->rw.vmo = vmo;
bop->rw.length = static_cast<uint32_t>(length);
bop->rw.offset_dev = dev_offset;
bop->rw.offset_vmo = vmo_offset;
memset(buffer.data() + (block_op_size_ * i) + sizeof(block_op_t), 0,
block_op_size_ - sizeof(block_op_t));
vmo_offset += length;
dev_offset += length;
Queue(bop, IoCallback, &cookie);
}
if (flushing) {
block_op_t* bop =
reinterpret_cast<block_op_t*>(buffer.data() + (block_op_size_ * num_data_txns));
memset(bop, 0, sizeof(*bop));
bop->command = {.opcode = BLOCK_OPCODE_FLUSH, .flags = 0};
Queue(bop, IoCallback, &cookie);
}
ZX_DEBUG_ASSERT(len_remaining == 0);
sync_completion_wait(&cookie.signal, ZX_TIME_INFINITE);
return static_cast<zx_status_t>(cookie.status.load());
}
zx_status_t VPartitionManager::Load() {
fbl::AutoLock lock(&lock_);
// Let DdkRelease know the thread was successfully created. It is guaranteed
// that DdkRelease will not be run until after we reply to |init_txn_|.
initialization_thread_started_ = true;
// Signal all threads blocked on this thread completion. Join Only happens in DdkRelease, but we
// need to block earlier to avoid races between DdkRemove and any API call.
auto singal_completion = fit::defer([this]() { sync_completion_signal(&worker_completed_); });
auto auto_detach = fit::defer([&]() TA_NO_THREAD_SAFETY_ANALYSIS {
zxlogf(ERROR, "Aborting Driver Load");
// This will schedule the device to be unbound.
if (init_txn_)
init_txn_->Reply(ZX_ERR_INTERNAL);
});
// Sanity check the device info passed to the constructor.
if (info_.block_size == 0) {
zxlogf(ERROR, "Can't have a zero block size.");
return ZX_ERR_BAD_STATE;
}
zx::vmo vmo;
zx_status_t status;
if ((status = zx::vmo::create(fvm::kBlockSize, 0, &vmo)) != ZX_OK) {
return status;
}
// Read the superblock first to find the secondary superblock.
//
// If the primary superblock is corrupt enough to cause us not to find the secondary one (the
// partition table allocation table sizes are incorrect), we won't be able to find the secondary
// and the drive will be lost. In the current design the A/B metadata primarily provides atomic
// update rather than full backup capabilities.
if ((status = DoIoLocked(vmo.get(), 0, fvm::kBlockSize, BLOCK_OPCODE_READ)) != ZX_OK) {
zxlogf(ERROR, "Failed to read first block from underlying device");
return status;
}
Header sb; // Use only to find the secondary superblock.
status = vmo.read(&sb, 0, sizeof(sb));
if (status != ZX_OK) {
return status;
}
// Cancelled before we return ZX_OK at the end of Load().
auto dump_header = fit::defer([&sb]() { zxlogf(ERROR, "%s\n", sb.ToString().c_str()); });
// Allocate a buffer big enough for the allocated metadata.
size_t metadata_vmo_size = sb.GetMetadataAllocatedBytes();
auto load_metadata = [&](size_t offset) -> zx::result<std::unique_ptr<VmoMetadataBuffer>> {
fzl::OwnedVmoMapper mapper;
zx_status_t status;
if (status = mapper.CreateAndMap(metadata_vmo_size, "fvm-metadata"); status != ZX_OK) {
return zx::error(status);
}
if (status = DoIoLocked(mapper.vmo().get(), offset, metadata_vmo_size, BLOCK_OPCODE_READ);
status != ZX_OK) {
zxlogf(ERROR, "Failed to read %lu bytes from offset %lu: %s", metadata_vmo_size, offset,
zx_status_get_string(status));
return zx::error(status);
}
return zx::ok(std::make_unique<VmoMetadataBuffer>(std::move(mapper)));
};
auto buffer_a_or = load_metadata(sb.GetSuperblockOffset(SuperblockType::kPrimary));
if (buffer_a_or.is_error()) {
return buffer_a_or.status_value();
}
auto buffer_b_or = load_metadata(sb.GetSuperblockOffset(SuperblockType::kSecondary));
if (buffer_b_or.is_error()) {
return buffer_b_or.status_value();
}
zx::result<Metadata> metadata_or = Metadata::Create(
DiskSize(), info_.block_size, std::move(buffer_a_or.value()), std::move(buffer_b_or.value()));
if (metadata_or.is_error()) {
zxlogf(ERROR, "Failed to parse fvm metadata.");
return metadata_or.status_value();
}
metadata_ = std::move(metadata_or.value());
slice_size_ = metadata_.GetHeader().slice_size;
// See if we need to grow the metadata to cover more of the underlying disk.
Header* header = GetHeaderLocked();
size_t slices_for_disk = header->GetMaxAllocationTableEntriesForDiskSize(DiskSize());
if (slices_for_disk > header->GetAllocationTableUsedEntryCount()) {
header->SetSliceCount(slices_for_disk);
// Persist the growth.
if ((status = WriteFvmLocked()) != ZX_OK) {
zxlogf(ERROR, "Persisting updated header failed.");
return status;
}
}
// Begin initializing the underlying partitions
// This will make the device visible and able to be unbound.
if (init_txn_)
init_txn_->Reply(ZX_OK);
auto_detach.cancel();
// 0th vpartition is invalid
std::unique_ptr<VPartition> vpartitions[fvm::kMaxVPartitions] = {};
bool has_updated_partitions = false;
size_t reserved_slices = 0;
// Iterate through FVM Entry table, allocating the VPartitions which
// claim to have slices.
for (size_t i = 1; i < fvm::kMaxVPartitions; i++) {
auto* entry = GetVPartEntryLocked(i);
if (entry->IsFree()) {
continue;
}
if (entry->IsInternalReservationPartition()) {
zxlogf(INFO, "Found reserved partition with %u slices", entry->slices);
reserved_slices = entry->slices;
}
// Update instance placeholder GUIDs to a newly generated guid.
if (GuidsEqual(entry->guid, kPlaceHolderInstanceGuid.data())) {
uuid::Uuid uuid = uuid::Uuid::Generate();
memcpy(entry->guid, uuid.bytes(), uuid::kUuidSize);
has_updated_partitions = true;
}
if ((status = VPartition::Create(this, i, &vpartitions[i])) != ZX_OK) {
zxlogf(ERROR, "Failed to Create vpartition %zu", i);
return status;
}
}
if (has_updated_partitions) {
if ((status = WriteFvmLocked()) != ZX_OK) {
return status;
}
}
// Iterate through the Slice Allocation table, filling the slice maps
// of VPartitions.
for (uint32_t i = 1; i <= GetHeaderLocked()->pslice_count; i++) {
const SliceEntry* entry = GetSliceEntryLocked(i);
if (entry->IsFree()) {
continue;
}
if (vpartitions[entry->VPartition()] == nullptr) {
continue;
}
// It's fine to load the slices while not holding the vpartition
// lock; no VPartition devices exist yet.
vpartitions[entry->VPartition()]->SliceSetUnsafe(entry->VSlice(), i);
pslice_allocated_count_++;
}
LogPartitionInfoLocked();
lock.release();
// Iterate through 'valid' VPartitions, and create their devices.
size_t device_count = 0;
std::vector<Diagnostics::OnMountArgs::Partition> partitions = {};
for (size_t i = 0; i < fvm::kMaxVPartitions; i++) {
if (vpartitions[i] == nullptr) {
continue;
}
VPartitionEntry* entry = GetAllocatedVPartEntry(i);
if (entry->IsInactive()) {
zxlogf(ERROR, "Freeing %u slices from inactive partition %zu (%s)", entry->slices, i,
entry->name().c_str());
FreeSlices(vpartitions[i].get(), 0, VSliceMax());
continue;
}
sync_completion_t on_visible = {};
if ((status = AddPartition(std::move(vpartitions[i]), &on_visible)) != ZX_OK) {
zxlogf(ERROR, "Failed to add partition: %s", zx_status_get_string(status));
continue;
}
sync_completion_wait(&on_visible, ZX_TIME_INFINITE);
partitions.push_back({.name = entry->name(), .num_slices = entry->slices});
device_count++;
}
diagnostics().OnMount({
.major_version = header->major_version,
.oldest_minor_version = header->oldest_minor_version,
.slice_size = header->slice_size,
.num_slices = header->pslice_count,
.partition_table_entries = header->GetPartitionTableEntryCount(),
// TODO(https://fxbug.dev/42116137): Set to the actual value when partition table size is configurable
.partition_table_reserved_entries = header->GetPartitionTableEntryCount(),
.allocation_table_entries = header->GetAllocationTableUsedEntryCount(),
.allocation_table_reserved_entries = header->GetAllocationTableAllocatedEntryCount(),
.num_reserved_slices = reserved_slices,
.partitions = std::move(partitions),
});
zxlogf(INFO, "Loaded %lu partitions, slice size=%zu", device_count, slice_size_);
// Signal that all entries have now been bound, and respond to any outstanding GetInfo requests.
std::optional<std::vector<GetInfoCompleter::Async>> get_info_requests;
{
fbl::AutoLock lock(&lock_);
using std::swap;
swap(get_info_requests, get_info_requests_);
}
if (get_info_requests) {
for (auto& request : *get_info_requests) {
ZX_DEBUG_ASSERT(request.is_reply_needed());
fidl::Arena allocator;
fidl::ObjectView<fuchsia_hardware_block_volume::wire::VolumeManagerInfo> info(allocator);
GetInfoInternal(info.get());
request.Reply(ZX_OK, info);
}
}
dump_header.cancel();
return ZX_OK;
}
zx_status_t VPartitionManager::WriteFvmLocked() {
fvm::Header* header = GetHeaderLocked();
header->generation++;
// Track the oldest minor version of the driver that has written to this FVM metadata.
if (header->oldest_minor_version > fvm::kCurrentMinorVersion)
header->oldest_minor_version = fvm::kCurrentMinorVersion;
metadata_.UpdateHash();
// This is safe as long as metadata_ was constructed with a VmoMetadataBuffer, which is the case
// in VPartitionManager::Load.
const VmoMetadataBuffer* buffer = reinterpret_cast<const VmoMetadataBuffer*>(metadata_.Get());
// Persist the changes to the inactive metadata. The active metadata is not modified.
if (zx_status_t status = DoIoLocked(buffer->vmo().get(), metadata_.GetInactiveHeaderOffset(),
header->GetMetadataUsedBytes(), BLOCK_OPCODE_WRITE);
status != ZX_OK) {
zxlogf(ERROR, "Failed to write metadata: %s", zx_status_get_string(status));
return status;
}
metadata_.SwitchActiveHeaders();
return ZX_OK;
}
zx_status_t VPartitionManager::FindFreeVPartEntryLocked(size_t* out) const {
for (size_t i = 1; i < fvm::kMaxVPartitions; i++) {
const VPartitionEntry* entry = GetVPartEntryLocked(i);
if (entry->IsFree() && !device_bound_at_entry_[i]) {
*out = i;
return ZX_OK;
}
}
return ZX_ERR_NO_SPACE;
}
zx_status_t VPartitionManager::FindFreeSliceLocked(size_t* out, size_t hint) const {
hint = std::max(hint, 1lu);
size_t slice_count = GetHeaderLocked()->GetAllocationTableUsedEntryCount();
for (size_t i = hint; i <= slice_count; i++) {
if (GetSliceEntryLocked(i)->IsFree()) {
*out = i;
return ZX_OK;
}
}
for (size_t i = 1; i < hint; i++) {
if (GetSliceEntryLocked(i)->IsFree()) {
*out = i;
return ZX_OK;
}
}
return ZX_ERR_NO_SPACE;
}
zx_status_t VPartitionManager::AllocateSlices(VPartition* vp, size_t vslice_start, size_t count) {
fbl::AutoLock lock(&lock_);
return AllocateSlicesLocked(vp, vslice_start, count);
}
zx_status_t VPartitionManager::AllocateSlicesLocked(VPartition* vp, size_t vslice_start,
size_t count) {
if (count == 0) {
return ZX_OK; // Nothing to do.
}
if (safemath::ClampAdd(vslice_start, count) > VSliceMax()) {
return ZX_ERR_INVALID_ARGS;
}
zx_status_t status = ZX_OK;
size_t hint = 0;
size_t total_slices_reserved = 0;
{
fbl::AutoLock lock(&vp->lock_);
if (vp->IsKilledLocked()) {
return ZX_ERR_BAD_STATE;
}
ZX_DEBUG_ASSERT_MSG(vp->entry_index() >= 1 && vp->entry_index() < fvm::kMaxVPartitions,
"VPartition entry index out of range.");
if (uint64_t max_slices = max_partition_sizes_[vp->entry_index()]) {
// Enforce the max partition size.
auto existing_slices = GetVPartEntryLocked(vp->entry_index())->slices;
uint64_t requested_slices = safemath::ClampAdd(existing_slices, count);
if (requested_slices > max_slices) {
return ZX_ERR_NO_SPACE;
}
}
for (size_t i = 0; i < count; i++) {
size_t pslice;
auto vslice = vslice_start + i;
if (vp->SliceGetLocked(vslice, &pslice)) {
zxlogf(ERROR, "FVM: Attempting to allocate vslice %zu that is already allocated.", vslice);
status = ZX_ERR_INVALID_ARGS;
}
// If the vslice is invalid, or there are no more free physical slices, undo all
// previous allocations.
if ((status != ZX_OK) || ((status = FindFreeSliceLocked(&pslice, hint)) != ZX_OK)) {
for (int j = safemath::checked_cast<int>(i) - 1; j >= 0; j--) {
vslice = vslice_start + j;
vp->SliceGetLocked(vslice, &pslice);
FreePhysicalSlice(vp, pslice);
vp->SliceFreeLocked(vslice);
}
return status;
}
// Allocate the slice in the partition then mark as allocated.
vp->SliceSetLocked(vslice, pslice);
AllocatePhysicalSlice(vp, pslice, vslice);
hint = pslice + 1;
}
total_slices_reserved = vp->NumSlicesLocked();
}
if ((status = WriteFvmLocked()) == ZX_OK) {
VPartitionEntry* entry = GetVPartEntryLocked(vp->entry_index());
diagnostics().UpdatePartitionMetrics(entry->name(), total_slices_reserved);
} else {
// Undo allocation in the event of failure; avoid holding VPartition lock while writing to fvm.
fbl::AutoLock lock(&vp->lock_);
for (int j = safemath::checked_cast<int>(count) - 1; j >= 0; j--) {
auto vslice = vslice_start + j;
uint64_t pslice;
// Will always return true, because partition slice allocation is synchronized.
if (vp->SliceGetLocked(vslice, &pslice)) {
FreePhysicalSlice(vp, pslice);
vp->SliceFreeLocked(vslice);
}
}
}
return status;
}
zx_status_t VPartitionManager::Upgrade(const uint8_t* old_guid, const uint8_t* new_guid) {
fbl::AutoLock lock(&lock_);
size_t old_index = 0;
size_t new_index = 0;
if (GuidsEqual(old_guid, new_guid)) {
old_guid = nullptr;
}
for (size_t i = 1; i < fvm::kMaxVPartitions; i++) {
auto entry = GetVPartEntryLocked(i);
if (entry->slices != 0) {
if (old_guid && entry->IsActive() && GuidsEqual(entry->guid, old_guid)) {
old_index = i;
} else if (entry->IsInactive() && GuidsEqual(entry->guid, new_guid)) {
new_index = i;
}
}
}
if (!new_index) {
return ZX_ERR_NOT_FOUND;
}
if (old_index) {
GetVPartEntryLocked(old_index)->SetActive(false);
}
GetVPartEntryLocked(new_index)->SetActive(true);
return WriteFvmLocked();
}
zx_status_t VPartitionManager::FreeSlices(VPartition* vp, size_t vslice_start, size_t count) {
fbl::AutoLock lock(&lock_);
return FreeSlicesLocked(vp, safemath::strict_cast<uint64_t>(vslice_start), count);
}
zx_status_t VPartitionManager::FreeSlicesLocked(VPartition* vp, uint64_t vslice_start,
size_t count) {
if (count == 0) {
return ZX_OK; // Nothing to do.
}
if (safemath::ClampAdd(vslice_start, count) > VSliceMax()) {
return ZX_ERR_INVALID_ARGS;
}
bool valid_range = false;
std::string partition_name;
size_t total_slices_reserved = 0;
{
fbl::AutoLock lock(&vp->lock_);
if (vp->IsKilledLocked())
return ZX_ERR_BAD_STATE;
auto entry = GetVPartEntryLocked(vp->entry_index());
partition_name = entry->name();
if (vslice_start == 0) {
// Special case: Freeing entire VPartition
for (auto extent = vp->ExtentBegin(); extent.IsValid(); extent = vp->ExtentBegin()) {
for (size_t i = extent->start(); i < extent->end(); i++) {
uint64_t pslice;
vp->SliceGetLocked(i, &pslice);
FreePhysicalSlice(vp, pslice);
}
vp->ExtentDestroyLocked(extent->start());
}
// Remove device, VPartition if this was a request to release all slices.
if (vp->zxdev()) {
vp->DdkAsyncRemove();
}
entry->Release();
vp->KillLocked();
valid_range = true;
} else {
for (int i = safemath::checked_cast<int>(count - 1); i >= 0; i--) {
auto vslice = vslice_start + i;
if (vp->SliceCanFree(vslice)) {
uint64_t pslice;
vp->SliceGetLocked(vslice, &pslice);
vp->SliceFreeLocked(vslice);
FreePhysicalSlice(vp, pslice);
valid_range = true;
}
}
}
total_slices_reserved = vp->NumSlicesLocked();
}
if (!valid_range) {
return ZX_ERR_INVALID_ARGS;
}
zx_status_t status = WriteFvmLocked();
if (status == ZX_OK) {
diagnostics().UpdatePartitionMetrics(partition_name, total_slices_reserved);
}
return status;
}
void VPartitionManager::GetInfoInternal(VolumeManagerInfo* info) {
fbl::AutoLock lock(&lock_);
info->slice_size = slice_size_;
info->slice_count = GetHeaderLocked()->pslice_count;
info->assigned_slice_count = pslice_allocated_count_;
info->maximum_slice_count = GetHeaderLocked()->GetAllocationTableAllocatedEntryCount();
info->max_virtual_slice = VSliceMax();
}
uint64_t VPartitionManager::GetPartitionLimitInternal(size_t index) const {
ZX_DEBUG_ASSERT(index >= 1);
ZX_DEBUG_ASSERT(index < fvm::kMaxVPartitions);
fbl::AutoLock lock(&lock_);
return max_partition_sizes_[index];
}
zx_status_t VPartitionManager::GetPartitionLimitInternal(const uint8_t* guid,
uint64_t* slice_count) const {
fbl::AutoLock lock(&lock_);
if (size_t partition = GetPartitionNumberLocked(guid)) {
*slice_count = max_partition_sizes_[partition];
return ZX_OK;
}
// The bad GUID will already have been logged by GetPartitionNumberLocked().
zxlogf(ERROR, "Unable to get partition limit, partition not found.");
*slice_count = 0;
return ZX_ERR_NOT_FOUND;
}
zx_status_t VPartitionManager::SetPartitionLimitInternal(const uint8_t* guid,
uint64_t slice_count) {
fbl::AutoLock lock(&lock_);
if (size_t partition = GetPartitionNumberLocked(guid)) {
zxlogf(INFO, "Setting partition limit to 0x%" PRIx64 " slices for partition #%zu", slice_count,
partition);
max_partition_sizes_[partition] = slice_count;
// Update Inspect diagnostic.
diagnostics().UpdateMaxBytes(GetVPartEntryLocked(partition)->name(), slice_count * slice_size_);
return ZX_OK;
}
// The partition GUID will already have been logged by GetPartitionNumberLocked().
zxlogf(ERROR, "Unable set partition limit to %" PRIu64 " slices, partition not found.",
slice_count);
// This additional logging by LogPartitionInfoLocked() about each partition was added due to
// reports of failures of this function. In the future it can be removed if we find this function
// fails in expected cases and the logging is excessive.
LogPartitionInfoLocked();
return ZX_ERR_NOT_FOUND;
}
zx_status_t VPartitionManager::SetPartitionNameInternal(const uint8_t* guid,
std::string_view name) {
fbl::AutoLock lock(&lock_);
const size_t partition = GetPartitionNumberLocked(guid);
if (!partition) {
// The partition GUID will already have been logged by GetPartitionNumberLocked().
zxlogf(ERROR, "Unable set partition name to %s, partition not found.",
std::string(name).c_str());
return ZX_ERR_NOT_FOUND;
}
VPartitionEntry* entry = GetVPartEntryLocked(partition);
zxlogf(INFO, "Renaming partition #%zu from %s to %s", partition, entry->name().c_str(),
std::string(name).c_str());
entry->set_name(name);
return WriteFvmLocked();
}
void VPartitionManager::FreePhysicalSlice(VPartition* vp, uint64_t pslice) {
auto entry = GetSliceEntryLocked(pslice);
ZX_DEBUG_ASSERT_MSG(entry->IsAllocated(), "Freeing already-free slice");
entry->Release();
GetVPartEntryLocked(vp->entry_index())->slices--;
pslice_allocated_count_--;
}
void VPartitionManager::AllocatePhysicalSlice(VPartition* vp, uint64_t pslice, uint64_t vslice) {
uint64_t vpart = vp->entry_index();
ZX_DEBUG_ASSERT(vpart <= fvm::kMaxVPartitions);
ZX_DEBUG_ASSERT(vslice <= fvm::kMaxVSlices);
auto entry = GetSliceEntryLocked(pslice);
ZX_DEBUG_ASSERT_MSG(entry->IsFree(), "Allocating previously allocated slice");
entry->Set(vpart, vslice);
GetVPartEntryLocked(vpart)->slices++;
pslice_allocated_count_++;
}
SliceEntry* VPartitionManager::GetSliceEntryLocked(size_t index) const {
const Header* header = GetHeaderLocked();
ZX_DEBUG_ASSERT(index >= 1);
ZX_DEBUG_ASSERT(index <= header->GetAllocationTableUsedEntryCount());
return &metadata_.GetSliceEntry(index);
}
VPartitionEntry* VPartitionManager::GetVPartEntryLocked(size_t index) const {
Header* header = GetHeaderLocked();
ZX_DEBUG_ASSERT(index >= 1);
ZX_DEBUG_ASSERT(index <= header->GetPartitionTableEntryCount());
return &metadata_.GetPartitionEntry(index);
}
size_t VPartitionManager::GetPartitionNumberLocked(const uint8_t* guid) const {
for (size_t i = 1; i < fvm::kMaxVPartitions; i++) {
auto* entry = GetVPartEntryLocked(i);
if (entry->IsAllocated() && GuidsEqual(entry->guid, guid))
return i;
}
zxlogf(ERROR, "Partition not found for GUID %s", uuid::Uuid(guid).ToString().c_str());
return 0;
}
void VPartitionManager::LogPartitionInfoLocked() const {
std::stringstream out;
const Header* header = GetHeaderLocked();
out << "FVM INFO: Header ";
out << (metadata_.active_header() == SuperblockType::kPrimary ? "A " : "B ");
out << *header;
// Additionally log the unallocated slices so we know how much we can grow the partitions.
size_t free_slices = 0;
for (size_t i = 1; i <= header->GetAllocationTableUsedEntryCount(); i++) {
if (GetSliceEntryLocked(i)->IsFree())
++free_slices;
}
out << " free_slices:" << free_slices;
zxlogf(INFO, "%s", out.str().c_str());
for (size_t i = 1; i < fvm::kMaxVPartitions; i++) {
if (auto* entry = GetVPartEntryLocked(i); entry->IsAllocated()) {
std::stringstream out;
out << " #" << i << ": " << *entry << " limit:" << max_partition_sizes_[i];
zxlogf(INFO, "%s", out.str().c_str());
}
}
}
// Device protocol (FVM)
zx::result<std::unique_ptr<VPartition>> VPartitionManager::AllocatePartition(
uint64_t slice_count, const fuchsia_hardware_block_partition::wire::Guid& type,
const fuchsia_hardware_block_partition::wire::Guid& instance, fidl::StringView name,
uint32_t flags) {
if (slice_count >= std::numeric_limits<uint32_t>::max()) {
return zx::error(ZX_ERR_OUT_OF_RANGE);
}
if (slice_count == 0) {
return zx::error(ZX_ERR_OUT_OF_RANGE);
}
// Validate the name. It should fit and not have any NULL terminators in it.
constexpr size_t kMaxNameLen = std::min<size_t>(
fuchsia_hardware_block_partition::wire::kNameLength, kMaxVPartitionNameLength);
if (name.size() > kMaxNameLen) {
return zx::error(ZX_ERR_INVALID_ARGS);
}
std::string name_str(name.get());
if (name_str.find('\0') != std::string::npos) {
return zx::error(ZX_ERR_INVALID_ARGS);
}
std::unique_ptr<VPartition> vpart;
{
fbl::AutoLock lock(&lock_);
size_t vpart_entry;
if (zx_status_t status = FindFreeVPartEntryLocked(&vpart_entry); status != ZX_OK) {
return zx::error(status);
}
if (zx_status_t status = VPartition::Create(this, vpart_entry, &vpart); status != ZX_OK) {
return zx::error(status);
}
auto* entry = GetVPartEntryLocked(vpart_entry);
*entry =
VPartitionEntry(type.value.data(), instance.value.data(), 0, std::move(name_str), flags);
// Each partition starts off with a 0 max length ("no limit").
max_partition_sizes_[vpart_entry] = 0;
if (zx_status_t status = AllocateSlicesLocked(vpart.get(), 0, slice_count); status != ZX_OK) {
entry->slices = 0; // Undo VPartition allocation
return zx::error(status);
}
}
return zx::ok(std::move(vpart));
}
void VPartitionManager::AllocatePartition(AllocatePartitionRequestView request,
AllocatePartitionCompleter::Sync& completer) {
auto partition_or = AllocatePartition(request->slice_count, request->type, request->instance,
request->name, request->flags);
zx_status_t status = partition_or.status_value();
if (partition_or.is_ok()) {
// Register the created partition with the device manager.
status = AddPartition(std::move(partition_or.value()), /*on_visible*/ nullptr);
}
completer.Reply(status);
}
void VPartitionManager::GetInfo(GetInfoCompleter::Sync& completer) {
// **IMPORTANT**: GetInfo() is used to synchronize visibility of partitions in devfs with clients.
// Responses to GetInfo() must only be sent once *all* child partitions have been bound and have
// been made visible. See https://fxbug.dev/42077585 for more information.
{
fbl::AutoLock lock(&lock_);
if (get_info_requests_) {
get_info_requests_->push_back(completer.ToAsync());
return; // Respond to the request when all child devices have been bound.
}
}
fidl::Arena allocator;
fidl::ObjectView<fuchsia_hardware_block_volume::wire::VolumeManagerInfo> info(allocator);
GetInfoInternal(info.get());
completer.Reply(ZX_OK, info);
}
void VPartitionManager::Activate(ActivateRequestView request, ActivateCompleter::Sync& completer) {
completer.Reply(Upgrade(request->old_guid.value.data(), request->new_guid.value.data()));
}
void VPartitionManager::GetPartitionLimit(GetPartitionLimitRequestView request,
GetPartitionLimitCompleter::Sync& completer) {
uint64_t slice_count = 0;
zx_status_t status = GetPartitionLimitInternal(request->guid.value.data(), &slice_count);
completer.Reply(status, slice_count);
}
void VPartitionManager::SetPartitionLimit(SetPartitionLimitRequestView request,
SetPartitionLimitCompleter::Sync& completer) {
completer.Reply(SetPartitionLimitInternal(request->guid.value.data(), request->slice_count));
}
void VPartitionManager::SetPartitionName(SetPartitionNameRequestView request,
SetPartitionNameCompleter::Sync& completer) {
zx_status_t status = SetPartitionNameInternal(request->guid.value.data(), request->name.get());
if (status == ZX_OK) {
completer.ReplySuccess();
} else {
completer.ReplyError(status);
}
}
void VPartitionManager::DdkUnbind(ddk::UnbindTxn txn) {
// Wait untill all work has been completed, before removing the device.
sync_completion_wait(&worker_completed_, zx::duration::infinite().get());
txn.Reply();
}
void VPartitionManager::DdkRelease() {
if (initialization_thread_started_) {
// Wait until the worker thread exits before freeing the resources.
thrd_join(initialization_thread_, nullptr);
}
delete this;
}
void VPartitionManager::DdkChildPreRelease(void* child) {
VPartition* vp = static_cast<VPartition*>(child);
fbl::AutoLock lock(&lock_);
device_bound_at_entry_[vp->entry_index()] = false;
}
zx_driver_ops_t driver_ops = {
.version = DRIVER_OPS_VERSION,
.bind = VPartitionManager::Bind,
};
} // namespace fvm
ZIRCON_DRIVER(fvm, fvm::driver_ops, "zircon", "0.1");