blob: 878b07c4b71defb4da59f0f2e061e7042b59352b [file] [log] [blame]
// Copyright 2019 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/lib/paver/partition-client.h"
#include <lib/component/incoming/cpp/clone.h>
#include <lib/driver-integration-test/fixture.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/fdio.h>
#include <string.h>
#include <zircon/errors.h>
#include <zircon/hw/gpt.h>
#include <iostream>
#include <memory>
#include <vector>
#include <zxtest/zxtest.h>
#include "src/storage/lib/paver/test/test-utils.h"
#include "src/storage/lib/paver/utils.h"
namespace {
using device_watcher::RecursiveWaitForFile;
using driver_integration_test::IsolatedDevmgr;
using paver::BlockWatcherPauser;
class FakePartitionClient final : public paver::PartitionClient {
public:
explicit FakePartitionClient(size_t block_size, size_t partition_size)
: block_size_(block_size), partition_size_(partition_size) {}
zx::result<size_t> GetBlockSize() final {
if (result_ == ZX_OK) {
return zx::ok(block_size_);
}
return zx::error(result_);
}
zx::result<size_t> GetPartitionSize() final {
if (result_ == ZX_OK) {
return zx::ok(partition_size_);
}
return zx::error(result_);
}
zx::result<> Read(const zx::vmo& vmo, size_t size) final {
read_called_ = true;
if (size > partition_size_) {
return zx::error(ZX_ERR_OUT_OF_RANGE);
}
return zx::make_result(result_);
}
zx::result<> Write(const zx::vmo& vmo, size_t vmo_size) final {
write_called_ = true;
if (vmo_size > partition_size_) {
return zx::error(ZX_ERR_OUT_OF_RANGE);
}
return zx::make_result(result_);
}
zx::result<> Trim() final {
trim_called_ = true;
return zx::make_result(result_);
}
zx::result<> Flush() final {
flush_called_ = true;
return zx::make_result(result_);
}
bool read_called() { return read_called_; }
bool write_called() { return write_called_; }
bool trim_called() { return trim_called_; }
bool flush_called() { return flush_called_; }
void set_result(zx_status_t result) { result_ = result; }
private:
size_t block_size_;
size_t partition_size_;
bool read_called_ = false;
bool write_called_ = false;
bool trim_called_ = false;
bool flush_called_ = false;
zx_status_t result_ = ZX_OK;
};
TEST(PartitionCopyClientTest, ConstructEmpty) { paver::PartitionCopyClient({}); }
TEST(PartitionCopyClientTest, ConstructSinglePartition) {
std::vector<std::unique_ptr<paver::PartitionClient>> partitions;
auto fake = std::make_unique<FakePartitionClient>(10, 100);
partitions.push_back(std::move(fake));
paver::PartitionCopyClient client(std::move(partitions));
}
TEST(PartitionCopyClientTest, GetBlockSizeSinglePartition) {
std::vector<std::unique_ptr<paver::PartitionClient>> partitions;
auto fake = std::make_unique<FakePartitionClient>(10, 100);
auto fake_ref = fake.get();
partitions.push_back(std::move(fake));
paver::PartitionCopyClient client(std::move(partitions));
auto status = client.GetBlockSize();
ASSERT_OK(status);
ASSERT_EQ(status.value(), 10);
fake_ref->set_result(ZX_ERR_ACCESS_DENIED);
ASSERT_NOT_OK(client.GetBlockSize());
}
TEST(PartitionCopyClientTest, GetPartitionSizeSinglePartition) {
std::vector<std::unique_ptr<paver::PartitionClient>> partitions;
auto fake = std::make_unique<FakePartitionClient>(10, 100);
auto fake_ref = fake.get();
partitions.push_back(std::move(fake));
paver::PartitionCopyClient client(std::move(partitions));
auto status = client.GetPartitionSize();
ASSERT_OK(status);
ASSERT_EQ(status.value(), 100);
fake_ref->set_result(ZX_ERR_ACCESS_DENIED);
ASSERT_NOT_OK(client.GetPartitionSize());
}
TEST(PartitionCopyClientTest, ReadSinglePartition) {
std::vector<std::unique_ptr<paver::PartitionClient>> partitions;
auto fake = std::make_unique<FakePartitionClient>(10, 100);
auto fake_ref = fake.get();
partitions.push_back(std::move(fake));
paver::PartitionCopyClient client(std::move(partitions));
zx::vmo vmo;
ASSERT_OK(client.Read(vmo, 0));
ASSERT_TRUE(fake_ref->read_called());
fake_ref->set_result(ZX_ERR_ACCESS_DENIED);
ASSERT_NOT_OK(client.Read(vmo, 0));
}
TEST(PartitionCopyClientTest, WriteSinglePartition) {
std::vector<std::unique_ptr<paver::PartitionClient>> partitions;
auto fake = std::make_unique<FakePartitionClient>(10, 100);
auto fake_ref = fake.get();
partitions.push_back(std::move(fake));
paver::PartitionCopyClient client(std::move(partitions));
zx::vmo vmo;
ASSERT_OK(client.Write(vmo, 0));
ASSERT_TRUE(fake_ref->write_called());
ASSERT_FALSE(fake_ref->trim_called());
fake_ref->set_result(ZX_ERR_ACCESS_DENIED);
ASSERT_NOT_OK(client.Write(vmo, 0));
ASSERT_TRUE(fake_ref->trim_called());
}
TEST(PartitionCopyClientTest, TrimSinglePartition) {
std::vector<std::unique_ptr<paver::PartitionClient>> partitions;
auto fake = std::make_unique<FakePartitionClient>(10, 100);
auto fake_ref = fake.get();
partitions.push_back(std::move(fake));
paver::PartitionCopyClient client(std::move(partitions));
zx::vmo vmo;
ASSERT_OK(client.Trim());
ASSERT_TRUE(fake_ref->trim_called());
fake_ref->set_result(ZX_ERR_NOT_SUPPORTED);
ASSERT_NOT_OK(client.Trim());
}
TEST(PartitionCopyClientTest, FlushSinglePartition) {
std::vector<std::unique_ptr<paver::PartitionClient>> partitions;
auto fake = std::make_unique<FakePartitionClient>(10, 100);
auto fake_ref = fake.get();
partitions.push_back(std::move(fake));
paver::PartitionCopyClient client(std::move(partitions));
zx::vmo vmo;
ASSERT_OK(client.Flush());
ASSERT_TRUE(fake_ref->flush_called());
fake_ref->set_result(ZX_ERR_ACCESS_DENIED);
ASSERT_NOT_OK(client.Flush());
}
TEST(PartitionCopyClientTest, GetChannelSinglePartition) {
std::vector<std::unique_ptr<paver::PartitionClient>> partitions;
auto fake = std::make_unique<FakePartitionClient>(10, 100);
partitions.push_back(std::move(fake));
paver::PartitionCopyClient client(std::move(partitions));
}
TEST(PartitionCopyClientTest, BlockFdSinglePartition) {
std::vector<std::unique_ptr<paver::PartitionClient>> partitions;
auto fake = std::make_unique<FakePartitionClient>(10, 100);
partitions.push_back(std::move(fake));
paver::PartitionCopyClient client(std::move(partitions));
}
TEST(PartitionCopyClientTest, ConstructMultiplePartitions) {
std::vector<std::unique_ptr<paver::PartitionClient>> partitions;
auto fake = std::make_unique<FakePartitionClient>(10, 100);
auto fake2 = std::make_unique<FakePartitionClient>(7, 90);
partitions.push_back(std::move(fake));
partitions.push_back(std::move(fake2));
paver::PartitionCopyClient client(std::move(partitions));
}
TEST(PartitionCopyClientTest, GetBlockSizeMultiplePartitions) {
std::vector<std::unique_ptr<paver::PartitionClient>> partitions;
auto fake = std::make_unique<FakePartitionClient>(10, 100);
auto fake2 = std::make_unique<FakePartitionClient>(7, 90);
auto fake_ref = fake.get();
auto fake_ref2 = fake2.get();
partitions.push_back(std::move(fake));
partitions.push_back(std::move(fake2));
paver::PartitionCopyClient client(std::move(partitions));
auto status = client.GetBlockSize();
ASSERT_OK(status);
ASSERT_EQ(status.value(), 70);
fake_ref->set_result(ZX_ERR_ACCESS_DENIED);
status = client.GetBlockSize();
ASSERT_OK(status);
ASSERT_EQ(status.value(), 7);
fake_ref2->set_result(ZX_ERR_ACCESS_DENIED);
ASSERT_NOT_OK(client.GetBlockSize());
}
TEST(PartitionCopyClientTest, GetPartitionSizeMultiplePartitions) {
std::vector<std::unique_ptr<paver::PartitionClient>> partitions;
auto fake = std::make_unique<FakePartitionClient>(10, 100);
auto fake2 = std::make_unique<FakePartitionClient>(7, 90);
auto fake_ref = fake.get();
auto fake_ref2 = fake2.get();
partitions.push_back(std::move(fake));
partitions.push_back(std::move(fake2));
paver::PartitionCopyClient client(std::move(partitions));
auto status = client.GetPartitionSize();
ASSERT_OK(status);
ASSERT_EQ(status.value(), 90);
fake_ref2->set_result(ZX_ERR_ACCESS_DENIED);
status = client.GetPartitionSize();
ASSERT_OK(status);
ASSERT_EQ(status.value(), 100);
fake_ref->set_result(ZX_ERR_ACCESS_DENIED);
ASSERT_NOT_OK(client.GetPartitionSize());
}
TEST(PartitionCopyClientTest, ReadMultiplePartitions) {
std::vector<std::unique_ptr<paver::PartitionClient>> partitions;
auto fake = std::make_unique<FakePartitionClient>(10, 100);
auto fake2 = std::make_unique<FakePartitionClient>(7, 90);
auto fake_ref = fake.get();
auto fake_ref2 = fake2.get();
partitions.push_back(std::move(fake));
partitions.push_back(std::move(fake2));
paver::PartitionCopyClient client(std::move(partitions));
zx::vmo vmo;
ASSERT_OK(client.Read(vmo, 0));
ASSERT_TRUE(fake_ref->read_called());
ASSERT_FALSE(fake_ref2->read_called());
fake_ref->set_result(ZX_ERR_ACCESS_DENIED);
ASSERT_OK(client.Read(vmo, 0));
ASSERT_TRUE(fake_ref2->read_called());
fake_ref2->set_result(ZX_ERR_ACCESS_DENIED);
ASSERT_NOT_OK(client.Read(vmo, 0));
}
TEST(PartitionCopyClientTest, WriteMultiplePartitions) {
std::vector<std::unique_ptr<paver::PartitionClient>> partitions;
auto fake = std::make_unique<FakePartitionClient>(10, 100);
auto fake2 = std::make_unique<FakePartitionClient>(7, 90);
auto fake_ref = fake.get();
auto fake_ref2 = fake2.get();
partitions.push_back(std::move(fake));
partitions.push_back(std::move(fake2));
paver::PartitionCopyClient client(std::move(partitions));
zx::vmo vmo;
ASSERT_OK(client.Write(vmo, 0));
ASSERT_TRUE(fake_ref->write_called());
ASSERT_TRUE(fake_ref2->write_called());
ASSERT_FALSE(fake_ref->trim_called());
ASSERT_FALSE(fake_ref->trim_called());
fake_ref->set_result(ZX_ERR_ACCESS_DENIED);
ASSERT_OK(client.Write(vmo, 0));
ASSERT_TRUE(fake_ref->trim_called());
ASSERT_FALSE(fake_ref2->trim_called());
fake_ref2->set_result(ZX_ERR_ACCESS_DENIED);
ASSERT_NOT_OK(client.Write(vmo, 0));
}
TEST(PartitionCopyClientTest, TrimMultiplePartitions) {
std::vector<std::unique_ptr<paver::PartitionClient>> partitions;
auto fake = std::make_unique<FakePartitionClient>(10, 100);
auto fake2 = std::make_unique<FakePartitionClient>(7, 90);
auto fake_ref = fake.get();
auto fake_ref2 = fake2.get();
partitions.push_back(std::move(fake));
partitions.push_back(std::move(fake2));
paver::PartitionCopyClient client(std::move(partitions));
zx::vmo vmo;
ASSERT_OK(client.Trim());
ASSERT_TRUE(fake_ref->trim_called());
ASSERT_TRUE(fake_ref2->trim_called());
fake_ref->set_result(ZX_ERR_NOT_SUPPORTED);
ASSERT_NOT_OK(client.Trim());
}
TEST(PartitionCopyClientTest, FlushMultiplePartitions) {
std::vector<std::unique_ptr<paver::PartitionClient>> partitions;
auto fake = std::make_unique<FakePartitionClient>(10, 100);
auto fake2 = std::make_unique<FakePartitionClient>(7, 90);
auto fake_ref = fake.get();
auto fake_ref2 = fake2.get();
partitions.push_back(std::move(fake));
partitions.push_back(std::move(fake2));
paver::PartitionCopyClient client(std::move(partitions));
zx::vmo vmo;
ASSERT_OK(client.Flush());
ASSERT_TRUE(fake_ref->flush_called());
ASSERT_TRUE(fake_ref2->flush_called());
fake_ref->set_result(ZX_ERR_ACCESS_DENIED);
ASSERT_NOT_OK(client.Flush());
}
TEST(PartitionCopyClientTest, GetChannelMultiplePartitions) {
std::vector<std::unique_ptr<paver::PartitionClient>> partitions;
auto fake = std::make_unique<FakePartitionClient>(10, 100);
auto fake2 = std::make_unique<FakePartitionClient>(7, 90);
partitions.push_back(std::move(fake));
partitions.push_back(std::move(fake2));
paver::PartitionCopyClient client(std::move(partitions));
}
TEST(PartitionCopyClientTest, BlockFdMultilplePartition) {
std::vector<std::unique_ptr<paver::PartitionClient>> partitions;
auto fake = std::make_unique<FakePartitionClient>(10, 100);
auto fake2 = std::make_unique<FakePartitionClient>(7, 90);
partitions.push_back(std::move(fake));
partitions.push_back(std::move(fake2));
paver::PartitionCopyClient client(std::move(partitions));
}
class FixedOffsetBlockPartitionClientTest : public zxtest::Test {
public:
void SetUp() override {
IsolatedDevmgr::Args args;
args.disable_block_watcher = false;
ASSERT_OK(IsolatedDevmgr::Create(&args, &devmgr_));
ASSERT_OK(RecursiveWaitForFile(devmgr_.devfs_root().get(), "sys/platform/ram-disk/ramctl")
.status_value());
ASSERT_OK(RecursiveWaitForFile(devmgr_.devfs_root().get(), "sys/platform").status_value());
constexpr uint8_t kEmptyType[GPT_GUID_LEN] = GUID_EMPTY_VALUE;
ASSERT_NO_FATAL_FAILURE(
BlockDevice::Create(devmgr_.devfs_root(), kEmptyType, 2, 512, &gpt_dev_));
}
// Creates a BlockPartitionClient which will read/write the entire device.
zx::result<std::unique_ptr<paver::BlockPartitionClient>> RawClient() {
return paver::BlockPartitionClient::Create(gpt_dev_->block_controller_interface());
}
// Creates a FixedOffsetBlockPartitionClient which will read/write with a partition
// and buffer offset
zx::result<std::unique_ptr<paver::FixedOffsetBlockPartitionClient>> FixedOffsetClient(
size_t partition_offset, size_t buffer_offset) {
return paver::FixedOffsetBlockPartitionClient::Create(gpt_dev_->block_controller_interface(),
partition_offset, buffer_offset);
}
fidl::ClientEnd<fuchsia_io::Directory> GetSvcRoot() { return devmgr_.fshost_svc_dir(); }
private:
IsolatedDevmgr devmgr_;
std::unique_ptr<BlockDevice> gpt_dev_;
};
// Writes |data| to |client|.
// Call with ASSERT_NO_FATAL_FAILURE().
void Write(std::unique_ptr<paver::PartitionClient> client, std::string_view data) {
// Write data to a VMO.
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(data.size(), 0, &vmo));
ASSERT_OK(vmo.write(data.data(), 0, data.size()));
// Write VMO to the client.
ASSERT_OK(client->Write(vmo, data.size()));
}
// Reads |size| bytes from |client| into |data|.
// Call with ASSERT_NO_FATAL_FAILURE().
void Read(std::unique_ptr<paver::PartitionClient> client, std::string* data, size_t size) {
if (data->size() < size) {
data->resize(size);
}
// Read client to a VMO.
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(data->size(), 0, &vmo));
ASSERT_OK(client->Read(vmo, data->size()));
// Read VMO to data.
ASSERT_OK(vmo.read(data->data(), 0, data->size()));
}
TEST_F(FixedOffsetBlockPartitionClientTest, DISABLED_GetPartitionSize) {
auto pauser = BlockWatcherPauser::Create(GetSvcRoot());
ASSERT_OK(pauser);
{
auto status = RawClient()->GetPartitionSize();
ASSERT_OK(status);
ASSERT_EQ(1024, status.value());
}
{
// GetPartitionSize size should not count block 0.
auto status = FixedOffsetClient(1, 0)->GetPartitionSize();
ASSERT_OK(status);
ASSERT_EQ(512, status.value());
}
}
TEST_F(FixedOffsetBlockPartitionClientTest, DISABLED_ReadOffsetedPartition) {
const std::string block0(512, '0');
const std::string firmware(512, 'F');
auto pauser = BlockWatcherPauser::Create(GetSvcRoot());
ASSERT_OK(pauser);
zx::result raw_client = RawClient();
ASSERT_OK(raw_client);
ASSERT_NO_FATAL_FAILURE(Write(std::move(*raw_client), block0 + firmware));
// Bootloader read should skip block 0.
zx::result fixed_client = FixedOffsetClient(1, 0);
ASSERT_OK(fixed_client);
std::string actual;
ASSERT_NO_FATAL_FAILURE(Read(std::move(*fixed_client), &actual, 512));
ASSERT_EQ(firmware, actual);
}
TEST_F(FixedOffsetBlockPartitionClientTest, DISABLED_WriteOffsetdPartition) {
const std::string block0(512, '0');
const std::string firmware(512, 'F');
auto pauser = BlockWatcherPauser::Create(GetSvcRoot());
ASSERT_OK(pauser);
zx::result raw_client = RawClient();
ASSERT_OK(raw_client);
ASSERT_NO_FATAL_FAILURE(Write(std::move(*raw_client), block0 + block0));
zx::result fixed_client = FixedOffsetClient(1, 0);
ASSERT_OK(fixed_client);
ASSERT_NO_FATAL_FAILURE(Write(std::move(*fixed_client), firmware));
// Bootloader write should have skipped block 0.
std::string actual;
raw_client = RawClient();
ASSERT_OK(raw_client);
ASSERT_NO_FATAL_FAILURE(Read(std::move(*raw_client), &actual, 1024));
ASSERT_EQ(block0 + firmware, actual);
}
TEST_F(FixedOffsetBlockPartitionClientTest, ReadPartitionOffsetedBuffer) {
size_t block_size = 512;
const std::string initial(block_size, '0');
auto pauser = BlockWatcherPauser::Create(GetSvcRoot());
ASSERT_OK(pauser);
zx::result raw_client = RawClient();
ASSERT_OK(raw_client);
ASSERT_NO_FATAL_FAILURE(Write(std::move(*raw_client), initial));
// Bootloader should read to |actual| from offset 512
std::string actual(2 * block_size, 'F');
zx::result fixed_client = FixedOffsetClient(0, 1);
ASSERT_OK(fixed_client);
ASSERT_NO_FATAL_FAILURE(Read(std::move(*fixed_client), &actual, block_size));
ASSERT_EQ(initial, actual.substr(block_size, block_size));
}
TEST_F(FixedOffsetBlockPartitionClientTest, WritePartitionOffsetedBuffer) {
size_t block_size = 512;
const std::string initial(block_size, '0');
const std::string firmware = std::string(block_size, 'A') + std::string(block_size, 'B');
auto pauser = BlockWatcherPauser::Create(GetSvcRoot());
ASSERT_OK(pauser);
zx::result raw_client = RawClient();
ASSERT_OK(raw_client);
ASSERT_NO_FATAL_FAILURE(Write(std::move(*raw_client), initial));
zx::result fixed_client = FixedOffsetClient(0, 1);
ASSERT_OK(fixed_client);
ASSERT_NO_FATAL_FAILURE(Write(std::move(*fixed_client), firmware));
// Bootloader should write only 'B' to storage
std::string actual;
raw_client = RawClient();
ASSERT_OK(raw_client);
ASSERT_NO_FATAL_FAILURE(Read(std::move(*raw_client), &actual, block_size));
ASSERT_EQ(firmware.substr(block_size), actual);
}
} // namespace