blob: f392b1950ce81fc3a0e11e247080e8a1809b3430 [file] [log] [blame]
// Copyright 2021 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 <zircon/errors.h>
#include <block-client/cpp/fake-device.h>
#include <gtest/gtest.h>
#include "src/storage/blobfs/blobfs.h"
#include "src/storage/blobfs/fsck.h"
#include "src/storage/blobfs/mkfs.h"
#include "src/storage/blobfs/test/blob_utils.h"
namespace blobfs {
namespace {
using ::block_client::BlockDevice;
using ::block_client::FakeBlockDevice;
using ::block_client::FakeFVMBlockDevice;
constexpr uint32_t kBlockSize = 512;
constexpr uint32_t kNumBlocks = 400 * kBlobfsBlockSize / kBlockSize;
std::unique_ptr<BlockDevice> CreateFakeBlockDevice(uint64_t num_blocks) {
return std::make_unique<FakeBlockDevice>(num_blocks, kBlockSize);
}
std::unique_ptr<BlockDevice> CreateFakeFVMBlockDevice(uint64_t num_blocks) {
return std::make_unique<FakeFVMBlockDevice>(num_blocks, kBlockSize, /*slice_size=*/32768,
/*slice_capacity=*/500);
}
template <uint64_t oldest_minor_version,
std::unique_ptr<BlockDevice> (*DeviceFactory)(uint64_t) = CreateFakeBlockDevice,
uint64_t num_blocks = kNumBlocks>
class BlobfsTestAtMinorVersion : public testing::Test {
public:
void SetUp() final {
loop_.StartThread();
srand(testing::UnitTest::GetInstance()->random_seed());
}
std::unique_ptr<BlockDevice> CreateAndFormat() {
FilesystemOptions options{.blob_layout_format = BlobLayoutFormat::kCompactMerkleTreeAtEnd,
.oldest_minor_version = oldest_minor_version};
std::unique_ptr<BlockDevice> device = DeviceFactory(num_blocks);
EXPECT_EQ(FormatFilesystem(device.get(), options), ZX_OK);
return device;
}
void Mount(std::unique_ptr<BlockDevice> device, const MountOptions& options) {
ASSERT_EQ(fs_, nullptr);
auto blobfs_or = Blobfs::Create(loop_.dispatcher(), std::move(device), options);
ASSERT_TRUE(blobfs_or.is_ok());
fs_ = std::move(blobfs_or.value());
}
std::unique_ptr<BlockDevice> Unmount() { return Blobfs::Destroy(std::move(fs_)); }
MountOptions ReadOnlyOptions() const {
return MountOptions{.writability = Writability::ReadOnlyDisk};
}
MountOptions ReadWriteOptions() const {
return MountOptions{.writability = Writability::Writable};
}
protected:
async::Loop loop_{&kAsyncLoopConfigNoAttachToCurrentThread};
std::unique_ptr<Blobfs> fs_;
};
using BlobfsTestAtRev1 = BlobfsTestAtMinorVersion<kBlobfsMinorVersionBackupSuperblock - 1>;
using BlobfsTestAtRev1WithFvm =
BlobfsTestAtMinorVersion<kBlobfsMinorVersionBackupSuperblock - 1, CreateFakeFVMBlockDevice>;
using BlobfsTestAtRev2 = BlobfsTestAtMinorVersion<kBlobfsMinorVersionBackupSuperblock>;
using BlobfsTestAtRev3 = BlobfsTestAtMinorVersion<kBlobfsMinorVersionNoOldCompressionFormats>;
using BlobfsTestAtRev4 =
BlobfsTestAtMinorVersion<kBlobfsMinorVersionHostToolHandlesNullBlobCorrectly>;
using BlobfsTestAtFutureRev = BlobfsTestAtMinorVersion<kBlobfsCurrentMinorVersion + 1>;
TEST_F(BlobfsTestAtRev1, UpgradedToLaterMinorVersion) {
Mount(CreateAndFormat(), ReadWriteOptions());
auto device = Unmount();
// Read the superblock, verify the oldest revision is set to the current revision
// This involves three migration steps:
// - 1->2 (NOP, since FVM is disabled so there's no backup superblock)
// - 2->3 (Overwrite blobs in old compression formats)
// - 3->4 (Fix zero-length extent in the null blob)
uint8_t block[kBlobfsBlockSize] = {};
static_assert(sizeof(block) >= sizeof(Superblock));
ASSERT_EQ(device->ReadBlock(0, kBlobfsBlockSize, &block), ZX_OK);
Superblock* info = reinterpret_cast<Superblock*>(block);
EXPECT_EQ(info->oldest_minor_version, kBlobfsMinorVersionHostToolHandlesNullBlobCorrectly);
ASSERT_EQ(Fsck(std::move(device), ReadOnlyOptions()), ZX_OK);
}
TEST_F(BlobfsTestAtRev1WithFvm, OldInstanceTriggersWriteToBackupSuperblock) {
Mount(CreateAndFormat(), ReadWriteOptions());
ASSERT_TRUE(fs_->Info().flags & kBlobFlagFVM);
auto device = Unmount();
// Read the superblock, verify the oldest revision is set to the current revision
// This involves three migration steps:
// - 1->2 (Add backup superblock)
// - 2->3 (Overwrite blobs in old compression formats)
// - 3->4 (Fix zero-length extent in the null blob)
uint8_t block[kBlobfsBlockSize] = {};
static_assert(sizeof(block) >= sizeof(Superblock));
ASSERT_EQ(device->ReadBlock(0, kBlobfsBlockSize, &block), ZX_OK);
Superblock* info = reinterpret_cast<Superblock*>(block);
EXPECT_EQ(info->oldest_minor_version, kBlobfsMinorVersionHostToolHandlesNullBlobCorrectly);
ASSERT_EQ(Fsck(std::move(device), ReadOnlyOptions()), ZX_OK);
}
TEST_F(BlobfsTestAtRev2, UpgradedToLaterMinorVersion) {
Mount(CreateAndFormat(), ReadWriteOptions());
auto device = Unmount();
// Read the superblock, verify the oldest revision is set to the current revision
// This involves two migration steps:
// - 2->3 (Overwrite blobs in old compression formats)
// - 3->4 (Fix zero-length extent in the null blob)
uint8_t block[kBlobfsBlockSize] = {};
static_assert(sizeof(block) >= sizeof(Superblock));
ASSERT_EQ(device->ReadBlock(0, kBlobfsBlockSize, &block), ZX_OK);
Superblock* info = reinterpret_cast<Superblock*>(block);
EXPECT_EQ(info->oldest_minor_version, kBlobfsMinorVersionHostToolHandlesNullBlobCorrectly);
ASSERT_EQ(Fsck(std::move(device), ReadOnlyOptions()), ZX_OK);
}
TEST_F(BlobfsTestAtRev3, UpgradedToLaterMinorVersion) {
Mount(CreateAndFormat(), ReadWriteOptions());
auto device = Unmount();
// Read the superblock, verify the oldest revision is set to the current revision
// This involves two migration steps:
// - 3->4 (Fix zero-length extent in the null blob)
uint8_t block[kBlobfsBlockSize] = {};
static_assert(sizeof(block) >= sizeof(Superblock));
ASSERT_EQ(device->ReadBlock(0, kBlobfsBlockSize, &block), ZX_OK);
Superblock* info = reinterpret_cast<Superblock*>(block);
EXPECT_EQ(info->oldest_minor_version, kBlobfsMinorVersionHostToolHandlesNullBlobCorrectly);
ASSERT_EQ(Fsck(std::move(device), ReadOnlyOptions()), ZX_OK);
}
TEST_F(BlobfsTestAtRev4, NotUpgraded) {
Mount(CreateAndFormat(), ReadWriteOptions());
auto device = Unmount();
// Read the superblock, verify the oldest revision is unmodified
uint8_t block[kBlobfsBlockSize] = {};
static_assert(sizeof(block) >= sizeof(Superblock));
ASSERT_EQ(device->ReadBlock(0, kBlobfsBlockSize, &block), ZX_OK);
Superblock* info = reinterpret_cast<Superblock*>(block);
EXPECT_EQ(info->oldest_minor_version, kBlobfsMinorVersionHostToolHandlesNullBlobCorrectly);
ASSERT_EQ(Fsck(std::move(device), ReadOnlyOptions()), ZX_OK);
}
TEST_F(BlobfsTestAtFutureRev, OldestMinorVersionSetToDriverMinorVersion) {
Mount(CreateAndFormat(), ReadWriteOptions());
auto device = Unmount();
// Read the superblock, verify the oldest revision is set to the current revision
uint8_t block[kBlobfsBlockSize] = {};
static_assert(sizeof(block) >= sizeof(Superblock));
ASSERT_EQ(device->ReadBlock(0, kBlobfsBlockSize, &block), ZX_OK);
Superblock* info = reinterpret_cast<Superblock*>(block);
EXPECT_EQ(info->oldest_minor_version, kBlobfsCurrentMinorVersion);
ASSERT_EQ(Fsck(std::move(device), ReadOnlyOptions()), ZX_OK);
}
} // namespace
} // namespace blobfs