blob: bdcf82cffa4387860dd87aad849d7bae3f11f516 [file] [log] [blame]
// Copyright 2018 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 <fbl/unique_fd.h>
#include <fcntl.h>
#include <gmock/gmock.h>
#include <lib/fdio/util.h>
#include <lib/fxl/arraysize.h>
#include <lib/fxl/logging.h>
#include <lib/fxl/strings/string_printf.h>
#include <limits.h>
#include <stdlib.h>
#include <string>
#include "garnet/bin/guest/vmm/device/block.h"
#include "garnet/bin/guest/vmm/device/qcow.h"
#include "garnet/bin/guest/vmm/device/qcow_test_data.h"
#include "guest_test.h"
using namespace qcow_test_data;
using testing::HasSubstr;
static constexpr char kVirtioBlockUtil[] = "virtio_block_test_util";
static constexpr uint32_t kVirtioBlockCount = 32;
static constexpr uint32_t kVirtioQcowBlockCount = 4 * 1024 * 1024 * 2;
static constexpr uint32_t kVirtioTestStep = 8;
template <class T>
T* GuestTest<T>::enclosed_guest_ = nullptr;
static fidl::VectorPtr<fuchsia::guest::BlockDevice> block_device(
fuchsia::guest::BlockMode mode, fuchsia::guest::BlockFormat format,
int fd) {
zx_handle_t handle;
zx_status_t status = fdio_get_service_handle(fd, &handle);
FXL_CHECK(status == ZX_OK) << "Failed to get temporary file handle";
fidl::VectorPtr<fuchsia::guest::BlockDevice> block_devices;
block_devices.push_back({
"test_device",
mode,
format,
fidl::InterfaceHandle<fuchsia::io::File>(zx::channel(handle)).Bind(),
});
return block_devices;
}
static zx_status_t write_raw_file(int fd) {
int ret = ftruncate(fd, kVirtioBlockCount * kBlockSectorSize);
if (ret != 0) {
return ZX_ERR_IO;
}
return ZX_OK;
}
template <typename T>
static bool write_at(int fd, const T* ptr, off_t off) {
ssize_t written = pwrite(fd, ptr, sizeof(T), off);
return written == static_cast<ssize_t>(sizeof(T));
}
// Writes an array of T values at the current file location.
template <typename T>
static bool write_at(int fd, const T* ptr, size_t len, off_t off) {
ssize_t written = pwrite(fd, ptr, len * sizeof(T), off);
return written == static_cast<ssize_t>(len * sizeof(T));
}
static zx_status_t write_qcow_file(int fd) {
QcowHeader header = kDefaultHeaderV2.HostToBigEndian();
bool write_success = write_at(fd, &header, 0);
if (!write_success) {
return ZX_ERR_IO;
}
// Convert L1 entries to big-endian
uint64_t be_table[arraysize(kL2TableClusterOffsets)];
for (size_t i = 0; i < arraysize(kL2TableClusterOffsets); ++i) {
be_table[i] = HostToBigEndianTraits::Convert(kL2TableClusterOffsets[i]);
}
// Write L1 table.
write_success = write_at(fd, be_table, arraysize(kL2TableClusterOffsets),
kDefaultHeaderV2.l1_table_offset);
if (!write_success) {
return ZX_ERR_IO;
}
// Initialize empty L2 tables.
for (size_t i = 0; i < arraysize(kL2TableClusterOffsets); ++i) {
write_success = write_at(fd, kZeroCluster, sizeof(kZeroCluster),
kL2TableClusterOffsets[i]);
if (!write_success) {
return ZX_ERR_IO;
}
}
// Write L2 entry
uint64_t l2_offset = kL2TableClusterOffsets[0];
uint64_t data_cluster_offset = ClusterOffset(kFirstDataCluster);
uint64_t l2_entry = HostToBigEndianTraits::Convert(data_cluster_offset);
write_success = write_at(fd, &l2_entry, l2_offset);
if (!write_success) {
return ZX_ERR_IO;
}
// Write data to cluster.
uint8_t cluster_data[kClusterSize];
memset(cluster_data, 0xab, sizeof(cluster_data));
write_success = write_at(fd, cluster_data, kClusterSize, data_cluster_offset);
if (!write_success) {
return ZX_ERR_IO;
}
return ZX_OK;
}
template <fuchsia::guest::BlockMode Mode, fuchsia::guest::BlockFormat Format>
class VirtioBlockZirconGuest : public ZirconEnclosedGuest {
public:
zx_status_t LaunchInfo(fuchsia::guest::LaunchInfo* launch_info) override {
zx_status_t status = ZirconEnclosedGuest::LaunchInfo(launch_info);
if (status != ZX_OK) {
return status;
}
fbl::unique_fd fd(mkstemp(file_path_.data()));
if (!fd) {
FXL_LOG(ERROR) << "Failed to create temporary file";
return ZX_ERR_IO;
}
status = ZX_ERR_BAD_STATE;
if (Format == fuchsia::guest::BlockFormat::RAW) {
status = write_raw_file(fd.get());
} else if (Format == fuchsia::guest::BlockFormat::QCOW) {
status = write_qcow_file(fd.get());
}
if (status != ZX_OK) {
return status;
}
launch_info->block_devices = block_device(Mode, Format, fd.release());
return ZX_OK;
}
fuchsia::guest::BlockMode BlockMode() const { return Mode; }
const std::string& FilePath() const { return file_path_; }
std::string file_path_ = "/tmp/guest-test.XXXXXX";
};
template <fuchsia::guest::BlockMode Mode, fuchsia::guest::BlockFormat Format>
class VirtioBlockLinuxGuest : public LinuxEnclosedGuest {
public:
zx_status_t LaunchInfo(fuchsia::guest::LaunchInfo* launch_info) override {
zx_status_t status = LinuxEnclosedGuest::LaunchInfo(launch_info);
if (status != ZX_OK) {
return status;
}
fbl::unique_fd fd(mkstemp(file_path_.data()));
if (!fd) {
FXL_LOG(ERROR) << "Failed to create temporary file";
return ZX_ERR_IO;
}
status = ZX_ERR_BAD_STATE;
if (Format == fuchsia::guest::BlockFormat::RAW) {
status = write_raw_file(fd.get());
} else if (Format == fuchsia::guest::BlockFormat::QCOW) {
status = write_qcow_file(fd.get());
}
if (status != ZX_OK) {
return status;
}
launch_info->block_devices = block_device(Mode, Format, fd.release());
return ZX_OK;
}
fuchsia::guest::BlockMode BlockMode() const { return Mode; }
const std::string& FilePath() const { return file_path_; }
std::string file_path_ = "/tmp/guest-test.XXXXXX";
};
template <class T>
class VirtioBlockGuestTest : public GuestTest<T> {
public:
const std::string& FilePath() const {
return this->GetEnclosedGuest()->FilePath();
}
fuchsia::guest::BlockMode BlockMode() const {
return this->GetEnclosedGuest()->BlockMode();
}
};
template <class T>
using RawVirtioBlockGuestTest = VirtioBlockGuestTest<T>;
using RawGuestTypes = ::testing::Types<
VirtioBlockZirconGuest<fuchsia::guest::BlockMode::READ_ONLY,
fuchsia::guest::BlockFormat::RAW>,
VirtioBlockZirconGuest<fuchsia::guest::BlockMode::READ_WRITE,
fuchsia::guest::BlockFormat::RAW>,
VirtioBlockZirconGuest<fuchsia::guest::BlockMode::VOLATILE_WRITE,
fuchsia::guest::BlockFormat::RAW>,
VirtioBlockLinuxGuest<fuchsia::guest::BlockMode::READ_ONLY,
fuchsia::guest::BlockFormat::RAW>,
VirtioBlockLinuxGuest<fuchsia::guest::BlockMode::READ_WRITE,
fuchsia::guest::BlockFormat::RAW>,
VirtioBlockLinuxGuest<fuchsia::guest::BlockMode::VOLATILE_WRITE,
fuchsia::guest::BlockFormat::RAW>>;
TYPED_TEST_CASE(RawVirtioBlockGuestTest, RawGuestTypes);
TYPED_TEST(RawVirtioBlockGuestTest, BlockDeviceExists) {
std::string args =
fxl::StringPrintf("%lu %u check", kBlockSectorSize, kVirtioBlockCount);
std::string result;
EXPECT_EQ(this->RunUtil(kVirtioBlockUtil, args, &result), ZX_OK);
EXPECT_THAT(result, HasSubstr("PASS"));
}
TYPED_TEST(RawVirtioBlockGuestTest, Read) {
fbl::unique_fd fd(open(this->FilePath().c_str(), O_RDWR));
ASSERT_TRUE(fd);
uint8_t data[kBlockSectorSize];
memset(data, 0xab, kBlockSectorSize);
for (off_t offset = 0; offset != kVirtioBlockCount;
offset += kVirtioTestStep) {
ASSERT_EQ(
pwrite(fd.get(), &data, kBlockSectorSize, offset * kBlockSectorSize),
static_cast<ssize_t>(kBlockSectorSize));
std::string args =
fxl::StringPrintf("%lu %u read %d %d", kBlockSectorSize,
kVirtioBlockCount, static_cast<int>(offset), 0xab);
std::string result;
EXPECT_EQ(this->RunUtil(kVirtioBlockUtil, args, &result), ZX_OK);
EXPECT_THAT(result, HasSubstr("PASS"));
}
}
TYPED_TEST(RawVirtioBlockGuestTest, Write) {
fbl::unique_fd fd(open(this->FilePath().c_str(), O_RDWR));
ASSERT_TRUE(fd);
uint8_t data[kBlockSectorSize];
memset(data, 0, kBlockSectorSize);
for (off_t offset = 0; offset != kVirtioBlockCount;
offset += kVirtioTestStep) {
// Write the block to zero.
ASSERT_EQ(
pwrite(fd.get(), &data, kBlockSectorSize, offset * kBlockSectorSize),
static_cast<ssize_t>(kBlockSectorSize));
// Tell the guest to write bytes to the block.
std::string args =
fxl::StringPrintf("%lu %u write %d %d", kBlockSectorSize,
kVirtioBlockCount, static_cast<int>(offset), 0xab);
std::string result;
EXPECT_EQ(this->RunUtil(kVirtioBlockUtil, args, &result), ZX_OK);
// TODO(MAC-234): The virtio-block driver on Zircon currently doesn't inform
// the rest of the system when the device is read only.
if (this->GetGuestKernel() == GuestKernel::LINUX &&
this->BlockMode() == fuchsia::guest::BlockMode::READ_ONLY) {
EXPECT_THAT(result, HasSubstr("PermissionDenied"));
} else {
EXPECT_THAT(result, HasSubstr("PASS"));
}
int expected_guest_read, expected_host_read;
switch (this->BlockMode()) {
case fuchsia::guest::BlockMode::READ_ONLY:
expected_guest_read = 0;
expected_host_read = 0;
break;
case fuchsia::guest::BlockMode::READ_WRITE:
expected_guest_read = 0xab;
expected_host_read = 0xab;
break;
case fuchsia::guest::BlockMode::VOLATILE_WRITE:
expected_guest_read = 0xab;
expected_host_read = 0;
break;
}
// Check the value when read from the guest.
args = fxl::StringPrintf("%lu %u read %d %d", kBlockSectorSize,
kVirtioBlockCount, static_cast<int>(offset),
expected_guest_read);
EXPECT_EQ(this->RunUtil(kVirtioBlockUtil, args, &result), ZX_OK);
EXPECT_THAT(result, HasSubstr("PASS"));
// Check the value when read from the host file.
ASSERT_EQ(
pread(fd.get(), &data, kBlockSectorSize, offset * kBlockSectorSize),
static_cast<ssize_t>(kBlockSectorSize));
for (off_t i = 0; i != kBlockSectorSize; ++i) {
EXPECT_EQ(data[i], expected_host_read);
}
}
}
template <class T>
using QcowVirtioBlockGuestTest = VirtioBlockGuestTest<T>;
using QcowGuestTypes = ::testing::Types<
VirtioBlockZirconGuest<fuchsia::guest::BlockMode::READ_ONLY,
fuchsia::guest::BlockFormat::QCOW>,
VirtioBlockZirconGuest<fuchsia::guest::BlockMode::VOLATILE_WRITE,
fuchsia::guest::BlockFormat::QCOW>,
VirtioBlockLinuxGuest<fuchsia::guest::BlockMode::READ_ONLY,
fuchsia::guest::BlockFormat::QCOW>,
VirtioBlockLinuxGuest<fuchsia::guest::BlockMode::VOLATILE_WRITE,
fuchsia::guest::BlockFormat::QCOW>>;
TYPED_TEST_CASE(QcowVirtioBlockGuestTest, QcowGuestTypes);
TYPED_TEST(QcowVirtioBlockGuestTest, BlockDeviceExists) {
std::string args = fxl::StringPrintf("%lu %u check", kBlockSectorSize,
kVirtioQcowBlockCount);
std::string result;
EXPECT_EQ(this->RunUtil(kVirtioBlockUtil, args, &result), ZX_OK);
EXPECT_THAT(result, HasSubstr("PASS"));
}
TYPED_TEST(QcowVirtioBlockGuestTest, ReadMappedCluster) {
for (off_t offset = 0; offset != kClusterSize / kBlockSectorSize;
offset += kVirtioTestStep) {
std::string args = fxl::StringPrintf("%lu %u read %d %d", kBlockSectorSize,
kVirtioQcowBlockCount,
static_cast<int>(offset), 0xab);
std::string result;
EXPECT_EQ(this->RunUtil(kVirtioBlockUtil, args, &result), ZX_OK);
EXPECT_THAT(result, HasSubstr("PASS"));
}
}
TYPED_TEST(QcowVirtioBlockGuestTest, ReadUnmappedCluster) {
for (off_t offset = kClusterSize;
offset != kClusterSize + (kClusterSize / kBlockSectorSize);
offset += kVirtioTestStep) {
std::string args =
fxl::StringPrintf("%lu %u read %d %d", kBlockSectorSize,
kVirtioQcowBlockCount, static_cast<int>(offset), 0);
std::string result;
EXPECT_EQ(this->RunUtil(kVirtioBlockUtil, args, &result), ZX_OK);
EXPECT_THAT(result, HasSubstr("PASS"));
}
}
TYPED_TEST(QcowVirtioBlockGuestTest, Write) {
for (off_t offset = kClusterSize;
offset != kClusterSize + (kClusterSize / kBlockSectorSize);
offset += kVirtioTestStep) {
std::string args = fxl::StringPrintf("%lu %u write %d %d", kBlockSectorSize,
kVirtioQcowBlockCount,
static_cast<int>(offset), 0xab);
std::string result;
EXPECT_EQ(this->RunUtil(kVirtioBlockUtil, args, &result), ZX_OK);
// TODO(MAC-234): The virtio-block driver on Zircon currently doesn't inform
// the rest of the system when the device is read only.
if (this->GetGuestKernel() == GuestKernel::LINUX &&
this->BlockMode() == fuchsia::guest::BlockMode::READ_ONLY) {
EXPECT_THAT(result, HasSubstr("PermissionDenied"));
} else {
EXPECT_THAT(result, HasSubstr("PASS"));
}
int expected_read;
switch (this->BlockMode()) {
case fuchsia::guest::BlockMode::READ_ONLY:
expected_read = 0;
break;
case fuchsia::guest::BlockMode::VOLATILE_WRITE:
expected_read = 0xab;
break;
case fuchsia::guest::BlockMode::READ_WRITE:
// READ_WRITE not supported for QCOW.
expected_read = -1;
break;
}
args = fxl::StringPrintf("%lu %u read %d %d", kBlockSectorSize,
kVirtioQcowBlockCount, static_cast<int>(offset),
expected_read);
EXPECT_EQ(this->RunUtil(kVirtioBlockUtil, args, &result), ZX_OK);
EXPECT_THAT(result, HasSubstr("PASS"));
}
}