blob: e37015e79989837c498c3014d4751a7534afd5c5 [file] [log] [blame]
// Copyright 2019 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 <lib/async/cpp/paged_vmo.h>
#include <lib/async/dispatcher.h>
#include <lib/fzl/vmo-mapper.h>
#include <lib/zx/pager.h>
#include <limits.h>
#include <threads.h>
#include <array>
#include <cstdint>
#include <memory>
#include <blobfs/compression-algorithm.h>
#include <blobfs/format.h>
#include <digest/digest.h>
#include <digest/merkle-tree.h>
#include <fbl/auto_call.h>
#include <zxtest/zxtest.h>
#include "blob-verifier.h"
#include "pager/page-watcher.h"
#include "pager/user-pager.h"
namespace blobfs {
namespace {
constexpr uint64_t kPagedVmoSize = 10 * PAGE_SIZE;
// kBlobSize is intentionally not page-aligned to exercise edge cases.
constexpr uint64_t kBlobSize = kPagedVmoSize - 42;
constexpr uint64_t kNumReadRequests = 100;
constexpr uint64_t kNumThreads = 10;
// Like a Blob w.r.t. the pager - creates a VMO linked to the pager and issues reads on it.
class MockBlob {
public:
MockBlob(char identifier, UserPager* pager, BlobfsMetrics* metrics) : identifier_(identifier) {
char data[kBlobSize];
memset(data, identifier, kBlobSize);
size_t tree_len;
Digest root;
ASSERT_OK(digest::MerkleTreeCreator::Create(data, kBlobSize, &merkle_tree_, &tree_len, &root));
std::unique_ptr<BlobVerifier> verifier;
ASSERT_OK(BlobVerifier::Create(std::move(root), metrics, merkle_tree_.get(), tree_len,
kBlobSize, &verifier));
UserPagerInfo pager_info;
pager_info.verifier = std::move(verifier);
pager_info.identifier = identifier_;
pager_info.data_length_bytes = kBlobSize;
pager_info.compression_algorithm = CompressionAlgorithm::UNCOMPRESSED;
page_watcher_ = std::make_unique<PageWatcher>(pager, std::move(pager_info));
ASSERT_OK(page_watcher_->CreatePagedVmo(kPagedVmoSize, &vmo_));
// Make sure the vmo is valid and of the desired size.
ASSERT_TRUE(vmo_.is_valid());
uint64_t vmo_size;
ASSERT_OK(vmo_.get_size(&vmo_size));
ASSERT_EQ(vmo_size, kPagedVmoSize);
// Make sure the vmo is pager-backed.
zx_info_vmo_t info;
ASSERT_OK(vmo_.get_info(ZX_INFO_VMO, &info, sizeof(info), nullptr, nullptr));
ASSERT_NE(info.flags & ZX_INFO_VMO_PAGER_BACKED, 0);
}
~MockBlob() { page_watcher_->DetachPagedVmoSync(); }
void Read(uint64_t offset, uint64_t length) {
char buf[length];
ASSERT_OK(vmo_.read(buf, offset, length));
if (length > 0) {
char comp[length];
memset(comp, identifier_, length);
// Make sure we got back the expected bytes.
ASSERT_EQ(memcmp(comp, buf, length), 0);
}
}
private:
zx::vmo vmo_;
std::unique_ptr<PageWatcher> page_watcher_;
char identifier_;
std::unique_ptr<uint8_t[]> merkle_tree_;
};
// Mock user pager. Defines the UserPager interface such that the result of reads on distinct
// mock blobs can be verified.
class MockPager : public UserPager {
public:
MockPager() { InitPager(); }
private:
zx_status_t AttachTransferVmo(const zx::vmo& transfer_vmo) override {
vmo_ = zx::unowned_vmo(transfer_vmo);
return ZX_OK;
}
zx_status_t PopulateTransferVmo(uint64_t offset, uint64_t length, UserPagerInfo* info) override {
// Fill the transfer buffer with the blob's identifier character, to service page requests. The
// identifier helps us distinguish between blobs.
char text[kBlobfsBlockSize];
memset(text, static_cast<char>(info->identifier), kBlobfsBlockSize);
for (uint32_t i = 0; i < length; i += kBlobfsBlockSize) {
zx_status_t status = vmo_->write(text, i, kBlobfsBlockSize);
if (status != ZX_OK) {
return status;
}
}
return ZX_OK;
}
zx_status_t VerifyTransferVmo(uint64_t offset, uint64_t length, const zx::vmo& transfer_vmo,
UserPagerInfo* info) override {
fzl::VmoMapper mapping;
auto unmap = fbl::MakeAutoCall([&]() { mapping.Unmap(); });
// Map the transfer VMO in order to pass the verifier a pointer to the data.
zx_status_t status = mapping.Map(transfer_vmo, 0, length, ZX_VM_PERM_READ);
if (status != ZX_OK) {
return status;
}
return info->verifier->VerifyPartial(mapping.start(), length, offset);
}
zx::unowned_vmo vmo_;
};
class BlobfsPagerTest : public zxtest::Test {
public:
void SetUp() override { pager_ = std::make_unique<MockPager>(); }
std::unique_ptr<MockBlob> CreateBlob(char identifier = 'z') {
return std::make_unique<MockBlob>(identifier, pager_.get(), &metrics_);
}
void ResetPager() { pager_.reset(); }
private:
std::unique_ptr<MockPager> pager_;
BlobfsMetrics metrics_;
};
void GetRandomOffsetAndLength(unsigned int* seed, uint64_t* offset, uint64_t* length) {
*offset = rand_r(seed) % kPagedVmoSize;
*length = 1 + (rand_r(seed) % (kPagedVmoSize - *offset));
}
struct ReadBlobFnArgs {
MockBlob* blob;
unsigned int seed = zxtest::Runner::GetInstance()->random_seed();
};
int ReadBlobFn(void* args) {
auto fnArgs = static_cast<ReadBlobFnArgs*>(args);
uint64_t offset, length;
for (uint64_t i = 0; i < kNumReadRequests; i++) {
GetRandomOffsetAndLength(&fnArgs->seed, &offset, &length);
fnArgs->blob->Read(offset, length);
}
return 0;
}
TEST_F(BlobfsPagerTest, CreateBlob) { auto blob = CreateBlob(); }
TEST_F(BlobfsPagerTest, ReadSequential) {
auto blob = CreateBlob();
blob->Read(0, kPagedVmoSize);
// Issue a repeated read on the same range.
blob->Read(0, kPagedVmoSize);
}
TEST_F(BlobfsPagerTest, ReadRandom) {
auto blob = CreateBlob();
uint64_t offset, length;
unsigned int seed = zxtest::Runner::GetInstance()->random_seed();
for (uint64_t i = 0; i < kNumReadRequests; i++) {
GetRandomOffsetAndLength(&seed, &offset, &length);
blob->Read(offset, length);
}
}
TEST_F(BlobfsPagerTest, CreateMultipleBlobs) {
auto blob1 = CreateBlob();
auto blob2 = CreateBlob();
auto blob3 = CreateBlob();
}
TEST_F(BlobfsPagerTest, ReadRandomMultipleBlobs) {
std::unique_ptr<MockBlob> blobs[3] = {CreateBlob('x'), CreateBlob('y'), CreateBlob('z')};
uint64_t offset, length;
unsigned int seed = zxtest::Runner::GetInstance()->random_seed();
for (uint64_t i = 0; i < kNumReadRequests; i++) {
uint64_t index = rand() % 3;
GetRandomOffsetAndLength(&seed, &offset, &length);
blobs[index]->Read(offset, length);
}
}
TEST_F(BlobfsPagerTest, ReadRandomMultithreaded) {
auto blob = CreateBlob();
std::array<thrd_t, kNumThreads> threads;
std::array<ReadBlobFnArgs, kNumThreads> args;
// All the threads will issue reads on the same blob.
for (uint64_t i = 0; i < kNumThreads; i++) {
args[i].blob = blob.get();
args[i].seed = static_cast<unsigned int>(i);
ASSERT_EQ(thrd_create(&threads[i], ReadBlobFn, &args[i]), thrd_success);
}
for (uint64_t i = 0; i < kNumThreads; i++) {
int res;
ASSERT_EQ(thrd_join(threads[i], &res), thrd_success);
ASSERT_EQ(res, 0);
}
}
TEST_F(BlobfsPagerTest, ReadRandomMultipleBlobsMultithreaded) {
constexpr uint64_t kNumBlobs = 3;
std::unique_ptr<MockBlob> blobs[kNumBlobs] = {CreateBlob('x'), CreateBlob('y'), CreateBlob('z')};
std::array<thrd_t, kNumBlobs> threads;
std::array<ReadBlobFnArgs, kNumThreads> args;
// Each thread will issue reads on a different blob.
for (uint64_t i = 0; i < kNumBlobs; i++) {
args[i].blob = blobs[i].get();
args[i].seed = static_cast<unsigned int>(i);
ASSERT_EQ(thrd_create(&threads[i], ReadBlobFn, &args[i]), thrd_success);
}
for (uint64_t i = 0; i < kNumBlobs; i++) {
int res;
ASSERT_EQ(thrd_join(threads[i], &res), thrd_success);
ASSERT_EQ(res, 0);
}
}
TEST_F(BlobfsPagerTest, AsyncLoopShutdown) {
auto blob = CreateBlob();
// Verify that we can exit cleanly if the UserPager (and its member async loop) is destroyed.
ResetPager();
}
} // namespace
} // namespace blobfs