| // Copyright 2018 The gVisor Authors. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include <fcntl.h> |
| #include <sys/syscall.h> |
| #include <sys/types.h> |
| #include <sys/uio.h> |
| |
| #include <string> |
| #include <vector> |
| |
| #include "gtest/gtest.h" |
| #include "test/syscalls/linux/file_base.h" |
| #include "test/util/file_descriptor.h" |
| #include "test/util/temp_path.h" |
| #include "test/util/test_util.h" |
| |
| namespace gvisor { |
| namespace testing { |
| |
| namespace { |
| |
| #ifndef SYS_pwritev2 |
| #if defined(__x86_64__) |
| #define SYS_pwritev2 328 |
| #elif defined(__aarch64__) |
| #define SYS_pwritev2 287 |
| #else |
| #error "Unknown architecture" |
| #endif |
| #endif // SYS_pwrite2 |
| |
| #ifndef RWF_HIPRI |
| #define RWF_HIPRI 0x1 |
| #endif // RWF_HIPRI |
| |
| #ifndef RWF_DSYNC |
| #define RWF_DSYNC 0x2 |
| #endif // RWF_DSYNC |
| |
| #ifndef RWF_SYNC |
| #define RWF_SYNC 0x4 |
| #endif // RWF_SYNC |
| |
| constexpr int kBufSize = 1024; |
| |
| void SetContent(std::vector<char>& content) { |
| for (uint i = 0; i < content.size(); i++) { |
| content[i] = static_cast<char>((i % 10) + '0'); |
| } |
| } |
| |
| ssize_t pwritev2(unsigned long fd, const struct iovec* iov, |
| unsigned long iovcnt, off_t offset, unsigned long flags) { |
| // syscall on pwritev2 does some weird things (see man syscall and search |
| // pwritev2), so we insert a 0 to word align the flags argument on native. |
| return syscall(SYS_pwritev2, fd, iov, iovcnt, offset, 0, flags); |
| } |
| |
| // This test is the base case where we call pwritev (no offset, no flags). |
| TEST(Writev2Test, BaseCall) { |
| SKIP_IF(pwritev2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS); |
| |
| const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( |
| GetAbsoluteTestTmpdir(), "", TempPath::kDefaultFileMode)); |
| const FileDescriptor fd = |
| ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR)); |
| |
| std::vector<char> content(kBufSize); |
| SetContent(content); |
| struct iovec iov[2]; |
| iov[0].iov_base = content.data(); |
| iov[0].iov_len = content.size() / 2; |
| iov[1].iov_base = static_cast<char*>(iov[0].iov_base) + (content.size() / 2); |
| iov[1].iov_len = content.size() / 2; |
| |
| ASSERT_THAT(pwritev2(fd.get(), iov, /*iovcnt=*/2, |
| /*offset=*/0, /*flags=*/0), |
| SyscallSucceedsWithValue(kBufSize)); |
| |
| std::vector<char> buf(kBufSize); |
| EXPECT_THAT(read(fd.get(), buf.data(), kBufSize), |
| SyscallSucceedsWithValue(kBufSize)); |
| |
| EXPECT_EQ(content, buf); |
| } |
| |
| // This test is where we call pwritev2 with a positive offset and no flags. |
| TEST(Pwritev2Test, ValidPositiveOffset) { |
| SKIP_IF(pwritev2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS); |
| |
| std::string prefix(kBufSize, '0'); |
| |
| const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( |
| GetAbsoluteTestTmpdir(), prefix, TempPath::kDefaultFileMode)); |
| const FileDescriptor fd = |
| ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR)); |
| |
| std::vector<char> content(kBufSize); |
| SetContent(content); |
| struct iovec iov; |
| iov.iov_base = content.data(); |
| iov.iov_len = content.size(); |
| |
| ASSERT_THAT(pwritev2(fd.get(), &iov, /*iovcnt=*/1, |
| /*offset=*/prefix.size(), /*flags=*/0), |
| SyscallSucceedsWithValue(content.size())); |
| |
| std::vector<char> buf(prefix.size() + content.size()); |
| EXPECT_THAT(read(fd.get(), buf.data(), buf.size()), |
| SyscallSucceedsWithValue(buf.size())); |
| |
| std::vector<char> want(prefix.begin(), prefix.end()); |
| want.insert(want.end(), content.begin(), content.end()); |
| EXPECT_EQ(want, buf); |
| } |
| |
| // This test is the base case where we call writev by using -1 as the offset. |
| // The write should use the file offset, so the test increments the file offset |
| // prior to call pwritev2. |
| TEST(Pwritev2Test, NegativeOneOffset) { |
| SKIP_IF(pwritev2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS); |
| |
| const std::string prefix = "00"; |
| const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( |
| GetAbsoluteTestTmpdir(), prefix.data(), TempPath::kDefaultFileMode)); |
| const FileDescriptor fd = |
| ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR)); |
| ASSERT_THAT(lseek(fd.get(), prefix.size(), SEEK_SET), |
| SyscallSucceedsWithValue(prefix.size())); |
| |
| std::vector<char> content(kBufSize); |
| SetContent(content); |
| struct iovec iov; |
| iov.iov_base = content.data(); |
| iov.iov_len = content.size(); |
| |
| ASSERT_THAT(pwritev2(fd.get(), &iov, /*iovcnt*/ 1, |
| /*offset=*/static_cast<off_t>(-1), /*flags=*/0), |
| SyscallSucceedsWithValue(content.size())); |
| |
| ASSERT_THAT(lseek(fd.get(), 0, SEEK_CUR), |
| SyscallSucceedsWithValue(prefix.size() + content.size())); |
| |
| std::vector<char> buf(prefix.size() + content.size()); |
| EXPECT_THAT(pread(fd.get(), buf.data(), buf.size(), /*offset=*/0), |
| SyscallSucceedsWithValue(buf.size())); |
| |
| std::vector<char> want(prefix.begin(), prefix.end()); |
| want.insert(want.end(), content.begin(), content.end()); |
| EXPECT_EQ(want, buf); |
| } |
| |
| // pwritev2 requires if the RWF_HIPRI flag is passed, the fd must be opened with |
| // O_DIRECT. This test implements a correct call with the RWF_HIPRI flag. |
| TEST(Pwritev2Test, CallWithRWF_HIPRI) { |
| SKIP_IF(pwritev2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS); |
| |
| const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( |
| GetAbsoluteTestTmpdir(), "", TempPath::kDefaultFileMode)); |
| const FileDescriptor fd = |
| ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR)); |
| |
| std::vector<char> content(kBufSize); |
| SetContent(content); |
| struct iovec iov; |
| iov.iov_base = content.data(); |
| iov.iov_len = content.size(); |
| |
| EXPECT_THAT(pwritev2(fd.get(), &iov, /*iovcnt=*/1, |
| /*offset=*/0, /*flags=*/RWF_HIPRI), |
| SyscallSucceedsWithValue(kBufSize)); |
| |
| std::vector<char> buf(content.size()); |
| EXPECT_THAT(read(fd.get(), buf.data(), buf.size()), |
| SyscallSucceedsWithValue(buf.size())); |
| |
| EXPECT_EQ(buf, content); |
| } |
| |
| // This test calls pwritev2 with a bad file descriptor. |
| TEST(Writev2Test, BadFile) { |
| SKIP_IF(pwritev2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS); |
| ASSERT_THAT(pwritev2(/*fd=*/-1, /*iov=*/nullptr, /*iovcnt=*/0, |
| /*offset=*/0, /*flags=*/0), |
| SyscallFailsWithErrno(EBADF)); |
| } |
| |
| // This test calls pwrite2 with an invalid offset. |
| TEST(Pwritev2Test, InvalidOffset) { |
| SKIP_IF(pwritev2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS); |
| |
| const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( |
| GetAbsoluteTestTmpdir(), "", TempPath::kDefaultFileMode)); |
| const FileDescriptor fd = |
| ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR)); |
| |
| char buf[16]; |
| struct iovec iov; |
| iov.iov_base = buf; |
| iov.iov_len = sizeof(buf); |
| |
| EXPECT_THAT(pwritev2(fd.get(), &iov, /*iovcnt=*/1, |
| /*offset=*/static_cast<off_t>(-8), /*flags=*/0), |
| SyscallFailsWithErrno(EINVAL)); |
| } |
| |
| TEST(Pwritev2Test, UnseekableFileValid) { |
| SKIP_IF(pwritev2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS); |
| |
| int pipe_fds[2]; |
| |
| ASSERT_THAT(pipe(pipe_fds), SyscallSucceeds()); |
| |
| std::vector<char> content(32, '0'); |
| SetContent(content); |
| struct iovec iov; |
| iov.iov_base = content.data(); |
| iov.iov_len = content.size(); |
| |
| EXPECT_THAT(pwritev2(pipe_fds[1], &iov, /*iovcnt=*/1, |
| /*offset=*/static_cast<off_t>(-1), /*flags=*/0), |
| SyscallSucceedsWithValue(content.size())); |
| |
| std::vector<char> buf(content.size()); |
| EXPECT_THAT(read(pipe_fds[0], buf.data(), buf.size()), |
| SyscallSucceedsWithValue(buf.size())); |
| |
| EXPECT_EQ(content, buf); |
| |
| EXPECT_THAT(close(pipe_fds[0]), SyscallSucceeds()); |
| EXPECT_THAT(close(pipe_fds[1]), SyscallSucceeds()); |
| } |
| |
| // Calling pwritev2 with a non-negative offset calls pwritev. Calling pwritev |
| // with an unseekable file is not allowed. A pipe is used for an unseekable |
| // file. |
| TEST(Pwritev2Test, UnseekableFileInvalid) { |
| SKIP_IF(pwritev2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS); |
| |
| int pipe_fds[2]; |
| char buf[16]; |
| struct iovec iov; |
| iov.iov_base = buf; |
| iov.iov_len = sizeof(buf); |
| |
| ASSERT_THAT(pipe(pipe_fds), SyscallSucceeds()); |
| |
| EXPECT_THAT(pwritev2(pipe_fds[1], &iov, /*iovcnt=*/1, |
| /*offset=*/2, /*flags=*/0), |
| SyscallFailsWithErrno(ESPIPE)); |
| |
| EXPECT_THAT(close(pipe_fds[0]), SyscallSucceeds()); |
| EXPECT_THAT(close(pipe_fds[1]), SyscallSucceeds()); |
| } |
| |
| TEST(Pwritev2Test, ReadOnlyFile) { |
| SKIP_IF(pwritev2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS); |
| |
| const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( |
| GetAbsoluteTestTmpdir(), "", TempPath::kDefaultFileMode)); |
| const FileDescriptor fd = |
| ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY)); |
| |
| char buf[16]; |
| struct iovec iov; |
| iov.iov_base = buf; |
| iov.iov_len = sizeof(buf); |
| |
| EXPECT_THAT(pwritev2(fd.get(), &iov, /*iovcnt=*/1, |
| /*offset=*/0, /*flags=*/0), |
| SyscallFailsWithErrno(EBADF)); |
| } |
| |
| // This test calls pwritev2 with an invalid flag. |
| TEST(Pwritev2Test, InvalidFlag) { |
| SKIP_IF(pwritev2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS); |
| |
| const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( |
| GetAbsoluteTestTmpdir(), "", TempPath::kDefaultFileMode)); |
| const FileDescriptor fd = |
| ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR | O_DIRECT)); |
| |
| char buf[16]; |
| struct iovec iov; |
| iov.iov_base = buf; |
| iov.iov_len = sizeof(buf); |
| |
| EXPECT_THAT(pwritev2(fd.get(), &iov, /*iovcnt=*/1, |
| /*offset=*/0, /*flags=*/0xF0), |
| SyscallFailsWithErrno(EOPNOTSUPP)); |
| } |
| |
| } // namespace |
| } // namespace testing |
| } // namespace gvisor |