blob: 064221c803cb91e8ba5181c494c1e754c1f1f838 [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 "src/storage/volume_image/fvm/fvm_sparse_image_reader.h"
#include <fidl/fuchsia.device/cpp/wire.h>
#include <lib/component/incoming/cpp/protocol.h>
#include <lib/device-watcher/cpp/device-watcher.h>
#include <lib/fdio/fdio.h>
#include <lib/fzl/resizeable-vmo-mapper.h>
#include <zircon/hw/gpt.h>
#include <fbl/unique_fd.h>
#include <gtest/gtest.h>
#include "src/storage/fvm/format.h"
#include "src/storage/fvm/fvm_sparse.h"
#include "src/storage/lib/block_client/cpp/remote_block_device.h"
#include "src/storage/lib/fs_management/cpp/admin.h"
#include "src/storage/lib/fs_management/cpp/fvm.h"
#include "src/storage/testing/fvm.h"
#include "src/storage/testing/ram_disk.h"
#include "src/storage/volume_image/ftl/ftl_image.h"
#include "src/storage/volume_image/ftl/options.h"
#include "src/storage/volume_image/utils/fd_reader.h"
#include "src/storage/volume_image/utils/writer.h"
namespace storage::volume_image {
namespace {
constexpr std::string_view sparse_image_path = "/pkg/data/test_fvm.sparse.blk";
zx::result<std::string> AttachFvm(const std::string& device_path) {
std::string controller_path = device_path + "/device_controller";
zx::result controller = component::Connect<fuchsia_device::Controller>(controller_path);
if (controller.is_error()) {
return controller.take_error();
}
if (auto status = storage::BindFvm(controller.value()); status.is_error())
return status.take_error();
std::string fvm_disk_path = device_path + "/fvm";
if (zx::result channel = device_watcher::RecursiveWaitForFile(fvm_disk_path.c_str(), zx::sec(3));
channel.is_error()) {
return channel.take_error();
}
return zx::ok(fvm_disk_path);
}
// Create a ram-disk and copy the output directly into the ram-disk, and then see if FVM can read
// it and minfs Fsck passes.
TEST(FvmSparseImageReaderTest, PartitionsInImagePassFsck) {
auto base_reader_or = FdReader::Create(sparse_image_path);
ASSERT_TRUE(base_reader_or.is_ok()) << base_reader_or.error();
auto sparse_image_or = OpenSparseImage(base_reader_or.value(), std::nullopt);
ASSERT_TRUE(sparse_image_or.is_ok()) << sparse_image_or.error();
// Create a ram-disk.
constexpr int kDeviceBlockSize = 8192;
const uint64_t disk_size = sparse_image_or.value().volume().size;
auto ram_disk_or = storage::RamDisk::Create(kDeviceBlockSize, disk_size / kDeviceBlockSize);
ASSERT_TRUE(ram_disk_or.is_ok()) << ram_disk_or.status_string();
// Open the ram disk
zx::result channel =
component::Connect<fuchsia_hardware_block_volume::Volume>(ram_disk_or.value().path());
ASSERT_TRUE(channel.is_ok()) << channel.status_string();
zx::result device = block_client::RemoteBlockDevice::Create(std::move(channel.value()));
ASSERT_TRUE(device.is_ok()) << device.status_string();
std::unique_ptr<block_client::RemoteBlockDevice> client = std::move(device.value());
constexpr uint64_t kInitialVmoSize = 1048576;
auto vmo = fzl::ResizeableVmoMapper::Create(kInitialVmoSize, "test");
ASSERT_TRUE(vmo);
storage::Vmoid vmoid;
ASSERT_EQ(client->BlockAttachVmo(vmo->vmo(), &vmoid), ZX_OK);
// This is a test, so we don't need to worry about cleaning it up.
vmoid_t vmo_id = vmoid.TakeId();
memset(vmo->start(), 0xaf, kInitialVmoSize);
// Initialize the entire ramdisk with a filler (that isn't zero).
for (uint64_t offset = 0; offset < disk_size; offset += kInitialVmoSize) {
block_fifo_request_t request = {
.command = {.opcode = BLOCK_OPCODE_WRITE, .flags = 0},
.vmoid = vmo_id,
.length =
static_cast<uint32_t>(std::min(disk_size - offset, kInitialVmoSize) / kDeviceBlockSize),
.dev_offset = offset / kDeviceBlockSize};
ASSERT_EQ(client->FifoTransaction(&request, 1), ZX_OK);
}
for (const AddressMap& map : sparse_image_or.value().address().mappings) {
ASSERT_EQ(map.count % kDeviceBlockSize, 0u);
ASSERT_EQ(map.target % kDeviceBlockSize, 0u);
ASSERT_LT(map.target, disk_size);
ASSERT_LE(map.target + map.count, disk_size);
EXPECT_TRUE(map.options.empty());
if (vmo->size() < map.count) {
ASSERT_EQ(vmo->Grow(map.count), ZX_OK);
}
auto result = sparse_image_or.value().reader()->Read(
map.source, cpp20::span<uint8_t>(reinterpret_cast<uint8_t*>(vmo->start()), map.count));
ASSERT_TRUE(result.is_ok()) << result.error();
// Write the mapping to the ram disk.
block_fifo_request_t request = {
.command = {.opcode = BLOCK_OPCODE_WRITE, .flags = 0},
.vmoid = vmo_id,
.length = static_cast<uint32_t>(map.count / kDeviceBlockSize),
.dev_offset = map.target / kDeviceBlockSize,
};
ASSERT_EQ(client->FifoTransaction(&request, 1), ZX_OK)
<< "length=" << request.length << ", dev_offset=" << request.dev_offset;
}
client.reset();
// Now try and attach FVM.
auto result = AttachFvm(ram_disk_or.value().path());
ASSERT_TRUE(result.is_ok()) << result.status_string();
fs_management::PartitionMatcher matcher{
.type_guids = {GUID_DATA_VALUE},
};
ASSERT_EQ(fs_management::OpenPartition(matcher, true).status_value(), ZX_OK);
// Attempt to fsck minfs.
{
zx::result partition = fs_management::OpenPartition(matcher, true);
ASSERT_EQ(ZX_OK, partition.status_value());
fidl::WireResult path = fidl::WireCall(partition.value())->GetTopologicalPath();
ASSERT_EQ(ZX_OK, path.status());
// And finally run fsck on the volume.
fs_management::FsckOptions options{
.verbose = false,
.never_modify = true,
.always_modify = false,
.force = true,
};
auto component = fs_management::FsComponent::FromDiskFormat(fs_management::kDiskFormatMinfs);
EXPECT_EQ(fs_management::Fsck(path->value()->path.get(), component, options), ZX_OK);
}
// Attempt to fsck blobfs.
{
fs_management::PartitionMatcher matcher{
.type_guids = {GUID_BLOB_VALUE},
};
zx::result partition = fs_management::OpenPartition(matcher, true);
ASSERT_EQ(ZX_OK, partition.status_value());
fidl::WireResult path = fidl::WireCall(partition.value())->GetTopologicalPath();
ASSERT_EQ(ZX_OK, path.status());
ASSERT_EQ(fs_management::OpenPartition(matcher, true).status_value(), ZX_OK);
// And finally run fsck on the volume.
fs_management::FsckOptions options{
.verbose = false,
.never_modify = true,
.always_modify = false,
.force = true,
};
auto component = fs_management::FsComponent::FromDiskFormat(fs_management::kDiskFormatBlobfs);
EXPECT_EQ(fs_management::Fsck(path->value()->path.get(), component, options), ZX_OK);
}
}
class NullWriter : public Writer {
public:
fpromise::result<void, std::string> Write(uint64_t offset,
cpp20::span<const uint8_t> buffer) override {
return fpromise::ok();
}
};
TEST(FvmSparseImageReaderTest, ImageWithMaxSizeAllocatesEnoughMetadata) {
auto base_reader_or = FdReader::Create(sparse_image_path);
ASSERT_TRUE(base_reader_or.is_ok()) << base_reader_or.error();
fvm::SparseImage image = {};
auto image_stream = cpp20::span<uint8_t>(reinterpret_cast<uint8_t*>(&image), sizeof(image));
auto read_result = base_reader_or.value().Read(0, image_stream);
ASSERT_TRUE(read_result.is_ok()) << read_result.error();
ASSERT_EQ(image.magic, fvm::kSparseFormatMagic);
auto sparse_image_or = OpenSparseImage(base_reader_or.value(), 300 << 20);
ASSERT_TRUE(sparse_image_or.is_ok()) << sparse_image_or.error();
auto sparse_image = sparse_image_or.take_value();
auto expected_header =
fvm::Header::FromDiskSize(fvm::kMaxUsablePartitions, 300 << 20, image.slice_size);
fvm::Header header = {};
auto header_stream = cpp20::span<uint8_t>(reinterpret_cast<uint8_t*>(&header), sizeof(header));
read_result = sparse_image.reader()->Read(
sparse_image.reader()->length() - expected_header.GetMetadataAllocatedBytes(), header_stream);
ASSERT_TRUE(read_result.is_ok()) << read_result.error();
ASSERT_EQ(header.magic, fvm::kMagic);
EXPECT_EQ(expected_header.GetMetadataAllocatedBytes(), header.GetMetadataAllocatedBytes());
}
// This doesn't test that the resulting image is valid, but it least tests that FtlImageWrite can
// consume the sparse image without complaining.
TEST(FvmSparseImageReaderTest, WriteFtlImageSucceeds) {
auto base_reader_or = FdReader::Create(sparse_image_path);
ASSERT_TRUE(base_reader_or.is_ok()) << base_reader_or.error();
auto sparse_image_or = OpenSparseImage(base_reader_or.value(), std::nullopt);
ASSERT_TRUE(sparse_image_or.is_ok()) << sparse_image_or.error();
constexpr int kFtlPageSize = 8192;
NullWriter writer;
auto result = FtlImageWrite(
RawNandOptions{
.page_size = kFtlPageSize,
.page_count = static_cast<uint32_t>(sparse_image_or.value().volume().size / kFtlPageSize),
.pages_per_block = 32,
.oob_bytes_size = 16,
},
sparse_image_or.value(), &writer);
EXPECT_TRUE(result.is_ok()) << result.error();
}
} // namespace
} // namespace storage::volume_image