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 | |
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
-rw-r--r-- | pkg/abi/linux/file.go | 7 | ||||
-rw-r--r-- | pkg/sentry/syscalls/linux/linux64.go | 2 | ||||
-rw-r--r-- | pkg/sentry/syscalls/linux/sys_read.go | 22 | ||||
-rw-r--r-- | pkg/sentry/syscalls/linux/sys_write.go | 66 | ||||
-rw-r--r-- | test/syscalls/BUILD | 2 | ||||
-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 | ||||
-rw-r--r-- | test/util/test_util.h | 7 |
9 files changed, 553 insertions, 73 deletions
diff --git a/pkg/abi/linux/file.go b/pkg/abi/linux/file.go index ac49ae9a6..ae33f4a4d 100644 --- a/pkg/abi/linux/file.go +++ b/pkg/abi/linux/file.go @@ -152,9 +152,10 @@ const ( // Values for preadv2/pwritev2. const ( - RWF_HIPRI = 0x0001 - RWF_DSYNC = 0X0002 - RWF_SYNC = 0x0004 + RWF_HIPRI = 0x00000001 + RWF_DSYNC = 0x00000002 + RWF_SYNC = 0x00000004 + RWF_VALID = RWF_HIPRI | RWF_DSYNC | RWF_SYNC ) // Stat represents struct stat. diff --git a/pkg/sentry/syscalls/linux/linux64.go b/pkg/sentry/syscalls/linux/linux64.go index cc5ebb955..e855590e6 100644 --- a/pkg/sentry/syscalls/linux/linux64.go +++ b/pkg/sentry/syscalls/linux/linux64.go @@ -377,7 +377,7 @@ var AMD64 = &kernel.SyscallTable{ // Syscalls after 325 are "backports" from versions of Linux after 4.4. // 326: CopyFileRange, 327: Preadv2, - // 328: Pwritev2, // Pwritev2, TODO + 328: Pwritev2, }, Emulate: map[usermem.Addr]uintptr{ diff --git a/pkg/sentry/syscalls/linux/sys_read.go b/pkg/sentry/syscalls/linux/sys_read.go index cbb9eb9f8..b6df4d9d4 100644 --- a/pkg/sentry/syscalls/linux/sys_read.go +++ b/pkg/sentry/syscalls/linux/sys_read.go @@ -188,14 +188,20 @@ func Preadv(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscal } // Preadv2 implements linux syscall preadv2(2). +// TODO: Implement RWF_HIPRI functionality. func Preadv2(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { + // While the syscall is + // preadv2(int fd, struct iovec* iov, int iov_cnt, off_t offset, int flags) + // the linux internal call + // (https://elixir.bootlin.com/linux/v4.18/source/fs/read_write.c#L1248) + // splits the offset argument into a high/low value for compatibility with + // 32-bit architectures. The flags argument is the 5th argument. + fd := kdefs.FD(args[0].Int()) addr := args[1].Pointer() iovcnt := int(args[2].Int()) offset := args[3].Int64() - flags := int(args[4].Int()) - - validFlags := linux.RWF_HIPRI + flags := int(args[5].Int()) file := t.FDMap().GetFile(fd) if file == nil { @@ -219,14 +225,8 @@ func Preadv2(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sysca } // Check flags field. - if flags != 0 { - if flags&^validFlags != 0 { - return 0, nil, syserror.EINVAL - } - // RWF_HIPRI must be called on a file with O_DIRECT flag set. - if flags&linux.RWF_HIPRI != 0 && !file.Flags().Direct { - return 0, nil, syserror.EINVAL - } + if flags&^linux.RWF_VALID != 0 { + return 0, nil, syserror.EOPNOTSUPP } // Read the iovecs that specify the destination of the read. diff --git a/pkg/sentry/syscalls/linux/sys_write.go b/pkg/sentry/syscalls/linux/sys_write.go index 08e263112..750a098cd 100644 --- a/pkg/sentry/syscalls/linux/sys_write.go +++ b/pkg/sentry/syscalls/linux/sys_write.go @@ -187,6 +187,72 @@ func Pwritev(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Sysca return uintptr(n), nil, handleIOError(t, n != 0, err, kernel.ERESTARTSYS, "pwritev", file) } +// Pwritev2 implements linux syscall pwritev2(2). +// TODO: Implement RWF_HIPRI functionality. +// TODO: Implement O_SYNC and D_SYNC functionality. +func Pwritev2(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { + // While the syscall is + // pwritev2(int fd, struct iovec* iov, int iov_cnt, off_t offset, int flags) + // the linux internal call + // (https://elixir.bootlin.com/linux/v4.18/source/fs/read_write.c#L1354) + // splits the offset argument into a high/low value for compatibility with + // 32-bit architectures. The flags argument is the 5th argument. + + fd := kdefs.FD(args[0].Int()) + addr := args[1].Pointer() + iovcnt := int(args[2].Int()) + offset := args[3].Int64() + flags := int(args[5].Int()) + + if int(args[4].Int())&0x4 == 1 { + return 0, nil, syserror.EACCES + } + + file := t.FDMap().GetFile(fd) + if file == nil { + return 0, nil, syserror.EBADF + } + defer file.DecRef() + + // Check that the offset is legitimate. + if offset < -1 { + return 0, nil, syserror.EINVAL + } + + // Is writing at an offset supported? + if offset > -1 && !file.Flags().Pwrite { + return 0, nil, syserror.ESPIPE + } + + if flags&^linux.RWF_VALID != 0 { + return uintptr(flags), nil, syserror.EOPNOTSUPP + } + + // Check that the file is writeable. + if !file.Flags().Write { + return 0, nil, syserror.EBADF + } + + // Read the iovecs that specify the source of the write. + src, err := t.IovecsIOSequence(addr, iovcnt, usermem.IOOpts{ + AddressSpaceActive: true, + }) + if err != nil { + return 0, nil, err + } + + // If pwritev2 is called with an offset of -1, writev is called. + if offset == -1 { + n, err := writev(t, file, src) + t.IOUsage().AccountWriteSyscall(n) + return uintptr(n), nil, handleIOError(t, n != 0, err, kernel.ERESTARTSYS, "pwritev2", file) + } + + n, err := pwritev(t, file, src, offset) + t.IOUsage().AccountWriteSyscall(n) + return uintptr(n), nil, handleIOError(t, n != 0, err, kernel.ERESTARTSYS, "pwritev2", file) +} + func writev(t *kernel.Task, f *fs.File, src usermem.IOSequence) (int64, error) { n, err := f.Writev(t, src) if err != syserror.ErrWouldBlock || f.Flags().NonBlocking { diff --git a/test/syscalls/BUILD b/test/syscalls/BUILD index 711b68c76..12c7049e7 100644 --- a/test/syscalls/BUILD +++ b/test/syscalls/BUILD @@ -219,6 +219,8 @@ syscall_test( test = "//test/syscalls/linux:pty_test", ) +syscall_test(test = "//test/syscalls/linux:pwritev2_test") + syscall_test(test = "//test/syscalls/linux:pwrite64_test") syscall_test(test = "//test/syscalls/linux:read_test") 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 diff --git a/test/util/test_util.h b/test/util/test_util.h index 2a7609e5c..cd71fdd64 100644 --- a/test/util/test_util.h +++ b/test/util/test_util.h @@ -217,13 +217,6 @@ void TestInit(int* argc, char*** argv); } \ } while (0) -#define SKIP_BEFORE_KERNEL(maj, min) \ - do { \ - auto version = ASSERT_NO_ERRNO_AND_VALUE(GetKernelVersion()); \ - SKIP_IF(version.major < (maj) || \ - (version.major == (maj) && version.minor < (min))); \ - } while (0) - enum class Platform { kNative, kKVM, |