| // 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 <fidl/fuchsia.device/cpp/wire.h> |
| #include <lib/fdio/directory.h> |
| #include <lib/fzl/owned-vmo-mapper.h> |
| #include <lib/syslog/cpp/macros.h> |
| #include <lib/zx/channel.h> |
| |
| #include <random> |
| #include <thread> |
| |
| #include <fbl/unique_fd.h> |
| #include <gtest/gtest.h> |
| |
| #include "src/storage/fs_test/fs_test.h" |
| #include "src/storage/fs_test/test_filesystem.h" |
| |
| namespace fs_test { |
| namespace { |
| |
| namespace device = fuchsia_device; |
| |
| class CorruptTest : public testing::Test, |
| public testing::WithParamInterface<TestFilesystemOptions> {}; |
| |
| TEST_P(CorruptTest, CorruptTest) { |
| // 768 blocks containing 64 pages of 4 KiB with 8 bytes OOB |
| constexpr int kSize = 768 * 64 * (4096 + 8); |
| |
| for (int pass = 0; pass < 2; ++pass) { |
| fzl::OwnedVmoMapper vmo; |
| ASSERT_EQ(vmo.CreateAndMap(kSize, "corrupt-test-vmo"), ZX_OK); |
| memset(vmo.start(), 0xff, kSize); |
| |
| TestFilesystemOptions options = GetParam(); |
| options.device_block_size = std::min(options.filesystem->GetTraits().max_block_size, 8192L); |
| // For now, a ram-nand device supports only a block size of 8KiB. |
| if (options.device_block_size == 8192) { |
| options.use_ram_nand = true; |
| } else { |
| options.ram_disk_discard_random_after_last_flush = true; |
| } |
| options.device_block_count = 0; // Use VMO size. |
| options.vmo = zx::unowned_vmo(vmo.vmo()); |
| if (options.use_fvm) { |
| // Create a dummy FVM partition that shifts the location of the minfs partition such that the |
| // offsets being used will hit the second half of the FTL's 8 KiB map pages. |
| options.dummy_fvm_partition_size = 8'388'608; |
| } else { |
| options.use_fvm = true; |
| options.fvm_slice_size = 32'768; |
| options.initial_fvm_slice_count = 5120; // Leaves 32 MiB for FVM & FTL metadata. |
| } |
| std::random_device random; |
| |
| if (pass == 0) { |
| // In this first pass, a write failure is closely targeted such that the failure is more |
| // likely to occur just as the FTL is writing the first page of a new map block. If things |
| // change with the write pattern for minfs, then this range might not be right, so on the |
| // second pass, we target a much wider range. |
| std::uniform_int_distribution distribution(1325, 1400); |
| |
| // Deliberately fail after an odd number so that we always fail half-way through an 8 KiB |
| // write. |
| options.fail_after = distribution(random) | 1; |
| } else { |
| // On the second pass, we use a wider random range in case a change in the system means that |
| // the first pass no longer targets weak spots. |
| std::uniform_int_distribution distribution(1300, 2300); |
| options.fail_after = distribution(random); |
| } |
| |
| { |
| auto fs_or = TestFilesystem::Create(options); |
| ASSERT_TRUE(fs_or.is_ok()) << fs_or.status_string(); |
| TestFilesystem fs = std::move(fs_or).value(); |
| |
| // Loop until we encounter write failures. |
| const std::string file1 = fs.mount_path() + "/file1"; |
| const std::string file2 = fs.mount_path() + "/file2"; |
| for (;;) { |
| { |
| fbl::unique_fd fd(open(file1.c_str(), O_RDWR | O_CREAT, 0644)); |
| if (!fd || write(fd.get(), "hello", 5) != 5 || ftruncate(fd.get(), 0) != 0 || |
| fsync(fd.get()) != 0 || unlink(file1.c_str()) != 0) { |
| break; |
| } |
| } |
| { |
| fbl::unique_fd fd(open(file2.c_str(), O_RDWR | O_CREAT, 0644)); |
| if (!fd || write(fd.get(), "hello", 5) != 5 || ftruncate(fd.get(), 0) != 0 || |
| fsync(fd.get()) != 0 || unlink(file2.c_str()) != 0) { |
| break; |
| } |
| } |
| } |
| } |
| |
| std::cout << "Remounting" << std::endl; |
| options.fail_after = 0; |
| auto fs_or = TestFilesystem::Open(options); |
| ASSERT_TRUE(fs_or.is_ok()) << fs_or.status_string(); |
| TestFilesystem fs = std::move(fs_or).value(); |
| EXPECT_EQ(fs.Unmount().status_value(), ZX_OK); |
| EXPECT_EQ(fs.Fsck().status_value(), ZX_OK); |
| } |
| } |
| |
| TEST_P(CorruptTest, OutOfOrderWrites) { |
| constexpr int kSize = 768 * 64 * 4096; |
| fzl::OwnedVmoMapper vmo; |
| ASSERT_EQ(vmo.CreateAndMap(kSize, "corrupt-test-vmo"), ZX_OK); |
| memset(vmo.start(), 0xff, kSize); |
| |
| TestFilesystemOptions options = GetParam(); |
| options.device_block_size = std::min(options.filesystem->GetTraits().max_block_size, 8192L); |
| options.device_block_count = 0; // Use VMO size. |
| options.vmo = zx::unowned_vmo(vmo.vmo()); |
| if (options.use_fvm) { |
| // Create a dummy FVM partition that shifts the location of the minfs partition such that the |
| // offsets being used will hit the second half of the FTL's 8 KiB map pages. |
| options.dummy_fvm_partition_size = 8'388'608; |
| } else { |
| options.use_fvm = true; |
| options.fvm_slice_size = 32'768; |
| options.initial_fvm_slice_count = 5120; // Leaves 32 MiB for FVM & FTL metadata. |
| } |
| std::random_device random; |
| std::uniform_int_distribution distribution(1300, 2300); |
| options.fail_after = distribution(random); |
| options.ram_disk_discard_random_after_last_flush = true; |
| |
| { |
| auto fs_or = TestFilesystem::Create(options); |
| ASSERT_TRUE(fs_or.is_ok()) << fs_or.status_string(); |
| TestFilesystem fs = std::move(fs_or).value(); |
| |
| const std::string file1 = fs.mount_path() + "/file1"; |
| const std::string file2 = fs.mount_path() + "/file2"; |
| for (;;) { |
| { |
| fbl::unique_fd fd(open(file1.c_str(), O_RDWR | O_CREAT, 0644)); |
| if (!fd || write(fd.get(), "hello", 5) != 5 || ftruncate(fd.get(), 0) != 0 || |
| fsync(fd.get()) != 0 || unlink(file1.c_str()) != 0) { |
| break; |
| } |
| } |
| { |
| fbl::unique_fd fd(open(file2.c_str(), O_RDWR | O_CREAT, 0644)); |
| if (!fd || write(fd.get(), "hello", 5) != 5 || ftruncate(fd.get(), 0) != 0 || |
| fsync(fd.get()) != 0 || unlink(file2.c_str()) != 0) { |
| break; |
| } |
| } |
| } |
| |
| ASSERT_EQ(fs.Unmount().status_value(), ZX_OK); |
| ASSERT_EQ(fs.GetRamDisk()->Wake().status_value(), ZX_OK); |
| } |
| |
| std::cout << "Remounting" << std::endl; |
| options.fail_after = 0; |
| auto fs_or = TestFilesystem::Open(options); |
| ASSERT_TRUE(fs_or.is_ok()) << fs_or.status_string(); |
| TestFilesystem fs = std::move(fs_or).value(); |
| EXPECT_EQ(fs.Unmount().status_value(), ZX_OK); |
| EXPECT_EQ(fs.Fsck().status_value(), ZX_OK); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| /*no prefix*/, CorruptTest, |
| testing::ValuesIn(MapAndFilterAllTestFilesystems( |
| [](const TestFilesystemOptions& options) -> std::optional<TestFilesystemOptions> { |
| if (options.filesystem->GetTraits().is_journaled) { |
| return options; |
| } else { |
| return std::nullopt; |
| } |
| })), |
| testing::PrintToStringParamName()); |
| |
| GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(CorruptTest); |
| |
| } // namespace |
| } // namespace fs_test |