blob: 527913c913f72c8b0c605bddb9a926d96bef0a35 [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 <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/fdio.h>
#include <lib/fdio/limits.h>
#include <lib/vfs/cpp/vmo_file.h>
#include <unistd.h>
#include <zircon/processargs.h>
#include <zircon/status.h>
#include <algorithm>
#include <string>
#include <utility>
#include <vector>
#include <gtest/gtest.h>
namespace {
void FillBuffer(char* buf, size_t size) {
for (size_t i = 0; i < size; i++) {
buf[i] = i % 256;
}
}
zx::vmo MakeTestVmo() {
zx::vmo ret;
EXPECT_EQ(ZX_OK, zx::vmo::create(4096, 0, &ret));
char buf[4096];
FillBuffer(buf, 4096);
EXPECT_EQ(ZX_OK, ret.write(buf, 0, 4096));
return ret;
}
fuchsia::io::FileSyncPtr OpenAsFile(vfs::internal::Node* node, async_dispatcher_t* dispatcher,
bool writable = false) {
zx::channel local, remote;
EXPECT_EQ(ZX_OK, zx::channel::create(0, &local, &remote));
fuchsia::io::OpenFlags flags = fuchsia::io::OpenFlags::RIGHT_READABLE;
if (writable) {
flags |= fuchsia::io::OpenFlags::RIGHT_WRITABLE;
}
EXPECT_EQ(ZX_OK, node->Serve(flags, std::move(remote), dispatcher));
fuchsia::io::FileSyncPtr ret;
ret.Bind(std::move(local));
return ret;
}
std::vector<uint8_t> ReadVmo(const zx::vmo& vmo, size_t offset, size_t length) {
std::vector<uint8_t> ret;
ret.resize(length);
EXPECT_EQ(ZX_OK, vmo.read(ret.data(), offset, length));
return ret;
}
TEST(VmoFile, ConstructTransferOwnership) {
vfs::VmoFile file(MakeTestVmo(), 24, 1000);
std::vector<uint8_t> output;
EXPECT_EQ(ZX_OK, file.ReadAt(1000, 0, &output));
EXPECT_EQ(1000u, output.size());
}
TEST(VmoFile, Reading) {
// Create a VmoFile wrapping 1000 bytes starting at offset 24 of the vmo.
zx::vmo test_vmo = MakeTestVmo();
vfs::VmoFile file(zx::unowned_vmo(test_vmo), 24, 1000, vfs::VmoFile::WriteOption::READ_ONLY,
vfs::VmoFile::Sharing::NONE);
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
loop.StartThread("vfs test thread");
auto file_ptr = OpenAsFile(&file, loop.dispatcher());
ASSERT_TRUE(file_ptr.is_bound());
// Reading the VMO from offset 24 should match reading the file from offset 0.
{
fuchsia::io::File2_Read_Result result;
EXPECT_EQ(ZX_OK, file_ptr->Read(500, &result));
ASSERT_TRUE(result.is_response()) << zx_status_get_string(result.err());
EXPECT_EQ(ReadVmo(test_vmo, 24, 500), result.response().data);
}
{
fuchsia::io::File2_Read_Result result;
EXPECT_EQ(ZX_OK, file_ptr->Read(500, &result));
ASSERT_TRUE(result.is_response()) << zx_status_get_string(result.err());
EXPECT_EQ(ReadVmo(test_vmo, 524, 500), result.response().data);
}
}
TEST(VmoFile, GetAttrReadOnly) {
// Create a VmoFile wrapping 1000 bytes starting at offset 24 of the vmo.
zx::vmo test_vmo = MakeTestVmo();
vfs::VmoFile file(zx::unowned_vmo(test_vmo), 24, 1000, vfs::VmoFile::WriteOption::READ_ONLY,
vfs::VmoFile::Sharing::NONE);
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
loop.StartThread("vfs test thread");
auto file_ptr = OpenAsFile(&file, loop.dispatcher());
ASSERT_TRUE(file_ptr.is_bound());
fuchsia::io::NodeAttributes attr;
zx_status_t status;
EXPECT_EQ(ZX_OK, file_ptr->GetAttr(&status, &attr));
EXPECT_EQ(ZX_OK, status);
EXPECT_EQ(1000u, attr.content_size);
EXPECT_EQ(1000u, attr.storage_size);
EXPECT_EQ(
fuchsia::io::MODE_TYPE_FILE | static_cast<uint32_t>(fuchsia::io::OpenFlags::RIGHT_READABLE),
attr.mode);
}
TEST(VmoFile, GetAttrWritable) {
// Create a VmoFile wrapping 1000 bytes starting at offset 24 of the vmo.
zx::vmo test_vmo = MakeTestVmo();
vfs::VmoFile file(zx::unowned_vmo(test_vmo), 24, 1000, vfs::VmoFile::WriteOption::WRITABLE,
vfs::VmoFile::Sharing::NONE);
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
loop.StartThread("vfs test thread");
auto file_ptr = OpenAsFile(&file, loop.dispatcher());
ASSERT_TRUE(file_ptr.is_bound());
fuchsia::io::NodeAttributes attr;
zx_status_t status;
EXPECT_EQ(ZX_OK, file_ptr->GetAttr(&status, &attr));
EXPECT_EQ(ZX_OK, status);
EXPECT_EQ(1000u, attr.content_size);
EXPECT_EQ(1000u, attr.storage_size);
EXPECT_EQ(
fuchsia::io::MODE_TYPE_FILE | static_cast<uint32_t>(fuchsia::io::OpenFlags::RIGHT_READABLE |
fuchsia::io::OpenFlags::RIGHT_WRITABLE),
attr.mode);
}
TEST(VmoFile, ReadOnlyNoSharing) {
// Create a VmoFile wrapping 1000 bytes starting at offset 24 of the vmo.
zx::vmo test_vmo = MakeTestVmo();
vfs::VmoFile file(zx::unowned_vmo(test_vmo), 24, 1000, vfs::VmoFile::WriteOption::READ_ONLY,
vfs::VmoFile::Sharing::NONE);
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
loop.StartThread("vfs test thread");
auto file_ptr = OpenAsFile(&file, loop.dispatcher());
ASSERT_TRUE(file_ptr.is_bound());
// Writes should fail, since the VMO is read-only.
{
std::vector<uint8_t> value{'a', 'b', 'c', 'd'};
fuchsia::io::File2_WriteAt_Result result;
EXPECT_EQ(ZX_OK, file_ptr->WriteAt(value, 0, &result));
EXPECT_TRUE(result.is_err());
EXPECT_EQ(ZX_ERR_BAD_HANDLE, result.err());
}
// Reading the VMO from offset 24 should match reading the file from offset 0.
{
std::vector<uint8_t> vmo_result = ReadVmo(test_vmo, 24, 1000);
fuchsia::io::File2_ReadAt_Result result;
EXPECT_EQ(ZX_OK, file_ptr->ReadAt(1000, 0, &result));
EXPECT_TRUE(result.is_response()) << zx_status_get_string(result.err());
EXPECT_EQ(vmo_result, result.response().data);
}
// The file should appear as a regular file, the fact that a VMO is backing it
// is hidden.
fuchsia::io::NodeInfo info;
EXPECT_EQ(ZX_OK, file_ptr->Describe(&info));
ASSERT_TRUE(info.is_file());
}
TEST(VmoFile, WritableNoSharing) {
// Create a VmoFile wrapping 1000 bytes starting at offset 24 of the vmo.
zx::vmo test_vmo = MakeTestVmo();
vfs::VmoFile file(zx::unowned_vmo(test_vmo), 24, 1000, vfs::VmoFile::WriteOption::WRITABLE,
vfs::VmoFile::Sharing::NONE);
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
loop.StartThread("vfs test thread");
auto file_ptr = OpenAsFile(&file, loop.dispatcher(), /*writable=*/true);
ASSERT_TRUE(file_ptr.is_bound());
// Writes should succeed.
{
std::vector<uint8_t> value{'a', 'b', 'c', 'd'};
fuchsia::io::File2_WriteAt_Result result;
EXPECT_EQ(ZX_OK, file_ptr->WriteAt(value, 0, &result));
EXPECT_TRUE(result.is_response()) << zx_status_get_string(result.err());
EXPECT_EQ(value.size(), result.response().actual_count);
}
// Reading the VMO from offset 24 should match reading the file from offset 0.
{
std::vector<uint8_t> vmo_result = ReadVmo(test_vmo, 24, 1000);
fuchsia::io::File2_ReadAt_Result result;
EXPECT_EQ(ZX_OK, file_ptr->ReadAt(1000, 0, &result));
EXPECT_TRUE(result.is_response()) << zx_status_get_string(result.err());
EXPECT_EQ(vmo_result, result.response().data);
EXPECT_EQ('a', result.response().data[0]);
}
// The file should appear as a regular file, the fact that a VMO is backing it
// is hidden.
fuchsia::io::NodeInfo info;
EXPECT_EQ(ZX_OK, file_ptr->Describe(&info));
ASSERT_TRUE(info.is_file());
}
TEST(VmoFile, ReadOnlyDuplicate) {
// Create a VmoFile wrapping 1000 bytes starting at offset 24 of the vmo.
zx::vmo test_vmo = MakeTestVmo();
vfs::VmoFile file(zx::unowned_vmo(test_vmo), 24, 1000);
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
loop.StartThread("vfs test thread");
auto file_ptr = OpenAsFile(&file, loop.dispatcher());
ASSERT_TRUE(file_ptr.is_bound());
// Writes should fail, since the VMO is read-only.
{
std::vector<uint8_t> value{'a', 'b', 'c', 'd'};
fuchsia::io::File2_WriteAt_Result result;
EXPECT_EQ(ZX_OK, file_ptr->WriteAt(value, 0, &result));
EXPECT_TRUE(result.is_err());
EXPECT_EQ(ZX_ERR_BAD_HANDLE, result.err());
}
// Reading the VMO from offset 24 should match reading the file from offset 0.
{
std::vector<uint8_t> vmo_result = ReadVmo(test_vmo, 24, 1000);
fuchsia::io::File2_ReadAt_Result result;
EXPECT_EQ(ZX_OK, file_ptr->ReadAt(1000, 0, &result));
EXPECT_TRUE(result.is_response()) << zx_status_get_string(result.err());
EXPECT_EQ(vmo_result, result.response().data);
}
// Describing the VMO duplicates the handle, and we can access the entire VMO.
fuchsia::io::NodeInfo info;
EXPECT_EQ(ZX_OK, file_ptr->Describe(&info));
ASSERT_TRUE(info.is_vmofile());
ASSERT_EQ(1000u, info.vmofile().length);
ASSERT_EQ(24u, info.vmofile().offset);
EXPECT_EQ(ReadVmo(test_vmo, 0, 4096), ReadVmo(info.vmofile().vmo, 0, 4096));
// Writing should fail on the new VMO.
EXPECT_NE(ZX_OK, info.vmofile().vmo.write("test", 0, 4));
}
TEST(VmoFile, WritableDuplicate) {
// Create a VmoFile wrapping 1000 bytes starting at offset 24 of the vmo.
zx::vmo test_vmo = MakeTestVmo();
vfs::VmoFile file(zx::unowned_vmo(test_vmo), 24, 1000, vfs::VmoFile::WriteOption::WRITABLE);
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
loop.StartThread("vfs test thread");
auto file_ptr = OpenAsFile(&file, loop.dispatcher(), /*writable=*/true);
ASSERT_TRUE(file_ptr.is_bound());
// Writes should succeed.
{
std::vector<uint8_t> value{'a', 'b', 'c', 'd'};
fuchsia::io::File2_WriteAt_Result result;
EXPECT_EQ(ZX_OK, file_ptr->WriteAt(value, 0, &result));
EXPECT_TRUE(result.is_response()) << zx_status_get_string(result.err());
EXPECT_EQ(value.size(), result.response().actual_count);
}
// Reading the VMO from offset 24 should match reading the file from offset 0.
{
std::vector<uint8_t> vmo_result = ReadVmo(test_vmo, 24, 1000);
fuchsia::io::File2_ReadAt_Result result;
EXPECT_EQ(ZX_OK, file_ptr->ReadAt(1000, 0, &result));
EXPECT_TRUE(result.is_response()) << zx_status_get_string(result.err());
EXPECT_EQ(vmo_result, result.response().data);
EXPECT_EQ('a', result.response().data[0]);
}
// Describing the VMO duplicates the handle, and we can access the entire VMO.
fuchsia::io::NodeInfo info;
EXPECT_EQ(ZX_OK, file_ptr->Describe(&info));
ASSERT_TRUE(info.is_vmofile());
ASSERT_EQ(1000u, info.vmofile().length);
ASSERT_EQ(24u, info.vmofile().offset);
EXPECT_EQ(ReadVmo(test_vmo, 0, 4096), ReadVmo(info.vmofile().vmo, 0, 4096));
// TODO(fxbug.dev/45287): As part of fxbug.dev/85334 it was discovered that Describe leaks
// writable handles even if the connection lacks OPEN_RIGHT_WRITABLE. In the long term, if handles
// to the underlying VMO require specific rights, they should either be obtained via GetBuffer(),
// or we need to allow the VmoFile node itself query the connection rights (since these are
// currently not available when handling the Describe call).
}
TEST(VmoFile, ReadOnlyCopyOnWrite) {
// Create a VmoFile wrapping the VMO.
zx::vmo test_vmo = MakeTestVmo();
vfs::VmoFile file(zx::unowned_vmo(test_vmo), 0, 4096, vfs::VmoFile::WriteOption::READ_ONLY,
vfs::VmoFile::Sharing::CLONE_COW);
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
loop.StartThread("vfs test thread");
auto file_ptr = OpenAsFile(&file, loop.dispatcher());
ASSERT_TRUE(file_ptr.is_bound());
// Writes should fail, since the VMO is read-only.
{
std::vector<uint8_t> value{'a', 'b', 'c', 'd'};
fuchsia::io::File2_WriteAt_Result result;
EXPECT_EQ(ZX_OK, file_ptr->WriteAt(value, 0, &result));
EXPECT_TRUE(result.is_err());
EXPECT_EQ(ZX_ERR_BAD_HANDLE, result.err());
}
// Reading the VMO should match reading the file.
{
std::vector<uint8_t> vmo_result = ReadVmo(test_vmo, 0, 4096);
fuchsia::io::File2_ReadAt_Result result;
EXPECT_EQ(ZX_OK, file_ptr->ReadAt(4096, 0, &result));
EXPECT_TRUE(result.is_response()) << zx_status_get_string(result.err());
EXPECT_EQ(vmo_result, result.response().data);
}
// Describing the VMO clones the handle, and we can access the entire VMO.
fuchsia::io::NodeInfo info;
EXPECT_EQ(ZX_OK, file_ptr->Describe(&info));
ASSERT_TRUE(info.is_vmofile());
ASSERT_EQ(4096u, info.vmofile().length);
ASSERT_EQ(0u, info.vmofile().offset);
EXPECT_EQ(ReadVmo(test_vmo, 0, 4096), ReadVmo(info.vmofile().vmo, 0, 4096));
// Writing should succeed on the new VMO, due to copy on write.
EXPECT_EQ(ZX_OK, info.vmofile().vmo.write("test", 0, 4));
EXPECT_NE(ReadVmo(test_vmo, 0, 4096), ReadVmo(info.vmofile().vmo, 0, 4096));
}
TEST(VmoFile, WritableCopyOnWrite) {
// Create a VmoFile wrapping the VMO.
zx::vmo test_vmo = MakeTestVmo();
vfs::VmoFile file(zx::unowned_vmo(test_vmo), 0, 4096, vfs::VmoFile::WriteOption::WRITABLE,
vfs::VmoFile::Sharing::CLONE_COW);
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
loop.StartThread("vfs test thread");
auto file_ptr = OpenAsFile(&file, loop.dispatcher(), /*writable=*/true);
ASSERT_TRUE(file_ptr.is_bound());
// Writes should succeed.
{
std::vector<uint8_t> value{'a', 'b', 'c', 'd'};
fuchsia::io::File2_WriteAt_Result result;
EXPECT_EQ(ZX_OK, file_ptr->WriteAt(value, 0, &result));
EXPECT_TRUE(result.is_response()) << zx_status_get_string(result.err());
EXPECT_EQ(value.size(), result.response().actual_count);
}
// Reading the VMO should match reading the file.
{
std::vector<uint8_t> vmo_result = ReadVmo(test_vmo, 0, 4096);
fuchsia::io::File2_ReadAt_Result result;
EXPECT_EQ(ZX_OK, file_ptr->ReadAt(4096, 0, &result));
EXPECT_TRUE(result.is_response()) << zx_status_get_string(result.err());
EXPECT_EQ(vmo_result, result.response().data);
EXPECT_EQ('a', result.response().data[0]);
}
// Describing the VMO duplicates the handle, and we can access the entire VMO.
fuchsia::io::NodeInfo info;
EXPECT_EQ(ZX_OK, file_ptr->Describe(&info));
ASSERT_TRUE(info.is_vmofile());
ASSERT_EQ(4096u, info.vmofile().length);
ASSERT_EQ(0u, info.vmofile().offset);
EXPECT_EQ(ReadVmo(test_vmo, 0, 4096), ReadVmo(info.vmofile().vmo, 0, 4096));
// Writing should succeed on the new VMO, due to copy on write.
EXPECT_EQ(ZX_OK, info.vmofile().vmo.write("test", 0, 4));
EXPECT_NE(ReadVmo(test_vmo, 0, 4096), ReadVmo(info.vmofile().vmo, 0, 4096));
}
TEST(VmoFile, VmoWithNoRights) {
// Create a VmoFile wrapping 1000 bytes of the VMO starting at offset 24.
// The vmo we use has no rights, so reading, writing, and duplication will
// fail.
zx::vmo test_vmo = MakeTestVmo();
zx::vmo bad_vmo;
ASSERT_EQ(ZX_OK, test_vmo.duplicate(0, &bad_vmo));
vfs::VmoFile file(std::move(bad_vmo), 24, 1000, vfs::VmoFile::WriteOption::WRITABLE);
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
loop.StartThread("vfs test thread");
auto file_ptr = OpenAsFile(&file, loop.dispatcher(), /*writable=*/true);
ASSERT_TRUE(file_ptr.is_bound());
// Writes should fail.
{
std::vector<uint8_t> value{'a', 'b', 'c', 'd'};
fuchsia::io::File2_WriteAt_Result result;
EXPECT_EQ(ZX_OK, file_ptr->WriteAt(value, 0, &result));
EXPECT_TRUE(result.is_err());
EXPECT_EQ(ZX_ERR_ACCESS_DENIED, result.err());
}
// Reading should fail.
{
fuchsia::io::File2_ReadAt_Result result;
EXPECT_EQ(ZX_OK, file_ptr->ReadAt(1000, 0, &result));
EXPECT_TRUE(result.is_err());
EXPECT_EQ(ZX_ERR_ACCESS_DENIED, result.err());
}
// Describing the VMO should close the connection.
fuchsia::io::NodeInfo info;
EXPECT_EQ(ZX_ERR_PEER_CLOSED, file_ptr->Describe(&info));
}
TEST(VmoFile, UnalignedCopyOnWrite) {
// Create a VmoFile wrapping 1000 bytes of the VMO starting at offset 24.
// This offset is not page-aligned, so cloning will fail.
zx::vmo test_vmo = MakeTestVmo();
vfs::VmoFile file(zx::unowned_vmo(test_vmo), 24, 1000, vfs::VmoFile::WriteOption::WRITABLE,
vfs::VmoFile::Sharing::CLONE_COW);
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
loop.StartThread("vfs test thread");
auto file_ptr = OpenAsFile(&file, loop.dispatcher(), /*writable=*/true);
ASSERT_TRUE(file_ptr.is_bound());
// Writes should succeed.
{
std::vector<uint8_t> value{'a', 'b', 'c', 'd'};
fuchsia::io::File2_WriteAt_Result result;
EXPECT_EQ(ZX_OK, file_ptr->WriteAt(value, 0, &result));
EXPECT_TRUE(result.is_response()) << zx_status_get_string(result.err());
EXPECT_EQ(value.size(), result.response().actual_count);
}
// Reading the VMO from offset 24 should match reading the file from offset 0.
{
std::vector<uint8_t> vmo_result = ReadVmo(test_vmo, 24, 1000);
fuchsia::io::File2_ReadAt_Result result;
EXPECT_EQ(ZX_OK, file_ptr->ReadAt(1000, 0, &result));
EXPECT_TRUE(result.is_response()) << zx_status_get_string(result.err());
EXPECT_EQ(vmo_result, result.response().data);
EXPECT_EQ('a', result.response().data[0]);
}
// Describing the VMO should close the connection.
fuchsia::io::NodeInfo info;
EXPECT_EQ(ZX_ERR_PEER_CLOSED, file_ptr->Describe(&info));
}
} // namespace