blob: b91dc4f2a2f7acc0a3c6f0cae4056fec735cbae9 [file] [log] [blame]
// Copyright 2023 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.
// These tests verify basic functionality of `vfs::VmoFile`. For more comprehensive tests, see
// //src/storage/lib/vfs/cpp and //src/storage/conformance.
#include <fcntl.h>
#include <fuchsia/io/cpp/fidl.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/io.h>
#include <lib/vfs/cpp/pseudo_dir.h>
#include <lib/vfs/cpp/vmo_file.h>
#include <zircon/status.h>
#include <memory>
#include <string>
#include <string_view>
#include <fbl/unique_fd.h>
#include "src/lib/testing/loop_fixture/real_loop_fixture.h"
namespace {
constexpr std::string_view kFileContents = "See you, Space Cowboy...";
constexpr std::string_view kNewContents = "This string should be longer than kFileContents.";
static_assert(kNewContents.size() > kFileContents.size());
// Fixture sets up the following hierarchy, where both files are initialized with and sized to
// the exact length of kFileContents:
//
// root/
// writable_file
// read_only_file
//
class VmoFileTest : public ::gtest::RealLoopFixture {
protected:
void SetUp() override {
root_ = std::make_unique<vfs::PseudoDir>();
zx::vmo vmo;
ASSERT_EQ(zx::vmo::create(kFileContents.size(), 0, &vmo), ZX_OK);
ASSERT_EQ(vmo.write(kFileContents.data(), 0, kFileContents.size()), ZX_OK);
auto writable_file = std::make_unique<vfs::VmoFile>(std::move(vmo), kFileContents.size(),
vfs::VmoFile::WriteMode::kWritable);
ASSERT_EQ(root_->AddEntry("writable_file", std::move(writable_file)), ZX_OK);
ASSERT_EQ(zx::vmo::create(kFileContents.size(), 0, &vmo), ZX_OK);
ASSERT_EQ(vmo.write(kFileContents.data(), 0, kFileContents.size()), ZX_OK);
auto read_only_file = std::make_unique<vfs::VmoFile>(std::move(vmo), kFileContents.size(),
vfs::VmoFile::WriteMode::kReadOnly);
ASSERT_EQ(root_->AddEntry("read_only_file", std::move(read_only_file)), ZX_OK);
zx::channel root_server;
ASSERT_EQ(zx::channel::create(0, &root_client_, &root_server), ZX_OK);
ASSERT_EQ(root_->Serve(
fuchsia::io::OpenFlags::RIGHT_READABLE | fuchsia::io::OpenFlags::RIGHT_WRITABLE,
std::move(root_server)),
ZX_OK);
}
// Consumes and opens the root connection as a file descriptor. This must be called from a
// different thread than the one serving the connection.
fbl::unique_fd open_root_fd() {
ZX_ASSERT_MSG(root_client_.is_valid(), "open_root_fd() can only be called once per test!");
fbl::unique_fd fd;
zx_status_t status = fdio_fd_create(root_client_.release(), fd.reset_and_get_address());
ZX_ASSERT_MSG(status == ZX_OK, "Failed to create fd: %s", zx_status_get_string(status));
return fd;
}
private:
std::unique_ptr<vfs::PseudoDir> root_;
zx::channel root_client_;
};
TEST_F(VmoFileTest, ReadOnlyIsNotWritable) {
PerformBlockingWork([this] {
auto root = open_root_fd();
fbl::unique_fd file(openat(root.get(), "read_only_file", O_RDWR));
ASSERT_FALSE(file) << "Should fail to open read_only_file as writable!";
ASSERT_EQ(errno, EACCES) << strerror(errno);
});
}
TEST_F(VmoFileTest, ReadContents) {
PerformBlockingWork([this] {
auto root = open_root_fd();
fbl::unique_fd file(openat(root.get(), "read_only_file", O_RDONLY));
ASSERT_TRUE(file) << "Failed to open file: " << strerror(errno);
// Attempts to read past EOF should respect file size.
std::string buffer(kFileContents.size() + 100, 0);
ssize_t bytes_read = read(file.get(), buffer.data(), kFileContents.size() + 100);
ASSERT_EQ(bytes_read, static_cast<ssize_t>(kFileContents.size()));
ASSERT_EQ(kFileContents, buffer.substr(0, kFileContents.size()));
});
}
TEST_F(VmoFileTest, WriteContents) {
PerformBlockingWork([this] {
auto root = open_root_fd();
fbl::unique_fd file(openat(root.get(), "writable_file", O_RDWR));
ASSERT_TRUE(file) << "Failed to open file: " << strerror(errno);
// Attempts to read/write past EOF should respect file size.
ssize_t bytes_written = write(file.get(), kNewContents.data(), kNewContents.size());
ASSERT_EQ(bytes_written, static_cast<ssize_t>(kFileContents.size()));
ASSERT_EQ(lseek(file.get(), 0, SEEK_SET), 0) << strerror(errno);
std::string buffer(kFileContents.size() + 100, 0);
ssize_t bytes_read = read(file.get(), buffer.data(), buffer.size());
ASSERT_EQ(bytes_read, static_cast<ssize_t>(kFileContents.size()));
ASSERT_EQ(kNewContents.substr(0, kFileContents.size()), buffer.substr(0, kFileContents.size()));
});
}
TEST_F(VmoFileTest, GetVmo) {
PerformBlockingWork([this] {
auto root = open_root_fd();
fbl::unique_fd file(openat(root.get(), "writable_file", O_RDWR));
ASSERT_TRUE(file) << "Failed to open file: " << strerror(errno);
zx::vmo vmo_exact, vmo_clone;
zx_status_t status = fdio_get_vmo_exact(file.get(), vmo_exact.reset_and_get_address());
ASSERT_EQ(status, ZX_OK) << "fdio_get_vmo_exact: " << zx_status_get_string(status);
status = fdio_get_vmo_clone(file.get(), vmo_clone.reset_and_get_address());
ASSERT_EQ(status, ZX_OK) << "fdio_get_vmo_clone: " << zx_status_get_string(status);
// Writes to the file should be reflected in vmo_exact but not vmo_clone.
ASSERT_EQ(write(file.get(), kNewContents.data(), kNewContents.size()),
static_cast<ssize_t>(kFileContents.size()));
ASSERT_EQ(lseek(file.get(), 0, SEEK_SET), 0) << strerror(errno);
std::string buffer(kFileContents.size(), 0);
ASSERT_EQ(vmo_exact.read(buffer.data(), 0, buffer.size()), ZX_OK);
ASSERT_EQ(buffer, kNewContents.substr(0, buffer.size()));
ASSERT_EQ(vmo_clone.read(buffer.data(), 0, buffer.size()), ZX_OK);
ASSERT_EQ(buffer, kFileContents);
});
}
} // namespace