| // Copyright 2022 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 <sys/mman.h> |
| #include <sys/uio.h> |
| #include <unistd.h> |
| |
| #include <gtest/gtest.h> |
| |
| #include "src/starnix/tests/syscalls/cpp/test_helper.h" |
| |
| TEST(ReadWriteTest, preadv_pwritev) { |
| test_helper::ScopedTempFD temp_file; |
| ASSERT_TRUE(temp_file); |
| |
| // Add some data to the file and seek back to the beginning so we know the offset took effect. |
| constexpr int kOffset = 7; |
| const char kOffsetBuffer[kOffset + 1] = "zzzzzzz"; |
| EXPECT_EQ(kOffset, HANDLE_EINTR(write(temp_file.fd(), kOffsetBuffer, kOffset))); |
| |
| constexpr size_t kWriteBufferSize = 32; |
| char kWriteBuffer[kWriteBufferSize + 1] = "aaaaaaaabbbbbbbbccccccccdddddddd"; |
| |
| struct iovec iov[] = { |
| { |
| .iov_base = &kWriteBuffer[8], // Pick 8 b's. |
| .iov_len = 8, |
| }, |
| { |
| .iov_base = &kWriteBuffer[0], // a's followed by b's |
| .iov_len = 16, |
| }, |
| { |
| .iov_base = &kWriteBuffer[16], // c's followed by d's. |
| .iov_len = 16, |
| }, |
| }; |
| |
| // Technically this can do partial writes but our implementation doesn't support this. |
| constexpr ssize_t kExpectedSize = 40; |
| ssize_t result = HANDLE_EINTR(pwritev(temp_file.fd(), iov, std::size(iov), kOffset)); |
| EXPECT_EQ(kExpectedSize, result); |
| |
| lseek(temp_file.fd(), 0, SEEK_SET); |
| |
| // Generate read the vector using the same offsets as above, but pointing into the read buffer. |
| char read_buffer[kWriteBufferSize + 1] = {0}; |
| iov[0].iov_base = &read_buffer[8]; |
| iov[1].iov_base = &read_buffer[0]; |
| iov[2].iov_base = &read_buffer[16]; |
| result = HANDLE_EINTR(preadv(temp_file.fd(), iov, std::size(iov), kOffset)); |
| EXPECT_EQ(kExpectedSize, result); |
| |
| read_buffer[kWriteBufferSize] = 0; // Null terminate input for comparison. |
| EXPECT_STREQ(read_buffer, kWriteBuffer); |
| } |
| |
| // TODO(https://fxbug.dev/42068846) implement partial read/write support (we'll also need tests for read(), |
| // writev(), and readv()). |
| TEST(ReadWriteTest, DISABLED_PartialWrite) { |
| test_helper::ScopedTempFD temp_file; |
| ASSERT_TRUE(temp_file); |
| |
| // Allocate 2 pages and remove permission from the second. |
| constexpr size_t kPageSize = 4096; |
| const size_t size = 2 * kPageSize; |
| void* addr = mmap(0, size, PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); |
| ASSERT_TRUE(addr); |
| |
| void* bad_addr = reinterpret_cast<char*>(addr) + kPageSize; |
| ASSERT_EQ(0, mprotect(bad_addr, kPageSize, PROT_NONE)); |
| |
| // Complete bad write. |
| errno = 0; |
| ssize_t sresult = write(temp_file.fd(), bad_addr, 2); |
| EXPECT_EQ(-1, sresult); |
| EXPECT_EQ(EFAULT, errno); |
| |
| // Partial write, should write the first page and stop on the (invalid) second one. |
| errno = 0; |
| sresult = write(temp_file.fd(), addr, size); |
| EXPECT_EQ(ssize_t(kPageSize), sresult); |
| EXPECT_EQ(0, errno); |
| |
| // The seek offset should reflect the last partial write. |
| off_t off = lseek(temp_file.fd(), 0, SEEK_CUR); |
| EXPECT_EQ(off_t(kPageSize), off); |
| |
| munmap(addr, size); |
| } |