| // 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 <fcntl.h> |
| #include <lib/ftl-mtd/ftl-volume-wrapper.h> |
| #include <string.h> |
| |
| #include <algorithm> |
| #include <limits> |
| #include <memory> |
| #include <vector> |
| |
| #include <zxtest/zxtest.h> |
| |
| #include "zircon/types.h" |
| |
| using namespace ftl_mtd; |
| |
| namespace { |
| |
| constexpr uint32_t kPageSize = 4 * 1024; // 4 KiB |
| constexpr uint32_t kNumPages = 128; |
| constexpr uint32_t kSize = kNumPages * kPageSize; |
| |
| class FakeVolume final : public ftl::Volume { |
| public: |
| explicit FakeVolume() {} |
| ~FakeVolume() final {} |
| |
| bool written() const { return written_; } |
| bool initialized() const { return initialized_; } |
| bool flushed() const { return flushed_; } |
| uint32_t first_page() const { return first_page_; } |
| int num_pages() const { return num_pages_; } |
| |
| void set_ftl_instance(ftl::FtlInstance* instance) { ftl_instance_ = instance; } |
| void set_read_buffer(uint8_t* buffer) { read_buffer_ = buffer; } |
| void set_write_buffer(uint8_t* buffer) { write_buffer_ = buffer; } |
| void set_fail_read(bool fail_read) { fail_read_ = fail_read; } |
| |
| // Volume interface. |
| const char* Init(std::unique_ptr<ftl::NdmDriver> driver) final { |
| initialized_ = ftl_instance_->OnVolumeAdded(kPageSize, kNumPages); |
| return nullptr; |
| } |
| const char* ReAttach() final { return nullptr; } |
| zx_status_t Read(uint32_t first_page, int num_pages, void* buffer) final { |
| if (fail_read_) { |
| return ZX_ERR_IO; |
| } |
| |
| first_page_ = first_page; |
| num_pages_ = num_pages; |
| memcpy(buffer, read_buffer_, num_pages * kPageSize); |
| return ZX_OK; |
| } |
| zx_status_t Write(uint32_t first_page, int num_pages, const void* buffer) final { |
| first_page_ = first_page; |
| num_pages_ = num_pages; |
| if (memcmp(write_buffer_, buffer, num_pages * kPageSize)) { |
| return ZX_ERR_IO_DATA_INTEGRITY; |
| } |
| |
| written_ = true; |
| return ZX_OK; |
| } |
| zx_status_t Format() final { return ZX_OK; } |
| zx_status_t FormatAndLevel() final { return ZX_OK; } |
| zx_status_t Mount() final { return ZX_OK; } |
| zx_status_t Unmount() final { return ZX_OK; } |
| zx_status_t Flush() final { |
| flushed_ = true; |
| return ZX_OK; |
| } |
| zx_status_t Trim(uint32_t first_page, uint32_t num_pages) final { return ZX_OK; } |
| zx_status_t GarbageCollect() final { return ZX_OK; } |
| zx_status_t GetStats(Stats* stats) final { return ZX_OK; } |
| zx_status_t GetCounters(Counters* counters) final { return ZX_OK; } |
| |
| private: |
| ftl::FtlInstance* ftl_instance_; |
| uint8_t* read_buffer_; |
| uint8_t* write_buffer_; |
| |
| uint32_t first_page_ = 0; |
| int num_pages_ = 0; |
| bool initialized_ = false; |
| bool written_ = false; |
| bool flushed_ = false; |
| bool fail_read_ = false; |
| }; |
| |
| class FtlVolumeWrapperTest : public zxtest::Test { |
| protected: |
| void SetUp() override { |
| auto volume = std::make_unique<FakeVolume>(); |
| volume_ = volume.get(); |
| |
| ftl_volume_wrapper_ = std::make_unique<FtlVolumeWrapper>(std::move(volume)); |
| volume_->set_ftl_instance(ftl_volume_wrapper_.get()); |
| ASSERT_OK( |
| ftl_volume_wrapper_->Init(std::unique_ptr<ftl::NdmBaseDriver>(ftl::DefaultLogger()))); |
| ASSERT_TRUE(volume_->initialized()); |
| } |
| |
| FakeVolume* volume_; |
| std::unique_ptr<FtlVolumeWrapper> ftl_volume_wrapper_; |
| }; |
| |
| TEST_F(FtlVolumeWrapperTest, SeekSucceeds) { |
| int offset = 3 * kPageSize; |
| |
| ASSERT_EQ(kSize - offset, ftl_volume_wrapper_->Seek(offset, SEEK_END)); |
| ASSERT_EQ(kSize - offset, ftl_volume_wrapper_->Tell()); |
| |
| ASSERT_EQ(offset, ftl_volume_wrapper_->Seek(offset, SEEK_SET)); |
| ASSERT_EQ(offset, ftl_volume_wrapper_->Tell()); |
| |
| ASSERT_EQ(2 * offset, ftl_volume_wrapper_->Seek(offset, SEEK_CUR)); |
| ASSERT_EQ(2 * offset, ftl_volume_wrapper_->Tell()); |
| ASSERT_EQ(0, ftl_volume_wrapper_->Seek(0, SEEK_SET)); |
| |
| // Negative values should also work |
| offset *= -1; |
| |
| ASSERT_EQ(kSize - offset, ftl_volume_wrapper_->Seek(offset, SEEK_END)); |
| ASSERT_EQ(kSize - offset, ftl_volume_wrapper_->Tell()); |
| |
| ASSERT_EQ(-2 * offset, ftl_volume_wrapper_->Seek(-2 * offset, SEEK_SET)); |
| ASSERT_EQ(-offset, ftl_volume_wrapper_->Seek(offset, SEEK_CUR)); |
| ASSERT_EQ(-offset, ftl_volume_wrapper_->Tell()); |
| } |
| |
| TEST_F(FtlVolumeWrapperTest, SeekFails) { |
| // Seek offset must be an integer multiple of page size; |
| ASSERT_EQ(ZX_ERR_INVALID_ARGS, ftl_volume_wrapper_->Seek(kPageSize / 2, SEEK_END)); |
| ASSERT_EQ(0, ftl_volume_wrapper_->Tell()); |
| |
| // Unknown whence returns error. |
| ASSERT_EQ(ZX_ERR_INVALID_ARGS, ftl_volume_wrapper_->Seek(0, -1)); |
| ASSERT_EQ(0, ftl_volume_wrapper_->Tell()); |
| |
| // An offset that would cause overflow of page index returns out of range error. |
| off_t large_offset = std::numeric_limits<off_t>::max() / kPageSize * kPageSize; |
| ASSERT_EQ(ZX_ERR_OUT_OF_RANGE, ftl_volume_wrapper_->Seek(-large_offset, SEEK_END)); |
| ASSERT_EQ(0, ftl_volume_wrapper_->Tell()); |
| |
| ASSERT_EQ(ZX_ERR_OUT_OF_RANGE, ftl_volume_wrapper_->Seek(large_offset, SEEK_CUR)); |
| ASSERT_EQ(0, ftl_volume_wrapper_->Tell()); |
| } |
| |
| TEST_F(FtlVolumeWrapperTest, WriteSucceeds) { |
| uint32_t first_page = 8; |
| uint32_t num_pages = 3; |
| uint32_t write_len = num_pages * kPageSize; |
| |
| auto buffer = std::make_unique<uint8_t[]>(write_len); |
| memset(buffer.get(), 0x8A, write_len); |
| volume_->set_write_buffer(buffer.get()); |
| |
| uint32_t byte_offset = first_page * kPageSize; |
| ASSERT_EQ(byte_offset, ftl_volume_wrapper_->Seek(byte_offset, SEEK_SET)); |
| ASSERT_EQ(write_len, ftl_volume_wrapper_->Write(buffer.get(), write_len)); |
| ASSERT_EQ(byte_offset + write_len, ftl_volume_wrapper_->Tell()); |
| ASSERT_OK(ftl_volume_wrapper_->Sync()); |
| |
| ASSERT_TRUE(volume_->written()); |
| ASSERT_TRUE(volume_->flushed()); |
| ASSERT_EQ(first_page, volume_->first_page()); |
| ASSERT_EQ(num_pages, volume_->num_pages()); |
| } |
| |
| TEST_F(FtlVolumeWrapperTest, BadWriteReturnsError) { |
| auto dropped_write_buffer = std::make_unique<uint8_t[]>(kPageSize); |
| memset(dropped_write_buffer.get(), 0xAA, kPageSize); |
| volume_->set_write_buffer(dropped_write_buffer.get()); |
| |
| auto expected_write_buffer = std::make_unique<uint8_t[]>(kPageSize); |
| memset(expected_write_buffer.get(), 0x11, kPageSize); |
| |
| ASSERT_EQ(ZX_ERR_IO_DATA_INTEGRITY, |
| ftl_volume_wrapper_->Write(expected_write_buffer.get(), kPageSize)); |
| ASSERT_EQ(0, ftl_volume_wrapper_->Tell()); |
| } |
| |
| TEST_F(FtlVolumeWrapperTest, ReadSucceeds) { |
| uint32_t first_page = 5; |
| uint32_t num_pages = 2; |
| uint32_t read_len = num_pages * kPageSize; |
| |
| auto read_buffer = std::make_unique<uint8_t[]>(read_len); |
| memset(read_buffer.get(), 0x6C, read_len); |
| volume_->set_read_buffer(read_buffer.get()); |
| |
| auto test_buffer = std::make_unique<uint8_t[]>(read_len); |
| |
| uint32_t byte_offset = first_page * kPageSize; |
| ASSERT_EQ(byte_offset, ftl_volume_wrapper_->Seek(byte_offset, SEEK_SET)); |
| ASSERT_EQ(read_len, ftl_volume_wrapper_->Read(test_buffer.get(), read_len)); |
| ASSERT_EQ(byte_offset + read_len, ftl_volume_wrapper_->Tell()); |
| |
| ASSERT_BYTES_EQ(read_buffer.get(), test_buffer.get(), read_len); |
| ASSERT_EQ(first_page, volume_->first_page()); |
| ASSERT_EQ(num_pages, volume_->num_pages()); |
| } |
| |
| TEST_F(FtlVolumeWrapperTest, BadReadReturnsError) { |
| auto read_buffer = std::make_unique<uint8_t[]>(kPageSize); |
| volume_->set_fail_read(true); |
| ASSERT_EQ(ZX_ERR_IO, ftl_volume_wrapper_->Read(read_buffer.get(), kPageSize)); |
| ASSERT_EQ(0, ftl_volume_wrapper_->Tell()); |
| } |
| |
| TEST_F(FtlVolumeWrapperTest, OutOfRangeReadWriteReturnsZero) { |
| auto buffer = std::make_unique<uint8_t[]>(kPageSize); |
| |
| ASSERT_EQ(ftl_volume_wrapper_->Size(), ftl_volume_wrapper_->Seek(0, SEEK_END)); |
| |
| ASSERT_EQ(0, ftl_volume_wrapper_->Read(buffer.get(), kPageSize)); |
| ASSERT_EQ(ftl_volume_wrapper_->Size(), ftl_volume_wrapper_->Tell()); |
| ASSERT_EQ(0, ftl_volume_wrapper_->Write(buffer.get(), kPageSize)); |
| ASSERT_EQ(ftl_volume_wrapper_->Size(), ftl_volume_wrapper_->Tell()); |
| } |
| |
| TEST_F(FtlVolumeWrapperTest, NonAlignedReadWriteReturnsInvalidArgs) { |
| std::unique_ptr<uint8_t[]> buffer = std::make_unique<uint8_t[]>(kPageSize + 1); |
| |
| ASSERT_EQ(ZX_ERR_INVALID_ARGS, ftl_volume_wrapper_->Read(buffer.get(), kPageSize + 1)); |
| ASSERT_EQ(0, ftl_volume_wrapper_->Tell()); |
| ASSERT_EQ(ZX_ERR_INVALID_ARGS, ftl_volume_wrapper_->Write(buffer.get(), kPageSize + 1)); |
| ASSERT_EQ(0, ftl_volume_wrapper_->Tell()); |
| } |
| |
| } // namespace |