blob: efcd6687b94ff8caf45d32f04633f99aba943710 [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 "src/lib/storage/vfs/cpp/journal/replay.h"
#include <lib/fit/function.h>
#include <lib/zx/vmo.h>
#include <map>
#include <gtest/gtest.h>
#include "entry_view.h"
namespace fs {
namespace {
const vmoid_t kInfoVmoid = 1;
const vmoid_t kJournalVmoid = 2;
const vmoid_t kOtherVmoid = 3;
const size_t kJournalLength = 10;
const uint32_t kBlockSize = 8192;
const uint64_t kGoldenSequenceNumber = 1337;
class MockVmoidRegistry : public storage::VmoidRegistry {
public:
~MockVmoidRegistry() { ZX_ASSERT(vmos_.empty()); }
const zx::vmo& GetVmo(vmoid_t vmoid) const { return *(vmos_.at(vmoid)); }
void SetNextVmoid(vmoid_t vmoid) { next_vmoid_ = vmoid; }
private:
zx_status_t BlockAttachVmo(const zx::vmo& vmo, storage::Vmoid* out) override {
vmos_.emplace(std::make_pair(next_vmoid_, zx::unowned_vmo(vmo.get())));
*out = storage::Vmoid(next_vmoid_++);
return ZX_OK;
}
zx_status_t BlockDetachVmo(storage::Vmoid vmoid) final {
vmos_.erase(vmoid.TakeId());
return ZX_OK;
}
vmoid_t next_vmoid_ = BLOCK_VMOID_INVALID;
std::map<vmoid_t, zx::unowned_vmo> vmos_;
};
class ParseJournalTestFixture : public testing::Test {
public:
virtual ~ParseJournalTestFixture() = default;
void SetUp() override {
auto info_block_buffer = std::make_unique<storage::VmoBuffer>();
registry_.SetNextVmoid(kInfoVmoid);
ASSERT_EQ(info_block_buffer->Initialize(&registry_, 1, kBlockSize, "info-block"), ZX_OK);
info_block_ = JournalSuperblock(std::move(info_block_buffer));
registry_.SetNextVmoid(kJournalVmoid);
ASSERT_EQ(journal_buffer_.Initialize(&registry_, kJournalLength, kBlockSize, "journal"), ZX_OK);
registry_.SetNextVmoid(kOtherVmoid);
}
JournalSuperblock* info_block() { return &info_block_; }
storage::VmoBuffer* journal_buffer() { return &journal_buffer_; }
MockVmoidRegistry* registry() { return &registry_; }
private:
MockVmoidRegistry registry_;
JournalSuperblock info_block_;
storage::VmoBuffer journal_buffer_;
};
using ParseJournalTest = ParseJournalTestFixture;
TEST_F(ParseJournalTest, EmptyJournalNoOperations) {
info_block()->Update(0, 0);
std::vector<storage::BufferedOperation> operations;
uint64_t sequence_number = 0;
uint64_t next_entry_start = 0;
EXPECT_EQ(ParseJournalEntries(info_block(), journal_buffer(), &operations, &sequence_number,
&next_entry_start),
ZX_OK);
EXPECT_EQ(operations.size(), 0ul);
EXPECT_EQ(sequence_number, 0ul);
EXPECT_EQ(0ul, next_entry_start);
}
TEST_F(ParseJournalTest, EmptyJournalNonzeroSequenceNumber) {
info_block()->Update(0, kGoldenSequenceNumber);
std::vector<storage::BufferedOperation> operations;
uint64_t sequence_number = 0;
uint64_t next_entry_start = 0;
EXPECT_EQ(ParseJournalEntries(info_block(), journal_buffer(), &operations, &sequence_number,
&next_entry_start),
ZX_OK);
EXPECT_EQ(operations.size(), 0ul);
EXPECT_EQ(kGoldenSequenceNumber, sequence_number);
EXPECT_EQ(next_entry_start, 0ul);
}
void AddOperation(uint64_t dev_offset, uint64_t length,
std::vector<storage::BufferedOperation>* operations) {
storage::BufferedOperation operation;
operation.op.type = storage::OperationType::kWrite;
operation.op.dev_offset = dev_offset;
operation.op.length = length;
operations->push_back(std::move(operation));
}
void CheckWriteOperation(const storage::BufferedOperation& operation, uint64_t vmo_offset,
uint64_t dev_offset, uint64_t length) {
EXPECT_EQ(kJournalVmoid, operation.vmoid);
EXPECT_EQ(storage::OperationType::kWrite, operation.op.type);
EXPECT_EQ(vmo_offset, operation.op.vmo_offset);
EXPECT_EQ(dev_offset, operation.op.dev_offset);
EXPECT_EQ(length, operation.op.length);
}
TEST_F(ParseJournalTest, OneEntryOneOperation) {
info_block()->Update(0, kGoldenSequenceNumber);
std::vector<storage::BufferedOperation> ops;
AddOperation(/* dev_offset= */ 10, /* length= */ 1, &ops);
const uint64_t kEntryLength = 1 + kEntryMetadataBlocks;
JournalEntryView entry_view(storage::BlockBufferView(journal_buffer(), 0, kEntryLength), ops,
kGoldenSequenceNumber);
std::vector<storage::BufferedOperation> operations;
uint64_t sequence_number = 0;
uint64_t next_entry_start = 0;
ASSERT_EQ(ParseJournalEntries(info_block(), journal_buffer(), &operations, &sequence_number,
&next_entry_start),
ZX_OK);
ASSERT_EQ(operations.size(), 1ul);
EXPECT_EQ(kGoldenSequenceNumber + 1, sequence_number);
EXPECT_EQ(kEntryLength, next_entry_start);
uint64_t vmo_offset = kJournalEntryHeaderBlocks;
ASSERT_NO_FATAL_FAILURE(CheckWriteOperation(operations[0], vmo_offset, 10, 1));
}
TEST_F(ParseJournalTest, OneEntryOneOperationFullJournal) {
info_block()->Update(0, kGoldenSequenceNumber);
std::vector<storage::BufferedOperation> ops;
const uint64_t kDevOffset = 10; // Arbitrary
const uint64_t kLength = kJournalLength - kEntryMetadataBlocks;
AddOperation(kDevOffset, kLength, &ops);
const uint64_t kEntryLength = kLength + kEntryMetadataBlocks;
static_assert(kEntryLength == kJournalLength, "Attempting to test full journal");
JournalEntryView entry_view(storage::BlockBufferView(journal_buffer(), 0, kEntryLength), ops,
kGoldenSequenceNumber);
std::vector<storage::BufferedOperation> operations;
uint64_t sequence_number = 0;
uint64_t next_entry_start = 0;
ASSERT_EQ(ParseJournalEntries(info_block(), journal_buffer(), &operations, &sequence_number,
&next_entry_start),
ZX_OK);
ASSERT_EQ(operations.size(), 1ul);
EXPECT_EQ(kGoldenSequenceNumber + 1, sequence_number);
EXPECT_EQ(next_entry_start, 0ul);
uint64_t vmo_offset = kJournalEntryHeaderBlocks;
ASSERT_NO_FATAL_FAILURE(CheckWriteOperation(operations[0], vmo_offset, kDevOffset, kLength));
}
TEST_F(ParseJournalTest, OneEntryOneOperationWrapsAroundJournal) {
// Start writing two blocks before the end of the journal.
uint64_t vmo_offset = kJournalLength - 2;
info_block()->Update(vmo_offset, kGoldenSequenceNumber);
// This operation will be split as follows:
// [ 2, 3, 4, C, _, _, _, _, _, H, 1 ]
//
// Resulting in two writeback operations:
// [ _, _, _, _, _, _, _, _, _, _, 1 ], and
// [ 2, 3, 4, _, _, _, _, _, _, _, _ ]
std::vector<storage::BufferedOperation> ops;
uint64_t dev_offset = 10;
const uint64_t kOperationLength = 4;
AddOperation(dev_offset, kOperationLength, &ops);
const uint64_t kEntryLength = kOperationLength + kEntryMetadataBlocks;
JournalEntryView entry_view(storage::BlockBufferView(journal_buffer(), vmo_offset, kEntryLength),
ops, kGoldenSequenceNumber);
std::vector<storage::BufferedOperation> operations;
uint64_t sequence_number = 0;
uint64_t next_entry_start = 0;
ASSERT_EQ(ParseJournalEntries(info_block(), journal_buffer(), &operations, &sequence_number,
&next_entry_start),
ZX_OK);
ASSERT_EQ(operations.size(), 2ul);
vmo_offset += kJournalEntryHeaderBlocks;
uint64_t length = kJournalLength - vmo_offset;
ASSERT_NO_FATAL_FAILURE(CheckWriteOperation(operations[0], vmo_offset, dev_offset, length));
dev_offset += length;
vmo_offset = 0;
length = kOperationLength - length;
ASSERT_NO_FATAL_FAILURE(CheckWriteOperation(operations[1], vmo_offset, dev_offset, length));
EXPECT_EQ(kGoldenSequenceNumber + 1, sequence_number);
ASSERT_EQ(vmo_offset + length + kJournalEntryCommitBlocks, next_entry_start);
}
TEST_F(ParseJournalTest, OneEntryManyOperations) {
info_block()->Update(0, kGoldenSequenceNumber);
std::vector<storage::BufferedOperation> ops;
AddOperation(/* dev_offset= */ 10, /* length= */ 3, &ops);
AddOperation(/* dev_offset= */ 20, /* length= */ 2, &ops);
AddOperation(/* dev_offset= */ 30, /* length= */ 1, &ops);
const uint64_t kEntryLength = 6 + kEntryMetadataBlocks;
JournalEntryView entry_view(storage::BlockBufferView(journal_buffer(), 0, kEntryLength), ops,
kGoldenSequenceNumber);
std::vector<storage::BufferedOperation> operations;
uint64_t sequence_number = 0;
uint64_t next_entry_start = 0;
EXPECT_EQ(ParseJournalEntries(info_block(), journal_buffer(), &operations, &sequence_number,
&next_entry_start),
ZX_OK);
EXPECT_EQ(operations.size(), 3ul);
EXPECT_EQ(kGoldenSequenceNumber + 1, sequence_number);
EXPECT_EQ(kEntryLength, next_entry_start);
uint64_t vmo_offset = kJournalEntryHeaderBlocks;
ASSERT_NO_FATAL_FAILURE(CheckWriteOperation(operations[0], vmo_offset, 10, 3));
vmo_offset += 3;
ASSERT_NO_FATAL_FAILURE(CheckWriteOperation(operations[1], vmo_offset, 20, 2));
vmo_offset += 2;
ASSERT_NO_FATAL_FAILURE(CheckWriteOperation(operations[2], vmo_offset, 30, 1));
}
TEST_F(ParseJournalTest, MultipleEntriesDifferentDevOffsetCausesTwoEntriesParsed) {
info_block()->Update(0, kGoldenSequenceNumber);
std::vector<storage::BufferedOperation> ops;
AddOperation(/* dev_offset= */ 10, /* length= */ 1, &ops);
const uint64_t kEntryLengthA = 1 + kEntryMetadataBlocks;
JournalEntryView entry_view_a(storage::BlockBufferView(journal_buffer(), 0, kEntryLengthA), ops,
kGoldenSequenceNumber);
ops.clear();
AddOperation(/* dev_offset= */ 20, /* length= */ 3, &ops);
const uint64_t kEntryLengthB = 3 + kEntryMetadataBlocks;
JournalEntryView entry_view_b(
storage::BlockBufferView(journal_buffer(), kEntryLengthA, kEntryLengthB), ops,
kGoldenSequenceNumber + 1);
std::vector<storage::BufferedOperation> operations;
uint64_t sequence_number = 0;
uint64_t next_entry_start = 0;
ASSERT_EQ(ParseJournalEntries(info_block(), journal_buffer(), &operations, &sequence_number,
&next_entry_start),
ZX_OK);
ASSERT_EQ(operations.size(), 2ul);
EXPECT_EQ(kGoldenSequenceNumber + 2, sequence_number);
EXPECT_EQ(kEntryLengthA + kEntryLengthB, next_entry_start);
uint64_t vmo_offset = kJournalEntryHeaderBlocks;
ASSERT_NO_FATAL_FAILURE(CheckWriteOperation(operations[0], vmo_offset, 10, 1));
vmo_offset += kEntryLengthA;
ASSERT_NO_FATAL_FAILURE(CheckWriteOperation(operations[1], vmo_offset, 20, 3));
}
TEST_F(ParseJournalTest, MultipleEntriesSameDevOffsetCausesOneEntryParsed) {
info_block()->Update(0, kGoldenSequenceNumber);
std::vector<storage::BufferedOperation> ops;
AddOperation(/* dev_offset= */ 10, /* length= */ 1, &ops);
const uint64_t kEntryLengthA = 1 + kEntryMetadataBlocks;
JournalEntryView entry_view_a(storage::BlockBufferView(journal_buffer(), 0, kEntryLengthA), ops,
kGoldenSequenceNumber);
ops.clear();
AddOperation(/* dev_offset= */ 10, /* length= */ 1, &ops);
const uint64_t kEntryLengthB = 1 + kEntryMetadataBlocks;
JournalEntryView entry_view_b(
storage::BlockBufferView(journal_buffer(), kEntryLengthA, kEntryLengthB), ops,
kGoldenSequenceNumber + 1);
std::vector<storage::BufferedOperation> operations;
uint64_t sequence_number = 0;
uint64_t next_entry_start = 0;
ASSERT_EQ(ParseJournalEntries(info_block(), journal_buffer(), &operations, &sequence_number,
&next_entry_start),
ZX_OK);
ASSERT_EQ(operations.size(), 1ul);
EXPECT_EQ(kGoldenSequenceNumber + 2, sequence_number);
EXPECT_EQ(kEntryLengthA + kEntryLengthB, next_entry_start);
uint64_t vmo_offset = kJournalEntryHeaderBlocks + kEntryLengthA;
ASSERT_NO_FATAL_FAILURE(CheckWriteOperation(operations[0], vmo_offset, 10, 1));
}
// Tests that contiguous entries with a non-increasing sequence number will be discarded. In a
// functioning journal, each subsequent entry will have exclusively incrementing sequence numbers,
// and deviation from that behavior will imply "invalid journal metadata" that should be discarded.
// This tests one of those deviations (sequence number is not incremented), and validates that the
// bad entry is ignored.
TEST_F(ParseJournalTest, MultipleEntriesWithSameSequenceNumberOnlyKeepsFirst) {
info_block()->Update(0, kGoldenSequenceNumber);
std::vector<storage::BufferedOperation> ops;
AddOperation(/* dev_offset= */ 10, /* length= */ 1, &ops);
const uint64_t kEntryLengthA = 1 + kEntryMetadataBlocks;
JournalEntryView entry_view_a(storage::BlockBufferView(journal_buffer(), 0, kEntryLengthA), ops,
kGoldenSequenceNumber);
ops.clear();
AddOperation(/* dev_offset= */ 20, /* length= */ 3, &ops);
const uint64_t kEntryLengthB = 3 + kEntryMetadataBlocks;
JournalEntryView entry_view_b(
storage::BlockBufferView(journal_buffer(), kEntryLengthA, kEntryLengthB), ops,
kGoldenSequenceNumber);
// Writing entries with the same sequence number only parses the first.
std::vector<storage::BufferedOperation> operations;
uint64_t sequence_number = 0;
uint64_t next_entry_start = 0;
ASSERT_EQ(ParseJournalEntries(info_block(), journal_buffer(), &operations, &sequence_number,
&next_entry_start),
ZX_OK);
ASSERT_EQ(operations.size(), 1ul);
ASSERT_EQ(kGoldenSequenceNumber + 1, sequence_number);
uint64_t vmo_offset = kJournalEntryHeaderBlocks;
ASSERT_NO_FATAL_FAILURE(CheckWriteOperation(operations[0], vmo_offset, 10, 1));
}
TEST_F(ParseJournalTest, EscapedEntry) {
info_block()->Update(0, kGoldenSequenceNumber);
std::vector<storage::BufferedOperation> ops;
AddOperation(/* dev_offset= */ 10, /* length= */ 1, &ops);
const uint64_t kEntryLength = 1 + kEntryMetadataBlocks;
// Create an "escaped" entry.
storage::BlockBufferView view(journal_buffer(), 1, 1);
auto ptr = static_cast<uint64_t*>(view.Data(0));
ptr[0] = kJournalEntryMagic;
ptr[1] = 0xDEADBEEF;
JournalEntryView entry_view(storage::BlockBufferView(journal_buffer(), 0, kEntryLength), ops,
kGoldenSequenceNumber);
// Verify that it was escaped.
const auto& const_entry_view = entry_view;
EXPECT_TRUE(const_entry_view.header().EscapedBlock(0));
EXPECT_EQ(ptr[0], 0ul);
EXPECT_EQ(0xDEADBEEF, ptr[1]);
std::vector<storage::BufferedOperation> operations;
uint64_t sequence_number = 0;
uint64_t next_entry_start = 0;
ASSERT_EQ(ParseJournalEntries(info_block(), journal_buffer(), &operations, &sequence_number,
&next_entry_start),
ZX_OK);
ASSERT_EQ(operations.size(), 1ul);
EXPECT_EQ(kGoldenSequenceNumber + 1, sequence_number);
EXPECT_EQ(kEntryLength, next_entry_start);
uint64_t vmo_offset = kJournalEntryHeaderBlocks;
ASSERT_NO_FATAL_FAILURE(CheckWriteOperation(operations[0], vmo_offset, 10, 1));
// Verify that the entry is un-escaped after parsing.
EXPECT_EQ(ptr[0], kJournalEntryMagic);
EXPECT_EQ(ptr[1], 0xDEADBEEFul);
}
TEST_F(ParseJournalTest, TooOldDropped) {
std::vector<storage::BufferedOperation> ops;
AddOperation(/* dev_offset= */ 10, /* length= */ 1, &ops);
const uint64_t kEntryLength = 1 + kEntryMetadataBlocks;
JournalEntryView entry_view(storage::BlockBufferView(journal_buffer(), 0, kEntryLength), ops,
kGoldenSequenceNumber);
// Move the info block past this counter, but in the same location.
info_block()->Update(0, kGoldenSequenceNumber + 1);
// Observe that the new sequence_number is parsed, but the entry is dropped.
std::vector<storage::BufferedOperation> operations;
uint64_t sequence_number = 0;
uint64_t next_entry_start = 0;
ASSERT_EQ(ParseJournalEntries(info_block(), journal_buffer(), &operations, &sequence_number,
&next_entry_start),
ZX_OK);
ASSERT_EQ(operations.size(), 0ul);
EXPECT_EQ(kGoldenSequenceNumber + 1, sequence_number);
EXPECT_EQ(next_entry_start, 0ul);
}
TEST_F(ParseJournalTest, NewerThanExpectedDropped) {
std::vector<storage::BufferedOperation> ops;
AddOperation(/* dev_offset= */ 10, /* length= */ 1, &ops);
const uint64_t kEntryLength = 1 + kEntryMetadataBlocks;
JournalEntryView entry_view(storage::BlockBufferView(journal_buffer(), 0, kEntryLength), ops,
kGoldenSequenceNumber);
// Move the info block backwards in time.
const uint64_t kUpdatedSequenceNumber = kGoldenSequenceNumber - 1;
info_block()->Update(0, kUpdatedSequenceNumber);
// Observe that the entry's sequence_number is parsed as too new, and dropped.
std::vector<storage::BufferedOperation> operations;
uint64_t sequence_number = 0;
uint64_t next_entry_start = 0;
ASSERT_EQ(ParseJournalEntries(info_block(), journal_buffer(), &operations, &sequence_number,
&next_entry_start),
ZX_OK);
EXPECT_EQ(operations.size(), 0ul);
EXPECT_EQ(kUpdatedSequenceNumber, sequence_number);
EXPECT_EQ(next_entry_start, 0ul);
}
TEST_F(ParseJournalTest, EntryMultipleTimes) {
info_block()->Update(0, kGoldenSequenceNumber);
std::vector<storage::BufferedOperation> ops;
AddOperation(/* dev_offset= */ 10, /* length= */ 1, &ops);
const uint64_t kEntryLength = 1 + kEntryMetadataBlocks;
JournalEntryView entry_view(storage::BlockBufferView(journal_buffer(), 0, kEntryLength), ops,
kGoldenSequenceNumber);
// Observe that we can replay journal entries with this setup.
std::vector<storage::BufferedOperation> operations;
uint64_t sequence_number = 0;
uint64_t next_entry_start = 0;
ASSERT_EQ(ParseJournalEntries(info_block(), journal_buffer(), &operations, &sequence_number,
&next_entry_start),
ZX_OK);
ASSERT_EQ(operations.size(), 1ul);
EXPECT_EQ(kGoldenSequenceNumber + 1, sequence_number);
EXPECT_EQ(kEntryLength, next_entry_start);
uint64_t vmo_offset = kJournalEntryHeaderBlocks;
ASSERT_NO_FATAL_FAILURE(CheckWriteOperation(operations[0], vmo_offset, 10, 1));
operations.clear();
// We can replay the same entries multiple times.
ASSERT_EQ(ParseJournalEntries(info_block(), journal_buffer(), &operations, &sequence_number,
&next_entry_start),
ZX_OK);
ASSERT_EQ(operations.size(), 1ul);
EXPECT_EQ(kGoldenSequenceNumber + 1, sequence_number);
EXPECT_EQ(kEntryLength, next_entry_start);
ASSERT_NO_FATAL_FAILURE(CheckWriteOperation(operations[0], vmo_offset, 10, 1));
}
TEST_F(ParseJournalTest, EntryModifiedHeaderDropped) {
info_block()->Update(0, kGoldenSequenceNumber);
std::vector<storage::BufferedOperation> ops;
AddOperation(/* dev_offset= */ 10, /* length= */ 1, &ops);
const uint64_t kEntryLength = 1 + kEntryMetadataBlocks;
JournalEntryView entry_view(storage::BlockBufferView(journal_buffer(), 0, kEntryLength), ops,
kGoldenSequenceNumber);
// Before we replay, flip some bits in the header.
storage::BlockBufferView buffer_view(journal_buffer(), 0, kEntryLength);
JournalHeaderView raw_block(cpp20::span<uint8_t>(reinterpret_cast<uint8_t*>(buffer_view.Data(0)),
buffer_view.BlockSize()));
raw_block.SetTargetBlock(16, ~(raw_block.TargetBlock(16)));
// As a result, there are no entries identified as replayable.
std::vector<storage::BufferedOperation> operations;
uint64_t sequence_number = 0;
uint64_t next_entry_start = 0;
ASSERT_EQ(ParseJournalEntries(info_block(), journal_buffer(), &operations, &sequence_number,
&next_entry_start),
ZX_OK);
ASSERT_EQ(operations.size(), 0ul);
EXPECT_EQ(kGoldenSequenceNumber, sequence_number);
EXPECT_EQ(next_entry_start, 0ul);
}
TEST_F(ParseJournalTest, EntryModifiedEntryDropped) {
info_block()->Update(0, kGoldenSequenceNumber);
std::vector<storage::BufferedOperation> ops;
AddOperation(/* dev_offset= */ 10, /* length= */ 1, &ops);
const uint64_t kEntryLength = 1 + kEntryMetadataBlocks;
JournalEntryView entry_view(storage::BlockBufferView(journal_buffer(), 0, kEntryLength), ops,
kGoldenSequenceNumber);
// Before we replay, flip some bits in the entry.
storage::BlockBufferView buffer_view(journal_buffer(), 0, kEntryLength);
auto raw_bytes = static_cast<uint8_t*>(buffer_view.Data(1));
raw_bytes[0] = static_cast<uint8_t>(~raw_bytes[0]);
// As a result, there are no entries identified as replayable.
std::vector<storage::BufferedOperation> operations;
uint64_t sequence_number = 0;
uint64_t next_entry_start = 0;
ASSERT_EQ(ParseJournalEntries(info_block(), journal_buffer(), &operations, &sequence_number,
&next_entry_start),
ZX_OK);
ASSERT_EQ(operations.size(), 0ul);
EXPECT_EQ(kGoldenSequenceNumber, sequence_number);
EXPECT_EQ(next_entry_start, 0ul);
}
TEST_F(ParseJournalTest, EntryModifiedCommitDropped) {
info_block()->Update(0, kGoldenSequenceNumber);
std::vector<storage::BufferedOperation> ops;
AddOperation(/* dev_offset= */ 10, /* length= */ 1, &ops);
const uint64_t kEntryLength = 1 + kEntryMetadataBlocks;
JournalEntryView entry_view(storage::BlockBufferView(journal_buffer(), 0, kEntryLength), ops,
kGoldenSequenceNumber);
// Before we replay, flip some bits in the commit.
storage::BlockBufferView buffer_view(journal_buffer(), 0, kEntryLength);
auto raw_commit = static_cast<JournalCommitBlock*>(buffer_view.Data(2));
raw_commit->prefix.sequence_number++;
// As a result, there are no entries identified as replayable.
std::vector<storage::BufferedOperation> operations;
uint64_t sequence_number = 0;
uint64_t next_entry_start = 0;
ASSERT_EQ(ParseJournalEntries(info_block(), journal_buffer(), &operations, &sequence_number,
&next_entry_start),
ZX_OK);
ASSERT_EQ(operations.size(), 0ul);
EXPECT_EQ(kGoldenSequenceNumber, sequence_number);
EXPECT_EQ(next_entry_start, 0ul);
}
TEST_F(ParseJournalTest, EntryModifiedAfterCommitStillKept) {
info_block()->Update(0, kGoldenSequenceNumber);
std::vector<storage::BufferedOperation> ops;
AddOperation(/* dev_offset= */ 10, /* length= */ 1, &ops);
const uint64_t kEntryLength = 1 + kEntryMetadataBlocks;
JournalEntryView entry_view(storage::BlockBufferView(journal_buffer(), 0, kEntryLength), ops,
kGoldenSequenceNumber);
// Before we replay, flip some bits in the commit block.
storage::BlockBufferView buffer_view(journal_buffer(), 0, kEntryLength);
auto raw_bytes = static_cast<uint8_t*>(buffer_view.Data(2));
// Intentionally flip bits AFTER the commit structure itself, but still in the
// same block.
size_t index = sizeof(JournalCommitBlock) + 1;
raw_bytes[index] = static_cast<uint8_t>(~raw_bytes[index]);
// The current implementation of journaling is not checksumming the commit block.
std::vector<storage::BufferedOperation> operations;
uint64_t sequence_number = 0;
uint64_t next_entry_start = 0;
ASSERT_EQ(ParseJournalEntries(info_block(), journal_buffer(), &operations, &sequence_number,
&next_entry_start),
ZX_OK);
ASSERT_EQ(operations.size(), 1ul);
EXPECT_EQ(kGoldenSequenceNumber + 1, sequence_number);
uint64_t vmo_offset = kJournalEntryHeaderBlocks;
ASSERT_NO_FATAL_FAILURE(CheckWriteOperation(operations[0], vmo_offset, 10, 1));
}
TEST_F(ParseJournalTest, DoesntDetectCorruptJournalIfOldEntryHasBadChecksumAndBadLength) {
info_block()->Update(0, kGoldenSequenceNumber);
const uint64_t kEntryLength = 1 + kEntryMetadataBlocks;
// Place two entries into the journal.
{
std::vector<storage::BufferedOperation> ops;
AddOperation(/* dev_offset= */ 10, /* length= */ 1, &ops);
JournalEntryView entry_view(storage::BlockBufferView(journal_buffer(), 0, kEntryLength), ops,
kGoldenSequenceNumber);
}
{
std::vector<storage::BufferedOperation> ops;
AddOperation(/* dev_offset= */ 20, /* length= */ 1, &ops);
JournalEntryView entry_view(
storage::BlockBufferView(journal_buffer(), kEntryLength, kEntryLength), ops,
kGoldenSequenceNumber + 1);
}
// Before we replay, flip some bits in the old entry's header.
//
// This time, flip the number of blocks to be replayed, so the subsequent entry cannot be located.
storage::BlockBufferView buffer_view(journal_buffer(), 0, kEntryLength);
auto raw_block = static_cast<JournalHeaderBlock*>(buffer_view.Data(0));
raw_block->payload_blocks = ~(raw_block->payload_blocks);
std::vector<storage::BufferedOperation> operations;
uint64_t sequence_number = 0;
uint64_t next_entry_start = 0;
EXPECT_EQ(ParseJournalEntries(info_block(), journal_buffer(), &operations, &sequence_number,
&next_entry_start),
ZX_OK);
ASSERT_EQ(operations.size(), 0ul);
EXPECT_EQ(kGoldenSequenceNumber, sequence_number);
}
class MockTransactionHandler final : public fs::TransactionHandler {
public:
using TransactionCallback =
fit::function<zx_status_t(const std::vector<storage::BufferedOperation>& requests)>;
MockTransactionHandler() = default;
MockTransactionHandler(TransactionCallback* callbacks, size_t transactions_expected)
: callbacks_(callbacks), transactions_expected_(transactions_expected) {}
~MockTransactionHandler() { EXPECT_EQ(transactions_expected_, transactions_seen_); }
uint64_t BlockNumberToDevice(uint64_t block_num) const final { return block_num; }
zx_status_t RunRequests(const std::vector<storage::BufferedOperation>& requests) override {
EXPECT_LT(transactions_seen_, transactions_expected_);
if (transactions_seen_ == transactions_expected_) {
return ZX_ERR_BAD_STATE;
}
return callbacks_[transactions_seen_++](requests);
}
zx_status_t Flush() override {
EXPECT_LT(transactions_seen_, transactions_expected_);
if (transactions_seen_ == transactions_expected_) {
return ZX_ERR_BAD_STATE;
}
return callbacks_[transactions_seen_++](GetFlushRequests());
}
static bool IsFlush(const std::vector<storage::BufferedOperation>& requests) {
return &requests == &GetFlushRequests();
}
private:
static const std::vector<storage::BufferedOperation>& GetFlushRequests() {
static auto requests = new std::vector<storage::BufferedOperation>();
return *requests;
}
TransactionCallback* callbacks_ = nullptr;
size_t transactions_expected_ = 0;
size_t transactions_seen_ = 0;
};
zx_status_t FlushCallback(const std::vector<storage::BufferedOperation>& requests) {
EXPECT_TRUE(MockTransactionHandler::IsFlush(requests));
return ZX_OK;
}
class ReplayJournalTest : public ParseJournalTestFixture {
public:
static constexpr uint64_t kJournalAreaStart = 5;
static constexpr uint64_t kJournalAreaLength = kJournalLength + kJournalMetadataBlocks;
static constexpr uint64_t kJournalEntryStart = kJournalAreaStart + kJournalMetadataBlocks;
static constexpr uint64_t kJournalEntryLength = kJournalLength;
void ValidInfoReadRequest(const storage::BufferedOperation& request) const {
EXPECT_EQ(storage::OperationType::kRead, request.op.type);
EXPECT_EQ(request.op.vmo_offset, 0ul);
EXPECT_EQ(kJournalAreaStart, request.op.dev_offset);
EXPECT_EQ(kJournalMetadataBlocks, request.op.length);
}
void ValidInfoWriteRequest(const storage::BufferedOperation& request) const {
EXPECT_EQ(storage::OperationType::kWrite, request.op.type);
EXPECT_EQ(request.op.vmo_offset, 0ul);
EXPECT_EQ(kJournalAreaStart, request.op.dev_offset);
EXPECT_EQ(kJournalMetadataBlocks, request.op.length);
}
void ValidEntriesReadRequest(const storage::BufferedOperation& request) const {
EXPECT_EQ(storage::OperationType::kRead, request.op.type);
EXPECT_EQ(request.op.vmo_offset, 0ul);
EXPECT_EQ(kJournalEntryStart, request.op.dev_offset);
EXPECT_EQ(kJournalEntryLength, request.op.length);
}
// Take the contents of the pre-registered journal superblock and transfer it into the requested
// vmoid.
void TransferInfoTo(vmoid_t vmoid) {
char buf[kBlockSize * kJournalMetadataBlocks];
EXPECT_EQ(registry()->GetVmo(kInfoVmoid).read(buf, 0, sizeof(buf)), ZX_OK);
EXPECT_EQ(registry()->GetVmo(vmoid).write(buf, 0, sizeof(buf)), ZX_OK);
}
// Take the contents of the pre-registered journal buffer and transfer it into the requested
// vmoid.
void TransferEntryTo(vmoid_t vmoid, size_t offset, uint64_t length) {
char entry_buf[kBlockSize * length];
EXPECT_EQ(
registry()->GetVmo(kJournalVmoid).read(entry_buf, offset * kBlockSize, sizeof(entry_buf)),
ZX_OK);
EXPECT_EQ(registry()->GetVmo(vmoid).write(entry_buf, offset * kBlockSize, sizeof(entry_buf)),
ZX_OK);
}
};
TEST_F(ReplayJournalTest, BadJournalSuperblockFails) {
MockTransactionHandler::TransactionCallback callbacks[] = {
[&](const std::vector<storage::BufferedOperation>& requests) {
// Return OK, but don't provide any values. This should fail during replay.
EXPECT_GE(requests.size(), 1ul);
EXPECT_EQ(storage::OperationType::kRead, requests[0].op.type);
return ZX_OK;
},
};
MockTransactionHandler transaction_handler(callbacks, std::size(callbacks));
JournalSuperblock superblock;
ASSERT_EQ(ZX_ERR_IO_DATA_INTEGRITY,
ReplayJournal(&transaction_handler, registry(), kJournalAreaStart, kJournalAreaLength,
kBlockSize)
.error_value());
}
TEST_F(ReplayJournalTest, CannotReadJournalFails) {
MockTransactionHandler::TransactionCallback callbacks[] = {
[&](const std::vector<storage::BufferedOperation>& requests) {
EXPECT_GE(requests.size(), 1ul);
EXPECT_EQ(storage::OperationType::kRead, requests[0].op.type);
return ZX_ERR_IO;
},
};
MockTransactionHandler transaction_handler(callbacks, std::size(callbacks));
JournalSuperblock superblock;
ASSERT_EQ(ZX_ERR_IO, ReplayJournal(&transaction_handler, registry(), kJournalAreaStart,
kJournalAreaLength, kBlockSize)
.error_value());
}
TEST_F(ReplayJournalTest, BadJournalStartExpectError) {
// Fill the info block with a bad start offset.
info_block()->Update(kJournalLength, 3);
MockTransactionHandler::TransactionCallback callbacks[] = {
[&](const std::vector<storage::BufferedOperation>& requests) {
// First request: Reading from the journal.
EXPECT_EQ(requests.size(), 2ul);
ValidInfoReadRequest(requests[0]);
ValidEntriesReadRequest(requests[1]);
// Transfer pre-seeded info block, but nothing else.
TransferInfoTo(requests[0].vmoid);
return ZX_OK;
},
};
MockTransactionHandler transaction_handler(callbacks, std::size(callbacks));
auto superblock_or = ReplayJournal(&transaction_handler, registry(), kJournalAreaStart,
kJournalAreaLength, kBlockSize);
ASSERT_EQ(superblock_or.status_value(), ZX_ERR_IO_DATA_INTEGRITY);
}
TEST_F(ReplayJournalTest, EmptyJournalDoesNothing) {
// Fill the preregistered info block with valid data.
constexpr uint64_t kStart = 1;
constexpr uint64_t kSequenceNumber = 3;
info_block()->Update(kStart, kSequenceNumber);
MockTransactionHandler::TransactionCallback callbacks[] = {
[&](const std::vector<storage::BufferedOperation>& requests) {
// First request: Reading from the journal.
EXPECT_EQ(requests.size(), 2ul);
ValidInfoReadRequest(requests[0]);
ValidEntriesReadRequest(requests[1]);
// Transfer pre-seeded info block, but nothing else.
TransferInfoTo(requests[0].vmoid);
return ZX_OK;
},
};
MockTransactionHandler transaction_handler(callbacks, std::size(callbacks));
auto superblock_or = ReplayJournal(&transaction_handler, registry(), kJournalAreaStart,
kJournalAreaLength, kBlockSize);
ASSERT_TRUE(superblock_or.is_ok());
auto superblock = std::move(superblock_or.value());
EXPECT_EQ(kStart, superblock.start());
EXPECT_EQ(kSequenceNumber, superblock.sequence_number());
}
TEST_F(ReplayJournalTest, OneEntry) {
// Fill the pre-registered info block with valid data.
constexpr uint64_t kStart = 0;
constexpr uint64_t kSequenceNumber = 3;
info_block()->Update(kStart, kSequenceNumber);
// Fill the pre-registered journal buffer with one entry.
std::vector<storage::BufferedOperation> operations = {
{
.vmoid = 0,
.op =
{
.type = storage::OperationType::kWrite,
.vmo_offset = 0,
.dev_offset = 1234,
.length = 1,
},
},
};
uint64_t entry_size = operations[0].op.length + kEntryMetadataBlocks;
JournalEntryView entry_view(storage::BlockBufferView(journal_buffer(), kStart, entry_size),
operations, kSequenceNumber);
MockTransactionHandler::TransactionCallback callbacks[] = {
[&](const std::vector<storage::BufferedOperation>& requests) {
// First request: Reading from the journal.
// Transfer the pre-seeded info block.
EXPECT_EQ(requests.size(), 2ul);
ValidInfoReadRequest(requests[0]);
ValidEntriesReadRequest(requests[1]);
// Transfer the pre-seeded journal entry.
TransferInfoTo(requests[0].vmoid);
TransferEntryTo(requests[1].vmoid, kStart, entry_size);
return ZX_OK;
},
[&](const std::vector<storage::BufferedOperation>& requests) {
// Observe that the replay code replays the provided operation.
EXPECT_EQ(requests.size(), 1ul);
EXPECT_EQ(storage::OperationType::kWrite, requests[0].op.type);
EXPECT_EQ(kJournalEntryHeaderBlocks, requests[0].op.vmo_offset);
EXPECT_EQ(operations[0].op.dev_offset, requests[0].op.dev_offset);
EXPECT_EQ(operations[0].op.length, requests[0].op.length);
return ZX_OK;
},
FlushCallback,
[&](const std::vector<storage::BufferedOperation>& requests) {
// Observe that the replay code updates the journal superblock.
EXPECT_EQ(requests.size(), 1ul);
ValidInfoWriteRequest(requests[0]);
return ZX_OK;
}};
MockTransactionHandler transaction_handler(callbacks, std::size(callbacks));
auto superblock_or = ReplayJournal(&transaction_handler, registry(), kJournalAreaStart,
kJournalAreaLength, kBlockSize);
ASSERT_TRUE(superblock_or.is_ok());
auto superblock = std::move(superblock_or.value());
EXPECT_EQ(kStart + entry_size, superblock.start());
// The sequence_number should have advanced to avoid replaying the old entry.
EXPECT_EQ(kSequenceNumber + 1, superblock.sequence_number());
}
TEST_F(ReplayJournalTest, BadSuperblockIsRestored) {
// Fill the pre-registered info block with valid data.
constexpr uint64_t kStart = 0;
constexpr uint64_t kSequenceNumber = 3;
info_block()->Update(kStart, kSequenceNumber);
// Fill the pre-registered journal buffer with one entry.
std::vector<storage::BufferedOperation> operations = {
{
.vmoid = 0,
.op =
{
.type = storage::OperationType::kWrite,
.vmo_offset = 0,
.dev_offset = 1234,
.length = 1,
},
},
};
uint64_t entry_size = operations[0].op.length + kEntryMetadataBlocks;
JournalEntryView entry_view(storage::BlockBufferView(journal_buffer(), kStart, entry_size),
operations, kSequenceNumber);
MockTransactionHandler::TransactionCallback callbacks[] = {
[&](const std::vector<storage::BufferedOperation>& requests) {
// First request: Reading from the journal.
// Transfer the pre-seeded info block.
EXPECT_EQ(requests.size(), 2ul);
ValidInfoReadRequest(requests[0]);
ValidEntriesReadRequest(requests[1]);
// Transfer the pre-seeded journal entry.
TransferEntryTo(requests[1].vmoid, kStart, entry_size);
return ZX_OK;
},
[&](const std::vector<storage::BufferedOperation>& requests) {
// Observe that the replay code updates the journal superblock.
EXPECT_EQ(requests.size(), 1ul);
ValidInfoWriteRequest(requests[0]);
return ZX_OK;
}};
MockTransactionHandler transaction_handler(callbacks, std::size(callbacks));
auto superblock_or = ReplayJournal(&transaction_handler, registry(), kJournalAreaStart,
kJournalAreaLength, kBlockSize);
ASSERT_TRUE(superblock_or.is_ok());
auto superblock = std::move(superblock_or.value());
EXPECT_EQ(superblock.start(), 0ul);
// The sequence_number should have advanced.
EXPECT_EQ(superblock.sequence_number(), kSequenceNumber + 1);
}
TEST_F(ReplayJournalTest, CannotWriteParsedEntriesFails) {
// Fill the pre-registered info block with valid data.
constexpr uint64_t kStart = 0;
constexpr uint64_t kSequenceNumber = 3;
info_block()->Update(kStart, kSequenceNumber);
// Fill the pre-registered journal buffer with one entry.
std::vector<storage::BufferedOperation> operations = {
{
.vmoid = 0,
.op =
{
.type = storage::OperationType::kWrite,
.vmo_offset = 0,
.dev_offset = 1234,
.length = 1,
},
},
};
uint64_t entry_size = operations[0].op.length + kEntryMetadataBlocks;
JournalEntryView entry_view(storage::BlockBufferView(journal_buffer(), kStart, entry_size),
operations, kSequenceNumber);
MockTransactionHandler::TransactionCallback callbacks[] = {
[&](const std::vector<storage::BufferedOperation>& requests) {
// First request: Reading from the journal.
// Transfer the pre-seeded info block.
EXPECT_EQ(requests.size(), 2ul);
ValidInfoReadRequest(requests[0]);
ValidEntriesReadRequest(requests[1]);
// Transfer the pre-seeded journal entry.
TransferInfoTo(requests[0].vmoid);
TransferEntryTo(requests[1].vmoid, kStart, entry_size);
return ZX_OK;
},
[&](const std::vector<storage::BufferedOperation>& requests) {
// Observe that the replay code replays the provided operation, but return
// an error instead.
EXPECT_EQ(requests.size(), 1ul);
EXPECT_EQ(storage::OperationType::kWrite, requests[0].op.type);
return ZX_ERR_IO;
}};
MockTransactionHandler transaction_handler(callbacks, std::size(callbacks));
JournalSuperblock superblock;
ASSERT_EQ(ZX_ERR_IO, ReplayJournal(&transaction_handler, registry(), kJournalAreaStart,
kJournalAreaLength, kBlockSize)
.error_value());
}
TEST_F(ReplayJournalTest, BadJournalLength) {
MockTransactionHandler transaction_handler({}, 0);
EXPECT_EQ(ReplayJournal(&transaction_handler, registry(), kJournalAreaStart, 0, kBlockSize)
.error_value(),
ZX_ERR_INVALID_ARGS);
}
} // namespace
} // namespace fs