| // 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/vfs/cpp/pseudo_file.h" |
| |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/async-loop/default.h> |
| #include <lib/fdio/fd.h> |
| #include <unistd.h> |
| #include <zircon/errors.h> |
| #include <zircon/status.h> |
| |
| #include <algorithm> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "fuchsia/io/cpp/fidl.h" |
| #include "src/lib/testing/loop_fixture/real_loop_fixture.h" |
| |
| namespace { |
| |
| class FileWrapper { |
| public: |
| const std::string& buffer() { return buffer_; } |
| |
| vfs::PseudoFile* file() { return file_.get(); } |
| |
| static FileWrapper CreateReadWriteFile(std::string initial_str, size_t capacity, |
| bool start_loop = true) { |
| return FileWrapper(true, std::move(initial_str), capacity, start_loop); |
| } |
| |
| static FileWrapper CreateReadOnlyFile(std::string initial_str, bool start_loop = true) { |
| size_t length = initial_str.length(); |
| return FileWrapper(false, std::move(initial_str), length, start_loop); |
| } |
| |
| async_dispatcher_t* dispatcher() { return loop_.dispatcher(); } |
| |
| async::Loop& loop() { return loop_; } |
| |
| private: |
| FileWrapper(bool write_allowed, std::string initial_str, size_t capacity, bool start_loop) |
| : buffer_(std::move(initial_str)), loop_(&kAsyncLoopConfigNoAttachToCurrentThread) { |
| auto readFn = [this](std::vector<uint8_t>* output, size_t max_file_size) { |
| output->resize(buffer_.length()); |
| std::copy(buffer_.begin(), buffer_.end(), output->begin()); |
| return ZX_OK; |
| }; |
| |
| vfs::PseudoFile::WriteHandler writeFn; |
| if (write_allowed) { |
| writeFn = [this](std::vector<uint8_t> input) { |
| std::string str(input.size(), 0); |
| std::copy(input.begin(), input.end(), str.begin()); |
| buffer_ = std::move(str); |
| return ZX_OK; |
| }; |
| } |
| |
| file_ = std::make_unique<vfs::PseudoFile>(capacity, std::move(readFn), std::move(writeFn)); |
| if (start_loop) { |
| loop_.StartThread("vfs test thread"); |
| } |
| } |
| |
| std::unique_ptr<vfs::PseudoFile> file_; |
| std::string buffer_; |
| async::Loop loop_; |
| }; |
| |
| class PseudoFileTest : public gtest::RealLoopFixture { |
| protected: |
| void AssertOpen(vfs::internal::Node* node, async_dispatcher_t* dispatcher, |
| fuchsia::io::OpenFlags flags, zx_status_t expected_status, |
| bool test_on_open_event = true) { |
| fuchsia::io::NodePtr node_ptr; |
| if (test_on_open_event) { |
| flags |= fuchsia::io::OpenFlags::DESCRIBE; |
| } |
| EXPECT_EQ(expected_status, node->Serve(flags, node_ptr.NewRequest().TakeChannel(), dispatcher)); |
| |
| if (test_on_open_event) { |
| bool on_open_called = false; |
| node_ptr.events().OnOpen = [&](zx_status_t status, |
| std::unique_ptr<fuchsia::io::NodeInfo> info) { |
| EXPECT_FALSE(on_open_called); // should be called only once |
| on_open_called = true; |
| EXPECT_EQ(expected_status, status); |
| if (expected_status == ZX_OK) { |
| ASSERT_NE(info.get(), nullptr); |
| EXPECT_TRUE(info->is_file()); |
| } else { |
| EXPECT_EQ(info.get(), nullptr); |
| } |
| }; |
| |
| RunLoopUntil([&]() { return on_open_called; }); |
| } |
| } |
| |
| static fuchsia::io::FileSyncPtr OpenReadWrite(vfs::internal::Node* node, |
| async_dispatcher_t* dispatcher) { |
| return OpenFile(node, |
| fuchsia::io::OpenFlags::RIGHT_READABLE | fuchsia::io::OpenFlags::RIGHT_WRITABLE, |
| dispatcher); |
| } |
| |
| static fuchsia::io::FileSyncPtr OpenRead(vfs::internal::Node* node, |
| async_dispatcher_t* dispatcher) { |
| return OpenFile(node, fuchsia::io::OpenFlags::RIGHT_READABLE, dispatcher); |
| } |
| |
| static fuchsia::io::FileSyncPtr OpenFile(vfs::internal::Node* node, fuchsia::io::OpenFlags flags, |
| async_dispatcher_t* dispatcher) { |
| fuchsia::io::FileSyncPtr ptr; |
| node->Serve(flags, ptr.NewRequest().TakeChannel(), dispatcher); |
| return ptr; |
| } |
| |
| static void AssertWriteAt(fuchsia::io::FileSyncPtr& file, const std::string& str, uint64_t offset, |
| zx_status_t expected_status = ZX_OK, int expected_actual = -1) { |
| std::vector<uint8_t> buffer; |
| buffer.resize(str.length()); |
| std::copy(str.begin(), str.end(), buffer.begin()); |
| fuchsia::io::File2_WriteAt_Result result; |
| ASSERT_EQ(ZX_OK, file->WriteAt(buffer, offset, &result)); |
| if (expected_status == ZX_OK) { |
| ASSERT_TRUE(result.is_response()) << zx_status_get_string(result.err()); |
| ASSERT_EQ(expected_actual == -1 ? str.length() : expected_actual, |
| result.response().actual_count); |
| } else { |
| ASSERT_TRUE(result.is_err()); |
| ASSERT_EQ(expected_status, result.err()); |
| } |
| } |
| |
| static void AssertWrite(fuchsia::io::FileSyncPtr& file, const std::string& str, |
| zx_status_t expected_status = ZX_OK, int expected_actual = -1) { |
| std::vector<uint8_t> buffer; |
| buffer.resize(str.length()); |
| std::copy(str.begin(), str.end(), buffer.begin()); |
| fuchsia::io::File2_Write_Result result; |
| ASSERT_EQ(ZX_OK, file->Write(buffer, &result)); |
| if (expected_status == ZX_OK) { |
| ASSERT_TRUE(result.is_response()) << zx_status_get_string(result.err()); |
| ASSERT_EQ(expected_actual == -1 ? str.length() : expected_actual, |
| result.response().actual_count); |
| } else { |
| ASSERT_TRUE(result.is_err()); |
| ASSERT_EQ(expected_status, result.err()); |
| } |
| } |
| |
| static void AssertReadAt(fuchsia::io::FileSyncPtr& file, uint64_t offset, uint64_t count, |
| const std::string& expected_str, zx_status_t expected_status = ZX_OK) { |
| fuchsia::io::File2_ReadAt_Result result; |
| ASSERT_EQ(ZX_OK, file->ReadAt(count, offset, &result)); |
| if (expected_status == ZX_OK) { |
| ASSERT_TRUE(result.is_response()) << zx_status_get_string(result.err()); |
| const std::vector<uint8_t>& data = result.response().data; |
| ASSERT_EQ(expected_str, |
| std::string_view(reinterpret_cast<const char*>(data.data()), data.size())); |
| } else { |
| ASSERT_TRUE(result.is_err()); |
| ASSERT_EQ(expected_status, result.err()); |
| } |
| } |
| |
| static void AssertRead(fuchsia::io::FileSyncPtr& file, uint64_t count, |
| const std::string& expected_str, zx_status_t expected_status = ZX_OK) { |
| fuchsia::io::File2_Read_Result result; |
| ASSERT_EQ(ZX_OK, file->Read(count, &result)); |
| if (expected_status == ZX_OK) { |
| ASSERT_TRUE(result.is_response()) << zx_status_get_string(result.err()); |
| const std::vector<uint8_t>& data = result.response().data; |
| ASSERT_EQ(expected_str, |
| std::string_view(reinterpret_cast<const char*>(data.data()), data.size())); |
| } else { |
| ASSERT_TRUE(result.is_err()); |
| ASSERT_EQ(expected_status, result.err()); |
| } |
| } |
| |
| static void AssertResize(fuchsia::io::FileSyncPtr& file, uint64_t count, |
| zx_status_t expected_status = ZX_OK) { |
| fuchsia::io::File2_Resize_Result result; |
| ASSERT_EQ(ZX_OK, file->Resize(count, &result)); |
| if (expected_status == ZX_OK) { |
| ASSERT_TRUE(result.is_response()) << zx_status_get_string(result.err()); |
| } else { |
| ASSERT_TRUE(result.is_err()); |
| ASSERT_EQ(expected_status, result.err()); |
| } |
| } |
| |
| static void AssertSeek(fuchsia::io::FileSyncPtr& file, int64_t offset, |
| fuchsia::io::SeekOrigin origin, uint64_t expected_offset, |
| zx_status_t expected_status = ZX_OK) { |
| fuchsia::io::File2_Seek_Result result; |
| ASSERT_EQ(ZX_OK, file->Seek(origin, offset, &result)); |
| if (expected_status == ZX_OK) { |
| ASSERT_TRUE(result.is_response()) << zx_status_get_string(result.err()); |
| ASSERT_EQ(expected_offset, result.response().offset_from_start); |
| } else { |
| ASSERT_TRUE(result.is_err()); |
| ASSERT_EQ(expected_status, result.err()); |
| } |
| } |
| |
| static void CloseFile(fuchsia::io::FileSyncPtr& file, zx_status_t expected_status = ZX_OK) { |
| fuchsia::io::Node2_Close_Result result; |
| ASSERT_EQ(ZX_OK, file->Close(&result)); |
| if (expected_status == ZX_OK) { |
| ASSERT_TRUE(result.is_response()) << zx_status_get_string(result.err()); |
| } else { |
| ASSERT_TRUE(result.is_err()); |
| EXPECT_EQ(expected_status, result.err()); |
| } |
| } |
| |
| void AssertFileWrapperState(FileWrapper& file_wrapper, const std::string& expected_str) { |
| RunLoopUntil([&]() { return file_wrapper.buffer() == expected_str; }); |
| } |
| |
| static int OpenAsFD(vfs::internal::Node* node, async_dispatcher_t* dispatcher) { |
| zx::channel local, remote; |
| EXPECT_EQ(ZX_OK, zx::channel::create(0, &local, &remote)); |
| EXPECT_EQ(ZX_OK, node->Serve(fuchsia::io::OpenFlags::RIGHT_READABLE | |
| fuchsia::io::OpenFlags::RIGHT_WRITABLE, |
| std::move(remote), dispatcher)); |
| int fd = -1; |
| EXPECT_EQ(ZX_OK, fdio_fd_create(local.release(), &fd)); |
| return fd; |
| } |
| }; |
| |
| TEST_F(PseudoFileTest, ServeOnInValidFlagsForReadWriteFile) { |
| auto file_wrapper = FileWrapper::CreateReadWriteFile("test_str", 100, false); |
| { |
| SCOPED_TRACE("OPEN_FLAG_DIRECTORY"); |
| AssertOpen(file_wrapper.file(), dispatcher(), fuchsia::io::OpenFlags::DIRECTORY, |
| ZX_ERR_NOT_DIR); |
| } |
| fuchsia::io::OpenFlags not_allowed_flags[] = {fuchsia::io::OpenFlags::CREATE, |
| fuchsia::io::OpenFlags::CREATE_IF_ABSENT, |
| fuchsia::io::OpenFlags::APPEND}; |
| for (auto not_allowed_flag : not_allowed_flags) { |
| SCOPED_TRACE(std::to_string(static_cast<uint32_t>(not_allowed_flag))); |
| AssertOpen(file_wrapper.file(), dispatcher(), not_allowed_flag, ZX_ERR_NOT_SUPPORTED); |
| } |
| } |
| |
| TEST_F(PseudoFileTest, ServeOnInValidFlagsForReadOnlyFile) { |
| auto file_wrapper = FileWrapper::CreateReadOnlyFile("test_str"); |
| { |
| SCOPED_TRACE("OPEN_FLAG_DIRECTORY"); |
| AssertOpen(file_wrapper.file(), dispatcher(), fuchsia::io::OpenFlags::DIRECTORY, |
| ZX_ERR_NOT_DIR); |
| } |
| fuchsia::io::OpenFlags not_allowed_flags[] = { |
| fuchsia::io::OpenFlags::CREATE, fuchsia::io::OpenFlags::CREATE_IF_ABSENT, |
| fuchsia::io::OpenFlags::RIGHT_WRITABLE, fuchsia::io::OpenFlags::TRUNCATE, |
| fuchsia::io::OpenFlags::APPEND}; |
| for (auto not_allowed_flag : not_allowed_flags) { |
| SCOPED_TRACE(std::to_string(static_cast<uint32_t>(not_allowed_flag))); |
| AssertOpen(file_wrapper.file(), dispatcher(), not_allowed_flag, ZX_ERR_NOT_SUPPORTED); |
| } |
| } |
| |
| TEST_F(PseudoFileTest, ServeOnValidFlagsForReadWriteFile) { |
| auto file_wrapper = FileWrapper::CreateReadWriteFile("test_str", 100, false); |
| fuchsia::io::OpenFlags allowed_flags[] = { |
| fuchsia::io::OpenFlags::RIGHT_READABLE, fuchsia::io::OpenFlags::RIGHT_WRITABLE, |
| fuchsia::io::OpenFlags::NODE_REFERENCE, fuchsia::io::OpenFlags::TRUNCATE, |
| fuchsia::io::OpenFlags::NOT_DIRECTORY}; |
| for (auto allowed_flag : allowed_flags) { |
| SCOPED_TRACE(std::to_string(static_cast<uint32_t>(allowed_flag))); |
| AssertOpen(file_wrapper.file(), dispatcher(), allowed_flag, ZX_OK); |
| } |
| } |
| |
| TEST_F(PseudoFileTest, ServeOnValidFlagsForReadOnlyFile) { |
| auto file_wrapper = FileWrapper::CreateReadOnlyFile("test_str", false); |
| fuchsia::io::OpenFlags allowed_flags[] = {fuchsia::io::OpenFlags::RIGHT_READABLE, |
| fuchsia::io::OpenFlags::NODE_REFERENCE}; |
| for (auto allowed_flag : allowed_flags) { |
| SCOPED_TRACE(std::to_string(static_cast<uint32_t>(allowed_flag))); |
| AssertOpen(file_wrapper.file(), dispatcher(), allowed_flag, ZX_OK); |
| } |
| } |
| |
| TEST_F(PseudoFileTest, Simple) { |
| auto file_wrapper = FileWrapper::CreateReadWriteFile("test_str", 100); |
| |
| int fd = OpenAsFD(file_wrapper.file(), file_wrapper.dispatcher()); |
| ASSERT_LE(0, fd); |
| |
| char buffer[1024]; |
| memset(buffer, 0, sizeof(buffer)); |
| ASSERT_EQ(5, pread(fd, buffer, 5, 0)); |
| EXPECT_STREQ("test_", buffer); |
| |
| ASSERT_EQ(4, write(fd, "abcd", 4)); |
| ASSERT_EQ(5, pread(fd, buffer, 5, 0)); |
| EXPECT_STREQ("abcd_", buffer); |
| |
| ASSERT_GE(0, close(fd)); |
| file_wrapper.loop().RunUntilIdle(); |
| |
| AssertFileWrapperState(file_wrapper, "abcd_str"); |
| } |
| |
| TEST_F(PseudoFileTest, WriteAt) { |
| const std::string str = "this is a test string"; |
| auto file_wrapper = FileWrapper::CreateReadWriteFile(str, 100); |
| auto file = OpenReadWrite(file_wrapper.file(), file_wrapper.dispatcher()); |
| |
| AssertWriteAt(file, "was", 5); |
| |
| const std::string updated_str = "this wasa test string"; |
| // confirm by reading |
| AssertRead(file, str.length(), updated_str); |
| |
| // make sure file was not updated before connection was closed. |
| ASSERT_EQ(file_wrapper.buffer(), str); |
| |
| CloseFile(file); |
| |
| AssertFileWrapperState(file_wrapper, updated_str); |
| } |
| |
| TEST_F(PseudoFileTest, MultipleWriteAt) { |
| const std::string str = "this is a test string"; |
| auto file_wrapper = FileWrapper::CreateReadWriteFile(str, 100); |
| auto file = OpenReadWrite(file_wrapper.file(), file_wrapper.dispatcher()); |
| |
| AssertWriteAt(file, "was", 5); |
| |
| AssertWriteAt(file, "tests", 10); |
| |
| const std::string updated_str = "this wasa testsstring"; |
| // confirm by reading |
| AssertRead(file, str.length(), updated_str); |
| |
| // make sure file was not updated before connection was closed. |
| ASSERT_EQ(file_wrapper.buffer(), str); |
| |
| CloseFile(file); |
| |
| AssertFileWrapperState(file_wrapper, updated_str); |
| } |
| |
| TEST_F(PseudoFileTest, ReadAt) { |
| const std::string str = "this is a test string"; |
| auto file_wrapper = FileWrapper::CreateReadWriteFile(str, 100); |
| auto file = OpenReadWrite(file_wrapper.file(), file_wrapper.dispatcher()); |
| |
| AssertReadAt(file, 5, 10, str.substr(5, 10)); |
| |
| // try one more |
| AssertReadAt(file, 15, 5, str.substr(15, 5)); |
| } |
| |
| TEST_F(PseudoFileTest, Read) { |
| const std::string str = "this is a test string"; |
| auto file_wrapper = FileWrapper::CreateReadWriteFile(str, 100); |
| auto file = OpenReadWrite(file_wrapper.file(), file_wrapper.dispatcher()); |
| |
| AssertRead(file, 10, str.substr(0, 10)); |
| |
| // offset should have moved |
| AssertRead(file, 10, str.substr(10, 10)); |
| } |
| |
| TEST_F(PseudoFileTest, GetAttr) { |
| const std::string str = "this is a test string"; |
| auto file_wrapper = FileWrapper::CreateReadWriteFile(str, 100); |
| auto file = OpenReadWrite(file_wrapper.file(), file_wrapper.dispatcher()); |
| |
| zx_status_t status; |
| fuchsia::io::NodeAttributes attr; |
| ASSERT_EQ(ZX_OK, file->GetAttr(&status, &attr)); |
| ASSERT_EQ(ZX_OK, status); |
| ASSERT_NE(0u, attr.mode & fuchsia::io::MODE_TYPE_FILE); |
| ASSERT_EQ(21u, attr.content_size); |
| } |
| |
| TEST_F(PseudoFileTest, Write) { |
| const std::string str = "this is a test string"; |
| auto file_wrapper = FileWrapper::CreateReadWriteFile(str, 100); |
| auto file = OpenReadWrite(file_wrapper.file(), file_wrapper.dispatcher()); |
| |
| AssertWrite(file, "It"); |
| |
| // offset should have moved |
| AssertWrite(file, " is"); |
| |
| const std::string updated_str = "It isis a test string"; |
| |
| AssertReadAt(file, 0, 100, updated_str); |
| |
| // make sure file was not updated before connection was closed. |
| ASSERT_EQ(file_wrapper.buffer(), str); |
| |
| CloseFile(file); |
| |
| // make sure file was updated |
| AssertFileWrapperState(file_wrapper, updated_str); |
| } |
| |
| TEST_F(PseudoFileTest, Truncate) { |
| const std::string str = "this is a test string"; |
| auto file_wrapper = FileWrapper::CreateReadWriteFile(str, 100); |
| auto file = OpenReadWrite(file_wrapper.file(), file_wrapper.dispatcher()); |
| |
| AssertResize(file, 10); |
| |
| AssertRead(file, 100, str.substr(0, 10)); |
| |
| // make sure file was not updated before connection was closed. |
| ASSERT_EQ(file_wrapper.buffer(), str); |
| |
| CloseFile(file); |
| |
| // make sure file was updated |
| AssertFileWrapperState(file_wrapper, str.substr(0, 10)); |
| } |
| |
| TEST_F(PseudoFileTest, SeekFromStart) { |
| const std::string str = "this is a test string"; |
| auto file_wrapper = FileWrapper::CreateReadOnlyFile(str); |
| auto file = OpenRead(file_wrapper.file(), file_wrapper.dispatcher()); |
| |
| AssertSeek(file, 5, fuchsia::io::SeekOrigin::START, 5); |
| |
| AssertRead(file, 100, str.substr(5)); |
| } |
| |
| TEST_F(PseudoFileTest, SeekFromCurent) { |
| const std::string str = "this is a test string"; |
| auto file_wrapper = FileWrapper::CreateReadOnlyFile(str); |
| auto file = OpenRead(file_wrapper.file(), file_wrapper.dispatcher()); |
| |
| AssertSeek(file, 5, fuchsia::io::SeekOrigin::START, 5); |
| |
| AssertSeek(file, 10, fuchsia::io::SeekOrigin::CURRENT, 15); |
| |
| AssertRead(file, 100, str.substr(15)); |
| } |
| |
| TEST_F(PseudoFileTest, SeekFromEnd) { |
| const std::string str = "this is a test string"; |
| auto file_wrapper = FileWrapper::CreateReadOnlyFile(str); |
| auto file = OpenRead(file_wrapper.file(), file_wrapper.dispatcher()); |
| |
| AssertSeek(file, -2, fuchsia::io::SeekOrigin::END, str.length() - 2); |
| |
| AssertRead(file, 100, str.substr(str.length() - 2)); |
| } |
| |
| TEST_F(PseudoFileTest, SeekFromEndWith0Offset) { |
| const std::string str = "this is a test string"; |
| auto file_wrapper = FileWrapper::CreateReadOnlyFile(str); |
| auto file = OpenRead(file_wrapper.file(), file_wrapper.dispatcher()); |
| |
| AssertSeek(file, 0, fuchsia::io::SeekOrigin::END, str.length()); |
| |
| AssertRead(file, 100, ""); |
| } |
| |
| TEST_F(PseudoFileTest, SeekFailsIfOffsetMoreThanCapacity) { |
| const std::string str = "this is a test string"; |
| auto file_wrapper = FileWrapper::CreateReadOnlyFile(str); |
| auto file = OpenRead(file_wrapper.file(), file_wrapper.dispatcher()); |
| |
| AssertSeek(file, 1, fuchsia::io::SeekOrigin::END, 0, ZX_ERR_OUT_OF_RANGE); |
| |
| // make sure offset did not change |
| AssertRead(file, 100, str); |
| } |
| |
| TEST_F(PseudoFileTest, WriteafterEndOfFile) { |
| const std::string str = "this is a test string"; |
| auto file_wrapper = FileWrapper::CreateReadWriteFile(str, 100); |
| auto file = OpenReadWrite(file_wrapper.file(), file_wrapper.dispatcher()); |
| |
| AssertSeek(file, 5, fuchsia::io::SeekOrigin::END, str.length() + 5); |
| |
| AssertWrite(file, "is"); |
| |
| auto updated_str = str; |
| updated_str.append(5, 0).append("is"); |
| |
| AssertReadAt(file, 0, 100, updated_str); |
| |
| // make sure file was not updated before connection was closed. |
| ASSERT_EQ(file_wrapper.buffer(), str); |
| |
| CloseFile(file); |
| |
| // make sure file was updated |
| AssertFileWrapperState(file_wrapper, updated_str); |
| } |
| |
| TEST_F(PseudoFileTest, WriteFailsForReadOnly) { |
| const std::string str = "this is a test string"; |
| auto file_wrapper = FileWrapper::CreateReadWriteFile(str, 100); |
| auto file = OpenRead(file_wrapper.file(), file_wrapper.dispatcher()); |
| |
| AssertWrite(file, "is", ZX_ERR_BAD_HANDLE, 0); |
| |
| CloseFile(file); |
| |
| // make sure file was not updated |
| AssertFileWrapperState(file_wrapper, str); |
| } |
| |
| TEST_F(PseudoFileTest, WriteAtFailsForReadOnly) { |
| const std::string str = "this is a test string"; |
| auto file_wrapper = FileWrapper::CreateReadWriteFile(str, 100); |
| auto file = OpenRead(file_wrapper.file(), file_wrapper.dispatcher()); |
| |
| AssertWriteAt(file, "is", 0, ZX_ERR_BAD_HANDLE, 0); |
| |
| CloseFile(file); |
| |
| // make sure file was not updated |
| AssertFileWrapperState(file_wrapper, str); |
| } |
| |
| TEST_F(PseudoFileTest, TruncateFailsForReadOnly) { |
| const std::string str = "this is a test string"; |
| auto file_wrapper = FileWrapper::CreateReadWriteFile(str, 100); |
| auto file = OpenRead(file_wrapper.file(), file_wrapper.dispatcher()); |
| |
| AssertResize(file, 10, ZX_ERR_BAD_HANDLE); |
| |
| CloseFile(file); |
| |
| // make sure file was not updated |
| AssertFileWrapperState(file_wrapper, str); |
| } |
| |
| TEST_F(PseudoFileTest, ReadAtFailsForWriteOnly) { |
| const std::string str = "this is a test string"; |
| auto file_wrapper = FileWrapper::CreateReadWriteFile(str, 100); |
| auto file = OpenFile(file_wrapper.file(), fuchsia::io::OpenFlags::RIGHT_WRITABLE, |
| file_wrapper.dispatcher()); |
| |
| AssertReadAt(file, 0, 10, "", ZX_ERR_BAD_HANDLE); |
| } |
| |
| TEST_F(PseudoFileTest, ReadFailsForWriteOnly) { |
| const std::string str = "this is a test string"; |
| auto file_wrapper = FileWrapper::CreateReadWriteFile(str, 100); |
| auto file = OpenFile(file_wrapper.file(), fuchsia::io::OpenFlags::RIGHT_WRITABLE, |
| file_wrapper.dispatcher()); |
| |
| AssertRead(file, 10, "", ZX_ERR_BAD_HANDLE); |
| } |
| |
| TEST_F(PseudoFileTest, CapacityisSameAsFileContentSize) { |
| const std::string str = "this is a test string"; |
| auto file_wrapper = FileWrapper::CreateReadWriteFile(str, str.length()); |
| auto file = OpenFile(file_wrapper.file(), fuchsia::io::OpenFlags::RIGHT_READABLE, |
| file_wrapper.dispatcher()); |
| |
| AssertRead(file, str.length(), str); |
| } |
| |
| TEST_F(PseudoFileTest, OpenFailsForOverFlowingFile) { |
| const std::string str = "this is a test string"; |
| auto file_wrapper = FileWrapper::CreateReadWriteFile(str, str.length() - 1); |
| AssertOpen(file_wrapper.file(), file_wrapper.dispatcher(), fuchsia::io::OpenFlags::RIGHT_READABLE, |
| ZX_ERR_FILE_BIG); |
| } |
| |
| TEST_F(PseudoFileTest, CantReadNodeReferenceFile) { |
| const std::string str = "this is a test string"; |
| auto file_wrapper = FileWrapper::CreateReadWriteFile(str, 100); |
| auto file = OpenFile(file_wrapper.file(), fuchsia::io::OpenFlags::NODE_REFERENCE, |
| file_wrapper.dispatcher()); |
| // make sure node reference was opened |
| zx_status_t status; |
| fuchsia::io::NodeAttributes attr; |
| ASSERT_EQ(ZX_OK, file->GetAttr(&status, &attr)); |
| ASSERT_EQ(ZX_OK, status); |
| ASSERT_NE(0u, attr.mode | fuchsia::io::MODE_TYPE_FILE); |
| |
| fuchsia::io::File2_Read_Result result; |
| ASSERT_EQ(ZX_ERR_PEER_CLOSED, file->Read(100, &result)); |
| } |
| |
| TEST_F(PseudoFileTest, CanCloneFileConnectionAndReadAndWrite) { |
| const std::string str = "this is a test string"; |
| auto file_wrapper = FileWrapper::CreateReadWriteFile(str, 100); |
| auto file = OpenReadWrite(file_wrapper.file(), file_wrapper.dispatcher()); |
| |
| fuchsia::io::FileSyncPtr cloned_file; |
| ASSERT_EQ(ZX_OK, file->Clone(fuchsia::io::OpenFlags::CLONE_SAME_RIGHTS, |
| fidl::InterfaceRequest<fuchsia::io::Node>( |
| cloned_file.NewRequest().TakeChannel()))); |
| |
| CloseFile(file); |
| |
| AssertWrite(cloned_file, "It"); |
| |
| const std::string updated_str = "Itis is a test string"; |
| |
| AssertReadAt(cloned_file, 0, 100, updated_str); |
| |
| // make sure file was not updated before connection was closed. |
| ASSERT_EQ(file_wrapper.buffer(), str); |
| |
| CloseFile(cloned_file); |
| |
| // make sure file was updated |
| AssertFileWrapperState(file_wrapper, updated_str); |
| } |
| |
| TEST_F(PseudoFileTest, NodeReferenceIsClonedAsNodeReference) { |
| const std::string str = "this is a test string"; |
| auto file_wrapper = FileWrapper::CreateReadWriteFile(str, 100); |
| auto file = OpenFile(file_wrapper.file(), fuchsia::io::OpenFlags::NODE_REFERENCE, |
| file_wrapper.dispatcher()); |
| |
| fuchsia::io::FileSyncPtr cloned_file; |
| ASSERT_EQ(ZX_OK, file->Clone(fuchsia::io::OpenFlags::CLONE_SAME_RIGHTS, |
| fidl::InterfaceRequest<fuchsia::io::Node>( |
| cloned_file.NewRequest().TakeChannel()))); |
| CloseFile(file); |
| |
| // make sure node reference was opened |
| zx_status_t status; |
| fuchsia::io::NodeAttributes attr; |
| ASSERT_EQ(ZX_OK, cloned_file->GetAttr(&status, &attr)); |
| ASSERT_EQ(ZX_OK, status); |
| ASSERT_NE(0u, attr.mode | fuchsia::io::MODE_TYPE_FILE); |
| |
| fuchsia::io::File2_Read_Result result; |
| ASSERT_EQ(ZX_ERR_PEER_CLOSED, cloned_file->Read(100, &result)); |
| } |
| |
| class FileWrapperWithFailingWriteFn { |
| public: |
| vfs::PseudoFile* file() { return file_.get(); } |
| |
| static FileWrapperWithFailingWriteFn Create(std::string initial_str, size_t capacity, |
| zx_status_t write_error) { |
| return FileWrapperWithFailingWriteFn(std::move(initial_str), capacity, write_error); |
| } |
| |
| async_dispatcher_t* dispatcher() { return loop_.dispatcher(); } |
| |
| async::Loop& loop() { return loop_; } |
| |
| private: |
| FileWrapperWithFailingWriteFn(std::string initial_str, size_t capacity, zx_status_t write_error) |
| : buffer_(std::move(initial_str)), loop_(&kAsyncLoopConfigNoAttachToCurrentThread) { |
| auto readFn = [this](std::vector<uint8_t>* output, size_t max_file_size) { |
| output->resize(buffer_.length()); |
| std::copy(buffer_.begin(), buffer_.end(), output->begin()); |
| return ZX_OK; |
| }; |
| |
| vfs::PseudoFile::WriteHandler writeFn; |
| writeFn = [this, write_error](std::vector<uint8_t> input) { |
| std::string str(input.size(), 0); |
| std::copy(input.begin(), input.end(), str.begin()); |
| buffer_ = std::move(str); |
| return write_error; |
| }; |
| |
| file_ = std::make_unique<vfs::PseudoFile>(capacity, std::move(readFn), std::move(writeFn)); |
| loop_.StartThread("vfs test thread"); |
| } |
| |
| std::unique_ptr<vfs::PseudoFile> file_; |
| std::string buffer_; |
| async::Loop loop_; |
| }; |
| |
| TEST_F(PseudoFileTest, CloseReturnsWriteError) { |
| const std::string str = "this is a test string"; |
| auto file_wrapper = FileWrapperWithFailingWriteFn::Create(str, 100, ZX_ERR_IO); |
| auto file = OpenReadWrite(file_wrapper.file(), file_wrapper.dispatcher()); |
| |
| AssertWrite(file, "It"); |
| |
| CloseFile(file, ZX_ERR_IO); |
| } |
| |
| } // namespace |