blob: a934f2befac3a77ba21b86d0408ba03cf24564bb [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 "src/storage/volume_image/adapter/mtd_writer.h"
#include <fcntl.h>
#include <lib/fit/result.h>
#include <string>
#include <string_view>
#include <fbl/unique_fd.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "src/storage/volume_image/ftl/ftl_io.h"
#include "src/storage/volume_image/fvm/fvm_descriptor.h"
#include "src/storage/volume_image/fvm/fvm_sparse_image.h"
#include "src/storage/volume_image/utils/block_utils.h"
#include "src/storage/volume_image/utils/fd_reader.h"
#include "src/storage/volume_image/utils/fd_test_helper.h"
#include "src/storage/volume_image/utils/fd_writer.h"
namespace storage::volume_image {
namespace {
// To run this test locally on a linux machine:
// * sudo modprobe nandsim id_bytes=0x2c,0xdc,0x90,0xa6,0x54,0x0 badblocks=5
// * chmod u=rw,og=rw /dev/mtd0
// chmod is required so fx test may run the test for you.
constexpr const char* kTestMtdDevicePath = "/dev/mtd0";
constexpr std::string_view kFvmSparseImagePath =
STORAGE_VOLUME_IMAGE_ADAPTER_TEST_IMAGE_PATH "test_fvm.sparse.blk";
fit::result<void, std::string> ReadUnalignedBlock(uint64_t offset, fbl::Span<uint8_t> data,
fbl::Span<uint8_t> block_buffer, Reader& reader) {
uint64_t read_bytes = 0;
while (read_bytes < data.size()) {
uint64_t current_offset = offset + read_bytes;
uint64_t block_offset =
GetBlockFromBytes(current_offset, block_buffer.size()) * block_buffer.size();
if (auto result = reader.Read(block_offset, block_buffer); result.is_error()) {
return result;
}
uint64_t bytes_in_buffer = block_offset + block_buffer.size() - current_offset;
if (bytes_in_buffer > data.size() - read_bytes) {
bytes_in_buffer = data.size() - read_bytes;
}
memcpy(data.data() + read_bytes, block_buffer.data(), bytes_in_buffer);
read_bytes += bytes_in_buffer;
}
return fit::ok();
}
TEST(MtdWriterTest, WriteContentsAreOk) {
// check that the mtd device and the block device are both present.
fbl::unique_fd mtd_fd(open(kTestMtdDevicePath, O_RDWR));
if (!mtd_fd.is_valid()) {
GTEST_SKIP() << "No MTD device availble. " << strerror(errno);
}
MtdParams params;
params.offset = 0;
params.max_bad_blocks = 5;
FtlHandle handle;
auto mtd_writer_or = CreateMtdWriter(kTestMtdDevicePath, params, &handle);
ASSERT_TRUE(mtd_writer_or.is_ok()) << mtd_writer_or.error();
ASSERT_TRUE(mtd_writer_or.is_ok()) << mtd_writer_or.error();
auto mtd_writer = mtd_writer_or.take_value();
std::vector<uint8_t> data = {1, 12, 123};
std::vector<uint8_t> actual_data;
actual_data.resize(data.size());
// This must be initialized after the previous write, so the FTL can be initialized from disk.
auto reader = handle.MakeReader();
std::vector<uint8_t> block_buffer;
block_buffer.resize(handle.instance().page_size(), 0);
{
auto write_result = mtd_writer->Write(0, data);
ASSERT_TRUE(write_result.is_ok()) << write_result.error();
auto read_result = ReadUnalignedBlock(0, actual_data, block_buffer, *reader);
ASSERT_TRUE(read_result.is_ok()) << read_result.error();
EXPECT_THAT(actual_data, testing::ElementsAreArray(data));
}
{
uint64_t offset = handle.instance().page_size() - 1;
auto write_result = mtd_writer->Write(offset, data);
ASSERT_TRUE(write_result.is_ok()) << write_result.error();
memset(block_buffer.data(), 0, block_buffer.size());
auto read_result = ReadUnalignedBlock(offset, actual_data, block_buffer, *reader);
ASSERT_TRUE(read_result.is_ok()) << read_result.error();
EXPECT_THAT(actual_data, testing::ElementsAreArray(data));
}
{
uint64_t offset = handle.instance().page_size();
data.resize(2 * handle.instance().page_size());
for (size_t i = 0; i < data.size(); ++i) {
data[i] = static_cast<uint8_t>((i + 3) % 256);
}
actual_data.resize(data.size());
auto write_result = mtd_writer->Write(offset, data);
ASSERT_TRUE(write_result.is_ok()) << write_result.error();
memset(block_buffer.data(), 0, block_buffer.size());
auto read_result = reader->Read(offset, actual_data);
ASSERT_TRUE(read_result.is_ok()) << read_result.error();
EXPECT_TRUE(memcmp(actual_data.data(), data.data(), data.size()) == 0);
}
}
TEST(MtdWriterTest, WriteFvmIsOk) {
// check that the mtd device and the block device are both present.
fbl::unique_fd mtd_fd(open(kTestMtdDevicePath, O_RDWR));
if (!mtd_fd.is_valid()) {
GTEST_SKIP() << "No MTD device availble. " << strerror(errno);
}
MtdParams params;
params.offset = 0;
params.max_bad_blocks = 5;
FtlHandle handle;
auto mtd_writer_or = CreateMtdWriter(kTestMtdDevicePath, params, &handle);
ASSERT_TRUE(mtd_writer_or.is_ok()) << mtd_writer_or.error();
// Read the compressed sparse image.
auto compressed_sparse_reader_or = FdReader::Create(kFvmSparseImagePath);
ASSERT_TRUE(compressed_sparse_reader_or.is_ok()) << compressed_sparse_reader_or.error();
FdReader compressed_sparse_reader = compressed_sparse_reader_or.take_value();
// Decompress the image.
auto decompressed_image_or = TempFile::Create();
ASSERT_TRUE(decompressed_image_or.is_ok()) << decompressed_image_or.error();
auto decompressed_image = decompressed_image_or.take_value();
auto decompressed_writer_or = FdWriter::Create(decompressed_image.path());
ASSERT_TRUE(decompressed_writer_or.is_ok()) << decompressed_writer_or.error();
auto decompressed_writer = decompressed_writer_or.take_value();
auto decompress_result =
FvmSparseDecompressImage(0, compressed_sparse_reader, decompressed_writer);
ASSERT_TRUE(decompress_result.is_ok()) << decompress_result.error();
ASSERT_TRUE(decompress_result.value());
auto decompressed_reader_or = FdReader::Create(decompressed_image.path());
ASSERT_TRUE(decompressed_reader_or.is_ok()) << decompressed_reader_or.error();
auto decompressed_reader = std::make_unique<FdReader>(decompressed_reader_or.take_value());
// Write the contents to the mtd device.
auto fvm_descriptor_or = FvmSparseReadImage(0, std::move(decompressed_reader));
ASSERT_TRUE(fvm_descriptor_or.is_ok()) << fvm_descriptor_or.error();
auto fvm_descriptor = fvm_descriptor_or.take_value();
auto write_result = fvm_descriptor.WriteBlockImage(*mtd_writer_or.value());
ASSERT_TRUE(write_result.is_ok()) << write_result.error();
// Write the contents into a file and compare each pair of blocks.
auto expected_image_or = TempFile::Create();
ASSERT_TRUE(expected_image_or.is_ok()) << expected_image_or.error();
auto expected_image = expected_image_or.take_value();
auto expected_image_writer_or = FdWriter::Create(expected_image.path());
ASSERT_TRUE(expected_image_writer_or.is_ok()) << expected_image_writer_or.error();
auto expected_image_writer = expected_image_writer_or.take_value();
auto expected_write_result = fvm_descriptor.WriteBlockImage(expected_image_writer);
ASSERT_TRUE(expected_write_result.is_ok()) << expected_write_result.error();
auto expected_image_reader_or = FdReader::Create(expected_image.path());
ASSERT_TRUE(expected_image_reader_or.is_ok()) << expected_image_reader_or.error();
auto expected_image_reader = expected_image_reader_or.take_value();
uint64_t read_bytes = 0;
std::vector<uint8_t> buffer;
buffer.resize(handle.instance().page_size());
std::vector<uint8_t> actual_buffer;
actual_buffer.resize(handle.instance().page_size());
std::vector<uint8_t> page_buffer;
page_buffer.resize(handle.instance().page_size());
auto actual_reader = handle.MakeReader();
while (read_bytes < expected_image_reader.length()) {
uint64_t bytes_to_read = buffer.size();
if (bytes_to_read > expected_image_reader.length() - read_bytes) {
bytes_to_read = expected_image_reader.length() - read_bytes;
}
auto expected_result = expected_image_reader.Read(read_bytes, buffer);
ASSERT_TRUE(expected_result.is_ok()) << expected_result.error();
// Read same range from the FTL.
auto actual_result =
ReadUnalignedBlock(read_bytes, fbl::Span<uint8_t>(actual_buffer).subspan(0, bytes_to_read),
page_buffer, *actual_reader);
ASSERT_TRUE(actual_result.is_ok()) << actual_result.error();
EXPECT_TRUE(memcmp(buffer.data(), actual_buffer.data(), bytes_to_read) == 0);
read_bytes += bytes_to_read;
}
}
} // namespace
} // namespace storage::volume_image