| // 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 <atomic> |
| #include <limits> |
| #include <new> |
| #include <string.h> |
| #include <threads.h> |
| #include <unistd.h> |
| #include <utility> |
| |
| #include <ddk/protocol/block.h> |
| #include <fbl/array.h> |
| #include <fbl/auto_call.h> |
| #include <fbl/auto_lock.h> |
| #include <fuchsia/hardware/block/volume/c/fidl.h> |
| #include <lib/fzl/owned-vmo-mapper.h> |
| #include <lib/sync/completion.h> |
| #include <lib/zx/vmo.h> |
| #include <zircon/compiler.h> |
| #include <zircon/device/block.h> |
| #include <zircon/status.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/thread_annotations.h> |
| |
| #include "fvm-private.h" |
| #include "slice-extent.h" |
| #include "vpartition.h" |
| |
| namespace fvm { |
| namespace { |
| |
| zx_status_t FvmLoadThread(void* arg) { |
| return reinterpret_cast<fvm::VPartitionManager*>(arg)->Load(); |
| } |
| |
| } // 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), pslice_allocated_count_(0), |
| block_op_size_(block_op_size) { |
| memcpy(&bp_, bp, sizeof(*bp)); |
| } |
| |
| VPartitionManager::~VPartitionManager() = default; |
| |
| // static |
| zx_status_t VPartitionManager::Bind(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) { |
| fprintf(stderr, "fvm: ERROR: block device '%s': does not support block protocol\n", |
| device_get_name(dev)); |
| 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("fvm", DEVICE_ADD_INVISIBLE); |
| if (status != ZX_OK) { |
| fprintf(stderr, "fvm: ERROR: block device '%s': failed to DdkAdd: %s\n", |
| device_get_name(dev), zx_status_get_string(status)); |
| return status; |
| } |
| |
| // Read vpartition table asynchronously. |
| int rc = |
| thrd_create_with_name(&vpm->initialization_thread_, FvmLoadThread, vpm.get(), "fvm-init"); |
| if (rc < 0) { |
| fprintf(stderr, "fvm: ERROR: block device '%s': Could not load initialization thread\n", |
| device_get_name(dev)); |
| // See comment in Load() |
| if (!vpm->device_remove_.exchange(true)) { |
| vpm->DdkRemove(); |
| } |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| // The VPartitionManager object is owned by the DDK, now that it has been |
| // added. It will be deleted when the device is released. |
| __UNUSED auto ptr = vpm.release(); |
| return ZX_OK; |
| } |
| |
| zx_status_t VPartitionManager::AddPartition(fbl::unique_ptr<VPartition> vp) const { |
| auto ename = reinterpret_cast<const char*>(GetAllocatedVPartEntry(vp->GetEntryIndex())->name); |
| char name[fvm::kMaxVPartitionNameLength + 32]; |
| snprintf(name, sizeof(name), "%.*s-p-%zu", fvm::kMaxVPartitionNameLength, ename, vp->GetEntryIndex()); |
| |
| zx_status_t status; |
| if ((status = vp->DdkAdd(name)) != ZX_OK) { |
| return status; |
| } |
| // TODO(johngro): ask smklein why it is OK to release this managed pointer. |
| __UNUSED auto ptr = vp.release(); |
| 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, |
| uint32_t command) { |
| const size_t block_size = info_.block_size; |
| const size_t max_transfer = info_.max_transfer_size / block_size; |
| size_t len_remaining = len / block_size; |
| size_t vmo_offset = 0; |
| size_t dev_offset = off / 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 = command == BLOCK_OP_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 = fbl::min(len_remaining, max_transfer); |
| len_remaining -= length; |
| |
| block_op_t* bop = reinterpret_cast<block_op_t*>(buffer.get() + (block_op_size_ * i)); |
| |
| bop->command = command; |
| 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.get() + (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.get() + (block_op_size_ * num_data_txns)); |
| memset(bop, 0, sizeof(*bop)); |
| bop->command = BLOCKIO_FLUSH; |
| 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_); |
| |
| auto auto_detach = fbl::MakeAutoCall([&]() TA_NO_THREAD_SAFETY_ANALYSIS { |
| // Need to release the lock before calling DdkRemove(), since it will |
| // free |this|. Need to disable thread safety analysis since it doesn't |
| // recognize that we were holding lock_. |
| lock.release(); |
| |
| fprintf(stderr, "fvm: Aborting Driver Load\n"); |
| // DdkRemove will cause the Release() hook to be called, cleaning up our |
| // state. The exchange below is sufficient to protect against a |
| // use-after-free, since if DdkRemove() has already been called by |
| // another thread (via DdkUnbind()), the release hook will block on thread_join() |
| // until this method returns. |
| if (!device_remove_.exchange(true)) { |
| DdkRemove(); |
| } |
| }); |
| |
| 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 determine the slice sice |
| if ((status = DoIoLocked(vmo.get(), 0, fvm::kBlockSize, BLOCK_OP_READ)) != ZX_OK) { |
| fprintf(stderr, "fvm: Failed to read first block from underlying device\n"); |
| return status; |
| } |
| |
| fvm_t sb; |
| status = vmo.read(&sb, 0, sizeof(sb)); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| format_info_ = FormatInfo::FromSuperBlock(sb); |
| |
| // Validate the superblock, confirm the slice size |
| if ((format_info_.slice_size() * VSliceMax()) / VSliceMax() != format_info_.slice_size()) { |
| fprintf(stderr, "fvm: Slice Size, VSliceMax overflow block address space\n"); |
| return ZX_ERR_BAD_STATE; |
| } else if (info_.block_size == 0 || SliceSize() % info_.block_size) { |
| fprintf(stderr, "fvm: Bad block (%u) or slice size (%zu)\n", info_.block_size, SliceSize()); |
| return ZX_ERR_BAD_STATE; |
| } else if (sb.vpartition_table_size != kVPartTableLength) { |
| fprintf(stderr, "fvm: Bad vpartition table size %zu (expected %zu)\n", |
| sb.vpartition_table_size, kVPartTableLength); |
| return ZX_ERR_BAD_STATE; |
| } else if (sb.allocation_table_size < AllocTableLength(sb.fvm_partition_size, SliceSize())) { |
| fprintf(stderr, "fvm: Bad allocation table size %zu (expected at least %zu)\n", |
| sb.allocation_table_size, AllocTableLength(sb.fvm_partition_size, SliceSize())); |
| return ZX_ERR_BAD_STATE; |
| } else if (sb.fvm_partition_size > DiskSize()) { |
| fprintf(stderr, |
| "fvm: Block Device too small (fvm_partition_size is %zu and block_device_size is " |
| "%zu).\n", |
| sb.fvm_partition_size, DiskSize()); |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| // Whether the metadata should grow or not. |
| bool metadata_should_grow = |
| sb.fvm_partition_size < DiskSize() && |
| AllocTableLength(sb.fvm_partition_size, sb.slice_size) < sb.allocation_table_size; |
| size_t metadata_vmo_size = metadata_should_grow ? format_info_.metadata_allocated_size() |
| : format_info_.metadata_size(); |
| |
| // Now that the slice size is known, read the rest of the metadata |
| auto make_metadata_vmo = [&](size_t offset, fzl::OwnedVmoMapper* out_mapping) { |
| fzl::OwnedVmoMapper mapper; |
| zx_status_t status = mapper.CreateAndMap(metadata_vmo_size, "fvm-metadata"); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| // Read both copies of metadata, ensure at least one is valid |
| if ((status = DoIoLocked(mapper.vmo().get(), offset, metadata_vmo_size, BLOCK_OP_READ)) != |
| ZX_OK) { |
| return status; |
| } |
| |
| *out_mapping = std::move(mapper); |
| return ZX_OK; |
| }; |
| |
| fzl::OwnedVmoMapper mapper; |
| if ((status = make_metadata_vmo(format_info_.GetSuperblockOffset(SuperblockType::kPrimary), |
| &mapper)) != ZX_OK) { |
| fprintf(stderr, "fvm: Failed to load metadata vmo: %d\n", status); |
| return status; |
| } |
| fzl::OwnedVmoMapper mapper_backup; |
| if ((status = make_metadata_vmo(format_info_.GetSuperblockOffset(SuperblockType::kSecondary), |
| &mapper_backup)) != ZX_OK) { |
| fprintf(stderr, "fvm: Failed to load backup metadata vmo: %d\n", status); |
| return status; |
| } |
| |
| // Validate metadata headers before growing. |
| const void* metadata; |
| if ((status = fvm_validate_header(mapper.start(), mapper_backup.start(), |
| format_info_.metadata_size(), &metadata)) != ZX_OK) { |
| fprintf(stderr, "fvm: Header validation failure: %d\n", status); |
| return status; |
| } |
| |
| if (metadata == mapper.start()) { |
| first_metadata_is_primary_ = true; |
| metadata_ = std::move(mapper); |
| } else { |
| first_metadata_is_primary_ = false; |
| metadata_ = std::move(mapper_backup); |
| } |
| |
| if (metadata_should_grow) { |
| // Now grow the fvm_partition to disk_size. |
| format_info_ = FormatInfo::FromDiskSize(DiskSize(), format_info_.slice_size()); |
| GetFvmLocked()->fvm_partition_size = DiskSize(); |
| GetFvmLocked()->pslice_count = format_info_.slice_count(); |
| // Persist the growth. |
| WriteFvmLocked(); |
| } |
| |
| // Begin initializing the underlying partitions |
| DdkMakeVisible(); |
| auto_detach.cancel(); |
| |
| // 0th vpartition is invalid |
| fbl::unique_ptr<VPartition> vpartitions[fvm::kMaxVPartitions] = {}; |
| |
| // Iterate through FVM Entry table, allocating the VPartitions which |
| // claim to have slices. |
| for (size_t i = 1; i < fvm::kMaxVPartitions; i++) { |
| if (GetVPartEntryLocked(i)->slices == 0) { |
| continue; |
| } else if ((status = VPartition::Create(this, i, &vpartitions[i])) != ZX_OK) { |
| fprintf(stderr, "FVM: Failed to Create vpartition %zu\n", i); |
| return status; |
| } |
| } |
| |
| // Iterate through the Slice Allocation table, filling the slice maps |
| // of VPartitions. |
| for (uint32_t i = 1; i <= GetFvmLocked()->pslice_count; i++) { |
| const slice_entry_t* 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_++; |
| } |
| |
| lock.release(); |
| |
| // Iterate through 'valid' VPartitions, and create their devices. |
| size_t device_count = 0; |
| for (size_t i = 0; i < fvm::kMaxVPartitions; i++) { |
| if (vpartitions[i] == nullptr) { |
| continue; |
| } else if (GetAllocatedVPartEntry(i)->IsInactive()) { |
| fprintf(stderr, "FVM: Freeing inactive partition\n"); |
| FreeSlices(vpartitions[i].get(), 0, VSliceMax()); |
| continue; |
| } else if (AddPartition(std::move(vpartitions[i]))) { |
| continue; |
| } |
| device_count++; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t VPartitionManager::WriteFvmLocked() { |
| zx_status_t status; |
| |
| GetFvmLocked()->generation++; |
| fvm_update_hash(GetFvmLocked(), format_info_.metadata_size()); |
| |
| // If we were reading from the primary, write to the backup. |
| status = DoIoLocked(metadata_.vmo().get(), BackupOffsetLocked(), format_info_.metadata_size(), |
| BLOCK_OP_WRITE); |
| if (status != ZX_OK) { |
| fprintf(stderr, "FVM: Failed to write metadata\n"); |
| return status; |
| } |
| |
| // We only allow the switch of "write to the other copy of metadata" |
| // once a valid version has been written entirely. |
| first_metadata_is_primary_ = !first_metadata_is_primary_; |
| return ZX_OK; |
| } |
| |
| zx_status_t VPartitionManager::FindFreeVPartEntryLocked(size_t* out) const { |
| for (size_t i = 1; i < fvm::kMaxVPartitions; i++) { |
| const vpart_entry_t* entry = GetVPartEntryLocked(i); |
| if (entry->slices == 0) { |
| *out = i; |
| return ZX_OK; |
| } |
| } |
| return ZX_ERR_NO_SPACE; |
| } |
| |
| zx_status_t VPartitionManager::FindFreeSliceLocked(size_t* out, size_t hint) const { |
| hint = fbl::max(hint, 1lu); |
| for (size_t i = hint; i <= format_info_.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 (vslice_start + count > VSliceMax()) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| zx_status_t status = ZX_OK; |
| size_t hint = 0; |
| |
| { |
| fbl::AutoLock lock(&vp->lock_); |
| if (vp->IsKilledLocked()) { |
| return ZX_ERR_BAD_STATE; |
| } |
| for (size_t i = 0; i < count; i++) { |
| size_t pslice; |
| auto vslice = vslice_start + i; |
| if (vp->SliceGetLocked(vslice, &pslice)) { |
| 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 = static_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; |
| } |
| } |
| |
| if ((status = WriteFvmLocked()) != ZX_OK) { |
| // Undo allocation in the event of failure; avoid holding VPartition |
| // lock while writing to fvm. |
| fbl::AutoLock lock(&vp->lock_); |
| for (int j = static_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 (!memcmp(old_guid, new_guid, GUID_LEN)) { |
| 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() && !memcmp(entry->guid, old_guid, GUID_LEN)) { |
| old_index = i; |
| } else if (entry->IsInactive() && !memcmp(entry->guid, new_guid, GUID_LEN)) { |
| 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, static_cast<uint64_t>(vslice_start), count); |
| } |
| |
| zx_status_t VPartitionManager::FreeSlicesLocked(VPartition* vp, uint64_t vslice_start, |
| size_t count) { |
| if (vslice_start + count > VSliceMax() || count > VSliceMax()) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| bool valid_range = false; |
| { |
| fbl::AutoLock lock(&vp->lock_); |
| if (vp->IsKilledLocked()) |
| return ZX_ERR_BAD_STATE; |
| |
| 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. |
| vp->DdkRemove(); |
| auto entry = GetVPartEntryLocked(vp->GetEntryIndex()); |
| entry->Release(); |
| vp->KillLocked(); |
| valid_range = true; |
| } else { |
| for (int i = static_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; |
| } |
| } |
| } |
| } |
| |
| if (!valid_range) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| return WriteFvmLocked(); |
| } |
| |
| void VPartitionManager::Query(volume_info_t* info) { |
| info->slice_size = SliceSize(); |
| info->vslice_count = VSliceMax(); |
| { |
| fbl::AutoLock lock(&lock_); |
| info->pslice_total_count = format_info_.slice_count(); |
| info->pslice_allocated_count = pslice_allocated_count_; |
| } |
| } |
| |
| 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->GetEntryIndex())->slices--; |
| pslice_allocated_count_--; |
| } |
| |
| void VPartitionManager::AllocatePhysicalSlice(VPartition* vp, uint64_t pslice, uint64_t vslice) { |
| uint64_t vpart = vp->GetEntryIndex(); |
| 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_++; |
| } |
| |
| slice_entry_t* VPartitionManager::GetSliceEntryLocked(size_t index) const { |
| ZX_DEBUG_ASSERT(index >= 1); |
| uintptr_t metadata_start = reinterpret_cast<uintptr_t>(GetFvmLocked()); |
| uintptr_t offset = static_cast<uintptr_t>(kAllocTableOffset + index * sizeof(slice_entry_t)); |
| ZX_DEBUG_ASSERT(kAllocTableOffset <= offset); |
| ZX_DEBUG_ASSERT(offset < kAllocTableOffset + AllocTableLength(DiskSize(), SliceSize())); |
| return reinterpret_cast<slice_entry_t*>(metadata_start + offset); |
| } |
| |
| vpart_entry_t* VPartitionManager::GetVPartEntryLocked(size_t index) const { |
| ZX_DEBUG_ASSERT(index >= 1); |
| uintptr_t metadata_start = reinterpret_cast<uintptr_t>(GetFvmLocked()); |
| uintptr_t offset = static_cast<uintptr_t>(kVPartTableOffset + index * sizeof(vpart_entry_t)); |
| ZX_DEBUG_ASSERT(kVPartTableOffset <= offset); |
| ZX_DEBUG_ASSERT(offset < kVPartTableOffset + kVPartTableLength); |
| return reinterpret_cast<vpart_entry_t*>(metadata_start + offset); |
| } |
| |
| // Device protocol (FVM) |
| |
| zx_status_t VPartitionManager::DdkMessage(fidl_msg_t* msg, fidl_txn_t* txn) { |
| return fuchsia_hardware_block_volume_VolumeManager_dispatch(this, txn, msg, Ops()); |
| } |
| |
| zx_status_t VPartitionManager::FIDLAllocatePartition( |
| uint64_t slice_count, const fuchsia_hardware_block_partition_GUID* type, |
| const fuchsia_hardware_block_partition_GUID* instance, const char* name_data, size_t name_size, |
| uint32_t flags, fidl_txn_t* txn) { |
| const auto reply = fuchsia_hardware_block_volume_VolumeManagerAllocatePartition_reply; |
| |
| if (slice_count >= std::numeric_limits<uint32_t>::max()) { |
| return reply(txn, ZX_ERR_OUT_OF_RANGE); |
| } else if (slice_count == 0) { |
| return reply(txn, ZX_ERR_OUT_OF_RANGE); |
| } else if (name_size > fuchsia_hardware_block_partition_NAME_LENGTH) { |
| return reply(txn, ZX_ERR_INVALID_ARGS); |
| } |
| |
| char name[fuchsia_hardware_block_partition_NAME_LENGTH + 1] = {}; |
| strlcpy(name, name_data, name_size); |
| |
| zx_status_t status; |
| fbl::unique_ptr<VPartition> vpart; |
| { |
| fbl::AutoLock lock(&lock_); |
| size_t vpart_entry; |
| if ((status = FindFreeVPartEntryLocked(&vpart_entry)) != ZX_OK) { |
| return reply(txn, status); |
| } |
| |
| if ((status = VPartition::Create(this, vpart_entry, &vpart)) != ZX_OK) { |
| return reply(txn, status); |
| } |
| |
| auto* entry = GetVPartEntryLocked(vpart_entry); |
| *entry = VPartitionEntry::Create(type->value, instance->value, 0, name, flags); |
| |
| if ((status = AllocateSlicesLocked(vpart.get(), 0, slice_count)) != ZX_OK) { |
| entry->slices = 0; // Undo VPartition allocation |
| return reply(txn, status); |
| } |
| } |
| if ((status = AddPartition(std::move(vpart))) != ZX_OK) { |
| return reply(txn, status); |
| } |
| |
| return reply(txn, ZX_OK); |
| } |
| |
| zx_status_t VPartitionManager::FIDLQuery(fidl_txn_t* txn) { |
| fuchsia_hardware_block_volume_VolumeInfo info; |
| Query(&info); |
| return fuchsia_hardware_block_volume_VolumeManagerQuery_reply(txn, ZX_OK, &info); |
| } |
| |
| zx_status_t VPartitionManager::FIDLActivate(const fuchsia_hardware_block_partition_GUID* old_guid, |
| const fuchsia_hardware_block_partition_GUID* new_guid, |
| fidl_txn_t* txn) { |
| zx_status_t status = Upgrade(old_guid->value, new_guid->value); |
| return fuchsia_hardware_block_volume_VolumeManagerActivate_reply(txn, status); |
| } |
| |
| void VPartitionManager::DdkUnbind() { |
| if (!device_remove_.exchange(true)) { |
| DdkRemove(); |
| } |
| } |
| |
| void VPartitionManager::DdkRelease() { |
| thrd_join(initialization_thread_, nullptr); |
| delete this; |
| } |
| |
| } // namespace fvm |
| |
| // C-compatibility definitions |
| |
| zx_status_t fvm_bind(zx_device_t* parent) { |
| return fvm::VPartitionManager::Bind(parent); |
| } |