| // Copyright 2020 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 <lib/async-loop/cpp/loop.h> |
| #include <lib/async-loop/default.h> |
| |
| #include <gtest/gtest.h> |
| |
| #include "src/storage/lib/block_client/cpp/fake_block_device.h" |
| #include "src/storage/minfs/bcache.h" |
| #include "src/storage/minfs/format.h" |
| #include "src/storage/minfs/lazy_buffer.h" |
| #include "src/storage/minfs/minfs.h" |
| #include "src/storage/minfs/runner.h" |
| |
| namespace minfs { |
| namespace { |
| |
| using ::block_client::FakeBlockDevice; |
| |
| class VnodeMapperTestFixture : public testing::Test { |
| public: |
| const int kNumBlocks = 1 << 20; |
| |
| VnodeMapperTestFixture() : vfs_loop_(&kAsyncLoopConfigNoAttachToCurrentThread) {} |
| |
| void SetUp() override { |
| auto device = std::make_unique<FakeBlockDevice>(kNumBlocks, kMinfsBlockSize); |
| ASSERT_TRUE(device); |
| auto bcache_or = Bcache::Create(std::move(device), kNumBlocks); |
| ASSERT_TRUE(bcache_or.is_ok()); |
| ASSERT_TRUE(Mkfs(bcache_or.value().get()).is_ok()); |
| |
| auto fs_or = |
| Runner::Create(vfs_loop_.dispatcher(), std::move(bcache_or.value()), MountOptions()); |
| ASSERT_TRUE(fs_or.is_ok()); |
| runner_ = std::move(fs_or.value()); |
| |
| VnodeMinfs::Allocate(&runner_->minfs(), kMinfsTypeFile, &vnode_); |
| EXPECT_EQ(vnode_->Open(nullptr), ZX_OK); |
| } |
| |
| ~VnodeMapperTestFixture() { vnode_->Close(); } |
| |
| protected: |
| async::Loop vfs_loop_; |
| std::unique_ptr<Runner> runner_; |
| fbl::RefPtr<VnodeMinfs> vnode_; |
| }; |
| |
| using VnodeIndirectMapperTest = VnodeMapperTestFixture; |
| |
| TEST_F(VnodeIndirectMapperTest, FirstIndirectBlockIsMapped) { |
| vnode_->GetMutableInode()->inum[0] = 10; |
| VnodeIndirectMapper mapper(vnode_.get()); |
| zx::result<DeviceBlockRange> device_range = mapper.Map(BlockRange(0, 2)); |
| ASSERT_EQ(device_range.status_value(), ZX_OK); |
| ASSERT_TRUE(device_range.value().IsMapped()); |
| EXPECT_EQ(runner_->minfs().Info().dat_block + 10, device_range.value().block()); |
| EXPECT_EQ(device_range.value().count(), 1ul); |
| } |
| |
| TEST_F(VnodeIndirectMapperTest, CoalescedBlocks) { |
| vnode_->GetMutableInode()->inum[0] = 10; |
| vnode_->GetMutableInode()->inum[1] = 11; |
| VnodeIndirectMapper mapper(vnode_.get()); |
| zx::result<DeviceBlockRange> device_range = mapper.Map(BlockRange(0, 2)); |
| ASSERT_EQ(device_range.status_value(), ZX_OK); |
| EXPECT_EQ(runner_->minfs().Info().dat_block + 10, device_range.value().block()); |
| EXPECT_EQ(device_range.value().count(), 2ul); |
| } |
| |
| TEST_F(VnodeIndirectMapperTest, LastIndirectBlockIsMapped) { |
| vnode_->GetMutableInode()->inum[kMinfsIndirect - 1] = 17; |
| VnodeIndirectMapper mapper(vnode_.get()); |
| zx::result<DeviceBlockRange> device_range = |
| mapper.Map(BlockRange(kMinfsIndirect - 1, kMinfsIndirect)); |
| ASSERT_EQ(device_range.status_value(), ZX_OK); |
| EXPECT_EQ(runner_->minfs().Info().dat_block + 17, device_range.value().block()); |
| EXPECT_EQ(device_range.value().count(), 1ul); |
| } |
| |
| TEST_F(VnodeIndirectMapperTest, IndirectBlocksAreUnmapped) { |
| vnode_->GetMutableInode()->inum[kMinfsIndirect - 1] = 17; |
| VnodeIndirectMapper mapper(vnode_.get()); |
| zx::result<DeviceBlockRange> device_range = mapper.Map(BlockRange(3, kMinfsIndirect)); |
| ASSERT_EQ(device_range.status_value(), ZX_OK); |
| EXPECT_FALSE(device_range.value().IsMapped()); |
| EXPECT_EQ(kMinfsIndirect - 3 - 1, device_range.value().count()); |
| } |
| |
| TEST_F(VnodeIndirectMapperTest, DoubleIndirectBlockIsMapped) { |
| vnode_->GetMutableInode()->dinum[0] = 17; |
| VnodeIndirectMapper mapper(vnode_.get()); |
| zx::result<DeviceBlockRange> device_range = |
| mapper.Map(BlockRange(kMinfsIndirect, kMinfsIndirect + 1)); |
| ASSERT_EQ(device_range.status_value(), ZX_OK); |
| EXPECT_EQ(runner_->minfs().Info().dat_block + 17, device_range.value().block()); |
| EXPECT_EQ(device_range.value().count(), 1ul); |
| } |
| |
| TEST_F(VnodeIndirectMapperTest, DoubleIndirectFirstLeafBlockIsMapped) { |
| vnode_->GetMutableInode()->dinum[0] = 17; |
| blk_t buffer[kMinfsDirectPerIndirect] = {18}; |
| ASSERT_TRUE(runner_->minfs() |
| .GetMutableBcache() |
| ->Writeblk(runner_->minfs().Info().dat_block + 17, buffer) |
| .is_ok()); |
| VnodeIndirectMapper mapper(vnode_.get()); |
| uint64_t block = kMinfsIndirect + kMinfsDoublyIndirect; |
| zx::result<DeviceBlockRange> device_range = mapper.Map(BlockRange(block, block + 1)); |
| ASSERT_EQ(device_range.status_value(), ZX_OK); |
| EXPECT_EQ(runner_->minfs().Info().dat_block + 18, device_range.value().block()); |
| EXPECT_EQ(device_range.value().count(), 1ul); |
| } |
| |
| TEST_F(VnodeIndirectMapperTest, DoubleIndirectLastLeafBlockIsMapped) { |
| vnode_->GetMutableInode()->dinum[0] = 17; |
| blk_t buffer[kMinfsDirectPerIndirect] = {}; |
| buffer[kMinfsDirectPerIndirect - 1] = 21; |
| ASSERT_TRUE(runner_->minfs() |
| .GetMutableBcache() |
| ->Writeblk(runner_->minfs().Info().dat_block + 17, buffer) |
| .is_ok()); |
| VnodeIndirectMapper mapper(vnode_.get()); |
| uint64_t block = |
| kMinfsIndirect + kMinfsDoublyIndirect + kMinfsDirectPerIndirect * kMinfsDoublyIndirect - 1; |
| zx::result<DeviceBlockRange> device_range = mapper.Map(BlockRange(block, block + 1)); |
| ASSERT_EQ(device_range.status_value(), ZX_OK); |
| EXPECT_EQ(runner_->minfs().Info().dat_block + 21, device_range.value().block()); |
| EXPECT_EQ(device_range.value().count(), 1ul); |
| } |
| |
| TEST_F(VnodeIndirectMapperTest, BlockOutOfRange) { |
| VnodeIndirectMapper mapper(vnode_.get()); |
| uint64_t block = |
| kMinfsIndirect + kMinfsDoublyIndirect + kMinfsDirectPerIndirect * kMinfsDoublyIndirect; |
| zx::result<DeviceBlockRange> device_range = mapper.Map(BlockRange(block, block + 1)); |
| EXPECT_EQ(device_range.status_value(), ZX_ERR_OUT_OF_RANGE); |
| } |
| |
| class FakeTransaction : public PendingWork { |
| public: |
| static constexpr uint64_t kFirstBlock = 31; |
| |
| FakeTransaction(Bcache* bcache) : bcache_(*bcache) {} |
| |
| void EnqueueMetadata(storage::Operation operation, storage::BlockBuffer* buffer) override { |
| uint64_t length = operation.length; |
| uint8_t data[kMinfsBlockSize]; |
| uint64_t vmo_offset = operation.vmo_offset * kMinfsBlockSize; |
| uint64_t dev_block = operation.dev_offset; |
| while (length > 0) { |
| if (operation.type == storage::OperationType::kRead) { |
| ASSERT_TRUE(bcache_.Readblk(static_cast<blk_t>(dev_block), data).is_ok()); |
| zx_vmo_write(buffer->Vmo(), data, vmo_offset, kMinfsBlockSize); |
| } else { |
| zx_vmo_read(buffer->Vmo(), data, vmo_offset, kMinfsBlockSize); |
| ASSERT_TRUE(bcache_.Writeblk(static_cast<blk_t>(dev_block), data).is_ok()); |
| } |
| ++vmo_offset; |
| ++dev_block; |
| --length; |
| } |
| ++write_count_; |
| } |
| void EnqueueData(storage::Operation operation, storage::BlockBuffer* buffer) override { |
| EnqueueMetadata(operation, buffer); |
| } |
| size_t AllocateBlock() override { return block_++; } |
| void DeallocateBlock(size_t block) override { deallocated_blocks_.push_back(block); } |
| |
| int write_count() const { return write_count_; } |
| std::vector<size_t>& deallocated_blocks() { return deallocated_blocks_; } |
| |
| private: |
| Bcache& bcache_; |
| uint64_t block_ = kFirstBlock; |
| int write_count_ = 0; |
| std::vector<size_t> deallocated_blocks_; |
| }; |
| |
| TEST_F(VnodeIndirectMapperTest, MapForWriteAllocatesBlock) { |
| VnodeIndirectMapper mapper(vnode_.get()); |
| FakeTransaction transaction(runner_->minfs().GetMutableBcache()); |
| bool allocated = false; |
| zx::result<DeviceBlockRange> device_range = |
| mapper.MapForWrite(&transaction, BlockRange(10, 10 + 2), &allocated); |
| ASSERT_EQ(device_range.status_value(), ZX_OK); |
| EXPECT_EQ(runner_->minfs().Info().dat_block + FakeTransaction::kFirstBlock, |
| device_range.value().block()); |
| EXPECT_EQ(device_range.value().count(), 1ul); |
| EXPECT_TRUE(allocated); |
| } |
| |
| using VnodeMapperTest = VnodeMapperTestFixture; |
| |
| TEST_F(VnodeMapperTest, VnodeMapperDirectBlocksAreMapped) { |
| vnode_->GetMutableInode()->dnum[0] = 17; |
| VnodeMapper mapper(vnode_.get()); |
| zx::result<std::pair<blk_t, uint64_t>> mapping = mapper.MapToBlk(BlockRange(0, 2)); |
| ASSERT_EQ(mapping.status_value(), ZX_OK); |
| EXPECT_EQ(mapping.value().first, 17u); |
| EXPECT_EQ(mapping.value().second, 1u); |
| zx::result<DeviceBlockRange> device_range = mapper.Map(BlockRange(0, 2)); |
| ASSERT_EQ(device_range.status_value(), ZX_OK); |
| ASSERT_TRUE(device_range.value().IsMapped()); |
| EXPECT_EQ(runner_->minfs().Info().dat_block + 17, device_range.value().block()); |
| EXPECT_EQ(device_range.value().count(), 1ul); |
| } |
| |
| TEST_F(VnodeMapperTest, VnodeMapperContiguousDirectBlocksAreCoalesced) { |
| vnode_->GetMutableInode()->dnum[0] = 17; |
| vnode_->GetMutableInode()->dnum[1] = 18; |
| vnode_->GetMutableInode()->dnum[2] = 20; |
| VnodeMapper mapper(vnode_.get()); |
| zx::result<DeviceBlockRange> device_range = mapper.Map(BlockRange(0, 3)); |
| ASSERT_EQ(device_range.status_value(), ZX_OK); |
| ASSERT_TRUE(device_range.value().IsMapped()); |
| EXPECT_EQ(runner_->minfs().Info().dat_block + 17, device_range.value().block()); |
| EXPECT_EQ(device_range.value().count(), 2ul); |
| } |
| |
| TEST_F(VnodeMapperTest, VnodeMapperIndirectBlocksAreMapped) { |
| vnode_->GetMutableInode()->inum[0] = 17; |
| blk_t buffer[kMinfsDirectPerIndirect] = {19}; |
| ASSERT_TRUE(runner_->minfs() |
| .GetMutableBcache() |
| ->Writeblk(runner_->minfs().Info().dat_block + 17, buffer) |
| .is_ok()); |
| VnodeMapper mapper(vnode_.get()); |
| uint64_t block = kMinfsDirect; |
| zx::result<DeviceBlockRange> device_range = mapper.Map(BlockRange(block, block + 2)); |
| ASSERT_EQ(device_range.status_value(), ZX_OK); |
| ASSERT_TRUE(device_range.value().IsMapped()); |
| EXPECT_EQ(runner_->minfs().Info().dat_block + 19, device_range.value().block()); |
| EXPECT_EQ(device_range.value().count(), 1ul); |
| } |
| |
| TEST_F(VnodeMapperTest, VnodeMapperDoubleIndirectBlocksAreMapped) { |
| vnode_->GetMutableInode()->dinum[0] = 17; |
| blk_t buffer[kMinfsDirectPerIndirect] = {19}; |
| ASSERT_TRUE(runner_->minfs() |
| .GetMutableBcache() |
| ->Writeblk(runner_->minfs().Info().dat_block + 17, buffer) |
| .is_ok()); |
| buffer[0] = 37; |
| buffer[1] = 38; |
| ASSERT_TRUE(runner_->minfs() |
| .GetMutableBcache() |
| ->Writeblk(runner_->minfs().Info().dat_block + 19, buffer) |
| .is_ok()); |
| VnodeMapper mapper(vnode_.get()); |
| uint64_t block_count = 3; |
| uint64_t block = kMinfsDirect + kMinfsIndirect * kMinfsDirectPerIndirect; |
| zx::result<DeviceBlockRange> device_range = mapper.Map(BlockRange(block, block + block_count)); |
| ASSERT_EQ(device_range.status_value(), ZX_OK); |
| ASSERT_TRUE(device_range.value().IsMapped()); |
| EXPECT_EQ(runner_->minfs().Info().dat_block + 37, device_range.value().block()); |
| EXPECT_EQ(device_range.value().count(), 2ul); |
| } |
| |
| using VnodeIteratorTest = VnodeMapperTestFixture; |
| |
| TEST_F(VnodeIteratorTest, WholeFileIsSparse) { |
| VnodeMapper mapper(vnode_.get()); |
| VnodeIterator iterator; |
| ASSERT_TRUE(iterator.Init(&mapper, /*transaction=*/nullptr, 0).is_ok()); |
| EXPECT_EQ(iterator.Blk(), 0u); |
| // The entire file should be sparse. |
| EXPECT_EQ(kMinfsDirect, iterator.GetContiguousBlockCount()); |
| EXPECT_TRUE(iterator.Advance(kMinfsDirect).is_ok()); |
| EXPECT_EQ(kMinfsIndirect * kMinfsDirectPerIndirect, iterator.GetContiguousBlockCount()); |
| EXPECT_EQ(kMinfsDirect, iterator.file_block()); |
| EXPECT_TRUE( |
| iterator.Advance(static_cast<uint64_t>(kMinfsIndirect) * kMinfsDirectPerIndirect).is_ok()); |
| EXPECT_EQ(kMinfsDoublyIndirect * kMinfsDirectPerIndirect * kMinfsDirectPerIndirect, |
| iterator.GetContiguousBlockCount()); |
| EXPECT_TRUE(iterator |
| .Advance(static_cast<uint64_t>(kMinfsDoublyIndirect) * kMinfsDirectPerIndirect * |
| kMinfsDirectPerIndirect) |
| .is_ok()); |
| EXPECT_EQ(iterator.GetContiguousBlockCount(), 0ul); |
| } |
| |
| TEST_F(VnodeIteratorTest, SparseFirstIndirectBlockCoalescedCorrectly) { |
| vnode_->GetMutableInode()->inum[1] = 17; |
| VnodeMapper mapper(vnode_.get()); |
| VnodeIterator iterator; |
| ASSERT_TRUE(iterator.Init(&mapper, /*transaction=*/nullptr, kMinfsDirect).is_ok()); |
| EXPECT_EQ(iterator.GetContiguousBlockCount(std::numeric_limits<uint64_t>::max()), 2048ul); |
| } |
| |
| TEST_F(VnodeIteratorTest, AdvanceBeyondMaximumFails) { |
| VnodeMapper mapper(vnode_.get()); |
| VnodeIterator iterator; |
| ASSERT_TRUE(iterator.Init(&mapper, /*transaction=*/nullptr, VnodeMapper::kMaxBlocks).is_ok()); |
| EXPECT_EQ(iterator.Advance(1).status_value(), ZX_ERR_BAD_STATE); |
| } |
| |
| TEST_F(VnodeIteratorTest, SetDirectBlock) { |
| VnodeMapper mapper(vnode_.get()); |
| VnodeIterator iterator; |
| FakeTransaction transaction(runner_->minfs().GetMutableBcache()); |
| ASSERT_TRUE(iterator.Init(&mapper, &transaction, 0).is_ok()); |
| EXPECT_TRUE(iterator.SetBlk(1).is_ok()); |
| ASSERT_TRUE(iterator.Flush().is_ok()); |
| EXPECT_EQ(vnode_->GetInode()->dnum[0], 1u); |
| } |
| |
| TEST_F(VnodeIteratorTest, SetIndirectBlock) { |
| VnodeMapper mapper(vnode_.get()); |
| VnodeIterator iterator; |
| FakeTransaction transaction(runner_->minfs().GetMutableBcache()); |
| ASSERT_TRUE(iterator.Init(&mapper, &transaction, kMinfsDirect).is_ok()); |
| EXPECT_TRUE(iterator.SetBlk(1).is_ok()); |
| EXPECT_TRUE(iterator.Flush().is_ok()); |
| EXPECT_EQ(FakeTransaction::kFirstBlock, vnode_->GetInode()->inum[0]); |
| // Check the indirect node was flushed. |
| blk_t data[kMinfsDirectPerIndirect]; |
| ASSERT_TRUE(runner_->minfs() |
| .GetMutableBcache() |
| ->Readblk(runner_->minfs().Info().dat_block + FakeTransaction::kFirstBlock, data) |
| .is_ok()); |
| EXPECT_EQ(data[0], 1u); |
| } |
| |
| TEST_F(VnodeIteratorTest, AllocateLastBlock) { |
| VnodeMapper mapper(vnode_.get()); |
| VnodeIterator iterator; |
| FakeTransaction transaction(runner_->minfs().GetMutableBcache()); |
| // Allocate the very last block. |
| ASSERT_TRUE(iterator |
| .Init(&mapper, &transaction, |
| kMinfsDirect + |
| (kMinfsIndirect + kMinfsDoublyIndirect * kMinfsDirectPerIndirect) * |
| kMinfsDirectPerIndirect - |
| 1) |
| .is_ok()); |
| EXPECT_TRUE(iterator.SetBlk(1).is_ok()); |
| EXPECT_TRUE(iterator.Flush().is_ok()); |
| // Check the doubly indirect node was flushed. |
| EXPECT_NE(vnode_->GetInode()->dinum[kMinfsDoublyIndirect - 1], 0u); |
| blk_t data[kMinfsDirectPerIndirect]; |
| ASSERT_TRUE(runner_->minfs() |
| .GetMutableBcache() |
| ->Readblk(runner_->minfs().Info().dat_block + |
| vnode_->GetInode()->dinum[kMinfsDoublyIndirect - 1], |
| data) |
| .is_ok()); |
| EXPECT_NE(data[kMinfsDirectPerIndirect - 1], 0u); |
| ASSERT_TRUE( |
| runner_->minfs() |
| .GetMutableBcache() |
| ->Readblk(runner_->minfs().Info().dat_block + data[kMinfsDirectPerIndirect - 1], data) |
| .is_ok()); |
| EXPECT_EQ(data[kMinfsDirectPerIndirect - 1], 1u); |
| } |
| |
| TEST_F(VnodeIteratorTest, IndirectBlockDeallocatedWhenCleared) { |
| VnodeMapper mapper(vnode_.get()); |
| // Test to ensure that indirect blocks are freed rather than written when they have no more |
| // entries. |
| blk_t blocks[2]; |
| blk_t indirect_blocks[3]; |
| blk_t data[kMinfsDirectPerIndirect]; |
| FakeTransaction transaction(runner_->minfs().GetMutableBcache()); |
| { |
| // First allocate two blocks in the double-indirect region. We should end up with something |
| // like: |
| // |
| // inode.dinum (a) |
| // | |
| // v |
| // |b|c| ... | |
| // | | |
| // | +-----------------+ |
| // v v |
| // |x| ... | |y| ... | |
| // |
| // where a, b and c are the indirect blocks allocated. These are tracked by |indirect_blocks|. x |
| // and y are the direct blocks and are tracked by |blocks|. |
| |
| VnodeIterator iterator; |
| ASSERT_TRUE( |
| iterator |
| .Init(&mapper, &transaction, kMinfsDirect + kMinfsIndirect * kMinfsDirectPerIndirect) |
| .is_ok()); |
| blocks[0] = static_cast<blk_t>(transaction.AllocateBlock()); |
| blocks[1] = static_cast<blk_t>(transaction.AllocateBlock()); |
| EXPECT_TRUE(iterator.SetBlk(blocks[0]).is_ok()); |
| EXPECT_TRUE(iterator.Advance(kMinfsDirectPerIndirect).is_ok()); |
| EXPECT_TRUE(iterator.SetBlk(blocks[1]).is_ok()); |
| EXPECT_TRUE(iterator.Flush().is_ok()); |
| |
| // Wait for the flush to make it through to the device. |
| sync_completion_t synced; |
| runner_->minfs().Sync([&](zx_status_t status) { sync_completion_signal(&synced); }); |
| EXPECT_EQ(sync_completion_wait(&synced, zx::sec(5).get()), ZX_OK); |
| |
| // Check the block pointers. |
| indirect_blocks[0] = vnode_->GetInode()->dinum[kMinfsDoublyIndirect - 1]; |
| EXPECT_NE(indirect_blocks[0], 0u); |
| ASSERT_TRUE(runner_->minfs() |
| .GetMutableBcache() |
| ->Readblk(runner_->minfs().Info().dat_block + indirect_blocks[0], data) |
| .is_ok()); |
| indirect_blocks[1] = data[0]; |
| indirect_blocks[2] = data[1]; |
| EXPECT_NE(indirect_blocks[1], 0u); |
| EXPECT_NE(indirect_blocks[2], 1u); |
| ASSERT_TRUE(runner_->minfs() |
| .GetMutableBcache() |
| ->Readblk(runner_->minfs().Info().dat_block + indirect_blocks[1], data) |
| .is_ok()); |
| EXPECT_EQ(blocks[0], data[0]); |
| ASSERT_TRUE(runner_->minfs() |
| .GetMutableBcache() |
| ->Readblk(runner_->minfs().Info().dat_block + indirect_blocks[2], data) |
| .is_ok()); |
| EXPECT_EQ(blocks[1], data[0]); |
| } |
| |
| const int write_count_for_set_up = transaction.write_count(); |
| |
| // Now with a new iterator, zero those entries out and flush, and it should deallocate all |
| // blocks. |
| VnodeIterator iterator; |
| ASSERT_TRUE( |
| iterator.Init(&mapper, &transaction, kMinfsDirect + kMinfsIndirect * kMinfsDirectPerIndirect) |
| .is_ok()); |
| EXPECT_TRUE(iterator.SetBlk(0).is_ok()); |
| EXPECT_TRUE(iterator.Advance(kMinfsDirectPerIndirect).is_ok()); |
| // That should have caused the first of the indirect blocks to be freed. |
| ASSERT_EQ(transaction.deallocated_blocks().size(), 1ul); |
| EXPECT_EQ(indirect_blocks[1], transaction.deallocated_blocks()[0]); |
| // But nothing should have been written yet via FakeTransaction because the iterator shouldn't |
| // have written any blocks yet. |
| EXPECT_EQ(write_count_for_set_up, transaction.write_count()); |
| |
| // Flush now. |
| EXPECT_TRUE(iterator.Flush().is_ok()); |
| ASSERT_TRUE(runner_->minfs() |
| .GetMutableBcache() |
| ->Readblk(runner_->minfs().Info().dat_block + indirect_blocks[0], data) |
| .is_ok()); |
| EXPECT_EQ(data[0], 0u); |
| EXPECT_EQ(indirect_blocks[2], data[1]); |
| |
| // Deallocate the second block and advance. |
| EXPECT_TRUE(iterator.SetBlk(0).is_ok()); |
| // Advance to the end. |
| EXPECT_TRUE(iterator.Advance(VnodeMapper::kMaxBlocks - iterator.file_block()).is_ok()); |
| // All blocks should have been deallocated now. |
| ASSERT_EQ(transaction.deallocated_blocks().size(), 3ul); |
| EXPECT_EQ(indirect_blocks[2], transaction.deallocated_blocks()[1]); |
| EXPECT_EQ(indirect_blocks[0], transaction.deallocated_blocks()[2]); |
| EXPECT_EQ(vnode_->GetInode()->dinum[kMinfsDoublyIndirect - 1], 0u); |
| } |
| |
| } // namespace |
| } // namespace minfs |