| // 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 <lib/fdio/fdio.h> |
| |
| #include <blobfs/mkfs.h> |
| #include <blobfs/mount.h> |
| #include <block-client/cpp/block-device.h> |
| #include <block-client/cpp/fake-device.h> |
| #include <zxtest/zxtest.h> |
| |
| #include "blobfs.h" |
| #include "fdio_test.h" |
| #include "nand_test.h" |
| #include "runner.h" |
| #include "test/blob_utils.h" |
| |
| namespace blobfs { |
| |
| namespace { |
| |
| using SyncFdioTest = FdioTest; |
| using SyncNandTest = NandTest; |
| |
| uint64_t GetSucceededFlushCalls(block_client::FakeBlockDevice* device) { |
| fuchsia_hardware_block_BlockStats stats; |
| device->GetStats(true, &stats); |
| return stats.flush.success.total_calls; |
| } |
| |
| } // namespace |
| |
| // Verifies that fdio "fsync" calls actually sync blobfs files to the block device and verifies |
| // behavior for different lifecycles of creating a file. |
| TEST_F(SyncFdioTest, Sync) { |
| std::unique_ptr<BlobInfo> info; |
| ASSERT_NO_FAILURES(GenerateRandomBlob("", 64, &info)); |
| |
| memmove(info->path, info->path + 1, strlen(info->path)); // Remove leading slash. |
| int file = openat(root_fd(), info->path, O_RDWR | O_CREAT); |
| EXPECT_TRUE(file >= 1); |
| |
| // We have not written any data to the file. Blobfs requires the file data to be written so the |
| // name is the hash of the contents. |
| EXPECT_EQ(-1, fsync(file)); |
| |
| // Write the contents. The file must be truncated before writing to declare its size. |
| EXPECT_EQ(0, ftruncate(file, info->size_data)); |
| EXPECT_EQ(info->size_data, write(file, info->data.get(), info->size_data)); |
| |
| // Sync the file. This will block until woken up by the file_wake_thread. |
| EXPECT_EQ(0, fsync(file)); |
| |
| // fsync on a file will flush the writes to the block device but not actually flush the block |
| // device itself. |
| fuchsia_hardware_block_BlockStats stats; |
| block_device()->GetStats(true, &stats); |
| EXPECT_LE(1u, stats.write.success.total_calls); |
| EXPECT_EQ(0u, stats.flush.success.total_calls); |
| |
| // Sync the root directory. Syncing a directory will force the block device to flush. |
| EXPECT_EQ(0, fsync(root_fd())); |
| EXPECT_EQ(1u, GetSucceededFlushCalls(block_device())); |
| } |
| |
| // Verifies that fdio "sync" actually flushes a NAND device. This tests the fdio, blobfs, block |
| // device, and FTL layers. |
| TEST_F(SyncNandTest, Sync) { |
| // Make a VMO to give to the RAM-NAND. |
| const size_t vmo_size = Connection::GetVMOSize(); |
| zx::vmo initial_vmo; |
| ASSERT_OK(zx::vmo::create(vmo_size, 0, &initial_vmo)); |
| |
| // NAND VMOs must be prepopulated with 0xff. |
| zx_vaddr_t vmar_address = 0; |
| ASSERT_OK(zx::vmar::root_self()->map(ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, 0, initial_vmo, 0, vmo_size, |
| &vmar_address)); |
| char* initial_vmo_data = reinterpret_cast<char*>(vmar_address); |
| std::fill(initial_vmo_data, &initial_vmo_data[vmo_size], 0xff); |
| |
| // Create a second VMO for later use. |
| zx::vmo second_vmo; |
| ASSERT_OK(zx::vmo::create(vmo_size, 0, &second_vmo)); |
| ASSERT_OK(zx::vmar::root_self()->map(ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, 0, second_vmo, 0, vmo_size, |
| &vmar_address)); |
| char* second_vmo_data = reinterpret_cast<char*>(vmar_address); |
| |
| std::unique_ptr<BlobInfo> info; |
| ASSERT_NO_FAILURES(GenerateRandomBlob("", 64, &info)); |
| |
| { |
| Connection initial_connection("/initial/dev", std::move(initial_vmo), true); |
| |
| memmove(info->path, info->path + 1, strlen(info->path)); // Remove leading slash. |
| fbl::unique_fd file(openat(initial_connection.root_fd(), info->path, O_RDWR | O_CREAT)); |
| ASSERT_TRUE(file.is_valid()); |
| |
| // Write the contents. The file must be truncated before writing to declare its size. |
| ASSERT_EQ(0, ftruncate(file.get(), info->size_data)); |
| ASSERT_EQ(info->size_data, write(file.get(), info->data.get(), info->size_data)); |
| |
| // This should block until the sync is complete. fsync-ing the root FD is required to flush |
| // everything. |
| ASSERT_EQ(0, fsync(file.get())); |
| ASSERT_EQ(0, fsync(initial_connection.root_fd())); |
| |
| // Without closing the file or tearing down the existing connection (which may add extra |
| // flushes, etc.), create a snapshot of the current memory in the second VMO. This will emulate |
| // a power cycle |
| memcpy(second_vmo_data, initial_vmo_data, vmo_size); |
| } |
| |
| // New connection with a completely new NAND controller reading the same memory. |
| // |
| // This call may fail if the above fsync on the root directory is not successful because the |
| // device will have garbage data in it. |
| Connection second_connection("/second/dev", std::move(second_vmo), false); |
| |
| // The blob file should exist. |
| fbl::unique_fd file(openat(second_connection.root_fd(), info->path, O_RDONLY)); |
| ASSERT_TRUE(file.is_valid()); |
| |
| // The contents should be exactly what we wrote. |
| std::unique_ptr<char[]> read_data = std::make_unique<char[]>(info->size_data); |
| ASSERT_EQ(info->size_data, read(file.get(), read_data.get(), info->size_data)); |
| EXPECT_BYTES_EQ(info->data.get(), &read_data[0], info->size_data, "mismatch"); |
| } |
| |
| } // namespace blobfs |