blob: 215fe84dc634a474e5eee2106f2b10ad953c9225 [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 <errno.h>
#include <fcntl.h>
#include <fuchsia/device/c/fidl.h>
#include <fuchsia/device/llcpp/fidl.h>
#include <fuchsia/hardware/block/c/fidl.h>
#include <fuchsia/hardware/block/partition/c/fidl.h>
#include <fuchsia/hardware/block/volume/c/fidl.h>
#include <fuchsia/io/llcpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/devmgr-integration-test/fixture.h>
#include <lib/fdio/cpp/caller.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/namespace.h>
#include <lib/fdio/unsafe.h>
#include <lib/memfs/memfs.h>
#include <lib/zircon-internal/thread_annotations.h>
#include <lib/zx/channel.h>
#include <lib/zx/fifo.h>
#include <lib/zx/vmo.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <threads.h>
#include <time.h>
#include <unistd.h>
#include <utime.h>
#include <zircon/device/block.h>
#include <zircon/device/vfs.h>
#include <zircon/syscalls.h>
#include <algorithm>
#include <climits>
#include <iterator>
#include <limits>
#include <memory>
#include <new>
#include <utility>
#include <block-client/client.h>
#include <fbl/algorithm.h>
#include <fbl/auto_lock.h>
#include <fbl/function.h>
#include <fbl/ref_counted.h>
#include <fbl/ref_ptr.h>
#include <fbl/unique_fd.h>
#include <fbl/vector.h>
#include <fs-management/fvm.h>
#include <fs-management/mount.h>
#include <ramdevice-client/ramdisk.h>
#include <zxtest/zxtest.h>
#include "src/storage/blobfs/format.h"
#include "src/storage/fvm/format.h"
#include "src/storage/fvm/fvm_check.h"
#include "src/storage/minfs/format.h"
#include "zircon/errors.h"
#define FVM_DRIVER_LIB "/boot/driver/fvm.so"
#define STRLEN(s) (sizeof(s) / sizeof((s)[0]))
namespace {
namespace fio = fuchsia_io;
using volume_info_t = fuchsia_hardware_block_volume_VolumeInfo;
constexpr char kTmpfsPath[] = "/fvm-tmp";
constexpr char kMountPath[] = "/fvm-tmp/minfs_test_mountpath";
constexpr char kTestDevPath[] = "/fake/dev";
// Returns the number of usable slices for a standard layout on a given-sized device.
size_t UsableSlicesCount(size_t disk_size, size_t slice_size) {
return fvm::Header::FromDiskSize(fvm::kMaxUsablePartitions, disk_size, slice_size)
.GetAllocationTableUsedEntryCount();
}
typedef struct {
const char* name;
size_t number;
} partition_entry_t;
class FvmTest : public zxtest::Test {
public:
void SetUp() override {
devmgr_launcher::Args args = devmgr_integration_test::IsolatedDevmgr::DefaultArgs();
args.disable_block_watcher = true;
args.driver_search_paths.push_back("/boot/driver");
ASSERT_OK(devmgr_integration_test::IsolatedDevmgr::Create(std::move(args), &devmgr_));
ASSERT_OK(
wait_for_device_at(devfs_root().get(), "misc/ramctl", zx::duration::infinite().get()));
ASSERT_OK(loop_.StartThread());
zx::channel memfs_root;
ASSERT_OK(
memfs_create_filesystem(loop_.dispatcher(), &memfs_, memfs_root.reset_and_get_address()));
fdio_ns_t* name_space;
ASSERT_OK(fdio_ns_get_installed(&name_space));
ASSERT_OK(fdio_ns_bind(name_space, kTmpfsPath, memfs_root.release()));
ASSERT_OK(fdio_ns_bind_fd(name_space, kTestDevPath, devmgr_.devfs_root().get()));
ASSERT_EQ(mkdir(kMountPath, 0666), 0);
mounting_options_.register_fs = false;
}
const fbl::unique_fd& devfs_root() const { return devmgr_.devfs_root(); }
void TearDown() override {
fdio_ns_t* name_space;
ASSERT_OK(fdio_ns_get_installed(&name_space));
ASSERT_OK(fdio_ns_unbind(name_space, kTestDevPath));
ASSERT_OK(fdio_ns_unbind(name_space, kTmpfsPath));
sync_completion_t unmounted;
memfs_free_filesystem(memfs_, &unmounted);
ASSERT_OK(ramdisk_destroy(ramdisk_));
ASSERT_OK(sync_completion_wait(&unmounted, zx::duration::infinite().get()));
}
fbl::unique_fd fvm_device() const { return fbl::unique_fd(open(fvm_driver_path_, O_RDWR)); }
const char* fvm_path() const { return fvm_driver_path_; }
fbl::unique_fd ramdisk_device() const { return fbl::unique_fd(open(ramdisk_path_, O_RDWR)); }
const ramdisk_client* ramdisk() const { return ramdisk_; }
const char* ramdisk_path() const { return ramdisk_path_; }
void FVMRebind(const partition_entry_t* entries, size_t entry_count);
void CreateFVM(uint64_t block_size, uint64_t block_count, uint64_t slice_size);
void CreateRamdisk(uint64_t block_size, uint64_t block_count);
protected:
async::Loop loop_ = async::Loop(&kAsyncLoopConfigNoAttachToCurrentThread);
devmgr_integration_test::IsolatedDevmgr devmgr_;
memfs_filesystem_t* memfs_ = nullptr;
ramdisk_client_t* ramdisk_ = nullptr;
mount_options_t mounting_options_ = default_mount_options;
char ramdisk_path_[PATH_MAX] = {};
char fvm_driver_path_[PATH_MAX] = {};
};
void FvmTest::CreateRamdisk(uint64_t block_size, uint64_t block_count) {
ASSERT_OK(ramdisk_create_at(devfs_root().get(), block_size, block_count, &ramdisk_));
snprintf(ramdisk_path_, PATH_MAX, "%s/%s", kTestDevPath, ramdisk_get_path(ramdisk_));
}
void FvmTest::CreateFVM(uint64_t block_size, uint64_t block_count, uint64_t slice_size) {
CreateRamdisk(block_size, block_count);
fbl::unique_fd fd(open(ramdisk_path_, O_RDWR));
ASSERT_TRUE(fd);
ASSERT_OK(fvm_init_preallocated(fd.get(), block_count * block_size, block_count * block_size,
slice_size));
zx::channel fvm_channel;
ASSERT_OK(fdio_get_service_handle(fd.get(), fvm_channel.reset_and_get_address()));
auto resp = fidl::WireCall<fuchsia_device::Controller>(zx::unowned_channel(fvm_channel.get()))
.Bind(::fidl::StringView(FVM_DRIVER_LIB));
ASSERT_OK(resp.status());
ASSERT_TRUE(resp->result.is_response());
fvm_channel.reset();
snprintf(fvm_driver_path_, PATH_MAX, "%s/fvm", ramdisk_path_);
ASSERT_OK(wait_for_device(fvm_driver_path_, zx::duration::infinite().get()));
}
void FvmTest::FVMRebind(const partition_entry_t* entries, size_t entry_count) {
fdio_cpp::UnownedFdioCaller disk_caller(ramdisk_get_block_fd(ramdisk_));
auto resp =
fidl::WireCall<fuchsia_device::Controller>(zx::unowned_channel(disk_caller.borrow_channel()))
.Rebind(::fidl::StringView(FVM_DRIVER_LIB));
ASSERT_OK(resp.status());
ASSERT_TRUE(resp->result.is_response());
char path[PATH_MAX];
snprintf(path, sizeof(path), "%s/fvm", ramdisk_path_);
ASSERT_OK(wait_for_device(path, zx::duration::infinite().get()));
for (size_t i = 0; i < entry_count; i++) {
snprintf(path, sizeof(path), "%s/fvm/%s-p-%zu/block", ramdisk_path_, entries[i].name,
entries[i].number);
ASSERT_OK(wait_for_device(path, zx::duration::infinite().get()));
}
}
void FVMCheckSliceSize(const fbl::unique_fd& fd, size_t expected_slice_size) {
ASSERT_TRUE(fd);
volume_info_t volume_info;
ASSERT_EQ(fvm_query(fd.get(), &volume_info), ZX_OK, "Failed to query fvm\n");
ASSERT_EQ(expected_slice_size, volume_info.slice_size, "Unexpected slice size\n");
}
void FVMCheckAllocatedCount(const fbl::unique_fd& fd, size_t expected_allocated,
size_t expected_total) {
volume_info_t volume_info;
ASSERT_EQ(fvm_query(fd.get(), &volume_info), ZX_OK);
ASSERT_EQ(volume_info.pslice_total_count, expected_total);
ASSERT_EQ(volume_info.pslice_allocated_count, expected_allocated);
}
enum class ValidationResult {
Valid,
Corrupted,
};
void ValidateFVM(fbl::unique_fd fd, ValidationResult result = ValidationResult::Valid) {
ASSERT_TRUE(fd);
fdio_cpp::UnownedFdioCaller disk_caller(fd.get());
fuchsia_hardware_block_BlockInfo block_info;
zx_status_t status;
ASSERT_EQ(fuchsia_hardware_block_BlockGetInfo(disk_caller.borrow_channel(), &status, &block_info),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
fvm::Checker checker(std::move(fd), block_info.block_size, true);
switch (result) {
case ValidationResult::Valid:
ASSERT_TRUE(checker.Validate());
break;
default:
ASSERT_FALSE(checker.Validate());
}
}
/////////////////////// Helper functions, definitions
constexpr uint8_t kTestUniqueGUID[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f};
constexpr uint8_t kTestUniqueGUID2[] = {0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f};
// Intentionally avoid aligning these GUIDs with
// the actual system GUIDs; otherwise, limited versions
// of Fuchsia may attempt to actually mount these
// partitions automatically.
// clang-format off
#define GUID_TEST_DATA_VALUE \
{ \
0xAA, 0xFF, 0xBB, 0x00, 0x33, 0x44, 0x88, 0x99, \
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, \
}
#define GUID_TEST_BLOB_VALUE \
{ \
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, \
0xAA, 0xFF, 0xBB, 0x00, 0x33, 0x44, 0x88, 0x99, \
}
#define GUID_TEST_SYS_VALUE \
{ \
0xEE, 0xFF, 0xBB, 0x00, 0x33, 0x44, 0x88, 0x99, \
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, \
}
// clang-format on
constexpr char kTestPartName1[] = "data";
constexpr uint8_t kTestPartGUIDData[] = {0xAA, 0xFF, 0xBB, 0x00, 0x33, 0x44, 0x88, 0x99,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17};
constexpr char kTestPartName2[] = "blob";
constexpr uint8_t kTestPartGUIDBlob[] = GUID_TEST_BLOB_VALUE;
constexpr char kTestPartName3[] = "system";
constexpr uint8_t kTestPartGUIDSystem[] = GUID_TEST_SYS_VALUE;
class VmoBuf;
class VmoClient : public fbl::RefCounted<VmoClient> {
public:
static void Create(int fd, fbl::RefPtr<VmoClient>* out);
~VmoClient();
void CheckWrite(VmoBuf* vbuf, size_t buf_off, size_t dev_off, size_t len);
void CheckRead(VmoBuf* vbuf, size_t buf_off, size_t dev_off, size_t len);
void Transaction(block_fifo_request_t* requests, size_t count) {
ASSERT_OK(block_fifo_txn(client_, &requests[0], count));
}
int fd() const { return fd_; }
groupid_t group() { return 0; }
private:
int fd_;
fuchsia_hardware_block_BlockInfo info_;
fifo_client_t* client_;
};
class VmoBuf {
public:
static void Create(fbl::RefPtr<VmoClient> client, size_t size, std::unique_ptr<VmoBuf>* out) {
std::unique_ptr<uint8_t[]> buf(new uint8_t[size]);
zx::vmo vmo;
ASSERT_EQ(zx::vmo::create(size, 0, &vmo), ZX_OK);
zx::vmo xfer_vmo;
ASSERT_EQ(vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &xfer_vmo), ZX_OK);
fdio_cpp::UnownedFdioCaller disk_connection(client->fd());
zx::unowned_channel channel(disk_connection.borrow_channel());
fuchsia_hardware_block_VmoId vmoid;
zx_status_t status;
ASSERT_EQ(
fuchsia_hardware_block_BlockAttachVmo(channel->get(), xfer_vmo.release(), &status, &vmoid),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
std::unique_ptr<VmoBuf> vb(
new VmoBuf(std::move(client), std::move(vmo), std::move(buf), vmoid));
*out = std::move(vb);
}
~VmoBuf() {
if (vmo_.is_valid()) {
block_fifo_request_t request;
request.group = client_->group();
request.vmoid = vmoid_.id;
request.opcode = BLOCKIO_CLOSE_VMO;
client_->Transaction(&request, 1);
}
}
private:
friend VmoClient;
VmoBuf(fbl::RefPtr<VmoClient> client, zx::vmo vmo, std::unique_ptr<uint8_t[]> buf,
fuchsia_hardware_block_VmoId vmoid)
: client_(std::move(client)), vmo_(std::move(vmo)), buf_(std::move(buf)), vmoid_(vmoid) {}
fbl::RefPtr<VmoClient> client_;
zx::vmo vmo_;
std::unique_ptr<uint8_t[]> buf_;
fuchsia_hardware_block_VmoId vmoid_;
};
void VmoClient::Create(int fd, fbl::RefPtr<VmoClient>* out) {
fbl::RefPtr<VmoClient> vc = fbl::AdoptRef(new VmoClient());
fdio_cpp::UnownedFdioCaller disk_connection(fd);
zx::unowned_channel channel(disk_connection.borrow_channel());
zx_status_t status;
zx::fifo fifo;
ASSERT_EQ(
fuchsia_hardware_block_BlockGetFifo(channel->get(), &status, fifo.reset_and_get_address()),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
ASSERT_EQ(fuchsia_hardware_block_BlockGetInfo(channel->get(), &status, &vc->info_), ZX_OK);
ASSERT_EQ(status, ZX_OK);
ASSERT_EQ(block_fifo_create_client(fifo.release(), &vc->client_), ZX_OK);
vc->fd_ = fd;
*out = std::move(vc);
}
VmoClient::~VmoClient() {
fdio_cpp::UnownedFdioCaller disk_connection(fd());
zx_status_t status;
fuchsia_hardware_block_BlockCloseFifo(disk_connection.borrow_channel(), &status);
block_fifo_release_client(client_);
}
void VmoClient::CheckWrite(VmoBuf* vbuf, size_t buf_off, size_t dev_off, size_t len) {
// Write to the client-side buffer
for (size_t i = 0; i < len; i++)
vbuf->buf_[i + buf_off] = static_cast<uint8_t>(rand());
// Write to the registered VMO
ASSERT_EQ(vbuf->vmo_.write(&vbuf->buf_[buf_off], buf_off, len), ZX_OK);
// Write to the block device
block_fifo_request_t request;
request.group = group();
request.vmoid = vbuf->vmoid_.id;
request.opcode = BLOCKIO_WRITE;
ASSERT_EQ(len % info_.block_size, 0);
ASSERT_EQ(buf_off % info_.block_size, 0);
ASSERT_EQ(dev_off % info_.block_size, 0);
request.length = static_cast<uint32_t>(len / info_.block_size);
request.vmo_offset = buf_off / info_.block_size;
request.dev_offset = dev_off / info_.block_size;
Transaction(&request, 1);
}
void VmoClient::CheckRead(VmoBuf* vbuf, size_t buf_off, size_t dev_off, size_t len) {
// Create a comparison buffer
fbl::AllocChecker ac;
std::unique_ptr<uint8_t[]> out(new (&ac) uint8_t[len]);
ASSERT_TRUE(ac.check());
memset(out.get(), 0, len);
// Read from the block device
block_fifo_request_t request;
request.group = group();
request.vmoid = vbuf->vmoid_.id;
request.opcode = BLOCKIO_READ;
ASSERT_EQ(len % info_.block_size, 0);
ASSERT_EQ(buf_off % info_.block_size, 0);
ASSERT_EQ(dev_off % info_.block_size, 0);
request.length = static_cast<uint32_t>(len / info_.block_size);
request.vmo_offset = buf_off / info_.block_size;
request.dev_offset = dev_off / info_.block_size;
Transaction(&request, 1);
// Read from the registered VMO
ASSERT_EQ(vbuf->vmo_.read(out.get(), buf_off, len), ZX_OK);
ASSERT_EQ(memcmp(&vbuf->buf_[buf_off], out.get(), len), 0);
}
void CheckWrite(int fd, size_t off, size_t len, uint8_t* buf) {
for (size_t i = 0; i < len; i++) {
buf[i] = static_cast<uint8_t>(rand());
}
ASSERT_EQ(lseek(fd, off, SEEK_SET), static_cast<ssize_t>(off));
ASSERT_EQ(write(fd, buf, len), static_cast<ssize_t>(len));
}
void CheckRead(int fd, size_t off, size_t len, const uint8_t* in) {
fbl::AllocChecker ac;
std::unique_ptr<uint8_t[]> out(new (&ac) uint8_t[len]);
ASSERT_TRUE(ac.check());
memset(out.get(), 0, len);
ASSERT_EQ(lseek(fd, off, SEEK_SET), static_cast<ssize_t>(off));
ASSERT_EQ(read(fd, out.get(), len), static_cast<ssize_t>(len));
ASSERT_EQ(memcmp(in, out.get(), len), 0);
}
void CheckWriteReadBlock(int fd, size_t block, size_t count) {
fdio_cpp::UnownedFdioCaller disk_connection(fd);
zx_status_t status;
fuchsia_hardware_block_BlockInfo block_info;
ASSERT_EQ(
fuchsia_hardware_block_BlockGetInfo(disk_connection.borrow_channel(), &status, &block_info),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
size_t len = block_info.block_size * count;
size_t off = block_info.block_size * block;
std::unique_ptr<uint8_t[]> in(new uint8_t[len]);
CheckWrite(fd, off, len, in.get());
CheckRead(fd, off, len, in.get());
}
void CheckNoAccessBlock(int fd, size_t block, size_t count) {
fdio_cpp::UnownedFdioCaller disk_connection(fd);
zx_status_t status;
fuchsia_hardware_block_BlockInfo block_info;
ASSERT_EQ(
fuchsia_hardware_block_BlockGetInfo(disk_connection.borrow_channel(), &status, &block_info),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
std::unique_ptr<uint8_t[]> buf(new uint8_t[block_info.block_size * count]);
size_t len = block_info.block_size * count;
size_t off = block_info.block_size * block;
for (size_t i = 0; i < len; i++)
buf[i] = static_cast<uint8_t>(rand());
ASSERT_EQ(lseek(fd, off, SEEK_SET), static_cast<ssize_t>(off));
ASSERT_EQ(write(fd, buf.get(), len), -1);
ASSERT_EQ(lseek(fd, off, SEEK_SET), static_cast<ssize_t>(off));
ASSERT_EQ(read(fd, buf.get(), len), -1);
}
void CheckDeadConnection(int fd) {
lseek(fd, 0, SEEK_SET);
bool is_dead = ((errno == EBADF) || (errno == EPIPE));
ASSERT_TRUE(is_dead);
}
void Upgrade(const fdio_cpp::FdioCaller& caller, const uint8_t* old_guid, const uint8_t* new_guid,
zx_status_t result) {
fuchsia_hardware_block_partition_GUID old_guid_fidl;
memcpy(&old_guid_fidl.value, old_guid, fuchsia_hardware_block_partition_GUID_LENGTH);
fuchsia_hardware_block_partition_GUID new_guid_fidl;
memcpy(&new_guid_fidl.value, new_guid, fuchsia_hardware_block_partition_GUID_LENGTH);
zx_status_t status;
zx_status_t io_status = fuchsia_hardware_block_volume_VolumeManagerActivate(
caller.borrow_channel(), &old_guid_fidl, &new_guid_fidl, &status);
ASSERT_EQ(ZX_OK, io_status);
ASSERT_EQ(result, status);
}
/////////////////////// Actual tests:
// Test initializing the FVM on a partition that is smaller than a slice
TEST_F(FvmTest, TestTooSmall) {
uint64_t block_size = 512;
uint64_t block_count = (1 << 15);
CreateRamdisk(block_size, block_count);
fbl::unique_fd fd(ramdisk_device());
ASSERT_TRUE(fd);
size_t slice_size = block_size * block_count;
ASSERT_EQ(fvm_init(fd.get(), slice_size), ZX_ERR_NO_SPACE);
ValidateFVM(ramdisk_device(), ValidationResult::Corrupted);
}
// Test initializing the FVM on a large partition, with metadata size > the max transfer size
TEST_F(FvmTest, TestLarge) {
char fvm_path[PATH_MAX];
uint64_t block_size = 512;
uint64_t block_count = 8 * (1 << 20);
CreateRamdisk(block_size, block_count);
fbl::unique_fd fd(ramdisk_device());
ASSERT_TRUE(fd);
size_t slice_size = 16 * (1 << 10);
fvm::Header fvm_header =
fvm::Header::FromDiskSize(fvm::kMaxUsablePartitions, block_size * block_count, slice_size);
fdio_cpp::UnownedFdioCaller disk_connection(fd.get());
zx::unowned_channel channel(disk_connection.borrow_channel());
zx_status_t status;
fuchsia_hardware_block_BlockInfo block_info;
ASSERT_EQ(fuchsia_hardware_block_BlockGetInfo(channel->get(), &status, &block_info), ZX_OK);
ASSERT_EQ(status, ZX_OK);
ASSERT_LT(block_info.max_transfer_size, fvm_header.GetMetadataAllocatedBytes());
ASSERT_EQ(fvm_init(fd.get(), slice_size), ZX_OK);
auto resp = fidl::WireCall<fuchsia_device::Controller>(zx::unowned_channel(channel->get()))
.Bind(::fidl::StringView(FVM_DRIVER_LIB));
ASSERT_OK(resp.status());
ASSERT_TRUE(resp->result.is_response());
snprintf(fvm_path, sizeof(fvm_path), "%s/fvm", ramdisk_path());
ASSERT_OK(wait_for_device(fvm_path, zx::duration::infinite().get()));
ValidateFVM(ramdisk_device());
}
// Load and unload an empty FVM
TEST_F(FvmTest, TestEmpty) {
constexpr uint64_t kBlockSize = 512;
constexpr uint64_t kBlockCount = 1 << 16;
constexpr uint64_t kSliceSize = 64 * kBlockSize;
CreateFVM(kBlockSize, kBlockCount, kSliceSize);
FVMCheckSliceSize(fvm_device(), kSliceSize);
ValidateFVM(ramdisk_device());
}
// Test allocating a single partition
TEST_F(FvmTest, TestAllocateOne) {
constexpr uint64_t kBlockSize = 512;
constexpr uint64_t kBlockCount = 1 << 16;
constexpr uint64_t kSliceSize = 64 * kBlockSize;
CreateFVM(kBlockSize, kBlockCount, kSliceSize);
fbl::unique_fd fd = fvm_device();
ASSERT_TRUE(fd);
// Allocate one VPart
alloc_req_t request;
memset(&request, 0, sizeof(request));
request.slice_count = 1;
memcpy(request.guid, kTestUniqueGUID, BLOCK_GUID_LEN);
strcpy(request.name, kTestPartName1);
memcpy(request.type, kTestPartGUIDData, BLOCK_GUID_LEN);
fbl::unique_fd vp_fd(fvm_allocate_partition_with_devfs(devfs_root().get(), fd.get(), &request));
ASSERT_TRUE(vp_fd);
// Check that the name matches what we provided
char name[fvm::kMaxVPartitionNameLength + 1];
fdio_cpp::UnownedFdioCaller partition_connection(vp_fd.get());
zx_status_t status;
size_t actual;
ASSERT_EQ(fuchsia_hardware_block_partition_PartitionGetName(partition_connection.borrow_channel(),
&status, name, sizeof(name), &actual),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
name[actual] = '\0';
ASSERT_EQ(memcmp(name, kTestPartName1, strlen(kTestPartName1)), 0);
// Check that we can read from / write to it.
CheckWriteReadBlock(vp_fd.get(), 0, 1);
// Try accessing the block again after closing / re-opening it.
ASSERT_EQ(close(vp_fd.release()), 0);
vp_fd.reset(open_partition_with_devfs(devfs_root().get(), kTestUniqueGUID, kTestPartGUIDData, 0,
nullptr));
ASSERT_TRUE(vp_fd, "Couldn't re-open Data VPart");
CheckWriteReadBlock(vp_fd.get(), 0, 1);
ASSERT_EQ(close(vp_fd.release()), 0);
ASSERT_EQ(close(fd.release()), 0);
FVMCheckSliceSize(fvm_device(), kSliceSize);
ValidateFVM(ramdisk_device());
}
// Test allocating a collection of partitions
TEST_F(FvmTest, TestAllocateMany) {
constexpr uint64_t kBlockSize = 512;
constexpr uint64_t kBlockCount = 1 << 16;
constexpr uint64_t kSliceSize = 64 * kBlockSize;
CreateFVM(kBlockSize, kBlockCount, kSliceSize);
fbl::unique_fd fd = fvm_device();
ASSERT_TRUE(fd);
// Test allocation of multiple VPartitions
alloc_req_t request;
memset(&request, 0, sizeof(request));
request.slice_count = 1;
memcpy(request.guid, kTestUniqueGUID, BLOCK_GUID_LEN);
strcpy(request.name, kTestPartName1);
memcpy(request.type, kTestPartGUIDData, BLOCK_GUID_LEN);
fbl::unique_fd data_fd(fvm_allocate_partition_with_devfs(devfs_root().get(), fd.get(), &request));
ASSERT_TRUE(data_fd);
strcpy(request.name, kTestPartName2);
memcpy(request.type, kTestPartGUIDBlob, BLOCK_GUID_LEN);
fbl::unique_fd blob_fd(fvm_allocate_partition_with_devfs(devfs_root().get(), fd.get(), &request));
ASSERT_TRUE(blob_fd);
strcpy(request.name, kTestPartName3);
memcpy(request.type, kTestPartGUIDSystem, BLOCK_GUID_LEN);
fbl::unique_fd sys_fd(fvm_allocate_partition_with_devfs(devfs_root().get(), fd.get(), &request));
ASSERT_TRUE(sys_fd);
CheckWriteReadBlock(data_fd.get(), 0, 1);
CheckWriteReadBlock(blob_fd.get(), 0, 1);
CheckWriteReadBlock(sys_fd.get(), 0, 1);
ASSERT_EQ(close(data_fd.release()), 0);
ASSERT_EQ(close(blob_fd.release()), 0);
ASSERT_EQ(close(sys_fd.release()), 0);
ASSERT_EQ(close(fd.release()), 0);
FVMCheckSliceSize(fvm_device(), kSliceSize);
ValidateFVM(ramdisk_device());
}
// Test allocating additional slices to a vpartition.
TEST_F(FvmTest, TestVPartitionExtend) {
constexpr uint64_t kBlockSize = 512;
constexpr uint64_t kBlockCount = 1 << 16;
constexpr uint64_t kSliceSize = 64 * kBlockSize;
CreateFVM(kBlockSize, kBlockCount, kSliceSize);
fbl::unique_fd fd = fvm_device();
ASSERT_TRUE(fd);
volume_info_t volume_info;
ASSERT_EQ(fvm_query(fd.get(), &volume_info), ZX_OK);
size_t slice_size = volume_info.slice_size;
constexpr uint64_t kDiskSize = kBlockSize * kBlockCount;
size_t slices_total = UsableSlicesCount(kDiskSize, slice_size);
size_t slices_left = slices_total;
FVMCheckAllocatedCount(fd, slices_total - slices_left, slices_total);
// Allocate one VPart
alloc_req_t request;
memset(&request, 0, sizeof(request));
size_t slice_count = 1;
request.slice_count = slice_count;
memcpy(request.guid, kTestUniqueGUID, BLOCK_GUID_LEN);
strcpy(request.name, kTestPartName1);
memcpy(request.type, kTestPartGUIDData, BLOCK_GUID_LEN);
fbl::unique_fd vp_fd(fvm_allocate_partition_with_devfs(devfs_root().get(), fd.get(), &request));
ASSERT_TRUE(vp_fd, "Couldn't open Volume");
slices_left--;
FVMCheckAllocatedCount(fd, slices_total - slices_left, slices_total);
// Confirm that the disk reports the correct number of slices
fdio_cpp::FdioCaller partition_caller(std::move(vp_fd));
zx::unowned_channel partition_channel(partition_caller.borrow_channel());
fuchsia_hardware_block_BlockInfo block_info;
zx_status_t status;
ASSERT_EQ(fuchsia_hardware_block_BlockGetInfo(partition_channel->get(), &status, &block_info),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
ASSERT_EQ(block_info.block_count * block_info.block_size, slice_size * slice_count);
// Try re-allocating an already allocated vslice
ASSERT_EQ(fuchsia_hardware_block_volume_VolumeExtend(partition_channel->get(), 0, 1, &status),
ZX_OK);
ASSERT_NE(status, ZX_OK, "Expected request failure");
ASSERT_EQ(fuchsia_hardware_block_BlockGetInfo(partition_channel->get(), &status, &block_info),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
ASSERT_EQ(block_info.block_count * block_info.block_size, slice_size * slice_count);
// Try again with a portion of the request which is unallocated
ASSERT_EQ(fuchsia_hardware_block_volume_VolumeExtend(partition_channel->get(), 0, 2, &status),
ZX_OK);
ASSERT_NE(status, ZX_OK, "Expected request failure");
// Allocate OBSCENELY too many slices
ASSERT_EQ(fuchsia_hardware_block_volume_VolumeExtend(partition_channel->get(), slice_count,
std::numeric_limits<size_t>::max(), &status),
ZX_OK);
ASSERT_NE(status, ZX_OK, "Expected request failure");
// Allocate slices at a too-large offset
ASSERT_EQ(fuchsia_hardware_block_volume_VolumeExtend(
partition_channel->get(), std::numeric_limits<size_t>::max(), 1, &status),
ZX_OK);
ASSERT_NE(status, ZX_OK, "Expected request failure");
// Attempt to allocate slightly too many slices
ASSERT_EQ(fuchsia_hardware_block_volume_VolumeExtend(partition_channel->get(), slice_count,
slices_left + 1, &status),
ZX_OK);
ASSERT_NE(status, ZX_OK, "Expected request failure");
// The number of free slices should be unchanged.
FVMCheckAllocatedCount(fd, slices_total - slices_left, slices_total);
// Allocate exactly the remaining number of slices
ASSERT_EQ(fuchsia_hardware_block_volume_VolumeExtend(partition_channel->get(), slice_count,
slices_left, &status),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
slice_count += slices_left;
slices_left = 0;
FVMCheckAllocatedCount(fd, slices_total - slices_left, slices_total);
ASSERT_EQ(fuchsia_hardware_block_BlockGetInfo(partition_channel->get(), &status, &block_info),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
ASSERT_EQ(block_info.block_count * block_info.block_size, slice_size * slice_count);
// We can't allocate any more to this VPartition
ASSERT_EQ(
fuchsia_hardware_block_volume_VolumeExtend(partition_channel->get(), slice_count, 1, &status),
ZX_OK);
ASSERT_NE(status, ZX_OK, "Expected request failure");
// We can't allocate a new VPartition
strcpy(request.name, kTestPartName2);
memcpy(request.type, kTestPartGUIDBlob, BLOCK_GUID_LEN);
ASSERT_LT(fvm_allocate_partition_with_devfs(devfs_root().get(), fd.get(), &request), 0,
"Expected VPart allocation failure");
FVMCheckSliceSize(fvm_device(), kSliceSize);
ValidateFVM(ramdisk_device());
}
// Test allocating very sparse VPartition
TEST_F(FvmTest, TestVPartitionExtendSparse) {
constexpr uint64_t kBlockSize = 512;
constexpr uint64_t kBlockCount = 1 << 16;
constexpr uint64_t kSliceSize = 64 * kBlockSize;
CreateFVM(kBlockSize, kBlockCount, kSliceSize);
fbl::unique_fd fd = fvm_device();
ASSERT_TRUE(fd);
size_t slices_left = UsableSlicesCount(kBlockSize * kBlockCount, kSliceSize);
alloc_req_t request;
memset(&request, 0, sizeof(request));
request.slice_count = 1;
slices_left--;
memcpy(request.guid, kTestUniqueGUID, BLOCK_GUID_LEN);
strcpy(request.name, kTestPartName1);
memcpy(request.type, kTestPartGUIDData, BLOCK_GUID_LEN);
fbl::unique_fd vp_fd(fvm_allocate_partition_with_devfs(devfs_root().get(), fd.get(), &request));
ASSERT_TRUE(vp_fd);
CheckWriteReadBlock(vp_fd.get(), 0, 1);
// Double check that we can access a block at this vslice address
// (this isn't always possible; for certain slice sizes, blocks may be
// allocatable / freeable, but not addressable).
size_t bno = (fvm::kMaxVSlices - 1) * (kSliceSize / kBlockSize);
ASSERT_EQ(bno / (kSliceSize / kBlockSize), (fvm::kMaxVSlices - 1), "bno overflowed");
ASSERT_EQ((bno * kBlockSize) / kBlockSize, bno, "block access will overflow");
fdio_cpp::UnownedFdioCaller partition_caller(vp_fd.get());
zx::unowned_channel partition_channel(partition_caller.borrow_channel());
zx_status_t status;
// Try allocating at a location that's slightly too large
ASSERT_EQ(fuchsia_hardware_block_volume_VolumeExtend(partition_channel->get(), fvm::kMaxVSlices,
1, &status),
ZX_OK);
ASSERT_NE(status, ZX_OK, "Expected request failure");
// Try allocating at the largest offset
ASSERT_EQ(fuchsia_hardware_block_volume_VolumeExtend(partition_channel->get(),
fvm::kMaxVSlices - 1, 1, &status),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
CheckWriteReadBlock(vp_fd.get(), bno, 1);
// Try freeing beyond largest offset
ASSERT_EQ(fuchsia_hardware_block_volume_VolumeShrink(partition_channel->get(), fvm::kMaxVSlices,
1, &status),
ZX_OK);
ASSERT_NE(status, ZX_OK, "Expected request failure");
CheckWriteReadBlock(vp_fd.get(), bno, 1);
// Try freeing at the largest offset
ASSERT_EQ(fuchsia_hardware_block_volume_VolumeShrink(partition_channel->get(),
fvm::kMaxVSlices - 1, 1, &status),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
CheckNoAccessBlock(vp_fd.get(), bno, 1);
ASSERT_EQ(close(vp_fd.release()), 0);
ASSERT_EQ(close(fd.release()), 0);
FVMCheckSliceSize(fvm_device(), kSliceSize);
ValidateFVM(ramdisk_device());
}
// Test removing slices from a VPartition.
TEST_F(FvmTest, TestVPartitionShrink) {
constexpr uint64_t kBlockSize = 512;
constexpr uint64_t kBlockCount = 1 << 16;
constexpr uint64_t kSliceSize = 64 * kBlockSize;
CreateFVM(kBlockSize, kBlockCount, kSliceSize);
fbl::unique_fd fd(fvm_device());
ASSERT_TRUE(fd);
volume_info_t volume_info;
ASSERT_EQ(fvm_query(fd.get(), &volume_info), ZX_OK);
size_t slice_size = volume_info.slice_size;
const size_t kDiskSize = kBlockSize * kBlockCount;
size_t slices_total = UsableSlicesCount(kDiskSize, slice_size);
size_t slices_left = slices_total;
FVMCheckAllocatedCount(fd, slices_total - slices_left, slices_total);
// Allocate one VPart
alloc_req_t request;
memset(&request, 0, sizeof(request));
size_t slice_count = 1;
request.slice_count = slice_count;
memcpy(request.guid, kTestUniqueGUID, BLOCK_GUID_LEN);
strcpy(request.name, kTestPartName1);
memcpy(request.type, kTestPartGUIDData, BLOCK_GUID_LEN);
fbl::unique_fd vp_fd(fvm_allocate_partition_with_devfs(devfs_root().get(), fd.get(), &request));
ASSERT_TRUE(vp_fd, "Couldn't open Volume");
slices_left--;
fdio_cpp::UnownedFdioCaller partition_caller(vp_fd.get());
zx::unowned_channel partition_channel(partition_caller.borrow_channel());
zx_status_t status;
// Confirm that the disk reports the correct number of slices
fuchsia_hardware_block_BlockInfo block_info;
ASSERT_EQ(fuchsia_hardware_block_BlockGetInfo(partition_channel->get(), &status, &block_info),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
ASSERT_EQ(block_info.block_count * block_info.block_size, slice_size * slice_count);
CheckWriteReadBlock(vp_fd.get(), (slice_size / block_info.block_size) - 1, 1);
CheckNoAccessBlock(vp_fd.get(), (slice_size / block_info.block_size) - 1, 2);
FVMCheckAllocatedCount(fd, slices_total - slices_left, slices_total);
// Try shrinking the 0th vslice
ASSERT_EQ(fuchsia_hardware_block_volume_VolumeShrink(partition_channel->get(), 0, 1, &status),
ZX_OK);
ASSERT_NE(status, ZX_OK, "Expected request failure");
// Try no-op requests (length = 0).
ASSERT_EQ(fuchsia_hardware_block_volume_VolumeExtend(partition_channel->get(), 1, 0, &status),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
ASSERT_EQ(fuchsia_hardware_block_volume_VolumeShrink(partition_channel->get(), 1, 0, &status),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
ASSERT_EQ(fuchsia_hardware_block_BlockGetInfo(partition_channel->get(), &status, &block_info),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
ASSERT_EQ(block_info.block_count * block_info.block_size, slice_size * slice_count);
// Try again with a portion of the request which is unallocated
ASSERT_EQ(fuchsia_hardware_block_volume_VolumeShrink(partition_channel->get(), 1, 2, &status),
ZX_OK);
ASSERT_NE(status, ZX_OK);
ASSERT_EQ(fuchsia_hardware_block_BlockGetInfo(partition_channel->get(), &status, &block_info),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
ASSERT_EQ(block_info.block_count * block_info.block_size, slice_size * slice_count);
FVMCheckAllocatedCount(fd, slices_total - slices_left, slices_total);
// Allocate exactly the remaining number of slices
ASSERT_EQ(fuchsia_hardware_block_volume_VolumeExtend(partition_channel->get(), slice_count,
slices_left, &status),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
slice_count += slices_left;
slices_left = 0;
ASSERT_EQ(fuchsia_hardware_block_BlockGetInfo(partition_channel->get(), &status, &block_info),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
ASSERT_EQ(block_info.block_count * block_info.block_size, slice_size * slice_count);
CheckWriteReadBlock(vp_fd.get(), (slice_size / block_info.block_size) - 1, 1);
CheckWriteReadBlock(vp_fd.get(), (slice_size / block_info.block_size) - 1, 2);
FVMCheckAllocatedCount(fd, slices_total - slices_left, slices_total);
// We can't allocate any more to this VPartition
ASSERT_EQ(
fuchsia_hardware_block_volume_VolumeExtend(partition_channel->get(), slice_count, 1, &status),
ZX_OK);
ASSERT_NE(status, ZX_OK);
// Try to shrink off the end (okay, since SOME of the slices are allocated)
ASSERT_EQ(fuchsia_hardware_block_volume_VolumeShrink(partition_channel->get(), 1, slice_count + 3,
&status),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
FVMCheckAllocatedCount(fd, 1, slices_total);
// The same request to shrink should now fail (NONE of the slices are
// allocated)
ASSERT_EQ(fuchsia_hardware_block_volume_VolumeShrink(partition_channel->get(), 1, slice_count - 1,
&status),
ZX_OK);
ASSERT_NE(status, ZX_OK);
FVMCheckAllocatedCount(fd, 1, slices_total);
// ... unless we re-allocate and try again.
ASSERT_EQ(fuchsia_hardware_block_volume_VolumeExtend(partition_channel->get(), 1, slice_count - 1,
&status),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
ASSERT_EQ(fuchsia_hardware_block_volume_VolumeShrink(partition_channel->get(), 1, slice_count - 1,
&status),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
FVMCheckSliceSize(fvm_device(), kSliceSize);
ValidateFVM(ramdisk_device());
}
// Test splitting a contiguous slice extent into multiple parts
TEST_F(FvmTest, TestVPartitionSplit) {
constexpr uint64_t kBlockSize = 512;
constexpr uint64_t kBlockCount = 1 << 16;
constexpr uint64_t kSliceSize = 64 * kBlockSize;
CreateFVM(kBlockSize, kBlockCount, kSliceSize);
fbl::unique_fd fd(fvm_device());
ASSERT_TRUE(fd);
volume_info_t volume_info;
ASSERT_EQ(fvm_query(fd.get(), &volume_info), ZX_OK);
size_t slice_size = volume_info.slice_size;
size_t slices_left = UsableSlicesCount(kBlockSize * kBlockCount, kSliceSize);
// Allocate one VPart
alloc_req_t request;
memset(&request, 0, sizeof(request));
size_t slice_count = 5;
request.slice_count = slice_count;
memcpy(request.guid, kTestUniqueGUID, BLOCK_GUID_LEN);
strcpy(request.name, kTestPartName1);
memcpy(request.type, kTestPartGUIDData, BLOCK_GUID_LEN);
fbl::unique_fd vp_fd(fvm_allocate_partition_with_devfs(devfs_root().get(), fd.get(), &request));
ASSERT_TRUE(vp_fd);
slices_left--;
fdio_cpp::UnownedFdioCaller partition_caller(vp_fd.get());
zx::unowned_channel partition_channel(partition_caller.borrow_channel());
// Confirm that the disk reports the correct number of slices
fuchsia_hardware_block_BlockInfo block_info;
zx_status_t status;
ASSERT_EQ(fuchsia_hardware_block_BlockGetInfo(partition_channel->get(), &status, &block_info),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
ASSERT_EQ(block_info.block_count * block_info.block_size, slice_size * slice_count);
extend_request_t reset_erequest;
reset_erequest.offset = 1;
reset_erequest.length = slice_count - 1;
extend_request_t mid_erequest;
mid_erequest.offset = 2;
mid_erequest.length = 1;
extend_request_t start_erequest;
start_erequest.offset = 1;
start_erequest.length = 1;
extend_request_t end_erequest;
end_erequest.offset = 3;
end_erequest.length = slice_count - 3;
auto verifyExtents = [&](bool start, bool mid, bool end) {
size_t start_block = start_erequest.offset * (slice_size / block_info.block_size);
size_t mid_block = mid_erequest.offset * (slice_size / block_info.block_size);
size_t end_block = end_erequest.offset * (slice_size / block_info.block_size);
if (start) {
CheckWriteReadBlock(vp_fd.get(), start_block, 1);
} else {
CheckNoAccessBlock(vp_fd.get(), start_block, 1);
}
if (mid) {
CheckWriteReadBlock(vp_fd.get(), mid_block, 1);
} else {
CheckNoAccessBlock(vp_fd.get(), mid_block, 1);
}
if (end) {
CheckWriteReadBlock(vp_fd.get(), end_block, 1);
} else {
CheckNoAccessBlock(vp_fd.get(), end_block, 1);
}
return true;
};
auto doExtend = [](const zx::unowned_channel& partition_channel, extend_request_t request) {
zx_status_t status;
ASSERT_EQ(fuchsia_hardware_block_volume_VolumeExtend(partition_channel->get(), request.offset,
request.length, &status),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
};
auto doShrink = [](const zx::unowned_channel& partition_channel, extend_request_t request) {
zx_status_t status;
ASSERT_EQ(fuchsia_hardware_block_volume_VolumeShrink(partition_channel->get(), request.offset,
request.length, &status),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
};
// We should be able to split the extent.
verifyExtents(true, true, true);
doShrink(partition_channel, mid_erequest);
verifyExtents(true, false, true);
doShrink(partition_channel, start_erequest);
verifyExtents(false, false, true);
doShrink(partition_channel, end_erequest);
verifyExtents(false, false, false);
doExtend(partition_channel, reset_erequest);
doShrink(partition_channel, start_erequest);
verifyExtents(false, true, true);
doShrink(partition_channel, mid_erequest);
verifyExtents(false, false, true);
doShrink(partition_channel, end_erequest);
verifyExtents(false, false, false);
doExtend(partition_channel, reset_erequest);
doShrink(partition_channel, end_erequest);
verifyExtents(true, true, false);
doShrink(partition_channel, mid_erequest);
verifyExtents(true, false, false);
doShrink(partition_channel, start_erequest);
verifyExtents(false, false, false);
doExtend(partition_channel, reset_erequest);
doShrink(partition_channel, end_erequest);
verifyExtents(true, true, false);
doShrink(partition_channel, start_erequest);
verifyExtents(false, true, false);
doShrink(partition_channel, mid_erequest);
verifyExtents(false, false, false);
// We should also be able to combine extents
doExtend(partition_channel, mid_erequest);
verifyExtents(false, true, false);
doExtend(partition_channel, start_erequest);
verifyExtents(true, true, false);
doExtend(partition_channel, end_erequest);
verifyExtents(true, true, true);
doShrink(partition_channel, reset_erequest);
doExtend(partition_channel, end_erequest);
verifyExtents(false, false, true);
doExtend(partition_channel, mid_erequest);
verifyExtents(false, true, true);
doExtend(partition_channel, start_erequest);
verifyExtents(true, true, true);
doShrink(partition_channel, reset_erequest);
doExtend(partition_channel, end_erequest);
verifyExtents(false, false, true);
doExtend(partition_channel, start_erequest);
verifyExtents(true, false, true);
doExtend(partition_channel, mid_erequest);
verifyExtents(true, true, true);
ASSERT_EQ(close(vp_fd.release()), 0);
ASSERT_EQ(close(fd.release()), 0);
FVMCheckSliceSize(fvm_device(), kSliceSize);
ValidateFVM(ramdisk_device());
}
// Test removing VPartitions within an FVM
TEST_F(FvmTest, TestVPartitionDestroy) {
constexpr uint64_t kBlockSize = 512;
constexpr uint64_t kBlockCount = 1 << 16;
constexpr uint64_t kSliceSize = 64 * kBlockSize;
CreateFVM(kBlockSize, kBlockCount, kSliceSize);
fbl::unique_fd fd = fvm_device();
ASSERT_TRUE(fd);
// Test allocation of multiple VPartitions
alloc_req_t request;
memset(&request, 0, sizeof(request));
request.slice_count = 1;
memcpy(request.guid, kTestUniqueGUID, BLOCK_GUID_LEN);
strcpy(request.name, kTestPartName1);
memcpy(request.type, kTestPartGUIDData, BLOCK_GUID_LEN);
fbl::unique_fd data_fd(fvm_allocate_partition_with_devfs(devfs_root().get(), fd.get(), &request));
ASSERT_TRUE(data_fd);
fdio_cpp::UnownedFdioCaller data_caller(data_fd.get());
zx::unowned_channel data_channel(data_caller.borrow_channel());
strcpy(request.name, kTestPartName2);
memcpy(request.type, kTestPartGUIDBlob, BLOCK_GUID_LEN);
fbl::unique_fd blob_fd(fvm_allocate_partition_with_devfs(devfs_root().get(), fd.get(), &request));
ASSERT_TRUE(blob_fd);
fdio_cpp::UnownedFdioCaller blob_caller(blob_fd.get());
zx::unowned_channel blob_channel(blob_caller.borrow_channel());
strcpy(request.name, kTestPartName3);
memcpy(request.type, kTestPartGUIDSystem, BLOCK_GUID_LEN);
fbl::unique_fd sys_fd(fvm_allocate_partition_with_devfs(devfs_root().get(), fd.get(), &request));
ASSERT_TRUE(sys_fd);
fdio_cpp::UnownedFdioCaller sys_caller(sys_fd.get());
zx::unowned_channel sys_channel(sys_caller.borrow_channel());
// We can access all three...
CheckWriteReadBlock(data_fd.get(), 0, 1);
CheckWriteReadBlock(blob_fd.get(), 0, 1);
CheckWriteReadBlock(sys_fd.get(), 0, 1);
// But not after we destroy the blob partition.
zx_status_t status;
ASSERT_EQ(fuchsia_hardware_block_volume_VolumeDestroy(blob_channel->get(), &status), ZX_OK);
ASSERT_EQ(status, ZX_OK);
CheckWriteReadBlock(data_fd.get(), 0, 1);
CheckWriteReadBlock(sys_fd.get(), 0, 1);
CheckDeadConnection(blob_fd.get());
// Destroy the other two VPartitions.
ASSERT_EQ(fuchsia_hardware_block_volume_VolumeDestroy(data_channel->get(), &status), ZX_OK);
ASSERT_EQ(status, ZX_OK);
CheckWriteReadBlock(sys_fd.get(), 0, 1);
CheckDeadConnection(data_fd.get());
ASSERT_EQ(fuchsia_hardware_block_volume_VolumeDestroy(sys_channel->get(), &status), ZX_OK);
ASSERT_EQ(status, ZX_OK);
CheckDeadConnection(sys_fd.get());
ASSERT_EQ(close(fd.release()), 0);
FVMCheckSliceSize(fvm_device(), kSliceSize);
}
TEST_F(FvmTest, TestVPartitionQuery) {
constexpr uint64_t kBlockSize = 512;
constexpr uint64_t kBlockCount = 1 << 16;
constexpr uint64_t kSliceSize = 64 * kBlockSize;
CreateFVM(kBlockSize, kBlockCount, kSliceSize);
fbl::unique_fd fd(fvm_device());
ASSERT_TRUE(fd);
// Allocate partition
alloc_req_t request;
memset(&request, 0, sizeof(request));
request.slice_count = 10;
memcpy(request.guid, kTestUniqueGUID, BLOCK_GUID_LEN);
strcpy(request.name, kTestPartName1);
memcpy(request.type, kTestPartGUIDData, BLOCK_GUID_LEN);
fbl::unique_fd part_fd(fvm_allocate_partition_with_devfs(devfs_root().get(), fd.get(), &request));
ASSERT_TRUE(part_fd);
fdio_cpp::FdioCaller partition_caller(std::move(part_fd));
zx::unowned_channel partition_channel(partition_caller.borrow_channel());
// Create non-contiguous extent.
zx_status_t status;
uint64_t offset = 20;
uint64_t length = 10;
ASSERT_EQ(
fuchsia_hardware_block_volume_VolumeExtend(partition_channel->get(), offset, length, &status),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
volume_info_t volume_info;
ASSERT_EQ(fvm_query(fd.get(), &volume_info), ZX_OK);
// Query various vslice ranges
uint64_t start_slices[6];
start_slices[0] = 0;
start_slices[1] = 10;
start_slices[2] = 20;
start_slices[3] = 50;
start_slices[4] = 25;
start_slices[5] = 15;
// Check response from partition query
fuchsia_hardware_block_volume_VsliceRange
ranges[fuchsia_hardware_block_volume_MAX_SLICE_REQUESTS];
size_t actual_ranges_count;
ASSERT_EQ(fuchsia_hardware_block_volume_VolumeQuerySlices(partition_channel->get(), start_slices,
std::size(start_slices), &status,
ranges, &actual_ranges_count),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
ASSERT_EQ(actual_ranges_count, std::size(start_slices));
ASSERT_TRUE(ranges[0].allocated);
ASSERT_EQ(ranges[0].count, 10);
ASSERT_FALSE(ranges[1].allocated);
ASSERT_EQ(ranges[1].count, 10);
ASSERT_TRUE(ranges[2].allocated);
ASSERT_EQ(ranges[2].count, 10);
ASSERT_FALSE(ranges[3].allocated);
ASSERT_EQ(ranges[3].count, volume_info.vslice_count - 50);
ASSERT_TRUE(ranges[4].allocated);
ASSERT_EQ(ranges[4].count, 5);
ASSERT_FALSE(ranges[5].allocated);
ASSERT_EQ(ranges[5].count, 5);
// Merge the extents!
offset = 10;
length = 10;
ASSERT_EQ(
fuchsia_hardware_block_volume_VolumeExtend(partition_channel->get(), offset, length, &status),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
// Check partition query response again after extend
ASSERT_EQ(fuchsia_hardware_block_volume_VolumeQuerySlices(partition_channel->get(), start_slices,
std::size(start_slices), &status,
ranges, &actual_ranges_count),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
ASSERT_EQ(actual_ranges_count, std::size(start_slices));
ASSERT_TRUE(ranges[0].allocated);
ASSERT_EQ(ranges[0].count, 30);
ASSERT_TRUE(ranges[1].allocated);
ASSERT_EQ(ranges[1].count, 20);
ASSERT_TRUE(ranges[2].allocated);
ASSERT_EQ(ranges[2].count, 10);
ASSERT_FALSE(ranges[3].allocated);
ASSERT_EQ(ranges[3].count, volume_info.vslice_count - 50);
ASSERT_TRUE(ranges[4].allocated);
ASSERT_EQ(ranges[4].count, 5);
ASSERT_TRUE(ranges[5].allocated);
ASSERT_EQ(ranges[5].count, 15);
start_slices[0] = volume_info.vslice_count + 1;
ASSERT_EQ(fuchsia_hardware_block_volume_VolumeQuerySlices(partition_channel->get(), start_slices,
std::size(start_slices), &status,
ranges, &actual_ranges_count),
ZX_OK);
ASSERT_EQ(status, ZX_ERR_OUT_OF_RANGE);
ASSERT_EQ(close(fd.release()), 0);
FVMCheckSliceSize(fvm_device(), kSliceSize);
}
// Test allocating and accessing slices which are allocated contiguously.
TEST_F(FvmTest, TestSliceAccessContiguous) {
constexpr uint64_t kBlockSize = 512;
constexpr uint64_t kBlockCount = 1 << 16;
constexpr uint64_t kSliceSize = 64 * kBlockSize;
CreateFVM(kBlockSize, kBlockCount, kSliceSize);
fbl::unique_fd fd = fvm_device();
ASSERT_TRUE(fd);
volume_info_t volume_info;
ASSERT_EQ(fvm_query(fd.get(), &volume_info), ZX_OK);
size_t slice_size = volume_info.slice_size;
// Allocate one VPart
alloc_req_t request;
memset(&request, 0, sizeof(request));
request.slice_count = 1;
memcpy(request.guid, kTestUniqueGUID, BLOCK_GUID_LEN);
strcpy(request.name, kTestPartName1);
memcpy(request.type, kTestPartGUIDData, BLOCK_GUID_LEN);
fbl::unique_fd vp_fd(fvm_allocate_partition_with_devfs(devfs_root().get(), fd.get(), &request));
ASSERT_TRUE(vp_fd);
fdio_cpp::UnownedFdioCaller partition_caller(vp_fd.get());
zx::unowned_channel partition_channel(partition_caller.borrow_channel());
fuchsia_hardware_block_BlockInfo block_info;
zx_status_t status;
ASSERT_EQ(fuchsia_hardware_block_BlockGetInfo(partition_channel->get(), &status, &block_info),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
// This is the last 'accessible' block.
size_t last_block = (slice_size / block_info.block_size) - 1;
{
fbl::RefPtr<VmoClient> vc;
VmoClient::Create(vp_fd.get(), &vc);
std::unique_ptr<VmoBuf> vb;
VmoBuf::Create(vc, block_info.block_size * 2, &vb);
vc->CheckWrite(vb.get(), 0, block_info.block_size * last_block, block_info.block_size);
vc->CheckRead(vb.get(), 0, block_info.block_size * last_block, block_info.block_size);
// Try writing out of bounds -- check that we don't have access.
CheckNoAccessBlock(vp_fd.get(), (slice_size / block_info.block_size) - 1, 2);
CheckNoAccessBlock(vp_fd.get(), slice_size / block_info.block_size, 1);
// Attempt to access the next contiguous slice
ASSERT_EQ(fuchsia_hardware_block_volume_VolumeExtend(partition_channel->get(), 1, 1, &status),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
// Now we can access the next slice...
vc->CheckWrite(vb.get(), block_info.block_size, block_info.block_size * (last_block + 1),
block_info.block_size);
vc->CheckRead(vb.get(), block_info.block_size, block_info.block_size * (last_block + 1),
block_info.block_size);
// ... We can still access the previous slice...
vc->CheckRead(vb.get(), 0, block_info.block_size * last_block, block_info.block_size);
// ... And we can cross slices
vc->CheckRead(vb.get(), 0, block_info.block_size * last_block, block_info.block_size * 2);
}
ASSERT_EQ(close(vp_fd.release()), 0);
ASSERT_EQ(close(fd.release()), 0);
FVMCheckSliceSize(fvm_device(), kSliceSize);
}
// Test allocating and accessing multiple (3+) slices at once.
TEST_F(FvmTest, TestSliceAccessMany) {
// The size of a slice must be carefully constructed for this test
// so that we can hold multiple slices in memory without worrying
// about hitting resource limits.
constexpr uint64_t kBlockSize = 512;
constexpr uint64_t kBlockCount = 1 << 20;
constexpr uint64_t kBlocksPerSlice = 256;
constexpr uint64_t kSliceSize = kBlocksPerSlice * kBlockSize;
CreateFVM(kBlockSize, kBlockCount, kSliceSize);
fbl::unique_fd fd(fvm_device());
ASSERT_TRUE(fd);
volume_info_t volume_info;
ASSERT_EQ(fvm_query(fd.get(), &volume_info), ZX_OK);
ASSERT_EQ(volume_info.slice_size, kSliceSize);
// Allocate one VPart
alloc_req_t request;
memset(&request, 0, sizeof(request));
request.slice_count = 1;
memcpy(request.guid, kTestUniqueGUID, BLOCK_GUID_LEN);
strcpy(request.name, kTestPartName1);
memcpy(request.type, kTestPartGUIDData, BLOCK_GUID_LEN);
fbl::unique_fd vp_fd(fvm_allocate_partition_with_devfs(devfs_root().get(), fd.get(), &request));
ASSERT_TRUE(vp_fd);
fdio_cpp::UnownedFdioCaller partition_caller(vp_fd.get());
zx::unowned_channel partition_channel(partition_caller.borrow_channel());
fuchsia_hardware_block_BlockInfo block_info;
zx_status_t status;
ASSERT_EQ(fuchsia_hardware_block_BlockGetInfo(partition_channel->get(), &status, &block_info),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
ASSERT_EQ(block_info.block_size, kBlockSize);
{
fbl::RefPtr<VmoClient> vc;
VmoClient::Create(vp_fd.get(), &vc);
std::unique_ptr<VmoBuf> vb;
VmoBuf::Create(vc, kSliceSize * 3, &vb);
// Access the first slice
vc->CheckWrite(vb.get(), 0, 0, kSliceSize);
vc->CheckRead(vb.get(), 0, 0, kSliceSize);
// Try writing out of bounds -- check that we don't have access.
CheckNoAccessBlock(vp_fd.get(), kBlocksPerSlice - 1, 2);
CheckNoAccessBlock(vp_fd.get(), kBlocksPerSlice, 1);
// Attempt to access the next contiguous slices
uint64_t offset = 1;
uint64_t length = 2;
ASSERT_EQ(fuchsia_hardware_block_volume_VolumeExtend(partition_channel->get(), offset, length,
&status),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
// Now we can access the next slices...
vc->CheckWrite(vb.get(), kSliceSize, kSliceSize, 2 * kSliceSize);
vc->CheckRead(vb.get(), kSliceSize, kSliceSize, 2 * kSliceSize);
// ... We can still access the previous slice...
vc->CheckRead(vb.get(), 0, 0, kSliceSize);
// ... And we can cross slices for reading.
vc->CheckRead(vb.get(), 0, 0, 3 * kSliceSize);
// Also, we can cross slices for writing.
vc->CheckWrite(vb.get(), 0, 0, 3 * kSliceSize);
vc->CheckRead(vb.get(), 0, 0, 3 * kSliceSize);
// Additionally, we can access "parts" of slices in a multi-slice
// operation. Here, read one block into the first slice, and read
// up to the last block in the final slice.
vc->CheckWrite(vb.get(), 0, kBlockSize, 3 * kSliceSize - 2 * kBlockSize);
vc->CheckRead(vb.get(), 0, kBlockSize, 3 * kSliceSize - 2 * kBlockSize);
}
ASSERT_EQ(close(vp_fd.release()), 0);
ASSERT_EQ(close(fd.release()), 0);
FVMCheckSliceSize(fvm_device(), kSliceSize);
ValidateFVM(ramdisk_device());
}
// Test allocating and accessing slices which are allocated
// virtually contiguously (they appear sequential to the client) but are
// actually noncontiguous on the FVM partition.
TEST_F(FvmTest, TestSliceAccessNonContiguousPhysical) {
constexpr uint64_t kBlockSize = 512;
constexpr uint64_t kBlockCount = 1 << 16;
constexpr uint64_t kSliceSize = kBlockSize * 64;
constexpr uint64_t kDiskSize = kBlockSize * kBlockCount;
CreateFVM(kBlockSize, kBlockCount, kSliceSize);
fbl::unique_fd fd(fvm_device());
ASSERT_TRUE(fd);
volume_info_t volume_info;
ASSERT_EQ(fvm_query(fd.get(), &volume_info), ZX_OK);
alloc_req_t request;
memset(&request, 0, sizeof(request));
request.slice_count = 1;
memcpy(request.guid, kTestUniqueGUID, BLOCK_GUID_LEN);
constexpr size_t kNumVParts = 3;
typedef struct vdata {
fbl::unique_fd fd;
uint8_t guid[BLOCK_GUID_LEN];
char name[32];
size_t slices_used;
} vdata_t;
vdata_t vparts[kNumVParts] = {
{fbl::unique_fd(), GUID_TEST_DATA_VALUE, "data", request.slice_count},
{fbl::unique_fd(), GUID_TEST_BLOB_VALUE, "blob", request.slice_count},
{fbl::unique_fd(), GUID_TEST_SYS_VALUE, "sys", request.slice_count},
};
for (size_t i = 0; i < std::size(vparts); i++) {
strcpy(request.name, vparts[i].name);
memcpy(request.type, vparts[i].guid, BLOCK_GUID_LEN);
vparts[i].fd.reset(fvm_allocate_partition_with_devfs(devfs_root().get(), fd.get(), &request));
ASSERT_TRUE(vparts[i].fd);
}
fdio_cpp::UnownedFdioCaller partition_caller(vparts[0].fd.get());
zx::unowned_channel partition_channel(partition_caller.borrow_channel());
fuchsia_hardware_block_BlockInfo block_info;
zx_status_t status;
ASSERT_EQ(fuchsia_hardware_block_BlockGetInfo(partition_channel->get(), &status, &block_info),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
size_t usable_slices_per_vpart = UsableSlicesCount(kDiskSize, kSliceSize) / kNumVParts;
size_t i = 0;
while (vparts[i].slices_used < usable_slices_per_vpart) {
int vfd = vparts[i].fd.get();
// This is the last 'accessible' block.
size_t last_block = (vparts[i].slices_used * (kSliceSize / block_info.block_size)) - 1;
fbl::RefPtr<VmoClient> vc;
VmoClient::Create(vfd, &vc);
std::unique_ptr<VmoBuf> vb;
VmoBuf::Create(vc, block_info.block_size * 2, &vb);
vc->CheckWrite(vb.get(), 0, block_info.block_size * last_block, block_info.block_size);
vc->CheckRead(vb.get(), 0, block_info.block_size * last_block, block_info.block_size);
// Try writing out of bounds -- check that we don't have access.
CheckNoAccessBlock(vfd, last_block, 2);
CheckNoAccessBlock(vfd, last_block + 1, 1);
// Attempt to access the next contiguous slice
fdio_cpp::UnownedFdioCaller partition_caller(vfd);
zx::unowned_channel partition_channel(partition_caller.borrow_channel());
uint64_t offset = vparts[i].slices_used;
uint64_t length = 1;
ASSERT_EQ(fuchsia_hardware_block_volume_VolumeExtend(partition_channel->get(), offset, length,
&status),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
// Now we can access the next slice...
vc->CheckWrite(vb.get(), block_info.block_size, block_info.block_size * (last_block + 1),
block_info.block_size);
vc->CheckRead(vb.get(), block_info.block_size, block_info.block_size * (last_block + 1),
block_info.block_size);
// ... We can still access the previous slice...
vc->CheckRead(vb.get(), 0, block_info.block_size * last_block, block_info.block_size);
// ... And we can cross slices
vc->CheckRead(vb.get(), 0, block_info.block_size * last_block, block_info.block_size * 2);
vparts[i].slices_used++;
i = (i + 1) % kNumVParts;
}
for (size_t i = 0; i < kNumVParts; i++) {
printf("Testing multi-slice operations on vslice %lu\n", i);
// We need at least five slices, so we can access three "middle"
// slices and jitter to test off-by-one errors.
ASSERT_GE(vparts[i].slices_used, 5);
{
fbl::RefPtr<VmoClient> vc;
VmoClient::Create(vparts[i].fd.get(), &vc);
std::unique_ptr<VmoBuf> vb;
VmoBuf::Create(vc, kSliceSize * 4, &vb);
// Try accessing 3 noncontiguous slices at once, with the
// addition of "off by one block".
size_t dev_off_start = kSliceSize - block_info.block_size;
size_t dev_off_end = kSliceSize + block_info.block_size;
size_t len_start = kSliceSize * 3 - block_info.block_size;
size_t len_end = kSliceSize * 3 + block_info.block_size;
// Test a variety of:
// Starting device offsets,
size_t bsz = block_info.block_size;
for (size_t dev_off = dev_off_start; dev_off <= dev_off_end; dev_off += bsz) {
printf(" Testing non-contiguous write/read starting at offset: %zu\n", dev_off);
// Operation lengths,
for (size_t len = len_start; len <= len_end; len += bsz) {
printf(" Testing operation of length: %zu\n", len);
// and starting VMO offsets
for (size_t vmo_off = 0; vmo_off < 3 * bsz; vmo_off += bsz) {
// Try writing & reading the entire section (multiple
// slices) at once.
vc->CheckWrite(vb.get(), vmo_off, dev_off, len);
vc->CheckRead(vb.get(), vmo_off, dev_off, len);
// Try reading the section one slice at a time.
// The results should be the same.
size_t sub_off = 0;
size_t sub_len = kSliceSize - (dev_off % kSliceSize);
while (sub_off < len) {
vc->CheckRead(vb.get(), vmo_off + sub_off, dev_off + sub_off, sub_len);
sub_off += sub_len;
sub_len = std::min(kSliceSize, len - sub_off);
}
}
}
}
}
ASSERT_EQ(close(vparts[i].fd.release()), 0);
}
ASSERT_EQ(close(fd.release()), 0);
FVMCheckSliceSize(fvm_device(), kSliceSize);
ValidateFVM(ramdisk_device());
}
// Test allocating and accessing slices which are
// allocated noncontiguously from the client's perspective.
TEST_F(FvmTest, TestSliceAccessNonContiguousVirtual) {
constexpr uint64_t kBlockSize = 512;
constexpr uint64_t kBlockCount = 1 << 20;
constexpr uint64_t kSliceSize = 64 * (1 << 20);
constexpr uint64_t kDiskSize = kBlockSize * kBlockCount;
CreateFVM(kBlockSize, kBlockCount, kSliceSize);
fbl::unique_fd fd = fvm_device();
ASSERT_TRUE(fd);
volume_info_t volume_info;
ASSERT_EQ(fvm_query(fd.get(), &volume_info), ZX_OK);
alloc_req_t request;
memset(&request, 0, sizeof(request));
request.slice_count = 1;
memcpy(request.guid, kTestUniqueGUID, BLOCK_GUID_LEN);
constexpr size_t kNumVParts = 3;
typedef struct vdata {
fbl::unique_fd fd;
uint8_t guid[BLOCK_GUID_LEN];
char name[32];
size_t slices_used;
size_t last_slice;
} vdata_t;
vdata_t vparts[kNumVParts] = {
{fbl::unique_fd(), GUID_TEST_DATA_VALUE, "data", request.slice_count, request.slice_count},
{fbl::unique_fd(), GUID_TEST_BLOB_VALUE, "blob", request.slice_count, request.slice_count},
{fbl::unique_fd(), GUID_TEST_SYS_VALUE, "sys", request.slice_count, request.slice_count},
};
for (size_t i = 0; i < std::size(vparts); i++) {
strcpy(request.name, vparts[i].name);
memcpy(request.type, vparts[i].guid, BLOCK_GUID_LEN);
vparts[i].fd.reset(fvm_allocate_partition_with_devfs(devfs_root().get(), fd.get(), &request));
ASSERT_TRUE(vparts[i].fd);
}
fdio_cpp::UnownedFdioCaller partition_caller(vparts[0].fd.get());
zx::unowned_channel partition_channel(partition_caller.borrow_channel());
fuchsia_hardware_block_BlockInfo block_info;
zx_status_t status;
ASSERT_EQ(fuchsia_hardware_block_BlockGetInfo(partition_channel->get(), &status, &block_info),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
size_t usable_slices_per_vpart = UsableSlicesCount(kDiskSize, kSliceSize) / kNumVParts;
size_t i = 0;
while (vparts[i].slices_used < usable_slices_per_vpart) {
int vfd = vparts[i].fd.get();
// This is the last 'accessible' block.
size_t last_block = (vparts[i].last_slice * (kSliceSize / block_info.block_size)) - 1;
CheckWriteReadBlock(vfd, last_block, 1);
// Try writing out of bounds -- check that we don't have access.
CheckNoAccessBlock(vfd, last_block, 2);
CheckNoAccessBlock(vfd, last_block + 1, 1);
// Attempt to access a non-contiguous slice
fdio_cpp::UnownedFdioCaller partition_caller(vfd);
zx::unowned_channel partition_channel(partition_caller.borrow_channel());
uint64_t offset = vparts[i].last_slice + 2;
uint64_t length = 1;
ASSERT_EQ(fuchsia_hardware_block_volume_VolumeExtend(partition_channel->get(), offset, length,
&status),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
// We still don't have access to the next slice...
CheckNoAccessBlock(vfd, last_block, 2);
CheckNoAccessBlock(vfd, last_block + 1, 1);
// But we have access to the slice we asked for!
size_t requested_block = (offset * kSliceSize) / block_info.block_size;
CheckWriteReadBlock(vfd, requested_block, 1);
vparts[i].slices_used++;
vparts[i].last_slice = offset;
i = (i + 1) % kNumVParts;
}
for (size_t i = 0; i < kNumVParts; i++) {
ASSERT_EQ(close(vparts[i].fd.release()), 0);
}
ASSERT_EQ(close(fd.release()), 0);
FVMCheckSliceSize(fvm_device(), kSliceSize);
ValidateFVM(ramdisk_device());
}
// Test that the FVM driver actually persists updates.
TEST_F(FvmTest, TestPersistenceSimple) {
constexpr uint64_t kBlockSize = 512;
constexpr uint64_t kBlockCount = 1 << 20;
constexpr uint64_t kSliceSize = 64 * (1 << 20);
CreateFVM(kBlockSize, kBlockCount, kSliceSize);
fbl::unique_fd fd(fvm_device());
ASSERT_TRUE(fd);
constexpr uint64_t kDiskSize = kBlockSize * kBlockCount;
size_t slices_left = UsableSlicesCount(kDiskSize, kSliceSize);
const uint64_t kSliceCount = slices_left;
volume_info_t volume_info;
ASSERT_EQ(fvm_query(fd.get(), &volume_info), ZX_OK);
// Allocate one VPart
alloc_req_t request;
memset(&request, 0, sizeof(request));
request.slice_count = 1;
memcpy(request.guid, kTestUniqueGUID, BLOCK_GUID_LEN);
strcpy(request.name, kTestPartName1);
memcpy(request.type, kTestPartGUIDData, BLOCK_GUID_LEN);
fbl::unique_fd vp_fd(fvm_allocate_partition_with_devfs(devfs_root().get(), fd.get(), &request));
ASSERT_TRUE(vp_fd);
slices_left--;
fdio_cpp::UnownedFdioCaller partition_caller(vp_fd.get());
zx::unowned_channel partition_channel(partition_caller.borrow_channel());
// Check that the name matches what we provided
char name[fvm::kMaxVPartitionNameLength + 1];
zx_status_t status;
size_t actual;
ASSERT_EQ(fuchsia_hardware_block_partition_PartitionGetName(partition_channel->get(), &status,
name, sizeof(name), &actual),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
name[actual] = '\0';
ASSERT_EQ(memcmp(name, kTestPartName1, strlen(kTestPartName1)), 0);
fuchsia_hardware_block_BlockInfo block_info;
ASSERT_EQ(fuchsia_hardware_block_BlockGetInfo(partition_channel->get(), &status, &block_info),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
std::unique_ptr<uint8_t[]> buf(new uint8_t[block_info.block_size * 2]);
// Check that we can read from / write to it
CheckWrite(vp_fd.get(), 0, block_info.block_size, buf.get());
CheckRead(vp_fd.get(), 0, block_info.block_size, buf.get());
ASSERT_EQ(close(vp_fd.release()), 0);
// Check that it still exists after rebinding the driver
const partition_entry_t entries[] = {
{kTestPartName1, 1},
};
ASSERT_EQ(close(fd.release()), 0);
FVMRebind(entries, 1);
fd = fvm_device();
ASSERT_TRUE(fd, "Failed to rebind FVM driver");
vp_fd.reset(open_partition_with_devfs(devfs_root().get(), kTestUniqueGUID, kTestPartGUIDData, 0,
nullptr));
ASSERT_TRUE(vp_fd, "Couldn't re-open Data VPart");
CheckRead(vp_fd.get(), 0, block_info.block_size, buf.get());
// Try extending the vpartition, and checking that the extension persists.
// This is the last 'accessible' block.
size_t last_block = (kSliceSize / block_info.block_size) - 1;
CheckWrite(vp_fd.get(), block_info.block_size * last_block, block_info.block_size, &buf[0]);
CheckRead(vp_fd.get(), block_info.block_size * last_block, block_info.block_size, &buf[0]);
// Try writing out of bounds -- check that we don't have access.
CheckNoAccessBlock(vp_fd.get(), (kSliceSize / block_info.block_size) - 1, 2);
CheckNoAccessBlock(vp_fd.get(), kSliceSize / block_info.block_size, 1);
partition_caller.reset(vp_fd.get());
partition_channel = zx::unowned_channel(partition_caller.borrow_channel());
uint64_t offset = 1;
uint64_t length = 1;
ASSERT_EQ(
fuchsia_hardware_block_volume_VolumeExtend(partition_channel->get(), offset, length, &status),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
slices_left--;
ASSERT_EQ(close(vp_fd.release()), 0);
// FVMRebind will cause the rebind on ramdisk block device. The fvm device is child device
// to ramdisk block device. Before issuing rebind make sure the fd is released.
// Rebind the FVM driver, check the extension has succeeded.
ASSERT_EQ(close(fd.release()), 0);
FVMRebind(entries, 1);
fd = fvm_device();
vp_fd.reset(open_partition_with_devfs(devfs_root().get(), kTestUniqueGUID, kTestPartGUIDData, 0,
nullptr));
partition_caller.reset(vp_fd.get());
partition_channel = zx::unowned_channel(partition_caller.borrow_channel());
// Now we can access the next slice...
CheckWrite(vp_fd.get(), block_info.block_size * (last_block + 1), block_info.block_size,
&buf[block_info.block_size]);
CheckRead(vp_fd.get(), block_info.block_size * (last_block + 1), block_info.block_size,
&buf[block_info.block_size]);
// ... We can still access the previous slice...
CheckRead(vp_fd.get(), block_info.block_size * last_block, block_info.block_size, &buf[0]);
// ... And we can cross slices
CheckRead(vp_fd.get(), block_info.block_size * last_block, block_info.block_size * 2, &buf[0]);
// Try allocating the rest of the slices, rebinding, and ensuring
// that the size stays updated.
ASSERT_EQ(fuchsia_hardware_block_BlockGetInfo(partition_channel->get(), &status, &block_info),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
ASSERT_EQ(block_info.block_count * block_info.block_size, kSliceSize * 2);
offset = 2;
length = slices_left;
ASSERT_EQ(
fuchsia_hardware_block_volume_VolumeExtend(partition_channel->get(), offset, length, &status),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
ASSERT_EQ(fuchsia_hardware_block_BlockGetInfo(partition_channel->get(), &status, &block_info),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
ASSERT_EQ(block_info.block_count * block_info.block_size, kSliceSize * kSliceCount);
ASSERT_EQ(close(vp_fd.release()), 0);
ASSERT_EQ(close(fd.release()), 0);
FVMRebind(entries, 1);
fd = fvm_device();
vp_fd.reset(open_partition_with_devfs(devfs_root().get(), kTestUniqueGUID, kTestPartGUIDData, 0,
nullptr));
ASSERT_TRUE(vp_fd, "Couldn't re-open Data VPart");
partition_caller.reset(vp_fd.get());
partition_channel = zx::unowned_channel(partition_caller.borrow_channel());
ASSERT_EQ(fuchsia_hardware_block_BlockGetInfo(partition_channel->get(), &status, &block_info),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
ASSERT_EQ(block_info.block_count * block_info.block_size, kSliceSize * kSliceCount);
ASSERT_EQ(close(vp_fd.release()), 0);
ASSERT_EQ(close(fd.release()), 0);
FVMCheckSliceSize(fvm_device(), 64lu * (1 << 20));
}
void CorruptMountHelper(const fbl::unique_fd& devfs_root, const char* partition_path,
const mount_options_t* mounting_options, disk_format_t disk_format,
const query_request_t& query_request) {
// Format the VPart as |disk_format|.
ASSERT_EQ(mkfs(partition_path, disk_format, launch_stdio_sync, &default_mkfs_options), ZX_OK);
fbl::unique_fd vp_fd(
open_partition_with_devfs(devfs_root.get(), kTestUniqueGUID, kTestPartGUIDData, 0, nullptr));
ASSERT_TRUE(vp_fd);
fuchsia_hardware_block_volume_VsliceRange
ranges[fuchsia_hardware_block_volume_MAX_SLICE_REQUESTS];
fuchsia_hardware_block_volume_VsliceRange
initial_ranges[fuchsia_hardware_block_volume_MAX_SLICE_REQUESTS];
zx_status_t status;
size_t actual_ranges_count;
// Check initial slice allocation.
//
// Avoid keping the "FdioCaller" in-scope across mount, as the caller prevents
// the file descriptor from being transferred.
{
fdio_cpp::UnownedFdioCaller partition_caller(vp_fd.get());
zx::unowned_channel partition_channel(partition_caller.borrow_channel());
ASSERT_EQ(fuchsia_hardware_block_volume_VolumeQuerySlices(
partition_channel->get(), query_request.vslice_start, query_request.count,
&status, ranges, &actual_ranges_count),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
ASSERT_EQ(query_request.count, actual_ranges_count);
for (unsigned i = 0; i < actual_ranges_count; i++) {
ASSERT_TRUE(ranges[i].allocated);
ASSERT_GT(ranges[i].count, 0);
initial_ranges[i] = ranges[i];
}
// Manually shrink slices so FVM will differ from the partition.
uint64_t offset = query_request.vslice_start[0] + ranges[0].count - 1;
uint64_t length = 1;
ASSERT_EQ(fuchsia_hardware_block_volume_VolumeShrink(partition_channel->get(), offset, length,
&status),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
// Check slice allocation after manual grow/shrink
ASSERT_EQ(fuchsia_hardware_block_volume_VolumeQuerySlices(
partition_channel->get(), query_request.vslice_start, query_request.count,
&status, ranges, &actual_ranges_count),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
ASSERT_EQ(query_request.count, actual_ranges_count);
ASSERT_FALSE(ranges[0].allocated);
ASSERT_EQ(ranges[0].count, query_request.vslice_start[1] - query_request.vslice_start[0]);
}
// Try to mount the VPart. Since this mount call is supposed to fail, we wait for the spawned
// fs process to finish and associated fidl channels to close before continuing to try and prevent
// race conditions with the later mount call.
ASSERT_NE(mount(vp_fd.release(), kMountPath, disk_format, mounting_options, launch_stdio_sync),
ZX_OK);
{
vp_fd.reset(open_partition_with_devfs(devfs_root.get(), kTestUniqueGUID, kTestPartGUIDData, 0,
nullptr));
ASSERT_TRUE(vp_fd);
fdio_cpp::UnownedFdioCaller partition_caller(vp_fd.get());
zx::unowned_channel partition_channel(partition_caller.borrow_channel());
// Grow back the slice we shrunk earlier.
uint64_t offset = query_request.vslice_start[0];
uint64_t length = 1;
ASSERT_EQ(fuchsia_hardware_block_volume_VolumeExtend(partition_channel->get(), offset, length,
&status),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
// Verify grow was successful.
ASSERT_EQ(fuchsia_hardware_block_volume_VolumeQuerySlices(
partition_channel->get(), query_request.vslice_start, query_request.count,
&status, ranges, &actual_ranges_count),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
ASSERT_EQ(query_request.count, actual_ranges_count);
ASSERT_TRUE(ranges[0].allocated);
ASSERT_EQ(ranges[0].count, 1);
// Now extend all extents by some number of additional slices.
fuchsia_hardware_block_volume_VsliceRange
ranges_before_extend[fuchsia_hardware_block_volume_MAX_SLICE_REQUESTS];
for (unsigned i = 0; i < query_request.count; i++) {
ranges_before_extend[i] = ranges[i];
uint64_t offset = query_request.vslice_start[i] + ranges[i].count;
uint64_t length = query_request.count - i;
ASSERT_EQ(fuchsia_hardware_block_volume_VolumeExtend(partition_channel->get(), offset, length,
&status),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
}
// Verify that the extensions were successful.
ASSERT_EQ(fuchsia_hardware_block_volume_VolumeQuerySlices(
partition_channel->get(), query_request.vslice_start, query_request.count,
&status, ranges, &actual_ranges_count),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
ASSERT_EQ(query_request.count, actual_ranges_count);
for (unsigned i = 0; i < query_request.count; i++) {
ASSERT_TRUE(ranges[i].allocated);
ASSERT_EQ(ranges[i].count, ranges_before_extend[i].count + query_request.count - i);
}
}
// Try mount again.
ASSERT_EQ(mount(vp_fd.release(), kMountPath, disk_format, mounting_options, launch_stdio_async),
ZX_OK);
ASSERT_EQ(umount(kMountPath), ZX_OK);
vp_fd.reset(
open_partition_with_devfs(devfs_root.get(), kTestUniqueGUID, kTestPartGUIDData, 0, nullptr));
ASSERT_TRUE(vp_fd);
fdio_cpp::UnownedFdioCaller partition_caller(vp_fd.get());
zx::unowned_channel partition_channel(partition_caller.borrow_channel());
// Verify that slices were fixed on mount.
ASSERT_EQ(fuchsia_hardware_block_volume_VolumeQuerySlices(
partition_channel->get(), query_request.vslice_start, query_request.count, &status,
ranges, &actual_ranges_count),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
ASSERT_EQ(query_request.count, actual_ranges_count);
for (unsigned i = 0; i < query_request.count; i++) {
ASSERT_TRUE(ranges[i].allocated);
ASSERT_EQ(ranges[i].count, initial_ranges[i].count);
}
}
TEST_F(FvmTest, TestCorruptMount) {
constexpr uint64_t kBlockSize = 512;
constexpr uint64_t kBlockCount = 1 << 16;
constexpr uint64_t kSliceSize = 64 * kBlockSize;
CreateFVM(kBlockSize, kBlockCount, kSliceSize);
fbl::unique_fd fd = fvm_device();
ASSERT_TRUE(fd);
volume_info_t volume_info;
ASSERT_EQ(fvm_query(fd.get(), &volume_info), ZX_OK);
ASSERT_EQ(kSliceSize, volume_info.slice_size);
// Allocate one VPart
alloc_req_t request;
memset(&request, 0, sizeof(request));
request.slice_count = 1;
memcpy(request.guid, kTestUniqueGUID, BLOCK_GUID_LEN);
strcpy(request.name, kTestPartName1);
memcpy(request.type, kTestPartGUIDData, BLOCK_GUID_LEN);
fbl::unique_fd vp_fd(fvm_allocate_partition_with_devfs(devfs_root().get(), fd.get(), &request));
ASSERT_TRUE(vp_fd);
ASSERT_EQ(close(vp_fd.release()), 0);
char partition_path[PATH_MAX];
snprintf(partition_path, sizeof(partition_path), "%s/%s-p-1/block", fvm_path(), kTestPartName1);
size_t kMinfsBlocksPerSlice = kSliceSize / minfs::kMinfsBlockSize;
query_request_t query_request;
query_request.count = 4;
query_request.vslice_start[0] = minfs::kFVMBlockInodeBmStart / kMinfsBlocksPerSlice;
query_request.vslice_start[1] = minfs::kFVMBlockDataBmStart / kMinfsBlocksPerSlice;
query_request.vslice_start[2] = minfs::kFVMBlockInodeStart / kMinfsBlocksPerSlice;
query_request.vslice_start[3] = minfs::kFVMBlockDataStart / kMinfsBlocksPerSlice;
// Run the test for Minfs.
CorruptMountHelper(devfs_root(), partition_path, &mounting_options_, DISK_FORMAT_MINFS,
query_request);
size_t kBlobfsBlocksPerSlice = kSliceSize / blobfs::kBlobfsBlockSize;
query_request.count = 3;
query_request.vslice_start[0] = blobfs::kFVMBlockMapStart / kBlobfsBlocksPerSlice;
query_request.vslice_start[1] = blobfs::kFVMNodeMapStart / kBlobfsBlocksPerSlice;
query_request.vslice_start[2] = blobfs::kFVMDataStart / kBlobfsBlocksPerSlice;
// Run the test for Blobfs.
CorruptMountHelper(devfs_root(), partition_path, &mounting_options_, DISK_FORMAT_BLOBFS,
query_request);
// Clean up
ASSERT_EQ(rmdir(kMountPath), 0);
ASSERT_EQ(close(fd.release()), 0);
}
TEST_F(FvmTest, TestVPartitionUpgrade) {
constexpr uint64_t kBlockSize = 512;
constexpr uint64_t kBlockCount = 1 << 16;
constexpr uint64_t kSliceSize = 64 * kBlockSize;
CreateFVM(kBlockSize, kBlockCount, kSliceSize);
fbl::unique_fd fd = fvm_device();
ASSERT_TRUE(fd);
fdio_cpp::FdioCaller volume_manager(std::move(fd));
// Short-hand for asking if we can open a partition.
auto openable = [this](const uint8_t* instanceGUID, const uint8_t* typeGUID) {
fbl::unique_fd fd(
open_partition_with_devfs(devfs_root().get(), instanceGUID, typeGUID, 0, nullptr));
return fd.is_valid();
};
// Allocate two VParts, one active, and one inactive.
alloc_req_t request;
memset(&request, 0, sizeof(request));
request.flags = fuchsia_hardware_block_volume_ALLOCATE_PARTITION_FLAG_INACTIVE;
request.slice_count = 1;
memcpy(request.guid, kTestUniqueGUID, BLOCK_GUID_LEN);
strcpy(request.name, kTestPartName1);
memcpy(request.type, kTestPartGUIDData, BLOCK_GUID_LEN);
fbl::unique_fd vp_fd(
fvm_allocate_partition_with_devfs(devfs_root().get(), volume_manager.fd().get(), &request));
ASSERT_TRUE(vp_fd, "Couldn't open Volume");
ASSERT_EQ(close(vp_fd.release()), 0);
request.flags = 0;
memcpy(request.guid, kTestUniqueGUID2, BLOCK_GUID_LEN);
strcpy(request.name, kTestPartName2);
vp_fd.reset(
fvm_allocate_partition_with_devfs(devfs_root().get(), volume_manager.fd().get(), &request));
ASSERT_TRUE(vp_fd, "Couldn't open volume");
ASSERT_EQ(close(vp_fd.release()), 0);
const partition_entry_t entries[] = {
{kTestPartName2, 2},
};
// Release FVM device that we opened earlier
ASSERT_EQ(close(volume_manager.release().get()), 0);
FVMRebind(entries, 1);
volume_manager.reset(fvm_device());
// We shouldn't be able to re-open the inactive partition...
ASSERT_FALSE(openable(kTestUniqueGUID, kTestPartGUIDData));
// ... but we SHOULD be able to re-open the active partition.
ASSERT_TRUE(openable(kTestUniqueGUID2, kTestPartGUIDData));
// Try to upgrade the partition (from GUID2 --> GUID)
request.flags = fuchsia_hardware_block_volume_ALLOCATE_PARTITION_FLAG_INACTIVE;
memcpy(request.guid, kTestUniqueGUID, BLOCK_GUID_LEN);
strcpy(request.name, kTestPartName1);
fbl::unique_fd new_fd(
fvm_allocate_partition_with_devfs(devfs_root().get(), volume_manager.fd().get(), &request));
ASSERT_TRUE(new_fd, "Couldn't open new volume");
ASSERT_EQ(close(new_fd.release()), 0);
Upgrade(volume_manager, kTestUniqueGUID2, kTestUniqueGUID, ZX_OK);
// After upgrading, we should be able to open both partitions
ASSERT_TRUE(openable(kTestUniqueGUID, kTestPartGUIDData));
ASSERT_TRUE(openable(kTestUniqueGUID2, kTestPartGUIDData));
// Rebind the FVM driver, check the upgrade has succeeded.
// The original (GUID2) should be deleted, and the new partition (GUID)
// should exist.
const partition_entry_t upgraded_entries[] = {
{kTestPartName1, 1},
};
// Release FVM device that we opened earlier
ASSERT_EQ(close(volume_manager.release().get()), 0);
FVMRebind(upgraded_entries, 1);
volume_manager.reset(fvm_device());
ASSERT_TRUE(openable(kTestUniqueGUID, kTestPartGUIDData));
ASSERT_FALSE(openable(kTestUniqueGUID2, kTestPartGUIDData));
// Try upgrading when the "new" version doesn't exist.
// (It should return an error and have no noticable effect).
Upgrade(volume_manager, kTestUniqueGUID, kTestUniqueGUID2, ZX_ERR_NOT_FOUND);
// Release FVM device that we opened earlier
ASSERT_EQ(close(volume_manager.release().get()), 0);
FVMRebind(upgraded_entries, 1);
volume_manager.reset(fvm_device());
ASSERT_TRUE(openable(kTestUniqueGUID, kTestPartGUIDData));
ASSERT_FALSE(openable(kTestUniqueGUID2, kTestPartGUIDData));
// Try upgrading when the "old" version doesn't exist.
request.flags = fuchsia_hardware_block_volume_ALLOCATE_PARTITION_FLAG_INACTIVE;
memcpy(request.guid, kTestUniqueGUID2, BLOCK_GUID_LEN);
strcpy(request.name, kTestPartName2);
new_fd.reset(
fvm_allocate_partition_with_devfs(devfs_root().get(), volume_manager.fd().get(), &request));
ASSERT_TRUE(new_fd, "Couldn't open volume");
ASSERT_EQ(close(new_fd.release()), 0);
uint8_t fake_guid[BLOCK_GUID_LEN];
memset(fake_guid, 0, BLOCK_GUID_LEN);
Upgrade(volume_manager, fake_guid, kTestUniqueGUID2, ZX_OK);
const partition_entry_t upgraded_entries_both[] = {
{kTestPartName1, 1},
{kTestPartName2, 2},
};
// Release FVM device that we opened earlier
ASSERT_EQ(close(volume_manager.release().get()), 0);
FVMRebind(upgraded_entries_both, 2);
volume_manager.reset(fvm_device());
// We should be able to open both partitions again.
ASSERT_TRUE(openable(kTestUniqueGUID, kTestPartGUIDData));
ASSERT_TRUE(openable(kTestUniqueGUID2, kTestPartGUIDData));
// Destroy and reallocate the first partition as inactive.
vp_fd.reset(open_partition_with_devfs(devfs_root().get(), kTestUniqueGUID, kTestPartGUIDData, 0,
nullptr));
ASSERT_TRUE(vp_fd, "Couldn't open volume");
fdio_cpp::FdioCaller partition_caller(std::move(vp_fd));
zx_status_t status;
ASSERT_EQ(fuchsia_hardware_block_volume_VolumeDestroy(partition_caller.borrow_channel(), &status),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
partition_caller.reset();
request.flags = fuchsia_hardware_block_volume_ALLOCATE_PARTITION_FLAG_INACTIVE;
memcpy(request.guid, kTestUniqueGUID, BLOCK_GUID_LEN);
strcpy(request.name, kTestPartName1);
new_fd.reset(
fvm_allocate_partition_with_devfs(devfs_root().get(), volume_manager.fd().get(), &request));
ASSERT_TRUE(new_fd, "Couldn't open volume");
ASSERT_EQ(close(new_fd.release()), 0);
// Upgrade the partition with old_guid == new_guid.
// This should activate the partition.
Upgrade(volume_manager, kTestUniqueGUID, kTestUniqueGUID, ZX_OK);
// Release FVM device that we opened earlier
ASSERT_EQ(close(volume_manager.release().get()), 0);
FVMRebind(upgraded_entries_both, 2);
volume_manager.reset(fvm_device());
// We should be able to open both partitions again.
ASSERT_TRUE(openable(kTestUniqueGUID, kTestPartGUIDData));
ASSERT_TRUE(openable(kTestUniqueGUID2, kTestPartGUIDData));
}
// Test that the FVM driver can mount filesystems.
TEST_F(FvmTest, TestMounting) {
constexpr uint64_t kBlockSize = 512;
constexpr uint64_t kBlockCount = 1 << 16;
constexpr uint64_t kSliceSize = 64 * kBlockSize;
CreateFVM(kBlockSize, kBlockCount, kSliceSize);
fbl::unique_fd fd = fvm_device();
ASSERT_TRUE(fd);
volume_info_t volume_info;
ASSERT_EQ(fvm_query(fd.get(), &volume_info), ZX_OK);
// Allocate one VPart
alloc_req_t request;
memset(&request, 0, sizeof(request));
request.slice_count = 1;
memcpy(request.guid, kTestUniqueGUID, BLOCK_GUID_LEN);
strcpy(request.name, kTestPartName1);
memcpy(request.type, kTestPartGUIDData, BLOCK_GUID_LEN);
fbl::unique_fd vp_fd(fvm_allocate_partition_with_devfs(devfs_root().get(), fd.get(), &request));
ASSERT_TRUE(vp_fd);
// Format the VPart as minfs
char partition_path[PATH_MAX];
snprintf(partition_path, sizeof(partition_path), "%s/%s-p-1/block", fvm_path(), kTestPartName1);
ASSERT_EQ(mkfs(partition_path, DISK_FORMAT_MINFS, launch_stdio_sync, &default_mkfs_options),
ZX_OK);
// Mount the VPart
ASSERT_EQ(
mount(vp_fd.release(), kMountPath, DISK_FORMAT_MINFS, &mounting_options_, launch_stdio_async),
ZX_OK);
// Verify that the mount was successful.
fbl::unique_fd rootfd(open(kMountPath, O_RDONLY | O_DIRECTORY));
ASSERT_TRUE(rootfd);
fdio_cpp::FdioCaller caller(std::move(rootfd));
auto result = fidl::WireCall(fidl::UnownedClientEnd<fio::DirectoryAdmin>(caller.borrow_channel()))
.QueryFilesystem();
ASSERT_TRUE(result.ok());
const char* kFsName = "minfs";
const char* name = reinterpret_cast<const char*>(result.value().info->name.data());
ASSERT_EQ(strncmp(name, kFsName, strlen(kFsName)), 0, "Unexpected filesystem mounted");
// Verify that MinFS does not try to use more of the VPartition than
// was originally allocated.
ASSERT_LE(result.value().info->total_bytes, kSliceSize * request.slice_count);
// Clean up.
ASSERT_EQ(umount(kMountPath), ZX_OK);
ASSERT_EQ(rmdir(kMountPath), 0);
ASSERT_EQ(close(fd.release()), 0);
FVMCheckSliceSize(fvm_device(), kSliceSize);
}
// Test that FVM-aware filesystem can be reformatted.
TEST_F(FvmTest, TestMkfs) {
constexpr uint64_t kBlockSize = 512;
constexpr uint64_t kBlockCount = 1 << 16;
constexpr uint64_t kSliceSize = 64 * kBlockSize;
CreateFVM(kBlockSize, kBlockCount, kSliceSize);
fbl::unique_fd fd = fvm_device();
ASSERT_TRUE(fd);
volume_info_t volume_info;
ASSERT_EQ(fvm_query(fd.get(), &volume_info), ZX_OK);
// Allocate one VPart.
alloc_req_t request;
memset(&request, 0, sizeof(request));
request.slice_count = 1;
memcpy(request.guid, kTestUniqueGUID, BLOCK_GUID_LEN);
strcpy(request.name, kTestPartName1);
memcpy(request.type, kTestPartGUIDData, BLOCK_GUID_LEN);
fbl::unique_fd vp_fd(fvm_allocate_partition_with_devfs(devfs_root().get(), fd.get(), &request));
ASSERT_TRUE(vp_fd);
// Format the VPart as minfs.
char partition_path[PATH_MAX];
snprintf(partition_path, sizeof(partition_path), "%s/%s-p-1/block", fvm_path(), kTestPartName1);
ASSERT_EQ(mkfs(partition_path, DISK_FORMAT_MINFS, launch_stdio_sync, &default_mkfs_options),
ZX_OK);
// Format it as MinFS again, even though it is already formatted.
ASSERT_EQ(mkfs(partition_path, DISK_FORMAT_MINFS, launch_stdio_sync, &default_mkfs_options),
ZX_OK);
// Now try reformatting as blobfs.
ASSERT_EQ(mkfs(partition_path, DISK_FORMAT_BLOBFS, launch_stdio_sync, &default_mkfs_options),
ZX_OK);
// Demonstrate that mounting as minfs will fail, but mounting as blobfs
// is successful.
ASSERT_NE(
mount(vp_fd.release(), kMountPath, DISK_FORMAT_MINFS, &mounting_options_, launch_stdio_sync),
ZX_OK);
vp_fd.reset(open(partition_path, O_RDWR));
ASSERT_TRUE(vp_fd);
ASSERT_EQ(mount(vp_fd.release(), kMountPath, DISK_FORMAT_BLOBFS, &mounting_options_,
launch_stdio_async),
ZX_OK);
ASSERT_EQ(umount(kMountPath), ZX_OK);
// ... and reformat back to MinFS again.
ASSERT_EQ(mkfs(partition_path, DISK_FORMAT_MINFS, launch_stdio_sync, &default_mkfs_options),
ZX_OK);
// Mount the VPart.
vp_fd.reset(open(partition_path, O_RDWR));
ASSERT_TRUE(vp_fd);
ASSERT_EQ(
mount(vp_fd.release(), kMountPath, DISK_FORMAT_MINFS, &mounting_options_, launch_stdio_async),
ZX_OK);
// Verify that the mount was successful.
fbl::unique_fd rootfd(open(kMountPath, O_RDONLY | O_DIRECTORY));
ASSERT_TRUE(rootfd);
fdio_cpp::FdioCaller caller(std::move(rootfd));
auto result = fidl::WireCall(fidl::UnownedClientEnd<fio::DirectoryAdmin>(caller.borrow_channel()))
.QueryFilesystem();
ASSERT_TRUE(result.ok());
const char* kFsName = "minfs";
const char* name = reinterpret_cast<const char*>(result.value().info->name.data());
ASSERT_EQ(strncmp(name, kFsName, strlen(kFsName)), 0, "Unexpected filesystem mounted");
// Verify that MinFS does not try to use more of the VPartition than
// was originally allocated.
ASSERT_LE(result.value().info->total_bytes, kSliceSize * request.slice_count);
// Clean up.
ASSERT_EQ(umount(kMountPath), ZX_OK);
ASSERT_EQ(rmdir(kMountPath), 0);
FVMCheckSliceSize(fvm_device(), kSliceSize);
}
// Test that the FVM can recover when one copy of
// metadata becomes corrupt.
TEST_F(FvmTest, TestCorruptionOk) {
constexpr uint64_t kBlockSize = 512;
constexpr uint64_t kBlockCount = 1 << 16;
constexpr uint64_t kSliceSize = 64 * kBlockSize;
CreateFVM(kBlockSize, kBlockCount, kSliceSize);
fbl::unique_fd fd = fvm_device();
ASSERT_TRUE(fd);
fbl::unique_fd ramdisk_fd = ramdisk_device();
ASSERT_TRUE(ramdisk_fd);
volume_info_t volume_info;
ASSERT_EQ(fvm_query(fd.get(), &volume_info), ZX_OK);
// Allocate one VPart (writes to backup)
alloc_req_t request;
memset(&request, 0, sizeof(request));
request.slice_count = 1;
memcpy(request.guid, kTestUniqueGUID, BLOCK_GUID_LEN);
strcpy(request.name, kTestPartName1);
memcpy(request.type, kTestPartGUIDData, BLOCK_GUID_LEN);
fbl::unique_fd vp_fd(fvm_allocate_partition_with_devfs(devfs_root().get(), fd.get(), &request));
ASSERT_TRUE(vp_fd);
// Extend the vpart (writes to primary)
fdio_cpp::UnownedFdioCaller partition_caller(vp_fd.get());
zx::unowned_channel partition_channel(partition_caller.borrow_channel());
zx_status_t status;
uint64_t offset = 1;
uint64_t length = 1;
ASSERT_EQ(
fuchsia_hardware_block_volume_VolumeExtend(partition_channel->get(), offset, length, &status),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
fuchsia_hardware_block_BlockInfo block_info;
ASSERT_EQ(fuchsia_hardware_block_BlockGetInfo(partition_channel->get(), &status, &block_info),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
ASSERT_EQ(block_info.block_count * block_info.block_size, kSliceSize * 2);
// Initial slice access
CheckWriteReadBlock(vp_fd.get(), 0, 1);
// Extended slice access
CheckWriteReadBlock(vp_fd.get(), kSliceSize / block_info.block_size, 1);
ASSERT_EQ(close(vp_fd.release()), 0);
// Corrupt the (backup) metadata and rebind.
// The 'primary' was the last one written, so it'll be used.
fvm::Header header =
fvm::Header::FromDiskSize(fvm::kMaxUsablePartitions, kBlockSize * kBlockCount, kSliceSize);
off_t off = header.GetSuperblockOffset(fvm::SuperblockType::kSecondary);
uint8_t buf[fvm::kBlockSize];
ASSERT_EQ(lseek(ramdisk_fd.get(), off, SEEK_SET), off);
ASSERT_EQ(read(ramdisk_fd.get(), buf, sizeof(buf)), sizeof(buf));
// Modify an arbitrary byte (not the magic bits; we still want it to mount!)
buf[128]++;
ASSERT_EQ(lseek(ramdisk_fd.get(), off, SEEK_SET), off);
ASSERT_EQ(write(ramdisk_fd.get(), buf, sizeof(buf)), sizeof(buf));
const partition_entry_t entries[] = {
{kTestPartName1, 1},
};
ASSERT_EQ(close(fd.release()), 0);
FVMRebind(entries, 1);
fd = fvm_device();
vp_fd.reset(open_partition_with_devfs(devfs_root().get(), kTestUniqueGUID, kTestPartGUIDData, 0,
nullptr));
ASSERT_TRUE(vp_fd, "Couldn't re-open Data VPart");
// The slice extension is still accessible.
CheckWriteReadBlock(vp_fd.get(), 0, 1);
CheckWriteReadBlock(vp_fd.get(), kSliceSize / block_info.block_size, 1);
// Clean up
ASSERT_EQ(close(vp_fd.release()), 0);
ASSERT_EQ(close(fd.release()), 0);
ASSERT_EQ(close(ramdisk_fd.release()), 0);
FVMCheckSliceSize(fvm_device(), kSliceSize);
}
TEST_F(FvmTest, TestCorruptionRegression) {
constexpr uint64_t kBlockSize = 512;
constexpr uint64_t kBlockCount = 1 << 16;
constexpr uint64_t kSliceSize = 64 * kBlockSize;
CreateFVM(kBlockSize, kBlockCount, kSliceSize);
fbl::unique_fd fd(fvm_device());
ASSERT_TRUE(fd);
fbl::unique_fd ramdisk_fd = ramdisk_device();
ASSERT_TRUE(ramdisk_fd);
volume_info_t volume_info;
ASSERT_EQ(fvm_query(fd.get(), &volume_info), ZX_OK);
size_t slice_size = volume_info.slice_size;
// Allocate one VPart (writes to backup)
alloc_req_t request;
memset(&request, 0, sizeof(request));
request.slice_count = 1;
memcpy(request.guid, kTestUniqueGUID, BLOCK_GUID_LEN);
strcpy(request.name, kTestPartName1);
memcpy(request.type, kTestPartGUIDData, BLOCK_GUID_LEN);
fbl::unique_fd vp_fd(fvm_allocate_partition_with_devfs(devfs_root().get(), fd.get(), &request));
ASSERT_TRUE(vp_fd);
fdio_cpp::UnownedFdioCaller partition_caller(vp_fd.get());
zx::unowned_channel partition_channel(partition_caller.borrow_channel());
zx_status_t status;
// Extend the vpart (writes to primary)
uint64_t offset = 1;
uint64_t length = 1;
ASSERT_EQ(
fuchsia_hardware_block_volume_VolumeExtend(partition_channel->get(), offset, length, &status),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
fuchsia_hardware_block_BlockInfo block_info;
ASSERT_EQ(fuchsia_hardware_block_BlockGetInfo(partition_channel->get(), &status, &block_info),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
ASSERT_EQ(block_info.block_count * block_info.block_size, slice_size * 2);
// Initial slice access
CheckWriteReadBlock(vp_fd.get(), 0, 1);
// Extended slice access
CheckWriteReadBlock(vp_fd.get(), slice_size / block_info.block_size, 1);
ASSERT_EQ(close(vp_fd.release()), 0);
// Corrupt the (primary) metadata and rebind.
// The 'primary' was the last one written, so the backup will be used.
off_t off = 0;
uint8_t buf[fvm::kBlockSize];
ASSERT_EQ(lseek(ramdisk_fd.get(), off, SEEK_SET), off);
ASSERT_EQ(read(ramdisk_fd.get(), buf, sizeof(buf)), sizeof(buf));
buf[128]++;
ASSERT_EQ(lseek(ramdisk_fd.get(), off, SEEK_SET), off);
ASSERT_EQ(write(ramdisk_fd.get(), buf, sizeof(buf)), sizeof(buf));
const partition_entry_t entries[] = {
{kTestPartName1, 1},
};
ASSERT_EQ(close(fd.release()), 0);
FVMRebind(entries, 1);
fd = fvm_device();
vp_fd.reset(open_partition_with_devfs(devfs_root().get(), kTestUniqueGUID, kTestPartGUIDData, 0,
nullptr));
ASSERT_TRUE(vp_fd);
// The slice extension is no longer accessible
CheckWriteReadBlock(vp_fd.get(), 0, 1);
CheckNoAccessBlock(vp_fd.get(), slice_size / block_info.block_size, 1);
// Clean up
ASSERT_EQ(close(vp_fd.release()), 0);
ASSERT_EQ(close(fd.release()), 0);
ASSERT_EQ(close(ramdisk_fd.release()), 0);
FVMCheckSliceSize(fvm_device(), kSliceSize);
}
TEST_F(FvmTest, TestCorruptionUnrecoverable) {
constexpr uint64_t kBlockSize = 512;
constexpr uint64_t kBlockCount = 1 << 16;
constexpr uint64_t kSliceSize = 64 * kBlockSize;
CreateFVM(kBlockSize, kBlockCount, kSliceSize);
fbl::unique_fd fd = fvm_device();
ASSERT_TRUE(fd);
// Allocate one VPart (writes to backup)
alloc_req_t request;
memset(&request, 0, sizeof(request));
request.slice_count = 1;
memcpy(request.guid, kTestUniqueGUID, BLOCK_GUID_LEN);
strcpy(request.name, kTestPartName1);
memcpy(request.type, kTestPartGUIDData, BLOCK_GUID_LEN);
fbl::unique_fd vp_fd(fvm_allocate_partition_with_devfs(devfs_root().get(), fd.get(), &request));
ASSERT_TRUE(vp_fd);
fdio_cpp::UnownedFdioCaller partition_caller(vp_fd.get());
zx::unowned_channel partition_channel(partition_caller.borrow_channel());
zx_status_t status;
// Extend the vpart (writes to primary)
uint64_t offset = 1;
uint64_t length = 1;
ASSERT_EQ(
fuchsia_hardware_block_volume_VolumeExtend(partition_channel->get(), offset, length, &status),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
fuchsia_hardware_block_BlockInfo block_info;
ASSERT_EQ(fuchsia_hardware_block_BlockGetInfo(partition_channel->get(), &status, &block_info),
ZX_OK);
ASSERT_EQ(status, ZX_OK);
ASSERT_EQ(block_info.block_count * block_info.block_size, kSliceSize * 2);
// Initial slice access
CheckWriteReadBlock(vp_fd.get(), 0, 1);
// Extended slice access
CheckWriteReadBlock(vp_fd.get(), kSliceSize / block_info.block_size, 1);
ASSERT_EQ(close(vp_fd.release()), 0);
// Corrupt both copies of the metadata.
// The 'primary' was the last one written, so the backup will be used.
off_t off = 0;
uint8_t buf[fvm::kBlockSize];
fbl::unique_fd ramdisk_fd = ramdisk_device();
ASSERT_TRUE(ramdisk_fd);
ASSERT_EQ(lseek(ramdisk_fd.get(), off, SEEK_SET), off);
ASSERT_EQ(read(ramdisk_fd.get(), buf, sizeof(buf)), sizeof(buf));
buf[128]++;
ASSERT_EQ(lseek(ramdisk_fd.get(), off, SEEK_SET), off);
ASSERT_EQ(write(ramdisk_fd.get(), buf, sizeof(buf)), sizeof(buf));
fvm::Header header =
fvm::Header::FromDiskSize(fvm::kMaxUsablePartitions, kBlockSize * kBlockCount, kSliceSize);
off = header.GetSuperblockOffset(fvm::SuperblockType::kSecondary);
ASSERT_EQ(lseek(ramdisk_fd.get(), off, SEEK_SET), off);
ASSERT_EQ(read(ramdisk_fd.get(), buf, sizeof(buf)), sizeof(buf));
buf[128]++;
ASSERT_EQ(lseek(ramdisk_fd.get(), off, SEEK_SET), off);
ASSERT_EQ(write(ramdisk_fd.get(), buf, sizeof(buf)), sizeof(buf));
ValidateFVM(ramdisk_device(), ValidationResult::Corrupted);
// Clean up
ASSERT_EQ(close(ramdisk_fd.release()), 0);
}
// Tests the FVM checker using invalid arguments.
TEST_F(FvmTest, TestCheckBadArguments) {
fvm::Checker checker;
ASSERT_FALSE(checker.Validate(), "Checker should be missing device, block size");
checker.SetBlockSize(512);
ASSERT_FALSE(checker.Validate(), "Checker should be missing device");
checker.SetBlockSize(0);
CreateFVM(512, 1 << 20, 64LU * (1 << 20));
fbl::unique_fd fd = ramdisk_device();
ASSERT_TRUE(fd);
checker.SetDevice(std::move(fd));
ASSERT_FALSE(checker.Validate(), "Checker should be missing block size");
}
// Tests the FVM checker against a just-initialized FVM.
TEST_F(FvmTest, TestCheckNewFVM) {
CreateFVM(512, 1 << 20, 64LU * (1 << 20));
fbl::unique_fd fd = ramdisk_device();
ASSERT_TRUE(fd);
fvm::Checker checker(std::move(fd), 512, true);
ASSERT_TRUE(checker.Validate());
}
TEST_F(FvmTest, TestAbortDriverLoadSmallDevice) {
constexpr uint64_t kMB = 1 << 20;
constexpr uint64_t kGB = 1 << 30;
constexpr uint64_t kBlockSize = 512;
constexpr uint64_t kBlockCount = 50 * kMB / kBlockSize;
constexpr uint64_t kSliceSize = kMB;
constexpr uint64_t kFvmPartitionSize = 4 * kGB;
CreateRamdisk(kBlockSize, kBlockCount);
fbl::unique_fd ramdisk_fd(ramdisk_device());
// Init fvm with a partition bigger than the underlying disk.
fvm_init_with_size(ramdisk_fd.get(), kFvmPartitionSize, kSliceSize);
zx::channel fvm_channel;
// Try to bind an fvm to the disk.
ASSERT_OK(fdio_get_service_handle(ramdisk_fd.get(), fvm_channel.reset_and_get_address()));
// Bind should return ZX_ERR_IO when the load of a driver fails.
auto resp = fidl::WireCall<fuchsia_device::Controller>(zx::unowned_channel(fvm_channel.get()))
.Bind(::fidl::StringView(FVM_DRIVER_LIB));
ASSERT_OK(resp.status());
ASSERT_FALSE(resp->result.is_response());
ASSERT_EQ(resp->result.err(), ZX_ERR_INTERNAL);
// Grow the ramdisk to the appropiate size and bind should succeed.
ASSERT_OK(ramdisk_grow(ramdisk(), kFvmPartitionSize));
// Use Controller::Call::Rebind because the driver might still be
// when init fails. Driver removes the device and will eventually be
// unloaded but Controller::Bind above does not wait until
// the device is removed. Controller::Rebind ensures nothing is
// bound to the device, before it tries to bind the driver again.
auto resp2 = fidl::WireCall<fuchsia_device::Controller>(zx::unowned_channel(fvm_channel.get()))
.Rebind(::fidl::StringView(FVM_DRIVER_LIB));
ASSERT_OK(resp2.status());
ASSERT_TRUE(resp2->result.is_response());
char fvm_path[PATH_MAX];
snprintf(fvm_path, sizeof(fvm_path), "%s/fvm", ramdisk_path());
ASSERT_OK(wait_for_device(fvm_path, zx::duration::infinite().get()));
}
} // namespace