| // 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 <fuchsia/hardware/block/c/banjo.h> |
| #include <fuchsia/hardware/block/cpp/banjo.h> |
| #include <fuchsia/hardware/block/partition/cpp/banjo.h> |
| #include <inttypes.h> |
| #include <lib/ddk/debug.h> |
| #include <lib/ddk/device.h> |
| #include <lib/ddk/driver.h> |
| #include <lib/fake_ddk/fake_ddk.h> |
| #include <lib/zircon-internal/thread_annotations.h> |
| #include <lib/zx/vmo.h> |
| #include <zircon/errors.h> |
| #include <zircon/hw/gpt.h> |
| |
| #include <ddktl/device.h> |
| #include <fbl/alloc_checker.h> |
| #include <fbl/string.h> |
| #include <fbl/vector.h> |
| #include <gpt/c/gpt.h> |
| #include <zxtest/zxtest.h> |
| |
| #include "gpt.h" |
| #include "gpt_test_data.h" |
| |
| namespace gpt { |
| namespace { |
| |
| // To make sure that we correctly convert UTF-16, to UTF-8, the second partition has a suffix with |
| // codepoint 0x10000, which in UTF-16 requires a surrogate pair. |
| constexpr std::string_view kPartition1Name = "Linux filesystem\xf0\x90\x80\x80"; |
| |
| class FakeBlockDevice : public ddk::BlockProtocol<FakeBlockDevice> { |
| public: |
| FakeBlockDevice() : proto_({&block_protocol_ops_, this}) { |
| info_.block_count = kBlockCnt; |
| info_.block_size = kBlockSz; |
| info_.max_transfer_size = BLOCK_MAX_TRANSFER_UNBOUNDED; |
| } |
| |
| block_protocol_t* proto() { return &proto_; } |
| |
| void SetInfo(const block_info_t* info) { info_ = *info; } |
| |
| void BlockQuery(block_info_t* info_out, size_t* block_op_size_out) { |
| *info_out = info_; |
| *block_op_size_out = sizeof(block_op_t); |
| } |
| |
| void BlockQueue(block_op_t* operation, block_queue_callback completion_cb, void* cookie); |
| |
| private: |
| zx_status_t BlockQueueOp(block_op_t* op); |
| |
| block_protocol_t proto_{}; |
| block_info_t info_{}; |
| }; |
| |
| void FakeBlockDevice::BlockQueue(block_op_t* operation, block_queue_callback completion_cb, |
| void* cookie) { |
| zx_status_t status = BlockQueueOp(operation); |
| completion_cb(cookie, status, operation); |
| } |
| |
| zx_status_t FakeBlockDevice::BlockQueueOp(block_op_t* op) { |
| const uint32_t command = op->command & BLOCK_OP_MASK; |
| const uint32_t bsize = info_.block_size; |
| if (command == BLOCK_OP_READ || command == BLOCK_OP_WRITE) { |
| if ((op->rw.offset_dev + op->rw.length) > (bsize * info_.block_count)) { |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| if (command == BLOCK_OP_WRITE) { |
| return ZX_OK; |
| } |
| } else if (command == BLOCK_OP_TRIM) { |
| if ((op->trim.offset_dev + op->trim.length) > (bsize * info_.block_count)) { |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| return ZX_OK; |
| } else if (command == BLOCK_OP_FLUSH) { |
| return ZX_OK; |
| } else { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| size_t part_size = sizeof(test_partition_table); |
| size_t read_off = op->rw.offset_dev * bsize; |
| size_t read_len = op->rw.length * bsize; |
| |
| if (read_len == 0) { |
| return ZX_OK; |
| } |
| |
| uint64_t vmo_addr = op->rw.offset_vmo * bsize; |
| |
| // Read initial part from header if in range. |
| if (read_off < part_size) { |
| size_t part_read_len = part_size - read_off; |
| if (part_read_len > read_len) { |
| part_read_len = read_len; |
| } |
| zx_vmo_write(op->rw.vmo, test_partition_table + read_off, vmo_addr, part_read_len); |
| |
| read_len -= part_read_len; |
| read_off += part_read_len; |
| vmo_addr += part_read_len; |
| |
| if (read_len == 0) { |
| return ZX_OK; |
| } |
| } |
| |
| std::unique_ptr<uint8_t[]> zbuf(new uint8_t[bsize]); |
| memset(zbuf.get(), 0, bsize); |
| // Zero-fill remaining. |
| for (; read_len > 0; read_len -= bsize) { |
| zx_vmo_write(op->rw.vmo, zbuf.get(), vmo_addr, bsize); |
| vmo_addr += bsize; |
| } |
| return ZX_OK; |
| } |
| |
| class GptDeviceTest : public zxtest::Test { |
| public: |
| GptDeviceTest() = default; |
| |
| DISALLOW_COPY_ASSIGN_AND_MOVE(GptDeviceTest); |
| |
| void SetInfo(const block_info_t* info) { fake_block_device_.SetInfo(info); } |
| |
| void Init() { ddk_.SetProtocol(ZX_PROTOCOL_BLOCK, fake_block_device_.proto()); } |
| |
| fake_ddk::Bind ddk_; |
| |
| protected: |
| struct BlockOpResult { |
| sync_completion_t completion; |
| block_op_t op; |
| zx_status_t status; |
| }; |
| |
| static void BlockOpCompleter(void* cookie, zx_status_t status, block_op_t* bop) { |
| auto* result = static_cast<BlockOpResult*>(cookie); |
| result->status = status; |
| result->op = *bop; |
| sync_completion_signal(&result->completion); |
| } |
| |
| private: |
| FakeBlockDevice fake_block_device_; |
| }; |
| |
| TEST_F(GptDeviceTest, DeviceTooSmall) { |
| Init(); |
| |
| const block_info_t info = {20, 512, BLOCK_MAX_TRANSFER_UNBOUNDED, 0, 0}; |
| SetInfo(&info); |
| |
| TableRef tab; |
| ASSERT_OK(PartitionTable::Create(fake_ddk::kFakeParent, &tab)); |
| ASSERT_EQ(ZX_ERR_NO_SPACE, tab->Bind()); |
| } |
| |
| TEST_F(GptDeviceTest, DdkLifecycle) { |
| Init(); |
| fbl::Vector<std::unique_ptr<PartitionDevice>> devices; |
| |
| TableRef tab; |
| ASSERT_OK(PartitionTable::Create(fake_ddk::kFakeParent, &tab, &devices)); |
| ASSERT_OK(tab->Bind()); |
| |
| ASSERT_EQ(devices.size(), 2); |
| |
| char name[MAX_PARTITION_NAME_LENGTH]; |
| guid_t guid; |
| |
| // Device 0 |
| PartitionDevice* dev0 = devices[0].get(); |
| ASSERT_NOT_NULL(dev0); |
| ASSERT_OK(dev0->BlockPartitionGetName(name, sizeof(name))); |
| ASSERT_EQ(strcmp(name, "Linux filesystem"), 0); |
| ASSERT_OK(dev0->BlockPartitionGetGuid(GUIDTYPE_TYPE, &guid)); |
| { |
| uint8_t expected_guid[GPT_GUID_LEN] = GUID_LINUX_FILESYSTEM; |
| EXPECT_BYTES_EQ(reinterpret_cast<uint8_t*>(&guid), expected_guid, GPT_GUID_LEN); |
| } |
| ASSERT_OK(dev0->BlockPartitionGetGuid(GUIDTYPE_INSTANCE, &guid)); |
| { |
| uint8_t expected_guid[GPT_GUID_LEN] = GUID_UNIQUE_PART0; |
| EXPECT_BYTES_EQ(reinterpret_cast<uint8_t*>(&guid), expected_guid, GPT_GUID_LEN); |
| } |
| |
| PartitionDevice* dev1 = devices[1].get(); |
| ASSERT_NOT_NULL(dev1); |
| ASSERT_OK(dev1->BlockPartitionGetName(name, sizeof(name))); |
| ASSERT_EQ(kPartition1Name, name); |
| |
| ASSERT_OK(dev1->BlockPartitionGetGuid(GUIDTYPE_TYPE, &guid)); |
| { |
| uint8_t expected_guid[GPT_GUID_LEN] = GUID_LINUX_FILESYSTEM; |
| EXPECT_BYTES_EQ(reinterpret_cast<uint8_t*>(&guid), expected_guid, GPT_GUID_LEN); |
| } |
| ASSERT_OK(dev1->BlockPartitionGetGuid(GUIDTYPE_INSTANCE, &guid)); |
| { |
| uint8_t expected_guid[GPT_GUID_LEN] = GUID_UNIQUE_PART1; |
| EXPECT_BYTES_EQ(reinterpret_cast<uint8_t*>(&guid), expected_guid, GPT_GUID_LEN); |
| } |
| |
| dev0->AsyncRemove(); |
| dev1->AsyncRemove(); |
| |
| EXPECT_TRUE(ddk_.Ok()); |
| } |
| |
| TEST_F(GptDeviceTest, GuidMapMetadata) { |
| Init(); |
| fbl::Vector<std::unique_ptr<PartitionDevice>> devices; |
| |
| const guid_map_t guid_map[] = { |
| {"Linux filesystem", GUID_METADATA}, |
| }; |
| ddk_.SetMetadata(DEVICE_METADATA_GUID_MAP, &guid_map, sizeof(guid_map)); |
| |
| TableRef tab; |
| ASSERT_OK(PartitionTable::Create(fake_ddk::kFakeParent, &tab, &devices)); |
| ASSERT_OK(tab->Bind()); |
| |
| ASSERT_EQ(devices.size(), 2); |
| |
| char name[MAX_PARTITION_NAME_LENGTH]; |
| guid_t guid; |
| |
| // Device 0 |
| PartitionDevice* dev0 = devices[0].get(); |
| ASSERT_NOT_NULL(dev0); |
| ASSERT_OK(dev0->BlockPartitionGetName(name, sizeof(name))); |
| ASSERT_EQ(strcmp(name, "Linux filesystem"), 0); |
| ASSERT_OK(dev0->BlockPartitionGetGuid(GUIDTYPE_TYPE, &guid)); |
| { |
| uint8_t expected_guid[GPT_GUID_LEN] = GUID_METADATA; |
| EXPECT_BYTES_EQ(reinterpret_cast<uint8_t*>(&guid), expected_guid, GPT_GUID_LEN); |
| } |
| ASSERT_OK(dev0->BlockPartitionGetGuid(GUIDTYPE_INSTANCE, &guid)); |
| { |
| uint8_t expected_guid[GPT_GUID_LEN] = GUID_UNIQUE_PART0; |
| EXPECT_BYTES_EQ(reinterpret_cast<uint8_t*>(&guid), expected_guid, GPT_GUID_LEN); |
| } |
| |
| PartitionDevice* dev1 = devices[1].get(); |
| ASSERT_NOT_NULL(dev1); |
| ASSERT_OK(dev1->BlockPartitionGetName(name, sizeof(name))); |
| ASSERT_EQ(kPartition1Name, name); |
| |
| ASSERT_OK(dev1->BlockPartitionGetGuid(GUIDTYPE_TYPE, &guid)); |
| { |
| uint8_t expected_guid[GPT_GUID_LEN] = GUID_LINUX_FILESYSTEM; |
| EXPECT_BYTES_EQ(reinterpret_cast<uint8_t*>(&guid), expected_guid, GPT_GUID_LEN); |
| } |
| ASSERT_OK(dev1->BlockPartitionGetGuid(GUIDTYPE_INSTANCE, &guid)); |
| { |
| uint8_t expected_guid[GPT_GUID_LEN] = GUID_UNIQUE_PART1; |
| EXPECT_BYTES_EQ(reinterpret_cast<uint8_t*>(&guid), expected_guid, GPT_GUID_LEN); |
| } |
| |
| dev0->AsyncRemove(); |
| dev1->AsyncRemove(); |
| |
| EXPECT_TRUE(ddk_.Ok()); |
| } |
| |
| TEST_F(GptDeviceTest, BlockOpsPropagate) { |
| Init(); |
| fbl::Vector<std::unique_ptr<PartitionDevice>> devices; |
| |
| const guid_map_t guid_map[] = { |
| {"Linux filesystem", GUID_METADATA}, |
| }; |
| ddk_.SetMetadata(DEVICE_METADATA_GUID_MAP, &guid_map, sizeof(guid_map)); |
| |
| TableRef tab; |
| ASSERT_OK(PartitionTable::Create(fake_ddk::kFakeParent, &tab, &devices)); |
| ASSERT_OK(tab->Bind()); |
| |
| ASSERT_EQ(devices.size(), 2); |
| |
| PartitionDevice* dev0 = devices[0].get(); |
| PartitionDevice* dev1 = devices[1].get(); |
| |
| block_info_t block_info = {}; |
| size_t block_op_size = 0; |
| dev0->BlockImplQuery(&block_info, &block_op_size); |
| EXPECT_EQ(block_op_size, sizeof(block_op_t)); |
| |
| zx::vmo vmo; |
| ASSERT_OK(zx::vmo::create(4 * block_info.block_size, 0, &vmo)); |
| |
| block_op_t op = {}; |
| op.rw.command = BLOCK_OP_READ; |
| op.rw.vmo = vmo.get(); |
| op.rw.length = 4; |
| op.rw.offset_dev = 1000; |
| |
| BlockOpResult result; |
| dev0->BlockImplQueue(&op, BlockOpCompleter, &result); |
| sync_completion_wait(&result.completion, ZX_TIME_INFINITE); |
| sync_completion_reset(&result.completion); |
| |
| EXPECT_EQ(result.op.command, BLOCK_OP_READ); |
| EXPECT_EQ(result.op.rw.length, 4); |
| EXPECT_EQ(result.op.rw.offset_dev, 2048 + 1000); |
| EXPECT_OK(result.status); |
| |
| op.rw.command = BLOCK_OP_WRITE; |
| op.rw.vmo = vmo.get(); |
| op.rw.length = 4; |
| op.rw.offset_dev = 5000; |
| |
| dev1->BlockImplQueue(&op, BlockOpCompleter, &result); |
| sync_completion_wait(&result.completion, ZX_TIME_INFINITE); |
| sync_completion_reset(&result.completion); |
| |
| EXPECT_EQ(result.op.command, BLOCK_OP_WRITE); |
| EXPECT_EQ(result.op.rw.length, 4); |
| EXPECT_EQ(result.op.rw.offset_dev, 22528 + 5000); |
| EXPECT_OK(result.status); |
| |
| op.trim.command = BLOCK_OP_TRIM; |
| op.trim.length = 16; |
| op.trim.offset_dev = 10000; |
| |
| dev0->BlockImplQueue(&op, BlockOpCompleter, &result); |
| sync_completion_wait(&result.completion, ZX_TIME_INFINITE); |
| sync_completion_reset(&result.completion); |
| |
| EXPECT_EQ(result.op.command, BLOCK_OP_TRIM); |
| EXPECT_EQ(result.op.trim.length, 16); |
| EXPECT_EQ(result.op.trim.offset_dev, 2048 + 10000); |
| EXPECT_OK(result.status); |
| |
| op.command = BLOCK_OP_FLUSH; |
| |
| dev1->BlockImplQueue(&op, BlockOpCompleter, &result); |
| sync_completion_wait(&result.completion, ZX_TIME_INFINITE); |
| sync_completion_reset(&result.completion); |
| |
| EXPECT_EQ(result.op.command, BLOCK_OP_FLUSH); |
| EXPECT_OK(result.status); |
| |
| dev0->AsyncRemove(); |
| dev1->AsyncRemove(); |
| |
| EXPECT_TRUE(ddk_.Ok()); |
| } |
| |
| TEST_F(GptDeviceTest, BlockOpsOutOfBounds) { |
| Init(); |
| fbl::Vector<std::unique_ptr<PartitionDevice>> devices; |
| |
| const guid_map_t guid_map[] = { |
| {"Linux filesystem", GUID_METADATA}, |
| }; |
| ddk_.SetMetadata(DEVICE_METADATA_GUID_MAP, &guid_map, sizeof(guid_map)); |
| |
| TableRef tab; |
| ASSERT_OK(PartitionTable::Create(fake_ddk::kFakeParent, &tab, &devices)); |
| ASSERT_OK(tab->Bind()); |
| |
| ASSERT_EQ(devices.size(), 2); |
| |
| PartitionDevice* dev0 = devices[0].get(); |
| PartitionDevice* dev1 = devices[1].get(); |
| |
| block_info_t block_info = {}; |
| size_t block_op_size = 0; |
| dev0->BlockImplQuery(&block_info, &block_op_size); |
| EXPECT_EQ(block_op_size, sizeof(block_op_t)); |
| |
| zx::vmo vmo; |
| ASSERT_OK(zx::vmo::create(4 * block_info.block_size, 0, &vmo)); |
| |
| block_op_t op = {}; |
| op.rw.command = BLOCK_OP_READ; |
| op.rw.vmo = vmo.get(); |
| op.rw.length = 4; |
| op.rw.offset_dev = 20481; |
| |
| BlockOpResult result; |
| dev0->BlockImplQueue(&op, BlockOpCompleter, &result); |
| sync_completion_wait(&result.completion, ZX_TIME_INFINITE); |
| sync_completion_reset(&result.completion); |
| |
| EXPECT_NOT_OK(result.status); |
| |
| op.rw.command = BLOCK_OP_WRITE; |
| op.rw.vmo = vmo.get(); |
| op.rw.length = 4; |
| op.rw.offset_dev = 20478; |
| |
| dev0->BlockImplQueue(&op, BlockOpCompleter, &result); |
| sync_completion_wait(&result.completion, ZX_TIME_INFINITE); |
| sync_completion_reset(&result.completion); |
| |
| EXPECT_NOT_OK(result.status); |
| |
| op.trim.command = BLOCK_OP_TRIM; |
| op.trim.length = 18434; |
| op.trim.offset_dev = 0; |
| |
| dev1->BlockImplQueue(&op, BlockOpCompleter, &result); |
| sync_completion_wait(&result.completion, ZX_TIME_INFINITE); |
| sync_completion_reset(&result.completion); |
| |
| EXPECT_NOT_OK(result.status); |
| |
| dev0->AsyncRemove(); |
| dev1->AsyncRemove(); |
| |
| EXPECT_TRUE(ddk_.Ok()); |
| } |
| |
| } // namespace |
| } // namespace gpt |