| // Copyright 2018 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 <blobfs/journal.h> |
| #include <zxtest/zxtest.h> |
| |
| #include "utils.h" |
| |
| namespace blobfs { |
| namespace { |
| |
| // Mock journal implementation which can be used to test JournalEntry / JournalProcessor |
| // functionality. |
| class FakeJournal : public JournalBase { |
| public: |
| FakeJournal() : readonly_(false), capacity_(0) {} |
| |
| ~FakeJournal() { |
| // On destruction, clean up work_queue_ entries. |
| while (!work_queue_.is_empty()) { |
| work_queue_.front().MarkCompleted(ZX_OK); |
| work_queue_.pop(); |
| } |
| } |
| |
| void ProcessEntryResult(zx_status_t result, JournalEntry* entry) final { |
| entry->SetStatusFromResult(result); |
| if (result != ZX_OK) { |
| readonly_ = true; |
| } |
| } |
| |
| fbl::unique_ptr<WritebackWork> CreateDefaultWork() { |
| return CreateWork(); |
| } |
| |
| fbl::unique_ptr<WritebackWork> CreateBufferedWork(size_t block_count) { |
| fbl::unique_ptr<WritebackWork> work = CreateWork(); |
| |
| zx::vmo vmo; |
| ZX_ASSERT(zx::vmo::create(PAGE_SIZE, 0, &vmo) == ZX_OK); |
| work->Transaction().Enqueue(vmo, 0, 0, block_count); |
| work->Transaction().SetBuffer(2); |
| return work; |
| } |
| |
| fbl::unique_ptr<WritebackWork> DequeueWork() { |
| return work_queue_.pop(); |
| } |
| |
| private: |
| using WorkQueue = fs::Queue<fbl::unique_ptr<WritebackWork>>; |
| |
| size_t GetCapacity() const final { |
| return capacity_; |
| } |
| |
| bool IsReadOnly() const final { |
| return readonly_; |
| } |
| |
| fbl::unique_ptr<WritebackWork> CreateWork() final { |
| return std::make_unique<WritebackWork>(nullptr); |
| } |
| |
| // The following functions are no-ops, and only exist so they can be called by the |
| // JournalProcessor. |
| void PrepareBuffer(JournalEntry* entry) final {} |
| void PrepareDelete(JournalEntry* entry, WritebackWork* work) final {} |
| |
| // Stores the WritebackWork in work_queue_; |
| zx_status_t EnqueueEntryWork(fbl::unique_ptr<WritebackWork> work) final { |
| work_queue_.push(std::move(work)); |
| return ZX_OK; |
| } |
| |
| bool readonly_; |
| size_t capacity_; |
| |
| // Enqueued entry works are stored here. |
| WorkQueue work_queue_; |
| }; |
| |
| TEST(JournalTest, JournalEntryLifetime) { |
| // Create a dummy journal and journal processor. |
| FakeJournal journal; |
| JournalProcessor processor(&journal); |
| |
| // Create and process a 'work' entry. |
| fbl::unique_ptr<JournalEntry> entry( |
| new JournalEntry(&journal, EntryStatus::kInit, 0, 0, |
| journal.CreateBufferedWork(1))); |
| fbl::unique_ptr<WritebackWork> first_work = journal.CreateDefaultWork(); |
| first_work->SetSyncCallback(entry->CreateSyncCallback()); |
| processor.ProcessWorkEntry(std::move(entry)); |
| |
| // Create and process another 'work' entry. |
| entry.reset(new JournalEntry(&journal, EntryStatus::kInit, 0, 0, |
| journal.CreateBufferedWork(1))); |
| fbl::unique_ptr<WritebackWork> second_work = journal.CreateDefaultWork(); |
| second_work->SetSyncCallback(entry->CreateSyncCallback()); |
| processor.ProcessWorkEntry(std::move(entry)); |
| |
| // Enqueue the processor's work (this is a no-op). |
| processor.EnqueueWork(); |
| |
| // Simulate an error in the writeback thread by calling the first entry's callback with an |
| // error status. |
| first_work->MarkCompleted(ZX_ERR_BAD_STATE); |
| |
| // Process the wait queue. |
| processor.ProcessWaitQueue(); |
| |
| // Now, attempt to call the second entry's callback with the error. If we are incorrectly |
| // disposing of entries before their callbacks have been invoked, this should trigger a |
| // "use-after-free" asan error, since the JournalEntry referenced by second_work will have |
| // already been deleted (see ZX-2940). |
| second_work->MarkCompleted(ZX_ERR_BAD_STATE); |
| |
| // Additionally, we should check that the processor queues are not empty - i.e., there is still |
| // one entry waiting to be processed. |
| ASSERT_FALSE(processor.IsEmpty()); |
| |
| // Process the rest of the queues. |
| processor.ProcessWaitQueue(); |
| processor.ProcessDeleteQueue(); |
| processor.ProcessSyncQueue(); |
| } |
| |
| TEST(JournalTest, JournalProcessorResetsWork) { |
| // Create a dummy journal and journal processor. |
| FakeJournal journal; |
| JournalProcessor processor(&journal); |
| |
| // Create and process a 'work' entry. |
| fbl::unique_ptr<JournalEntry> entry( |
| new JournalEntry(&journal, EntryStatus::kInit, 0, 1, journal.CreateBufferedWork(1))); |
| fbl::unique_ptr<WritebackWork> first_work = journal.CreateDefaultWork(); |
| first_work->SetSyncCallback(entry->CreateSyncCallback()); |
| processor.ProcessWorkEntry(std::move(entry)); |
| |
| // Create and process another 'work' entry. |
| entry.reset(new JournalEntry(&journal, EntryStatus::kInit, 2, 3, |
| journal.CreateBufferedWork(1))); |
| fbl::unique_ptr<WritebackWork> second_work = journal.CreateDefaultWork(); |
| second_work->SetSyncCallback(entry->CreateSyncCallback()); |
| processor.ProcessWorkEntry(std::move(entry)); |
| |
| // Enqueue and process the processor's work. |
| processor.EnqueueWork(); |
| journal.DequeueWork()->MarkCompleted(ZX_OK); |
| |
| // Call the entries' callbacks so they are moved to the next queue. |
| first_work->MarkCompleted(ZX_OK); |
| second_work->MarkCompleted(ZX_OK); |
| |
| // Process the wait queue. |
| processor.ProcessWaitQueue(); |
| |
| // Grab the works that were enqueued from the two entries in the wait queue (which have now |
| // been moved to the delete queue). |
| first_work = journal.DequeueWork(); |
| second_work = journal.DequeueWork(); |
| |
| ASSERT_NE(first_work.get(), nullptr); |
| ASSERT_NE(second_work.get(), nullptr); |
| |
| // Simulate an error in the writeback thread by calling the second entry's callback with an |
| // error status. |
| first_work->MarkCompleted(ZX_OK); |
| second_work->MarkCompleted(ZX_ERR_BAD_STATE); |
| |
| processor.ProcessDeleteQueue(); |
| |
| ASSERT_TRUE(processor.HasError()); |
| // ASSERT_GT(processor.GetBlocksProcessed(), 0); |
| |
| // Since we encountered an error and blocks have been processed, we must reset the work |
| // generated by the processor. Previously, since ResetWork() would invoke the WritebackWork |
| // callback but would not delete the WritebackWork, this would trigger an assertion (work_ == |
| // nullptr) when switching to the kSync context. |
| processor.ResetWork(); |
| |
| processor.ProcessSyncQueue(); |
| } |
| |
| TEST(JournalTest, DestroyJournalWithoutTeardown) { |
| MockTransactionManager transaction_manager; |
| |
| // Journal creation requires a valid superblock, so make sure Load will acquire one from "disk". |
| transaction_manager.SetTransactionCallback([](const block_fifo_request_t& request, |
| const zx::vmo& vmo){ |
| if (request.dev_offset == 0 && request.opcode == BLOCKIO_READ) { |
| JournalInfo journal_info; |
| memset(&journal_info, 0, sizeof(JournalInfo)); |
| journal_info.magic = kJournalMagic; |
| return vmo.write(&journal_info, request.vmo_offset * kBlobfsBlockSize, |
| sizeof(JournalInfo)); |
| } |
| return ZX_OK; |
| }); |
| |
| fbl::unique_ptr<Journal> journal_; |
| ASSERT_EQ(ZX_OK, Journal::Create(&transaction_manager, 16, 0, &journal_)); |
| ASSERT_EQ(ZX_OK, journal_->Replay()); |
| ASSERT_EQ(ZX_OK, journal_->InitWriteback()); |
| journal_.reset(); |
| } |
| |
| } // namespace |
| } // namespace blobfs |