| // Copyright 2020 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/zx/event.h> |
| #include <lib/zx/stream.h> |
| #include <lib/zx/time.h> |
| #include <lib/zx/vmo.h> |
| #include <limits.h> |
| #include <stdio.h> |
| #include <unistd.h> |
| #include <zircon/syscalls/object.h> |
| #include <zircon/system/public/zircon/syscalls.h> |
| #include <zircon/system/utest/core/pager/userpager.h> |
| #include <zircon/types.h> |
| |
| #include <numeric> |
| #include <string> |
| #include <thread> |
| #include <vector> |
| |
| #include <zxtest/zxtest.h> |
| |
| namespace { |
| |
| // This value corresponds to `VmObject::LookupInfo::kMaxPages` |
| static constexpr uint64_t kMaxPagesBatch = 16; |
| |
| void CheckRights(const zx::stream& stream, zx_rights_t expected_rights, const char* message) { |
| zx_info_handle_basic_t info = {}; |
| EXPECT_OK(stream.get_info(ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, nullptr)); |
| printf("CheckRights: %s\n", message); |
| EXPECT_EQ(expected_rights, info.rights); |
| } |
| |
| TEST(StreamTestCase, Create) { |
| zx_handle_t raw_stream = ZX_HANDLE_INVALID; |
| ASSERT_EQ(ZX_ERR_BAD_HANDLE, zx_stream_create(0, ZX_HANDLE_INVALID, 0, &raw_stream)); |
| |
| zx::event event; |
| ASSERT_OK(zx::event::create(0, &event)); |
| ASSERT_EQ(ZX_ERR_WRONG_TYPE, zx_stream_create(0, event.get(), 0, &raw_stream)); |
| |
| zx::vmo vmo; |
| ASSERT_OK(zx::vmo::create(zx_system_get_page_size() * 4, 0, &vmo)); |
| size_t content_size = 0u; |
| ASSERT_OK(vmo.set_property(ZX_PROP_VMO_CONTENT_SIZE, &content_size, sizeof(content_size))); |
| |
| static_assert(!(ZX_DEFAULT_STREAM_RIGHTS & ZX_RIGHT_WRITE), |
| "Streams are not writable by default"); |
| static_assert(!(ZX_DEFAULT_STREAM_RIGHTS & ZX_RIGHT_READ), "Streams are not readable by default"); |
| |
| zx::stream stream; |
| |
| ASSERT_EQ(ZX_ERR_INVALID_ARGS, zx::stream::create(-42, vmo, 0, &stream)); |
| |
| ASSERT_OK(zx::stream::create(0, vmo, 0, &stream)); |
| CheckRights(stream, ZX_DEFAULT_STREAM_RIGHTS, "Default"); |
| |
| ASSERT_OK(zx::stream::create(ZX_STREAM_MODE_READ, vmo, 0, &stream)); |
| CheckRights(stream, ZX_DEFAULT_STREAM_RIGHTS | ZX_RIGHT_READ, "ZX_STREAM_MODE_READ"); |
| |
| ASSERT_OK(zx::stream::create(ZX_STREAM_MODE_WRITE, vmo, 0, &stream)); |
| CheckRights(stream, ZX_DEFAULT_STREAM_RIGHTS | ZX_RIGHT_WRITE, "ZX_STREAM_MODE_WRITE"); |
| |
| ASSERT_OK(zx::stream::create(ZX_STREAM_MODE_READ | ZX_STREAM_MODE_WRITE, vmo, 0, &stream)); |
| CheckRights(stream, ZX_DEFAULT_STREAM_RIGHTS | ZX_RIGHT_READ | ZX_RIGHT_WRITE, |
| "ZX_STREAM_MODE_READ | ZX_STREAM_MODE_WRITE"); |
| |
| ASSERT_OK(zx::stream::create(ZX_STREAM_MODE_APPEND, vmo, 0, &stream)); |
| CheckRights(stream, ZX_DEFAULT_STREAM_RIGHTS, "ZX_STREAM_MODE_APPEND"); |
| |
| ASSERT_OK(zx::stream::create(ZX_STREAM_MODE_READ | ZX_STREAM_MODE_APPEND, vmo, 0, &stream)); |
| CheckRights(stream, ZX_DEFAULT_STREAM_RIGHTS | ZX_RIGHT_READ, |
| "ZX_STREAM_MODE_READ | ZX_STREAM_MODE_APPEND"); |
| |
| ASSERT_OK(zx::stream::create(ZX_STREAM_MODE_WRITE | ZX_STREAM_MODE_APPEND, vmo, 0, &stream)); |
| CheckRights(stream, ZX_DEFAULT_STREAM_RIGHTS | ZX_RIGHT_WRITE, |
| "ZX_STREAM_MODE_WRITE | ZX_STREAM_MODE_APPEND"); |
| |
| ASSERT_OK(zx::stream::create(ZX_STREAM_MODE_READ | ZX_STREAM_MODE_WRITE | ZX_STREAM_MODE_APPEND, |
| vmo, 0, &stream)); |
| CheckRights(stream, ZX_DEFAULT_STREAM_RIGHTS | ZX_RIGHT_READ | ZX_RIGHT_WRITE, |
| "ZX_STREAM_MODE_READ | ZX_STREAM_MODE_WRITE | ZX_STREAM_MODE_APPEND"); |
| |
| { |
| zx::vmo read_only; |
| vmo.duplicate(ZX_RIGHT_READ, &read_only); |
| |
| ASSERT_OK(zx::stream::create(0, read_only, 0, &stream)); |
| CheckRights(stream, ZX_DEFAULT_STREAM_RIGHTS, "read_only: Default"); |
| |
| ASSERT_OK(zx::stream::create(ZX_STREAM_MODE_READ, read_only, 0, &stream)); |
| CheckRights(stream, ZX_DEFAULT_STREAM_RIGHTS | ZX_RIGHT_READ, "read_only: ZX_STREAM_MODE_READ"); |
| |
| ASSERT_EQ(ZX_ERR_ACCESS_DENIED, |
| zx::stream::create(ZX_STREAM_MODE_WRITE, read_only, 0, &stream)); |
| ASSERT_EQ(ZX_ERR_ACCESS_DENIED, zx::stream::create(ZX_STREAM_MODE_READ | ZX_STREAM_MODE_WRITE, |
| read_only, 0, &stream)); |
| } |
| |
| { |
| zx::vmo write_only; |
| vmo.duplicate(ZX_RIGHT_WRITE, &write_only); |
| |
| ASSERT_OK(zx::stream::create(0, write_only, 0, &stream)); |
| CheckRights(stream, ZX_DEFAULT_STREAM_RIGHTS, "write_only: Default"); |
| |
| ASSERT_EQ(ZX_ERR_ACCESS_DENIED, |
| zx::stream::create(ZX_STREAM_MODE_READ, write_only, 0, &stream)); |
| |
| ASSERT_OK(zx::stream::create(ZX_STREAM_MODE_WRITE, write_only, 0, &stream)); |
| CheckRights(stream, ZX_DEFAULT_STREAM_RIGHTS | ZX_RIGHT_WRITE, |
| "write_only: ZX_STREAM_MODE_WRITE"); |
| |
| ASSERT_EQ(ZX_ERR_ACCESS_DENIED, zx::stream::create(ZX_STREAM_MODE_READ | ZX_STREAM_MODE_WRITE, |
| write_only, 0, &stream)); |
| } |
| |
| { |
| zx::vmo none; |
| vmo.duplicate(0, &none); |
| |
| ASSERT_OK(zx::stream::create(0, none, 0, &stream)); |
| CheckRights(stream, ZX_DEFAULT_STREAM_RIGHTS, "none: Default"); |
| |
| ASSERT_EQ(ZX_ERR_ACCESS_DENIED, zx::stream::create(ZX_STREAM_MODE_READ, none, 0, &stream)); |
| ASSERT_EQ(ZX_ERR_ACCESS_DENIED, zx::stream::create(ZX_STREAM_MODE_WRITE, none, 0, &stream)); |
| ASSERT_EQ(ZX_ERR_ACCESS_DENIED, |
| zx::stream::create(ZX_STREAM_MODE_READ | ZX_STREAM_MODE_WRITE, none, 0, &stream)); |
| } |
| } |
| |
| TEST(StreamTestCase, Seek) { |
| zx::vmo vmo; |
| ASSERT_OK(zx::vmo::create(zx_system_get_page_size() * 4, 0, &vmo)); |
| size_t content_size = 42u; |
| ASSERT_OK(vmo.set_property(ZX_PROP_VMO_CONTENT_SIZE, &content_size, sizeof(content_size))); |
| |
| zx::stream stream; |
| zx_off_t result = 81u; |
| |
| ASSERT_OK(zx::stream::create(0, vmo, 0, &stream)); |
| ASSERT_EQ(ZX_ERR_ACCESS_DENIED, stream.seek(ZX_STREAM_SEEK_ORIGIN_START, 0, &result)); |
| |
| ASSERT_OK(zx::stream::create(ZX_STREAM_MODE_READ, vmo, 9, &stream)); |
| ASSERT_OK(stream.seek(ZX_STREAM_SEEK_ORIGIN_CURRENT, 0, &result)); |
| EXPECT_EQ(9u, result); |
| |
| ASSERT_OK(zx::stream::create(ZX_STREAM_MODE_WRITE, vmo, 518, &stream)); |
| ASSERT_OK(stream.seek(ZX_STREAM_SEEK_ORIGIN_CURRENT, 0, &result)); |
| EXPECT_EQ(518u, result); |
| |
| ASSERT_OK(zx::stream::create(ZX_STREAM_MODE_READ | ZX_STREAM_MODE_WRITE, vmo, 0, &stream)); |
| |
| ASSERT_EQ(ZX_ERR_INVALID_ARGS, stream.seek(34893, 12, &result)); |
| ASSERT_EQ(ZX_ERR_INVALID_ARGS, stream.seek(ZX_STREAM_SEEK_ORIGIN_START, -10, &result)); |
| ASSERT_OK(stream.seek(ZX_STREAM_SEEK_ORIGIN_START, 10, &result)); |
| EXPECT_EQ(10u, result); |
| ASSERT_OK(stream.seek(ZX_STREAM_SEEK_ORIGIN_START, 12, &result)); |
| EXPECT_EQ(12u, result); |
| |
| ASSERT_EQ(ZX_ERR_INVALID_ARGS, stream.seek(ZX_STREAM_SEEK_ORIGIN_CURRENT, -21, &result)); |
| ASSERT_OK(stream.seek(ZX_STREAM_SEEK_ORIGIN_CURRENT, 3, &result)); |
| EXPECT_EQ(15u, result); |
| ASSERT_OK(stream.seek(ZX_STREAM_SEEK_ORIGIN_CURRENT, -15, &result)); |
| EXPECT_EQ(0u, result); |
| ASSERT_OK(stream.seek(ZX_STREAM_SEEK_ORIGIN_CURRENT, INT64_MAX, &result)); |
| EXPECT_EQ(static_cast<zx_off_t>(INT64_MAX), result); |
| |
| ASSERT_OK(stream.seek(ZX_STREAM_SEEK_ORIGIN_CURRENT, 1038, &result)); |
| EXPECT_EQ(static_cast<zx_off_t>(INT64_MAX) + 1038, result); |
| ASSERT_EQ(ZX_ERR_INVALID_ARGS, stream.seek(ZX_STREAM_SEEK_ORIGIN_CURRENT, INT64_MAX, &result)); |
| |
| ASSERT_OK(stream.seek(ZX_STREAM_SEEK_ORIGIN_END, 0, &result)); |
| EXPECT_EQ(content_size, result); |
| ASSERT_OK(stream.seek(ZX_STREAM_SEEK_ORIGIN_END, -11, &result)); |
| EXPECT_EQ(31u, result); |
| ASSERT_OK(stream.seek(ZX_STREAM_SEEK_ORIGIN_END, -13, &result)); |
| EXPECT_EQ(29u, result); |
| ASSERT_OK(stream.seek(ZX_STREAM_SEEK_ORIGIN_END, -content_size, &result)); |
| EXPECT_EQ(0u, result); |
| ASSERT_OK(stream.seek(ZX_STREAM_SEEK_ORIGIN_END, 24, &result)); |
| EXPECT_EQ(66u, result); |
| ASSERT_EQ(ZX_ERR_INVALID_ARGS, stream.seek(ZX_STREAM_SEEK_ORIGIN_END, -1238, &result)); |
| |
| content_size = UINT64_MAX; |
| ASSERT_OK(vmo.set_property(ZX_PROP_VMO_CONTENT_SIZE, &content_size, sizeof(content_size))); |
| ASSERT_OK(stream.seek(ZX_STREAM_SEEK_ORIGIN_END, -11, &result)); |
| EXPECT_EQ(UINT64_MAX - 11u, result); |
| ASSERT_EQ(ZX_ERR_INVALID_ARGS, stream.seek(ZX_STREAM_SEEK_ORIGIN_END, 5, &result)); |
| |
| ASSERT_OK(stream.seek(ZX_STREAM_SEEK_ORIGIN_START, 0, nullptr)); |
| } |
| |
| const char kAlphabet[] = "abcdefghijklmnopqrstuvwxyz"; |
| |
| TEST(StreamTestCase, ReadV) { |
| zx::vmo vmo; |
| ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmo)); |
| ASSERT_OK(vmo.write(kAlphabet, 0u, strlen(kAlphabet))); |
| size_t content_size = 26u; |
| ASSERT_OK(vmo.set_property(ZX_PROP_VMO_CONTENT_SIZE, &content_size, sizeof(content_size))); |
| |
| zx::stream stream; |
| char buffer[16] = {}; |
| zx_iovec_t vec = { |
| .buffer = buffer, |
| .capacity = sizeof(buffer), |
| }; |
| size_t actual = 42u; |
| |
| ASSERT_OK(zx::stream::create(0, vmo, 0, &stream)); |
| ASSERT_EQ(ZX_ERR_ACCESS_DENIED, stream.readv(0, &vec, 1, &actual)); |
| |
| ASSERT_OK(zx::stream::create(ZX_STREAM_MODE_READ, vmo, 0, &stream)); |
| vec.capacity = 7u; |
| ASSERT_OK(stream.readv(0, &vec, 1, &actual)); |
| EXPECT_EQ(7u, actual); |
| EXPECT_STREQ("abcdefg", buffer); |
| memset(buffer, 0, sizeof(buffer)); |
| |
| ASSERT_EQ(ZX_ERR_INVALID_ARGS, stream.readv(24098, &vec, 1, &actual)); |
| ASSERT_EQ(ZX_ERR_INVALID_ARGS, stream.readv(0, nullptr, 1, &actual)); |
| ASSERT_EQ(ZX_ERR_INVALID_ARGS, stream.readv(0, nullptr, 0, &actual)); |
| |
| vec.capacity = 3u; |
| ASSERT_OK(stream.readv(0, &vec, 1, nullptr)); |
| EXPECT_STREQ("hij", buffer); |
| memset(buffer, 0, sizeof(buffer)); |
| |
| vec.buffer = nullptr; |
| vec.capacity = 7u; |
| ASSERT_EQ(ZX_ERR_NOT_FOUND, stream.readv(0, &vec, 1, &actual)); |
| vec.buffer = buffer; |
| |
| const size_t kVectorCount = 7; |
| zx_iovec_t multivec[kVectorCount] = {}; |
| for (size_t i = 0; i < kVectorCount; ++i) { |
| multivec[i].buffer = buffer; |
| multivec[i].capacity = INT64_MAX; |
| } |
| |
| ASSERT_EQ(ZX_ERR_INVALID_ARGS, stream.readv(0, multivec, kVectorCount, &actual)); |
| |
| vec.capacity = sizeof(buffer); |
| ASSERT_OK(stream.readv(0, &vec, 1, &actual)); |
| memset(buffer, 0, sizeof(buffer)); |
| |
| content_size = 6u; |
| ASSERT_OK(vmo.set_property(ZX_PROP_VMO_CONTENT_SIZE, &content_size, sizeof(content_size))); |
| |
| vec.capacity = 3u; |
| actual = 42u; |
| ASSERT_OK(stream.readv(0, &vec, 1, &actual)); |
| EXPECT_EQ(0u, actual); |
| memset(buffer, 0, sizeof(buffer)); |
| |
| ASSERT_OK(stream.seek(ZX_STREAM_SEEK_ORIGIN_START, 0, nullptr)); |
| vec.capacity = 12u; |
| actual = 42u; |
| ASSERT_OK(stream.readv(0, &vec, 1, &actual)); |
| EXPECT_EQ(6u, actual); |
| EXPECT_STREQ("abcdef", buffer); |
| memset(buffer, 0, sizeof(buffer)); |
| |
| content_size = 26u; |
| ASSERT_OK(vmo.set_property(ZX_PROP_VMO_CONTENT_SIZE, &content_size, sizeof(content_size))); |
| |
| for (size_t i = 0; i < kVectorCount; ++i) { |
| multivec[i].buffer = &buffer[i]; |
| multivec[i].capacity = 1; |
| } |
| |
| ASSERT_OK(stream.seek(ZX_STREAM_SEEK_ORIGIN_START, 0, nullptr)); |
| ASSERT_OK(stream.readv(0, multivec, kVectorCount, &actual)); |
| EXPECT_EQ(kVectorCount, actual); |
| EXPECT_STREQ("abcdef", buffer); |
| memset(buffer, 0, sizeof(buffer)); |
| } |
| |
| std::string GetData(const zx::vmo& vmo) { |
| std::vector<char> buffer(zx_system_get_page_size(), '\0'); |
| EXPECT_OK(vmo.read(buffer.data(), 0, buffer.size())); |
| return std::string(buffer.data()); |
| } |
| |
| TEST(StreamTestCase, WriteV) { |
| zx::vmo vmo; |
| ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmo)); |
| ASSERT_OK(vmo.write(kAlphabet, 0u, strlen(kAlphabet))); |
| size_t content_size = 26u; |
| ASSERT_OK(vmo.set_property(ZX_PROP_VMO_CONTENT_SIZE, &content_size, sizeof(content_size))); |
| |
| zx::stream stream; |
| char buffer[17] = "0123456789ABCDEF"; |
| zx_iovec_t vec = { |
| .buffer = buffer, |
| .capacity = sizeof(buffer), |
| }; |
| size_t actual = 42u; |
| |
| ASSERT_OK(zx::stream::create(0, vmo, 0, &stream)); |
| ASSERT_EQ(ZX_ERR_ACCESS_DENIED, stream.writev(0, &vec, 1, &actual)); |
| |
| ASSERT_OK(zx::stream::create(ZX_STREAM_MODE_WRITE, vmo, 0, &stream)); |
| vec.capacity = 7u; |
| ASSERT_OK(stream.writev(0, &vec, 1, &actual)); |
| EXPECT_EQ(7u, actual); |
| EXPECT_STREQ("0123456hijklmnopqrstuvwxyz", GetData(vmo).c_str()); |
| ASSERT_OK(vmo.write(kAlphabet, 0u, strlen(kAlphabet))); |
| |
| ASSERT_EQ(ZX_ERR_INVALID_ARGS, stream.writev(24098, &vec, 1, &actual)); |
| ASSERT_EQ(ZX_ERR_INVALID_ARGS, stream.writev(0, nullptr, 1, &actual)); |
| ASSERT_EQ(ZX_ERR_INVALID_ARGS, stream.writev(0, nullptr, 0, &actual)); |
| |
| vec.capacity = 3u; |
| ASSERT_OK(stream.writev(0, &vec, 1, nullptr)); |
| EXPECT_STREQ("abcdefg012klmnopqrstuvwxyz", GetData(vmo).c_str()); |
| ASSERT_OK(vmo.write(kAlphabet, 0u, strlen(kAlphabet))); |
| |
| vec.buffer = nullptr; |
| vec.capacity = 7u; |
| ASSERT_EQ(ZX_ERR_NOT_FOUND, stream.writev(0, &vec, 1, &actual)); |
| vec.buffer = buffer; |
| |
| const size_t kVectorCount = 7; |
| zx_iovec_t multivec[kVectorCount] = {}; |
| for (size_t i = 0; i < kVectorCount; ++i) { |
| multivec[i].buffer = buffer; |
| multivec[i].capacity = INT64_MAX; |
| } |
| |
| ASSERT_EQ(ZX_ERR_INVALID_ARGS, stream.writev(0, multivec, kVectorCount, &actual)); |
| |
| for (size_t i = 0; i < kVectorCount; ++i) { |
| multivec[kVectorCount - i - 1].buffer = &buffer[i]; |
| multivec[kVectorCount - i - 1].capacity = 1; |
| } |
| |
| ASSERT_OK(stream.seek(ZX_STREAM_SEEK_ORIGIN_START, 0, nullptr)); |
| ASSERT_OK(stream.writev(0, multivec, kVectorCount, &actual)); |
| EXPECT_EQ(kVectorCount, actual); |
| EXPECT_STREQ("6543210hijklmnopqrstuvwxyz", GetData(vmo).c_str()); |
| ASSERT_OK(vmo.write(kAlphabet, 0u, strlen(kAlphabet))); |
| } |
| |
| size_t GetContentSize(const zx::vmo& vmo) { |
| size_t content_size = 45684651u; |
| EXPECT_OK(vmo.get_property(ZX_PROP_VMO_CONTENT_SIZE, &content_size, sizeof(content_size))); |
| return content_size; |
| } |
| |
| TEST(StreamTestCase, WriteExtendsContentSize) { |
| zx::vmo vmo; |
| ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmo)); |
| ASSERT_OK(vmo.write(kAlphabet, 0u, strlen(kAlphabet))); |
| size_t content_size = 3u; |
| ASSERT_OK(vmo.set_property(ZX_PROP_VMO_CONTENT_SIZE, &content_size, sizeof(content_size))); |
| |
| zx::stream stream; |
| char buffer[17] = "0123456789ABCDEF"; |
| zx_iovec_t vec = { |
| .buffer = buffer, |
| .capacity = sizeof(buffer), |
| }; |
| size_t actual = 42u; |
| |
| ASSERT_OK(zx::stream::create(ZX_STREAM_MODE_WRITE, vmo, 0, &stream)); |
| vec.capacity = 7u; |
| ASSERT_OK(stream.writev(0, &vec, 1, &actual)); |
| EXPECT_EQ(7u, actual); |
| EXPECT_STREQ("0123456", GetData(vmo).c_str()); |
| EXPECT_EQ(7u, GetContentSize(vmo)); |
| ASSERT_OK(vmo.write(kAlphabet, 0u, strlen(kAlphabet))); |
| |
| vec.capacity = 2u; |
| ASSERT_OK(stream.writev(0, &vec, 1, &actual)); |
| EXPECT_EQ(2u, actual); |
| EXPECT_STREQ("abcdefg01jklmnopqrstuvwxyz", GetData(vmo).c_str()); |
| EXPECT_EQ(9u, GetContentSize(vmo)); |
| ASSERT_OK(vmo.write(kAlphabet, 0u, strlen(kAlphabet))); |
| |
| ASSERT_OK(stream.seek(ZX_STREAM_SEEK_ORIGIN_START, 0, nullptr)); |
| |
| vec.capacity = 10u; |
| for (size_t i = 1; i * 10 < zx_system_get_page_size(); ++i) { |
| ASSERT_OK(stream.writev(0, &vec, 1, &actual)); |
| EXPECT_EQ(10u, actual); |
| } |
| EXPECT_EQ(4090u, GetContentSize(vmo)); |
| |
| actual = 9823u; |
| EXPECT_EQ(ZX_OK, stream.writev(0, &vec, 1, &actual)); |
| EXPECT_EQ(6u, actual); |
| EXPECT_EQ(4096u, GetContentSize(vmo)); |
| |
| char scratch[17] = {}; |
| ASSERT_OK(vmo.read(scratch, 4090u, 6u)); |
| EXPECT_STREQ("012345", scratch); |
| |
| actual = 9823u; |
| ASSERT_EQ(ZX_ERR_NO_SPACE, stream.writev(0, &vec, 1, &actual)); |
| EXPECT_EQ(4096u, GetContentSize(vmo)); |
| } |
| |
| TEST(StreamTestCase, WriteExtendsVMOSize) { |
| zx::vmo vmo; |
| ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), ZX_VMO_RESIZABLE, &vmo)); |
| size_t content_size = 0u; |
| ASSERT_OK(vmo.set_property(ZX_PROP_VMO_CONTENT_SIZE, &content_size, sizeof(content_size))); |
| |
| zx::stream stream; |
| char buffer[17] = "0123456789ABCDEF"; |
| zx_iovec_t vec = { |
| .buffer = buffer, |
| .capacity = sizeof(buffer), |
| }; |
| size_t actual = 42u; |
| |
| ASSERT_OK(zx::stream::create(ZX_STREAM_MODE_WRITE, vmo, 0, &stream)); |
| vec.capacity = 10u; |
| for (size_t i = 1; i * 10 < zx_system_get_page_size(); ++i) { |
| ASSERT_OK(stream.writev(0, &vec, 1, &actual)); |
| EXPECT_EQ(10u, actual); |
| } |
| EXPECT_EQ(4090u, GetContentSize(vmo)); |
| |
| actual = 9823u; |
| ASSERT_OK(stream.writev(0, &vec, 1, &actual)); |
| EXPECT_EQ(10u, actual); |
| EXPECT_EQ(4100u, GetContentSize(vmo)); |
| |
| uint64_t vmo_size = 839u; |
| ASSERT_OK(vmo.get_size(&vmo_size)); |
| EXPECT_EQ(zx_system_get_page_size() * 2, vmo_size); |
| |
| vec.capacity = UINT64_MAX; |
| actual = 5423u; |
| ASSERT_EQ(ZX_ERR_FILE_BIG, stream.writev(0, &vec, 1, &actual)); |
| |
| ASSERT_OK(vmo.get_size(&vmo_size)); |
| EXPECT_EQ(zx_system_get_page_size() * 2, vmo_size); |
| } |
| |
| TEST(StreamTestCase, ReadVAt) { |
| zx::vmo vmo; |
| ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmo)); |
| ASSERT_OK(vmo.write(kAlphabet, 0u, strlen(kAlphabet))); |
| size_t content_size = 26u; |
| ASSERT_OK(vmo.set_property(ZX_PROP_VMO_CONTENT_SIZE, &content_size, sizeof(content_size))); |
| |
| zx::stream stream; |
| char buffer[16] = {}; |
| zx_iovec_t vec = { |
| .buffer = buffer, |
| .capacity = sizeof(buffer), |
| }; |
| size_t actual = 42u; |
| |
| ASSERT_OK(zx::stream::create(ZX_STREAM_MODE_READ, vmo, 0, &stream)); |
| vec.capacity = 7u; |
| ASSERT_OK(stream.readv_at(0, 24u, &vec, 1, &actual)); |
| EXPECT_EQ(2u, actual); |
| EXPECT_STREQ("yz", buffer); |
| memset(buffer, 0, sizeof(buffer)); |
| |
| zx_off_t seek = 39u; |
| ASSERT_OK(stream.seek(ZX_STREAM_SEEK_ORIGIN_CURRENT, 0, &seek)); |
| EXPECT_EQ(0u, seek); |
| |
| ASSERT_OK(stream.readv_at(0, 36u, &vec, 1, &actual)); |
| EXPECT_EQ(0u, actual); |
| EXPECT_STREQ("", buffer); |
| memset(buffer, 0, sizeof(buffer)); |
| |
| ASSERT_OK(stream.readv_at(0, 3645651u, &vec, 1, &actual)); |
| EXPECT_EQ(0u, actual); |
| EXPECT_STREQ("", buffer); |
| memset(buffer, 0, sizeof(buffer)); |
| } |
| |
| TEST(StreamTestCase, WriteVAt) { |
| zx::vmo vmo; |
| ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmo)); |
| ASSERT_OK(vmo.write(kAlphabet, 0u, strlen(kAlphabet))); |
| size_t content_size = 26u; |
| ASSERT_OK(vmo.set_property(ZX_PROP_VMO_CONTENT_SIZE, &content_size, sizeof(content_size))); |
| |
| zx::stream stream; |
| char buffer[17] = "0123456789ABCDEF"; |
| zx_iovec_t vec = { |
| .buffer = buffer, |
| .capacity = sizeof(buffer), |
| }; |
| size_t actual = 42u; |
| |
| ASSERT_OK(zx::stream::create(ZX_STREAM_MODE_WRITE, vmo, 0, &stream)); |
| vec.capacity = 3u; |
| ASSERT_OK(stream.writev_at(0, 7, &vec, 1, &actual)); |
| EXPECT_EQ(3u, actual); |
| EXPECT_STREQ("abcdefg012klmnopqrstuvwxyz", GetData(vmo).c_str()); |
| ASSERT_OK(vmo.write(kAlphabet, 0u, strlen(kAlphabet))); |
| |
| zx_off_t seek = 39u; |
| ASSERT_OK(stream.seek(ZX_STREAM_SEEK_ORIGIN_CURRENT, 0, &seek)); |
| EXPECT_EQ(0u, seek); |
| |
| vec.capacity = 10u; |
| actual = 9823u; |
| ASSERT_EQ(ZX_ERR_NO_SPACE, stream.writev_at(0, 4100u, &vec, 1, &actual)); |
| |
| ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), ZX_VMO_RESIZABLE, &vmo)); |
| ASSERT_OK(zx::stream::create(ZX_STREAM_MODE_WRITE, vmo, 0, &stream)); |
| |
| vec.capacity = 10u; |
| actual = 9823u; |
| ASSERT_OK(stream.writev_at(0, 4090, &vec, 1, &actual)); |
| EXPECT_EQ(10u, actual); |
| EXPECT_EQ(4100u, GetContentSize(vmo)); |
| |
| uint64_t vmo_size = 839u; |
| ASSERT_OK(vmo.get_size(&vmo_size)); |
| EXPECT_EQ(zx_system_get_page_size() * 2, vmo_size); |
| |
| vec.capacity = UINT64_MAX; |
| actual = 5423u; |
| ASSERT_EQ(ZX_ERR_FILE_BIG, stream.writev_at(0, 5414u, &vec, 1, &actual)); |
| |
| ASSERT_OK(vmo.get_size(&vmo_size)); |
| EXPECT_EQ(zx_system_get_page_size() * 2, vmo_size); |
| EXPECT_EQ(4100u, GetContentSize(vmo)); |
| |
| zx_iovec_t bad_vec = { |
| .buffer = nullptr, |
| .capacity = 42u, |
| }; |
| |
| actual = 5423u; |
| ASSERT_NOT_OK(stream.writev_at(0, 5000u, &bad_vec, 1, &actual)); |
| ASSERT_EQ(4100u, GetContentSize(vmo)); |
| } |
| |
| TEST(StreamTestCase, ReadVectorAlias) { |
| zx::vmo vmo; |
| ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmo)); |
| ASSERT_OK(vmo.write(kAlphabet, 0u, strlen(kAlphabet))); |
| size_t content_size = 26u; |
| ASSERT_OK(vmo.set_property(ZX_PROP_VMO_CONTENT_SIZE, &content_size, sizeof(content_size))); |
| |
| zx::stream stream; |
| const size_t kVectorCount = 7; |
| zx_iovec_t multivec[kVectorCount] = {}; |
| for (size_t i = 0; i < kVectorCount; ++i) { |
| multivec[i].buffer = multivec; // Notice the alias. |
| multivec[i].capacity = sizeof(multivec); |
| } |
| |
| ASSERT_OK(zx::stream::create(ZX_STREAM_MODE_READ, vmo, 0, &stream)); |
| size_t actual = 42u; |
| EXPECT_OK(stream.readv(0, multivec, kVectorCount, &actual)); |
| ASSERT_EQ(26u, actual); |
| } |
| |
| TEST(StreamTestCase, Append) { |
| zx::vmo vmo; |
| ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmo)); |
| ASSERT_OK(vmo.write(kAlphabet, 0u, strlen(kAlphabet))); |
| size_t content_size = 26u; |
| ASSERT_OK(vmo.set_property(ZX_PROP_VMO_CONTENT_SIZE, &content_size, sizeof(content_size))); |
| |
| zx::stream stream; |
| char buffer[17] = "0123456789ABCDEF"; |
| zx_iovec_t vec = { |
| .buffer = buffer, |
| .capacity = sizeof(buffer), |
| }; |
| |
| ASSERT_OK(zx::stream::create(ZX_STREAM_MODE_WRITE, vmo, 0, &stream)); |
| { |
| zx_info_stream_t info{}; |
| ASSERT_OK(stream.get_info(ZX_INFO_STREAM, &info, sizeof(info), nullptr, nullptr)); |
| EXPECT_EQ(ZX_STREAM_MODE_WRITE, info.options); |
| EXPECT_EQ(0u, info.seek); |
| EXPECT_EQ(26u, info.content_size); |
| } |
| |
| vec.capacity = 7u; |
| size_t actual = 42u; |
| ASSERT_OK(stream.writev(ZX_STREAM_APPEND, &vec, 1, &actual)); |
| EXPECT_EQ(7u, actual); |
| EXPECT_STREQ("abcdefghijklmnopqrstuvwxyz0123456", GetData(vmo).c_str()); |
| |
| { |
| zx_info_stream_t info{}; |
| ASSERT_OK(stream.get_info(ZX_INFO_STREAM, &info, sizeof(info), nullptr, nullptr)); |
| EXPECT_EQ(ZX_STREAM_MODE_WRITE, info.options); |
| EXPECT_EQ(33u, info.seek); |
| EXPECT_EQ(33u, info.content_size); |
| |
| vec.capacity = 26u; |
| for (size_t size = info.content_size; size + vec.capacity < zx_system_get_page_size(); |
| size += vec.capacity) { |
| ASSERT_OK(stream.writev(ZX_STREAM_APPEND, &vec, 1, &actual)); |
| EXPECT_EQ(vec.capacity, actual); |
| } |
| } |
| |
| { |
| zx_info_stream_t info{}; |
| ASSERT_OK(stream.get_info(ZX_INFO_STREAM, &info, sizeof(info), nullptr, nullptr)); |
| EXPECT_GT(zx_system_get_page_size(), info.content_size); |
| |
| EXPECT_OK(stream.writev(ZX_STREAM_APPEND, &vec, 1, &actual)); |
| EXPECT_EQ(zx_system_get_page_size() - info.content_size, actual); |
| } |
| |
| ASSERT_EQ(ZX_ERR_NO_SPACE, stream.writev(ZX_STREAM_APPEND, &vec, 1, &actual)); |
| |
| vec.capacity = UINT64_MAX; |
| ASSERT_EQ(ZX_ERR_OUT_OF_RANGE, stream.writev(ZX_STREAM_APPEND, &vec, 1, &actual)); |
| } |
| |
| TEST(StreamTestCase, WriteVectorWithStreamInAppendMode) { |
| zx::vmo vmo; |
| ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmo)); |
| ASSERT_OK(vmo.write(kAlphabet, 0u, strlen(kAlphabet))); |
| size_t content_size = 26u; |
| ASSERT_OK(vmo.set_property(ZX_PROP_VMO_CONTENT_SIZE, &content_size, sizeof(content_size))); |
| |
| zx::stream stream; |
| char buffer[17] = "0123456789ABCDEF"; |
| zx_iovec_t vec = { |
| .buffer = buffer, |
| .capacity = sizeof(buffer), |
| }; |
| |
| ASSERT_OK(zx::stream::create(ZX_STREAM_MODE_WRITE | ZX_STREAM_MODE_APPEND, vmo, 0, &stream)); |
| { |
| zx_info_stream_t info{}; |
| ASSERT_OK(stream.get_info(ZX_INFO_STREAM, &info, sizeof(info), nullptr, nullptr)); |
| EXPECT_EQ(ZX_STREAM_MODE_WRITE | ZX_STREAM_MODE_APPEND, info.options); |
| EXPECT_EQ(0u, info.seek); |
| EXPECT_EQ(26u, info.content_size); |
| } |
| |
| vec.capacity = 7u; |
| size_t actual = 42u; |
| ASSERT_OK(stream.writev(0, &vec, 1, &actual)); |
| EXPECT_EQ(7u, actual); |
| EXPECT_STREQ("abcdefghijklmnopqrstuvwxyz0123456", GetData(vmo).c_str()); |
| |
| { |
| zx_info_stream_t info{}; |
| ASSERT_OK(stream.get_info(ZX_INFO_STREAM, &info, sizeof(info), nullptr, nullptr)); |
| EXPECT_EQ(ZX_STREAM_MODE_WRITE | ZX_STREAM_MODE_APPEND, info.options); |
| EXPECT_EQ(33u, info.seek); |
| EXPECT_EQ(33u, info.content_size); |
| |
| vec.capacity = 26u; |
| for (size_t size = info.content_size; size + vec.capacity < zx_system_get_page_size(); |
| size += vec.capacity) { |
| ASSERT_OK(stream.writev(0, &vec, 1, &actual)); |
| EXPECT_EQ(vec.capacity, actual); |
| } |
| } |
| |
| { |
| zx_info_stream_t info{}; |
| ASSERT_OK(stream.get_info(ZX_INFO_STREAM, &info, sizeof(info), nullptr, nullptr)); |
| EXPECT_GT(zx_system_get_page_size(), info.content_size); |
| |
| ASSERT_OK(stream.writev(0, &vec, 1, &actual)); |
| EXPECT_EQ(zx_system_get_page_size() - info.content_size, actual); |
| } |
| |
| ASSERT_EQ(ZX_ERR_NO_SPACE, stream.writev(0, &vec, 1, nullptr)); |
| |
| vec.capacity = UINT64_MAX; |
| ASSERT_EQ(ZX_ERR_OUT_OF_RANGE, stream.writev(0, &vec, 1, nullptr)); |
| } |
| |
| TEST(StreamTestCase, PropertyModeAppend) { |
| zx::vmo vmo; |
| ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmo)); |
| ASSERT_OK(vmo.set_prop_content_size(0)); |
| |
| zx::stream stream; |
| char buffer[] = "0123456789ABCDEF"; |
| zx_iovec_t vec = { |
| .buffer = buffer, |
| .capacity = 16, |
| }; |
| |
| // Create the stream not in append mode. |
| ASSERT_OK(zx::stream::create(ZX_STREAM_MODE_WRITE, vmo, 0, &stream)); |
| ASSERT_OK(stream.writev(0, &vec, 1, nullptr)); |
| |
| { |
| zx_info_stream_t info{}; |
| ASSERT_OK(stream.get_info(ZX_INFO_STREAM, &info, sizeof(info), nullptr, nullptr)); |
| EXPECT_FALSE(info.options & ZX_STREAM_MODE_APPEND); |
| EXPECT_EQ(16u, info.seek); |
| EXPECT_EQ(16u, info.content_size); |
| uint8_t mode_append; |
| ASSERT_OK(stream.get_prop_mode_append(&mode_append)); |
| EXPECT_FALSE(mode_append); |
| } |
| |
| // Switch the stream to append mode. |
| ASSERT_OK(stream.set_prop_mode_append(true)); |
| { |
| zx_info_stream_t info{}; |
| ASSERT_OK(stream.get_info(ZX_INFO_STREAM, &info, sizeof(info), nullptr, nullptr)); |
| EXPECT_TRUE(info.options & ZX_STREAM_MODE_APPEND); |
| uint8_t mode_append; |
| ASSERT_OK(stream.get_prop_mode_append(&mode_append)); |
| EXPECT_TRUE(mode_append); |
| } |
| ASSERT_OK(stream.seek(ZX_STREAM_SEEK_ORIGIN_START, 10, nullptr)); |
| ASSERT_OK(stream.writev(0, &vec, 1, nullptr)); |
| EXPECT_STREQ("0123456789ABCDEF0123456789ABCDEF", GetData(vmo).c_str()); |
| |
| // Take the stream out of append mode. |
| ASSERT_OK(stream.set_prop_mode_append(false)); |
| { |
| zx_info_stream_t info{}; |
| ASSERT_OK(stream.get_info(ZX_INFO_STREAM, &info, sizeof(info), nullptr, nullptr)); |
| EXPECT_FALSE(info.options & ZX_STREAM_MODE_APPEND); |
| // The previous write appended to the stream despite the seek offset not being at the end of the |
| // stream. |
| EXPECT_EQ(32u, info.seek); |
| EXPECT_EQ(32u, info.content_size); |
| uint8_t mode_append; |
| ASSERT_OK(stream.get_prop_mode_append(&mode_append)); |
| EXPECT_FALSE(mode_append); |
| } |
| ASSERT_OK(stream.seek(ZX_STREAM_SEEK_ORIGIN_START, 10, nullptr)); |
| ASSERT_OK(stream.writev(0, &vec, 1, nullptr)); |
| EXPECT_STREQ("01234567890123456789ABCDEFABCDEF", GetData(vmo).c_str()); |
| } |
| |
| TEST(StreamTestCase, AppendWithMultipleThreads) { |
| // kThreadCount threads collectively write the numbers 0 to kBufferSize-1 to the vmo. |
| constexpr uint64_t kThreadCount = 4; |
| constexpr uint64_t kBufferSize = 256; |
| constexpr uint64_t kIterationCount = kBufferSize / kThreadCount; |
| zx::vmo vmo; |
| ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmo)); |
| ASSERT_OK(vmo.set_prop_content_size(0)); |
| |
| std::vector<uint8_t> buffer(kBufferSize, 0); |
| std::iota(buffer.begin(), buffer.end(), 0); |
| |
| std::vector<std::thread> threads; |
| threads.reserve(kThreadCount); |
| for (uint64_t thread = 0; thread < kThreadCount; ++thread) { |
| threads.emplace_back([&vmo, &buffer, thread]() { |
| zx::stream stream; |
| ASSERT_OK(zx::stream::create(ZX_STREAM_MODE_WRITE | ZX_STREAM_MODE_APPEND, vmo, 0, &stream)); |
| for (uint64_t i = 0; i < kIterationCount; ++i) { |
| zx_iovec_t vec = { |
| .buffer = &buffer[thread * kIterationCount + i], |
| .capacity = 1, |
| }; |
| ASSERT_OK(stream.writev(0, &vec, 1, nullptr)); |
| } |
| }); |
| } |
| for (auto& thread : threads) { |
| thread.join(); |
| } |
| |
| // With several threads simultaneously appending, the data is likely out of order but none of the |
| // appends should have overwritten each other. |
| std::vector<uint8_t> vmo_data(kBufferSize, 0); |
| ASSERT_OK(vmo.read(vmo_data.data(), 0, kBufferSize)); |
| std::sort(vmo_data.begin(), vmo_data.end()); |
| EXPECT_BYTES_EQ(buffer.data(), vmo_data.data(), kBufferSize); |
| } |
| |
| TEST(StreamTestCase, ExtendFillsWithZeros) { |
| const size_t kPageCount = 6; |
| const size_t kVmoSize = zx_system_get_page_size() * kPageCount; |
| zx::vmo vmo; |
| ASSERT_OK(zx::vmo::create(kVmoSize, 0, &vmo)); |
| size_t content_size = 0u; |
| ASSERT_OK(vmo.set_property(ZX_PROP_VMO_CONTENT_SIZE, &content_size, sizeof(content_size))); |
| |
| zx::stream stream; |
| ASSERT_OK(zx::stream::create(ZX_STREAM_MODE_WRITE, vmo, 0, &stream)); |
| |
| char scratch[zx_system_get_page_size()]; |
| memset(scratch, 'x', sizeof(scratch)); |
| |
| for (size_t i = 0; i < kPageCount; ++i) { |
| ASSERT_OK(vmo.write(scratch, zx_system_get_page_size() * i, sizeof(scratch))); |
| } |
| |
| char buffer[17] = "0123456789ABCDEF"; |
| zx_iovec_t vec = { |
| .buffer = buffer, |
| .capacity = 4, |
| }; |
| |
| size_t actual = 0u; |
| ASSERT_OK(stream.writev_at(0, zx_system_get_page_size() * 2 - 2, &vec, 1, &actual)); |
| ASSERT_EQ(4, actual); |
| |
| memset(scratch, 'a', sizeof(scratch)); |
| ASSERT_OK(vmo.read(scratch, 0, sizeof(scratch))); |
| |
| for (size_t i = 0; i < zx_system_get_page_size(); ++i) { |
| ASSERT_EQ(0, scratch[i], "The %zu byte should be zero.", i); |
| } |
| |
| memset(scratch, 'a', sizeof(scratch)); |
| ASSERT_OK(vmo.read(scratch, zx_system_get_page_size(), sizeof(scratch))); |
| |
| for (size_t i = 0; i < zx_system_get_page_size() - 2; ++i) { |
| ASSERT_EQ(0, scratch[i], "The %zu byte of the second page should be zero.", i); |
| } |
| |
| ASSERT_EQ('0', scratch[zx_system_get_page_size() - 2]); |
| ASSERT_EQ('1', scratch[zx_system_get_page_size() - 1]); |
| |
| memset(scratch, 'a', sizeof(scratch)); |
| ASSERT_OK(vmo.read(scratch, zx_system_get_page_size() * 2, sizeof(scratch))); |
| |
| ASSERT_EQ('2', scratch[0]); |
| ASSERT_EQ('3', scratch[1]); |
| ASSERT_EQ('x', scratch[2]); |
| ASSERT_EQ('x', scratch[3]); |
| |
| ASSERT_OK(stream.seek(ZX_STREAM_SEEK_ORIGIN_START, zx_system_get_page_size() * 5 - 2, nullptr)); |
| |
| actual = 0; |
| ASSERT_OK(stream.writev(0, &vec, 1, &actual)); |
| ASSERT_EQ(4, actual); |
| |
| memset(scratch, 'a', sizeof(scratch)); |
| ASSERT_OK(vmo.read(scratch, zx_system_get_page_size() * 2, sizeof(scratch))); |
| |
| ASSERT_EQ('2', scratch[0]); |
| ASSERT_EQ('3', scratch[1]); |
| ASSERT_EQ(0, scratch[2]); |
| ASSERT_EQ(0, scratch[3]); |
| |
| memset(scratch, 'a', sizeof(scratch)); |
| ASSERT_OK(vmo.read(scratch, zx_system_get_page_size() * 3, sizeof(scratch))); |
| |
| for (size_t i = 0; i < zx_system_get_page_size(); ++i) { |
| ASSERT_EQ(0, scratch[i], "The %zu byte of the third page should be zero.", i); |
| } |
| |
| memset(scratch, 'a', sizeof(scratch)); |
| ASSERT_OK(vmo.read(scratch, zx_system_get_page_size() * 4, sizeof(scratch))); |
| |
| for (size_t i = 0; i < zx_system_get_page_size() - 2; ++i) { |
| ASSERT_EQ(0, scratch[i], "The %zu byte of the fourth page should be zero.", i); |
| } |
| |
| ASSERT_EQ('0', scratch[zx_system_get_page_size() - 2]); |
| ASSERT_EQ('1', scratch[zx_system_get_page_size() - 1]); |
| |
| memset(scratch, 'a', sizeof(scratch)); |
| ASSERT_OK(vmo.read(scratch, zx_system_get_page_size() * 5, sizeof(scratch))); |
| |
| ASSERT_EQ('2', scratch[0]); |
| ASSERT_EQ('3', scratch[1]); |
| ASSERT_EQ('x', scratch[2]); |
| ASSERT_EQ('x', scratch[3]); |
| } |
| |
| TEST(StreamTestCase, ReadShrinkRace) { |
| // This test is slow because of the `WaitForPageRead`. Be careful about the number of iterations. |
| constexpr size_t kNumIterations = 10; |
| |
| constexpr size_t kInitialVmoSize = 80u; |
| constexpr size_t kInitialVmoNumPages = |
| fbl::round_up(kInitialVmoSize, ZX_PAGE_SIZE) / ZX_PAGE_SIZE; |
| constexpr size_t kTruncateToSize = 0u; |
| |
| for (size_t i = 0; i < kNumIterations; ++i) { |
| pager_tests::UserPager pager; |
| ASSERT_TRUE(pager.Init()); |
| |
| pager_tests::Vmo* vmo; |
| ASSERT_TRUE(pager.CreateVmoWithOptions(kInitialVmoNumPages, ZX_VMO_RESIZABLE, &vmo)); |
| |
| zx::stream stream; |
| ASSERT_OK(zx::stream::create(ZX_STREAM_MODE_READ, vmo->vmo(), 0, &stream)); |
| |
| // Create a read that intersects with the truncate. |
| std::thread read_thread([&] { |
| std::array<char, 16u> buffer = {}; |
| zx_iovec_t vec = { |
| .buffer = buffer.data(), |
| .capacity = buffer.size(), |
| }; |
| |
| size_t actual = 42u; |
| ASSERT_OK(stream.readv(0, &vec, 1, &actual)); |
| |
| // The read should have happened either before or after the set size, so either nothing or |
| // everything should've been read. |
| ASSERT_TRUE(actual == 0u || actual == buffer.size()); |
| }); |
| |
| std::thread set_size_thread([&] { ASSERT_OK(vmo->vmo().set_size(kTruncateToSize)); }); |
| |
| // Wait for and supply page read, in case |read_thread| wins. This is inherently a race we want |
| // to test, so waiting is the best we can do. |
| if (pager.WaitForPageRead(vmo, 0u, 1u, zx::deadline_after(zx::sec(5)).get())) { |
| pager.SupplyPages(vmo, 0u, 1u); |
| } |
| |
| set_size_thread.join(); |
| read_thread.join(); |
| |
| // The set size must now be complete. |
| uint64_t content_size = 42u; |
| ASSERT_OK(vmo->vmo().get_prop_content_size(&content_size)); |
| EXPECT_EQ(kTruncateToSize, content_size); |
| |
| // Reads should be okay and return nothing. |
| std::array<char, 16u> buffer = {}; |
| zx_iovec_t vec = { |
| .buffer = buffer.data(), |
| .capacity = buffer.size(), |
| }; |
| size_t actual = 42u; |
| ASSERT_OK(stream.readv(0, &vec, 1, &actual)); |
| EXPECT_EQ(0u, actual); |
| } |
| } |
| |
| TEST(StreamTestCase, WriteShrinkRace) { |
| constexpr size_t kNumIterations = 50; |
| const size_t kInitialVmoSize = static_cast<size_t>(zx_system_get_page_size()) + 8u; |
| const size_t kInitialVmoNumPages = |
| fbl::round_up(kInitialVmoSize, zx_system_get_page_size()) / zx_system_get_page_size(); |
| const size_t kTruncateToSize = static_cast<size_t>(zx_system_get_page_size()); |
| ASSERT_GT(kInitialVmoSize, kTruncateToSize); |
| |
| for (size_t i = 0; i < kNumIterations; ++i) { |
| pager_tests::UserPager pager; |
| ASSERT_TRUE(pager.Init()); |
| |
| pager_tests::Vmo* vmo; |
| ASSERT_TRUE(pager.CreateVmoWithOptions(kInitialVmoNumPages, ZX_VMO_RESIZABLE, &vmo)); |
| |
| zx::stream stream; |
| ASSERT_OK(zx::stream::create(ZX_STREAM_MODE_WRITE, vmo->vmo(), 0u, &stream)); |
| |
| pager.SupplyPages(vmo, 0u, kInitialVmoNumPages); |
| |
| // Create a write that intersects with the truncate. |
| std::thread boundary_write_thread([&] { |
| std::array<char, 16u> buffer = {}; |
| ASSERT_LE(buffer.size(), kInitialVmoSize); |
| zx_iovec_t vec = { |
| .buffer = buffer.data(), |
| .capacity = buffer.size(), |
| }; |
| |
| // Attempt to write the last |buffer.size()| bytes. |
| const zx_off_t kOffset = kInitialVmoSize - buffer.size(); |
| size_t actual = 42u; |
| ASSERT_OK(stream.writev_at(0, kOffset, &vec, 1u, &actual)); |
| ASSERT_EQ(actual, buffer.size()); |
| }); |
| |
| // Create a write that should always complete, regardless of truncation. |
| std::thread full_write_thread([&] { |
| std::array<char, 16> buffer = {}; |
| ASSERT_LE(buffer.size(), kInitialVmoSize); |
| zx_iovec_t vec = { |
| .buffer = buffer.data(), |
| .capacity = buffer.size(), |
| }; |
| |
| // Attempt to write the first |buffer.size()| bytes. |
| size_t actual = 42u; |
| ASSERT_OK(stream.writev_at(0, 0u, &vec, 1u, &actual)); |
| ASSERT_EQ(actual, buffer.size()); |
| }); |
| |
| // Simultaneously try to truncate. |
| std::thread truncate_thread([&] { ASSERT_OK(vmo->vmo().set_size(kTruncateToSize)); }); |
| |
| boundary_write_thread.join(); |
| full_write_thread.join(); |
| truncate_thread.join(); |
| |
| // The set size must now be complete. |
| // The size will either be |kTruncateToSize| if the truncate happened last or |kInitialVmoSize| |
| // if the write happened last. |
| uint64_t content_size = 42u; |
| ASSERT_OK(vmo->vmo().get_prop_content_size(&content_size)); |
| ASSERT_TRUE(content_size == kInitialVmoSize || content_size == kTruncateToSize); |
| } |
| } |
| |
| TEST(StreamTestCase, ReadWriteShrinkRace) { |
| constexpr size_t kNumIterations = 500; |
| constexpr size_t kInitialVmoSize = (ZX_PAGE_SIZE * 8) + 8u; |
| constexpr size_t kInitialVmoNumPages = |
| fbl::round_up(kInitialVmoSize, ZX_PAGE_SIZE) / ZX_PAGE_SIZE; |
| constexpr size_t kTruncateToSize = ZX_PAGE_SIZE; |
| ASSERT_GT(kInitialVmoSize, kTruncateToSize); |
| |
| for (size_t i = 0; i < kNumIterations; ++i) { |
| pager_tests::UserPager pager; |
| ASSERT_TRUE(pager.Init()); |
| |
| pager_tests::Vmo* vmo; |
| ASSERT_TRUE(pager.CreateVmoWithOptions(kInitialVmoNumPages, ZX_VMO_RESIZABLE, &vmo)); |
| |
| zx::stream stream; |
| ASSERT_OK( |
| zx::stream::create(ZX_STREAM_MODE_READ | ZX_STREAM_MODE_WRITE, vmo->vmo(), 0u, &stream)); |
| |
| pager.SupplyPages(vmo, 0u, kInitialVmoNumPages); |
| |
| // Create a write that intersects with the truncate. |
| std::thread write_thread([&] { |
| std::array<char, 16u> buffer = {}; |
| ASSERT_LE(buffer.size(), kInitialVmoSize); |
| zx_iovec_t vec = { |
| .buffer = buffer.data(), |
| .capacity = buffer.size(), |
| }; |
| |
| // Attempt to write the last |buffer.size()| bytes. |
| const zx_off_t kOffset = kInitialVmoSize - buffer.size(); |
| size_t actual = 42u; |
| ASSERT_OK(stream.writev_at(0, kOffset, &vec, 1u, &actual)); |
| ASSERT_EQ(actual, buffer.size()); |
| }); |
| |
| // Simultaneously try to truncate. |
| std::thread truncate_thread([&] { ASSERT_OK(vmo->vmo().set_size(kTruncateToSize)); }); |
| |
| // Create a read that intersects with the truncate. |
| std::thread read_thread([&] { |
| std::array<char, kInitialVmoSize> buffer = {}; |
| zx_iovec_t vec = { |
| .buffer = buffer.data(), |
| .capacity = buffer.size(), |
| }; |
| |
| size_t actual = 42u; |
| ASSERT_OK(stream.readv_at(0, 0u, &vec, 1u, &actual)); |
| // If the write happens after the truncate, the read may see a content size in the range |
| // [kTruncateToSize, kInitialVmoSize] because of a partial expanding write updating content |
| // size as it progresses. |
| ASSERT_TRUE(actual >= kTruncateToSize || actual <= kInitialVmoSize); |
| }); |
| |
| write_thread.join(); |
| truncate_thread.join(); |
| read_thread.join(); |
| |
| // The set size must now be complete. |
| // The size will either be |kTruncateToSize| if the truncate happened last or |kInitialVmoSize| |
| // if the write happened last. |
| uint64_t content_size = 42u; |
| ASSERT_OK(vmo->vmo().get_prop_content_size(&content_size)); |
| ASSERT_TRUE(content_size == kInitialVmoSize || content_size == kTruncateToSize); |
| } |
| } |
| |
| // Regression test for https://fxbug.dev/42176351. Writing to an offset that requires expansion |
| // should not result in an overflow when computing the new required VMO size. |
| TEST(StreamTestCase, ExpandOverflow) { |
| zx::vmo vmo; |
| ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), ZX_VMO_RESIZABLE, &vmo)); |
| |
| zx::stream stream; |
| ASSERT_OK(zx::stream::create(ZX_STREAM_MODE_WRITE, vmo, 0, &stream)); |
| |
| char buffer[] = "AAAA"; |
| zx_iovec_t vec = { |
| .buffer = buffer, |
| .capacity = 4, |
| }; |
| |
| size_t actual = 0u; |
| // This write will require a content size of 0xfffffffffffffffc, which when rounded up to the page |
| // boundary to compute the VMO size will overflow. So content expansion should fail. |
| EXPECT_EQ(ZX_ERR_OUT_OF_RANGE, stream.writev_at(0, 0xfffffffffffffff8, &vec, 1, &actual)); |
| EXPECT_EQ(0, actual); |
| |
| // Verify the VMO and content sizes. |
| uint64_t vmo_size; |
| ASSERT_OK(vmo.get_size(&vmo_size)); |
| EXPECT_EQ(zx_system_get_page_size(), vmo_size); |
| |
| uint64_t content_size; |
| ASSERT_OK(vmo.get_prop_content_size(&content_size)); |
| EXPECT_EQ(zx_system_get_page_size(), content_size); |
| |
| // Verify that a subsequent resize succeeds. |
| EXPECT_OK(vmo.set_size(2 * zx_system_get_page_size())); |
| ASSERT_OK(vmo.get_size(&vmo_size)); |
| EXPECT_EQ(2 * zx_system_get_page_size(), vmo_size); |
| ASSERT_OK(vmo.get_prop_content_size(&content_size)); |
| EXPECT_EQ(2 * zx_system_get_page_size(), content_size); |
| } |
| |
| // Tests that content size is updated as soon as bytes are committed to the VMO. |
| TEST(StreamTestCase, ContentSizeUpdatedOnPartialWrite) { |
| constexpr uint64_t kNumPagesToWrite = kMaxPagesBatch * 3; |
| |
| pager_tests::UserPager pager; |
| ASSERT_TRUE(pager.Init()); |
| |
| pager_tests::Vmo* vmo; |
| ASSERT_TRUE(pager.CreateVmoWithOptions(1, ZX_VMO_RESIZABLE | ZX_VMO_TRAP_DIRTY, &vmo)); |
| ASSERT_OK(vmo->vmo().set_prop_content_size(0)); |
| |
| zx::stream stream; |
| ASSERT_OK( |
| zx::stream::create(ZX_STREAM_MODE_READ | ZX_STREAM_MODE_WRITE, vmo->vmo(), 0u, &stream)); |
| |
| std::thread write_thread([&] { |
| std::vector<char> buffer(kNumPagesToWrite * zx_system_get_page_size(), 'a'); |
| zx_iovec_t vec = { |
| .buffer = buffer.data(), |
| .capacity = buffer.size(), |
| }; |
| size_t actual; |
| ASSERT_OK(stream.writev(0, &vec, 1, &actual)); |
| |
| ASSERT_EQ(actual, buffer.size()); |
| }); |
| |
| for (uint64_t page_num = 0; page_num < kNumPagesToWrite; page_num += kMaxPagesBatch) { |
| const uint64_t num_pages_to_dirty = std::min(kMaxPagesBatch, kNumPagesToWrite - page_num); |
| |
| pager.WaitForPageDirty(vmo, page_num, num_pages_to_dirty, ZX_TIME_INFINITE); |
| ASSERT_EQ(GetContentSize(vmo->vmo()), page_num * zx_system_get_page_size()); |
| pager.DirtyPages(vmo, page_num, num_pages_to_dirty); |
| } |
| |
| write_thread.join(); |
| } |
| |
| // Tests that resizing a `zx_iovec_t` capacity smaller while a read is using it does not fail. |
| TEST(StreamTestCase, RaceReadResizeVecSmaller) { |
| constexpr size_t kNumIterations = 50; |
| constexpr size_t kInitialVecSize = 26; |
| constexpr size_t kResizeVecSize = 10; |
| constexpr char kInitialBufferChar = '!'; |
| |
| for (size_t i = 0; i < kNumIterations; ++i) { |
| zx::vmo vmo; |
| ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmo)); |
| ASSERT_OK(vmo.write(kAlphabet, 0u, strlen(kAlphabet))); |
| |
| std::string buffer(kInitialVecSize, kInitialBufferChar); |
| zx_iovec_t vec = { |
| .buffer = buffer.data(), |
| .capacity = buffer.size(), |
| }; |
| |
| zx::stream stream; |
| ASSERT_OK(zx::stream::create(ZX_STREAM_MODE_READ, vmo, 0, &stream)); |
| |
| std::thread read_thread([&] { |
| size_t actual = 42u; |
| ASSERT_OK(stream.readv(0, &vec, 1, &actual)); |
| |
| ASSERT_TRUE(actual == buffer.size() || actual == kResizeVecSize); |
| |
| if (actual == kResizeVecSize) { |
| std::string spliced = std::string(kAlphabet).substr(0, kResizeVecSize) + |
| std::string(kInitialVecSize - kResizeVecSize, kInitialBufferChar); |
| |
| EXPECT_STREQ(spliced.c_str(), buffer.c_str()); |
| } else { |
| EXPECT_STREQ(kAlphabet, GetData(vmo).c_str()); |
| } |
| }); |
| |
| std::thread resize_thread([&] { vec.capacity = kResizeVecSize; }); |
| |
| read_thread.join(); |
| resize_thread.join(); |
| } |
| } |
| |
| // Tests that resizing a `zx_iovec_t` capacity smaller while a write is using it does not fail. |
| TEST(StreamTestCase, RaceWriteResizeVecSmaller) { |
| constexpr size_t kNumIterations = 50; |
| constexpr size_t kResizeVecSize = 10; |
| |
| for (size_t i = 0; i < kNumIterations; ++i) { |
| zx::vmo vmo; |
| ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmo)); |
| ASSERT_OK(vmo.write(kAlphabet, 0u, strlen(kAlphabet))); |
| |
| std::string buffer = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; |
| zx_iovec_t vec = { |
| .buffer = buffer.data(), |
| .capacity = buffer.size(), |
| }; |
| |
| zx::stream stream; |
| ASSERT_OK(zx::stream::create(ZX_STREAM_MODE_WRITE, vmo, 0, &stream)); |
| |
| std::thread write_thread([&] { |
| size_t actual = 42u; |
| ASSERT_OK(stream.writev(0, &vec, 1, &actual)); |
| |
| ASSERT_TRUE(actual == buffer.size() || actual == kResizeVecSize); |
| |
| if (actual == kResizeVecSize) { |
| std::string spliced = |
| buffer.substr(0, kResizeVecSize) + std::string(kAlphabet).substr(kResizeVecSize); |
| |
| EXPECT_STREQ(spliced.c_str(), GetData(vmo).c_str()); |
| } else { |
| EXPECT_STREQ(buffer.c_str(), GetData(vmo).c_str()); |
| } |
| }); |
| |
| std::thread resize_thread([&] { vec.capacity = kResizeVecSize; }); |
| |
| write_thread.join(); |
| resize_thread.join(); |
| } |
| } |
| |
| // Tests that resizing a `zx_iovec_t` capacity larger while a read is using it does not fail. |
| TEST(StreamTestCase, RaceReadResizeVecLarger) { |
| constexpr size_t kNumIterations = 50; |
| constexpr size_t kInitialVecSize = 10; |
| constexpr size_t kResizeVecSize = 26; |
| constexpr char kInitialBufferChar = '!'; |
| |
| for (size_t i = 0; i < kNumIterations; ++i) { |
| zx::vmo vmo; |
| ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmo)); |
| ASSERT_OK(vmo.write(kAlphabet, 0u, strlen(kAlphabet))); |
| |
| std::string buffer(kResizeVecSize, kInitialBufferChar); |
| zx_iovec_t vec = { |
| .buffer = buffer.data(), |
| .capacity = kInitialVecSize, |
| }; |
| |
| zx::stream stream; |
| ASSERT_OK(zx::stream::create(ZX_STREAM_MODE_READ, vmo, 0, &stream)); |
| |
| std::thread read_thread([&] { |
| size_t actual = 42u; |
| ASSERT_OK(stream.readv(0, &vec, 1, &actual)); |
| |
| ASSERT_TRUE(actual == kInitialVecSize || actual == buffer.size()); |
| |
| if (actual == kResizeVecSize) { |
| EXPECT_STREQ(kAlphabet, buffer.c_str()); |
| } else { |
| std::string spliced = std::string(kAlphabet).substr(0, kInitialVecSize) + |
| std::string(kResizeVecSize - kInitialVecSize, kInitialBufferChar); |
| |
| EXPECT_STREQ(spliced.c_str(), buffer.c_str()); |
| } |
| }); |
| |
| std::thread resize_thread([&] { vec.capacity = kResizeVecSize; }); |
| |
| read_thread.join(); |
| resize_thread.join(); |
| } |
| } |
| |
| // Tests that resizing a `zx_iovec_t` capacity larger while a write is using it does not fail. |
| TEST(StreamTestCase, RaceWriteResizeVecLarger) { |
| constexpr size_t kNumIterations = 50; |
| constexpr size_t kInitialVecSize = 10; |
| |
| for (size_t i = 0; i < kNumIterations; ++i) { |
| zx::vmo vmo; |
| ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmo)); |
| ASSERT_OK(vmo.write(kAlphabet, 0u, strlen(kAlphabet))); |
| |
| std::string buffer = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; |
| zx_iovec_t vec = { |
| .buffer = buffer.data(), |
| .capacity = kInitialVecSize, |
| }; |
| |
| zx::stream stream; |
| ASSERT_OK(zx::stream::create(ZX_STREAM_MODE_WRITE, vmo, 0, &stream)); |
| |
| std::thread write_thread([&] { |
| size_t actual = 42u; |
| ASSERT_OK(stream.writev(0, &vec, 1, &actual)); |
| |
| ASSERT_TRUE(actual == kInitialVecSize || actual == buffer.size()); |
| |
| if (actual == kInitialVecSize) { |
| std::string spliced = |
| buffer.substr(0, kInitialVecSize) + std::string(kAlphabet).substr(kInitialVecSize); |
| |
| EXPECT_STREQ(spliced.c_str(), GetData(vmo).c_str()); |
| } else { |
| EXPECT_STREQ(buffer.c_str(), GetData(vmo).c_str()); |
| } |
| }); |
| |
| std::thread resize_thread([&] { vec.capacity = buffer.size(); }); |
| |
| write_thread.join(); |
| resize_thread.join(); |
| } |
| } |
| |
| // Tests that ZX_RIGHT_RESIZE is required on the VMO that the stream is created against for the |
| // stream to be able to expand the VMO. |
| TEST(StreamTestCase, ExpandVmoResizeRight) { |
| // Create a resizable VMO. |
| zx::vmo vmo; |
| ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), ZX_VMO_RESIZABLE, &vmo)); |
| |
| // The VMO handle has the resize right. |
| zx_info_handle_basic_t info; |
| ASSERT_OK(vmo.get_info(ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, nullptr)); |
| EXPECT_EQ(info.rights & ZX_RIGHT_RESIZE, ZX_RIGHT_RESIZE); |
| |
| // Create a duplicate handle without the resize right. |
| zx::vmo vmo_no_resize; |
| ASSERT_OK(vmo.duplicate(info.rights & ~ZX_RIGHT_RESIZE, &vmo_no_resize)); |
| |
| // Create streams against both the handles. |
| const size_t seek = zx_system_get_page_size() - 1; |
| zx::stream stream, stream_no_resize; |
| ASSERT_OK(zx::stream::create(ZX_STREAM_MODE_WRITE, vmo, seek, &stream)); |
| ASSERT_OK(zx::stream::create(ZX_STREAM_MODE_WRITE, vmo_no_resize, seek, &stream_no_resize)); |
| |
| char buffer[] = "012345678"; |
| zx_iovec_t vec = { |
| .buffer = buffer, |
| .capacity = sizeof(buffer), |
| }; |
| |
| // Should be able to write to the current VMO size without the resize right. |
| size_t actual; |
| ASSERT_OK(stream_no_resize.writev(0, &vec, 1, &actual)); |
| EXPECT_EQ(1u, actual); |
| |
| // Should not be able to write any further as that would require expanding the VMO. |
| actual = 0; |
| ASSERT_EQ(ZX_ERR_NO_SPACE, stream_no_resize.writev(0, &vec, 1, &actual)); |
| EXPECT_EQ(0u, actual); |
| |
| // Verify that the VMO has not been resized. |
| uint64_t vmo_size; |
| ASSERT_OK(vmo.get_size(&vmo_size)); |
| EXPECT_EQ(zx_system_get_page_size(), vmo_size); |
| |
| // Should be able to expand the VMO with the resize right. |
| ASSERT_OK(stream.writev(0, &vec, 1, &actual)); |
| EXPECT_EQ(10u, actual); |
| |
| // Verify new VMO size. |
| ASSERT_OK(vmo.get_size(&vmo_size)); |
| EXPECT_EQ(zx_system_get_page_size() * 2, vmo_size); |
| |
| // Verify written contents. |
| char data[sizeof(buffer)]; |
| ASSERT_OK(vmo.read(data, seek, sizeof(buffer))); |
| EXPECT_STREQ(buffer, data); |
| } |
| |
| } // namespace |