blob: 3d752b60bdb2f46caa964613ed0afe9257525488 [file] [log] [blame]
// 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