blob: 10874c712a760cbc9f30db8277b639c1af5983d6 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/storage/blobfs/service/health_check.h"
#include <gtest/gtest.h>
#include "src/storage/blobfs/blob.h"
#include "src/storage/blobfs/blobfs.h"
#include "src/storage/blobfs/format.h"
#include "src/storage/blobfs/mkfs.h"
#include "src/storage/blobfs/mount.h"
#include "src/storage/blobfs/test/blob_utils.h"
#include "src/storage/blobfs/test/blobfs_test_setup.h"
#include "src/storage/lib/block_client/cpp/fake_block_device.h"
#include "zircon/syscalls.h"
namespace blobfs {
namespace {
namespace fuv = ::fuchsia_update_verify;
constexpr uint32_t kBlockSize = 512;
constexpr uint32_t kNumBlocks = 400 * kBlobfsBlockSize / kBlockSize;
class HealthCheckServiceTest : public testing::Test {
protected:
void SetUp() override {
EXPECT_EQ(ZX_OK, setup_.CreateFormatMount(kNumBlocks, kBlockSize));
svc_ = fbl::MakeRefCounted<HealthCheckService>(setup_.dispatcher(), *setup_.blobfs());
}
void InstallBlob(const BlobInfo& info) {
auto root = OpenRoot();
fbl::RefPtr<fs::Vnode> file;
ASSERT_EQ(root->Create(info.path, 0, &file), ZX_OK);
size_t out_actual = 0;
ASSERT_EQ(file->Truncate(info.size_data), ZX_OK);
ASSERT_EQ(file->Write(info.data.get(), info.size_data, 0, &out_actual), ZX_OK);
ASSERT_EQ(out_actual, info.size_data);
file->Close();
}
void CorruptBlob(const BlobInfo& info) {
ZX_ASSERT(info.size_data);
uint64_t block;
{
auto root = OpenRoot();
fbl::RefPtr<fs::Vnode> file;
ASSERT_EQ(root->Lookup(info.path, &file), ZX_OK);
auto blob = fbl::RefPtr<Blob>::Downcast(file);
block = setup_.blobfs()->GetNode(blob->Ino())->extents[0].Start() +
DataStartBlock(setup_.blobfs()->Info());
}
// Unmount.
std::unique_ptr<block_client::BlockDevice> device = setup_.Unmount();
// Read the block that contains the blob.
storage::VmoBuffer buffer;
ASSERT_EQ(buffer.Initialize(device.get(), 1, kBlobfsBlockSize, "test_buffer"), ZX_OK);
block_fifo_request_t request = {
.command = {.opcode = BLOCK_OPCODE_READ, .flags = 0},
.vmoid = buffer.vmoid(),
.length = kBlobfsBlockSize / kBlockSize,
.vmo_offset = 0,
.dev_offset = block * kBlobfsBlockSize / kBlockSize,
};
ASSERT_EQ(device->FifoTransaction(&request, 1), ZX_OK);
// Flip a byte.
uint8_t* target = static_cast<uint8_t*>(buffer.Data(0));
*target ^= 0xff;
// Write the block back.
request.command = {.opcode = BLOCK_OPCODE_WRITE, .flags = 0};
ASSERT_EQ(device->FifoTransaction(&request, 1), ZX_OK);
// Remount and try and read the blob.
EXPECT_EQ(ZX_OK, setup_.Mount(std::move(device)));
svc_ = fbl::MakeRefCounted<HealthCheckService>(setup_.dispatcher(), *setup_.blobfs());
}
fbl::RefPtr<fs::Vnode> OpenRoot() {
fbl::RefPtr<fs::Vnode> root;
EXPECT_EQ(setup_.blobfs()->OpenRootNode(&root), ZX_OK);
return root;
}
fidl::WireSyncClient<fuv::BlobfsVerifier> Client() {
auto endpoints = fidl::Endpoints<fuv::BlobfsVerifier>::Create();
EXPECT_EQ(setup_.vfs()->Serve(svc_, endpoints.server.TakeChannel(), {}), ZX_OK);
return fidl::WireSyncClient(std::move(endpoints.client));
}
BlobfsTestSetupWithThread setup_;
fbl::RefPtr<HealthCheckService> svc_; // References setup_.blobfs().
};
TEST_F(HealthCheckServiceTest, EmptyFilesystemPassesChecks) {
fidl::WireSyncClient<fuv::BlobfsVerifier> client = Client();
auto result = client->Verify(fuv::wire::VerifyOptions{});
ASSERT_TRUE(result.ok()) << result.error();
}
TEST_F(HealthCheckServiceTest, PopulatedFilesystemPassesChecks) {
// Since only open files are validated, open a bunch of valid files.
std::vector<fbl::RefPtr<fs::Vnode>> files;
auto root = OpenRoot();
for (int i = 0; i < 10; ++i) {
std::unique_ptr<BlobInfo> info = GenerateRandomBlob("", 65536);
InstallBlob(*info);
auto& file = files.emplace_back();
EXPECT_EQ(root->Lookup(info->path, &file), ZX_OK);
EXPECT_EQ(file->Open(nullptr), ZX_OK);
}
fidl::WireSyncClient<fuv::BlobfsVerifier> client = Client();
auto result = client->Verify(fuv::wire::VerifyOptions{});
ASSERT_TRUE(result.ok()) << result.error();
ASSERT_FALSE(result->is_error());
// Balance out the Open() calls above so the node can clean up properly.
for (auto& file : files) {
file->Close();
}
}
TEST_F(HealthCheckServiceTest, NullBlobPassesChecks) {
std::unique_ptr<BlobInfo> info = GenerateRandomBlob("", 0);
InstallBlob(*info);
auto root = OpenRoot();
fbl::RefPtr<fs::Vnode> file;
ASSERT_EQ(root->Lookup(info->path, &file), ZX_OK);
ASSERT_EQ(file->Open(nullptr), ZX_OK);
fidl::WireSyncClient<fuv::BlobfsVerifier> client = Client();
auto result = client->Verify(fuv::wire::VerifyOptions{});
ASSERT_TRUE(result.ok()) << result.error();
ASSERT_FALSE(result->is_error());
// Balance out the Open() call above so the node can clean up properly.
file->Close();
}
TEST_F(HealthCheckServiceTest, InvalidFileFailsChecks) {
std::unique_ptr<BlobInfo> info = GenerateRandomBlob("", 65536);
InstallBlob(*info);
CorruptBlob(*info);
auto root = OpenRoot();
fbl::RefPtr<fs::Vnode> file;
ASSERT_EQ(root->Lookup(info->path, &file), ZX_OK);
ASSERT_EQ(file->Open(nullptr), ZX_OK);
fidl::WireSyncClient<fuv::BlobfsVerifier> client = Client();
auto result = client->Verify(fuv::wire::VerifyOptions{});
ASSERT_TRUE(result.ok()) << result.error();
ASSERT_TRUE(result->is_error());
// Balance out the Open() call above so the node can clean up properly.
file->Close();
}
TEST_F(HealthCheckServiceTest, InvalidButClosedFilePassesChecks) {
std::unique_ptr<BlobInfo> info = GenerateRandomBlob("", 65536);
InstallBlob(*info);
CorruptBlob(*info);
fidl::WireSyncClient<fuv::BlobfsVerifier> client = Client();
auto result = client->Verify(fuv::wire::VerifyOptions{});
ASSERT_TRUE(result.ok()) << result.error();
ASSERT_FALSE(result->is_error());
}
} // namespace
} // namespace blobfs