diff options
author | Zach Koopmans <zkoopmans@google.com> | 2018-12-19 13:14:53 -0800 |
---|---|---|
committer | Shentubot <shentubot@google.com> | 2018-12-19 13:16:06 -0800 |
commit | ff7178a4d10f9f1fb34e54fed5ef27cfbff5d6f9 (patch) | |
tree | b49fb1a2b4cbb1291f41502a7494a1d56a395a87 /test/syscalls/linux | |
parent | 898838e34d1b0c76405f3e7f7f5fa7f1a444da0e (diff) |
Implement pwritev2.
Implement pwritev2 and associated unit tests.
Clean up preadv2 unit tests.
Tag RWF_ flags in both preadv2 and pwritev2 with associated bug tickets.
PiperOrigin-RevId: 226222119
Change-Id: Ieb22672418812894ba114bbc88e67f1dd50de620
Diffstat (limited to 'test/syscalls/linux')
-rw-r--r-- | test/syscalls/linux/BUILD | 27 | ||||
-rw-r--r-- | test/syscalls/linux/preadv2.cc | 156 | ||||
-rw-r--r-- | test/syscalls/linux/pwritev2.cc | 337 |
3 files changed, 469 insertions, 51 deletions
diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD index aca55f492..f13e32daa 100644 --- a/test/syscalls/linux/BUILD +++ b/test/syscalls/linux/BUILD @@ -1338,18 +1338,18 @@ cc_binary( name = "preadv2_test", testonly = 1, srcs = [ + "file_base.h", "preadv2.cc", - "readv_common.cc", - "readv_common.h", ], linkstatic = 1, deps = [ - ":file_base", "//test/util:file_descriptor", - "//test/util:memory_util", + "//test/util:posix_error", "//test/util:temp_path", "//test/util:test_main", "//test/util:test_util", + "@com_google_absl//absl/memory", + "@com_google_absl//absl/strings", "@com_google_googletest//:gtest", ], ) @@ -1453,6 +1453,25 @@ cc_binary( ) cc_binary( + name = "pwritev2_test", + testonly = 1, + srcs = [ + "pwritev2.cc", + ], + linkstatic = 1, + deps = [ + ":file_base", + "//test/util:file_descriptor", + "//test/util:posix_error", + "//test/util:temp_path", + "//test/util:test_main", + "//test/util:test_util", + "@com_google_absl//absl/strings", + "@com_google_googletest//:gtest", + ], +) + +cc_binary( name = "read_test", testonly = 1, srcs = ["read.cc"], diff --git a/test/syscalls/linux/preadv2.cc b/test/syscalls/linux/preadv2.cc index 642eed624..58a4f9224 100644 --- a/test/syscalls/linux/preadv2.cc +++ b/test/syscalls/linux/preadv2.cc @@ -13,23 +13,18 @@ // limitations under the License. #include <fcntl.h> -#include <stdlib.h> #include <sys/syscall.h> #include <sys/types.h> #include <sys/uio.h> -#include <sys/wait.h> -#include <unistd.h> -#include <atomic> #include <string> #include <vector> #include "gtest/gtest.h" #include "gtest/gtest.h" +#include "absl/memory/memory.h" #include "test/syscalls/linux/file_base.h" -#include "test/syscalls/linux/readv_common.h" #include "test/util/file_descriptor.h" -#include "test/util/memory_util.h" #include "test/util/temp_path.h" #include "test/util/test_util.h" @@ -60,11 +55,17 @@ std::string SetContent() { return content; } +ssize_t preadv2(unsigned long fd, const struct iovec* iov, unsigned long iovcnt, + off_t offset, unsigned long flags) { + // syscall on preadv2 does some weird things (see man syscall and search + // preadv2), so we insert a 0 to word align the flags argument on native. + return syscall(SYS_preadv2, fd, iov, iovcnt, offset, 0, flags); +} + // This test is the base case where we call preadv (no offset, no flags). TEST(Preadv2Test, TestBaseCall) { - if (!IsRunningOnGvisor()) { - SKIP_BEFORE_KERNEL(/*major_version=*/4, /*minor_version=*/6); - } + SKIP_IF(preadv2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS); + std::string content = SetContent(); const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( @@ -73,12 +74,13 @@ TEST(Preadv2Test, TestBaseCall) { ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY)); std::vector<char> buf(kBufSize); - struct iovec iov; - iov.iov_base = buf.data(); - iov.iov_len = buf.size(); + struct iovec iov[2]; + iov[0].iov_base = buf.data(); + iov[0].iov_len = buf.size() / 2; + iov[1].iov_base = static_cast<char*>(iov[0].iov_base) + (content.size() / 2); + iov[1].iov_len = content.size() / 2; - EXPECT_THAT(syscall(SYS_preadv2, fd.get(), &iov, /*iov_cnt*/ 1, - /*offset=*/0, /*flags=*/0), + EXPECT_THAT(preadv2(fd.get(), iov, /*iovcnt*/ 2, /*offset=*/0, /*flags=*/0), SyscallSucceedsWithValue(kBufSize)); EXPECT_EQ(content, std::string(buf.data(), buf.size())); @@ -86,9 +88,8 @@ TEST(Preadv2Test, TestBaseCall) { // This test is where we call preadv with an offset and no flags. TEST(Preadv2Test, TestValidPositiveOffset) { - if (!IsRunningOnGvisor()) { - SKIP_BEFORE_KERNEL(/*major_version=*/4, /*minor_version=*/6); - } + SKIP_IF(preadv2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS); + std::string content = SetContent(); const std::string prefix = "0"; @@ -102,10 +103,12 @@ TEST(Preadv2Test, TestValidPositiveOffset) { iov.iov_base = buf.data(); iov.iov_len = buf.size(); - EXPECT_THAT(syscall(SYS_preadv2, fd.get(), &iov, /*iov_cnt=*/1, - /*offset=*/prefix.size(), /*flags=*/0), + EXPECT_THAT(preadv2(fd.get(), &iov, /*iovcnt=*/1, /*offset=*/prefix.size(), + /*flags=*/0), SyscallSucceedsWithValue(kBufSize)); + EXPECT_THAT(lseek(fd.get(), 0, SEEK_CUR), SyscallSucceedsWithValue(0)); + EXPECT_EQ(content, std::string(buf.data(), buf.size())); } @@ -113,9 +116,8 @@ TEST(Preadv2Test, TestValidPositiveOffset) { // read should use the file offset, so the test increments it by one prior to // calling preadv2. TEST(Preadv2Test, TestNegativeOneOffset) { - if (!IsRunningOnGvisor()) { - SKIP_BEFORE_KERNEL(/*major_version=*/4, /*minor_version=*/6); - } + SKIP_IF(preadv2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS); + std::string content = SetContent(); const std::string prefix = "231"; @@ -123,6 +125,7 @@ TEST(Preadv2Test, TestNegativeOneOffset) { GetAbsoluteTestTmpdir(), prefix + content, TempPath::kDefaultFileMode)); const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY)); + ASSERT_THAT(lseek(fd.get(), prefix.size(), SEEK_SET), SyscallSucceedsWithValue(prefix.size())); @@ -131,79 +134,111 @@ TEST(Preadv2Test, TestNegativeOneOffset) { iov.iov_base = buf.data(); iov.iov_len = buf.size(); - EXPECT_THAT(syscall(SYS_preadv2, fd.get(), &iov, /*iov_cnt=*/1, - /*offset=*/static_cast<off_t>(-1), /*flags=*/0), + EXPECT_THAT(preadv2(fd.get(), &iov, /*iovcnt=*/1, /*offset=*/-1, /*flags=*/0), SyscallSucceedsWithValue(kBufSize)); + EXPECT_THAT(lseek(fd.get(), 0, SEEK_CUR), + SyscallSucceedsWithValue(prefix.size() + buf.size())); + EXPECT_EQ(content, std::string(buf.data(), buf.size())); } +// preadv2 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(Preadv2Test, TestCallWithRWF_HIPRI) { + SKIP_IF(preadv2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS); + + std::string content = SetContent(); + + const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( + GetAbsoluteTestTmpdir(), content, TempPath::kDefaultFileMode)); + const FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY)); + + EXPECT_THAT(fsync(fd.get()), SyscallSucceeds()); + + std::vector<char> buf(kBufSize, '0'); + struct iovec iov; + iov.iov_base = buf.data(); + iov.iov_len = buf.size(); + + EXPECT_THAT( + preadv2(fd.get(), &iov, /*iovcnt=*/1, /*offset=*/0, /*flags=*/RWF_HIPRI), + SyscallSucceedsWithValue(kBufSize)); + + EXPECT_THAT(lseek(fd.get(), 0, SEEK_CUR), SyscallSucceedsWithValue(0)); + + EXPECT_EQ(content, std::string(buf.data(), buf.size())); +} // This test calls preadv2 with an invalid flag. TEST(Preadv2Test, TestInvalidFlag) { - if (!IsRunningOnGvisor()) { - SKIP_BEFORE_KERNEL(/*major_version=*/4, /*minor_version=*/6); - } + SKIP_IF(preadv2(-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 | O_DIRECT)); + std::vector<char> buf(kBufSize, '0'); struct iovec iov; + iov.iov_base = buf.data(); + iov.iov_len = buf.size(); - EXPECT_THAT(syscall(SYS_preadv2, fd.get(), &iov, /*iov_cnt=*/1, - /*offset=*/0, /*flags=*/RWF_HIPRI << 1), - SyscallFailsWithErrno(EINVAL)); + EXPECT_THAT(preadv2(fd.get(), &iov, /*iovcnt=*/1, + /*offset=*/0, /*flags=*/0xF0), + SyscallFailsWithErrno(EOPNOTSUPP)); } // This test calls preadv2 with an invalid offset. TEST(Preadv2Test, TestInvalidOffset) { - if (!IsRunningOnGvisor()) { - SKIP_BEFORE_KERNEL(/*major_version=*/4, /*minor_version=*/6); - } + SKIP_IF(preadv2(-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 | O_DIRECT)); - struct iovec iov; - EXPECT_THAT(syscall(SYS_preadv2, fd.get(), &iov, /*iov_cnt=*/1, - /*offset=*/static_cast<off_t>(-8), /*flags=*/RWF_HIPRI), + auto iov = absl::make_unique<struct iovec[]>(1); + iov[0].iov_base = nullptr; + iov[0].iov_len = 0; + + EXPECT_THAT(preadv2(fd.get(), iov.get(), /*iovcnt=*/1, /*offset=*/-8, + /*flags=*/RWF_HIPRI), SyscallFailsWithErrno(EINVAL)); } // This test calls preadv with a file set O_WRONLY. TEST(Preadv2Test, TestUnreadableFile) { - if (!IsRunningOnGvisor()) { - SKIP_BEFORE_KERNEL(/*major_version=*/4, /*minor_version=*/6); - } + SKIP_IF(preadv2(-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_WRONLY)); - struct iovec iov; - EXPECT_THAT(syscall(SYS_preadv2, fd.get(), &iov, /*iov_cnt=*/1, + auto iov = absl::make_unique<struct iovec[]>(1); + iov[0].iov_base = nullptr; + iov[0].iov_len = 0; + + EXPECT_THAT(preadv2(fd.get(), iov.get(), /*iovcnt=*/1, /*offset=*/0, /*flags=*/0), SyscallFailsWithErrno(EBADF)); } // Calling preadv2 with a non-negative offset calls preadv. Calling preadv with // an unseekable file is not allowed. A pipe is used for an unseekable file. -TEST(Preadv2Test, TestUnseekableFile) { - if (!IsRunningOnGvisor()) { - SKIP_BEFORE_KERNEL(/*major_version=*/4, /*minor_version=*/6); - } +TEST(Preadv2Test, TestUnseekableFileInvalid) { + SKIP_IF(preadv2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS); int pipe_fds[2]; ASSERT_THAT(pipe(pipe_fds), SyscallSucceeds()); - struct iovec iov; + auto iov = absl::make_unique<struct iovec[]>(1); + iov[0].iov_base = nullptr; + iov[0].iov_len = 0; - EXPECT_THAT(syscall(SYS_preadv2, pipe_fds[0], &iov, /*iov_cnt=*/1, + EXPECT_THAT(preadv2(pipe_fds[0], iov.get(), /*iovcnt=*/1, /*offset=*/2, /*flags=*/0), SyscallFailsWithErrno(ESPIPE)); @@ -211,6 +246,33 @@ TEST(Preadv2Test, TestUnseekableFile) { EXPECT_THAT(close(pipe_fds[1]), SyscallSucceeds()); } +TEST(Preadv2Test, TestUnseekableFileValid) { + SKIP_IF(preadv2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS); + + int pipe_fds[2]; + + ASSERT_THAT(pipe(pipe_fds), SyscallSucceeds()); + + std::vector<char> content(32, 'X'); + + EXPECT_THAT(write(pipe_fds[1], content.data(), content.size()), + SyscallSucceedsWithValue(content.size())); + + std::vector<char> buf(content.size()); + auto iov = absl::make_unique<struct iovec[]>(1); + iov[0].iov_base = buf.data(); + iov[0].iov_len = buf.size(); + + EXPECT_THAT(preadv2(pipe_fds[0], iov.get(), /*iovcnt=*/1, + /*offset=*/static_cast<off_t>(-1), /*flags=*/0), + SyscallSucceedsWithValue(buf.size())); + + EXPECT_EQ(content, buf); + + EXPECT_THAT(close(pipe_fds[0]), SyscallSucceeds()); + EXPECT_THAT(close(pipe_fds[1]), SyscallSucceeds()); +} + } // namespace } // namespace testing diff --git a/test/syscalls/linux/pwritev2.cc b/test/syscalls/linux/pwritev2.cc new file mode 100644 index 000000000..a6949f08e --- /dev/null +++ b/test/syscalls/linux/pwritev2.cc @@ -0,0 +1,337 @@ +// Copyright 2018 Google LLC +// +// 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 "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 +#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, TestBaseCall) { + 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, TestValidPositiveOffset) { + 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, TestNegativeOneOffset) { + 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, TestCallWithRWF_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 checks that pwritev2 can be called with valid flags +TEST(Pwritev2Test, TestCallWithValidFlags) { + 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, '0'); + 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_DSYNC), + SyscallSucceedsWithValue(kBufSize)); + + std::vector<char> buf(content.size()); + EXPECT_THAT(read(fd.get(), buf.data(), buf.size()), + SyscallSucceedsWithValue(buf.size())); + + EXPECT_EQ(buf, content); + + SetContent(content); + + EXPECT_THAT(pwritev2(fd.get(), &iov, /*iovcnt=*/1, + /*offset=*/0, /*flags=*/0x4), + SyscallSucceedsWithValue(kBufSize)); + + ASSERT_THAT(lseek(fd.get(), 0, SEEK_CUR), + SyscallSucceedsWithValue(content.size())); + + EXPECT_THAT(pread(fd.get(), buf.data(), buf.size(), /*offset=*/0), + SyscallSucceedsWithValue(buf.size())); + + EXPECT_EQ(buf, content); +} + +// This test calls pwritev2 with a bad file descriptor. +TEST(Writev2Test, TestBadFile) { + 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, TestInvalidOffset) { + 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)); + + struct iovec iov; + iov.iov_base = nullptr; + + EXPECT_THAT(pwritev2(fd.get(), &iov, /*iovcnt=*/1, + /*offset=*/static_cast<off_t>(-8), /*flags=*/0), + SyscallFailsWithErrno(EINVAL)); +} + +TEST(Pwritev2Test, TestUnseekableFileValid) { + 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, TestUnseekableFileInValid) { + SKIP_IF(pwritev2(-1, nullptr, 0, 0, 0) < 0 && errno == ENOSYS); + + int pipe_fds[2]; + struct iovec iov; + iov.iov_base = nullptr; + + 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, TestReadOnlyFile) { + 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)); + + struct iovec iov; + iov.iov_base = nullptr; + + EXPECT_THAT(pwritev2(fd.get(), &iov, /*iovcnt=*/1, + /*offset=*/0, /*flags=*/0), + SyscallFailsWithErrno(EBADF)); +} + +// This test calls pwritev2 with an invalid flag. +TEST(Pwritev2Test, TestInvalidFlag) { + 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)); + + struct iovec iov; + iov.iov_base = nullptr; + + EXPECT_THAT(pwritev2(fd.get(), &iov, /*iovcnt=*/1, + /*offset=*/0, /*flags=*/0xF0), + SyscallFailsWithErrno(EOPNOTSUPP)); +} + +} // namespace +} // namespace testing +} // namespace gvisor |