diff options
Diffstat (limited to 'test/syscalls/linux/pwritev2.cc')
-rw-r--r-- | test/syscalls/linux/pwritev2.cc | 307 |
1 files changed, 307 insertions, 0 deletions
diff --git a/test/syscalls/linux/pwritev2.cc b/test/syscalls/linux/pwritev2.cc new file mode 100644 index 000000000..63b686c62 --- /dev/null +++ b/test/syscalls/linux/pwritev2.cc @@ -0,0 +1,307 @@ +// 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 |